[CalendarServer-changes] [15430] CalendarServer/trunk
source_changes at macosforge.org
source_changes at macosforge.org
Fri Jan 8 13:03:23 PST 2016
Revision: 15430
http://trac.calendarserver.org//changeset/15430
Author: sagen at apple.com
Date: 2016-01-08 13:03:23 -0800 (Fri, 08 Jan 2016)
Log Message:
-----------
Refactoring of calendar-specific code from contrib.performance.loadtest. Requires use of ClientLoadSimulator project.
Added Paths:
-----------
CalendarServer/trunk/bin/newsim
CalendarServer/trunk/simplugin/
CalendarServer/trunk/simplugin/__init__.py
CalendarServer/trunk/simplugin/caldavclient.py
CalendarServer/trunk/simplugin/caldavprofile.py
CalendarServer/trunk/simplugin/clients.plist
CalendarServer/trunk/simplugin/config.plist
CalendarServer/trunk/simplugin/request-data/
CalendarServer/trunk/simplugin/request-data/OS_X_10_11/
CalendarServer/trunk/simplugin/request-data/OS_X_10_11/Profile
CalendarServer/trunk/simplugin/request-data/OS_X_10_11/StartupProfile
CalendarServer/trunk/simplugin/request-data/OS_X_10_11/notification_multiget_report_hrefs.request
CalendarServer/trunk/simplugin/request-data/OS_X_10_11/notification_sync.request
CalendarServer/trunk/simplugin/request-data/OS_X_10_11/poll_calendar_depth1_propfind.request
CalendarServer/trunk/simplugin/request-data/OS_X_10_11/poll_calendar_propfind.request
CalendarServer/trunk/simplugin/request-data/OS_X_10_11/poll_calendarhome_depth1_propfind.request
CalendarServer/trunk/simplugin/request-data/OS_X_10_11/poll_calendarhome_sync.request
CalendarServer/trunk/simplugin/request-data/OS_X_10_11/poll_notification_depth1_propfind.request
CalendarServer/trunk/simplugin/request-data/OS_X_10_11/post_freebusy.request
CalendarServer/trunk/simplugin/request-data/OS_X_10_11/principal_search_report.request
CalendarServer/trunk/simplugin/request-data/OS_X_10_11/report_principal_search.request
CalendarServer/trunk/simplugin/request-data/OS_X_10_11/startup_calendar_color_proppatch.request
CalendarServer/trunk/simplugin/request-data/OS_X_10_11/startup_calendar_description_proppatch.request.xml
CalendarServer/trunk/simplugin/request-data/OS_X_10_11/startup_calendar_displayname_proppatch.request
CalendarServer/trunk/simplugin/request-data/OS_X_10_11/startup_calendar_order_proppatch.request
CalendarServer/trunk/simplugin/request-data/OS_X_10_11/startup_calendar_timezone_proppatch.request
CalendarServer/trunk/simplugin/request-data/OS_X_10_11/startup_calendar_transparent_proppatch.request
CalendarServer/trunk/simplugin/request-data/OS_X_10_11/startup_calendarhome_default_alarm_date_proppatch.request
CalendarServer/trunk/simplugin/request-data/OS_X_10_11/startup_calendarhome_default_alarm_datetime_proppatch.request
CalendarServer/trunk/simplugin/request-data/OS_X_10_11/startup_create_calendar.request
CalendarServer/trunk/simplugin/request-data/OS_X_10_11/startup_delegate_principal_propfind.request
CalendarServer/trunk/simplugin/request-data/OS_X_10_11/startup_principal_expand.request
CalendarServer/trunk/simplugin/request-data/OS_X_10_11/startup_principal_initial_propfind.request
CalendarServer/trunk/simplugin/request-data/OS_X_10_11/startup_principal_propfind.request
CalendarServer/trunk/simplugin/request-data/OS_X_10_11/startup_principals_report.request
CalendarServer/trunk/simplugin/request-data/OS_X_10_11/startup_query_events_depth1_report.request
CalendarServer/trunk/simplugin/request-data/OS_X_10_11/startup_well_known_propfind.request
CalendarServer/trunk/simplugin/request-data/OS_X_10_6/
CalendarServer/trunk/simplugin/request-data/OS_X_10_6/poll_calendar_multiget.request
CalendarServer/trunk/simplugin/request-data/OS_X_10_6/poll_calendar_multiget_hrefs.request
CalendarServer/trunk/simplugin/request-data/OS_X_10_6/poll_calendar_propfind.request
CalendarServer/trunk/simplugin/request-data/OS_X_10_6/poll_calendar_propfind_d1.request
CalendarServer/trunk/simplugin/request-data/OS_X_10_6/poll_calendarhome_propfind.request
CalendarServer/trunk/simplugin/request-data/OS_X_10_6/poll_notification_propfind_d1.request
CalendarServer/trunk/simplugin/request-data/OS_X_10_6/post_availability.request
CalendarServer/trunk/simplugin/request-data/OS_X_10_6/startup_calendar_color_proppatch.request
CalendarServer/trunk/simplugin/request-data/OS_X_10_6/startup_calendar_order_proppatch.request
CalendarServer/trunk/simplugin/request-data/OS_X_10_6/startup_calendar_timezone_proppatch.request
CalendarServer/trunk/simplugin/request-data/OS_X_10_6/startup_notification_propfind.request
CalendarServer/trunk/simplugin/request-data/OS_X_10_6/startup_principal_expand.request
CalendarServer/trunk/simplugin/request-data/OS_X_10_6/startup_principal_propfind.request
CalendarServer/trunk/simplugin/request-data/OS_X_10_6/startup_principal_propfind_initial.request
CalendarServer/trunk/simplugin/request-data/OS_X_10_6/startup_principals_report.request
CalendarServer/trunk/simplugin/request-data/OS_X_10_6/startup_well_known.request
CalendarServer/trunk/simplugin/request-data/OS_X_10_6/user_list_principal_property_search.request
CalendarServer/trunk/simplugin/request-data/OS_X_10_7/
CalendarServer/trunk/simplugin/request-data/OS_X_10_7/Profile
CalendarServer/trunk/simplugin/request-data/OS_X_10_7/poll_calendar_multiget.request
CalendarServer/trunk/simplugin/request-data/OS_X_10_7/poll_calendar_multiget_hrefs.request
CalendarServer/trunk/simplugin/request-data/OS_X_10_7/poll_calendar_propfind.request
CalendarServer/trunk/simplugin/request-data/OS_X_10_7/poll_calendar_propfind_d1.request
CalendarServer/trunk/simplugin/request-data/OS_X_10_7/poll_calendar_sync.request
CalendarServer/trunk/simplugin/request-data/OS_X_10_7/poll_calendarhome_propfind.request
CalendarServer/trunk/simplugin/request-data/OS_X_10_7/poll_notification_propfind_d1.request
CalendarServer/trunk/simplugin/request-data/OS_X_10_7/post_availability.request
CalendarServer/trunk/simplugin/request-data/OS_X_10_7/startup_calendar_color_proppatch.request
CalendarServer/trunk/simplugin/request-data/OS_X_10_7/startup_calendar_order_proppatch.request
CalendarServer/trunk/simplugin/request-data/OS_X_10_7/startup_calendar_timezone_proppatch.request
CalendarServer/trunk/simplugin/request-data/OS_X_10_7/startup_delegate_principal_propfind.request
CalendarServer/trunk/simplugin/request-data/OS_X_10_7/startup_principal_expand.request
CalendarServer/trunk/simplugin/request-data/OS_X_10_7/startup_principal_propfind.request
CalendarServer/trunk/simplugin/request-data/OS_X_10_7/startup_principal_propfind_initial.request
CalendarServer/trunk/simplugin/request-data/OS_X_10_7/startup_principals_report.request
CalendarServer/trunk/simplugin/request-data/OS_X_10_7/startup_well_known.request
CalendarServer/trunk/simplugin/request-data/OS_X_10_7/user_list_principal_property_search.request
CalendarServer/trunk/simplugin/request-data/iOS_5/
CalendarServer/trunk/simplugin/request-data/iOS_5/Profile
CalendarServer/trunk/simplugin/request-data/iOS_5/poll_calendar_multiget.request
CalendarServer/trunk/simplugin/request-data/iOS_5/poll_calendar_multiget_hrefs.request
CalendarServer/trunk/simplugin/request-data/iOS_5/poll_calendar_propfind.request
CalendarServer/trunk/simplugin/request-data/iOS_5/poll_calendar_propfind_d1.request
CalendarServer/trunk/simplugin/request-data/iOS_5/poll_calendar_vevent_tr_query.request
CalendarServer/trunk/simplugin/request-data/iOS_5/poll_calendar_vtodo_query.request
CalendarServer/trunk/simplugin/request-data/iOS_5/poll_calendarhome_propfind.request
CalendarServer/trunk/simplugin/request-data/iOS_5/startup_calendar_color_proppatch.request
CalendarServer/trunk/simplugin/request-data/iOS_5/startup_calendar_order_proppatch.request
CalendarServer/trunk/simplugin/request-data/iOS_5/startup_principal_propfind.request
CalendarServer/trunk/simplugin/request-data/iOS_5/startup_principal_propfind_initial.request
CalendarServer/trunk/simplugin/request-data/iOS_5/startup_principals_report.request
CalendarServer/trunk/simplugin/request-data/iOS_5/startup_well_known.request
Added: CalendarServer/trunk/bin/newsim
===================================================================
--- CalendarServer/trunk/bin/newsim (rev 0)
+++ CalendarServer/trunk/bin/newsim 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1,30 @@
+#!/bin/sh
+# -*- sh-basic-offset: 2 -*-
+
+##
+# Copyright (c) 2005-2016 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)";
+
+. "${wd}/bin/_build.sh";
+
+do_setup="false";
+develop > /dev/null;
+
+exec "${python}" "${wd}/clientsim/framework/sim.py" "--config=simplugin/config.plist" "--clients=simplugin/clients.plist" "$@";
Property changes on: CalendarServer/trunk/bin/newsim
___________________________________________________________________
Added: svn:executable
+ *
Added: CalendarServer/trunk/simplugin/__init__.py
===================================================================
--- CalendarServer/trunk/simplugin/__init__.py (rev 0)
+++ CalendarServer/trunk/simplugin/__init__.py 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1,15 @@
+##
+# Copyright (c) 2012-2016 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.
+##
Added: CalendarServer/trunk/simplugin/caldavclient.py
===================================================================
--- CalendarServer/trunk/simplugin/caldavclient.py (rev 0)
+++ CalendarServer/trunk/simplugin/caldavclient.py 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1,2326 @@
+##
+# Copyright (c) 2010-2016 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 __future__ import print_function
+
+from caldavclientlibrary.protocol.caldav.definitions import caldavxml
+from caldavclientlibrary.protocol.caldav.definitions import csxml
+from caldavclientlibrary.protocol.calendarserver.invite import AddInvitees, RemoveInvitee, InviteUser
+from caldavclientlibrary.protocol.calendarserver.notifications import InviteNotification
+from caldavclientlibrary.protocol.url import URL
+from caldavclientlibrary.protocol.utils.xmlhelpers import BetterElementTree
+from caldavclientlibrary.protocol.webdav.definitions import davxml
+from caldavclientlibrary.protocol.webdav.propfindparser import PropFindParser
+
+from push.amppush import subscribeToIDs
+
+from clientsim.framework.httpclient import StringProducer, readBody
+from clientsim.framework.subscribe import Periodical
+
+from clientsim.framework.baseclient import (
+ IncorrectResponseCode, u2str, BaseClient
+)
+
+from pycalendar.datetime import DateTime
+from pycalendar.duration import Duration
+from pycalendar.timezone import Timezone
+
+from twisted.internet.defer import Deferred, inlineCallbacks, returnValue, \
+ succeed
+from twisted.internet.task import LoopingCall
+from twisted.python.log import msg
+from twisted.python.util import FancyEqMixin
+from twisted.web.http import (
+ OK, MULTI_STATUS, CREATED, NO_CONTENT, PRECONDITION_FAILED,
+ MOVED_PERMANENTLY, FORBIDDEN, FOUND, NOT_FOUND
+)
+from twisted.web.http_headers import Headers
+
+from twistedcaldav.ical import Component, Property
+
+from urlparse import urlparse, urlsplit, urljoin
+from uuid import uuid4
+from xml.etree.ElementTree import ElementTree, Element, SubElement, QName
+
+from StringIO import StringIO
+
+import json
+import os
+import random
+import shutil
+
+from twisted.python.filepath import FilePath
+
+
+QName.__repr__ = lambda self: '<QName %r>' % (self.text,)
+
+
+
+SUPPORTED_REPORT_SET = '{DAV:}supported-report-set'
+
+def loadRequestBody(clientType, label):
+ return FilePath(__file__).sibling('request-data').child(clientType).child(label + '.request').getContent()
+
+
+class MissingCalendarHome(Exception):
+ """
+ Raised when the calendar home for a user is 404
+ """
+
+
+
+class XMPPPush(object, FancyEqMixin):
+ """
+ This represents an XMPP PubSub location where push notifications for
+ particular calendar home might be received.
+ """
+ compareAttributes = ('server', 'uri', 'pushkey')
+
+ def __init__(self, server, uri, pushkey):
+ self.server = server
+ self.uri = uri
+ self.pushkey = pushkey
+
+
+
+
+class Event(object):
+ def __init__(self, serializeBasePath, url, etag, component=None):
+ self.serializeBasePath = serializeBasePath
+ self.url = url
+ self.etag = etag
+ self.scheduleTag = None
+ if component is not None:
+ self.component = component
+ self.uid = component.resourceUID() if component is not None else None
+
+
+ def getUID(self):
+ """
+ Return the UID of the calendar resource.
+ """
+ return self.uid
+
+
+ def serializePath(self):
+ if self.serializeBasePath:
+ calendar = os.path.join(self.serializeBasePath, self.url.split("/")[-2])
+ if not os.path.exists(calendar):
+ os.makedirs(calendar)
+ return os.path.join(calendar, self.url.split("/")[-1])
+ else:
+ return None
+
+
+ def serialize(self):
+ """
+ Create a dict of the data so we can serialize as JSON.
+ """
+
+ result = {}
+ for attr in ("url", "etag", "scheduleTag", "uid",):
+ result[attr] = getattr(self, attr)
+ return result
+
+
+ @staticmethod
+ def deserialize(serializeLocation, data):
+ """
+ Convert dict (deserialized from JSON) into an L{Event}.
+ """
+
+ event = Event(serializeLocation, None, None)
+ for attr in ("url", "etag", "scheduleTag", "uid",):
+ setattr(event, attr, u2str(data[attr]))
+ return event
+
+
+ @property
+ def component(self):
+ """
+ Data always read from disk - never cached in the object.
+ """
+ path = self.serializePath()
+ if path and os.path.exists(path):
+ with open(path) as f:
+ comp = Component.fromString(f.read())
+ return comp
+ else:
+ return None
+
+
+ @component.setter
+ def component(self, component):
+ """
+ Data always written to disk - never cached on the object.
+ """
+ path = self.serializePath()
+ if path:
+ if component is None:
+ os.remove(path)
+ else:
+ with open(path, "w") as f:
+ f.write(str(component))
+ self.uid = component.resourceUID() if component is not None else None
+
+
+ def removed(self):
+ """
+ Resource no longer exists on the server - remove associated data.
+ """
+ path = self.serializePath()
+ if path and os.path.exists(path):
+ os.remove(path)
+
+
+
+class Calendar(object):
+ def __init__(
+ self, resourceType, componentTypes, name, url, changeToken,
+ shared=False, sharedByMe=False
+ ):
+ self.resourceType = resourceType
+ self.componentTypes = componentTypes
+ self.name = name
+ self.url = url
+ self.changeToken = changeToken
+ self.events = {}
+ self.shared = shared
+ self.sharedByMe = sharedByMe
+
+ if self.name is None and self.url is not None:
+ self.name = self.url.rstrip("/").split("/")[-1]
+
+
+ def serialize(self):
+ """
+ Create a dict of the data so we can serialize as JSON.
+ """
+
+ result = {}
+ for attr in ("resourceType", "name", "url", "changeToken", "shared", "sharedByMe"):
+ result[attr] = getattr(self, attr)
+ result["componentTypes"] = list(sorted(self.componentTypes))
+ result["events"] = sorted(self.events.keys())
+ return result
+
+
+ @staticmethod
+ def deserialize(data, events):
+ """
+ Convert dict (deserialized from JSON) into an L{Calendar}.
+ """
+
+ calendar = Calendar(None, None, None, None, None)
+ for attr in ("resourceType", "name", "url", "changeToken", "shared", "sharedByMe"):
+ setattr(calendar, attr, u2str(data[attr]))
+ calendar.componentTypes = set(map(u2str, data["componentTypes"]))
+
+ for event in data["events"]:
+ url = urljoin(calendar.url, event)
+ if url in events:
+ calendar.events[event] = events[url]
+ else:
+ # Ughh - an event is missing - force changeToken to empty to trigger full resync
+ calendar.changeToken = ""
+ return calendar
+
+
+ @staticmethod
+ def addInviteeXML(uid, summary, readwrite=True):
+ return AddInvitees(None, '/', [uid], readwrite, summary=summary).request_data.text
+
+
+ @staticmethod
+ def removeInviteeXML(uid):
+ invitee = InviteUser()
+ # Usually an InviteUser is populated through .parseFromUser, but we only care about a uid
+ invitee.user_uid = uid
+ return RemoveInvitee(None, '/', invitee).request_data.text
+
+
+
+class NotificationCollection(object):
+ def __init__(self, url, changeToken):
+ self.url = url
+ self.changeToken = changeToken
+ self.notifications = {}
+ self.name = "notification"
+
+
+ def serialize(self):
+ """
+ Create a dict of the data so we can serialize as JSON.
+ """
+
+ result = {}
+ for attr in ("url", "changeToken"):
+ result[attr] = getattr(self, attr)
+ result["notifications"] = sorted(self.notifications.keys())
+ return result
+
+
+ @staticmethod
+ def deserialize(data, notifications):
+ """
+ Convert dict (deserialized from JSON) into an L{Calendar}.
+ """
+
+ coll = NotificationCollection(None, None)
+ for attr in ("url", "changeToken"):
+ if attr in data:
+ setattr(coll, attr, u2str(data[attr]))
+
+ if "notifications" in data:
+ for notification in data["notifications"]:
+ url = urljoin(coll.url, notification)
+ if url in notifications:
+ coll.notifications[notification] = notifications[url]
+ else:
+ # Ughh - a notification is missing - force changeToken to empty to trigger full resync
+ coll.changeToken = ""
+ return coll
+
+
+
+
+
+class BaseAppleClient(BaseClient):
+ """
+ Implementation of common OS X/iOS client behavior.
+ """
+
+ _events = None # Cache of events keyed by href
+ _calendars = None # Cache of calendars keyed by href
+ _notificationCollection = None # Cache of the notification collection
+
+ # The default interval, used if none is specified in external
+ # configuration.
+ CALENDAR_HOME_POLL_INTERVAL = 15 * 60
+
+ # The maximum number of resources to retrieve in a single multiget
+ MULTIGET_BATCH_SIZE = 200
+
+ # Override and turn on if client supports Sync REPORT
+ _SYNC_REPORT = False
+
+ # Override and turn on if client syncs using time-range queries
+ _SYNC_TIMERANGE = False
+
+ # Override and turn off if client does not support attendee lookups
+ _ATTENDEE_LOOKUPS = True
+
+ # Request body data
+ _LOAD_PATH = None
+
+ _STARTUP_WELL_KNOWN = None
+ _STARTUP_PRINCIPAL_PROPFIND_INITIAL = None
+ _STARTUP_PRINCIPAL_PROPFIND = None
+ _STARTUP_PRINCIPALS_REPORT = None
+ _STARTUP_PRINCIPAL_EXPAND = None
+ _STARTUP_PROPPATCH_CALENDAR_COLOR = None
+ _STARTUP_PROPPATCH_CALENDAR_ORDER = None
+ _STARTUP_PROPPATCH_CALENDAR_TIMEZONE = None
+
+ _POLL_CALENDARHOME_PROPFIND = None
+ _POLL_CALENDAR_PROPFIND = None
+ _POLL_CALENDAR_PROPFIND_D1 = None
+ _POLL_CALENDAR_MULTIGET_REPORT = None
+ _POLL_CALENDAR_MULTIGET_REPORT_HREF = None
+ _POLL_CALENDAR_SYNC_REPORT = None
+ _POLL_NOTIFICATION_PROPFIND = None
+ _POLL_NOTIFICATION_PROPFIND_D1 = None
+
+ _NOTIFICATION_SYNC_REPORT = None
+
+ _USER_LIST_PRINCIPAL_PROPERTY_SEARCH = None
+ _POST_AVAILABILITY = None
+
+ _CALENDARSERVER_PRINCIPAL_SEARCH_REPORT = None
+
+ email = None
+
+ def __init__(
+ self,
+ reactor,
+ server,
+ serializePath,
+ record,
+ auth,
+ title=None,
+ calendarHomePollInterval=None,
+ supportPush=True,
+ supportAmpPush=True,
+ principalPathTemplate="/principals/users/{}/",
+ ):
+ super(BaseAppleClient, self).__init__(
+ reactor, server, serializePath, record, auth, title=title
+ )
+
+ self.principalPathTemplate = principalPathTemplate
+
+ if calendarHomePollInterval is None:
+ calendarHomePollInterval = self.CALENDAR_HOME_POLL_INTERVAL
+ self.calendarHomePollInterval = calendarHomePollInterval
+
+ self.calendarHomeHref = None
+
+ self.supportPush = supportPush
+
+ self.supportAmpPush = supportAmpPush
+ ampPushHosts = self.server.get("ampPushHosts")
+ if ampPushHosts is None:
+ ampPushHosts = [urlparse(self.server["uri"])[1].split(":")[0]]
+ self.ampPushHosts = ampPushHosts
+ self.ampPushPort = self.server.get("ampPushPort", 62311)
+
+ self.serializePath = serializePath
+
+ self.supportSync = self._SYNC_REPORT
+ self.supportNotificationSync = self._NOTIFICATION_SYNC_REPORT
+
+ self.supportEnhancedAttendeeAutoComplete = self._CALENDARSERVER_PRINCIPAL_SEARCH_REPORT
+
+ # Keep track of the calendars on this account, keys are
+ # Calendar URIs, values are Calendar instances.
+ self._calendars = {}
+
+ # The principalURL found during discovery
+ self.principalURL = None
+
+ # The principal collection found during startup
+ self.principalCollection = None
+
+ # Keep track of the events on this account, keys are event
+ # URIs (which are unambiguous across different calendars
+ # because they start with the uri of the calendar they are
+ # part of), values are Event instances.
+ self._events = {}
+
+ # Keep track of which calendar homes are being polled
+ self._checking = set()
+
+ # Keep track of XMPP parameters for calendar homes we encounter. This
+ # dictionary has calendar home URLs as keys and XMPPPush instances as
+ # values.
+ self.xmpp = {}
+
+ self.ampPushKeys = {}
+
+ # Keep track of push factories so we can unsubscribe at shutdown
+ self._pushFactories = []
+
+ # Allow events to go out into the world.
+ self.catalog = {
+ "eventChanged": Periodical(),
+ }
+
+ # Keep track of previously downloaded attachments
+ self._attachments = {}
+
+
+ def _setEvent(self, href, event):
+ """
+ Cache the provided event
+ """
+ self._events[href] = event
+ calendar, basePath = href.rsplit('/', 1)
+ self._calendars[calendar + '/'].events[basePath] = event
+
+
+ def _removeEvent(self, href):
+ """
+ Remove event from local cache.
+ """
+ self._events[href].removed()
+ del self._events[href]
+ calendar, basePath = href.rsplit('/', 1)
+ del self._calendars[calendar + '/'].events[basePath]
+
+
+
+ def _addDefaultHeaders(self, headers):
+ """
+ Add the clients default set of headers to ones being used in a request.
+ Default is to add User-Agent, sub-classes should override to add other
+ client specific things, Accept etc.
+ """
+ headers.setRawHeaders('User-Agent', [self.USER_AGENT])
+
+
+ @inlineCallbacks
+ def _request(self, expectedResponseCodes, method, url, headers=None, body=None, method_label=None):
+ """
+ Execute a request and check against the expected response codes.
+ """
+ if type(expectedResponseCodes) is int:
+ expectedResponseCodes = (expectedResponseCodes,)
+ if headers is None:
+ headers = Headers({})
+ self._addDefaultHeaders(headers)
+ msg(
+ type="request",
+ method=method_label if method_label else method,
+ url=url,
+ user=self.record.uid,
+ client_type=self.title,
+ client_id=self._client_id,
+ )
+
+ before = self.reactor.seconds()
+ response = yield self.agent.request(method, url, headers, body)
+
+ # XXX This is time to receive response headers, not time
+ # to receive full response. Should measure the latter, if
+ # not both.
+ after = self.reactor.seconds()
+
+ success = response.code in expectedResponseCodes
+
+ msg(
+ type="response",
+ success=success,
+ method=method_label if method_label else method,
+ headers=headers,
+ body=body,
+ code=response.code,
+ user=self.record.uid,
+ client_type=self.title,
+ client_id=self._client_id,
+ duration=(after - before),
+ url=url,
+ )
+
+ if success:
+ returnValue(response)
+
+ raise IncorrectResponseCode(expectedResponseCodes, response)
+
+
+ def _parseMultiStatus(self, response, otherTokens=False):
+ """
+ Parse a <multistatus> - might need to return other top-level elements
+ in the response - e.g. DAV:sync-token
+ I{PROPFIND} request for the principal URL.
+
+ @type response: C{str}
+ @rtype: C{cls}
+ """
+ parser = PropFindParser()
+ parser.parseData(response)
+ if otherTokens:
+ return (parser.getResults(), parser.getOthers(),)
+ else:
+ return parser.getResults()
+
+ _CALENDAR_TYPES = set([
+ caldavxml.calendar,
+ caldavxml.schedule_inbox,
+ ])
+
+ @inlineCallbacks
+ def _propfind(self, url, body, depth='0', allowedStatus=(MULTI_STATUS,), method_label=None):
+ """
+ Issue a PROPFIND on the chosen URL
+ """
+ hdrs = Headers({'content-type': ['text/xml']})
+ if depth is not None:
+ hdrs.addRawHeader('depth', depth)
+ response = yield self._request(
+ allowedStatus,
+ 'PROPFIND',
+ self.server["uri"] + url.encode('utf-8'),
+ hdrs,
+ StringProducer(body),
+ method_label=method_label,
+ )
+
+ body = yield readBody(response)
+ result = self._parseMultiStatus(body) if response.code == MULTI_STATUS else None
+
+ returnValue((response, result,))
+
+
+ @inlineCallbacks
+ def _proppatch(self, url, body, method_label=None):
+ """
+ Issue a PROPPATCH on the chosen URL
+ """
+ hdrs = Headers({'content-type': ['text/xml']})
+ response = yield self._request(
+ (OK, MULTI_STATUS,),
+ 'PROPPATCH',
+ self.server["uri"] + url.encode('utf-8'),
+ hdrs,
+ StringProducer(body),
+ method_label=method_label,
+ )
+ if response.code == MULTI_STATUS:
+ body = yield readBody(response)
+ result = self._parseMultiStatus(body)
+ returnValue(result)
+ else:
+ returnValue(None)
+
+
+ @inlineCallbacks
+ def _get(self, url, allowedStatus=(MULTI_STATUS,), method_label=None):
+ """
+ Issue a GET on the chosen URL
+ """
+ response = yield self._request(
+ allowedStatus,
+ 'GET',
+ url,
+ method_label=method_label,
+ )
+
+ body = yield readBody(response)
+ returnValue(body)
+
+
+ @inlineCallbacks
+ def _report(self, url, body, depth='0', allowedStatus=(MULTI_STATUS,), otherTokens=False, method_label=None):
+ """
+ Issue a REPORT on the chosen URL
+ """
+ hdrs = Headers({'content-type': ['text/xml']})
+ if depth is not None:
+ hdrs.addRawHeader('depth', depth)
+ response = yield self._request(
+ allowedStatus,
+ 'REPORT',
+ self.server["uri"] + url.encode('utf-8'),
+ hdrs,
+ StringProducer(body),
+ method_label=method_label,
+ )
+
+ body = yield readBody(response)
+ result = self._parseMultiStatus(body, otherTokens) if response.code == MULTI_STATUS else None
+
+ returnValue(result)
+
+
+ @inlineCallbacks
+ def _startupPropfindWellKnown(self):
+ """
+ Issue a PROPFIND on the /.well-known/caldav/ URL
+ """
+
+ location = "/.well-known/caldav/"
+ response, result = yield self._propfind(
+ location,
+ self._STARTUP_WELL_KNOWN,
+ allowedStatus=(MULTI_STATUS, MOVED_PERMANENTLY, FOUND,),
+ method_label="PROPFIND{well-known}",
+ )
+
+ # Follow any redirect
+ if response.code in (MOVED_PERMANENTLY, FOUND,):
+ location = response.headers.getRawHeaders("location")[0]
+ location = urlsplit(location)[2]
+ response, result = yield self._propfind(
+ location,
+ self._STARTUP_WELL_KNOWN,
+ allowedStatus=(MULTI_STATUS),
+ method_label="PROPFIND{well-known}",
+ )
+
+ returnValue(result[location])
+
+
+ @inlineCallbacks
+ def _principalPropfindInitial(self, user):
+ """
+ Issue a PROPFIND on the /principals/users/<uid> URL to retrieve
+ the /principals/__uids__/<guid> principal URL
+ """
+ principalPath = self.principalPathTemplate.format(user)
+ _ignore_response, result = yield self._propfind(
+ principalPath,
+ self._STARTUP_PRINCIPAL_PROPFIND_INITIAL,
+ method_label="PROPFIND{find-principal}",
+ )
+ returnValue(result[principalPath])
+
+
+ @inlineCallbacks
+ def _principalPropfind(self):
+ """
+ Issue a PROPFIND on the likely principal URL for the given
+ user and return a L{Principal} instance constructed from the
+ response.
+ """
+ _ignore_response, result = yield self._propfind(
+ self.principalURL,
+ self._STARTUP_PRINCIPAL_PROPFIND,
+ method_label="PROPFIND{principal}",
+ )
+ returnValue(result[self.principalURL])
+
+
+ def _principalSearchPropertySetReport(self, principalCollectionSet):
+ """
+ Issue a principal-search-property-set REPORT against the chosen URL
+ """
+ return self._report(
+ principalCollectionSet,
+ self._STARTUP_PRINCIPALS_REPORT,
+ allowedStatus=(OK,),
+ method_label="REPORT{pset}",
+ )
+
+
+ @inlineCallbacks
+ def _calendarHomePropfind(self, calendarHomeSet):
+ """
+ Do the poll Depth:1 PROPFIND on the calendar home.
+ """
+ if not calendarHomeSet.endswith('/'):
+ calendarHomeSet = calendarHomeSet + '/'
+ _ignore_response, result = yield self._propfind(
+ calendarHomeSet,
+ self._POLL_CALENDARHOME_PROPFIND,
+ depth='1',
+ method_label="PROPFIND{home}",
+ )
+ calendars, notificationCollection = self._extractCalendars(
+ result, calendarHomeSet
+ )
+ returnValue((calendars, notificationCollection, result,))
+
+
+ @inlineCallbacks
+ def _extractPrincipalDetails(self):
+ # Using the actual principal URL, retrieve principal information
+ principal = yield self._principalPropfind()
+
+ hrefs = principal.getHrefProperties()
+
+ # Remember our outbox and ignore notifications
+ self.outbox = hrefs[caldavxml.schedule_outbox_URL].toString()
+ self.notificationURL = None
+
+ # Remember our own email-like principal address
+ self.email = None
+ self.uuid = None
+ cuaddrs = hrefs[caldavxml.calendar_user_address_set]
+ if isinstance(cuaddrs, URL):
+ cuaddrs = (cuaddrs,)
+ for cuaddr in cuaddrs:
+ if cuaddr.toString().startswith(u"mailto:"):
+ self.email = cuaddr.toString()
+ elif cuaddr.toString().startswith(u"urn:x-uid"):
+ self.uuid = cuaddr.toString()
+ elif cuaddr.toString().startswith(u"urn:uuid") and self.uuid is None:
+ self.uuid = cuaddr.toString()
+ if self.email is None:
+ raise ValueError("Cannot operate without a mail-style principal URL")
+
+ # Do another kind of thing I guess
+ self.principalCollection = hrefs[davxml.principal_collection_set].toString()
+ yield self._principalSearchPropertySetReport(self.principalCollection)
+
+ returnValue(principal)
+
+
+ def _extractCalendars(self, results, calendarHome=None):
+ """
+ Parse a calendar home PROPFIND response and create local state
+ representing the calendars it contains.
+
+ If XMPP push is enabled, also look for and record information about
+ that from the response.
+ """
+ calendars = []
+ notificationCollection = None
+
+ changeTag = davxml.sync_token if self.supportSync else csxml.getctag
+
+ for href in results:
+
+ if href == calendarHome:
+ text = results[href].getTextProperties()
+
+ try:
+ pushkey = text[csxml.pushkey]
+ except KeyError:
+ pass
+ else:
+ if pushkey:
+ self.ampPushKeys[href] = pushkey
+
+ try:
+ server = text[csxml.xmpp_server]
+ uri = text[csxml.xmpp_uri]
+ pushkey = text[csxml.pushkey]
+ except KeyError:
+ pass
+ else:
+ if server and uri:
+ self.xmpp[href] = XMPPPush(server, uri, pushkey)
+
+ nodes = results[href].getNodeProperties()
+ isCalendar = False
+ isNotifications = False
+ isShared = False
+ isSharedByMe = False
+ resourceType = None
+ for nodeType in nodes[davxml.resourcetype]:
+ if nodeType.tag in self._CALENDAR_TYPES:
+ isCalendar = True
+ resourceType = nodeType.tag
+ elif nodeType.tag == csxml.notification:
+ isNotifications = True
+ elif nodeType.tag.startswith("{http://calendarserver.org/ns/}shared"):
+ isShared = True
+ if nodeType.tag == "{http://calendarserver.org/ns/}shared-owner":
+ isSharedByMe = True
+
+ if isCalendar:
+ textProps = results[href].getTextProperties()
+ componentTypes = set()
+ if nodeType.tag == caldavxml.calendar:
+ if caldavxml.supported_calendar_component_set in nodes:
+ for comp in nodes[caldavxml.supported_calendar_component_set]:
+ componentTypes.add(comp.get("name").upper())
+
+ calendars.append(Calendar(
+ resourceType,
+ componentTypes,
+ textProps.get(davxml.displayname, None),
+ href,
+ textProps.get(changeTag, None),
+ shared=isShared,
+ sharedByMe=isSharedByMe
+ ))
+
+ elif isNotifications:
+ textProps = results[href].getTextProperties()
+ notificationCollection = NotificationCollection(
+ href,
+ textProps.get(changeTag, None)
+ )
+
+ return calendars, notificationCollection
+
+
+ def _updateCalendar(self, calendar, newToken, fetchEvents=True):
+ """
+ Update the local cached data for a calendar in an appropriate manner.
+ """
+ if self.supportSync:
+ return self._updateCalendar_SYNC(calendar, newToken, fetchEvents=fetchEvents)
+ else:
+ return self._updateCalendar_PROPFIND(calendar, newToken)
+
+
+ @inlineCallbacks
+ def _updateCalendar_PROPFIND(self, calendar, newToken):
+ """
+ Sync a collection by doing a full PROPFIND Depth:1 on it and then sync
+ the results with local cached data.
+ """
+
+ # Grab old hrefs prior to the PROPFIND so we sync with the old state. We need this because
+ # the sim can fire a PUT between the PROPFIND and when process the removals.
+ old_hrefs = set([calendar.url + child for child in calendar.events.keys()])
+
+ _ignore_response, result = yield self._propfind(
+ calendar.url,
+ self._POLL_CALENDAR_PROPFIND_D1,
+ depth='1',
+ method_label="PROPFIND{calendar}"
+ )
+
+ yield self._updateApplyChanges(calendar, result, old_hrefs)
+
+ # Now update calendar to the new token
+ self._calendars[calendar.url].changeToken = newToken
+
+
+ @inlineCallbacks
+ def _updateCalendar_SYNC(self, calendar, newToken, fetchEvents=True):
+ """
+ Execute a sync REPORT against a calendar and apply changes to the local cache.
+ The new token from the changed collection is passed in and must be applied to
+ the existing calendar once sync is done.
+ """
+
+ # Grab old hrefs prior to the REPORT so we sync with the old state. We need this because
+ # the sim can fire a PUT between the REPORT and when process the removals.
+ old_hrefs = set([calendar.url + child for child in calendar.events.keys()])
+
+ # Get changes from sync REPORT (including the other nodes at the top-level
+ # which will have the new sync token.
+ fullSync = not calendar.changeToken
+ result = yield self._report(
+ calendar.url,
+ self._POLL_CALENDAR_SYNC_REPORT % {'sync-token': calendar.changeToken},
+ depth='1',
+ allowedStatus=(MULTI_STATUS, FORBIDDEN,),
+ otherTokens=True,
+ method_label="REPORT{sync}" if calendar.changeToken else "REPORT{sync-init}",
+ )
+ if result is None:
+ if not fullSync:
+ fullSync = True
+ result = yield self._report(
+ calendar.url,
+ self._POLL_CALENDAR_SYNC_REPORT % {'sync-token': ''},
+ depth='1',
+ otherTokens=True,
+ method_label="REPORT{sync}" if calendar.changeToken else "REPORT{sync-init}",
+ )
+ else:
+ raise IncorrectResponseCode((MULTI_STATUS,), None)
+
+ result, others = result
+
+ if fetchEvents:
+ changed = []
+ for responseHref in result:
+ if responseHref == calendar.url:
+ continue
+
+ try:
+ etag = result[responseHref].getTextProperties()[davxml.getetag]
+ except KeyError:
+ # XXX Ignore things with no etag? Seems to be dropbox.
+ continue
+
+ # Differentiate a remove vs new/update result
+ if result[responseHref].getStatus() / 100 == 2:
+ if responseHref not in self._events:
+ self._setEvent(responseHref, Event(self.serializeLocation(), responseHref, None))
+
+ event = self._events[responseHref]
+ if event.etag != etag:
+ changed.append(responseHref)
+ elif result[responseHref].getStatus() == 404:
+ self._removeEvent(responseHref)
+
+ yield self._updateChangedEvents(calendar, changed)
+
+ # Handle removals only when doing an initial sync
+ if fullSync:
+ # Detect removed items and purge them
+ remove_hrefs = old_hrefs - set(changed)
+ for href in remove_hrefs:
+ self._removeEvent(href)
+
+ # Now update calendar to the new token taken from the report
+ for node in others:
+ if node.tag == davxml.sync_token:
+ newToken = node.text
+ break
+ self._calendars[calendar.url].changeToken = newToken
+
+
+ @inlineCallbacks
+ def _updateApplyChanges(self, calendar, multistatus, old_hrefs):
+ """
+ Given a multistatus for an entire collection, sync the reported items
+ against the cached items.
+ """
+
+ # Detect changes and new items
+ all_hrefs = []
+ changed_hrefs = []
+ for responseHref in multistatus:
+ if responseHref == calendar.url:
+ continue
+ all_hrefs.append(responseHref)
+ try:
+ etag = multistatus[responseHref].getTextProperties()[davxml.getetag]
+ except KeyError:
+ # XXX Ignore things with no etag? Seems to be dropbox.
+ continue
+
+ if responseHref not in self._events:
+ self._setEvent(responseHref, Event(self.serializeLocation(), responseHref, None))
+
+ event = self._events[responseHref]
+ if event.etag != etag:
+ changed_hrefs.append(responseHref)
+
+ # Retrieve changes
+ yield self._updateChangedEvents(calendar, changed_hrefs)
+
+ # Detect removed items and purge them
+ remove_hrefs = old_hrefs - set(all_hrefs)
+ for href in remove_hrefs:
+ self._removeEvent(href)
+
+
+ @inlineCallbacks
+ def _updateChangedEvents(self, calendar, changed):
+ """
+ Given a set of changed hrefs, batch multiget them all to update the
+ local cache.
+ """
+
+ changed.sort()
+ while changed:
+ batchedHrefs = changed[:self.MULTIGET_BATCH_SIZE]
+ changed = changed[self.MULTIGET_BATCH_SIZE:]
+
+ multistatus = yield self._eventReport(calendar.url, batchedHrefs)
+ for responseHref in batchedHrefs:
+ try:
+ res = multistatus[responseHref]
+ except KeyError:
+ # Resource might have been deleted
+ continue
+ if res.getStatus() == 200:
+ 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):
+ event = self._events[href]
+ event.etag = etag
+ if scheduleTag is not None:
+ event.scheduleTag = scheduleTag
+ event.component = Component.fromString(body)
+ self.catalog["eventChanged"].issue(href)
+
+
+ def _eventReport(self, calendar, events):
+ # Next do a REPORT on events that might have information
+ # we don't know about.
+ hrefs = "".join([self._POLL_CALENDAR_MULTIGET_REPORT_HREF % {'href': event} for event in events])
+
+ label_suffix = "small"
+ if len(events) > 5:
+ label_suffix = "medium"
+ if len(events) > 20:
+ label_suffix = "large"
+ if len(events) > 75:
+ label_suffix = "huge"
+
+ return self._report(
+ calendar,
+ self._POLL_CALENDAR_MULTIGET_REPORT % {'hrefs': hrefs},
+ depth=None,
+ method_label="REPORT{multiget-%s}" % (label_suffix,),
+ )
+
+
+ @inlineCallbacks
+ def _checkCalendarsForEvents(self, calendarHomeSet, firstTime=False, push=False):
+ """
+ The actions a client does when polling for changes, or in response to a
+ push notification of a change. There are some actions done on the first poll
+ we should emulate.
+ """
+
+ result = True
+ try:
+ result = yield self._newOperation("push" if push else "poll", self._poll(calendarHomeSet, firstTime))
+ finally:
+ if result:
+ try:
+ self._checking.remove(calendarHomeSet)
+ except KeyError:
+ pass
+ returnValue(result)
+
+
+ @inlineCallbacks
+ def _updateNotifications(self, oldToken, newToken):
+
+ fullSync = not oldToken
+
+ # Get the list of notification xml resources
+
+ result = yield self._report(
+ self._notificationCollection.url,
+ self._NOTIFICATION_SYNC_REPORT % {'sync-token': oldToken},
+ depth='1',
+ allowedStatus=(MULTI_STATUS, FORBIDDEN,),
+ otherTokens=True,
+ method_label="REPORT{sync}" if oldToken else "REPORT{sync-init}",
+ )
+ if result is None:
+ if not fullSync:
+ fullSync = True
+ result = yield self._report(
+ self._notificationCollection.url,
+ self._NOTIFICATION_SYNC_REPORT % {'sync-token': ''},
+ depth='1',
+ otherTokens=True,
+ method_label="REPORT{sync}" if oldToken else "REPORT{sync-init}",
+ )
+ else:
+ raise IncorrectResponseCode((MULTI_STATUS,), None)
+
+ result, _ignore_others = result
+
+ # Scan for the sharing invites
+ inviteNotifications = []
+ toDelete = []
+ for responseHref in result:
+ if responseHref == self._notificationCollection.url:
+ continue
+
+ # try:
+ # etag = result[responseHref].getTextProperties()[davxml.getetag]
+ # except KeyError:
+ # # XXX Ignore things with no etag? Seems to be dropbox.
+ # continue
+
+ toDelete.append(responseHref)
+
+ if result[responseHref].getStatus() / 100 == 2:
+ # Get the notification
+ response = yield self._request(
+ OK,
+ 'GET',
+ self.server["uri"] + responseHref.encode('utf-8'),
+ method_label="GET{notification}",
+ )
+ body = yield readBody(response)
+ node = ElementTree(file=StringIO(body)).getroot()
+ if node.tag == str(csxml.notification):
+ nurl = URL(url=responseHref)
+ for child in node.getchildren():
+ if child.tag == str(csxml.invite_notification):
+ if child.find(str(csxml.invite_noresponse)) is not None:
+ inviteNotifications.append(
+ InviteNotification().parseFromNotification(
+ nurl, child
+ )
+ )
+
+ # Accept the invites
+ for notification in inviteNotifications:
+ # Create an invite-reply
+ """
+ <?xml version="1.0" encoding="UTF-8"?>
+ <C:invite-reply xmlns:C="http://calendarserver.org/ns/">
+ <A:href xmlns:A="DAV:">urn:x-uid:10000000-0000-0000-0000-000000000002</A:href>
+ <C:invite-accepted/>
+ <C:hosturl>
+ <A:href xmlns:A="DAV:">/calendars/__uids__/10000000-0000-0000-0000-000000000001/A1DDC58B-651E-4B1C-872A-C6588CA09ADB</A:href>
+ </C:hosturl>
+ <C:in-reply-to>d2683fa9-7a50-4390-82bb-cbcea5e0fa86</C:in-reply-to>
+ <C:summary>to share</C:summary>
+ </C:invite-reply>
+ """
+ reply = Element(csxml.invite_reply)
+ href = SubElement(reply, davxml.href)
+ href.text = notification.user_uid
+ SubElement(reply, csxml.invite_accepted)
+ hosturl = SubElement(reply, csxml.hosturl)
+ href = SubElement(hosturl, davxml.href)
+ href.text = notification.hosturl
+ inReplyTo = SubElement(reply, csxml.in_reply_to)
+ inReplyTo.text = notification.uid
+ summary = SubElement(reply, csxml.summary)
+ summary.text = notification.summary
+
+ xmldoc = BetterElementTree(reply)
+ os = StringIO()
+ xmldoc.writeUTF8(os)
+ # Post to my calendar home
+ response = yield self.postXML(
+ self.calendarHomeHref,
+ os.getvalue(),
+ "POST{invite-accept}"
+ )
+
+ # Delete all the notification resources
+ for responseHref in toDelete:
+ response = yield self._request(
+ (NO_CONTENT, NOT_FOUND),
+ 'DELETE',
+ self.server["uri"] + responseHref.encode('utf-8'),
+ method_label="DELETE{invite}",
+ )
+
+ self._notificationCollection.changeToken = newToken
+
+
+ @inlineCallbacks
+ def _poll(self, calendarHomeSet, firstTime):
+ """
+ This gets called during a normal poll or in response to a push
+ """
+
+ if calendarHomeSet in self._checking:
+ returnValue(False)
+ self._checking.add(calendarHomeSet)
+
+ calendars, notificationCollection, results = yield self._calendarHomePropfind(calendarHomeSet)
+
+ # First time operations
+ if firstTime:
+ yield self._pollFirstTime1(results[calendarHomeSet], calendars)
+
+ # Normal poll
+ for cal in calendars:
+ newToken = cal.changeToken
+ if cal.url not in self._calendars:
+ # Calendar seen for the first time - reload it
+ self._calendars[cal.url] = cal
+ cal.changeToken = ""
+ # If this is the first time this run and we have no cached copy
+ # of this calendar, do an update but don't fetch the events.
+ # We'll only take notice of ongoing activity and not bother
+ # with existing events.
+ yield self._updateCalendar(
+ self._calendars[cal.url], newToken,
+ fetchEvents=(not firstTime)
+ )
+ elif self._calendars[cal.url].changeToken != newToken:
+ # Calendar changed - reload it
+ yield self._updateCalendar(self._calendars[cal.url], newToken)
+
+ if notificationCollection is not None:
+ if self.supportNotificationSync:
+ if self._notificationCollection:
+ oldToken = self._notificationCollection.changeToken
+ else:
+ oldToken = ""
+ self._notificationCollection = notificationCollection
+ newToken = notificationCollection.changeToken
+ yield self._updateNotifications(oldToken, newToken)
+ else:
+ # When there is no sync REPORT, clients have to do a full PROPFIND
+ # on the notification collection because there is no ctag
+ self._notificationCollection = notificationCollection
+ yield self._notificationPropfind(self._notificationCollection.url)
+ yield self._notificationChangesPropfind(self._notificationCollection.url)
+
+ # One time delegate expansion
+ if firstTime:
+ yield self._pollFirstTime2()
+
+ returnValue(True)
+
+
+ @inlineCallbacks
+ def _pollFirstTime1(self, homeNode, calendars):
+ # Detect sync report if needed
+ if self.supportSync:
+ nodes = homeNode.getNodeProperties()
+ syncnodes = nodes[davxml.supported_report_set].findall(
+ str(davxml.supported_report) + "/" +
+ str(davxml.report) + "/" +
+ str(davxml.sync_collection)
+ )
+ self.supportSync = len(syncnodes) != 0
+
+ # Patch calendar properties
+ for cal in calendars:
+ if cal.name != "inbox":
+ yield self._proppatch(
+ cal.url,
+ self._STARTUP_PROPPATCH_CALENDAR_COLOR,
+ method_label="PROPPATCH{calendar}",
+ )
+ yield self._proppatch(
+ cal.url,
+ self._STARTUP_PROPPATCH_CALENDAR_ORDER,
+ method_label="PROPPATCH{calendar}",
+ )
+ yield self._proppatch(
+ cal.url,
+ self._STARTUP_PROPPATCH_CALENDAR_TIMEZONE,
+ method_label="PROPPATCH{calendar}",
+ )
+
+
+ def _pollFirstTime2(self):
+ return self._principalExpand(self.principalURL)
+
+
+ @inlineCallbacks
+ def _notificationPropfind(self, notificationURL):
+ _ignore_response, result = yield self._propfind(
+ notificationURL,
+ self._POLL_NOTIFICATION_PROPFIND,
+ method_label="PROPFIND{notification}",
+ )
+ returnValue(result)
+
+
+ @inlineCallbacks
+ def _notificationChangesPropfind(self, notificationURL):
+ _ignore_response, result = yield self._propfind(
+ notificationURL,
+ self._POLL_NOTIFICATION_PROPFIND_D1,
+ depth='1',
+ method_label="PROPFIND{notification-items}",
+ )
+ returnValue(result)
+
+
+ @inlineCallbacks
+ def _principalExpand(self, principalURL):
+ result = yield self._report(
+ principalURL,
+ self._STARTUP_PRINCIPAL_EXPAND,
+ depth=None,
+ method_label="REPORT{expand}",
+ )
+ returnValue(result)
+
+
+ def startup(self):
+ raise NotImplementedError
+
+
+ def _calendarCheckLoop(self, calendarHome):
+ """
+ Periodically check the calendar home for changes to calendars.
+ """
+ pollCalendarHome = LoopingCall(
+ self._checkCalendarsForEvents, calendarHome)
+ return pollCalendarHome.start(self.calendarHomePollInterval, now=False)
+
+
+ @inlineCallbacks
+ def _newOperation(self, label, deferred):
+ before = self.reactor.seconds()
+ msg(
+ type="operation",
+ phase="start",
+ user=self.record.uid,
+ client_type=self.title,
+ client_id=self._client_id,
+ label=label,
+ )
+
+ try:
+ result = yield deferred
+ except IncorrectResponseCode:
+ # Let this through
+ success = False
+ result = None
+ except:
+ # Anything else is fatal
+ raise
+ else:
+ success = True
+
+ after = self.reactor.seconds()
+ msg(
+ type="operation",
+ phase="end",
+ duration=after - before,
+ user=self.record.uid,
+ client_type=self.title,
+ client_id=self._client_id,
+ label=label,
+ success=success,
+ )
+ returnValue(result)
+
+
+ def _receivedPush(self, inboundID, dataChangedTimestamp, priority=5):
+ for href, id in self.ampPushKeys.iteritems():
+ if inboundID == id:
+ self._checkCalendarsForEvents(href, push=True)
+ break
+ else:
+ # somehow we are not subscribed to this id
+ pass
+
+
+ def _monitorAmpPush(self, home, pushKeys):
+ """
+ Start monitoring for AMP-based push notifications
+ """
+ for host in self.ampPushHosts:
+ subscribeToIDs(
+ host, self.ampPushPort, pushKeys,
+ self._receivedPush, self.reactor
+ )
+
+
+ @inlineCallbacks
+ def _unsubscribePubSub(self):
+ for factory in self._pushFactories:
+ yield factory.unsubscribeAll()
+
+
+ @inlineCallbacks
+ def run(self):
+ """
+ Emulate a CalDAV client.
+ """
+ @inlineCallbacks
+ def startup():
+ principal = yield self.startup()
+ hrefs = principal.getHrefProperties()
+ calendarHome = hrefs[caldavxml.calendar_home_set].toString()
+ if calendarHome is None:
+ raise MissingCalendarHome
+ else:
+ self.calendarHomeHref = calendarHome
+ yield self._checkCalendarsForEvents(calendarHome, firstTime=True)
+ returnValue(calendarHome)
+ calendarHome = yield self._newOperation("startup: %s" % (self.title,), startup())
+
+ self.started = True
+
+ # Start monitoring PubSub notifications, if possible.
+ # _checkCalendarsForEvents populates self.xmpp if it finds
+ # anything.
+ if self.supportAmpPush and calendarHome in self.ampPushKeys:
+ pushKeys = self.ampPushKeys.values()
+ self._monitorAmpPush(calendarHome, pushKeys)
+ # Run indefinitely.
+ yield Deferred()
+ else:
+ # 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 stop(self):
+ """
+ Called before connections are closed, giving a chance to clean up
+ """
+
+ self.serialize()
+ return self._unsubscribePubSub()
+
+
+ def serializeLocation(self):
+ """
+ Return the path to the directory where data for this user is serialized.
+ """
+ if self.serializePath is None or not os.path.isdir(self.serializePath):
+ return None
+
+ key = "%s-%s" % (self.record.uid, self.title.replace(" ", "_"))
+ path = os.path.join(self.serializePath, key)
+ if not os.path.exists(path):
+ os.mkdir(path)
+ elif not os.path.isdir(path):
+ return None
+
+ return path
+
+
+ def serialize(self):
+ """
+ Write current state to disk.
+ """
+
+ path = self.serializeLocation()
+ if path is None:
+ return
+
+ # Create dict for all the data we need to store
+ data = {
+ "principalURL": self.principalURL,
+ "calendars": [calendar.serialize() for calendar in sorted(self._calendars.values(), key=lambda x:x.name)],
+ "events": [event.serialize() for event in sorted(self._events.values(), key=lambda x:x.url)],
+ "notificationCollection" : self._notificationCollection.serialize() if self._notificationCollection else {},
+ "attachments": self._attachments
+ }
+ # Write JSON data
+ with open(os.path.join(path, "index.json"), "w") as f:
+ json.dump(data, f, indent=2)
+
+
+ def deserialize(self):
+ """
+ Read state from disk.
+ """
+
+ self._calendars = {}
+ self._events = {}
+ self._attachments = {}
+
+ path = self.serializeLocation()
+ if path is None:
+ return
+
+ # Parse JSON data for calendars
+ try:
+ with open(os.path.join(path, "index.json")) as f:
+ data = json.load(f)
+ except IOError:
+ return
+
+ self.principalURL = data["principalURL"]
+
+ # Extract all the events first, then do the calendars (which reference the events)
+ for event in data["events"]:
+ event = Event.deserialize(self.serializeLocation(), event)
+ self._events[event.url] = event
+ for calendar in data["calendars"]:
+ calendar = Calendar.deserialize(calendar, self._events)
+ self._calendars[calendar.url] = calendar
+ if data.get("notificationCollection"):
+ self._notificationCollection = NotificationCollection.deserialize(data, {})
+ self._attachments = data.get("attachments", {})
+
+
+
+ @inlineCallbacks
+ def reset(self):
+ path = self.serializeLocation()
+ if path is not None and os.path.exists(path):
+ shutil.rmtree(path)
+ yield self.startup()
+ yield self._checkCalendarsForEvents(self.calendarHomeHref)
+
+
+ def _makeSelfAttendee(self):
+ attendee = Property(
+ name=u'ATTENDEE',
+ value=self.email,
+ params={
+ 'CN': self.record.commonName,
+ 'CUTYPE': 'INDIVIDUAL',
+ 'PARTSTAT': 'ACCEPTED',
+ },
+ )
+ return attendee
+
+
+ def _makeSelfOrganizer(self):
+ organizer = Property(
+ name=u'ORGANIZER',
+ value=self.email,
+ params={
+ 'CN': self.record.commonName,
+ },
+ )
+ return organizer
+
+
+ @inlineCallbacks
+ def addEventAttendee(self, href, attendee):
+
+ event = self._events[href]
+ component = event.component
+
+ # Trigger auto-complete behavior
+ yield self._attendeeAutoComplete(component, attendee)
+
+ # If the event has no attendees, add ourselves as an attendee.
+ attendees = list(component.mainComponent().properties('ATTENDEE'))
+ if len(attendees) == 0:
+ # First add ourselves as a participant and as the
+ # organizer. In the future for this event we should
+ # already have those roles.
+ component.mainComponent().addProperty(self._makeSelfOrganizer())
+ component.mainComponent().addProperty(self._makeSelfAttendee())
+ attendees.append(attendee)
+ component.mainComponent().addProperty(attendee)
+
+ label_suffix = "small"
+ if len(attendees) > 5:
+ label_suffix = "medium"
+ if len(attendees) > 20:
+ label_suffix = "large"
+ if len(attendees) > 75:
+ label_suffix = "huge"
+
+ # At last, upload the new event definition
+ response = yield self._request(
+ (NO_CONTENT, PRECONDITION_FAILED,),
+ 'PUT',
+ self.server["uri"] + href.encode('utf-8'),
+ Headers({
+ 'content-type': ['text/calendar'],
+ 'if-match': [event.etag]}),
+ StringProducer(component.getTextWithTimezones(includeTimezones=True)),
+ method_label="PUT{organizer-%s}" % (label_suffix,)
+ )
+
+ # Finally, re-retrieve the event to update the etag
+ yield self._updateEvent(response, href)
+
+
+ @inlineCallbacks
+ def _attendeeAutoComplete(self, component, attendee):
+
+ if self._ATTENDEE_LOOKUPS:
+ # Temporarily use some non-test names (some which will return
+ # many results, and others which will return fewer) because the
+ # test account names are all too similar
+ # name = attendee.parameterValue('CN').encode("utf-8")
+ # prefix = name[:4].lower()
+ prefix = random.choice([
+ "chris", "cyru", "dre", "eric", "morg",
+ "well", "wilfr", "witz"
+ ])
+
+ email = attendee.value()
+ if email.startswith("mailto:"):
+ email = email[7:]
+ elif attendee.hasParameter('EMAIL'):
+ email = attendee.parameterValue('EMAIL').encode("utf-8")
+
+ if self.supportEnhancedAttendeeAutoComplete:
+ # New calendar server enhanced query
+ search = "<C:search-token>{}</C:search-token>".format(prefix)
+ body = self._CALENDARSERVER_PRINCIPAL_SEARCH_REPORT.format(
+ context="attendee", searchTokens=search)
+ yield self._report(
+ self.principalCollection,
+ body,
+ depth=None,
+ method_label="REPORT{cpsearch}",
+ )
+ else:
+ # First try to discover some names to supply to the
+ # auto-completion
+ yield self._report(
+ self.principalCollection,
+ self._USER_LIST_PRINCIPAL_PROPERTY_SEARCH % {
+ 'displayname': prefix,
+ 'email': prefix,
+ 'firstname': prefix,
+ 'lastname': prefix,
+ },
+ depth=None,
+ method_label="REPORT{psearch}",
+ )
+
+ # Now learn about the attendee's availability
+ yield self.requestAvailability(
+ component.mainComponent().getStartDateUTC(),
+ component.mainComponent().getEndDateUTC(),
+ [self.email, u'mailto:' + email],
+ [component.resourceUID()]
+ )
+
+
+ @inlineCallbacks
+ def changeEventAttendee(self, href, oldAttendee, newAttendee):
+ event = self._events[href]
+ component = event.component
+
+ # Change the event to have the new attendee instead of the old attendee
+ component.mainComponent().removeProperty(oldAttendee)
+ component.mainComponent().addProperty(newAttendee)
+ okCodes = NO_CONTENT
+ headers = Headers({
+ 'content-type': ['text/calendar'],
+ })
+ if event.scheduleTag is not None:
+ headers.addRawHeader('if-schedule-tag-match', event.scheduleTag)
+ okCodes = (NO_CONTENT, PRECONDITION_FAILED,)
+
+ attendees = list(component.mainComponent().properties('ATTENDEE'))
+ label_suffix = "small"
+ if len(attendees) > 5:
+ label_suffix = "medium"
+ if len(attendees) > 20:
+ label_suffix = "large"
+ if len(attendees) > 75:
+ label_suffix = "huge"
+
+ response = yield self._request(
+ okCodes,
+ 'PUT',
+ self.server["uri"] + href.encode('utf-8'),
+ headers, StringProducer(component.getTextWithTimezones(includeTimezones=True)),
+ method_label="PUT{attendee-%s}" % (label_suffix,),
+ )
+
+ # Finally, re-retrieve the event to update the etag
+ yield self._updateEvent(response, href)
+
+
+ @inlineCallbacks
+ def deleteEvent(self, href):
+ """
+ Issue a DELETE for the given URL and remove local state
+ associated with that event.
+ """
+
+ self._removeEvent(href)
+
+ response = yield self._request(
+ (NO_CONTENT, NOT_FOUND),
+ 'DELETE',
+ self.server["uri"] + href.encode('utf-8'),
+ method_label="DELETE{event}",
+ )
+ returnValue(response)
+
+
+ @inlineCallbacks
+ def addEvent(self, href, component, invite=False):
+ headers = Headers({
+ 'content-type': ['text/calendar'],
+ })
+
+ attendees = list(component.mainComponent().properties('ATTENDEE'))
+ label_suffix = "small"
+ if len(attendees) > 5:
+ label_suffix = "medium"
+ if len(attendees) > 20:
+ label_suffix = "large"
+ if len(attendees) > 75:
+ label_suffix = "huge"
+
+ response = yield self._request(
+ CREATED,
+ 'PUT',
+ self.server["uri"] + href.encode('utf-8'),
+ headers,
+ StringProducer(component.getTextWithTimezones(includeTimezones=True)),
+ method_label="PUT{organizer-%s}" % (label_suffix,) if invite else "PUT{event}",
+ )
+ self._localUpdateEvent(response, href, component)
+
+
+ @inlineCallbacks
+ def addInvite(self, href, component):
+ """
+ Add an event that is an invite - i.e., has attendees. We will do attendee lookups and freebusy
+ checks on each attendee to simulate what happens when an organizer creates a new invite.
+ """
+
+ # Do lookup and free busy of each attendee (not self)
+ attendees = list(component.mainComponent().properties('ATTENDEE'))
+ for attendee in attendees:
+ if attendee.value() in (self.uuid, self.email):
+ continue
+ yield self._attendeeAutoComplete(component, attendee)
+
+ # Now do a normal PUT
+ yield self.addEvent(href, component, invite=True)
+
+
+ @inlineCallbacks
+ def changeEvent(self, href):
+
+ event = self._events[href]
+ component = event.component
+
+ # At last, upload the new event definition
+ response = yield self._request(
+ (NO_CONTENT, PRECONDITION_FAILED,),
+ 'PUT',
+ self.server["uri"] + href.encode('utf-8'),
+ Headers({
+ 'content-type': ['text/calendar'],
+ 'if-match': [event.etag]
+ }),
+ StringProducer(component.getTextWithTimezones(includeTimezones=True)),
+ method_label="PUT{update}"
+ )
+
+ # Finally, re-retrieve the event to update the etag
+ yield self._updateEvent(response, href)
+
+
+ def _localUpdateEvent(self, response, href, component):
+ headers = response.headers
+ etag = headers.getRawHeaders("etag", [None])[0]
+ scheduleTag = headers.getRawHeaders("schedule-tag", [None])[0]
+
+ event = Event(self.serializeLocation(), href, etag, component)
+ event.scheduleTag = scheduleTag
+ self._setEvent(href, event)
+
+
+ def updateEvent(self, href):
+ return self._updateEvent(None, href)
+
+
+ @inlineCallbacks
+ def _updateEvent(self, ignored, href):
+ response = yield self._request(
+ OK,
+ 'GET',
+ self.server["uri"] + href.encode('utf-8'),
+ method_label="GET{event}",
+ )
+ headers = response.headers
+ etag = headers.getRawHeaders('etag')[0]
+ scheduleTag = headers.getRawHeaders('schedule-tag', [None])[0]
+ body = yield readBody(response)
+ self.eventChanged(href, etag, scheduleTag, body)
+
+
+ @inlineCallbacks
+ def 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.server["uri"] + self.outbox
+
+ 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.compareDate(end):
+ msg("Availability request spanning multiple days (%r to %r), "
+ "dropping the end date." % (start, end))
+
+ start.setTimezone(Timezone.UTCTimezone)
+ start.setHHMMSS(0, 0, 0)
+ end = start + Duration(hours=24)
+
+ start = start.getText()
+ end = end.getText()
+ now = DateTime.getNowUTC().getText()
+
+ label_suffix = "small"
+ if len(users) > 5:
+ label_suffix = "medium"
+ if len(users) > 20:
+ label_suffix = "large"
+ if len(users) > 75:
+ label_suffix = "huge"
+
+ response = yield 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,
+ }),
+ method_label="POST{fb-%s}" % (label_suffix,),
+ )
+ body = yield readBody(response)
+ returnValue(body)
+
+
+ @inlineCallbacks
+ def postAttachment(self, href, content):
+ url = self.server["uri"] + "{0}?{1}".format(href, "action=attachment-add")
+ filename = 'file-{}.txt'.format(len(content))
+ headers = Headers({
+ 'Content-Disposition': ['attachment; filename="{}"'.format(filename)]
+ })
+ response = yield self._request(
+ CREATED,
+ 'POST',
+ url,
+ headers=headers,
+ body=StringProducer(content),
+ method_label="POST{attach}"
+ )
+ body = yield readBody(response)
+
+ # We don't want to download an attachment we uploaded, so look for the
+ # Cal-Managed-Id: and Location: headers and remember those
+ managedId = response.headers.getRawHeaders("Cal-Managed-Id")[0]
+ location = response.headers.getRawHeaders("Location")[0]
+ self._attachments[managedId] = location
+
+ yield self.updateEvent(href)
+ returnValue(body)
+
+
+ @inlineCallbacks
+ def getAttachment(self, href, managedId):
+
+ # If we've already downloaded this managedId, skip it.
+ if managedId not in self._attachments:
+ self._attachments[managedId] = href
+ yield self._newOperation("download", self._get(href, 200))
+
+
+ @inlineCallbacks
+ def postXML(self, href, content, label):
+ headers = Headers({
+ 'content-type': ['text/xml']
+ })
+ response = yield self._request(
+ (OK, CREATED, MULTI_STATUS),
+ 'POST',
+ self.server["uri"] + href,
+ headers=headers,
+ body=StringProducer(content),
+ method_label=label
+ )
+ body = yield readBody(response)
+ returnValue(body)
+
+
+
+class OS_X_10_6(BaseAppleClient):
+ """
+ Implementation of the OS X 10.6 iCal network behavior.
+
+ Anything OS X 10.6 iCal does on its own, or any particular
+ network behaviors it takes in response to a user action, belong on
+ this class.
+
+ Usage-profile based behaviors ("the user modifies an event every
+ 3.2 minutes") belong elsewhere.
+ """
+
+ _client_type = "OS X 10.6"
+
+ USER_AGENT = "DAVKit/4.0.3 (732); CalendarStore/4.0.3 (991); iCal/4.0.3 (1388); Mac OS X/10.6.4 (10F569)"
+
+ # The default interval, used if none is specified in external
+ # configuration. This is also the actual value used by Snow
+ # Leopard iCal.
+ CALENDAR_HOME_POLL_INTERVAL = 15 * 60
+
+ # The maximum number of resources to retrieve in a single multiget
+ MULTIGET_BATCH_SIZE = 200
+
+ # Override and turn on if client supports Sync REPORT
+ _SYNC_REPORT = False
+
+ # Override and turn on if client syncs using time-range queries
+ _SYNC_TIMERANGE = False
+
+ # Override and turn off if client does not support attendee lookups
+ _ATTENDEE_LOOKUPS = True
+
+ # Request body data
+ _LOAD_PATH = "OS_X_10_6"
+
+ _STARTUP_WELL_KNOWN = loadRequestBody(_LOAD_PATH, 'startup_well_known')
+ _STARTUP_PRINCIPAL_PROPFIND_INITIAL = loadRequestBody(_LOAD_PATH, 'startup_principal_propfind_initial')
+ _STARTUP_PRINCIPAL_PROPFIND = loadRequestBody(_LOAD_PATH, 'startup_principal_propfind')
+ _STARTUP_PRINCIPALS_REPORT = loadRequestBody(_LOAD_PATH, 'startup_principals_report')
+ _STARTUP_PRINCIPAL_EXPAND = loadRequestBody(_LOAD_PATH, 'startup_principal_expand')
+ _STARTUP_PROPPATCH_CALENDAR_COLOR = loadRequestBody(_LOAD_PATH, 'startup_calendar_color_proppatch')
+ _STARTUP_PROPPATCH_CALENDAR_ORDER = loadRequestBody(_LOAD_PATH, 'startup_calendar_order_proppatch')
+ _STARTUP_PROPPATCH_CALENDAR_TIMEZONE = loadRequestBody(_LOAD_PATH, 'startup_calendar_timezone_proppatch')
+
+ _POLL_CALENDARHOME_PROPFIND = loadRequestBody(_LOAD_PATH, 'poll_calendarhome_propfind')
+ _POLL_CALENDAR_PROPFIND = loadRequestBody(_LOAD_PATH, 'poll_calendar_propfind')
+ _POLL_CALENDAR_PROPFIND_D1 = loadRequestBody(_LOAD_PATH, 'poll_calendar_propfind_d1')
+ _POLL_CALENDAR_MULTIGET_REPORT = loadRequestBody(_LOAD_PATH, 'poll_calendar_multiget')
+ _POLL_CALENDAR_MULTIGET_REPORT_HREF = loadRequestBody(_LOAD_PATH, 'poll_calendar_multiget_hrefs')
+ _POLL_CALENDAR_SYNC_REPORT = None
+ _POLL_NOTIFICATION_PROPFIND = loadRequestBody(_LOAD_PATH, 'poll_calendar_propfind')
+ _POLL_NOTIFICATION_PROPFIND_D1 = loadRequestBody(_LOAD_PATH, 'poll_notification_propfind_d1')
+
+ _USER_LIST_PRINCIPAL_PROPERTY_SEARCH = loadRequestBody(_LOAD_PATH, 'user_list_principal_property_search')
+ _POST_AVAILABILITY = loadRequestBody(_LOAD_PATH, 'post_availability')
+
+ @inlineCallbacks
+ def startup(self):
+
+ # Try to read data from disk - if it succeeds self.principalURL will be set
+ self.deserialize()
+
+ if self.principalURL is None:
+ # PROPFIND principal path to retrieve actual principal-URL
+ response = yield self._principalPropfindInitial(self.record.uid)
+ hrefs = response.getHrefProperties()
+ self.principalURL = hrefs[davxml.principal_URL].toString()
+
+ # Using the actual principal URL, retrieve principal information
+ principal = (yield self._extractPrincipalDetails())
+ returnValue(principal)
+
+
+
+class OS_X_10_7(BaseAppleClient):
+ """
+ Implementation of the OS X 10.7 iCal network behavior.
+ """
+
+ _client_type = "OS X 10.7"
+
+ USER_AGENT = "CalendarStore/5.0.2 (1166); iCal/5.0.2 (1571); Mac OS X/10.7.3 (11D50)"
+
+ # The default interval, used if none is specified in external
+ # configuration. This is also the actual value used by Snow
+ # Leopard iCal.
+ CALENDAR_HOME_POLL_INTERVAL = 15 * 60
+
+ # The maximum number of resources to retrieve in a single multiget
+ MULTIGET_BATCH_SIZE = 50
+
+ # Override and turn on if client supports Sync REPORT
+ _SYNC_REPORT = True
+
+ # Override and turn on if client syncs using time-range queries
+ _SYNC_TIMERANGE = False
+
+ # Override and turn off if client does not support attendee lookups
+ _ATTENDEE_LOOKUPS = True
+
+ # Request body data
+ _LOAD_PATH = "OS_X_10_7"
+
+ _STARTUP_WELL_KNOWN = loadRequestBody(_LOAD_PATH, 'startup_well_known')
+ _STARTUP_PRINCIPAL_PROPFIND_INITIAL = loadRequestBody(_LOAD_PATH, 'startup_principal_propfind_initial')
+ _STARTUP_PRINCIPAL_PROPFIND = loadRequestBody(_LOAD_PATH, 'startup_principal_propfind')
+ _STARTUP_PRINCIPALS_REPORT = loadRequestBody(_LOAD_PATH, 'startup_principals_report')
+ _STARTUP_PRINCIPAL_EXPAND = loadRequestBody(_LOAD_PATH, 'startup_principal_expand')
+ _STARTUP_PROPPATCH_CALENDAR_COLOR = loadRequestBody(_LOAD_PATH, 'startup_calendar_color_proppatch')
+ _STARTUP_PROPPATCH_CALENDAR_ORDER = loadRequestBody(_LOAD_PATH, 'startup_calendar_order_proppatch')
+ _STARTUP_PROPPATCH_CALENDAR_TIMEZONE = loadRequestBody(_LOAD_PATH, 'startup_calendar_timezone_proppatch')
+
+ _POLL_CALENDARHOME_PROPFIND = loadRequestBody(_LOAD_PATH, 'poll_calendarhome_propfind')
+ _POLL_CALENDAR_PROPFIND = loadRequestBody(_LOAD_PATH, 'poll_calendar_propfind')
+ _POLL_CALENDAR_PROPFIND_D1 = loadRequestBody(_LOAD_PATH, 'poll_calendar_propfind_d1')
+ _POLL_CALENDAR_MULTIGET_REPORT = loadRequestBody(_LOAD_PATH, 'poll_calendar_multiget')
+ _POLL_CALENDAR_MULTIGET_REPORT_HREF = loadRequestBody(_LOAD_PATH, 'poll_calendar_multiget_hrefs')
+ _POLL_CALENDAR_SYNC_REPORT = loadRequestBody(_LOAD_PATH, 'poll_calendar_sync')
+ _POLL_NOTIFICATION_PROPFIND = loadRequestBody(_LOAD_PATH, 'poll_calendar_propfind')
+ _POLL_NOTIFICATION_PROPFIND_D1 = loadRequestBody(_LOAD_PATH, 'poll_notification_propfind_d1')
+
+ _USER_LIST_PRINCIPAL_PROPERTY_SEARCH = loadRequestBody(_LOAD_PATH, 'user_list_principal_property_search')
+ _POST_AVAILABILITY = loadRequestBody(_LOAD_PATH, 'post_availability')
+
+
+ def _addDefaultHeaders(self, headers):
+ """
+ Add the clients default set of headers to ones being used in a request.
+ Default is to add User-Agent, sub-classes should override to add other
+ client specific things, Accept etc.
+ """
+
+ super(OS_X_10_7, self)._addDefaultHeaders(headers)
+ headers.setRawHeaders('Accept', ['*/*'])
+ headers.setRawHeaders('Accept-Language', ['en-us'])
+ headers.setRawHeaders('Accept-Encoding', ['gzip,deflate'])
+ headers.setRawHeaders('Connection', ['keep-alive'])
+
+
+ @inlineCallbacks
+ def startup(self):
+
+ # Try to read data from disk - if it succeeds self.principalURL will be set
+ self.deserialize()
+
+ if self.principalURL is None:
+ # PROPFIND well-known with redirect
+ response = yield self._startupPropfindWellKnown()
+ hrefs = response.getHrefProperties()
+ if davxml.current_user_principal in hrefs:
+ self.principalURL = hrefs[davxml.current_user_principal].toString()
+ elif davxml.principal_URL in hrefs:
+ self.principalURL = hrefs[davxml.principal_URL].toString()
+ else:
+ # PROPFIND principal path to retrieve actual principal-URL
+ response = yield self._principalPropfindInitial(self.record.uid)
+ hrefs = response.getHrefProperties()
+ self.principalURL = hrefs[davxml.principal_URL].toString()
+
+ # Using the actual principal URL, retrieve principal information
+ principal = yield self._extractPrincipalDetails()
+ returnValue(principal)
+
+
+
+class OS_X_10_11(BaseAppleClient):
+ """
+ Implementation of the OS X 10.11 Calendar.app network behavior.
+ """
+
+ _client_type = "OS X 10.11"
+
+ USER_AGENT = "Mac+OS+X/10.11 (15A283) CalendarAgent/361"
+
+ # The default interval, used if none is specified in external
+ # configuration. This is also the actual value used by El
+ # Capital Calendar.app.
+ CALENDAR_HOME_POLL_INTERVAL = 15 * 60 # in seconds
+
+ # The maximum number of resources to retrieve in a single multiget
+ MULTIGET_BATCH_SIZE = 50
+
+ # Override and turn on if client supports Sync REPORT
+ _SYNC_REPORT = True
+
+ # Override and turn off if client does not support attendee lookups
+ _ATTENDEE_LOOKUPS = True
+
+ # Request body data
+ _LOAD_PATH = "OS_X_10_11"
+
+ _STARTUP_WELL_KNOWN = loadRequestBody(_LOAD_PATH, 'startup_well_known_propfind')
+ _STARTUP_PRINCIPAL_PROPFIND_INITIAL = loadRequestBody(_LOAD_PATH, 'startup_principal_initial_propfind')
+ _STARTUP_PRINCIPAL_PROPFIND = loadRequestBody(_LOAD_PATH, 'startup_principal_propfind')
+ _STARTUP_PRINCIPALS_REPORT = loadRequestBody(_LOAD_PATH, 'startup_principals_report')
+ _STARTUP_PRINCIPAL_EXPAND = loadRequestBody(_LOAD_PATH, 'startup_principal_expand')
+
+ _STARTUP_CREATE_CALENDAR = loadRequestBody(_LOAD_PATH, 'startup_create_calendar')
+ _STARTUP_PROPPATCH_CALENDAR_COLOR = loadRequestBody(_LOAD_PATH, 'startup_calendar_color_proppatch')
+ # _STARTUP_PROPPATCH_CALENDAR_NAME = loadRequestBody(_LOAD_PATH, 'startup_calendar_displayname_proppatch')
+ _STARTUP_PROPPATCH_CALENDAR_ORDER = loadRequestBody(_LOAD_PATH, 'startup_calendar_order_proppatch')
+ _STARTUP_PROPPATCH_CALENDAR_TIMEZONE = loadRequestBody(_LOAD_PATH, 'startup_calendar_timezone_proppatch')
+
+ _POLL_CALENDARHOME_PROPFIND = loadRequestBody(_LOAD_PATH, 'poll_calendarhome_depth1_propfind')
+ _POLL_CALENDAR_PROPFIND = loadRequestBody(_LOAD_PATH, 'poll_calendar_propfind')
+ _POLL_CALENDAR_PROPFIND_D1 = loadRequestBody(_LOAD_PATH, 'poll_calendar_depth1_propfind')
+ _POLL_CALENDAR_MULTIGET_REPORT = loadRequestBody('OS_X_10_7', 'poll_calendar_multiget')
+ _POLL_CALENDAR_MULTIGET_REPORT_HREF = loadRequestBody('OS_X_10_7', 'poll_calendar_multiget_hrefs')
+ _POLL_CALENDAR_SYNC_REPORT = loadRequestBody('OS_X_10_7', 'poll_calendar_sync')
+ _POLL_NOTIFICATION_PROPFIND = loadRequestBody(_LOAD_PATH, 'poll_calendar_propfind')
+ _POLL_NOTIFICATION_PROPFIND_D1 = loadRequestBody(_LOAD_PATH, 'poll_notification_depth1_propfind')
+
+ _NOTIFICATION_SYNC_REPORT = loadRequestBody(_LOAD_PATH, 'notification_sync')
+
+ _USER_LIST_PRINCIPAL_PROPERTY_SEARCH = loadRequestBody('OS_X_10_7', 'user_list_principal_property_search')
+ _POST_AVAILABILITY = loadRequestBody('OS_X_10_7', 'post_availability')
+
+ _CALENDARSERVER_PRINCIPAL_SEARCH_REPORT = loadRequestBody(_LOAD_PATH, 'principal_search_report')
+
+
+ def _addDefaultHeaders(self, headers):
+ """
+ Add the clients default set of headers to ones being used in a request.
+ Default is to add User-Agent, sub-classes should override to add other
+ client specific things, Accept etc.
+ """
+
+ super(OS_X_10_11, self)._addDefaultHeaders(headers)
+ headers.setRawHeaders('Accept', ['*/*'])
+ headers.setRawHeaders('Accept-Language', ['en-us'])
+ headers.setRawHeaders('Accept-Encoding', ['gzip,deflate'])
+ headers.setRawHeaders('Connection', ['keep-alive'])
+
+
+ @inlineCallbacks
+ def startup(self):
+ # Try to read data from disk - if it succeeds self.principalURL will be set
+ self.deserialize()
+
+ if self.principalURL is None:
+ # PROPFIND well-known with redirect
+ response = yield self._startupPropfindWellKnown()
+ hrefs = response.getHrefProperties()
+ if davxml.current_user_principal in hrefs:
+ self.principalURL = hrefs[davxml.current_user_principal].toString()
+ elif davxml.principal_URL in hrefs:
+ self.principalURL = hrefs[davxml.principal_URL].toString()
+ else:
+ # PROPFIND principal path to retrieve actual principal-URL
+ response = yield self._principalPropfindInitial(self.record.uid)
+ hrefs = response.getHrefProperties()
+ self.principalURL = hrefs[davxml.principal_URL].toString()
+
+ # Using the actual principal URL, retrieve principal information
+ principal = yield self._extractPrincipalDetails()
+ returnValue(principal)
+
+
+
+class iOS_5(BaseAppleClient):
+ """
+ Implementation of the iOS 5 network behavior.
+ """
+
+ _client_type = "iOS 5"
+
+ USER_AGENT = "iOS/5.1 (9B179) dataaccessd/1.0"
+
+ # The default interval, used if none is specified in external
+ # configuration. This is also the actual value used by Snow
+ # Leopard iCal.
+ CALENDAR_HOME_POLL_INTERVAL = 15 * 60
+
+ # The maximum number of resources to retrieve in a single multiget
+ MULTIGET_BATCH_SIZE = 50
+
+ # Override and turn on if client supports Sync REPORT
+ _SYNC_REPORT = False
+
+ # Override and turn on if client syncs using time-range queries
+ _SYNC_TIMERANGE = True
+
+ # Override and turn off if client does not support attendee lookups
+ _ATTENDEE_LOOKUPS = False
+
+ # Request body data
+ _LOAD_PATH = "iOS_5"
+
+ _STARTUP_WELL_KNOWN = loadRequestBody(_LOAD_PATH, 'startup_well_known')
+ _STARTUP_PRINCIPAL_PROPFIND_INITIAL = loadRequestBody(_LOAD_PATH, 'startup_principal_propfind_initial')
+ _STARTUP_PRINCIPAL_PROPFIND = loadRequestBody(_LOAD_PATH, 'startup_principal_propfind')
+ _STARTUP_PRINCIPALS_REPORT = loadRequestBody(_LOAD_PATH, 'startup_principals_report')
+ _STARTUP_PROPPATCH_CALENDAR_COLOR = loadRequestBody(_LOAD_PATH, 'startup_calendar_color_proppatch')
+ _STARTUP_PROPPATCH_CALENDAR_ORDER = loadRequestBody(_LOAD_PATH, 'startup_calendar_order_proppatch')
+
+ _POLL_CALENDARHOME_PROPFIND = loadRequestBody(_LOAD_PATH, 'poll_calendarhome_propfind')
+ _POLL_CALENDAR_PROPFIND = loadRequestBody(_LOAD_PATH, 'poll_calendar_propfind')
+ _POLL_CALENDAR_VEVENT_TR_QUERY = loadRequestBody(_LOAD_PATH, 'poll_calendar_vevent_tr_query')
+ _POLL_CALENDAR_VTODO_QUERY = loadRequestBody(_LOAD_PATH, 'poll_calendar_vtodo_query')
+ _POLL_CALENDAR_PROPFIND_D1 = loadRequestBody(_LOAD_PATH, 'poll_calendar_propfind_d1')
+ _POLL_CALENDAR_MULTIGET_REPORT = loadRequestBody(_LOAD_PATH, 'poll_calendar_multiget')
+ _POLL_CALENDAR_MULTIGET_REPORT_HREF = loadRequestBody(_LOAD_PATH, 'poll_calendar_multiget_hrefs')
+
+
+ def _addDefaultHeaders(self, headers):
+ """
+ Add the clients default set of headers to ones being used in a request.
+ Default is to add User-Agent, sub-classes should override to add other
+ client specific things, Accept etc.
+ """
+
+ super(iOS_5, self)._addDefaultHeaders(headers)
+ headers.setRawHeaders('Accept', ['*/*'])
+ headers.setRawHeaders('Accept-Language', ['en-us'])
+ headers.setRawHeaders('Accept-Encoding', ['gzip,deflate'])
+ headers.setRawHeaders('Connection', ['keep-alive'])
+
+
+ @inlineCallbacks
+ def _pollFirstTime1(self, homeNode, calendars):
+ # Patch calendar properties
+ for cal in calendars:
+ if cal.name != "inbox":
+ yield self._proppatch(
+ cal.url,
+ self._STARTUP_PROPPATCH_CALENDAR_COLOR,
+ method_label="PROPPATCH{calendar}",
+ )
+ yield self._proppatch(
+ cal.url,
+ self._STARTUP_PROPPATCH_CALENDAR_ORDER,
+ method_label="PROPPATCH{calendar}",
+ )
+
+
+ def _pollFirstTime2(self):
+ # Nothing here
+ return succeed(None)
+
+
+ def _updateCalendar(self, calendar, newToken):
+ """
+ Update the local cached data for a calendar in an appropriate manner.
+ """
+ if calendar.name == "inbox":
+ # Inbox is done as a PROPFIND Depth:1
+ return self._updateCalendar_PROPFIND(calendar, newToken)
+ elif "VEVENT" in calendar.componentTypes:
+ # VEVENTs done as time-range VEVENT-only queries
+ return self._updateCalendar_VEVENT(calendar, newToken)
+ elif "VTODO" in calendar.componentTypes:
+ # VTODOs done as VTODO-only queries
+ return self._updateCalendar_VTODO(calendar, newToken)
+
+
+ @inlineCallbacks
+ def _updateCalendar_VEVENT(self, calendar, newToken):
+ """
+ Sync all locally cached VEVENTs using a VEVENT-only time-range query.
+ """
+
+ # Grab old hrefs prior to the PROPFIND so we sync with the old state. We need this because
+ # the sim can fire a PUT between the PROPFIND and when process the removals.
+ old_hrefs = set([calendar.url + child for child in calendar.events.keys()])
+
+ now = DateTime.getNowUTC()
+ now.setDateOnly(True)
+ now.offsetMonth(-1) # 1 month back default
+ result = yield self._report(
+ calendar.url,
+ self._POLL_CALENDAR_VEVENT_TR_QUERY % {"start-date": now.getText()},
+ depth='1',
+ method_label="REPORT{vevent}",
+ )
+
+ yield self._updateApplyChanges(calendar, result, old_hrefs)
+
+ # Now update calendar to the new token
+ self._calendars[calendar.url].changeToken = newToken
+
+
+ @inlineCallbacks
+ def _updateCalendar_VTODO(self, calendar, newToken):
+ """
+ Sync all locally cached VTODOs using a VTODO-only query.
+ """
+
+ # Grab old hrefs prior to the PROPFIND so we sync with the old state. We need this because
+ # the sim can fire a PUT between the PROPFIND and when process the removals.
+ old_hrefs = set([calendar.url + child for child in calendar.events.keys()])
+
+ result = yield self._report(
+ calendar.url,
+ self._POLL_CALENDAR_VTODO_QUERY,
+ depth='1',
+ method_label="REPORT{vtodo}",
+ )
+
+ yield self._updateApplyChanges(calendar, result, old_hrefs)
+
+ # Now update calendar to the new token
+ self._calendars[calendar.url].changeToken = newToken
+
+
+ @inlineCallbacks
+ def startup(self):
+
+ # Try to read data from disk - if it succeeds self.principalURL will be set
+ self.deserialize()
+
+ if self.principalURL is None:
+ # PROPFIND well-known with redirect
+ response = yield self._startupPropfindWellKnown()
+ hrefs = response.getHrefProperties()
+ if davxml.current_user_principal in hrefs:
+ self.principalURL = hrefs[davxml.current_user_principal].toString()
+ elif davxml.principal_URL in hrefs:
+ self.principalURL = hrefs[davxml.principal_URL].toString()
+ else:
+ # PROPFIND principal path to retrieve actual principal-URL
+ response = yield self._principalPropfindInitial(self.record.uid)
+ hrefs = response.getHrefProperties()
+ self.principalURL = hrefs[davxml.principal_URL].toString()
+
+ # Using the actual principal URL, retrieve principal information
+ principal = yield self._extractPrincipalDetails()
+ returnValue(principal)
Added: CalendarServer/trunk/simplugin/caldavprofile.py
===================================================================
--- CalendarServer/trunk/simplugin/caldavprofile.py (rev 0)
+++ CalendarServer/trunk/simplugin/caldavprofile.py 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1,1166 @@
+##
+# Copyright (c) 2011-2016 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.
+#
+##
+
+"""
+Implementation of specific end-user behaviors.
+"""
+
+from __future__ import division
+
+import json
+import sys
+from uuid import uuid4
+
+from caldavclientlibrary.protocol.caldav.definitions import caldavxml
+
+from twisted.python import context
+from twisted.python.log import msg
+from twisted.python.failure import Failure
+from twisted.internet.defer import Deferred, succeed, inlineCallbacks, returnValue
+from twisted.internet.task import LoopingCall
+from twisted.web.http import PRECONDITION_FAILED
+
+from twistedcaldav.ical import Property, Component
+
+from clientsim.framework.stats import NearFutureDistribution, NormalDistribution, UniformDiscreteDistribution, mean, median
+from clientsim.framework.stats import LogNormalDistribution, RecurrenceDistribution
+from clientsim.framework.logger import SummarizingMixin
+from clientsim.framework.baseclient import IncorrectResponseCode
+from clientsim.framework.baseprofile import ProfileBase as SuperProfileBase, loopWithDistribution
+from simplugin.caldavclient import Calendar
+
+from pycalendar.datetime import DateTime
+from pycalendar.duration import Duration
+
+from datetime import datetime
+
+class ProfileBase(SuperProfileBase):
+
+ def _calendarsOfType(self, calendarType, componentType, justOwned=False):
+ results = []
+
+ for cal in self._client._calendars.itervalues():
+ if cal.resourceType == calendarType and componentType in cal.componentTypes:
+ if justOwned:
+ if (not cal.shared) or cal.sharedByMe:
+ results.append(cal)
+ else:
+ results.append(cal)
+
+ return results
+
+
+ def _getRandomCalendarOfType(self, componentType, justOwned=False):
+ """
+ Return a random L{Calendar} object from the current user
+ or C{None} if there are no calendars to work with
+ """
+ calendars = self._calendarsOfType(caldavxml.calendar, componentType, justOwned=justOwned)
+ if not calendars:
+ return None
+ # Choose a random calendar
+ calendar = self.random.choice(calendars)
+ return calendar
+
+
+ def _getRandomEventOfType(self, componentType, justOwned=True):
+ """
+ Return a random L{Event} object from the current user
+ or C{None} if there are no events to work with
+ """
+ calendars = self._calendarsOfType(caldavxml.calendar, componentType, justOwned=justOwned)
+ while calendars:
+ calendar = self.random.choice(calendars)
+ calendars.remove(calendar)
+ if not calendar.events:
+ continue
+
+ events = calendar.events.keys()
+ while events:
+ href = self.random.choice(events)
+ events.remove(href)
+ event = calendar.events[href]
+ if not event.component:
+ continue
+ if event.scheduleTag:
+ continue
+
+ return event
+ return None
+
+
+ def _isSelfAttendee(self, attendee):
+ """
+ Try to match one of the attendee's identifiers against one of
+ C{self._client}'s identifiers. Return C{True} if something matches,
+ C{False} otherwise.
+ """
+ return attendee.parameterValue('EMAIL') == self._client.email[len('mailto:'):]
+
+
+ def _newOperation(self, label, deferred):
+ """
+ Helper to emit a log event when a new operation is started and
+ another one when it completes.
+ """
+ # If this is a scheduled request, record the lag in the
+ # scheduling now so it can be reported when the response is
+ # received.
+ lag = context.get('lag', None)
+
+ before = self._reactor.seconds()
+ msg(
+ type="operation",
+ phase="start",
+ user=self._client.record.uid,
+ client_type=self._client.title,
+ client_id=self._client._client_id,
+ label=label,
+ lag=lag,
+ )
+
+ def finished(passthrough):
+ success = not isinstance(passthrough, Failure)
+ if not success:
+ passthrough.trap(IncorrectResponseCode)
+ passthrough = passthrough.value.response
+ after = self._reactor.seconds()
+ msg(
+ type="operation",
+ phase="end",
+ duration=after - before,
+ user=self._client.record.uid,
+ client_type=self._client.title,
+ client_id=self._client._client_id,
+ label=label,
+ success=success,
+ )
+ return passthrough
+ deferred.addBoth(finished)
+ return deferred
+
+
+ def _failedOperation(self, label, reason):
+ """
+ Helper to emit a log event when an operation fails.
+ """
+ msg(
+ type="operation",
+ phase="failed",
+ user=self._client.record.uid,
+ client_type=self._client.title,
+ client_id=self._client._client_id,
+ label=label,
+ reason=reason,
+ )
+ self._sim._simFailure("%s: %s" % (label, reason,), self._reactor)
+
+
+
+class CannotAddAttendee(Exception):
+ """
+ Indicates no new attendees can be invited to a particular event.
+ """
+ pass
+
+
+
+
+
+class Inviter(ProfileBase):
+ """
+ A Calendar user who invites other users to new events.
+ """
+ _eventTemplate = Component.fromString("""\
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//iCal 4.0.3//EN
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+CREATED:20101018T155431Z
+UID:C98AD237-55AD-4F7D-9009-0D355D835822
+DTEND;TZID=America/New_York:20101021T130000
+TRANSP:OPAQUE
+SUMMARY:Simple event
+DTSTART;TZID=America/New_York:20101021T120000
+DTSTAMP:20101018T155438Z
+SEQUENCE:2
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"))
+
+
+ def setParameters(
+ self,
+ enabled=True,
+ sendInvitationDistribution=NormalDistribution(600, 60),
+ inviteeDistribution=UniformDiscreteDistribution(range(-10, 11)),
+ inviteeClumping=True,
+ inviteeCountDistribution=LogNormalDistribution(1.2, 1.2),
+ eventStartDistribution=NearFutureDistribution(),
+ eventDurationDistribution=UniformDiscreteDistribution([
+ 15 * 60, 30 * 60,
+ 45 * 60, 60 * 60,
+ 120 * 60
+ ]),
+ recurrenceDistribution=RecurrenceDistribution(False),
+ ):
+ self.enabled = enabled
+ self._sendInvitationDistribution = sendInvitationDistribution
+ self._inviteeDistribution = inviteeDistribution
+ self._inviteeClumping = inviteeClumping
+ self._inviteeCountDistribution = inviteeCountDistribution
+ self._eventStartDistribution = eventStartDistribution
+ self._eventDurationDistribution = eventDurationDistribution
+ self._recurrenceDistribution = recurrenceDistribution
+
+
+ def run(self):
+ return loopWithDistribution(
+ self._reactor, self._sendInvitationDistribution, self._invite)
+
+
+ def _addAttendee(self, event, attendees):
+ """
+ Create a new attendee to add to the list of attendees for the
+ given event.
+ """
+ selfRecord = self._sim.getUserRecord(self._number)
+ invitees = set([u'mailto:%s' % (selfRecord.email,)])
+ for att in attendees:
+ invitees.add(att.value())
+
+ for _ignore_i in range(10):
+
+ sample = self._inviteeDistribution.sample()
+ if self._inviteeClumping:
+ sample = self._number + sample
+ invitee = max(0, sample)
+
+ try:
+ record = self._sim.getUserRecord(invitee)
+ except IndexError:
+ continue
+ cuaddr = u'mailto:%s' % (record.email,)
+ if cuaddr not in invitees:
+ break
+ else:
+ raise CannotAddAttendee("Can't find uninvited user to invite.")
+
+ attendee = Property(
+ name=u'ATTENDEE',
+ value=cuaddr.encode("utf-8"),
+ params={
+ 'CN': record.commonName,
+ 'CUTYPE': 'INDIVIDUAL',
+ 'PARTSTAT': 'NEEDS-ACTION',
+ 'ROLE': 'REQ-PARTICIPANT',
+ 'RSVP': 'TRUE',
+ },
+ )
+
+ event.addProperty(attendee)
+ attendees.append(attendee)
+
+
+ def _invite(self):
+ """
+ Try to add a new event, or perhaps remove an
+ existing attendee from an event.
+
+ @return: C{None} if there are no events to play with,
+ otherwise a L{Deferred} which fires when the attendee
+ change has been made.
+ """
+
+ if not self._client.started:
+ return succeed(None)
+
+ # Find calendars which are eligible for invites
+ calendars = self._calendarsOfType(caldavxml.calendar, "VEVENT")
+
+ while calendars:
+ # Pick one at random from which to try to create an event
+ # to modify.
+ calendar = self.random.choice(calendars)
+ calendars.remove(calendar)
+
+ # Copy the template event and fill in some of its fields
+ # to make a new event to create on the calendar.
+ vcalendar = self._eventTemplate.duplicate()
+ vevent = vcalendar.mainComponent()
+ uid = str(uuid4())
+ dtstart = self._eventStartDistribution.sample()
+ dtend = dtstart + Duration(seconds=self._eventDurationDistribution.sample())
+ vevent.replaceProperty(Property("CREATED", DateTime.getNowUTC()))
+ vevent.replaceProperty(Property("DTSTAMP", DateTime.getNowUTC()))
+ vevent.replaceProperty(Property("DTSTART", dtstart))
+ vevent.replaceProperty(Property("DTEND", dtend))
+ vevent.replaceProperty(Property("UID", uid))
+
+ rrule = self._recurrenceDistribution.sample()
+ if rrule is not None:
+ vevent.addProperty(Property(None, None, None, pycalendar=rrule))
+
+ vevent.addProperty(self._client._makeSelfOrganizer())
+ vevent.addProperty(self._client._makeSelfAttendee())
+
+ attendees = list(vevent.properties('ATTENDEE'))
+ for _ignore in range(int(self._inviteeCountDistribution.sample())):
+ try:
+ self._addAttendee(vevent, attendees)
+ except CannotAddAttendee:
+ continue
+
+ href = '%s%s.ics' % (calendar.url, uid)
+ d = self._client.addInvite(href, vcalendar)
+ return self._newOperation("invite", d)
+
+
+
+class Accepter(ProfileBase):
+ """
+ A Calendar user who accepts invitations to events. As well as accepting requests, this
+ will also remove cancels and replies.
+ """
+ def setParameters(
+ self,
+ enabled=True,
+ acceptDelayDistribution=NormalDistribution(1200, 60)
+ ):
+ self.enabled = enabled
+ 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):
+ # Just respond to normal calendar events
+ calendar = href.rsplit('/', 1)[0] + '/'
+ try:
+ calendar = self._client._calendars[calendar]
+ except KeyError:
+ return
+
+ if calendar.resourceType == caldavxml.schedule_inbox:
+ # Handle inbox differently
+ self.inboxEventChanged(calendar, href)
+ elif calendar.resourceType == caldavxml.calendar:
+ self.calendarEventChanged(calendar, href)
+ else:
+ return
+
+
+ def calendarEventChanged(self, calendar, href):
+ if href in self._accepting:
+ return
+
+ component = self._client._events[href].component
+ # Check to see if this user is in the attendee list in the
+ # NEEDS-ACTION PARTSTAT.
+ attendees = tuple(component.mainComponent().properties('ATTENDEE'))
+ for attendee in attendees:
+ if self._isSelfAttendee(attendee):
+ if attendee.parameterValue('PARTSTAT') == 'NEEDS-ACTION':
+ delay = self._acceptDelayDistribution.sample()
+ self._accepting.add(href)
+ self._reactor.callLater(
+ delay, self._acceptInvitation, href, attendee)
+
+
+ def inboxEventChanged(self, calendar, href):
+ if href in self._accepting:
+ return
+
+ component = self._client._events[href].component
+ method = component.propertyValue('METHOD')
+ if method == "REPLY":
+ # Replies are immediately deleted
+ self._accepting.add(href)
+ self._reactor.callLater(
+ 0, self._handleReply, href)
+
+ elif method == "CANCEL":
+ # Cancels are handled after a user delay
+ delay = self._acceptDelayDistribution.sample()
+ self._accepting.add(href)
+ self._reactor.callLater(
+ delay, self._handleCancel, href)
+
+
+ def _acceptInvitation(self, href, attendee):
+ def change():
+ accepted = self._makeAcceptedAttendee(attendee)
+ return self._client.changeEventAttendee(href, attendee, accepted)
+ d = change()
+
+ def scheduleError(reason):
+ reason.trap(IncorrectResponseCode)
+ if reason.value.response.code != PRECONDITION_FAILED:
+ return reason.value.response.code
+
+ # Download the event again and attempt to make the change
+ # to the attendee list again.
+ d = self._client.updateEvent(href)
+ def cbUpdated(ignored):
+ d = change()
+ d.addErrback(scheduleError)
+ return d
+ d.addCallback(cbUpdated)
+ return d
+ d.addErrback(scheduleError)
+
+ def accepted(ignored):
+ # Find the corresponding event in the inbox and delete it.
+ uid = self._client._events[href].getUID()
+ for cal in self._client._calendars.itervalues():
+ if cal.resourceType == caldavxml.schedule_inbox:
+ for event in cal.events.itervalues():
+ if uid == event.getUID():
+ return self._client.deleteEvent(event.url)
+ d.addCallback(accepted)
+ def finished(passthrough):
+ self._accepting.remove(href)
+ return passthrough
+ d.addBoth(finished)
+ return self._newOperation("accept", d)
+
+
+ def _handleReply(self, href):
+ d = self._client.deleteEvent(href)
+ d.addBoth(self._finishRemoveAccepting, href)
+ return self._newOperation("reply done", d)
+
+
+ def _finishRemoveAccepting(self, passthrough, href):
+ self._accepting.remove(href)
+ if isinstance(passthrough, Failure):
+ passthrough.trap(IncorrectResponseCode)
+ passthrough = passthrough.value.response
+ return passthrough
+
+
+ def _handleCancel(self, href):
+
+ uid = self._client._events[href].getUID()
+ d = self._client.deleteEvent(href)
+
+ def removed(ignored):
+ # Find the corresponding event in any calendar and delete it.
+ for cal in self._client._calendars.itervalues():
+ if cal.resourceType == caldavxml.calendar:
+ for event in cal.events.itervalues():
+ if uid == event.getUID():
+ return self._client.deleteEvent(event.url)
+ d.addCallback(removed)
+ d.addBoth(self._finishRemoveAccepting, href)
+ return self._newOperation("cancelled", d)
+
+
+ def _makeAcceptedAttendee(self, attendee):
+ accepted = attendee.duplicate()
+ accepted.setParameter('PARTSTAT', 'ACCEPTED')
+ accepted.removeParameter('RSVP')
+ return accepted
+
+
+
+class AttachmentDownloader(ProfileBase):
+ """
+ A Calendar user who downloads attachments.
+ """
+ def setParameters(
+ self,
+ enabled=True,
+ ):
+ self.enabled = enabled
+
+
+ def run(self):
+ self._subscription = self._client.catalog["eventChanged"].subscribe(self.eventChanged)
+ return Deferred()
+
+
+ def eventChanged(self, href):
+ # Just respond to normal calendar events
+ calendar = href.rsplit('/', 1)[0] + '/'
+ try:
+ calendar = self._client._calendars[calendar]
+ except KeyError:
+ return
+
+ if calendar.resourceType == caldavxml.calendar:
+ self.calendarEventChanged(calendar, href)
+
+
+ def calendarEventChanged(self, calendar, href):
+ component = self._client._events[href].component
+ attachments = tuple(component.mainComponent().properties('ATTACH'))
+ if attachments:
+ for attachment in attachments:
+ attachmentHref = attachment.value()
+ managedId = attachment.parameterValue('MANAGED-ID')
+ self._reactor.callLater(
+ 0, self._client.getAttachment, attachmentHref, managedId
+ )
+
+
+
+class Eventer(ProfileBase):
+ """
+ A Calendar user who creates new events.
+ """
+ _eventTemplate = Component.fromString("""\
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//iCal 4.0.3//EN
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+CREATED:20101018T155431Z
+UID:C98AD237-55AD-4F7D-9009-0D355D835822
+DTEND;TZID=America/New_York:20101021T130000
+TRANSP:OPAQUE
+SUMMARY:Simple event
+DTSTART;TZID=America/New_York:20101021T120000
+DTSTAMP:20101018T155438Z
+SEQUENCE:2
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"))
+
+ def setParameters(
+ self,
+ enabled=True,
+ interval=25,
+ eventStartDistribution=NearFutureDistribution(),
+ eventDurationDistribution=UniformDiscreteDistribution([
+ 15 * 60, 30 * 60,
+ 45 * 60, 60 * 60,
+ 120 * 60
+ ]),
+ recurrenceDistribution=RecurrenceDistribution(False),
+ ):
+ self.enabled = enabled
+ self._interval = interval
+ self._eventStartDistribution = eventStartDistribution
+ self._eventDurationDistribution = eventDurationDistribution
+ self._recurrenceDistribution = recurrenceDistribution
+
+
+ def run(self):
+ self._call = LoopingCall(self._addEvent)
+ self._call.clock = self._reactor
+ return self._call.start(self._interval)
+
+
+ def _addEvent(self):
+ # Don't perform any operations until the client is up and running
+ if not self._client.started:
+ return succeed(None)
+
+ calendar = self._getRandomCalendarOfType('VEVENT')
+
+ if not calendar:
+ # No VEVENT calendars, so no new event...
+ return succeed(None)
+
+ # Copy the template event and fill in some of its fields
+ # to make a new event to create on the calendar.
+ vcalendar = self._eventTemplate.duplicate()
+ vevent = vcalendar.mainComponent()
+ uid = str(uuid4())
+ dtstart = self._eventStartDistribution.sample()
+ dtend = dtstart + Duration(seconds=self._eventDurationDistribution.sample())
+ vevent.replaceProperty(Property("CREATED", DateTime.getNowUTC()))
+ vevent.replaceProperty(Property("DTSTAMP", DateTime.getNowUTC()))
+ vevent.replaceProperty(Property("DTSTART", dtstart))
+ vevent.replaceProperty(Property("DTEND", dtend))
+ vevent.replaceProperty(Property("UID", uid))
+
+ rrule = self._recurrenceDistribution.sample()
+ if rrule is not None:
+ vevent.addProperty(Property(None, None, None, pycalendar=rrule))
+
+ href = '%s%s.ics' % (calendar.url, uid)
+ d = self._client.addEvent(href, vcalendar)
+ return self._newOperation("create", d)
+
+
+
+class EventUpdaterBase(ProfileBase):
+
+ @inlineCallbacks
+ def action(self):
+ # Don't perform any operations until the client is up and running
+ if not self._client.started:
+ returnValue(None)
+
+ event = self._getRandomEventOfType('VEVENT', justOwned=True)
+ if not event:
+ returnValue(None)
+ component = event.component
+ vevent = component.mainComponent()
+
+ label = yield self.modifyEvent(event.url, vevent)
+ if label:
+ vevent.replaceProperty(Property("DTSTAMP", DateTime.getNowUTC()))
+
+ event.component = component
+ yield self._newOperation(
+ label,
+ self._client.changeEvent(event.url)
+ )
+
+
+ def run(self):
+ self._call = LoopingCall(self.action)
+ self._call.clock = self._reactor
+ return self._call.start(self._interval)
+
+
+ def modifyEvent(self, href, vevent):
+ """Overridden by subclasses"""
+ pass
+
+
+
+class TitleChanger(EventUpdaterBase):
+
+ def setParameters(
+ self,
+ enabled=True,
+ interval=60,
+ titleLengthDistribution=NormalDistribution(10, 2)
+ ):
+ self.enabled = enabled
+ self._interval = interval
+ self._titleLength = titleLengthDistribution
+
+
+ def modifyEvent(self, _ignore_href, vevent):
+ length = max(5, int(self._titleLength.sample()))
+ vevent.replaceProperty(Property("SUMMARY", "Event" + "." * (length - 5)))
+ return succeed("update{title}")
+
+
+
+class DescriptionChanger(EventUpdaterBase):
+
+ def setParameters(
+ self,
+ enabled=True,
+ interval=60,
+ descriptionLengthDistribution=NormalDistribution(10, 2)
+ ):
+ self.enabled = enabled
+ self._interval = interval
+ self._descriptionLength = descriptionLengthDistribution
+
+
+ def modifyEvent(self, _ignore_href, vevent):
+ length = int(self._descriptionLength.sample())
+ vevent.replaceProperty(Property("DESCRIPTION", "." * length))
+ return succeed("update{description}")
+
+
+
+class Attacher(EventUpdaterBase):
+
+ def setParameters(
+ self,
+ enabled=True,
+ interval=60,
+ fileSizeDistribution=NormalDistribution(1024, 1),
+ ):
+ self.enabled = enabled
+ self._interval = interval
+ self._fileSize = fileSizeDistribution
+
+
+ @inlineCallbacks
+ def modifyEvent(self, href, vevent):
+ fileSize = int(self._fileSize.sample())
+ yield self._client.postAttachment(href, 'x' * fileSize)
+ returnValue(None)
+
+
+
+class EventCountLimiter(EventUpdaterBase):
+ """
+ Examines the number of events in each calendar collection, and when that
+ count exceeds eventCountLimit, events are randomly removed until the count
+ falls back to the limit.
+ """
+
+ def setParameters(
+ self,
+ enabled=True,
+ interval=60,
+ eventCountLimit=1000
+ ):
+ self.enabled = enabled
+ self._interval = interval
+ self._limit = eventCountLimit
+
+
+ @inlineCallbacks
+ def action(self):
+ # Don't perform any operations until the client is up and running
+ if not self._client.started:
+ returnValue(None)
+
+ for calendar in self._calendarsOfType(caldavxml.calendar, "VEVENT", justOwned=True):
+ while len(calendar.events) > self._limit:
+ event = calendar.events[self.random.choice(calendar.events.keys())]
+ yield self._client.deleteEvent(event.url)
+
+
+
+class CalendarSharer(ProfileBase):
+ """
+ A Calendar user who shares calendars to other random users.
+ """
+ def setParameters(
+ self,
+ enabled=True,
+ interval=60
+ ):
+ self.enabled = enabled
+ self._interval = interval
+
+
+ def run(self):
+ self._call = LoopingCall(self.action)
+ self._call.clock = self._reactor
+ return self._call.start(self._interval)
+
+
+ @inlineCallbacks
+ def action(self):
+ # Don't perform any operations until the client is up and running
+ if not self._client.started:
+ returnValue(None)
+
+ yield self.shareCalendar()
+
+
+ @inlineCallbacks
+ def shareCalendar(self):
+
+ # pick a calendar
+ calendar = self._getRandomCalendarOfType('VEVENT', justOwned=True)
+ if not calendar:
+ returnValue(None)
+
+ # pick a random sharee
+ shareeRecord = self._sim.getRandomUserRecord(besides=self._number)
+ if shareeRecord is None:
+ returnValue(None)
+
+ # POST the sharing invite
+ mailto = "mailto:{}".format(shareeRecord.email)
+ body = Calendar.addInviteeXML(mailto, calendar.name, readwrite=True)
+ yield self._client.postXML(
+ calendar.url,
+ body,
+ label="POST{share-calendar}"
+ )
+
+
+
+class AlarmAcknowledger(ProfileBase):
+ """
+ A Calendar user who creates a new event, and then updates its alarm.
+ """
+ _eventTemplate = Component.fromString("""\
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//iCal 4.0.3//EN
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+CREATED:20101018T155431Z
+UID:C98AD237-55AD-4F7D-9009-0D355D835822
+DTEND;TZID=America/New_York:20101021T130000
+TRANSP:OPAQUE
+SUMMARY:Simple event
+DTSTART;TZID=America/New_York:20101021T120000
+DTSTAMP:20101018T155438Z
+SEQUENCE:2
+BEGIN:VALARM
+X-WR-ALARMUID:D9D1AC84-F629-4B9D-9B6B-4A6CA9A11FEF
+UID:D9D1AC84-F629-4B9D-9B6B-4A6CA9A11FEF
+DESCRIPTION:Event reminder
+TRIGGER:-PT8M
+ACTION:DISPLAY
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"))
+
+ def setParameters(
+ self,
+ enabled=True,
+ interval=5,
+ pastTheHour=[0, 15, 30, 45],
+ eventStartDistribution=NearFutureDistribution(),
+ eventDurationDistribution=UniformDiscreteDistribution([
+ 15 * 60, 30 * 60,
+ 45 * 60, 60 * 60,
+ 120 * 60
+ ]),
+ recurrenceDistribution=RecurrenceDistribution(False),
+ ):
+ self.enabled = enabled
+ self._interval = interval
+ self._pastTheHour = pastTheHour
+ self._eventStartDistribution = eventStartDistribution
+ self._eventDurationDistribution = eventDurationDistribution
+ self._recurrenceDistribution = recurrenceDistribution
+ self._lastMinuteChecked = -1
+
+
+ def initialize(self):
+ """
+ Called before the profile runs for real. Can be used to initialize client state.
+
+ @return: a L{Deferred} that fires when initialization is done
+ """
+
+ return self._initEvent()
+
+
+ def run(self):
+ self._call = LoopingCall(self._updateEvent)
+ self._call.clock = self._reactor
+ return self._call.start(self._interval)
+
+
+ def _initEvent(self):
+ # Don't perform any operations until the client is up and running
+ if not self._client.started:
+ return succeed(None)
+
+ # If it already exists, don't re-create
+ calendar = self._calendarsOfType(caldavxml.calendar, "VEVENT")[0]
+ if calendar.events:
+ events = [event for event in calendar.events.values() if event.url.endswith("event_to_update.ics")]
+ if events:
+ return succeed(None)
+
+ # Copy the template event and fill in some of its fields
+ # to make a new event to create on the calendar.
+ vcalendar = self._eventTemplate.duplicate()
+ vevent = vcalendar.mainComponent()
+ uid = str(uuid4())
+ dtstart = self._eventStartDistribution.sample()
+ dtend = dtstart + Duration(seconds=self._eventDurationDistribution.sample())
+ vevent.replaceProperty(Property("CREATED", DateTime.getNowUTC()))
+ vevent.replaceProperty(Property("DTSTAMP", DateTime.getNowUTC()))
+ vevent.replaceProperty(Property("DTSTART", dtstart))
+ vevent.replaceProperty(Property("DTEND", dtend))
+ vevent.replaceProperty(Property("UID", uid))
+
+ rrule = self._recurrenceDistribution.sample()
+ if rrule is not None:
+ vevent.addProperty(Property(None, None, None, pycalendar=rrule))
+
+ href = '%s%s' % (calendar.url, "event_to_update.ics")
+ d = self._client.addEvent(href, vcalendar)
+ return self._newOperation("create", d)
+
+
+ def _shouldUpdate(self, minutePastTheHour):
+ """
+ We want to only acknowledge our alarm at the "past the hour" minutes
+ we've been configured for.
+ """
+ should = False
+ if minutePastTheHour in self._pastTheHour:
+ # This is one of the minutes we should update on, but only update
+ # as we pass into this minute, and not subsequent times
+ if minutePastTheHour != self._lastMinuteChecked:
+ should = True
+
+ self._lastMinuteChecked = minutePastTheHour
+ return should
+
+
+ def _updateEvent(self):
+ """
+ Set the ACKNOWLEDGED property on an event.
+
+ @return: C{None} if there are no events to play with,
+ otherwise a L{Deferred} which fires when the acknowledged
+ change has been made.
+ """
+
+ # Only do updates when we reach of the designated minutes past the hour
+ if not self._shouldUpdate(datetime.now().minute):
+ return succeed(None)
+
+ if not self._client.started:
+ return succeed(None)
+
+ # If it does not exist, try to create it
+ calendar = self._calendarsOfType(caldavxml.calendar, "VEVENT")[0]
+ if not calendar.events:
+ return self._initEvent()
+ events = [event for event in calendar.events.values() if event.url.endswith("event_to_update.ics")]
+ if not events:
+ return self._initEvent()
+ event = events[0]
+
+ # Add/update the ACKNOWLEDGED property
+ component = event.component.mainComponent()
+ component.replaceProperty(Property("ACKNOWLEDGED", DateTime.getNowUTC()))
+ d = self._client.changeEvent(event.url)
+ return self._newOperation("update", d)
+
+
+
+class Tasker(ProfileBase):
+ """
+ A Calendar user who creates new tasks.
+ """
+ _taskTemplate = Component.fromString("""\
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//iCal 4.0.3//EN
+CALSCALE:GREGORIAN
+BEGIN:VTODO
+CREATED:20101018T155431Z
+UID:C98AD237-55AD-4F7D-9009-0D355D835822
+SUMMARY:Simple task
+DUE;TZID=America/New_York:20101021T120000
+DTSTAMP:20101018T155438Z
+END:VTODO
+END:VCALENDAR
+""".replace("\n", "\r\n"))
+
+ def setParameters(
+ self,
+ enabled=True,
+ interval=25,
+ taskDueDistribution=NearFutureDistribution(),
+ ):
+ self.enabled = enabled
+ self._interval = interval
+ self._taskStartDistribution = taskDueDistribution
+
+
+ def run(self):
+ self._call = LoopingCall(self._addTask)
+ self._call.clock = self._reactor
+ return self._call.start(self._interval)
+
+
+ def _addTask(self):
+ # Don't perform any operations until the client is up and running
+ if not self._client.started:
+ return succeed(None)
+
+ calendars = self._calendarsOfType(caldavxml.calendar, "VTODO")
+
+ while calendars:
+ calendar = self.random.choice(calendars)
+ calendars.remove(calendar)
+
+ # Copy the template task and fill in some of its fields
+ # to make a new task to create on the calendar.
+ vcalendar = self._taskTemplate.duplicate()
+ vtodo = vcalendar.mainComponent()
+ uid = str(uuid4())
+ due = self._taskStartDistribution.sample()
+ vtodo.replaceProperty(Property("CREATED", DateTime.getNowUTC()))
+ vtodo.replaceProperty(Property("DTSTAMP", DateTime.getNowUTC()))
+ vtodo.replaceProperty(Property("DUE", due))
+ vtodo.replaceProperty(Property("UID", uid))
+
+ href = '%s%s.ics' % (calendar.url, uid)
+ d = self._client.addEvent(href, vcalendar)
+ return self._newOperation("create", d)
+
+
+
+class Resetter(ProfileBase):
+ """
+ A Calendar user who resets their account and re-downloads everything.
+ """
+
+ def setParameters(
+ self,
+ enabled=True,
+ interval=600,
+ ):
+ self.enabled = enabled
+ self._interval = interval
+
+
+ def run(self):
+ self._call = LoopingCall(self._resetAccount)
+ self._call.clock = self._reactor
+ return self._call.start(self._interval)
+
+
+ def _resetAccount(self):
+ # Don't perform any operations until the client is up and running
+ if not self._client.started:
+ return succeed(None)
+
+ return self._client.reset()
+
+
+
+class OperationLogger(SummarizingMixin):
+ """
+ Profiles will initiate operations which may span multiple requests. Start
+ and stop log messages are emitted for these operations and logged by this
+ logger.
+ """
+ formats = {
+ u"start" : u"%(user)s - - - - - - - - - - - %(label)8s BEGIN %(lag)s",
+ u"end" : u"%(user)s - - - - - - - - - - - %(label)8s END [%(duration)5.2f s]",
+ u"failed": u"%(user)s x x x x x x x x x x x %(label)8s FAILED %(reason)s",
+ }
+
+ lagFormat = u'{lag %5.2f ms}'
+
+ # the response time thresholds to display together with failing % count threshold
+ _thresholds_default = {
+ "operations": {
+ "limits": [0.1, 0.5, 1.0, 3.0, 5.0, 10.0, 30.0],
+ "thresholds": {
+ "default": [100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0],
+ }
+ }
+ }
+ _lag_cut_off = 1.0 # Maximum allowed median scheduling latency, seconds
+ _fail_cut_off = 1.0 # % of total count at which failed requests will cause a failure
+
+ _fields_init = [
+ ('operation', -30, '%-30s'),
+ ('count', 8, '%8s'),
+ ('failed', 8, '%8s'),
+ ]
+
+ _fields_extend = [
+ ('mean', 8, '%8.4f'),
+ ('median', 8, '%8.4f'),
+ ('stddev', 8, '%8.4f'),
+ ('avglag (ms)', 12, '%12.4f'),
+ ('STATUS', 8, '%8s'),
+ ]
+
+ def __init__(self, outfile=None, **params):
+ self._perOperationTimes = {}
+ self._perOperationLags = {}
+ if outfile is None:
+ outfile = sys.stdout
+ self._outfile = outfile
+
+ # Load parameters from config
+ if "thresholdsPath" in params:
+ with open(params["thresholdsPath"]) as f:
+ jsondata = json.load(f)
+ elif "thresholds" in params:
+ jsondata = params["thresholds"]
+ else:
+ jsondata = self._thresholds_default
+ self._thresholds = [[limit, {}] for limit in jsondata["operations"]["limits"]]
+ for ctr, item in enumerate(self._thresholds):
+ for k, v in jsondata["operations"]["thresholds"].items():
+ item[1][k] = v[ctr]
+
+ self._fields = self._fields_init[:]
+ for threshold, _ignore_fail_at in self._thresholds:
+ self._fields.append(('>%g sec' % (threshold,), 10, '%10s'))
+ self._fields.extend(self._fields_extend)
+
+ if "lagCutoff" in params:
+ self._lag_cut_off = params["lagCutoff"]
+
+ if "failCutoff" in params:
+ self._fail_cut_off = params["failCutoff"]
+
+ self._fail_if_no_push = params.get("failIfNoPush", False)
+
+
+ def observe(self, event):
+ if event.get("type") == "operation":
+ event = event.copy()
+ lag = event.get('lag')
+ if lag is None:
+ event['lag'] = ''
+ else:
+ event['lag'] = self.lagFormat % (lag * 1000.0,)
+
+ self._outfile.write(
+ (self.formats[event[u'phase']] % event).encode('utf-8') + '\n')
+
+ if event[u'phase'] == u'end':
+ dataset = self._perOperationTimes.setdefault(event[u'label'], [])
+ dataset.append((event[u'success'], event[u'duration']))
+ elif lag is not None:
+ dataset = self._perOperationLags.setdefault(event[u'label'], [])
+ dataset.append(lag)
+
+
+ def _summarizeData(self, operation, data):
+ avglag = mean(self._perOperationLags.get(operation, [0.0])) * 1000.0
+ data = SummarizingMixin._summarizeData(self, operation, data)
+ return data[:-1] + (avglag,) + data[-1:]
+
+
+ def report(self, output):
+ output.write("\n")
+ self.printHeader(output, [
+ (label, width)
+ for (label, width, _ignore_fmt) in self._fields
+ ])
+ self.printData(
+ output,
+ [fmt for (label, width, fmt) in self._fields],
+ sorted(self._perOperationTimes.items())
+ )
+
+ _LATENCY_REASON = "Median %(operation)s scheduling lag greater than %(cutoff)sms"
+ _FAILED_REASON = "Greater than %(cutoff).0f%% %(operation)s failed"
+ _PUSH_MISSING_REASON = "Push was configured but no pushes were received by clients"
+
+ def failures(self):
+ reasons = []
+
+ for operation, lags in self._perOperationLags.iteritems():
+ if median(lags) > self._lag_cut_off:
+ reasons.append(self._LATENCY_REASON % dict(
+ operation=operation.upper(), cutoff=self._lag_cut_off * 1000))
+
+ for operation, times in self._perOperationTimes.iteritems():
+ failures = len([success for (success, _ignore_duration) in times if not success])
+ if failures * 100.0 / len(times) > self._fail_cut_off:
+ reasons.append(self._FAILED_REASON % dict(
+ operation=operation.upper(), cutoff=self._fail_cut_off))
+
+ if self._fail_if_no_push and "push" not in self._perOperationTimes:
+ reasons.append(self._PUSH_MISSING_REASON)
+
+ return reasons
Added: CalendarServer/trunk/simplugin/clients.plist
===================================================================
--- CalendarServer/trunk/simplugin/clients.plist (rev 0)
+++ CalendarServer/trunk/simplugin/clients.plist 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1,722 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+ Copyright (c) 2011-2016 Apple Inc. All rights reserved.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+ <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 OS X client simulator. -->
+ <key>software</key>
+ <string>simplugin.caldavclient.OS_X_10_11</string>
+
+ <!-- Arguments to use to initialize the OS_X_10_11 instance. -->
+ <key>params</key>
+ <dict>
+ <!-- Name that appears in logs. -->
+ <key>title</key>
+ <string>10.11</string>
+
+ <!-- OS_X_10_11 can poll the calendar home at some interval. This is
+ in seconds. -->
+ <key>calendarHomePollInterval</key>
+ <integer>30</integer>
+
+ <!-- If the server advertises xmpp push, OS_X_10_7 can wait for notifications
+ about calendar home changes instead of polling for them periodically. If
+ this option is true, then look for the server advertisement for xmpp push
+ and use it if possible. Still fall back to polling if there is no xmpp push
+ advertised. -->
+ <key>supportPush</key>
+ <true/>
+
+ <key>supportAmpPush</key>
+ <true/>
+
+ <!-- The template URI for doing initial principal lookup on. -->
+ <key>principalPathTemplate</key>
+ <string>/principals/users/{}/</string>
+
+ </dict>
+
+ <!-- The profiles define certain types of user behavior on top of the
+ client software being simulated. -->
+ <key>profiles</key>
+ <array>
+
+ <!-- First an event-creating profile, which will periodically create
+ new events at a random time on a random calendar. -->
+ <dict>
+ <key>class</key>
+ <string>simplugin.caldavprofile.Eventer</string>
+
+ <key>params</key>
+ <dict>
+ <key>enabled</key>
+ <true/>
+
+ <!-- Define the interval (in seconds) at which this profile will use
+ its client to create a new event. -->
+ <key>interval</key>
+ <integer>120</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>clientsim.framework.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>
+
+ <!-- Define how recurrences are created. -->
+ <key>recurrenceDistribution</key>
+ <dict>
+
+ <!-- This distribution is pretty specialized. We have a fixed set of
+ RRULEs defined for this distribution and pick each based on a
+ weight. -->
+ <key>type</key>
+ <string>clientsim.framework.stats.RecurrenceDistribution</string>
+
+ <key>params</key>
+ <dict>
+ <!-- False to disable RRULEs -->
+ <key>allowRecurrence</key>
+ <true/>
+
+ <!-- These are the weights for the specific set of RRULEs. -->
+ <key>weights</key>
+ <dict>
+ <!-- Half of all events will be non-recurring -->
+ <key>none</key>
+ <integer>50</integer>
+
+ <!-- Daily and weekly are pretty common -->
+ <key>daily</key>
+ <integer>10</integer>
+ <key>weekly</key>
+ <integer>20</integer>
+
+ <!-- Monthly, yearly, daily & weekly limit not so common -->
+ <key>monthly</key>
+ <integer>2</integer>
+ <key>yearly</key>
+ <integer>1</integer>
+ <key>dailylimit</key>
+ <integer>2</integer>
+ <key>weeklylimit</key>
+ <integer>5</integer>
+
+ <!-- Work days pretty common -->
+ <key>workdays</key>
+ <integer>10</integer>
+ </dict>
+ </dict>
+ </dict>
+ </dict>
+ </dict>
+
+ <!-- This profile will create a new event, and then periodically update the ACKNOWLEDGED property. -->
+
+ <dict>
+ <key>class</key>
+ <string>simplugin.caldavprofile.AlarmAcknowledger</string>
+
+ <key>params</key>
+ <dict>
+ <key>enabled</key>
+ <true/>
+
+ <!-- Define how often to check (in seconds) whether it's
+ time to do an alarm acknowledgement -->
+ <key>interval</key>
+ <integer>15</integer>
+
+ <!-- Acknowledgements will only actually occur at one of these
+ minutes past the hour: -->
+ <key>pastTheHour</key>
+ <array>
+ <integer>0</integer>
+ <integer>15</integer>
+ <integer>30</integer>
+ <integer>45</integer>
+ </array>
+
+ <!-- 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>clientsim.framework.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>
+
+ <!-- Define how recurrences are created. -->
+ <key>recurrenceDistribution</key>
+ <dict>
+
+ <!-- This distribution is pretty specialized. We have a fixed set of
+ RRULEs defined for this distribution and pick each based on a
+ weight. -->
+ <key>type</key>
+ <string>clientsim.framework.stats.RecurrenceDistribution</string>
+
+ <key>params</key>
+ <dict>
+ <!-- False to disable RRULEs -->
+ <key>allowRecurrence</key>
+ <true/>
+
+ <!-- These are the weights for the specific set of RRULEs. -->
+ <key>weights</key>
+ <dict>
+ <!-- Half of all events will be non-recurring -->
+ <key>none</key>
+ <integer>50</integer>
+
+ <!-- Daily and weekly are pretty common -->
+ <key>daily</key>
+ <integer>25</integer>
+ <key>weekly</key>
+ <integer>25</integer>
+
+ <!-- Monthly, yearly, daily & weekly limit not so common -->
+ <key>monthly</key>
+ <integer>0</integer>
+ <key>yearly</key>
+ <integer>0</integer>
+ <key>dailylimit</key>
+ <integer>0</integer>
+ <key>weeklylimit</key>
+ <integer>0</integer>
+
+ <!-- Work days pretty common -->
+ <key>workdays</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ </dict>
+ </dict>
+ </dict>
+
+ <!-- Picks a random event and changes the title -->
+ <dict>
+ <key>class</key>
+ <string>simplugin.caldavprofile.TitleChanger</string>
+
+ <key>params</key>
+ <dict>
+ <key>enabled</key>
+ <true/>
+
+ <!-- Define the interval (in seconds) at which this profile will use
+ its client to create a new event. -->
+ <key>interval</key>
+ <integer>120</integer>
+
+ </dict>
+ </dict>
+
+ <!-- Picks a random event and changes the description -->
+ <dict>
+ <key>class</key>
+ <string>simplugin.caldavprofile.DescriptionChanger</string>
+
+ <key>params</key>
+ <dict>
+ <key>enabled</key>
+ <true/>
+
+ <!-- Define the interval (in seconds) at which this profile will use
+ its client to create a new event. -->
+ <key>interval</key>
+ <integer>120</integer>
+
+ <!-- Define the description length distribution. -->
+ <key>descriptionLengthDistribution</key>
+ <dict>
+ <key>type</key>
+ <string>clientsim.framework.stats.LogNormalDistribution</string>
+ <key>params</key>
+ <dict>
+ <!-- mode - peak-->
+ <key>mode</key>
+ <integer>450</integer>
+ <!-- mean - average-->
+ <key>median</key>
+ <integer>650</integer>
+ <!-- maximum -->
+ <key>maximum</key>
+ <real>1000000</real>
+ </dict>
+ </dict>
+
+ </dict>
+ </dict>
+
+ <!-- Picks a random event and attaches -->
+ <dict>
+ <key>class</key>
+ <string>simplugin.caldavprofile.Attacher</string>
+
+ <key>params</key>
+ <dict>
+ <key>enabled</key>
+ <true/>
+
+ <!-- Define the interval (in seconds) at which this profile will use
+ its client to create a new event. -->
+ <key>interval</key>
+ <integer>120</integer>
+
+ <!-- Define the attachment size distribution. -->
+ <key>fileSizeDistribution</key>
+ <dict>
+ <key>type</key>
+ <string>clientsim.framework.stats.NormalDistribution</string>
+ <key>params</key>
+ <dict>
+ <!-- mu gives the mean of the normal distribution (in seconds). -->
+ <key>mu</key>
+ <integer>500000</integer>
+
+ <!-- and sigma gives its standard deviation. -->
+ <key>sigma</key>
+ <integer>100000</integer>
+ </dict>
+ </dict>
+
+ </dict>
+ </dict>
+
+ <!-- Removes events from calendars exceeding a threshold -->
+ <dict>
+ <key>class</key>
+ <string>simplugin.caldavprofile.EventCountLimiter</string>
+
+ <key>params</key>
+ <dict>
+ <key>enabled</key>
+ <true/>
+
+ <!-- Define the interval (in seconds) at which this profile will check
+ for too-large collections. -->
+ <key>interval</key>
+ <integer>60</integer>
+
+ <!-- The upper bound. -->
+ <key>eventCountLimit</key>
+ <integer>100</integer>
+
+ </dict>
+ </dict>
+
+
+ <!-- Shares calendars -->
+ <dict>
+ <key>class</key>
+ <string>simplugin.caldavprofile.CalendarSharer</string>
+
+ <key>params</key>
+ <dict>
+ <key>enabled</key>
+ <true/>
+
+ <!-- Define the interval (in seconds) at which this profile will share calendars. -->
+ <key>interval</key>
+ <integer>300</integer>
+
+ </dict>
+ </dict>
+
+ <!-- This profile invites some number of new attendees to new events. -->
+ <dict>
+ <key>class</key>
+ <string>simplugin.caldavprofile.Inviter</string>
+
+ <key>params</key>
+ <dict>
+ <key>enabled</key>
+ <true/>
+
+ <!-- Define the frequency at which new invitations will be sent out. -->
+ <key>sendInvitationDistribution</key>
+ <dict>
+ <key>type</key>
+ <string>clientsim.framework.stats.NormalDistribution</string>
+ <key>params</key>
+ <dict>
+ <!-- mu gives the mean of the normal distribution (in seconds). -->
+ <key>mu</key>
+ <integer>120</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.
+
+ When inviteeClumping is turned on each invitee is based on a sample of
+ users "close to" the organizer based on account index. If the clumping
+ is too "tight" for the requested number of attendees, then invites for
+ those larger numbers will simply fail (the sim will report that situation).
+
+ When inviteeClumping is off invitees will be sampled across an entire
+ range of account indexes. In this case the distribution ought to be a
+ UniformIntegerDistribution with min=0 and max set to the number of accounts.
+ -->
+ <key>inviteeDistribution</key>
+ <dict>
+ <key>type</key>
+ <string>clientsim.framework.stats.UniformIntegerDistribution</string>
+ <key>params</key>
+ <dict>
+ <!-- The minimum value (inclusive) of the uniform distribution. -->
+ <key>min</key>
+ <integer>0</integer>
+ <!-- The maximum value (exclusive) of the uniform distribution. -->
+ <key>max</key>
+ <integer>99</integer>
+ </dict>
+ </dict>
+
+ <key>inviteeClumping</key>
+ <true/>
+
+ <!-- Define the distribution of how many attendees will be invited to an event.
+
+ LogNormal is the best fit to observed data.
+
+
+ For LogNormal "mode" is the peak, "mean" is the mean value. For invites,
+ mode should typically be 1, and mean whatever matches the user behavior.
+ Our typical mean is 6.
+ -->
+ <key>inviteeCountDistribution</key>
+ <dict>
+ <key>type</key>
+ <string>clientsim.framework.stats.LogNormalDistribution</string>
+ <key>params</key>
+ <dict>
+ <!-- mode - peak-->
+ <key>mode</key>
+ <integer>6</integer>
+ <!-- mean - average-->
+ <key>median</key>
+ <integer>8</integer>
+ <!-- maximum -->
+ <key>maximum</key>
+ <real>60</real>
+ </dict>
+ </dict>
+
+ <!-- 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>clientsim.framework.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>
+
+ <!-- Define how recurrences are created. -->
+ <key>recurrenceDistribution</key>
+ <dict>
+
+ <!-- This distribution is pretty specialized. We have a fixed set of
+ RRULEs defined for this distribution and pick each based on a
+ weight. -->
+ <key>type</key>
+ <string>clientsim.framework.stats.RecurrenceDistribution</string>
+
+ <key>params</key>
+ <dict>
+ <!-- False to disable RRULEs -->
+ <key>allowRecurrence</key>
+ <true/>
+
+ <!-- These are the weights for the specific set of RRULEs. -->
+ <key>weights</key>
+ <dict>
+ <!-- Half of all events will be non-recurring -->
+ <key>none</key>
+ <integer>50</integer>
+
+ <!-- Daily and weekly are pretty common -->
+ <key>daily</key>
+ <integer>10</integer>
+ <key>weekly</key>
+ <integer>20</integer>
+
+ <!-- Monthly, yearly, daily & weekly limit not so common -->
+ <key>monthly</key>
+ <integer>2</integer>
+ <key>yearly</key>
+ <integer>1</integer>
+ <key>dailylimit</key>
+ <integer>2</integer>
+ <key>weeklylimit</key>
+ <integer>5</integer>
+
+ <!-- Work days pretty common -->
+ <key>workdays</key>
+ <integer>10</integer>
+ </dict>
+ </dict>
+ </dict>
+ </dict>
+ </dict>
+
+ <!-- This profile accepts invitations to events, handles cancels, and
+ handles replies received. -->
+ <dict>
+ <key>class</key>
+ <string>simplugin.caldavprofile.Accepter</string>
+
+ <key>params</key>
+ <dict>
+ <key>enabled</key>
+ <true/>
+
+ <!-- Define how long to wait after seeing a new invitation before
+ accepting it.
+
+ For LogNormal "mode" is the peak, "median" is the 50% cummulative value
+ (i.e., half of the user have accepted by that time).
+ -->
+ <key>acceptDelayDistribution</key>
+ <dict>
+ <key>type</key>
+ <string>clientsim.framework.stats.LogNormalDistribution</string>
+ <key>params</key>
+ <dict>
+ <!-- mode - peak-->
+ <key>mode</key>
+ <integer>300</integer>
+ <!-- median - 50% done-->
+ <key>median</key>
+ <integer>1800</integer>
+ </dict>
+ </dict>
+ </dict>
+ </dict>
+
+
+ <!-- This profile downloads attachments when an event changes. -->
+ <dict>
+ <key>class</key>
+ <string>simplugin.caldavprofile.AttachmentDownloader</string>
+
+ <key>params</key>
+ <dict>
+ <key>enabled</key>
+ <true/>
+ </dict>
+ </dict>
+
+ <!-- A task-creating profile, which will periodically create
+ new tasks at a random time on a random calendar. -->
+ <dict>
+ <key>class</key>
+ <string>simplugin.caldavprofile.Tasker</string>
+
+ <key>params</key>
+ <dict>
+ <key>enabled</key>
+ <true/>
+
+ <!-- Define the interval (in seconds) at which this profile will use
+ its client to create a new task. -->
+ <key>interval</key>
+ <integer>300</integer>
+
+ <!-- Define how due times (DUE) for the randomly generated tasks
+ will be selected. This is an example of a "Distribution" parameter. The value
+ for most "Distribution" parameters are interchangeable and extensible. -->
+ <key>taskDueDistribution</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>clientsim.framework.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>
+
+
+ <!-- A profile which will periodically reset itself, as if a user removed
+ and re-added their account. -->
+ <dict>
+ <key>class</key>
+ <string>simplugin.caldavprofile.Resetter</string>
+
+ <key>params</key>
+ <dict>
+ <key>enabled</key>
+ <false/>
+
+ <!-- Define the interval (in seconds) at which this profile will
+ reset its client. -->
+ <key>interval</key>
+ <integer>600</integer>
+ </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>
+ </dict>
+</plist>
Added: CalendarServer/trunk/simplugin/config.plist
===================================================================
--- CalendarServer/trunk/simplugin/config.plist (rev 0)
+++ CalendarServer/trunk/simplugin/config.plist 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1,223 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+ Copyright (c) 2011-2016 Apple Inc. All rights reserved.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+ <dict>
+ <!-- Identify all the servers to be load tested. -->
+ <key>servers</key>
+ <dict>
+ <key>PodA</key>
+ <dict>
+ <key>enabled</key>
+ <true/>
+
+ <!-- Identify the server to be load tested. -->
+ <key>uri</key>
+ <string>http://localhost:8008</string>
+
+ <key>ampPushHosts</key>
+ <array>
+ <string>localhost</string>
+ </array>
+ <key>ampPushPort</key>
+ <integer>62311</integer>
+
+ <!-- Define whether server supports stats socket. -->
+ <key>stats</key>
+ <dict>
+ <key>enabled</key>
+ <true/>
+ <key>Port</key>
+ <integer>8100</integer>
+ </dict>
+ </dict>
+ <key>PodB</key>
+ <dict>
+ <key>enabled</key>
+ <false/>
+
+ <!-- Identify the server to be load tested. -->
+ <key>uri</key>
+ <string>https://localhost:8543</string>
+
+ <key>ampPushHosts</key>
+ <array>
+ <string>localhost</string>
+ </array>
+ <key>ampPushPort</key>
+ <integer>62312</integer>
+
+ <!-- Define whether server supports stats socket. -->
+ <key>stats</key>
+ <dict>
+ <key>enabled</key>
+ <true/>
+ <key>Port</key>
+ <integer>8101</integer>
+ </dict>
+ </dict>
+ </dict>
+
+
+ <!-- Configure Admin Web UI. -->
+ <key>webadmin</key>
+ <dict>
+ <key>enabled</key>
+ <true/>
+
+ <key>HTTPPort</key>
+ <integer>8080</integer>
+ </dict>
+
+ <!-- Define whether client data should be re-used. It will always be saved to the specified path.-->
+ <key>clientDataSerialization</key>
+ <dict>
+ <key>UseOldData</key>
+ <true/>
+ <key>Path</key>
+ <string>/tmp/sim</string>
+ </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. clientsim.framework.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>clientsim.framework.sim.recordsFromCSVFile</string>
+
+ <!-- Keyword arguments may be passed to the loader. -->
+ <key>params</key>
+ <dict>
+ <!-- recordsFromCSVFile interprets the path relative to the config.plist,
+ to make it independent of the script's working directory while still allowing
+ a relative path. This isn't a great solution. -->
+ <key>path</key>
+ <string>clientsim/framework/accounts.csv</string>
+
+ <!-- When there are accounts for multiple pods, interleave the accounts for each
+ pod so that the arrival mechanism will cycle clients between each pod. -->
+ <key>interleavePods</key>
+ <true/>
+ </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. clientsim.framework.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>clientsim.framework.population.SmoothRampUp</string>
+
+ <key>params</key>
+ <dict>
+ <!-- groups gives the total number of groups of clients to introduce. -->
+ <key>groups</key>
+ <integer>10</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>3</integer>
+
+ <!-- Number of seconds between the introduction of each group. -->
+ <key>interval</key>
+ <integer>3</integer>
+
+ <!-- Number of clients each user is assigned to. -->
+ <!-- Set weight of clients to 1 if this is > 1. Number of clients must match this value if > 1. -->
+ <key>clientsPerUser</key>
+ <integer>1</integer>
+ </dict>
+
+ </dict>
+
+ <!-- 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. -->
+ <dict>
+ <key>type</key>
+ <string>clientsim.framework.population.ReportStatistics</string>
+ <key>params</key>
+ <dict>
+ <!-- The thresholds for each request type -->
+ <key>thresholdsPath</key>
+ <string>clientsim/framework/thresholds.json</string>
+
+ <!-- The benchmarks for overall QoS -->
+ <key>benchmarksPath</key>
+ <string>clientsim/framework/benchmarks.json</string>
+
+ <!-- The % of failures that constitute a failed test -->
+ <key>failCutoff</key>
+ <real>1.0</real>
+ </dict>
+ </dict>
+
+ <!-- RequestLogger generates a realtime log of all HTTP requests made
+ during the load test. -->
+ <dict>
+ <key>type</key>
+ <string>clientsim.framework.baseclient.RequestLogger</string>
+ <key>params</key>
+ <dict>
+ </dict>
+ </dict>
+
+ <!-- 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). -->
+ <dict>
+ <key>type</key>
+ <string>clientsim.framework.baseprofile.OperationLogger</string>
+ <key>params</key>
+ <dict>
+ <!-- The thresholds for each operation type -->
+ <key>thresholdsPath</key>
+ <string>clientsim/framework/thresholds.json</string>
+
+ <!-- The % of operations beyond the lag cut-off that constitute a failed test -->
+ <key>lagCutoff</key>
+ <real>1.0</real>
+
+ <!-- The % of failures that constitute a failed test -->
+ <key>failCutoff</key>
+ <real>1.0</real>
+
+ <!-- There must be at least one push operation -->
+ <key>failIfNoPush</key>
+ <true/>
+ </dict>
+ </dict>
+ </array>
+ </dict>
+</plist>
Added: CalendarServer/trunk/simplugin/request-data/OS_X_10_11/Profile
===================================================================
--- CalendarServer/trunk/simplugin/request-data/OS_X_10_11/Profile (rev 0)
+++ CalendarServer/trunk/simplugin/request-data/OS_X_10_11/Profile 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1,267 @@
+PROPFIND ./well-known/caldav -> /principals/
+ current-user-principal
+ principal-URL
+ resourcetype
+
+PROPFIND /principals/ ->
+ current-user-principal /principals/__uids__/<uid>
+ principal-URL ----
+ resourcetype collection
+
+OPTIONS /principals/__uids__/<uid>/
+
+PROPFIND /principals/__uids__/<uid>/
+ calendar-home-set /calendars/__uids__/<uid>/
+ calendar-user-address-set mailto:user#@example.com
+ urn:uuid:<uid>
+ urn:x-uid:<uid>
+ current-user-principal /principals/__uids__/<uid>/
+ displayname User #
+ dropbox-home-URL /calendars/__uids__/<uid>/dropbox/
+ email-address-set user#@example.com
+ notification-URL /calendars/__uids__/<uid>/notification/
+ principal-collection-set /principals/
+ principal-URL /principals/__uids__/<uid>/
+ resource-id urn:x-uid:<uid>
+ schedule-inbox-URL /calendars/__uids__/<uid>/inbox/
+ schedule-outbox-URL /calendars/__uids__/<uid>/outbox/
+ supported-report-set acl-principal-prop-set
+ principal-match
+ principal-property-search
+ expand-property
+ calendarserver-principal-search
+
+OPTIONS /principals/__uids__/<uid>
+
+REPORT /principals/ ->
+ principal-search-property-set displayname
+ email-address-set
+ calendar-user-address-set
+ calendar-user-type
+
+PROPFIND /calendars/__uids__/<uid>/inbox/ ->
+ calendar-availability ???
+
+PROPFIND /calendars/__uids__/<uid>/
+Depth 1
+ add-member
+ allowed-sharing-modes
+ autoprovisioned
+ bulk-requests
+ calendar-alarm
+ calendar-color
+ calendar-description
+ calendar-free-busy-set
+ calendar-order
+ calendar-timezone
+ current-user-privilege-set all/read/read-free-busy/write/write-properties/write-content/bind/unbind/unlock/read-acl/write-acl/read-current-user-privilege-set
+ default-alarm-vevent-date
+ default-alarm-vevent-datetime
+ displayname User #
+ getctag
+ invite
+ language-code
+ location-code
+ owner /principals/__uids__/<uid>/
+ pre-publish-url
+ publish-url
+ push-transports
+ pushkey /CalDAV/localhost/<uid>/
+ quota-available-bytes 104857600
+ quota-used-bytes 0
+ refreshrate
+ resource-id
+ resourcetype collection
+ schedule-calendar-transp
+ schedule-default-calendar-URL
+ source
+ subscribed-strip-alarms
+ subscribed-strip-attachments
+ subscribed-strip-todos
+ supported-calendar-component-set VEVENT/VTODO
+ supported-calendar-component-sets
+ supported-report-set acl-principal-prop-set/principal-match/principal-property-search/expand-property/calendarserver-principal-search/calendar-query/calendar-multiget/free-busy-query/addressbook-query/addressbook-multiget/sync-collection
+ sync-token data:,36_58/<hex>
+ ** and more **
+
+PROPPATCH /calendars/__uids__/<uid>/ -> default-alarm-vevent-date
+PROPPATCH /calendars/__uids__/<uid>/ -> default-alarm-vevent-datetime
+
+PROPPATCH /calendars/__uids__/<uid>/calendar/ -> calendar-order
+PROPPATCH /calendars/__uids__/<uid>/calendar/ -> displayname
+PROPPATCH /calendars/__uids__/<uid>/calendar/ -> calendar-color
+PROPPATCH /calendars/__uids__/<uid>/calendar/ -> calendar-order
+PROPPATCH /calendars/__uids__/<uid>/calendar/ -> calendar-timezone
+
+PROPPATCH /calendars/__uids__/<uid>/tasks/ -> calendar-order
+PROPPATCH /calendars/__uids__/<uid>/tasks/ -> displayname
+PROPPATCH /calendars/__uids__/<uid>/tasks/ -> calendar-color
+PROPPATCH /calendars/__uids__/<uid>/tasks/ -> calendar-order
+PROPPATCH /calendars/__uids__/<uid>/tasks/ -> calendar-timezone
+
+PROPFIND /calendars/__uids__/<uid>/calendar/->
+ getctag 37_63
+ sync-token data:,37_63/<hex>
+
+REPORT /calendars/__uids__/<uid>/calendar/ ->
+ getcontenttype
+ getetag
+REPORT /calendar/__uids__/<uid>/calendar/
+ getcontenttype
+ getetag
+
+PROPFIND /calendars/__uids__/<uid>/ ->
+ checksum-versions ???
+
+PROPFIND /calendars/__uids__/<uid>/calendar/ ->
+ getctag
+ sync-token
+PROPFIND /calendars/__uids__/<uid>/calendar/
+ getcontenttype httpd/unix-directory
+ getetag "<hex>"
+
+PROPFIND /calendars/__uids__/<uid>/ -> (again?)
+ checksum-versions
+
+PROPFIND /calendars/__uids__/<uid>/tasks/ ->
+ getctag
+ sync-token
+PROPFIND /calendars/__uids__/<uid>/tasks/ ->
+ getcontenttype
+ getetag
+
+PROPFIND /calendars/__uids__/<uid>/inbox/ ->
+ getctag
+ sync-token
+PROPFIND /calendars/__uids__/<uid>/inbox/ ->
+ getcontenttype
+ getetag
+
+PROPFIND /calendars/__uids__/<uid>/tasks/ ->
+ getctag
+ sync-token
+PROPFIND /calendars/__uids__/<uid>/tasks/ ->
+ getcontenttype
+ getetag
+
+PROPFIND /calendars/__uids__/<uid>/notification/ ->
+ getctag
+ sync-token
+PROPFIND /calendars/__uids__/<uid>/notification/ ->
+ notificationtype
+ getetag
+
+REPORT /principals/__uids__/<uid>/
+ calendar-proxy-write-for
+ calendar-user-address-set
+ email-address-set
+ displayname
+ calendar-proxy-read-for
+ calendar-user-address-set
+ email-address-set
+ displayname
+
+REPORT /calendars/__uids__/<uid>/
+ sync-collection
+ sync-token
+ sync-level
+ *lots of properties*
+
+PROPFIND /calendars/__uids__/<uid>/inbox/
+ getctag
+ sync-token
+
+PROPFIND /principals/__uids__/<uid>/
+ calendar-proxy-write-for
+ calendar-user-address-set
+ email-address-set
+ displayname
+ calendar-proxy-read-for
+ calendar-user-address-set
+ email-address-set
+ displayname
+
+----------------------------------------------------------------
+Deep Refresh (CMD + SHIFT + R)
+
+PROPFIND /principals/__uids__/<uid>/
+ <B:calendar-home-set xmlns:B="urn:ietf:params:xml:ns:caldav"/>
+ <B:calendar-user-address-set xmlns:B="urn:ietf:params:xml:ns:caldav"/>
+ <A:current-user-principal/>
+ <A:displayname/>
+ <C:dropbox-home-URL xmlns:C="http://calendarserver.org/ns/"/>
+ <C:email-address-set xmlns:C="http://calendarserver.org/ns/"/>
+ <C:notification-URL xmlns:C="http://calendarserver.org/ns/"/>
+ <A:principal-collection-set/>
+ <A:principal-URL/>
+ <A:resource-id/>
+ <B:schedule-inbox-URL xmlns:B="urn:ietf:params:xml:ns:caldav"/>
+ <B:schedule-outbox-URL xmlns:B="urn:ietf:params:xml:ns:caldav"/>
+ <A:supported-report-set/>
+
+OPTIONS /principals/__uids__/10000000-0000-0000-0000-000000000001/
+
+REPORT /principals/
+ principal-search-property-set
+
+PROPFIND /calendars/__uids__/10000000-0000-0000-0000-000000000001/inbox/
+ calendar-availability
+
+PROPFIND /calendars/__uids__/10000000-0000-0000-0000-000000000001/
+Depth 1
+ <A:add-member/>
+ <C:allowed-sharing-modes xmlns:C="http://calendarserver.org/ns/"/>
+ <D:autoprovisioned xmlns:D="http://apple.com/ns/ical/"/>
+ <E:bulk-requests xmlns:E="http://me.com/_namespace/"/>
+ <B:calendar-alarm xmlns:B="urn:ietf:params:xml:ns:caldav"/>
+ <D:calendar-color xmlns:D="http://apple.com/ns/ical/"/>
+ <B:calendar-description xmlns:B="urn:ietf:params:xml:ns:caldav"/>
+ <B:calendar-free-busy-set xmlns:B="urn:ietf:params:xml:ns:caldav"/>
+ <D:calendar-order xmlns:D="http://apple.com/ns/ical/"/>
+ <B:calendar-timezone xmlns:B="urn:ietf:params:xml:ns:caldav"/>
+ <A:current-user-privilege-set/>
+ <B:default-alarm-vevent-date xmlns:B="urn:ietf:params:xml:ns:caldav"/>
+ <B:default-alarm-vevent-datetime xmlns:B="urn:ietf:params:xml:ns:caldav"/>
+ <A:displayname/>
+ <C:getctag xmlns:C="http://calendarserver.org/ns/"/>
+ <C:invite xmlns:C="http://calendarserver.org/ns/"/>
+ <D:language-code xmlns:D="http://apple.com/ns/ical/"/>
+ <D:location-code xmlns:D="http://apple.com/ns/ical/"/>
+ <A:owner/>
+ <C:pre-publish-url xmlns:C="http://calendarserver.org/ns/"/>
+ <C:publish-url xmlns:C="http://calendarserver.org/ns/"/>
+ <C:push-transports xmlns:C="http://calendarserver.org/ns/"/>
+ <C:pushkey xmlns:C="http://calendarserver.org/ns/"/>
+ <A:quota-available-bytes/>
+ <A:quota-used-bytes/>
+ <D:refreshrate xmlns:D="http://apple.com/ns/ical/"/>
+ <A:resource-id/>
+ <A:resourcetype/>
+ <B:schedule-calendar-transp xmlns:B="urn:ietf:params:xml:ns:caldav"/>
+ <B:schedule-default-calendar-URL xmlns:B="urn:ietf:params:xml:ns:caldav"/>
+ <C:source xmlns:C="http://calendarserver.org/ns/"/>
+ <C:subscribed-strip-alarms xmlns:C="http://calendarserver.org/ns/"/>
+ <C:subscribed-strip-attachments xmlns:C="http://calendarserver.org/ns/"/>
+ <C:subscribed-strip-todos xmlns:C="http://calendarserver.org/ns/"/>
+ <B:supported-calendar-component-set xmlns:B="urn:ietf:params:xml:ns:caldav"/>
+ <B:supported-calendar-component-sets xmlns:B="urn:ietf:params:xml:ns:caldav"/>
+ <A:supported-report-set/>
+ <A:sync-token/>
+
+PROPFIND on calendar/tasks/inbox/notifications as before
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
Added: CalendarServer/trunk/simplugin/request-data/OS_X_10_11/StartupProfile
===================================================================
--- CalendarServer/trunk/simplugin/request-data/OS_X_10_11/StartupProfile (rev 0)
+++ CalendarServer/trunk/simplugin/request-data/OS_X_10_11/StartupProfile 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1,71 @@
+PROPFIND ./well-known/caldav - startup_well_known_propfind
+
+PROPFIND /principals/ - startup_principal_initial_propfind
+
+PROPFIND /principals/__uids__/<uid>/ - startup_principal_propfind
+
+REPORT /principals/ - startup_principals_report
+
+PROPFIND /calendars/__uids__/<uid>/inbox/ - ???
+ calendar-availability
+
+PROPFIND /calendars/__uids__/<uid>/ - poll_calendar_home_depth1_propfind
+
+PROPPATCH /calendars/__uids__/<uid>/ - startup_calendarhome_default_alarm_date_proppatch
+PROPPATCH /calendars/__uids__/<uid>/ - startup_calendarhome_default_alarm_datetime_proppatch
+
+PROPPATCH /calendars/__uids__/<uid>/calendar/ - startup_calendar_order_proppatch
+PROPPATCH /calendars/__uids__/<uid>/calendar/ - startup_calendar_displayname_proppatch
+PROPPATCH /calendars/__uids__/<uid>/calendar/ - startup_calendar_color_proppatch
+PROPPATCH /calendars/__uids__/<uid>/calendar/ - startup_calendar_timezone_proppatch
+
+PROPPATCH /calendars/__uids__/<uid>/tasks/ - startup_calendar_order_proppatch
+PROPPATCH /calendars/__uids__/<uid>/tasks/ - startup_calendar_displayname_proppatch
+PROPPATCH /calendars/__uids__/<uid>/tasks/ - startup_calendar_color_proppatch
+PROPPATCH /calendars/__uids__/<uid>/tasks/ - startup_calendar_timezone_proppatch
+
+PROPFIND /calendars/__uids__/<uid>/calendar/ - poll_calendar_propfind
+
+REPORT /calendars/__uids__/<uid>/calendar/ - startup_query_events_depth1_report.request
+
+PROPFIND /calendars/__uids__/<uid>/calendar/ - poll_calendar_propfind
+PROPFIND /calendars/__uids__/<uid>/calendar/ - poll_calendar_depth1_propfind
+
+PROPFIND /calendars/__uids__/<uid>/tasks/ - poll_calendar_propfind
+PROPFIND /calendars/__uids__/<uid>/tasks/ - poll_calendar_depth1_propfind
+PROPFIND /calendars/__uids__/<uid>/inbox/ - poll_calendar_propfind
+PROPFIND /calendars/__uids__/<uid>/inbox/ - poll_calendar_depth1_propfind
+PROPFIND /calendars/__uids__/<uid>/tasks/ - poll_calendar_propfind
+PROPFIND /calendars/__uids__/<uid>/tasks/ - poll_calendar_depth1_propfind
+PROPFIND /calendars/__uids__/<uid>/notification/ - poll_calendar_propfind
+PROPFIND /calendars/__uids__/<uid>/notification/ - poll_notification_depth1_propfind
+
+REPORT /principals/__uids__/<uid>/
+ calendar-proxy-write-for
+ calendar-user-address-set
+ email-address-set
+ displayname
+ calendar-proxy-read-for
+ calendar-user-address-set
+ email-address-set
+ displayname
+
+REPORT /calendars/__uids__/<uid>/
+ sync-collection
+ sync-token
+ sync-level
+ *lots of properties*
+
+PROPFIND /calendars/__uids__/<uid>/inbox/
+ getctag
+ sync-token
+
+PROPFIND /principals/__uids__/<uid>/
+ calendar-proxy-write-for
+ calendar-user-address-set
+ email-address-set
+ displayname
+ calendar-proxy-read-for
+ calendar-user-address-set
+ email-address-set
+ displayname
Added: CalendarServer/trunk/simplugin/request-data/OS_X_10_11/notification_multiget_report_hrefs.request
===================================================================
--- CalendarServer/trunk/simplugin/request-data/OS_X_10_11/notification_multiget_report_hrefs.request (rev 0)
+++ CalendarServer/trunk/simplugin/request-data/OS_X_10_11/notification_multiget_report_hrefs.request 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1 @@
+ <A:href xmlns:A="DAV:">%(href)s</A:href>
Added: CalendarServer/trunk/simplugin/request-data/OS_X_10_11/notification_sync.request
===================================================================
--- CalendarServer/trunk/simplugin/request-data/OS_X_10_11/notification_sync.request (rev 0)
+++ CalendarServer/trunk/simplugin/request-data/OS_X_10_11/notification_sync.request 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:sync-collection xmlns:A="DAV:">
+ <A:sync-token>%(sync-token)s</A:sync-token>
+ <A:sync-level>1</A:sync-level>
+ <A:prop>
+ <C:notificationtype xmlns:C="http://calendarserver.org/ns/"/>
+ <A:getetag/>
+ </A:prop>
+</A:sync-collection>
Added: CalendarServer/trunk/simplugin/request-data/OS_X_10_11/poll_calendar_depth1_propfind.request
===================================================================
--- CalendarServer/trunk/simplugin/request-data/OS_X_10_11/poll_calendar_depth1_propfind.request (rev 0)
+++ CalendarServer/trunk/simplugin/request-data/OS_X_10_11/poll_calendar_depth1_propfind.request 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propfind xmlns:A="DAV:">
+ <A:prop>
+ <A:getcontenttype/>
+ <A:getetag/>
+ </A:prop>
+</A:propfind>
Added: CalendarServer/trunk/simplugin/request-data/OS_X_10_11/poll_calendar_propfind.request
===================================================================
--- CalendarServer/trunk/simplugin/request-data/OS_X_10_11/poll_calendar_propfind.request (rev 0)
+++ CalendarServer/trunk/simplugin/request-data/OS_X_10_11/poll_calendar_propfind.request 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propfind xmlns:A="DAV:">
+ <A:prop>
+ <C:getctag xmlns:C="http://calendarserver.org/ns/"/>
+ <A:sync-token/>
+ </A:prop>
+</A:propfind>
Added: CalendarServer/trunk/simplugin/request-data/OS_X_10_11/poll_calendarhome_depth1_propfind.request
===================================================================
--- CalendarServer/trunk/simplugin/request-data/OS_X_10_11/poll_calendarhome_depth1_propfind.request (rev 0)
+++ CalendarServer/trunk/simplugin/request-data/OS_X_10_11/poll_calendarhome_depth1_propfind.request 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propfind xmlns:A="DAV:">
+ <A:prop>
+ <A:add-member/>
+ <C:allowed-sharing-modes xmlns:C="http://calendarserver.org/ns/"/>
+ <D:autoprovisioned xmlns:D="http://apple.com/ns/ical/"/>
+ <E:bulk-requests xmlns:E="http://me.com/_namespace/"/>
+ <B:calendar-alarm xmlns:B="urn:ietf:params:xml:ns:caldav"/>
+ <D:calendar-color xmlns:D="http://apple.com/ns/ical/"/>
+ <B:calendar-description xmlns:B="urn:ietf:params:xml:ns:caldav"/>
+ <B:calendar-free-busy-set xmlns:B="urn:ietf:params:xml:ns:caldav"/>
+ <D:calendar-order xmlns:D="http://apple.com/ns/ical/"/>
+ <B:calendar-timezone xmlns:B="urn:ietf:params:xml:ns:caldav"/>
+ <A:current-user-privilege-set/>
+ <B:default-alarm-vevent-date xmlns:B="urn:ietf:params:xml:ns:caldav"/>
+ <B:default-alarm-vevent-datetime xmlns:B="urn:ietf:params:xml:ns:caldav"/>
+ <A:displayname/>
+ <C:getctag xmlns:C="http://calendarserver.org/ns/"/>
+ <C:invite xmlns:C="http://calendarserver.org/ns/"/>
+ <D:language-code xmlns:D="http://apple.com/ns/ical/"/>
+ <D:location-code xmlns:D="http://apple.com/ns/ical/"/>
+ <A:owner/>
+ <C:pre-publish-url xmlns:C="http://calendarserver.org/ns/"/>
+ <C:publish-url xmlns:C="http://calendarserver.org/ns/"/>
+ <C:push-transports xmlns:C="http://calendarserver.org/ns/"/>
+ <C:pushkey xmlns:C="http://calendarserver.org/ns/"/>
+ <A:quota-available-bytes/>
+ <A:quota-used-bytes/>
+ <D:refreshrate xmlns:D="http://apple.com/ns/ical/"/>
+ <A:resource-id/>
+ <A:resourcetype/>
+ <B:schedule-calendar-transp xmlns:B="urn:ietf:params:xml:ns:caldav"/>
+ <B:schedule-default-calendar-URL xmlns:B="urn:ietf:params:xml:ns:caldav"/>
+ <C:source xmlns:C="http://calendarserver.org/ns/"/>
+ <C:subscribed-strip-alarms xmlns:C="http://calendarserver.org/ns/"/>
+ <C:subscribed-strip-attachments xmlns:C="http://calendarserver.org/ns/"/>
+ <C:subscribed-strip-todos xmlns:C="http://calendarserver.org/ns/"/>
+ <B:supported-calendar-component-set xmlns:B="urn:ietf:params:xml:ns:caldav"/>
+ <B:supported-calendar-component-sets xmlns:B="urn:ietf:params:xml:ns:caldav"/>
+ <A:supported-report-set/>
+ <A:sync-token/>
+ </A:prop>
+</A:propfind>
Added: CalendarServer/trunk/simplugin/request-data/OS_X_10_11/poll_calendarhome_sync.request
===================================================================
--- CalendarServer/trunk/simplugin/request-data/OS_X_10_11/poll_calendarhome_sync.request (rev 0)
+++ CalendarServer/trunk/simplugin/request-data/OS_X_10_11/poll_calendarhome_sync.request 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:sync-collection xmlns:A="DAV:">
+ <A:sync-token>data:,30_1122/8bbf7c540e5fca2cc3220f114a8164f7</A:sync-token>
+ <A:sync-level>1</A:sync-level>
+ <A:prop>
+ <C:publish-url xmlns:C="http://calendarserver.org/ns/"/>
+ <B:supported-calendar-component-sets xmlns:B="urn:ietf:params:xml:ns:caldav"/>
+ <B:schedule-calendar-transp xmlns:B="urn:ietf:params:xml:ns:caldav"/>
+ <C:source xmlns:C="http://calendarserver.org/ns/"/>
+ <B:calendar-description xmlns:B="urn:ietf:params:xml:ns:caldav"/>
+ <D:location-code xmlns:D="http://apple.com/ns/ical/"/>
+ <D:autoprovisioned xmlns:D="http://apple.com/ns/ical/"/>
+ <A:quota-used-bytes/>
+ <C:pre-publish-url xmlns:C="http://calendarserver.org/ns/"/>
+ <D:calendar-order xmlns:D="http://apple.com/ns/ical/"/>
+ <D:refreshrate xmlns:D="http://apple.com/ns/ical/"/>
+ <C:subscribed-strip-attachments xmlns:C="http://calendarserver.org/ns/"/>
+ <C:push-transports xmlns:C="http://calendarserver.org/ns/"/>
+ <B:schedule-default-calendar-URL xmlns:B="urn:ietf:params:xml:ns:caldav"/>
+ <B:calendar-alarm xmlns:B="urn:ietf:params:xml:ns:caldav"/>
+ <B:supported-calendar-component-set xmlns:B="urn:ietf:params:xml:ns:caldav"/>
+ <A:owner/>
+ <A:add-member/>
+ <C:invite xmlns:C="http://calendarserver.org/ns/"/>
+ <A:resource-id/>
+ <E:bulk-requests xmlns:E="http://me.com/_namespace/"/>
+ <B:calendar-timezone xmlns:B="urn:ietf:params:xml:ns:caldav"/>
+ <A:supported-report-set/>
+ <A:displayname/>
+ <C:subscribed-strip-alarms xmlns:C="http://calendarserver.org/ns/"/>
+ <D:language-code xmlns:D="http://apple.com/ns/ical/"/>
+ <C:subscribed-strip-todos xmlns:C="http://calendarserver.org/ns/"/>
+ <C:allowed-sharing-modes xmlns:C="http://calendarserver.org/ns/"/>
+ <A:current-user-privilege-set/>
+ <B:calendar-free-busy-set xmlns:B="urn:ietf:params:xml:ns:caldav"/>
+ <A:sync-token/>
+ <A:quota-available-bytes/>
+ <C:pushkey xmlns:C="http://calendarserver.org/ns/"/>
+ <B:default-alarm-vevent-date xmlns:B="urn:ietf:params:xml:ns:caldav"/>
+ <A:resourcetype/>
+ <B:default-alarm-vevent-datetime xmlns:B="urn:ietf:params:xml:ns:caldav"/>
+ <D:calendar-color xmlns:D="http://apple.com/ns/ical/"/>
+ <C:getctag xmlns:C="http://calendarserver.org/ns/"/>
+ </A:prop>
+</A:sync-collection>
Added: CalendarServer/trunk/simplugin/request-data/OS_X_10_11/poll_notification_depth1_propfind.request
===================================================================
--- CalendarServer/trunk/simplugin/request-data/OS_X_10_11/poll_notification_depth1_propfind.request (rev 0)
+++ CalendarServer/trunk/simplugin/request-data/OS_X_10_11/poll_notification_depth1_propfind.request 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propfind xmlns:A="DAV:">
+ <A:prop>
+ <A:getetag/>
+ <C:notificationtype xmlns:C="http://calendarserver.org/ns/"/>
+ </A:prop>
+</A:propfind>
Added: CalendarServer/trunk/simplugin/request-data/OS_X_10_11/post_freebusy.request
===================================================================
--- CalendarServer/trunk/simplugin/request-data/OS_X_10_11/post_freebusy.request (rev 0)
+++ CalendarServer/trunk/simplugin/request-data/OS_X_10_11/post_freebusy.request 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1,13 @@
+BEGIN:VCALENDAR
+VERSION:2.0
+METHOD:REPLY
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VFREEBUSY
+UID:4288F0F3-5C5B-4DF4-9AD8-B1E5FE3F5B97
+DTSTART:20150804T211500Z
+DTEND:20150804T231500Z
+ATTENDEE:urn:uuid:30000000-0000-0000-0000-000000000005
+DTSTAMP:20150727T203410Z
+ORGANIZER:mailto:user01 at example.com
+END:VFREEBUSY
+END:VCALENDAR
\ No newline at end of file
Added: CalendarServer/trunk/simplugin/request-data/OS_X_10_11/principal_search_report.request
===================================================================
--- CalendarServer/trunk/simplugin/request-data/OS_X_10_11/principal_search_report.request (rev 0)
+++ CalendarServer/trunk/simplugin/request-data/OS_X_10_11/principal_search_report.request 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<C:calendarserver-principal-search xmlns:C="http://calendarserver.org/ns/" context="{context}">
+ {searchTokens}
+ <A:prop xmlns:A="DAV:">
+ <C:email-address-set/>
+ <B:calendar-user-type xmlns:B="urn:ietf:params:xml:ns:caldav"/>
+ <B:calendar-user-address-set xmlns:B="urn:ietf:params:xml:ns:caldav"/>
+ <A:principal-URL/>
+ <C:last-name/>
+ <C:record-type/>
+ <A:displayname/>
+ <C:first-name/>
+ </A:prop>
+</C:calendarserver-principal-search>
\ No newline at end of file
Added: CalendarServer/trunk/simplugin/request-data/OS_X_10_11/report_principal_search.request
===================================================================
--- CalendarServer/trunk/simplugin/request-data/OS_X_10_11/report_principal_search.request (rev 0)
+++ CalendarServer/trunk/simplugin/request-data/OS_X_10_11/report_principal_search.request 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<C:calendarserver-principal-search xmlns:C="http://calendarserver.org/ns/" context="attendee">
+ <C:search-token>%(search)s</C:search-token>
+ <A:prop xmlns:A="DAV:">
+ <B:calendar-user-type xmlns:B="urn:ietf:params:xml:ns:caldav"/>
+ <C:email-address-set/>
+ <A:displayname/>
+ <C:first-name/>
+ <C:last-name/>
+ <A:principal-URL/>
+ <C:record-type/>
+ <B:calendar-user-address-set xmlns:B="urn:ietf:params:xml:ns:caldav"/>
+ </A:prop>
+</C:calendarserver-principal-search>
\ No newline at end of file
Added: CalendarServer/trunk/simplugin/request-data/OS_X_10_11/startup_calendar_color_proppatch.request
===================================================================
--- CalendarServer/trunk/simplugin/request-data/OS_X_10_11/startup_calendar_color_proppatch.request (rev 0)
+++ CalendarServer/trunk/simplugin/request-data/OS_X_10_11/startup_calendar_color_proppatch.request 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propertyupdate xmlns:A="DAV:"><A:set><A:prop><D:calendar-color xmlns:D="http://apple.com/ns/ical/" symbolic-color="orange">#FD8208FF</D:calendar-color></A:prop></A:set></A:propertyupdate>
\ No newline at end of file
Added: CalendarServer/trunk/simplugin/request-data/OS_X_10_11/startup_calendar_description_proppatch.request.xml
===================================================================
--- CalendarServer/trunk/simplugin/request-data/OS_X_10_11/startup_calendar_description_proppatch.request.xml (rev 0)
+++ CalendarServer/trunk/simplugin/request-data/OS_X_10_11/startup_calendar_description_proppatch.request.xml 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propertyupdate xmlns:A="DAV:"><A:set><A:prop><B:calendar-description xmlns:B="urn:ietf:params:xml:ns:caldav">some description</B:calendar-description></A:prop></A:set></A:propertyupdate>
\ No newline at end of file
Added: CalendarServer/trunk/simplugin/request-data/OS_X_10_11/startup_calendar_displayname_proppatch.request
===================================================================
--- CalendarServer/trunk/simplugin/request-data/OS_X_10_11/startup_calendar_displayname_proppatch.request (rev 0)
+++ CalendarServer/trunk/simplugin/request-data/OS_X_10_11/startup_calendar_displayname_proppatch.request 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propertyupdate xmlns:A="DAV:"><A:set><A:prop><A:displayname>calendar</A:displayname></A:prop></A:set></A:propertyupdate>
\ No newline at end of file
Added: CalendarServer/trunk/simplugin/request-data/OS_X_10_11/startup_calendar_order_proppatch.request
===================================================================
--- CalendarServer/trunk/simplugin/request-data/OS_X_10_11/startup_calendar_order_proppatch.request (rev 0)
+++ CalendarServer/trunk/simplugin/request-data/OS_X_10_11/startup_calendar_order_proppatch.request 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propertyupdate xmlns:A="DAV:"><A:set><A:prop><D:calendar-order xmlns:D="http://apple.com/ns/ical/">1</D:calendar-order></A:prop></A:set></A:propertyupdate>
Added: CalendarServer/trunk/simplugin/request-data/OS_X_10_11/startup_calendar_timezone_proppatch.request
===================================================================
--- CalendarServer/trunk/simplugin/request-data/OS_X_10_11/startup_calendar_timezone_proppatch.request (rev 0)
+++ CalendarServer/trunk/simplugin/request-data/OS_X_10_11/startup_calendar_timezone_proppatch.request 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propertyupdate xmlns:A="DAV:"><A:set><A:prop><B:calendar-timezone xmlns:B="urn:ietf:params:xml:ns:caldav">BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//Mac OS X 10.11//EN
+CALSCALE:GREGORIAN
+BEGIN:VTIMEZONE
+TZID:America/Los_Angeles
+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
+END:VCALENDAR
+</B:calendar-timezone></A:prop></A:set></A:propertyupdate>
\ No newline at end of file
Added: CalendarServer/trunk/simplugin/request-data/OS_X_10_11/startup_calendar_transparent_proppatch.request
===================================================================
--- CalendarServer/trunk/simplugin/request-data/OS_X_10_11/startup_calendar_transparent_proppatch.request (rev 0)
+++ CalendarServer/trunk/simplugin/request-data/OS_X_10_11/startup_calendar_transparent_proppatch.request 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propertyupdate xmlns:A="DAV:"><A:set><A:prop><B:schedule-calendar-transp xmlns:B="urn:ietf:params:xml:ns:caldav"><B:transparent/></B:schedule-calendar-transp></A:prop></A:set></A:propertyupdate>
+
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propertyupdate xmlns:A="DAV:"><A:set><A:prop><B:schedule-calendar-transp xmlns:B="urn:ietf:params:xml:ns:caldav"><B:opaque/></B:schedule-calendar-transp></A:prop></A:set></A:propertyupdate>
\ No newline at end of file
Added: CalendarServer/trunk/simplugin/request-data/OS_X_10_11/startup_calendarhome_default_alarm_date_proppatch.request
===================================================================
--- CalendarServer/trunk/simplugin/request-data/OS_X_10_11/startup_calendarhome_default_alarm_date_proppatch.request (rev 0)
+++ CalendarServer/trunk/simplugin/request-data/OS_X_10_11/startup_calendarhome_default_alarm_date_proppatch.request 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propertyupdate xmlns:A="DAV:"><A:set><A:prop><B:default-alarm-vevent-date xmlns:B="urn:ietf:params:xml:ns:caldav">BEGIN:VALARM
+X-WR-ALARMUID:49F29226-D2D7-4464-AE22-0147EDEFB2B4
+UID:49F29226-D2D7-4464-AE22-0147EDEFB2B4
+TRIGGER:-PT15H
+ATTACH;VALUE=URI:Basso
+ACTION:AUDIO
+END:VALARM
+</B:default-alarm-vevent-date></A:prop></A:set></A:propertyupdate>
Added: CalendarServer/trunk/simplugin/request-data/OS_X_10_11/startup_calendarhome_default_alarm_datetime_proppatch.request
===================================================================
--- CalendarServer/trunk/simplugin/request-data/OS_X_10_11/startup_calendarhome_default_alarm_datetime_proppatch.request (rev 0)
+++ CalendarServer/trunk/simplugin/request-data/OS_X_10_11/startup_calendarhome_default_alarm_datetime_proppatch.request 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propertyupdate xmlns:A="DAV:"><A:set><A:prop><B:default-alarm-vevent-datetime xmlns:B="urn:ietf:params:xml:ns:caldav">BEGIN:VALARM
+X-WR-ALARMUID:4AD03A33-54A6-42BE-A157-47273DD60803
+UID:4AD03A33-54A6-42BE-A157-47273DD60803
+TRIGGER;VALUE=DATE-TIME:19760401T005545Z
+ACTION:NONE
+END:VALARM
+</B:default-alarm-vevent-datetime></A:prop></A:set></A:propertyupdate>
Added: CalendarServer/trunk/simplugin/request-data/OS_X_10_11/startup_create_calendar.request
===================================================================
--- CalendarServer/trunk/simplugin/request-data/OS_X_10_11/startup_create_calendar.request (rev 0)
+++ CalendarServer/trunk/simplugin/request-data/OS_X_10_11/startup_create_calendar.request 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<B:mkcalendar xmlns:B="urn:ietf:params:xml:ns:caldav">
+ <A:set xmlns:A="DAV:">
+ <A:prop>
+ <D:calendar-order xmlns:D="http://apple.com/ns/ical/">{order}</D:calendar-order>
+ <B:supported-calendar-component-set>
+ <B:comp name="{component_type}"/>
+ </B:supported-calendar-component-set>
+ <D:calendar-color xmlns:D="http://apple.com/ns/ical/" symbolic-color="custom">#{color}</D:calendar-color>
+ <B:calendar-timezone>BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//Mac OS X 10.11//EN
+CALSCALE:GREGORIAN
+BEGIN:VTIMEZONE
+TZID:America/Los_Angeles
+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
+END:VCALENDAR
+</B:calendar-timezone>
+ <A:displayname>{name}</A:displayname>
+ <B:schedule-calendar-transp>
+ <B:opaque/>
+ </B:schedule-calendar-transp>
+ </A:prop>
+ </A:set>
+</B:mkcalendar>
\ No newline at end of file
Added: CalendarServer/trunk/simplugin/request-data/OS_X_10_11/startup_delegate_principal_propfind.request
===================================================================
--- CalendarServer/trunk/simplugin/request-data/OS_X_10_11/startup_delegate_principal_propfind.request (rev 0)
+++ CalendarServer/trunk/simplugin/request-data/OS_X_10_11/startup_delegate_principal_propfind.request 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propfind xmlns:A="DAV:">
+ <A:prop>
+ <B:allowed-calendar-component-set xmlns:B="http://calendarserver.org/ns/"/>
+ <C:calendar-home-set xmlns:C="urn:ietf:params:xml:ns:caldav"/>
+ <C:calendar-user-address-set xmlns:C="urn:ietf:params:xml:ns:caldav"/>
+ <A:current-user-principal/>
+ <A:displayname/>
+ <B:dropbox-home-URL xmlns:B="http://calendarserver.org/ns/"/>
+ <B:email-address-set xmlns:B="http://calendarserver.org/ns/"/>
+ <B:notification-URL xmlns:B="http://calendarserver.org/ns/"/>
+ <A:principal-collection-set/>
+ <A:principal-URL/>
+ <A:resource-id/>
+ <C:schedule-inbox-URL xmlns:C="urn:ietf:params:xml:ns:caldav"/>
+ <C:schedule-outbox-URL xmlns:C="urn:ietf:params:xml:ns:caldav"/>
+ <A:supported-report-set/>
+ </A:prop>
+</A:propfind>
Added: CalendarServer/trunk/simplugin/request-data/OS_X_10_11/startup_principal_expand.request
===================================================================
--- CalendarServer/trunk/simplugin/request-data/OS_X_10_11/startup_principal_expand.request (rev 0)
+++ CalendarServer/trunk/simplugin/request-data/OS_X_10_11/startup_principal_expand.request 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:expand-property xmlns:A="DAV:">
+ <A:property name="calendar-proxy-read-for" namespace="http://calendarserver.org/ns/">
+ <A:property name="calendar-user-address-set" namespace="urn:ietf:params:xml:ns:caldav"/>
+ <A:property name="email-address-set" namespace="http://calendarserver.org/ns/"/>
+ <A:property name="displayname" namespace="DAV:"/>
+ </A:property>
+ <A:property name="calendar-proxy-write-for" namespace="http://calendarserver.org/ns/">
+ <A:property name="calendar-user-address-set" namespace="urn:ietf:params:xml:ns:caldav"/>
+ <A:property name="email-address-set" namespace="http://calendarserver.org/ns/"/>
+ <A:property name="displayname" namespace="DAV:"/>
+ </A:property>
+</A:expand-property>
Added: CalendarServer/trunk/simplugin/request-data/OS_X_10_11/startup_principal_initial_propfind.request
===================================================================
--- CalendarServer/trunk/simplugin/request-data/OS_X_10_11/startup_principal_initial_propfind.request (rev 0)
+++ CalendarServer/trunk/simplugin/request-data/OS_X_10_11/startup_principal_initial_propfind.request 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propfind xmlns:A="DAV:">
+ <A:prop>
+ <A:current-user-principal/>
+ <A:principal-URL/>
+ <A:resourcetype/>
+ </A:prop>
+</A:propfind>
Added: CalendarServer/trunk/simplugin/request-data/OS_X_10_11/startup_principal_propfind.request
===================================================================
--- CalendarServer/trunk/simplugin/request-data/OS_X_10_11/startup_principal_propfind.request (rev 0)
+++ CalendarServer/trunk/simplugin/request-data/OS_X_10_11/startup_principal_propfind.request 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propfind xmlns:A="DAV:">
+ <A:prop>
+ <B:calendar-home-set xmlns:B="urn:ietf:params:xml:ns:caldav"/>
+ <B:calendar-user-address-set xmlns:B="urn:ietf:params:xml:ns:caldav"/>
+ <A:current-user-principal/>
+ <A:displayname/>
+ <C:dropbox-home-URL xmlns:C="http://calendarserver.org/ns/"/>
+ <C:email-address-set xmlns:C="http://calendarserver.org/ns/"/>
+ <C:notification-URL xmlns:C="http://calendarserver.org/ns/"/>
+ <A:principal-collection-set/>
+ <A:principal-URL/>
+ <A:resource-id/>
+ <B:schedule-inbox-URL xmlns:B="urn:ietf:params:xml:ns:caldav"/>
+ <B:schedule-outbox-URL xmlns:B="urn:ietf:params:xml:ns:caldav"/>
+ <A:supported-report-set/>
+ </A:prop>
+</A:propfind>
Added: CalendarServer/trunk/simplugin/request-data/OS_X_10_11/startup_principals_report.request
===================================================================
--- CalendarServer/trunk/simplugin/request-data/OS_X_10_11/startup_principals_report.request (rev 0)
+++ CalendarServer/trunk/simplugin/request-data/OS_X_10_11/startup_principals_report.request 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:principal-search-property-set xmlns:A="DAV:"/>
Added: CalendarServer/trunk/simplugin/request-data/OS_X_10_11/startup_query_events_depth1_report.request
===================================================================
--- CalendarServer/trunk/simplugin/request-data/OS_X_10_11/startup_query_events_depth1_report.request (rev 0)
+++ CalendarServer/trunk/simplugin/request-data/OS_X_10_11/startup_query_events_depth1_report.request 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<B:calendar-query xmlns:B="urn:ietf:params:xml:ns:caldav">
+ <A:prop xmlns:A="DAV:">
+ <A:getetag/>
+ <A:getcontenttype/>
+ </A:prop>
+ <B:filter>
+ <B:comp-filter name="VCALENDAR">
+ <B:comp-filter name="VEVENT">
+ <B:time-range start="20150630T010101Z" end="20150721T010101Z"/>
+ </B:comp-filter>
+ </B:comp-filter>
+ </B:filter>
+</B:calendar-query>
Added: CalendarServer/trunk/simplugin/request-data/OS_X_10_11/startup_well_known_propfind.request
===================================================================
--- CalendarServer/trunk/simplugin/request-data/OS_X_10_11/startup_well_known_propfind.request (rev 0)
+++ CalendarServer/trunk/simplugin/request-data/OS_X_10_11/startup_well_known_propfind.request 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propfind xmlns:A="DAV:">
+ <A:prop>
+ <A:current-user-principal/>
+ <A:principal-URL/>
+ <A:resourcetype/>
+ </A:prop>
+</A:propfind>
Added: CalendarServer/trunk/simplugin/request-data/OS_X_10_6/poll_calendar_multiget.request
===================================================================
--- CalendarServer/trunk/simplugin/request-data/OS_X_10_6/poll_calendar_multiget.request (rev 0)
+++ CalendarServer/trunk/simplugin/request-data/OS_X_10_6/poll_calendar_multiget.request 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<x0:calendar-multiget xmlns:x0="urn:ietf:params:xml:ns:caldav" xmlns:x1="DAV:">
+ <x1:prop>
+ <x1:getetag/>
+ <x0:calendar-data/>
+ <x0:schedule-tag/>
+ </x1:prop>
+%(hrefs)s
+</x0:calendar-multiget>
Added: CalendarServer/trunk/simplugin/request-data/OS_X_10_6/poll_calendar_multiget_hrefs.request
===================================================================
--- CalendarServer/trunk/simplugin/request-data/OS_X_10_6/poll_calendar_multiget_hrefs.request (rev 0)
+++ CalendarServer/trunk/simplugin/request-data/OS_X_10_6/poll_calendar_multiget_hrefs.request 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1 @@
+ <x1:href>%(href)s</x1:href>
Added: CalendarServer/trunk/simplugin/request-data/OS_X_10_6/poll_calendar_propfind.request
===================================================================
--- CalendarServer/trunk/simplugin/request-data/OS_X_10_6/poll_calendar_propfind.request (rev 0)
+++ CalendarServer/trunk/simplugin/request-data/OS_X_10_6/poll_calendar_propfind.request 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propfind xmlns:A="DAV:">
+ <A:prop>
+ <B:getctag xmlns:B="http://calendarserver.org/ns/"/>
+ <A:sync-token/>
+ </A:prop>
+</A:propfind>
Added: CalendarServer/trunk/simplugin/request-data/OS_X_10_6/poll_calendar_propfind_d1.request
===================================================================
--- CalendarServer/trunk/simplugin/request-data/OS_X_10_6/poll_calendar_propfind_d1.request (rev 0)
+++ CalendarServer/trunk/simplugin/request-data/OS_X_10_6/poll_calendar_propfind_d1.request 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<x0:propfind xmlns:x0="DAV:" xmlns:x1="http://calendarserver.org/ns/">
+ <x0:prop>
+ <x0:getetag/>
+ <x0:resourcetype/>
+ <x1:notificationtype/>
+ </x0:prop>
+</x0:propfind>
Added: CalendarServer/trunk/simplugin/request-data/OS_X_10_6/poll_calendarhome_propfind.request
===================================================================
--- CalendarServer/trunk/simplugin/request-data/OS_X_10_6/poll_calendarhome_propfind.request (rev 0)
+++ CalendarServer/trunk/simplugin/request-data/OS_X_10_6/poll_calendarhome_propfind.request 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<x0:propfind xmlns:x0="DAV:" xmlns:x3="http://apple.com/ns/ical/" xmlns:x1="http://calendarserver.org/ns/" xmlns:x2="urn:ietf:params:xml:ns:caldav">
+ <x0:prop>
+ <x1:xmpp-server/>
+ <x1:xmpp-uri/>
+ <x1:getctag/>
+ <x0:displayname/>
+ <x2:calendar-description/>
+ <x3:calendar-color/>
+ <x3:calendar-order/>
+ <x2:supported-calendar-component-set/>
+ <x0:resourcetype/>
+ <x0:owner/>
+ <x2:calendar-free-busy-set/>
+ <x2:schedule-calendar-transp/>
+ <x2:schedule-default-calendar-URL/>
+ <x0:quota-available-bytes/>
+ <x0:quota-used-bytes/>
+ <x2:calendar-timezone/>
+ <x0:current-user-privilege-set/>
+ <x1:source/>
+ <x1:subscribed-strip-alarms/>
+ <x1:subscribed-strip-attachments/>
+ <x1:subscribed-strip-todos/>
+ <x3:refreshrate/>
+ <x1:push-transports/>
+ <x1:pushkey/>
+ <x1:publish-url/>
+ </x0:prop>
+</x0:propfind>
Added: CalendarServer/trunk/simplugin/request-data/OS_X_10_6/poll_notification_propfind_d1.request
===================================================================
--- CalendarServer/trunk/simplugin/request-data/OS_X_10_6/poll_notification_propfind_d1.request (rev 0)
+++ CalendarServer/trunk/simplugin/request-data/OS_X_10_6/poll_notification_propfind_d1.request 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propfind xmlns:A="DAV:">
+ <A:prop>
+ <A:getetag/>
+ <B:notificationtype xmlns:B="http://calendarserver.org/ns/"/>
+ </A:prop>
+</A:propfind>
Added: CalendarServer/trunk/simplugin/request-data/OS_X_10_6/post_availability.request
===================================================================
--- CalendarServer/trunk/simplugin/request-data/OS_X_10_6/post_availability.request (rev 0)
+++ CalendarServer/trunk/simplugin/request-data/OS_X_10_6/post_availability.request 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1,14 @@
+BEGIN:VCALENDAR
+CALSCALE:GREGORIAN
+VERSION:2.0
+METHOD:REQUEST
+PRODID:-//Apple Inc.//iCal 4.0.3//EN
+BEGIN:VFREEBUSY
+UID:%(vfreebusy-uid)s
+DTEND:%(end)s
+%(attendees)sDTSTART:%(start)s
+%(event-mask)sDTSTAMP:%(now)s
+ORGANIZER:%(organizer)s
+SUMMARY:%(summary)s
+END:VFREEBUSY
+END:VCALENDAR
Added: CalendarServer/trunk/simplugin/request-data/OS_X_10_6/startup_calendar_color_proppatch.request
===================================================================
--- CalendarServer/trunk/simplugin/request-data/OS_X_10_6/startup_calendar_color_proppatch.request (rev 0)
+++ CalendarServer/trunk/simplugin/request-data/OS_X_10_6/startup_calendar_color_proppatch.request 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propertyupdate xmlns:A="DAV:"><A:set><A:prop><E:calendar-color xmlns:E="http://apple.com/ns/ical/">#882F00FF</E:calendar-color></A:prop></A:set></A:propertyupdate>
Added: CalendarServer/trunk/simplugin/request-data/OS_X_10_6/startup_calendar_order_proppatch.request
===================================================================
--- CalendarServer/trunk/simplugin/request-data/OS_X_10_6/startup_calendar_order_proppatch.request (rev 0)
+++ CalendarServer/trunk/simplugin/request-data/OS_X_10_6/startup_calendar_order_proppatch.request 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propertyupdate xmlns:A="DAV:"><A:set><A:prop><E:calendar-order xmlns:E="http://apple.com/ns/ical/">1</E:calendar-order></A:prop></A:set></A:propertyupdate>
Added: CalendarServer/trunk/simplugin/request-data/OS_X_10_6/startup_calendar_timezone_proppatch.request
===================================================================
--- CalendarServer/trunk/simplugin/request-data/OS_X_10_6/startup_calendar_timezone_proppatch.request (rev 0)
+++ CalendarServer/trunk/simplugin/request-data/OS_X_10_6/startup_calendar_timezone_proppatch.request 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propertyupdate xmlns:A="DAV:"><A:set><A:prop><C:calendar-timezone xmlns:C="urn:ietf:params:xml:ns:caldav">BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//iCal 5.0.2//EN
+CALSCALE:GREGORIAN
+BEGIN:VTIMEZONE
+TZID:America/New_York
+BEGIN:DAYLIGHT
+TZOFFSETFROM:-0500
+RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
+DTSTART:20070311T020000
+TZNAME:EDT
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+TZOFFSETFROM:-0400
+RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
+DTSTART:20071104T020000
+TZNAME:EST
+TZOFFSETTO:-0500
+END:STANDARD
+END:VTIMEZONE
+END:VCALENDAR
+</C:calendar-timezone></A:prop></A:set></A:propertyupdate>
Added: CalendarServer/trunk/simplugin/request-data/OS_X_10_6/startup_notification_propfind.request
===================================================================
--- CalendarServer/trunk/simplugin/request-data/OS_X_10_6/startup_notification_propfind.request (rev 0)
+++ CalendarServer/trunk/simplugin/request-data/OS_X_10_6/startup_notification_propfind.request 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<x0:propfind xmlns:x0="DAV:" xmlns:x1="http://calendarserver.org/ns/">
+ <x0:prop>
+ <x0:getetag/>
+ <x0:resourcetype/>
+ <x1:notificationtype/>
+ </x0:prop>
+</x0:propfind>
Added: CalendarServer/trunk/simplugin/request-data/OS_X_10_6/startup_principal_expand.request
===================================================================
--- CalendarServer/trunk/simplugin/request-data/OS_X_10_6/startup_principal_expand.request (rev 0)
+++ CalendarServer/trunk/simplugin/request-data/OS_X_10_6/startup_principal_expand.request 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<x0:expand-property xmlns:x0="DAV:"><x0:property name="calendar-proxy-write-for" namespace="http://calendarserver.org/ns/"><x0:property name="displayname"/><x0:property name="principal-URL"/><x0:property name="calendar-user-address-set" namespace="urn:ietf:params:xml:ns:caldav"/></x0:property><x0:property name="calendar-proxy-read-for" namespace="http://calendarserver.org/ns/"><x0:property name="displayname"/><x0:property name="principal-URL"/><x0:property name="calendar-user-address-set" namespace="urn:ietf:params:xml:ns:caldav"/></x0:property></x0:expand-property>
Added: CalendarServer/trunk/simplugin/request-data/OS_X_10_6/startup_principal_propfind.request
===================================================================
--- CalendarServer/trunk/simplugin/request-data/OS_X_10_6/startup_principal_propfind.request (rev 0)
+++ CalendarServer/trunk/simplugin/request-data/OS_X_10_6/startup_principal_propfind.request 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<x0:propfind xmlns:x1="urn:ietf:params:xml:ns:caldav" xmlns:x0="DAV:" xmlns:x2="http://calendarserver.org/ns/">
+ <x0:prop>
+ <x0:principal-collection-set/>
+ <x1:calendar-home-set/>
+ <x1:calendar-user-address-set/>
+ <x1:schedule-inbox-URL/>
+ <x1:schedule-outbox-URL/>
+ <x2:dropbox-home-URL/>
+ <x2:xmpp-uri/>
+ <x2:notification-URL/>
+ <x0:displayname/>
+ <x0:principal-URL/>
+ <x0:supported-report-set/>
+ </x0:prop>
+</x0:propfind>
Added: CalendarServer/trunk/simplugin/request-data/OS_X_10_6/startup_principal_propfind_initial.request
===================================================================
--- CalendarServer/trunk/simplugin/request-data/OS_X_10_6/startup_principal_propfind_initial.request (rev 0)
+++ CalendarServer/trunk/simplugin/request-data/OS_X_10_6/startup_principal_propfind_initial.request 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propfind xmlns:A="DAV:">
+ <A:prop>
+ <A:current-user-principal/>
+ <A:principal-URL/>
+ <A:resourcetype/>
+ </A:prop>
+</A:propfind>
Added: CalendarServer/trunk/simplugin/request-data/OS_X_10_6/startup_principals_report.request
===================================================================
--- CalendarServer/trunk/simplugin/request-data/OS_X_10_6/startup_principals_report.request (rev 0)
+++ CalendarServer/trunk/simplugin/request-data/OS_X_10_6/startup_principals_report.request 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<x0:principal-search-property-set xmlns:x0="DAV:"/>
Added: CalendarServer/trunk/simplugin/request-data/OS_X_10_6/startup_well_known.request
===================================================================
--- CalendarServer/trunk/simplugin/request-data/OS_X_10_6/startup_well_known.request (rev 0)
+++ CalendarServer/trunk/simplugin/request-data/OS_X_10_6/startup_well_known.request 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propfind xmlns:A="DAV:">
+ <A:prop>
+ <A:current-user-principal/>
+ <A:principal-URL/>
+ <A:resourcetype/>
+ </A:prop>
+</A:propfind>
Added: CalendarServer/trunk/simplugin/request-data/OS_X_10_6/user_list_principal_property_search.request
===================================================================
--- CalendarServer/trunk/simplugin/request-data/OS_X_10_6/user_list_principal_property_search.request (rev 0)
+++ CalendarServer/trunk/simplugin/request-data/OS_X_10_6/user_list_principal_property_search.request 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<x0:principal-property-search xmlns:x2="urn:ietf:params:xml:ns:caldav" xmlns:x0="DAV:" xmlns:x1="http://calendarserver.org/ns/" test="anyof"><x0:property-search><x0:prop><x0:displayname/></x0:prop><x0:match match-type="starts-with">%(displayname)s</x0:match></x0:property-search><x0:property-search><x0:prop><x1:email-address-set/></x0:prop><x0:match match-type="starts-with">%(email)s</x0:match></x0:property-search><x0:property-search><x0:prop><x1:first-name/></x0:prop><x0:match match-type="starts-with">%(firstname)s</x0:match></x0:property-search><x0:property-search><x0:prop><x1:last-name/></x0:prop><x0:match match-type="starts-with">%(lastname)s</x0:match></x0:property-search><x0:prop><x1:email-address-set/><x2:calendar-user-address-set/><x2:calendar-user-type/><x0:displayname/><x1:last-name/><x1:first-name/><x1:record-type/><x0:principal-URL/></x0:prop></x0:principal-property-search>
Added: CalendarServer/trunk/simplugin/request-data/OS_X_10_7/Profile
===================================================================
--- CalendarServer/trunk/simplugin/request-data/OS_X_10_7/Profile (rev 0)
+++ CalendarServer/trunk/simplugin/request-data/OS_X_10_7/Profile 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1,64 @@
+** Initial account setup
+
+PROPFIND well-known unauthorized - startup_well_known.request
+PROPFIND / unauthorized - startup_well_known.request
+HEAD well-known unauthorized
+PROPFIND / unauthorized - startup_well_known.request
+HEAD / unauthorized
+PROPFIND / unauthorized - startup_well_known.request
+PROPFIND /calendar/dav/user01/user/ unauthorized - startup_principal_propfind_initial.request
+PROPFIND /principals/users/user01/ unauthorized - startup_principal_propfind_initial.request
+PROPFIND /principals/users/user01/ - startup_principal_propfind_initial.request
+
+PROPFIND /principals/users/user01/ unauthorized - startup_principal_propfind.request
+PROPFIND /principals/users/user01/ - startup_principal_propfind.request
+OPTIONS /principals/__uids__/user01/
+
+PROPFIND /principals/users/user01/ - startup_principal_propfind.request
+OPTIONS /principals/__uids__/user01/
+
+REPORT /principals/ unauthorized - startup_principals_report.request
+REPORT /principals/ - startup_principals_report.request
+
+PROPFIND /calendars/__uids__/user01/ unauthorized - poll_calendarhome_propfind.request
+PROPFIND /calendars/__uids__/user01/ - poll_calendarhome_propfind.request
+
+PROPPATCH /calendars/__uids__/user01/tasks/ - startup_calendar_color_proppatch.request
+PROPPATCH /calendars/__uids__/user01/tasks/ - startup_calendar_order_proppatch.request
+PROPPATCH /calendars/__uids__/user01/tasks/ - startup_calendar_timezone_proppatch.request
+
+PROPPATCH /calendars/__uids__/user01/calendar/ - startup_calendar_color_proppatch.request
+PROPPATCH /calendars/__uids__/user01/calendar/ - startup_calendar_order_proppatch.request
+PROPPATCH /calendars/__uids__/user01/calendar/ - startup_calendar_timezone_proppatch.request
+
+PROPFIND /calendars/__uids__/user01/tasks/ - poll_calendar_propfind.request
+PROPFIND /calendars/__uids__/user01/tasks/ - poll_calendar_propfind_d1.request
+
+PROPFIND /calendars/__uids__/user01/calendar/ - poll_calendar_propfind.request
+PROPFIND /calendars/__uids__/user01/calendar/ - poll_calendar_propfind_d1.request
+
+PROPFIND /calendars/__uids__/user01/inbox/ - poll_calendar_propfind.request
+PROPFIND /calendars/__uids__/user01/inbox/ - poll_calendar_propfind_d1.request
+
+PROPFIND /calendars/__uids__/user01/notification/ - poll_calendar_propfind.request
+PROPFIND /calendars/__uids__/user01/notification/ - poll_notification_propfind_d1.request
+
+REPORT /principals/__uids__/user01/ - startup_principal_expand.request
+
+ ** for each delegate
+ PROPFIND /principals/__uids__/resource10/ - startup_delegate_principal_propfind.request
+ OPTIONS /principals/__uids__/user01/
+ REPORT /principals/ - startup_principals_report.request
+
+** Polling - with sync
+PROPFIND /calendars/__uids__/user01/ - poll_calendarhome_propfind.request
+REPORT /calendars/__uids__/user01/calendar/ - poll_calendar_sync.request
+PROPFIND /calendars/__uids__/user01/calendar/ - poll_calendar_multiget.request
+
+** Polling - without sync
+PROPFIND /calendars/__uids__/user01/ - poll_calendarhome_propfind.request
+PROPFIND /calendars/__uids__/user01/calendar/ - poll_calendar_propfind.request
+PROPFIND /calendars/__uids__/user01/calendar/ - poll_calendar_propfind_d1.request
+REPORT /calendars/__uids__/user01/calendar/ - poll_calendar_multiget.request
+PROPFIND /calendars/__uids__/user01/notification/ - poll_calendar_propfind.request
+PROPFIND /calendars/__uids__/user01/notification/ - poll_notification_propfind_d1.request
Added: CalendarServer/trunk/simplugin/request-data/OS_X_10_7/poll_calendar_multiget.request
===================================================================
--- CalendarServer/trunk/simplugin/request-data/OS_X_10_7/poll_calendar_multiget.request (rev 0)
+++ CalendarServer/trunk/simplugin/request-data/OS_X_10_7/poll_calendar_multiget.request 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<C:calendar-multiget xmlns:C="urn:ietf:params:xml:ns:caldav">
+ <A:prop xmlns:A="DAV:">
+ <A:getetag/>
+ <C:calendar-data/>
+ <C:schedule-tag/>
+ </A:prop>
+%(hrefs)s
+</C:calendar-multiget>
Added: CalendarServer/trunk/simplugin/request-data/OS_X_10_7/poll_calendar_multiget_hrefs.request
===================================================================
--- CalendarServer/trunk/simplugin/request-data/OS_X_10_7/poll_calendar_multiget_hrefs.request (rev 0)
+++ CalendarServer/trunk/simplugin/request-data/OS_X_10_7/poll_calendar_multiget_hrefs.request 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1 @@
+ <A:href xmlns:A="DAV:">%(href)s</A:href>
Added: CalendarServer/trunk/simplugin/request-data/OS_X_10_7/poll_calendar_propfind.request
===================================================================
--- CalendarServer/trunk/simplugin/request-data/OS_X_10_7/poll_calendar_propfind.request (rev 0)
+++ CalendarServer/trunk/simplugin/request-data/OS_X_10_7/poll_calendar_propfind.request 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propfind xmlns:A="DAV:">
+ <A:prop>
+ <B:getctag xmlns:B="http://calendarserver.org/ns/"/>
+ <A:sync-token/>
+ </A:prop>
+</A:propfind>
Added: CalendarServer/trunk/simplugin/request-data/OS_X_10_7/poll_calendar_propfind_d1.request
===================================================================
--- CalendarServer/trunk/simplugin/request-data/OS_X_10_7/poll_calendar_propfind_d1.request (rev 0)
+++ CalendarServer/trunk/simplugin/request-data/OS_X_10_7/poll_calendar_propfind_d1.request 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propfind xmlns:A="DAV:">
+ <A:prop>
+ <A:getcontenttype/>
+ <A:getetag/>
+ </A:prop>
+</A:propfind>
Added: CalendarServer/trunk/simplugin/request-data/OS_X_10_7/poll_calendar_sync.request
===================================================================
--- CalendarServer/trunk/simplugin/request-data/OS_X_10_7/poll_calendar_sync.request (rev 0)
+++ CalendarServer/trunk/simplugin/request-data/OS_X_10_7/poll_calendar_sync.request 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:sync-collection xmlns:A="DAV:">
+ <A:sync-token>%(sync-token)s</A:sync-token>
+ <A:prop>
+ <A:getetag/>
+ <A:getcontenttype/>
+ </A:prop>
+</A:sync-collection>
Added: CalendarServer/trunk/simplugin/request-data/OS_X_10_7/poll_calendarhome_propfind.request
===================================================================
--- CalendarServer/trunk/simplugin/request-data/OS_X_10_7/poll_calendarhome_propfind.request (rev 0)
+++ CalendarServer/trunk/simplugin/request-data/OS_X_10_7/poll_calendarhome_propfind.request 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propfind xmlns:A="DAV:">
+ <A:prop>
+ <A:add-member/>
+ <B:allowed-sharing-modes xmlns:B="http://calendarserver.org/ns/"/>
+ <D:bulk-requests xmlns:D="http://me.com/_namespace/"/>
+ <E:calendar-color xmlns:E="http://apple.com/ns/ical/"/>
+ <C:calendar-description xmlns:C="urn:ietf:params:xml:ns:caldav"/>
+ <C:calendar-free-busy-set xmlns:C="urn:ietf:params:xml:ns:caldav"/>
+ <E:calendar-order xmlns:E="http://apple.com/ns/ical/"/>
+ <C:calendar-timezone xmlns:C="urn:ietf:params:xml:ns:caldav"/>
+ <A:current-user-privilege-set/>
+ <A:displayname/>
+ <B:getctag xmlns:B="http://calendarserver.org/ns/"/>
+ <F:max-image-size xmlns:F="urn:ietf:params:xml:ns:carddav"/>
+ <F:max-resource-size xmlns:F="urn:ietf:params:xml:ns:carddav"/>
+ <B:me-card xmlns:B="http://calendarserver.org/ns/"/>
+ <A:owner/>
+ <B:publish-url xmlns:B="http://calendarserver.org/ns/"/>
+ <B:push-transports xmlns:B="http://calendarserver.org/ns/"/>
+ <B:pushkey xmlns:B="http://calendarserver.org/ns/"/>
+ <A:quota-available-bytes/>
+ <A:quota-used-bytes/>
+ <E:refreshrate xmlns:E="http://apple.com/ns/ical/"/>
+ <A:resource-id/>
+ <A:resourcetype/>
+ <C:schedule-calendar-transp xmlns:C="urn:ietf:params:xml:ns:caldav"/>
+ <C:schedule-default-calendar-URL xmlns:C="urn:ietf:params:xml:ns:caldav"/>
+ <B:source xmlns:B="http://calendarserver.org/ns/"/>
+ <B:subscribed-strip-alarms xmlns:B="http://calendarserver.org/ns/"/>
+ <B:subscribed-strip-attachments xmlns:B="http://calendarserver.org/ns/"/>
+ <B:subscribed-strip-todos xmlns:B="http://calendarserver.org/ns/"/>
+ <C:supported-calendar-component-set xmlns:C="urn:ietf:params:xml:ns:caldav"/>
+ <A:supported-report-set/>
+ <A:sync-token/>
+ <B:xmpp-server xmlns:B="http://calendarserver.org/ns/"/>
+ <B:xmpp-uri xmlns:B="http://calendarserver.org/ns/"/>
+ </A:prop>
+</A:propfind>
Added: CalendarServer/trunk/simplugin/request-data/OS_X_10_7/poll_notification_propfind_d1.request
===================================================================
--- CalendarServer/trunk/simplugin/request-data/OS_X_10_7/poll_notification_propfind_d1.request (rev 0)
+++ CalendarServer/trunk/simplugin/request-data/OS_X_10_7/poll_notification_propfind_d1.request 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propfind xmlns:A="DAV:">
+ <A:prop>
+ <A:getetag/>
+ <B:notificationtype xmlns:B="http://calendarserver.org/ns/"/>
+ </A:prop>
+</A:propfind>
Added: CalendarServer/trunk/simplugin/request-data/OS_X_10_7/post_availability.request
===================================================================
--- CalendarServer/trunk/simplugin/request-data/OS_X_10_7/post_availability.request (rev 0)
+++ CalendarServer/trunk/simplugin/request-data/OS_X_10_7/post_availability.request 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1,14 @@
+BEGIN:VCALENDAR
+CALSCALE:GREGORIAN
+VERSION:2.0
+METHOD:REQUEST
+PRODID:-//Apple Inc.//iCal 4.0.3//EN
+BEGIN:VFREEBUSY
+UID:%(vfreebusy-uid)s
+DTEND:%(end)s
+%(attendees)sDTSTART:%(start)s
+%(event-mask)sDTSTAMP:%(now)s
+ORGANIZER:%(organizer)s
+SUMMARY:%(summary)s
+END:VFREEBUSY
+END:VCALENDAR
Added: CalendarServer/trunk/simplugin/request-data/OS_X_10_7/startup_calendar_color_proppatch.request
===================================================================
--- CalendarServer/trunk/simplugin/request-data/OS_X_10_7/startup_calendar_color_proppatch.request (rev 0)
+++ CalendarServer/trunk/simplugin/request-data/OS_X_10_7/startup_calendar_color_proppatch.request 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propertyupdate xmlns:A="DAV:"><A:set><A:prop><E:calendar-color xmlns:E="http://apple.com/ns/ical/">#882F00FF</E:calendar-color></A:prop></A:set></A:propertyupdate>
Added: CalendarServer/trunk/simplugin/request-data/OS_X_10_7/startup_calendar_order_proppatch.request
===================================================================
--- CalendarServer/trunk/simplugin/request-data/OS_X_10_7/startup_calendar_order_proppatch.request (rev 0)
+++ CalendarServer/trunk/simplugin/request-data/OS_X_10_7/startup_calendar_order_proppatch.request 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propertyupdate xmlns:A="DAV:"><A:set><A:prop><E:calendar-order xmlns:E="http://apple.com/ns/ical/">1</E:calendar-order></A:prop></A:set></A:propertyupdate>
Added: CalendarServer/trunk/simplugin/request-data/OS_X_10_7/startup_calendar_timezone_proppatch.request
===================================================================
--- CalendarServer/trunk/simplugin/request-data/OS_X_10_7/startup_calendar_timezone_proppatch.request (rev 0)
+++ CalendarServer/trunk/simplugin/request-data/OS_X_10_7/startup_calendar_timezone_proppatch.request 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propertyupdate xmlns:A="DAV:"><A:set><A:prop><C:calendar-timezone xmlns:C="urn:ietf:params:xml:ns:caldav">BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//iCal 5.0.2//EN
+CALSCALE:GREGORIAN
+BEGIN:VTIMEZONE
+TZID:America/New_York
+BEGIN:DAYLIGHT
+TZOFFSETFROM:-0500
+RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
+DTSTART:20070311T020000
+TZNAME:EDT
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+TZOFFSETFROM:-0400
+RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
+DTSTART:20071104T020000
+TZNAME:EST
+TZOFFSETTO:-0500
+END:STANDARD
+END:VTIMEZONE
+END:VCALENDAR
+</C:calendar-timezone></A:prop></A:set></A:propertyupdate>
Added: CalendarServer/trunk/simplugin/request-data/OS_X_10_7/startup_delegate_principal_propfind.request
===================================================================
--- CalendarServer/trunk/simplugin/request-data/OS_X_10_7/startup_delegate_principal_propfind.request (rev 0)
+++ CalendarServer/trunk/simplugin/request-data/OS_X_10_7/startup_delegate_principal_propfind.request 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propfind xmlns:A="DAV:">
+ <A:prop>
+ <B:allowed-calendar-component-set xmlns:B="http://calendarserver.org/ns/"/>
+ <C:calendar-home-set xmlns:C="urn:ietf:params:xml:ns:caldav"/>
+ <C:calendar-user-address-set xmlns:C="urn:ietf:params:xml:ns:caldav"/>
+ <A:current-user-principal/>
+ <A:displayname/>
+ <B:dropbox-home-URL xmlns:B="http://calendarserver.org/ns/"/>
+ <B:email-address-set xmlns:B="http://calendarserver.org/ns/"/>
+ <B:notification-URL xmlns:B="http://calendarserver.org/ns/"/>
+ <A:principal-collection-set/>
+ <A:principal-URL/>
+ <A:resource-id/>
+ <C:schedule-inbox-URL xmlns:C="urn:ietf:params:xml:ns:caldav"/>
+ <C:schedule-outbox-URL xmlns:C="urn:ietf:params:xml:ns:caldav"/>
+ <A:supported-report-set/>
+ </A:prop>
+</A:propfind>
Added: CalendarServer/trunk/simplugin/request-data/OS_X_10_7/startup_principal_expand.request
===================================================================
--- CalendarServer/trunk/simplugin/request-data/OS_X_10_7/startup_principal_expand.request (rev 0)
+++ CalendarServer/trunk/simplugin/request-data/OS_X_10_7/startup_principal_expand.request 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:expand-property xmlns:A="DAV:">
+ <A:property name="calendar-proxy-read-for" namespace="http://calendarserver.org/ns/">
+ <A:property name="calendar-user-address-set" namespace="urn:ietf:params:xml:ns:caldav"/>
+ <A:property name="email-address-set" namespace="http://calendarserver.org/ns/"/>
+ <A:property name="displayname" namespace="DAV:"/>
+ </A:property>
+ <A:property name="calendar-proxy-write-for" namespace="http://calendarserver.org/ns/">
+ <A:property name="calendar-user-address-set" namespace="urn:ietf:params:xml:ns:caldav"/>
+ <A:property name="email-address-set" namespace="http://calendarserver.org/ns/"/>
+ <A:property name="displayname" namespace="DAV:"/>
+ </A:property>
+</A:expand-property>
Added: CalendarServer/trunk/simplugin/request-data/OS_X_10_7/startup_principal_propfind.request
===================================================================
--- CalendarServer/trunk/simplugin/request-data/OS_X_10_7/startup_principal_propfind.request (rev 0)
+++ CalendarServer/trunk/simplugin/request-data/OS_X_10_7/startup_principal_propfind.request 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propfind xmlns:A="DAV:">
+ <A:prop>
+ <B:allowed-calendar-component-set xmlns:B="http://calendarserver.org/ns/"/>
+ <C:calendar-home-set xmlns:C="urn:ietf:params:xml:ns:caldav"/>
+ <C:calendar-user-address-set xmlns:C="urn:ietf:params:xml:ns:caldav"/>
+ <A:current-user-principal/>
+ <A:displayname/>
+ <B:dropbox-home-URL xmlns:B="http://calendarserver.org/ns/"/>
+ <B:email-address-set xmlns:B="http://calendarserver.org/ns/"/>
+ <B:notification-URL xmlns:B="http://calendarserver.org/ns/"/>
+ <A:principal-collection-set/>
+ <A:principal-URL/>
+ <A:resource-id/>
+ <C:schedule-inbox-URL xmlns:C="urn:ietf:params:xml:ns:caldav"/>
+ <C:schedule-outbox-URL xmlns:C="urn:ietf:params:xml:ns:caldav"/>
+ <A:supported-report-set/>
+ </A:prop>
+</A:propfind>
Added: CalendarServer/trunk/simplugin/request-data/OS_X_10_7/startup_principal_propfind_initial.request
===================================================================
--- CalendarServer/trunk/simplugin/request-data/OS_X_10_7/startup_principal_propfind_initial.request (rev 0)
+++ CalendarServer/trunk/simplugin/request-data/OS_X_10_7/startup_principal_propfind_initial.request 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propfind xmlns:A="DAV:">
+ <A:prop>
+ <A:current-user-principal/>
+ <A:principal-URL/>
+ <A:resourcetype/>
+ </A:prop>
+</A:propfind>
Added: CalendarServer/trunk/simplugin/request-data/OS_X_10_7/startup_principals_report.request
===================================================================
--- CalendarServer/trunk/simplugin/request-data/OS_X_10_7/startup_principals_report.request (rev 0)
+++ CalendarServer/trunk/simplugin/request-data/OS_X_10_7/startup_principals_report.request 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<x0:principal-search-property-set xmlns:x0="DAV:"/>
Added: CalendarServer/trunk/simplugin/request-data/OS_X_10_7/startup_well_known.request
===================================================================
--- CalendarServer/trunk/simplugin/request-data/OS_X_10_7/startup_well_known.request (rev 0)
+++ CalendarServer/trunk/simplugin/request-data/OS_X_10_7/startup_well_known.request 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propfind xmlns:A="DAV:">
+ <A:prop>
+ <A:current-user-principal/>
+ <A:principal-URL/>
+ <A:resourcetype/>
+ </A:prop>
+</A:propfind>
Added: CalendarServer/trunk/simplugin/request-data/OS_X_10_7/user_list_principal_property_search.request
===================================================================
--- CalendarServer/trunk/simplugin/request-data/OS_X_10_7/user_list_principal_property_search.request (rev 0)
+++ CalendarServer/trunk/simplugin/request-data/OS_X_10_7/user_list_principal_property_search.request 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<x0:principal-property-search xmlns:x2="urn:ietf:params:xml:ns:caldav" xmlns:x0="DAV:" xmlns:x1="http://calendarserver.org/ns/" test="anyof"><x0:property-search><x0:prop><x0:displayname/></x0:prop><x0:match match-type="starts-with">%(displayname)s</x0:match></x0:property-search><x0:property-search><x0:prop><x1:email-address-set/></x0:prop><x0:match match-type="starts-with">%(email)s</x0:match></x0:property-search><x0:property-search><x0:prop><x1:first-name/></x0:prop><x0:match match-type="starts-with">%(firstname)s</x0:match></x0:property-search><x0:property-search><x0:prop><x1:last-name/></x0:prop><x0:match match-type="starts-with">%(lastname)s</x0:match></x0:property-search><x0:prop><x1:email-address-set/><x2:calendar-user-address-set/><x2:calendar-user-type/><x0:displayname/><x1:last-name/><x1:first-name/><x1:record-type/><x0:principal-URL/></x0:prop></x0:principal-property-search>
Added: CalendarServer/trunk/simplugin/request-data/iOS_5/Profile
===================================================================
--- CalendarServer/trunk/simplugin/request-data/iOS_5/Profile (rev 0)
+++ CalendarServer/trunk/simplugin/request-data/iOS_5/Profile 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1,44 @@
+** Initial account setup
+
+PROPFIND well-known unauthorized - startup_well_known.request
+PROPFIND / unauthorized - startup_well_known.request
+PROPFIND /principals/ unauthorized - startup_principal_propfind_initial.request
+PROPFIND /principals/ authorized - startup_principal_propfind_initial.request
+
+OPTIONS /principals/__uids__/user01/ unauthorized
+OPTIONS /principals/__uids__/user01/ authorized
+
+PROPFIND /principals/users/user01/ unauthorized - startup_principal_propfind.request
+PROPFIND /principals/users/user01/ - startup_principal_propfind.request
+
+REPORT /principals/ unauthorized - startup_principals_report.request
+REPORT /principals/ - startup_principals_report.request
+
+PROPFIND /calendars/__uids__/user01/ unauthorized - poll_calendarhome_propfind.request
+PROPFIND /calendars/__uids__/user01/ - poll_calendarhome_propfind.request
+
+PROPPATCH /calendars/__uids__/user01/tasks/ - startup_calendar_color_proppatch.request
+PROPPATCH /calendars/__uids__/user01/tasks/ - startup_calendar_order_proppatch.request
+
+PROPPATCH /calendars/__uids__/user01/calendar/ - startup_calendar_color_proppatch.request
+PROPPATCH /calendars/__uids__/user01/calendar/ - startup_calendar_order_proppatch.request
+
+PROPFIND /calendars/__uids__/user01/tasks/ - poll_calendar_propfind.request
+PROPFIND /calendars/__uids__/user01/tasks/ - poll_calendar_vtodo_query.request
+
+PROPFIND /calendars/__uids__/user01/calendar/ - poll_calendar_propfind.request
+PROPFIND /calendars/__uids__/user01/calendar/ - poll_calendar_vevent_tr_query.request
+
+PROPFIND /calendars/__uids__/user01/inbox/ - poll_calendar_propfind.request
+PROPFIND /calendars/__uids__/user01/inbox/ - poll_calendar_propfind_d1.request
+
+** Polling - without sync
+PROPFIND /calendars/__uids__/user01/ - poll_calendarhome_propfind.request
+
+PROPFIND /calendars/__uids__/user01/calendar/ - poll_calendar_propfind.request
+REPORT /calendars/__uids__/user01/calendar/ - poll_calendar_propfind_d1.request
+REPORT /calendars/__uids__/user01/calendar/ - poll_calendar_multiget.request
+
+PROPFIND /calendars/__uids__/user01/calendar/ - poll_calendar_propfind.request
+REPORT /calendars/__uids__/user01/tasks/ - poll_calendar_vevent_tr_query.request
+REPORT /calendars/__uids__/user01/tasks/ - poll_calendar_vtodo_query.request
Added: CalendarServer/trunk/simplugin/request-data/iOS_5/poll_calendar_multiget.request
===================================================================
--- CalendarServer/trunk/simplugin/request-data/iOS_5/poll_calendar_multiget.request (rev 0)
+++ CalendarServer/trunk/simplugin/request-data/iOS_5/poll_calendar_multiget.request 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<G:calendar-multiget xmlns:G="urn:ietf:params:xml:ns:caldav">
+ <A:prop xmlns:A="DAV:">
+ <A:getetag/>
+ <G:calendar-data/>
+ <G:schedule-tag/>
+ </A:prop>
+%(hrefs)s
+</G:calendar-multiget>
Added: CalendarServer/trunk/simplugin/request-data/iOS_5/poll_calendar_multiget_hrefs.request
===================================================================
--- CalendarServer/trunk/simplugin/request-data/iOS_5/poll_calendar_multiget_hrefs.request (rev 0)
+++ CalendarServer/trunk/simplugin/request-data/iOS_5/poll_calendar_multiget_hrefs.request 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1 @@
+ <A:href xmlns:A="DAV:">%(href)s</A:href>
Added: CalendarServer/trunk/simplugin/request-data/iOS_5/poll_calendar_propfind.request
===================================================================
--- CalendarServer/trunk/simplugin/request-data/iOS_5/poll_calendar_propfind.request (rev 0)
+++ CalendarServer/trunk/simplugin/request-data/iOS_5/poll_calendar_propfind.request 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propfind xmlns:A="DAV:">
+ <A:prop>
+ <C:getctag xmlns:C="http://calendarserver.org/ns/"/>
+ <A:sync-token/>
+ </A:prop>
+</A:propfind>
Added: CalendarServer/trunk/simplugin/request-data/iOS_5/poll_calendar_propfind_d1.request
===================================================================
--- CalendarServer/trunk/simplugin/request-data/iOS_5/poll_calendar_propfind_d1.request (rev 0)
+++ CalendarServer/trunk/simplugin/request-data/iOS_5/poll_calendar_propfind_d1.request 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propfind xmlns:A="DAV:">
+ <A:prop>
+ <A:getcontenttype/>
+ <A:getetag/>
+ </A:prop>
+</A:propfind>
Added: CalendarServer/trunk/simplugin/request-data/iOS_5/poll_calendar_vevent_tr_query.request
===================================================================
--- CalendarServer/trunk/simplugin/request-data/iOS_5/poll_calendar_vevent_tr_query.request (rev 0)
+++ CalendarServer/trunk/simplugin/request-data/iOS_5/poll_calendar_vevent_tr_query.request 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<G:calendar-query xmlns:G="urn:ietf:params:xml:ns:caldav">
+ <A:prop xmlns:A="DAV:">
+ <A:getetag/>
+ <A:getcontenttype/>
+ </A:prop>
+ <G:filter>
+ <G:comp-filter name="VCALENDAR">
+ <G:comp-filter name="VEVENT">
+ <G:time-range start="%(start-date)sT000000Z"/>
+ </G:comp-filter>
+ </G:comp-filter>
+ </G:filter>
+</G:calendar-query>
Added: CalendarServer/trunk/simplugin/request-data/iOS_5/poll_calendar_vtodo_query.request
===================================================================
--- CalendarServer/trunk/simplugin/request-data/iOS_5/poll_calendar_vtodo_query.request (rev 0)
+++ CalendarServer/trunk/simplugin/request-data/iOS_5/poll_calendar_vtodo_query.request 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<G:calendar-query xmlns:G="urn:ietf:params:xml:ns:caldav">
+ <A:prop xmlns:A="DAV:">
+ <A:getetag/>
+ <A:getcontenttype/>
+ </A:prop>
+ <G:filter>
+ <G:comp-filter name="VCALENDAR">
+ <G:comp-filter name="VTODO"/>
+ </G:comp-filter>
+ </G:filter>
+</G:calendar-query>
Added: CalendarServer/trunk/simplugin/request-data/iOS_5/poll_calendarhome_propfind.request
===================================================================
--- CalendarServer/trunk/simplugin/request-data/iOS_5/poll_calendarhome_propfind.request (rev 0)
+++ CalendarServer/trunk/simplugin/request-data/iOS_5/poll_calendarhome_propfind.request 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propfind xmlns:A="DAV:">
+ <A:prop>
+ <A:add-member/>
+ <C:allowed-sharing-modes xmlns:C="http://calendarserver.org/ns/"/>
+ <E:bulk-requests xmlns:E="http://me.com/_namespace/"/>
+ <H:calendar-color xmlns:H="http://apple.com/ns/ical/"/>
+ <G:calendar-description xmlns:G="urn:ietf:params:xml:ns:caldav"/>
+ <G:calendar-free-busy-set xmlns:G="urn:ietf:params:xml:ns:caldav"/>
+ <H:calendar-order xmlns:H="http://apple.com/ns/ical/"/>
+ <G:calendar-timezone xmlns:G="urn:ietf:params:xml:ns:caldav"/>
+ <A:current-user-privilege-set/>
+ <A:displayname/>
+ <C:getctag xmlns:C="http://calendarserver.org/ns/"/>
+ <B:max-image-size xmlns:B="urn:ietf:params:xml:ns:carddav"/>
+ <B:max-resource-size xmlns:B="urn:ietf:params:xml:ns:carddav"/>
+ <C:me-card xmlns:C="http://calendarserver.org/ns/"/>
+ <A:owner/>
+ <C:publish-url xmlns:C="http://calendarserver.org/ns/"/>
+ <C:push-transports xmlns:C="http://calendarserver.org/ns/"/>
+ <C:pushkey xmlns:C="http://calendarserver.org/ns/"/>
+ <A:quota-available-bytes/>
+ <A:quota-used-bytes/>
+ <H:refreshrate xmlns:H="http://apple.com/ns/ical/"/>
+ <A:resource-id/>
+ <A:resourcetype/>
+ <G:schedule-calendar-transp xmlns:G="urn:ietf:params:xml:ns:caldav"/>
+ <G:schedule-default-calendar-URL xmlns:G="urn:ietf:params:xml:ns:caldav"/>
+ <C:source xmlns:C="http://calendarserver.org/ns/"/>
+ <C:subscribed-strip-alarms xmlns:C="http://calendarserver.org/ns/"/>
+ <C:subscribed-strip-attachments xmlns:C="http://calendarserver.org/ns/"/>
+ <C:subscribed-strip-todos xmlns:C="http://calendarserver.org/ns/"/>
+ <G:supported-calendar-component-set xmlns:G="urn:ietf:params:xml:ns:caldav"/>
+ <A:supported-report-set/>
+ <A:sync-token/>
+ <C:xmpp-server xmlns:C="http://calendarserver.org/ns/"/>
+ <C:xmpp-uri xmlns:C="http://calendarserver.org/ns/"/>
+ </A:prop>
+</A:propfind>
Added: CalendarServer/trunk/simplugin/request-data/iOS_5/startup_calendar_color_proppatch.request
===================================================================
--- CalendarServer/trunk/simplugin/request-data/iOS_5/startup_calendar_color_proppatch.request (rev 0)
+++ CalendarServer/trunk/simplugin/request-data/iOS_5/startup_calendar_color_proppatch.request 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propertyupdate xmlns:A="DAV:"><A:set><A:prop><H:calendar-color xmlns:H="http://apple.com/ns/ical/">#711A76</H:calendar-color></A:prop></A:set></A:propertyupdate>
Added: CalendarServer/trunk/simplugin/request-data/iOS_5/startup_calendar_order_proppatch.request
===================================================================
--- CalendarServer/trunk/simplugin/request-data/iOS_5/startup_calendar_order_proppatch.request (rev 0)
+++ CalendarServer/trunk/simplugin/request-data/iOS_5/startup_calendar_order_proppatch.request 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propertyupdate xmlns:A="DAV:"><A:set><A:prop><H:calendar-order xmlns:H="http://apple.com/ns/ical/">0</H:calendar-order></A:prop></A:set></A:propertyupdate>
Added: CalendarServer/trunk/simplugin/request-data/iOS_5/startup_principal_propfind.request
===================================================================
--- CalendarServer/trunk/simplugin/request-data/iOS_5/startup_principal_propfind.request (rev 0)
+++ CalendarServer/trunk/simplugin/request-data/iOS_5/startup_principal_propfind.request 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propfind xmlns:A="DAV:">
+ <A:prop>
+ <C:allowed-calendar-component-set xmlns:C="http://calendarserver.org/ns/"/>
+ <G:calendar-home-set xmlns:G="urn:ietf:params:xml:ns:caldav"/>
+ <G:calendar-user-address-set xmlns:G="urn:ietf:params:xml:ns:caldav"/>
+ <A:current-user-principal/>
+ <A:displayname/>
+ <C:dropbox-home-URL xmlns:C="http://calendarserver.org/ns/"/>
+ <C:email-address-set xmlns:C="http://calendarserver.org/ns/"/>
+ <C:notification-URL xmlns:C="http://calendarserver.org/ns/"/>
+ <A:principal-collection-set/>
+ <A:principal-URL/>
+ <A:resource-id/>
+ <G:schedule-inbox-URL xmlns:G="urn:ietf:params:xml:ns:caldav"/>
+ <G:schedule-outbox-URL xmlns:G="urn:ietf:params:xml:ns:caldav"/>
+ <A:supported-report-set/>
+ </A:prop>
+</A:propfind>
Added: CalendarServer/trunk/simplugin/request-data/iOS_5/startup_principal_propfind_initial.request
===================================================================
--- CalendarServer/trunk/simplugin/request-data/iOS_5/startup_principal_propfind_initial.request (rev 0)
+++ CalendarServer/trunk/simplugin/request-data/iOS_5/startup_principal_propfind_initial.request 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propfind xmlns:A="DAV:">
+ <A:prop>
+ <A:current-user-principal/>
+ <A:principal-URL/>
+ <A:resourcetype/>
+ </A:prop>
+</A:propfind>
Added: CalendarServer/trunk/simplugin/request-data/iOS_5/startup_principals_report.request
===================================================================
--- CalendarServer/trunk/simplugin/request-data/iOS_5/startup_principals_report.request (rev 0)
+++ CalendarServer/trunk/simplugin/request-data/iOS_5/startup_principals_report.request 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:principal-search-property-set xmlns:A="DAV:"/>
Added: CalendarServer/trunk/simplugin/request-data/iOS_5/startup_well_known.request
===================================================================
--- CalendarServer/trunk/simplugin/request-data/iOS_5/startup_well_known.request (rev 0)
+++ CalendarServer/trunk/simplugin/request-data/iOS_5/startup_well_known.request 2016-01-08 21:03:23 UTC (rev 15430)
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propfind xmlns:A="DAV:">
+ <A:prop>
+ <A:current-user-principal/>
+ <A:principal-URL/>
+ <A:resourcetype/>
+ </A:prop>
+</A:propfind>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20160108/4fa3fefd/attachment-0001.html>
More information about the calendarserver-changes
mailing list