[CalendarServer-changes] [11579] CalendarServer/trunk
source_changes at macosforge.org
source_changes at macosforge.org
Mon Aug 5 10:55:30 PDT 2013
Revision: 11579
http://trac.calendarserver.org//changeset/11579
Author: cdaboo at apple.com
Date: 2013-08-05 10:55:30 -0700 (Mon, 05 Aug 2013)
Log Message:
-----------
Make sure implicit component splitting sends scheduling messages for non-hosted attendees. Also found and fixed
a bunch of iSchedule bugs introduced by scheduling-in-the-store changes.
Modified Paths:
--------------
CalendarServer/trunk/twistedcaldav/ical.py
CalendarServer/trunk/txdav/caldav/datastore/scheduling/icalsplitter.py
CalendarServer/trunk/txdav/caldav/datastore/scheduling/implicit.py
CalendarServer/trunk/txdav/caldav/datastore/scheduling/ischedule/delivery.py
CalendarServer/trunk/txdav/caldav/datastore/scheduling/ischedule/dkim.py
CalendarServer/trunk/txdav/caldav/datastore/scheduling/ischedule/localservers.py
CalendarServer/trunk/txdav/caldav/datastore/scheduling/ischedule/resource.py
CalendarServer/trunk/txdav/caldav/datastore/scheduling/ischedule/scheduler.py
CalendarServer/trunk/txdav/caldav/datastore/scheduling/ischedule/test/test_dkim.py
CalendarServer/trunk/txdav/caldav/datastore/scheduling/ischedule/test/test_localservers.py
CalendarServer/trunk/txdav/caldav/datastore/scheduling/processing.py
CalendarServer/trunk/txdav/caldav/datastore/scheduling/test/test_icalsplitter.py
CalendarServer/trunk/txdav/caldav/datastore/sql.py
CalendarServer/trunk/txdav/caldav/datastore/test/test_sql.py
CalendarServer/trunk/txdav/caldav/icalendarstore.py
Modified: CalendarServer/trunk/twistedcaldav/ical.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/ical.py 2013-08-05 17:51:24 UTC (rev 11578)
+++ CalendarServer/trunk/twistedcaldav/ical.py 2013-08-05 17:55:30 UTC (rev 11579)
@@ -30,6 +30,7 @@
import cStringIO as StringIO
import codecs
+from difflib import unified_diff
import heapq
import itertools
import uuid
@@ -950,12 +951,17 @@
Remove a property from this component.
@param property: the L{Property} to remove from this component.
"""
- self._pycalendar.removeProperty(property._pycalendar)
- self._pycalendar.finalise()
- property._parent = None
- self._markAsDirty()
+ if isinstance(property, str):
+ for property in self.properties(property):
+ self.removeProperty(property)
+ else:
+ self._pycalendar.removeProperty(property._pycalendar)
+ self._pycalendar.finalise()
+ property._parent = None
+ self._markAsDirty()
+
def removeAllPropertiesWithName(self, pname):
"""
Remove all properties with the given name from all components.
@@ -3512,3 +3518,23 @@
break
else:
heapq.heappop(heap)
+
+
+
+def normalize_iCalStr(icalstr):
+ """
+ Normalize a string representation of ical data for easy test comparison.
+ """
+
+ icalstr = str(icalstr).replace("\r\n ", "")
+ icalstr = icalstr.replace("\n ", "")
+ icalstr = "\r\n".join([line for line in icalstr.splitlines() if not line.startswith("DTSTAMP")])
+ return icalstr
+
+
+
+def diff_iCalStrs(icalstr1, icalstr2):
+
+ icalstr1 = normalize_iCalStr(icalstr1).splitlines()
+ icalstr2 = normalize_iCalStr(icalstr2).splitlines()
+ return "\n".join(unified_diff(icalstr1, icalstr2))
Modified: CalendarServer/trunk/txdav/caldav/datastore/scheduling/icalsplitter.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/scheduling/icalsplitter.py 2013-08-05 17:51:24 UTC (rev 11578)
+++ CalendarServer/trunk/txdav/caldav/datastore/scheduling/icalsplitter.py 2013-08-05 17:55:30 UTC (rev 11579)
@@ -113,7 +113,7 @@
return rid
- def split(self, ical, rid=None, newUID=None):
+ def split(self, ical, rid=None, olderUID=None):
"""
Split the specified iCalendar object. This assumes that L{willSplit} has already
been called and returned C{True}. Splitting is done by carving out old instances
@@ -126,11 +126,11 @@
@param rid: recurrence-id where the split should occur, or C{None} to determine it here
@type rid: L{PyCalendarDateTime} or C{None}
- @param newUID: UID to use for the split off component, or C{None} to generate one here
- @type newUID: C{str} or C{None}
+ @param olderUID: UID to use for the split off component, or C{None} to generate one here
+ @type olderUID: C{str} or C{None}
- @return: iCalendar object for the old "carved out" instances
- @rtype: L{Component}
+ @return: iCalendar objects for the old and new "carved out" instances
+ @rtype: C{tuple} of two L{Component}'s
"""
# Find the instance RECURRENCE-ID where a split is going to happen
@@ -138,16 +138,17 @@
# Create the old one with a new UID value (or the one passed in)
icalOld = ical.duplicate()
- oldUID = icalOld.newUID(newUID=newUID)
+ oldUID = icalOld.newUID(newUID=olderUID)
icalOld.onlyPastInstances(rid)
# Adjust the current one
- ical.onlyFutureInstances(rid)
+ icalNew = ical.duplicate()
+ icalNew.onlyFutureInstances(rid)
# Relate them - add RELATED-TO;RELTYPE=RECURRENCE-SET if not already present
if not icalOld.hasPropertyWithParameterMatch("RELATED-TO", "RELTYPE", "X-CALENDARSERVER-RECURRENCE-SET"):
property = Property("RELATED-TO", oldUID, params={"RELTYPE": "X-CALENDARSERVER-RECURRENCE-SET"})
icalOld.addPropertyToAllComponents(property)
- ical.addPropertyToAllComponents(property)
+ icalNew.addPropertyToAllComponents(property)
- return icalOld
+ return (icalOld, icalNew,)
Modified: CalendarServer/trunk/txdav/caldav/datastore/scheduling/implicit.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/scheduling/implicit.py 2013-08-05 17:51:24 UTC (rev 11578)
+++ CalendarServer/trunk/txdav/caldav/datastore/scheduling/implicit.py 2013-08-05 17:55:30 UTC (rev 11579)
@@ -63,6 +63,8 @@
self.allowed_to_schedule = True
self.suppress_refresh = False
+ self.split_details = None
+
NotAllowedExceptionDetails = collections.namedtuple("NotAllowedExceptionDetails", ("type", "args", "kwargs",))
def setSchedulingNotAllowed(self, ex, *ex_args, **ex_kwargs):
@@ -301,7 +303,7 @@
@inlineCallbacks
- def doImplicitScheduling(self, do_smart_merge=False):
+ def doImplicitScheduling(self, do_smart_merge=False, split_details=None):
"""
Do implicit scheduling operation based on the data already set by call to checkImplicitScheduling.
@@ -315,6 +317,7 @@
self.do_smart_merge = do_smart_merge
self.except_attendees = ()
self.only_refresh_attendees = None
+ self.split_details = split_details
# Determine what type of scheduling this is: Organizer triggered or Attendee triggered
if self.state == "organizer":
@@ -561,25 +564,29 @@
log.debug("Implicit - organizer '%s' is modifying UID: '%s' but change is not significant" % (self.organizer, self.uid))
returnValue(None)
else:
- log.debug("Implicit - organizer '%s' is modifying UID: '%s'" % (self.organizer, self.uid))
+ # Do not change PARTSTATs for a split operation
+ if self.split_details is None:
+ log.debug("Implicit - organizer '%s' is modifying UID: '%s'" % (self.organizer, self.uid))
- for rid in self.needs_action_rids:
- comp = self.calendar.overriddenComponent(rid)
- if comp is None:
- comp = self.calendar.deriveInstance(rid)
- self.calendar.addComponent(comp)
+ for rid in self.needs_action_rids:
+ comp = self.calendar.overriddenComponent(rid)
+ if comp is None:
+ comp = self.calendar.deriveInstance(rid)
+ self.calendar.addComponent(comp)
- for attendee in comp.getAllAttendeeProperties():
- if attendee.hasParameter("PARTSTAT"):
- cuaddr = attendee.value()
+ for attendee in comp.getAllAttendeeProperties():
+ if attendee.hasParameter("PARTSTAT"):
+ cuaddr = attendee.value()
- if cuaddr in self.organizerPrincipal.calendarUserAddresses:
- # If the attendee is the organizer then do not update
- # the PARTSTAT to NEEDS-ACTION.
- # The organizer is automatically ACCEPTED to the event.
- continue
+ if cuaddr in self.organizerPrincipal.calendarUserAddresses:
+ # If the attendee is the organizer then do not update
+ # the PARTSTAT to NEEDS-ACTION.
+ # The organizer is automatically ACCEPTED to the event.
+ continue
- attendee.setParameter("PARTSTAT", "NEEDS-ACTION")
+ attendee.setParameter("PARTSTAT", "NEEDS-ACTION")
+ else:
+ log.debug("Implicit - organizer '%s' is splitting UID: '%s'" % (self.organizer, self.uid))
# Check for removed attendees
if not recurrence_reschedule:
@@ -592,8 +599,12 @@
self.needs_sequence_change = self.calendar.needsiTIPSequenceChange(self.oldcalendar)
elif self.action == "create":
- log.debug("Implicit - organizer '%s' is creating UID: '%s'" % (self.organizer, self.uid))
- self.coerceAttendeesPartstatOnCreate()
+ if self.split_details is None:
+ log.debug("Implicit - organizer '%s' is creating UID: '%s'" % (self.organizer, self.uid))
+ self.coerceAttendeesPartstatOnCreate()
+ else:
+ log.debug("Implicit - organizer '%s' is creating a split UID: '%s'" % (self.organizer, self.uid))
+ self.needs_sequence_change = False
# Always set RSVP=TRUE for any NEEDS-ACTION
for attendee in self.calendar.getAllAttendeeProperties():
@@ -932,6 +943,13 @@
if attendee in self.organizerPrincipal.calendarUserAddresses:
continue
+ # Handle split by not scheduling local attendees
+ if self.split_details is not None:
+ attendeePrincipal = self.calendar_home.directoryService().recordWithCalendarUserAddress(attendee)
+ attendeeAddress = (yield addressmapping.mapper.getCalendarUser(attendee, attendeePrincipal))
+ if type(attendeeAddress) is LocalCalendarUser:
+ continue
+
# Generate an iTIP CANCEL message for this attendee, cancelling
# each instance or the whole
@@ -944,6 +962,13 @@
# Send scheduling message
if itipmsg:
+
+ # Add split details if needed
+ if self.split_details is not None:
+ rid, uid, newer_piece = self.split_details
+ itipmsg.addProperty(Property("X-CALENDARSERVER-SPLIT-RID", rid))
+ itipmsg.addProperty(Property("X-CALENDARSERVER-SPLIT-OLDER-UID" if newer_piece else "X-CALENDARSERVER-SPLIT-NEWER-UID", uid))
+
# This is a local CALDAV scheduling operation.
scheduler = self.makeScheduler()
@@ -983,10 +1008,24 @@
if self.reinvites and attendee not in self.reinvites:
continue
+ # Handle split by not scheduling local attendees
+ if self.split_details is not None:
+ attendeePrincipal = self.calendar_home.directoryService().recordWithCalendarUserAddress(attendee)
+ attendeeAddress = (yield addressmapping.mapper.getCalendarUser(attendee, attendeePrincipal))
+ if type(attendeeAddress) is LocalCalendarUser:
+ continue
+
itipmsg = iTipGenerator.generateAttendeeRequest(self.calendar, (attendee,), self.changed_rids)
# Send scheduling message
if itipmsg is not None:
+
+ # Add split details if needed
+ if self.split_details is not None:
+ rid, uid, newer_piece = self.split_details
+ itipmsg.addProperty(Property("X-CALENDARSERVER-SPLIT-RID", rid))
+ itipmsg.addProperty(Property("X-CALENDARSERVER-SPLIT-OLDER-UID" if newer_piece else "X-CALENDARSERVER-SPLIT-NEWER-UID", uid))
+
# This is a local CALDAV scheduling operation.
scheduler = self.makeScheduler()
Modified: CalendarServer/trunk/txdav/caldav/datastore/scheduling/ischedule/delivery.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/scheduling/ischedule/delivery.py 2013-08-05 17:51:24 UTC (rev 11578)
+++ CalendarServer/trunk/txdav/caldav/datastore/scheduling/ischedule/delivery.py 2013-08-05 17:55:30 UTC (rev 11579)
@@ -38,6 +38,8 @@
from twistedcaldav.client.pool import _configuredClientContextFactory
from twistedcaldav.config import config
from twistedcaldav.ical import normalizeCUAddress, Component
+from twistedcaldav.util import utf8String
+
from txdav.caldav.datastore.scheduling.cuaddress import PartitionedCalendarUser, RemoteCalendarUser, \
OtherServerCalendarUser
from txdav.caldav.datastore.scheduling.delivery import DeliveryService
@@ -49,7 +51,7 @@
RequestStatus, Recipient, ischedule_namespace, CalendarData, \
ResponseDescription, Error
from txdav.caldav.datastore.scheduling.itip import iTIPRequestStatus
-from twistedcaldav.util import utf8String, normalizationLookup
+from txdav.caldav.datastore.util import normalizationLookup
from urlparse import urlsplit
@@ -226,11 +228,10 @@
# Generate an HTTP client request
try:
- if not hasattr(self.scheduler.request, "extendedLogItems"):
- self.scheduler.request.extendedLogItems = {}
- if "itip.ischedule" not in self.scheduler.request.extendedLogItems:
- self.scheduler.request.extendedLogItems["itip.ischedule"] = 0
- self.scheduler.request.extendedLogItems["itip.ischedule"] += 1
+ if self.scheduler.logItems is not None:
+ if "itip.ischedule" not in self.scheduler.logItems:
+ self.scheduler.logItems["itip.ischedule"] = 0
+ self.scheduler.logItems["itip.ischedule"] += 1
# Loop over at most 3 redirects
ssl, host, port, path = self.server.details()
@@ -363,7 +364,7 @@
# The Originator must be the ORGANIZER (for a request) or ATTENDEE (for a reply)
originator = self.scheduler.organizer.cuaddr if self.scheduler.isiTIPRequest else self.scheduler.attendee
- originator = normalizeCUAddress(originator, normalizationLookup, self.scheduler.resource.principalForCalendarUserAddress, toUUID=False)
+ originator = normalizeCUAddress(originator, normalizationLookup, self.scheduler.txn.directoryService().recordWithCalendarUserAddress, toUUID=False)
self.headers.addRawHeader("Originator", utf8String(originator))
self.sign_headers.append("Originator")
@@ -417,7 +418,7 @@
normalizedCalendar = self.scheduler.calendar.duplicate()
normalizedCalendar.normalizeCalendarUserAddresses(
normalizationLookup,
- self.scheduler.resource.principalForCalendarUserAddress,
+ self.scheduler.txn.directoryService().recordWithCalendarUserAddress,
toUUID=False)
else:
normalizedCalendar = self.scheduler.calendar
Modified: CalendarServer/trunk/txdav/caldav/datastore/scheduling/ischedule/dkim.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/scheduling/ischedule/dkim.py 2013-08-05 17:51:24 UTC (rev 11578)
+++ CalendarServer/trunk/txdav/caldav/datastore/scheduling/ischedule/dkim.py 2013-08-05 17:55:30 UTC (rev 11579)
@@ -513,12 +513,15 @@
Class used to verify an DKIM-signed HTTP request.
"""
- def __init__(self, request, key_lookup=None, protocol_debug=False):
+ def __init__(self, headers, body, key_lookup=None, protocol_debug=False):
"""
- @param request: The HTTP request to process
- @type request: L{twext.server.Request}
+ @param headers: The HTTP request headers to process
+ @type headers: L{twext.web2.http_headers.Headers}
+ @param body: The HTTP request body to process
+ @type body: C{str}
"""
- self.request = request
+ self.headers = headers
+ self.body = body
self._debug = protocol_debug
self.dkim_tags = {}
@@ -563,17 +566,14 @@
Public key used:
%s
-""" % (self.request.headers.getRawHeaders(DKIM_SIGNATURE)[0], headers, pubkey._original_data,)
+""" % (self.headers.getRawHeaders(DKIM_SIGNATURE)[0], headers, pubkey._original_data,)
log.debug("DKIM: %s:%s" % (msg, _debug_msg,))
if self._debug:
msg = "%s:%s" % (msg, _debug_msg,)
raise DKIMVerificationError(msg)
# Do body validation
- data = (yield allDataFromStream(self.request.stream))
- self.request.stream = MemoryStream(data if data is not None else "")
- self.request.stream.doStartReading = None
- body = DKIMUtils.canonicalizeBody(data)
+ body = DKIMUtils.canonicalizeBody(self.body)
bh = base64.b64encode(self.hash_method(body).digest())
if bh != self.dkim_tags["_bh"]:
msg = "Could not verify the DKIM body hash"
@@ -584,7 +584,7 @@
Base64 encoded body:
%s
-""" % (self.request.headers.getRawHeaders(DKIM_SIGNATURE), self.hash_method.__name__, base64.b64encode(body),)
+""" % (self.headers.getRawHeaders(DKIM_SIGNATURE), self.hash_method.__name__, base64.b64encode(body),)
log.debug("DKIM: %s:%s" % (msg, _debug_msg,))
if self._debug:
msg = "%s:%s" % (msg, _debug_msg,)
@@ -599,7 +599,7 @@
"""
# Check presence of header
- dkim = self.request.headers.getRawHeaders(DKIM_SIGNATURE)
+ dkim = self.headers.getRawHeaders(DKIM_SIGNATURE)
if dkim is None:
msg = "No DKIM-Signature header present in the request"
log.debug("DKIM: " + msg)
@@ -683,12 +683,12 @@
headers = []
for header in header_list:
- actual_headers = self.request.headers.getRawHeaders(header)
+ actual_headers = self.headers.getRawHeaders(header)
if actual_headers:
headers.append((header, ",".join(actual_headers),))
# DKIM-Signature is always included at the end
- headers.append((DKIM_SIGNATURE, self.request.headers.getRawHeaders(DKIM_SIGNATURE)[0],))
+ headers.append((DKIM_SIGNATURE, self.headers.getRawHeaders(DKIM_SIGNATURE)[0],))
# Now canonicalize the values
return "".join([DKIMUtils.canonicalizeHeader(name, value, dkim_tags=self.dkim_tags) for name, value in headers])
Modified: CalendarServer/trunk/txdav/caldav/datastore/scheduling/ischedule/localservers.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/scheduling/ischedule/localservers.py 2013-08-05 17:51:24 UTC (rev 11578)
+++ CalendarServer/trunk/txdav/caldav/datastore/scheduling/ischedule/localservers.py 2013-08-05 17:55:30 UTC (rev 11579)
@@ -193,10 +193,10 @@
return ip in self.allowed_from_ips
- def checkSharedSecret(self, request):
+ def checkSharedSecret(self, headers):
# Get header from the request
- request_secret = request.headers.getRawHeaders(SERVER_SECRET_HEADER)
+ request_secret = headers.getRawHeaders(SERVER_SECRET_HEADER)
if request_secret is not None and self.shared_secret is None:
log.error("iSchedule request included unexpected %s header" % (SERVER_SECRET_HEADER,))
Modified: CalendarServer/trunk/txdav/caldav/datastore/scheduling/ischedule/resource.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/scheduling/ischedule/resource.py 2013-08-05 17:51:24 UTC (rev 11578)
+++ CalendarServer/trunk/txdav/caldav/datastore/scheduling/ischedule/resource.py 2013-08-05 17:55:30 UTC (rev 11579)
@@ -20,6 +20,7 @@
from twext.web2 import responsecode
from twext.web2.dav.http import ErrorResponse
from twext.web2.dav.noneprops import NonePropertyStore
+from twext.web2.dav.util import allDataFromStream
from twext.web2.http import Response, HTTPError, StatusResponse, XMLResponse
from twext.web2.http_headers import MimeType
@@ -223,11 +224,11 @@
originator = self.loadOriginatorFromRequestHeaders(request)
recipients = self.loadRecipientsFromRequestHeaders(request)
- calendar = (yield self.loadCalendarFromRequest(request))
+ body = (yield allDataFromStream(request.stream))
# Do the POST processing treating this as a non-local schedule
try:
- result = (yield scheduler.doSchedulingViaPOST(request, originator, recipients, calendar))
+ result = (yield scheduler.doSchedulingViaPOST(request.remoteAddr, request.headers, body, originator, recipients))
except Exception:
ex = Failure()
yield txn.abort()
Modified: CalendarServer/trunk/txdav/caldav/datastore/scheduling/ischedule/scheduler.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/scheduling/ischedule/scheduler.py 2013-08-05 17:51:24 UTC (rev 11578)
+++ CalendarServer/trunk/txdav/caldav/datastore/scheduling/ischedule/scheduler.py 2013-08-05 17:55:30 UTC (rev 11579)
@@ -23,7 +23,7 @@
from twisted.internet.defer import inlineCallbacks, returnValue
from twistedcaldav.config import config
-from twistedcaldav.ical import normalizeCUAddress
+from twistedcaldav.ical import normalizeCUAddress, Component
from txdav.caldav.datastore.scheduling import addressmapping
from txdav.caldav.datastore.scheduling.cuaddress import RemoteCalendarUser
@@ -139,15 +139,17 @@
}
@inlineCallbacks
- def doSchedulingViaPOST(self, request, originator, recipients, calendar):
+ def doSchedulingViaPOST(self, remoteAddr, headers, body, originator, recipients):
"""
Carry out iSchedule specific processing.
"""
- self.request = request
+ self.remoteAddr = remoteAddr
+ self.headers = headers
self.verified = False
+
if config.Scheduling.iSchedule.DKIM.Enabled:
- verifier = DKIMVerifier(self.request, protocol_debug=config.Scheduling.iSchedule.DKIM.ProtocolDebug)
+ verifier = DKIMVerifier(self.headers, body, protocol_debug=config.Scheduling.iSchedule.DKIM.ProtocolDebug)
try:
yield verifier.verify()
self.verified = True
@@ -170,7 +172,9 @@
msg,
))
- if self.request.headers.getRawHeaders('x-calendarserver-itip-refreshonly', ("F"))[0] == "T":
+ calendar = Component.fromString(body)
+
+ if self.headers.getRawHeaders('x-calendarserver-itip-refreshonly', ("F"))[0] == "T":
self.txn.doing_attendee_refresh = 1
# Normalize recipient addresses
@@ -193,15 +197,6 @@
self.calendar.normalizeCalendarUserAddresses(normalizationLookup, self.txn.directoryService().recordWithCalendarUserAddress)
- def loadRecipientsFromRequestHeaders(self):
- """
- Need to normalize the calendar data and recipient values to keep those in sync,
- as we might later try to match them
- """
- super(IScheduleScheduler, self).loadRecipientsFromRequestHeaders()
- self.recipients = [normalizeCUAddress(recipient, normalizationLookup, self.txn.directoryService().recordWithCalendarUserAddress) for recipient in self.recipients]
-
-
def checkAuthorization(self):
# Must have an unauthenticated user
if self.originator_uid is not None:
@@ -259,7 +254,7 @@
))
else:
# Get the request IP and map to hostname.
- clientip = self.request.remoteAddr.host
+ clientip = self.remoteAddr.host
# First compare as dotted IP
matched = False
@@ -312,7 +307,7 @@
expected_uri = urlparse.urlparse(expected_uri)
# Get the request IP and map to hostname.
- clientip = self.request.remoteAddr.host
+ clientip = self.remoteAddr.host
# Check against this server (or any of its partitions). We need this because an external iTIP message
# may be addressed to users on different partitions, and the node receiving the iTIP message will need to
@@ -345,7 +340,7 @@
log.debug("iSchedule cannot lookup client ip '%s': %s" % (clientip, str(e),))
# Check possible shared secret
- if matched and not Servers.getThisServer().checkSharedSecret(self.request):
+ if matched and not Servers.getThisServer().checkSharedSecret(self.headers):
log.error("Invalid iSchedule shared secret")
matched = False
Modified: CalendarServer/trunk/txdav/caldav/datastore/scheduling/ischedule/test/test_dkim.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/scheduling/ischedule/test/test_dkim.py 2013-08-05 17:51:24 UTC (rev 11578)
+++ CalendarServer/trunk/txdav/caldav/datastore/scheduling/ischedule/test/test_dkim.py 2013-08-05 17:55:30 UTC (rev 11579)
@@ -16,6 +16,7 @@
from Crypto.PublicKey import RSA
+from twext.web2.dav.util import allDataFromStream
from twext.web2.http_headers import Headers, MimeType
from twext.web2.stream import MemoryStream
@@ -244,6 +245,13 @@
self.stream = MemoryStream(body)
+ def _makeHeaders(self, headers_pairs):
+ headers = Headers()
+ for name, value in headers_pairs:
+ headers.addRawHeader(name, value)
+ return headers
+
+
def test_valid_dkim_headers(self):
"""
L{DKIMVerifier.processDKIMHeader} correctly validates DKIM-Signature headers.
@@ -276,8 +284,7 @@
)
for headers, result in data:
- request = self.StubRequest("POST", "/", headers, "")
- verifier = DKIMVerifier(request)
+ verifier = DKIMVerifier(self._makeHeaders(headers), "")
if result:
verifier.processDKIMHeader()
else:
@@ -307,8 +314,7 @@
)
for name, value, result in data:
- request = self.StubRequest("POST", "/", ((name, value,),), "")
- verifier = DKIMVerifier(request)
+ verifier = DKIMVerifier(self._makeHeaders(((name, value,),)), "")
if name == "DKIM-Signature":
verifier.processDKIMHeader()
canonicalized = DKIMUtils.canonicalizeHeader(name, value, verifier.dkim_tags if name == "DKIM-Signature" else None)
@@ -374,8 +380,7 @@
for hdrs, result in data:
headers = [hdr.split(":", 1) for hdr in hdrs.splitlines()]
- request = self.StubRequest("POST", "/", headers, "")
- verifier = DKIMVerifier(request)
+ verifier = DKIMVerifier(self._makeHeaders(headers), "")
verifier.processDKIMHeader()
extracted = verifier.extractSignedHeaders()
self.assertEqual(extracted, result.replace("\n", "\r\n"))
@@ -427,9 +432,8 @@
for hdrs, keys, result in data:
headers = [hdr.split(":", 1) for hdr in hdrs.splitlines()]
- request = self.StubRequest("POST", "/", headers, "")
TestPublicKeyLookup.PublicKeyLookup_Testing.keys = keys
- verifier = DKIMVerifier(request, key_lookup=(TestPublicKeyLookup.PublicKeyLookup_Testing,))
+ verifier = DKIMVerifier(self._makeHeaders(headers), "", key_lookup=(TestPublicKeyLookup.PublicKeyLookup_Testing,))
verifier.processDKIMHeader()
pkey = (yield verifier.locatePublicKey())
if result:
@@ -461,7 +465,8 @@
# Verify signature
TestPublicKeyLookup.PublicKeyLookup_Testing.keys = keys
- verifier = DKIMVerifier(request, key_lookup=(TestPublicKeyLookup.PublicKeyLookup_Testing,))
+ data = (yield allDataFromStream(request.stream))
+ verifier = DKIMVerifier(request.headers, data, key_lookup=(TestPublicKeyLookup.PublicKeyLookup_Testing,))
TestPublicKeyLookup.PublicKeyLookup_Testing({}).flushCache()
try:
yield verifier.verify()
Modified: CalendarServer/trunk/txdav/caldav/datastore/scheduling/ischedule/test/test_localservers.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/scheduling/ischedule/test/test_localservers.py 2013-08-05 17:51:24 UTC (rev 11578)
+++ CalendarServer/trunk/txdav/caldav/datastore/scheduling/ischedule/test/test_localservers.py 2013-08-05 17:55:30 UTC (rev 11579)
@@ -167,22 +167,22 @@
request = SimpleRequest(None, "POST", "/ischedule")
request.headers.addRawHeader(SERVER_SECRET_HEADER, "foobar")
- self.assertTrue(servers.getServerById("00001").checkSharedSecret(request))
+ self.assertTrue(servers.getServerById("00001").checkSharedSecret(request.headers))
request = SimpleRequest(None, "POST", "/ischedule")
request.headers.addRawHeader(SERVER_SECRET_HEADER, "foobar1")
- self.assertFalse(servers.getServerById("00001").checkSharedSecret(request))
+ self.assertFalse(servers.getServerById("00001").checkSharedSecret(request.headers))
request = SimpleRequest(None, "POST", "/ischedule")
- self.assertFalse(servers.getServerById("00001").checkSharedSecret(request))
+ self.assertFalse(servers.getServerById("00001").checkSharedSecret(request.headers))
request = SimpleRequest(None, "POST", "/ischedule")
request.headers.addRawHeader(SERVER_SECRET_HEADER, "foobar")
- self.assertFalse(servers.getServerById("00002").checkSharedSecret(request))
+ self.assertFalse(servers.getServerById("00002").checkSharedSecret(request.headers))
request = SimpleRequest(None, "POST", "/ischedule")
request.headers.addRawHeader(SERVER_SECRET_HEADER, "foobar1")
- self.assertFalse(servers.getServerById("00002").checkSharedSecret(request))
+ self.assertFalse(servers.getServerById("00002").checkSharedSecret(request.headers))
request = SimpleRequest(None, "POST", "/ischedule")
- self.assertTrue(servers.getServerById("00002").checkSharedSecret(request))
+ self.assertTrue(servers.getServerById("00002").checkSharedSecret(request.headers))
Modified: CalendarServer/trunk/txdav/caldav/datastore/scheduling/processing.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/scheduling/processing.py 2013-08-05 17:51:24 UTC (rev 11578)
+++ CalendarServer/trunk/txdav/caldav/datastore/scheduling/processing.py 2013-08-05 17:55:30 UTC (rev 11579)
@@ -462,6 +462,26 @@
log.debug("ImplicitProcessing - originator '%s' to recipient '%s' ignoring UID: '%s' - organizer has no copy" % (self.originator.cuaddr, self.recipient.cuaddr, self.uid))
raise ImplicitProcessorException("5.3;Organizer change not allowed")
+ # Handle splitting of data early so we can preserve per-attendee data
+ if self.message.hasProperty("X-CALENDARSERVER-SPLIT-OLDER-UID"):
+ if config.Scheduling.Options.Splitting.Enabled:
+ # Tell the existing resource to split
+ log.debug("ImplicitProcessing - originator '%s' to recipient '%s' splitting UID: '%s'" % (self.originator.cuaddr, self.recipient.cuaddr, self.uid))
+ split = (yield self.doImplicitAttendeeSplit())
+ if split:
+ returnValue((True, False, False, None,))
+ else:
+ self.message.removeProperty("X-CALENDARSERVER-SPLIT-OLDER-UID")
+ self.message.removeProperty("X-CALENDARSERVER-SPLIT-RID")
+
+ elif self.message.hasProperty("X-CALENDARSERVER-SPLIT-NEWER-UID"):
+ if config.Scheduling.Options.Splitting.Enabled:
+ log.debug("ImplicitProcessing - originator '%s' to recipient '%s' ignoring UID: '%s' - split already done" % (self.originator.cuaddr, self.recipient.cuaddr, self.uid))
+ returnValue((True, False, False, None,))
+ else:
+ self.message.removeProperty("X-CALENDARSERVER-SPLIT-OLDER-UID")
+ self.message.removeProperty("X-CALENDARSERVER-SPLIT-RID")
+
# Different based on method
if self.method == "REQUEST":
result = (yield self.doImplicitAttendeeRequest())
@@ -478,6 +498,22 @@
@inlineCallbacks
+ def doImplicitAttendeeSplit(self):
+ """
+ Handle splitting of the existing calendar data.
+ """
+ olderUID = self.message.propertyValue("X-CALENDARSERVER-SPLIT-OLDER-UID")
+ split_rid = self.message.propertyValue("X-CALENDARSERVER-SPLIT-RID")
+ if olderUID is None or split_rid is None:
+ returnValue(False)
+
+ # Split the resource
+ yield self.recipient_calendar_resource.splitForAttendee(rid=split_rid, olderUID=olderUID)
+
+ returnValue(True)
+
+
+ @inlineCallbacks
def doImplicitAttendeeRequest(self):
"""
@return: C{tuple} of (processed, auto-processed, store inbox item, changes)
Modified: CalendarServer/trunk/txdav/caldav/datastore/scheduling/test/test_icalsplitter.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/scheduling/test/test_icalsplitter.py 2013-08-05 17:51:24 UTC (rev 11578)
+++ CalendarServer/trunk/txdav/caldav/datastore/scheduling/test/test_icalsplitter.py 2013-08-05 17:55:30 UTC (rev 11579)
@@ -2009,12 +2009,12 @@
splitter = iCalSplitter(1024, 14)
if title[0] == "1":
self.assertTrue(splitter.willSplit(ical), "Failed will split: %s" % (title,))
- icalOld = splitter.split(ical)
+ icalOld, icalNew = splitter.split(ical)
relsubs = dict(self.subs)
relsubs["relID"] = icalOld.resourceUID()
- self.assertEqual(str(ical).replace("\r\n ", ""), split_future.replace("\n", "\r\n") % relsubs, "Failed future: %s" % (title,))
+ self.assertEqual(str(icalNew).replace("\r\n ", ""), split_future.replace("\n", "\r\n") % relsubs, "Failed future: %s" % (title,))
self.assertEqual(str(icalOld).replace("\r\n ", ""), split_past.replace("\n", "\r\n") % relsubs, "Failed past: %s" % (title,))
# Make sure new items won't split again
- self.assertFalse(splitter.willSplit(ical), "Failed future will split: %s" % (title,))
+ self.assertFalse(splitter.willSplit(icalNew), "Failed future will split: %s" % (title,))
self.assertFalse(splitter.willSplit(icalOld), "Failed past will split: %s" % (title,))
Modified: CalendarServer/trunk/txdav/caldav/datastore/sql.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/sql.py 2013-08-05 17:51:24 UTC (rev 11578)
+++ CalendarServer/trunk/txdav/caldav/datastore/sql.py 2013-08-05 17:55:30 UTC (rev 11579)
@@ -1047,7 +1047,7 @@
@inlineCallbacks
- def _createCalendarObjectWithNameInternal(self, name, component, internal_state, options=None):
+ def _createCalendarObjectWithNameInternal(self, name, component, internal_state, options=None, split_details=None):
# Create => a new resource name
if name in self._objects and self._objects[name]:
@@ -1060,7 +1060,7 @@
raise TooManyObjectResourcesError()
objectResource = (
- yield self._objectResourceClass._createInternal(self, name, component, internal_state, options)
+ yield self._objectResourceClass._createInternal(self, name, component, internal_state, options, split_details)
)
self._objects[objectResource.name()] = objectResource
self._objects[objectResource.uid()] = objectResource
@@ -1414,7 +1414,7 @@
@classproperty
- def _moveTimeRangeUpdateQuery(cls): #@NoSelf
+ def _moveTimeRangeUpdateQuery(cls): #@NoSelf
"""
DAL query to update a child to be in a new parent.
"""
@@ -1509,7 +1509,7 @@
@classmethod
@inlineCallbacks
- def _createInternal(cls, parent, name, component, internal_state, options=None):
+ def _createInternal(cls, parent, name, component, internal_state, options=None, split_details=None):
child = (yield cls.objectWithName(parent, name, None))
if child:
@@ -1519,7 +1519,7 @@
raise ObjectResourceNameNotAllowedError(name)
objectResource = cls(parent, name, None, None, options=options)
- yield objectResource._setComponentInternal(component, inserting=True, internal_state=internal_state)
+ yield objectResource._setComponentInternal(component, inserting=True, internal_state=internal_state, split_details=split_details)
yield objectResource._loadPropertyStore(created=True)
# Note: setComponent triggers a notification, so we don't need to
@@ -1931,17 +1931,28 @@
@inlineCallbacks
- def doImplicitScheduling(self, component, inserting, internal_state):
+ def doImplicitScheduling(self, component, inserting, internal_state, split_details=None):
new_component = None
did_implicit_action = False
is_scheduling_resource = False
schedule_state = None
- is_internal = internal_state not in (ComponentUpdateState.NORMAL, ComponentUpdateState.ATTACHMENT_UPDATE,)
+ is_internal = internal_state not in (
+ ComponentUpdateState.NORMAL,
+ ComponentUpdateState.ATTACHMENT_UPDATE,
+ ComponentUpdateState.SPLIT_OWNER,
+ )
# Do scheduling
if not self.calendar().isInbox():
+ # For splitting we are passed a "raw" component - one with the per-user data pieces in it.
+ # We need to filter that down just to the owner's view to do scheduling, but still ensure the
+ # raw component is written out.
+ if split_details is not None:
+ user_uuid = self._parentCollection.viewerHome().uid()
+ component = PerUserDataFilter(user_uuid).filter(component.duplicate())
+
scheduler = ImplicitScheduler()
# PUT
@@ -1961,7 +1972,7 @@
"Sharee's cannot schedule",
)
- new_calendar = (yield scheduler.doImplicitScheduling(self.schedule_tag_match))
+ new_calendar = (yield scheduler.doImplicitScheduling(self.schedule_tag_match, split_details))
if new_calendar:
if isinstance(new_calendar, int):
returnValue(new_calendar)
@@ -2076,7 +2087,7 @@
@inlineCallbacks
- def _setComponentInternal(self, component, inserting=False, internal_state=ComponentUpdateState.NORMAL, smart_merge=False):
+ def _setComponentInternal(self, component, inserting=False, internal_state=ComponentUpdateState.NORMAL, smart_merge=False, split_details=None):
"""
Setting the component internally to the store itself. This will bypass a whole bunch of data consistency checks
on the assumption that those have been done prior to the component data being provided, provided the flag is set.
@@ -2087,9 +2098,9 @@
self.schedule_tag_match = not self.calendar().isInbox() and internal_state == ComponentUpdateState.NORMAL and smart_merge
schedule_state = None
- if internal_state == ComponentUpdateState.SPLIT:
+ if internal_state in (ComponentUpdateState.SPLIT_OWNER, ComponentUpdateState.SPLIT_ATTENDEE,):
# When splitting, some state from the previous resource needs to be properly
- # preserved in thus new one when storing the component. Since we don't do the "full"
+ # preserved in the new one when storing the component. Since we don't do the "full"
# store here, we need to add the explicit pieces we need for state preservation.
# Check access
@@ -2101,6 +2112,10 @@
managed_copied, managed_removed = (yield self.resourceCheckAttachments(component, inserting))
+ # Do scheduling only for owner split
+ if internal_state == ComponentUpdateState.SPLIT_OWNER:
+ yield self.doImplicitScheduling(component, inserting, internal_state, split_details)
+
self.isScheduleObject = True
self.processScheduleTags(component, inserting, internal_state)
@@ -2165,7 +2180,11 @@
yield self.updateDatabase(component, inserting=inserting)
# Post process managed attachments
- if internal_state in (ComponentUpdateState.NORMAL, ComponentUpdateState.SPLIT):
+ if internal_state in (
+ ComponentUpdateState.NORMAL,
+ ComponentUpdateState.SPLIT_OWNER,
+ ComponentUpdateState.SPLIT_ATTENDEE,
+ ):
if managed_copied:
yield self.copyResourceAttachments(managed_copied)
if managed_removed:
@@ -2179,7 +2198,7 @@
yield self._calendar.notifyChanged()
# Finally check if a split is needed
- if internal_state != ComponentUpdateState.SPLIT and schedule_state == "organizer":
+ if internal_state not in (ComponentUpdateState.SPLIT_OWNER, ComponentUpdateState.SPLIT_ATTENDEE,) and schedule_state == "organizer":
yield self.checkSplit()
returnValue(self._componentChanged)
@@ -2613,7 +2632,7 @@
@classproperty
- def _recurrenceMinMaxByIDQuery(cls): #@NoSelf
+ def _recurrenceMinMaxByIDQuery(cls): #@NoSelf
"""
DAL query to load RECURRANCE_MIN, RECURRANCE_MAX via an object's resource ID.
"""
@@ -2647,7 +2666,7 @@
@classproperty
- def _instanceQuery(cls): #@NoSelf
+ def _instanceQuery(cls): #@NoSelf
"""
DAL query to load TIME_RANGE data via an object's resource ID.
"""
@@ -3282,59 +3301,92 @@
@inlineCallbacks
- def split(self):
+ def split(self, onlyThis=False, rid=None, olderUID=None):
"""
Split this and all matching UID calendar objects as per L{iCalSplitter}.
+
+ We need to handle scheduling with non-hosted users here. Here is what we will do:
+
+ 1) Send an iTIP message for the original event (in its now future-truncated state) and
+ include a special X- parameter in the iTIP message to indicate a split was done and
+ what the RECURRENCE-ID was where the split was made. This will allow "smart" clients/servers
+ to spot the split action and apply that locally upon receipt and processing of the iTIP
+ message. That way they get to preserve the existing per-user data for the old instances. Other
+ clients/servers will just apply the change via normal iTIP processing.
+
+ 2) Send an iTIP message for the new event (which will be for the old instances). "Smart"
+ clients that already got and processed the message from #1 will simply apply this on top
+ of their split copy - it should be identical, part from per-user data, so it will apply
+ cleanly. We can include an X- headers to indicate the split R-ID so "smart" clients/servers
+ can simply ignore this message.
"""
# First job is to grab a UID lock on this entire series of events
yield NamedLock.acquire(self._txn, "ImplicitUIDLock:%s" % (hashlib.md5(self._uid).hexdigest(),))
# Find all other calendar objects on this server with the same UID
- resources = (yield CalendarStoreFeatures(self._txn._store).calendarObjectsWithUID(self._txn, self._uid))
+ if onlyThis:
+ resources = ()
+ else:
+ resources = (yield CalendarStoreFeatures(self._txn._store).calendarObjectsWithUID(self._txn, self._uid))
splitter = iCalSplitter(config.Scheduling.Options.Splitting.Size, config.Scheduling.Options.Splitting.PastDays)
# Determine the recurrence-id of the split and create a new UID for it
calendar = (yield self.component())
- rid = splitter.whereSplit(calendar)
- newUID = str(uuid.uuid4())
+ if rid is None:
+ rid = splitter.whereSplit(calendar)
+ newerUID = calendar.resourceUID()
+ if olderUID is None:
+ olderUID = str(uuid.uuid4())
# Now process this resource, but do implicit scheduling for attendees not hosted on this server.
# We need to do this before processing attendee copies.
- calendar_old = splitter.split(calendar, rid=rid, newUID=newUID)
+ calendar_old, calendar_new = splitter.split(calendar, rid=rid, olderUID=olderUID)
+ calendar_new.bumpiTIPInfo(oldcalendar=calendar, doSequence=True)
+ calendar_old.bumpiTIPInfo(oldcalendar=None, doSequence=True)
+ # If the split results in nothing either resource, then there is really nothing
+ # to actually split
+ if calendar_new.mainType() is None or calendar_old.mainType() is None:
+ returnValue(None)
+
# Store changed data
- if calendar.mainType() is not None:
- yield self._setComponentInternal(calendar, internal_state=ComponentUpdateState.SPLIT)
- else:
- yield self._removeInternal(internal_state=ComponentUpdateState.SPLIT)
- if calendar_old.mainType() is not None:
- yield self.calendar()._createCalendarObjectWithNameInternal("%s.ics" % (newUID,), calendar_old, ComponentUpdateState.SPLIT)
+ yield self._setComponentInternal(calendar_new, internal_state=ComponentUpdateState.SPLIT_OWNER, split_details=(rid, olderUID, True,))
+ yield self.calendar()._createCalendarObjectWithNameInternal("%s.ics" % (olderUID,), calendar_old, ComponentUpdateState.SPLIT_OWNER, split_details=(rid, newerUID, False,))
# Split each one - but not this resource
for resource in resources:
if resource._resourceID == self._resourceID:
continue
- ical = (yield resource.component())
- ical_old = splitter.split(ical, rid=rid, newUID=newUID)
+ yield resource.splitForAttendee(rid, olderUID)
- # Store changed data
- if ical.mainType() is not None:
- yield resource._setComponentInternal(ical, internal_state=ComponentUpdateState.SPLIT)
- else:
- # The split removed all components from this object - remove it
- yield resource._removeInternal(internal_state=ComponentUpdateState.SPLIT)
+ returnValue(olderUID)
- # Create a new resource and store its data (but not if the parent is "inbox", or if it is empty)
- if not resource.calendar().isInbox() and ical_old.mainType() is not None:
- yield resource.calendar()._createCalendarObjectWithNameInternal("%s.ics" % (newUID,), ical_old, ComponentUpdateState.SPLIT)
- # TODO: scheduling currently turned off until we figure out how to properly do that
+ @inlineCallbacks
+ def splitForAttendee(self, rid=None, olderUID=None):
+ """
+ Split this attendee resource as per L{split}.
+ """
+ splitter = iCalSplitter(config.Scheduling.Options.Splitting.Size, config.Scheduling.Options.Splitting.PastDays)
+ ical = (yield self.component())
+ ical_old, ical_new = splitter.split(ical, rid=rid, olderUID=olderUID)
+ ical_new.bumpiTIPInfo(oldcalendar=ical, doSequence=True)
+ ical_old.bumpiTIPInfo(oldcalendar=None, doSequence=True)
- returnValue(newUID)
+ # Store changed data
+ if ical_new.mainType() is not None:
+ yield self._setComponentInternal(ical_new, internal_state=ComponentUpdateState.SPLIT_ATTENDEE)
+ else:
+ # The split removed all components from this object - remove it
+ yield self._removeInternal(internal_state=ComponentRemoveState.INTERNAL)
+ # Create a new resource and store its data (but not if the parent is "inbox", or if it is empty)
+ if not self.calendar().isInbox() and ical_old.mainType() is not None:
+ yield self.calendar()._createCalendarObjectWithNameInternal("%s.ics" % (olderUID,), ical_old, ComponentUpdateState.SPLIT_ATTENDEE)
+
class CalendarObjectSplitterWork(WorkItem, fromTable(schema.CALENDAR_OBJECT_SPLITTER_WORK)):
group = property(lambda self: "CalendarObjectSplitterWork:%s" % (self.resourceID,))
Modified: CalendarServer/trunk/txdav/caldav/datastore/test/test_sql.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/test/test_sql.py 2013-08-05 17:51:24 UTC (rev 11578)
+++ CalendarServer/trunk/txdav/caldav/datastore/test/test_sql.py 2013-08-05 17:55:30 UTC (rev 11579)
@@ -13,6 +13,13 @@
# See the License for the specific language governing permissions and
# limitations under the License.
##
+from txdav.caldav.datastore.scheduling.processing import ImplicitProcessor
+from txdav.caldav.datastore.scheduling.cuaddress import RemoteCalendarUser, \
+ LocalCalendarUser
+from txdav.caldav.datastore.scheduling.caldav.scheduler import CalDAVScheduler
+from txdav.caldav.datastore.scheduling.scheduler import ScheduleResponseQueue
+from twext.web2 import responsecode
+from txdav.caldav.datastore.scheduling.itip import iTIPRequestStatus
"""
Tests for txdav.caldav.datastore.postgres, mostly based on
@@ -28,7 +35,8 @@
from twext.web2.stream import MemoryStream
from twisted.internet import reactor
-from twisted.internet.defer import inlineCallbacks, returnValue, DeferredList
+from twisted.internet.defer import inlineCallbacks, returnValue, DeferredList, \
+ succeed
from twisted.internet.task import deferLater
from twisted.trial import unittest
@@ -36,7 +44,7 @@
from twistedcaldav.caldavxml import CalendarDescription
from twistedcaldav.config import config
from twistedcaldav.dateops import datetimeMktime
-from twistedcaldav.ical import Component
+from twistedcaldav.ical import Component, normalize_iCalStr, diff_iCalStrs
from twistedcaldav.query import calendarqueryfilter
from txdav.base.propertystore.base import PropertyName
@@ -2117,6 +2125,7 @@
ORGANIZER;SCHEDULE-AGENT=NONE;SCHEDULE-STATUS=5.3:mailto:user1 at example.org
RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:%(relID)s
RRULE:FREQ=DAILY
+SEQUENCE:1
END:VEVENT
END:VCALENDAR
"""
@@ -2157,6 +2166,7 @@
ORGANIZER;SCHEDULE-AGENT=NONE;SCHEDULE-STATUS=5.3:mailto:user1 at example.org
RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:%(relID)s
RRULE:FREQ=DAILY;UNTIL=%(now_back14_1)s
+SEQUENCE:1
END:VEVENT
BEGIN:VEVENT
UID:%(relID)s
@@ -2168,6 +2178,7 @@
DTSTAMP:20051222T210507Z
ORGANIZER;SCHEDULE-AGENT=NONE;SCHEDULE-STATUS=5.3:mailto:user1 at example.org
RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:%(relID)s
+SEQUENCE:1
END:VEVENT
BEGIN:VEVENT
UID:%(relID)s
@@ -2179,6 +2190,7 @@
DTSTAMP:20051222T210507Z
ORGANIZER;SCHEDULE-AGENT=NONE;SCHEDULE-STATUS=5.3:mailto:user1 at example.org
RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:%(relID)s
+SEQUENCE:1
END:VEVENT
END:VCALENDAR
"""
@@ -2214,8 +2226,8 @@
title = "temp"
relsubs = dict(self.subs)
relsubs["relID"] = newUID
- self.assertEqual(str(ical_future).replace("\r\n ", ""), data_future.replace("\n", "\r\n") % relsubs, "Failed future: %s" % (title,))
- self.assertEqual(str(ical_past).replace("\r\n ", ""), data_past.replace("\n", "\r\n") % relsubs, "Failed past: %s" % (title,))
+ self.assertEqual(normalize_iCalStr(ical_future), normalize_iCalStr(data_future) % relsubs, "Failed future: %s" % (title,))
+ self.assertEqual(normalize_iCalStr(ical_past), normalize_iCalStr(data_past) % relsubs, "Failed past: %s" % (title,))
@inlineCallbacks
@@ -2297,6 +2309,7 @@
ORGANIZER;CN=User 01;EMAIL=user01 at example.com:urn:uuid:user01
RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:%(relID)s
RRULE:FREQ=DAILY
+SEQUENCE:1
SUMMARY:1234567890123456789012345678901234567890
1234567890123456789012345678901234567890
1234567890123456789012345678901234567890
@@ -2312,6 +2325,7 @@
DTSTAMP:20051222T210507Z
ORGANIZER;CN=User 01;EMAIL=user01 at example.com:urn:uuid:user01
RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:%(relID)s
+SEQUENCE:1
END:VEVENT
END:VCALENDAR
"""
@@ -2330,6 +2344,7 @@
ORGANIZER;CN=User 01;EMAIL=user01 at example.com:urn:uuid:user01
RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:%(relID)s
RRULE:FREQ=DAILY;UNTIL=%(now_back14_1)s
+SEQUENCE:1
SUMMARY:1234567890123456789012345678901234567890
1234567890123456789012345678901234567890
1234567890123456789012345678901234567890
@@ -2346,6 +2361,7 @@
DTSTAMP:20051222T210507Z
ORGANIZER;CN=User 01;EMAIL=user01 at example.com:urn:uuid:user01
RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:%(relID)s
+SEQUENCE:1
END:VEVENT
BEGIN:VEVENT
UID:%(relID)s
@@ -2357,6 +2373,7 @@
DTSTAMP:20051222T210507Z
ORGANIZER;CN=User 01;EMAIL=user01 at example.com:urn:uuid:user01
RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:%(relID)s
+SEQUENCE:1
END:VEVENT
END:VCALENDAR
"""
@@ -2376,6 +2393,7 @@
ORGANIZER;CN=User 01;EMAIL=user01 at example.com:urn:uuid:user01
RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:%(relID)s
RRULE:FREQ=DAILY
+SEQUENCE:1
SUMMARY:1234567890123456789012345678901234567890
1234567890123456789012345678901234567890
1234567890123456789012345678901234567890
@@ -2405,6 +2423,7 @@
ORGANIZER;CN=User 01;EMAIL=user01 at example.com:urn:uuid:user01
RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:%(relID)s
RRULE:FREQ=DAILY;UNTIL=%(now_back14_1)s
+SEQUENCE:1
SUMMARY:1234567890123456789012345678901234567890
1234567890123456789012345678901234567890
1234567890123456789012345678901234567890
@@ -2421,6 +2440,7 @@
DTSTAMP:20051222T210507Z
ORGANIZER;CN=User 01;EMAIL=user01 at example.com:urn:uuid:user01
RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:%(relID)s
+SEQUENCE:1
END:VEVENT
BEGIN:VEVENT
UID:%(relID)s
@@ -2432,6 +2452,7 @@
DTSTAMP:20051222T210507Z
ORGANIZER;CN=User 01;EMAIL=user01 at example.com:urn:uuid:user01
RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:%(relID)s
+SEQUENCE:1
END:VEVENT
BEGIN:X-CALENDARSERVER-PERUSER
UID:%(relID)s
@@ -2459,6 +2480,7 @@
ORGANIZER;CN=User 01;EMAIL=user01 at example.com:urn:uuid:user01
RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:%(relID)s
RRULE:FREQ=DAILY
+SEQUENCE:1
SUMMARY:1234567890123456789012345678901234567890
1234567890123456789012345678901234567890
1234567890123456789012345678901234567890
@@ -2482,6 +2504,7 @@
ORGANIZER;CN=User 01;EMAIL=user01 at example.com:urn:uuid:user01
RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:%(relID)s
RRULE:FREQ=DAILY
+SEQUENCE:1
SUMMARY:1234567890123456789012345678901234567890
1234567890123456789012345678901234567890
1234567890123456789012345678901234567890
@@ -2513,6 +2536,7 @@
ORGANIZER;CN=User 01;EMAIL=user01 at example.com:urn:uuid:user01
RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:%(relID)s
RRULE:FREQ=DAILY;UNTIL=%(now_back14_1)s
+SEQUENCE:1
SUMMARY:1234567890123456789012345678901234567890
1234567890123456789012345678901234567890
1234567890123456789012345678901234567890
@@ -2544,6 +2568,7 @@
ORGANIZER;CN=User 01;EMAIL=user01 at example.com:urn:uuid:user01
RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:%(relID)s
RRULE:FREQ=DAILY
+SEQUENCE:1
SUMMARY:1234567890123456789012345678901234567890
1234567890123456789012345678901234567890
1234567890123456789012345678901234567890
@@ -2566,6 +2591,7 @@
DTSTAMP:20051222T210507Z
ORGANIZER;CN=User 01;EMAIL=user01 at example.com:urn:uuid:user01
RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:%(relID)s
+SEQUENCE:1
END:VEVENT
BEGIN:X-CALENDARSERVER-PERUSER
UID:%(relID)s
@@ -2591,6 +2617,7 @@
DTSTAMP:20051222T210507Z
ORGANIZER;CN=User 01;EMAIL=user01 at example.com:urn:uuid:user01
RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:%(relID)s
+SEQUENCE:1
END:VEVENT
BEGIN:X-CALENDARSERVER-PERUSER
UID:12345-67890
@@ -2617,6 +2644,7 @@
DTSTAMP:20051222T210507Z
ORGANIZER;CN=User 01;EMAIL=user01 at example.com:urn:uuid:user01
RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:%(relID)s
+SEQUENCE:1
END:VEVENT
END:VCALENDAR
"""
@@ -2663,8 +2691,8 @@
title = "user01"
relsubs = dict(self.subs)
relsubs["relID"] = newUID
- self.assertEqual(str(ical_future).replace("\r\n ", ""), data_future.replace("\n", "\r\n").replace("\r\n ", "") % relsubs, "Failed future: %s" % (title,))
- self.assertEqual(str(ical_past).replace("\r\n ", ""), data_past.replace("\n", "\r\n").replace("\r\n ", "") % relsubs, "Failed past: %s" % (title,))
+ self.assertEqual(normalize_iCalStr(ical_future), normalize_iCalStr(data_future) % relsubs, "Failed future: %s" % (title,))
+ self.assertEqual(normalize_iCalStr(ical_past), normalize_iCalStr(data_past) % relsubs, "Failed past: %s" % (title,))
# Get user02 data
cal = yield self.calendarUnderTest(name="calendar", home="user02")
@@ -2684,9 +2712,9 @@
# Verify user02 data
title = "user02"
- self.assertEqual(str(ical_future).replace("\r\n ", ""), data_future2.replace("\n", "\r\n").replace("\r\n ", "") % relsubs, "Failed future: %s" % (title,))
- self.assertEqual(str(ical_past).replace("\r\n ", ""), data_past2.replace("\n", "\r\n").replace("\r\n ", "") % relsubs, "Failed past: %s" % (title,))
- self.assertEqual(str(ical_inbox).replace("\r\n ", ""), data_inbox2.replace("\n", "\r\n").replace("\r\n ", "") % relsubs, "Failed inbox: %s" % (title,))
+ self.assertEqual(normalize_iCalStr(ical_future), normalize_iCalStr(data_future2) % relsubs, "Failed future: %s" % (title,))
+ self.assertEqual(normalize_iCalStr(ical_past), normalize_iCalStr(data_past2) % relsubs, "Failed past: %s" % (title,))
+ self.assertEqual(normalize_iCalStr(ical_inbox), normalize_iCalStr(data_inbox2) % relsubs, "Failed inbox: %s" % (title,))
# Get user03 data
cal = yield self.calendarUnderTest(name="calendar", home="user03")
@@ -2707,9 +2735,9 @@
# Verify user03 data
title = "user03"
- self.assertEqual(str(ical_future).replace("\r\n ", ""), data_future3.replace("\n", "\r\n").replace("\r\n ", "") % relsubs, "Failed future: %s" % (title,))
- self.assertEqual(str(ical_past).replace("\r\n ", ""), data_past3.replace("\n", "\r\n").replace("\r\n ", "") % relsubs, "Failed past: %s" % (title,))
- self.assertEqual(str(ical_inbox).replace("\r\n ", ""), data_inbox3.replace("\n", "\r\n").replace("\r\n ", "") % relsubs, "Failed inbox: %s" % (title,))
+ self.assertEqual(normalize_iCalStr(ical_future), normalize_iCalStr(data_future3) % relsubs, "Failed future: %s" % (title,))
+ self.assertEqual(normalize_iCalStr(ical_past), normalize_iCalStr(data_past3) % relsubs, "Failed past: %s" % (title,))
+ self.assertEqual(normalize_iCalStr(ical_inbox), normalize_iCalStr(data_inbox3) % relsubs, "Failed inbox: %s" % (title,))
# Get user04 data
cal = yield self.calendarUnderTest(name="calendar", home="user04")
@@ -2724,7 +2752,7 @@
# Verify user04 data
title = "user04"
- self.assertEqual(str(ical_past).replace("\r\n ", ""), data_past4.replace("\n", "\r\n").replace("\r\n ", "") % relsubs, "Failed past: %s" % (title,))
+ self.assertEqual(normalize_iCalStr(ical_past), normalize_iCalStr(data_past4) % relsubs, "Failed past: %s" % (title,))
# Get user05 data
cal = yield self.calendarUnderTest(name="calendar", home="user05")
@@ -2740,8 +2768,8 @@
# Verify user05 data
title = "user05"
- self.assertEqual(str(ical_future).replace("\r\n ", ""), data_future5.replace("\n", "\r\n").replace("\r\n ", "") % relsubs, "Failed future: %s" % (title,))
- self.assertEqual(str(ical_inbox).replace("\r\n ", ""), data_inbox5.replace("\n", "\r\n").replace("\r\n ", "") % relsubs, "Failed inbox: %s" % (title,))
+ self.assertEqual(normalize_iCalStr(ical_future), normalize_iCalStr(data_future5) % relsubs, "Failed future: %s" % (title,))
+ self.assertEqual(normalize_iCalStr(ical_inbox), normalize_iCalStr(data_inbox5) % relsubs, "Failed inbox: %s" % (title,))
@inlineCallbacks
@@ -3045,16 +3073,16 @@
cobj = cobjs[0]
cname2 = cobj.name()
ical = yield cobj.component()
- self.assertEqual(str(ical).replace("\r\n ", ""), data_2.replace("\n", "\r\n").replace("\r\n ", "") % self.subs, "Failed 2")
+ self.assertEqual(normalize_iCalStr(ical), normalize_iCalStr(data_2) % self.subs, "Failed 2")
yield cobj.setComponent(Component.fromString(data_2_update % self.subs))
yield self.commit()
cobj = yield self.calendarObjectUnderTest(name="data1.ics", calendar_name="calendar", home="user01")
ical = yield cobj.component()
- self.assertEqual(str(ical).replace("\r\n ", ""), data_1.replace("\n", "\r\n").replace("\r\n ", "") % self.subs, "Failed 2")
+ self.assertEqual(normalize_iCalStr(ical), normalize_iCalStr(data_1) % self.subs, "Failed 2")
cobj = yield self.calendarObjectUnderTest(name=cname2, calendar_name="calendar", home="user02")
ical = yield cobj.component()
- self.assertEqual(str(ical).replace("\r\n ", ""), data_2_changed.replace("\n", "\r\n").replace("\r\n ", "") % self.subs, "Failed 2")
+ self.assertEqual(normalize_iCalStr(ical), normalize_iCalStr(data_2_changed) % self.subs, "Failed 2")
yield self.commit()
@@ -3244,7 +3272,7 @@
ORGANIZER;CN=User 01;EMAIL=user01 at example.com:urn:uuid:user01
RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:%(relID)s
RRULE:FREQ=DAILY
-SEQUENCE:2
+SEQUENCE:3
SUMMARY:1234567890123456789012345678901234567890
1234567890123456789012345678901234567890
1234567890123456789012345678901234567890
@@ -3261,7 +3289,7 @@
DTSTAMP:%(dtstamp)s
ORGANIZER;CN=User 01;EMAIL=user01 at example.com:urn:uuid:user01
RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:%(relID)s
-SEQUENCE:2
+SEQUENCE:3
END:VEVENT
END:VCALENDAR
"""
@@ -3280,7 +3308,7 @@
ORGANIZER;CN=User 01;EMAIL=user01 at example.com:urn:uuid:user01
RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:%(relID)s
RRULE:FREQ=DAILY;UNTIL=%(now_back14_1)s
-SEQUENCE:2
+SEQUENCE:3
SUMMARY:1234567890123456789012345678901234567890
1234567890123456789012345678901234567890
1234567890123456789012345678901234567890
@@ -3297,7 +3325,7 @@
DTSTAMP:%(dtstamp)s
ORGANIZER;CN=User 01;EMAIL=user01 at example.com:urn:uuid:user01
RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:%(relID)s
-SEQUENCE:2
+SEQUENCE:3
END:VEVENT
BEGIN:VEVENT
UID:%(relID)s
@@ -3310,7 +3338,7 @@
DTSTAMP:%(dtstamp)s
ORGANIZER;CN=User 01;EMAIL=user01 at example.com:urn:uuid:user01
RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:%(relID)s
-SEQUENCE:2
+SEQUENCE:3
END:VEVENT
END:VCALENDAR
"""
@@ -3329,7 +3357,7 @@
ORGANIZER;CN=User 01;EMAIL=user01 at example.com:urn:uuid:user01
RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:%(relID)s
RRULE:FREQ=DAILY
-SEQUENCE:2
+SEQUENCE:3
SUMMARY:1234567890123456789012345678901234567890
1234567890123456789012345678901234567890
1234567890123456789012345678901234567890
@@ -3346,7 +3374,7 @@
DTSTAMP:%(dtstamp)s
ORGANIZER;CN=User 01;EMAIL=user01 at example.com:urn:uuid:user01
RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:%(relID)s
-SEQUENCE:2
+SEQUENCE:3
END:VEVENT
BEGIN:X-CALENDARSERVER-PERUSER
UID:12345-67890
@@ -3372,7 +3400,7 @@
ORGANIZER;CN=User 01;EMAIL=user01 at example.com:urn:uuid:user01
RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:%(relID)s
RRULE:FREQ=DAILY;UNTIL=%(now_back14_1)s
-SEQUENCE:2
+SEQUENCE:3
SUMMARY:1234567890123456789012345678901234567890
1234567890123456789012345678901234567890
1234567890123456789012345678901234567890
@@ -3389,7 +3417,7 @@
DTSTAMP:%(dtstamp)s
ORGANIZER;CN=User 01;EMAIL=user01 at example.com:urn:uuid:user01
RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:%(relID)s
-SEQUENCE:2
+SEQUENCE:3
END:VEVENT
BEGIN:VEVENT
UID:%(relID)s
@@ -3402,7 +3430,7 @@
DTSTAMP:%(dtstamp)s
ORGANIZER;CN=User 01;EMAIL=user01 at example.com:urn:uuid:user01
RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:%(relID)s
-SEQUENCE:2
+SEQUENCE:3
END:VEVENT
BEGIN:X-CALENDARSERVER-PERUSER
UID:%(relID)s
@@ -3436,7 +3464,7 @@
relsubs["mid"] = mid
relsubs["att_uri"] = location
relsubs["dtstamp"] = str(ical.masterComponent().propertyValue("DTSTAMP"))
- self.assertEqual(str(ical).replace("\r\n ", ""), data_attach_1.replace("\n", "\r\n").replace("\r\n ", "") % relsubs, "Failed attachment user01")
+ self.assertEqual(normalize_iCalStr(ical), normalize_iCalStr(data_attach_1) % relsubs, "Failed attachment user01")
yield self.commit()
# Add overrides to cause a split
@@ -3468,8 +3496,8 @@
# Verify user01 data
title = "user01"
- self.assertEqual(str(ical_future).replace("\r\n ", ""), data_future.replace("\n", "\r\n").replace("\r\n ", "") % relsubs, "Failed future: %s" % (title,))
- self.assertEqual(str(ical_past).replace("\r\n ", ""), data_past.replace("\n", "\r\n").replace("\r\n ", "") % relsubs, "Failed past: %s" % (title,))
+ self.assertEqual(normalize_iCalStr(ical_future), normalize_iCalStr(data_future) % relsubs, "Failed future: %s" % (title,))
+ self.assertEqual(normalize_iCalStr(ical_past), normalize_iCalStr(data_past) % relsubs, "Failed past: %s" % (title,))
# Get user02 data
cal = yield self.calendarUnderTest(name="calendar", home="user02")
@@ -3484,5 +3512,1433 @@
# Verify user02 data
title = "user02"
- self.assertEqual(str(ical_future).replace("\r\n ", ""), data_future2.replace("\n", "\r\n").replace("\r\n ", "") % relsubs, "Failed future: %s" % (title,))
- self.assertEqual(str(ical_past).replace("\r\n ", ""), data_past2.replace("\n", "\r\n").replace("\r\n ", "") % relsubs, "Failed past: %s" % (title,))
+ self.assertEqual(normalize_iCalStr(ical_future), normalize_iCalStr(data_future2) % relsubs, "Failed future: %s" % (title,))
+ self.assertEqual(normalize_iCalStr(ical_past), normalize_iCalStr(data_past2) % relsubs, "Failed past: %s" % (title,))
+
+
+ @inlineCallbacks
+ def test_calendarObjectSplit_processing_simple(self):
+ """
+ Test that splitting of calendar objects works when outside invites are processed.
+ """
+ self.patch(config.Scheduling.Options.Splitting, "Enabled", True)
+ self.patch(config.Scheduling.Options.Splitting, "Size", 1024)
+ self.patch(config.Scheduling.Options.Splitting, "PastDays", 14)
+ self.patch(config.Scheduling.Options.Splitting, "Delay", 2)
+
+ # Create one event from outside organizer that will not split
+ calendar = yield self.calendarUnderTest(name="calendar", home="user01")
+
+ data = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:%(now_back30)s
+DURATION:PT1H
+ATTENDEE;PARTSTAT=ACCEPTED:mailto:cuser01 at example.org
+ATTENDEE;PARTSTAT=ACCEPTED:mailto:user01 at example.com
+DTSTAMP:20051222T210507Z
+ORGANIZER;SCHEDULE-AGENT=NONE:mailto:cuser01 at example.org
+RRULE:FREQ=DAILY
+SUMMARY:1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Master
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:%(now_back25)s
+DTSTART:%(now_back25)s
+DURATION:PT1H
+ATTENDEE;PARTSTAT=TENTATIVE:mailto:cuser01 at example.org
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user01 at example.com
+DTSTAMP:20051222T210507Z
+ORGANIZER;SCHEDULE-AGENT=NONE:mailto:cuser01 at example.org
+TRANSP:TRANSPARENT
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:now_back25
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:%(now_back24)s
+DTSTART:%(now_back24)s
+DURATION:PT1H
+ATTENDEE;PARTSTAT=DECLINED:mailto:cuser01 at example.org
+ATTENDEE;PARTSTAT=DECLINED:mailto:user01 at example.com
+DTSTAMP:20051222T210507Z
+ORGANIZER;SCHEDULE-AGENT=NONE:mailto:cuser01 at example.org
+TRANSP:TRANSPARENT
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:now_back24
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:%(now_fwd10)s
+DTSTART:%(now_fwd10)s
+DURATION:PT1H
+ATTENDEE;PARTSTAT=TENTATIVE:mailto:cuser01 at example.org
+ATTENDEE;PARTSTAT=TENTATIVE:mailto:user01 at example.com
+DTSTAMP:20051222T210507Z
+ORGANIZER;SCHEDULE-AGENT=NONE:mailto:cuser01 at example.org
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:now_fwd10
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+"""
+
+ itip1 = """BEGIN:VCALENDAR
+VERSION:2.0
+METHOD:REQUEST
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+X-CALENDARSERVER-SPLIT-OLDER-UID:C4526F4C-4324-4893-B769-BD766E4A4E7C
+X-CALENDARSERVER-SPLIT-RID;VALUE=DATE-TIME:%(now_back14)s
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:%(now_back14)s
+DURATION:PT1H
+ATTENDEE;PARTSTAT=ACCEPTED:mailto:cuser01 at example.org
+ATTENDEE;PARTSTAT=ACCEPTED:mailto:user01 at example.com
+DTSTAMP:20051222T210507Z
+ORGANIZER:mailto:cuser01 at example.org
+RRULE:FREQ=DAILY
+SEQUENCE:1
+SUMMARY:1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:%(now_fwd10)s
+DTSTART:%(now_fwd10)s
+DURATION:PT1H
+ATTENDEE;PARTSTAT=TENTATIVE:mailto:cuser01 at example.org
+ATTENDEE;PARTSTAT=TENTATIVE:mailto:user01 at example.com
+DTSTAMP:20051222T210507Z
+ORGANIZER:mailto:cuser01 at example.org
+SEQUENCE:1
+END:VEVENT
+END:VCALENDAR
+"""
+
+ data_future = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:%(now_back14)s
+DURATION:PT1H
+ATTENDEE;PARTSTAT=ACCEPTED:mailto:cuser01 at example.org
+ATTENDEE;CN=User 01;EMAIL=user01 at example.com;PARTSTAT=ACCEPTED:urn:uuid:user01
+DTSTAMP:20051222T210507Z
+ORGANIZER;SCHEDULE-AGENT=NONE:mailto:cuser01 at example.org
+RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:C4526F4C-4324-4893-B769-BD766E4A4E7C
+RRULE:FREQ=DAILY
+SEQUENCE:1
+SUMMARY:1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:%(now_fwd10)s
+DTSTART:%(now_fwd10)s
+DURATION:PT1H
+ATTENDEE;PARTSTAT=TENTATIVE:mailto:cuser01 at example.org
+ATTENDEE;CN=User 01;EMAIL=user01 at example.com;PARTSTAT=TENTATIVE:urn:uuid:user01
+DTSTAMP:20051222T210507Z
+ORGANIZER;SCHEDULE-AGENT=NONE:mailto:cuser01 at example.org
+RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:C4526F4C-4324-4893-B769-BD766E4A4E7C
+SEQUENCE:1
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Master
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:%(now_fwd10)s
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:now_fwd10
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+"""
+
+ data_past = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:C4526F4C-4324-4893-B769-BD766E4A4E7C
+DTSTART:%(now_back30)s
+DURATION:PT1H
+ATTENDEE;PARTSTAT=ACCEPTED:mailto:cuser01 at example.org
+ATTENDEE;CN=User 01;EMAIL=user01 at example.com;PARTSTAT=ACCEPTED:urn:uuid:user01
+DTSTAMP:20051222T210507Z
+ORGANIZER;SCHEDULE-AGENT=NONE:mailto:cuser01 at example.org
+RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:C4526F4C-4324-4893-B769-BD766E4A4E7C
+RRULE:FREQ=DAILY;UNTIL=%(now_back14_1)s
+SEQUENCE:1
+SUMMARY:1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+END:VEVENT
+BEGIN:VEVENT
+UID:C4526F4C-4324-4893-B769-BD766E4A4E7C
+RECURRENCE-ID:%(now_back25)s
+DTSTART:%(now_back25)s
+DURATION:PT1H
+ATTENDEE;PARTSTAT=TENTATIVE:mailto:cuser01 at example.org
+ATTENDEE;CN=User 01;EMAIL=user01 at example.com;PARTSTAT=NEEDS-ACTION:urn:uuid:user01
+DTSTAMP:20051222T210507Z
+ORGANIZER;SCHEDULE-AGENT=NONE:mailto:cuser01 at example.org
+RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:C4526F4C-4324-4893-B769-BD766E4A4E7C
+SEQUENCE:1
+END:VEVENT
+BEGIN:VEVENT
+UID:C4526F4C-4324-4893-B769-BD766E4A4E7C
+RECURRENCE-ID:%(now_back24)s
+DTSTART:%(now_back24)s
+DURATION:PT1H
+ATTENDEE;PARTSTAT=DECLINED:mailto:cuser01 at example.org
+ATTENDEE;CN=User 01;EMAIL=user01 at example.com;PARTSTAT=DECLINED:urn:uuid:user01
+DTSTAMP:20051222T210507Z
+ORGANIZER;SCHEDULE-AGENT=NONE:mailto:cuser01 at example.org
+RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:C4526F4C-4324-4893-B769-BD766E4A4E7C
+SEQUENCE:1
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:C4526F4C-4324-4893-B769-BD766E4A4E7C
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Master
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:%(now_back25)s
+TRANSP:TRANSPARENT
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:now_back25
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:%(now_back24)s
+TRANSP:TRANSPARENT
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:now_back24
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+"""
+
+ itip2 = """BEGIN:VCALENDAR
+VERSION:2.0
+METHOD:REQUEST
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+X-CALENDARSERVER-SPLIT-NEWER-UID:12345-67890
+X-CALENDARSERVER-SPLIT-RID;VALUE=DATE-TIME:%(now_back14)s
+BEGIN:VEVENT
+UID:C4526F4C-4324-4893-B769-BD766E4A4E7C
+DTSTART:%(now_back30)s
+DURATION:PT1H
+ATTENDEE;PARTSTAT=ACCEPTED:mailto:cuser01 at example.org
+ATTENDEE;CN=User 01;EMAIL=user01 at example.com;PARTSTAT=ACCEPTED:urn:uuid:user01
+DTSTAMP:20051222T210507Z
+ORGANIZER;SCHEDULE-AGENT=NONE:mailto:cuser01 at example.org
+RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:C4526F4C-4324-4893-B769-BD766E4A4E7C
+RRULE:FREQ=DAILY;UNTIL=%(now_back14_1)s
+SEQUENCE:1
+SUMMARY:1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+END:VEVENT
+BEGIN:VEVENT
+UID:C4526F4C-4324-4893-B769-BD766E4A4E7C
+RECURRENCE-ID:%(now_back25)s
+DTSTART:%(now_back25)s
+DURATION:PT1H
+ATTENDEE;PARTSTAT=TENTATIVE:mailto:cuser01 at example.org
+ATTENDEE;CN=User 01;EMAIL=user01 at example.com;PARTSTAT=NEEDS-ACTION:urn:uuid:user01
+DTSTAMP:20051222T210507Z
+ORGANIZER;SCHEDULE-AGENT=NONE:mailto:cuser01 at example.org
+RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:C4526F4C-4324-4893-B769-BD766E4A4E7C
+SEQUENCE:1
+END:VEVENT
+BEGIN:VEVENT
+UID:C4526F4C-4324-4893-B769-BD766E4A4E7C
+RECURRENCE-ID:%(now_back24)s
+DTSTART:%(now_back24)s
+DURATION:PT1H
+ATTENDEE;PARTSTAT=DECLINED:mailto:cuser01 at example.org
+ATTENDEE;CN=User 01;EMAIL=user01 at example.com;PARTSTAT=DECLINED:urn:uuid:user01
+DTSTAMP:20051222T210507Z
+ORGANIZER;SCHEDULE-AGENT=NONE:mailto:cuser01 at example.org
+RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:C4526F4C-4324-4893-B769-BD766E4A4E7C
+SEQUENCE:1
+END:VEVENT
+END:VCALENDAR
+"""
+
+ component = Component.fromString(data % self.subs)
+ cobj = yield calendar.createCalendarObjectWithName("data.ics", component)
+ self.assertFalse(hasattr(cobj, "_workItems"))
+ yield self.commit()
+
+ # Now inject an iTIP with split
+ processor = ImplicitProcessor()
+ processor.getRecipientsCopy = lambda : succeed(None)
+
+ cobj = yield self.calendarObjectUnderTest(name="data.ics", calendar_name="calendar", home="user01")
+ processor.recipient_calendar_resource = cobj
+ processor.recipient_calendar = (yield cobj.componentForUser("user01"))
+ processor.message = Component.fromString(itip1 % self.subs)
+ processor.originator = RemoteCalendarUser("mailto:cuser01 at example.org")
+ processor.recipient = LocalCalendarUser("urn:uuid:user01", None)
+ processor.method = "REQUEST"
+ processor.uid = "12345-67890"
+
+ result = yield processor.doImplicitAttendee()
+ self.assertEqual(result, (True, False, False, None,))
+ yield self.commit()
+
+ new_name = []
+
+ @inlineCallbacks
+ def _verify_state():
+ # Get user01 data
+ cal = yield self.calendarUnderTest(name="calendar", home="user01")
+ cobjs = yield cal.calendarObjects()
+ self.assertEqual(len(cobjs), 2)
+ for cobj in cobjs:
+ ical = yield cobj.component()
+ if ical.resourceUID() == "12345-67890":
+ ical_future = ical
+ else:
+ ical_past = ical
+ new_name.append(cobj.name())
+
+ # Verify user01 data
+ title = "user01"
+ self.assertEqual(normalize_iCalStr(ical_future), normalize_iCalStr(data_future) % self.subs, "Failed future: %s\n%s" % (title, diff_iCalStrs(ical_future, data_future % self.subs),))
+ self.assertEqual(normalize_iCalStr(ical_past), normalize_iCalStr(data_past) % self.subs, "Failed past: %s\n%s" % (title, diff_iCalStrs(ical_past, data_past % self.subs),))
+
+ # No inbox
+ cal = yield self.calendarUnderTest(name="inbox", home="user01")
+ cobjs = yield cal.calendarObjects()
+ self.assertEqual(len(cobjs), 0)
+ yield self.commit()
+
+ yield _verify_state()
+
+ # Now inject an iTIP with split
+ processor = ImplicitProcessor()
+ processor.getRecipientsCopy = lambda : succeed(None)
+
+ cobj = yield self.calendarObjectUnderTest(name=new_name[0], calendar_name="calendar", home="user01")
+ self.assertTrue(cobj is not None)
+ processor.recipient_calendar_resource = cobj
+ processor.recipient_calendar = (yield cobj.componentForUser("user01"))
+ processor.message = Component.fromString(itip2 % self.subs)
+ processor.originator = RemoteCalendarUser("mailto:cuser01 at example.org")
+ processor.recipient = LocalCalendarUser("urn:uuid:user01", None)
+ processor.method = "REQUEST"
+ processor.uid = "C4526F4C-4324-4893-B769-BD766E4A4E7C"
+
+ result = yield processor.doImplicitAttendee()
+ self.assertEqual(result, (True, False, False, None,))
+ yield self.commit()
+
+ yield _verify_state()
+
+
+ @inlineCallbacks
+ def test_calendarObjectSplit_processing_one_past_instance(self):
+ """
+ Test that splitting of calendar objects works when outside invites are processed.
+ """
+ self.patch(config.Scheduling.Options.Splitting, "Enabled", True)
+ self.patch(config.Scheduling.Options.Splitting, "Size", 1024)
+ self.patch(config.Scheduling.Options.Splitting, "PastDays", 14)
+ self.patch(config.Scheduling.Options.Splitting, "Delay", 2)
+
+ # Create one event from outside organizer that will not split
+ calendar = yield self.calendarUnderTest(name="calendar", home="user01")
+
+ data = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:%(now_back25)s
+DTSTART:%(now_back25)s
+DURATION:PT1H
+ATTENDEE;PARTSTAT=TENTATIVE:mailto:cuser01 at example.org
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user01 at example.com
+DTSTAMP:20051222T210507Z
+ORGANIZER;SCHEDULE-AGENT=NONE:mailto:cuser01 at example.org
+TRANSP:TRANSPARENT
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:now_back25
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+"""
+
+ itip1 = """BEGIN:VCALENDAR
+VERSION:2.0
+METHOD:CANCEL
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+X-CALENDARSERVER-SPLIT-OLDER-UID:C4526F4C-4324-4893-B769-BD766E4A4E7C
+X-CALENDARSERVER-SPLIT-RID;VALUE=DATE-TIME:%(now_back14)s
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:%(now_back25)s
+DTSTART:%(now_back25)s
+DURATION:PT1H
+ATTENDEE;PARTSTAT=TENTATIVE:mailto:cuser01 at example.org
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user01 at example.com
+DTSTAMP:20051222T210507Z
+ORGANIZER;SCHEDULE-AGENT=NONE:mailto:cuser01 at example.org
+END:VEVENT
+END:VCALENDAR
+"""
+
+ data_past = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:C4526F4C-4324-4893-B769-BD766E4A4E7C
+RECURRENCE-ID:%(now_back25)s
+DTSTART:%(now_back25)s
+DURATION:PT1H
+ATTENDEE;PARTSTAT=TENTATIVE:mailto:cuser01 at example.org
+ATTENDEE;CN=User 01;EMAIL=user01 at example.com;PARTSTAT=NEEDS-ACTION:urn:uuid:user01
+DTSTAMP:20051222T210507Z
+ORGANIZER;SCHEDULE-AGENT=NONE:mailto:cuser01 at example.org
+RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:C4526F4C-4324-4893-B769-BD766E4A4E7C
+SEQUENCE:1
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:C4526F4C-4324-4893-B769-BD766E4A4E7C
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:%(now_back25)s
+TRANSP:TRANSPARENT
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:now_back25
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+"""
+
+ component = Component.fromString(data % self.subs)
+ cobj = yield calendar.createCalendarObjectWithName("data.ics", component)
+ self.assertFalse(hasattr(cobj, "_workItems"))
+ yield self.commit()
+
+ # Now inject an iTIP with split
+ processor = ImplicitProcessor()
+ processor.getRecipientsCopy = lambda : succeed(None)
+
+ cobj = yield self.calendarObjectUnderTest(name="data.ics", calendar_name="calendar", home="user01")
+ processor.recipient_calendar_resource = cobj
+ processor.recipient_calendar = (yield cobj.componentForUser("user01"))
+ processor.message = Component.fromString(itip1 % self.subs)
+ processor.originator = RemoteCalendarUser("mailto:cuser01 at example.org")
+ processor.recipient = LocalCalendarUser("urn:uuid:user01", None)
+ processor.method = "CANCEL"
+ processor.uid = "12345-67890"
+
+ result = yield processor.doImplicitAttendee()
+ self.assertEqual(result, (True, False, False, None,))
+ yield self.commit()
+
+ # Get user01 data
+ cal = yield self.calendarUnderTest(name="calendar", home="user01")
+ cobjs = yield cal.calendarObjects()
+ self.assertEqual(len(cobjs), 1)
+ ical = yield cobjs[0].component()
+ ical_past = ical
+
+ # Verify user01 data
+ title = "user01"
+ self.assertEqual(normalize_iCalStr(ical_past), normalize_iCalStr(data_past) % self.subs, "Failed past: %s\n%s" % (title, diff_iCalStrs(ical_past, data_past % self.subs),))
+
+
+ @inlineCallbacks
+ def test_calendarObjectSplit_processing_one_future_instance(self):
+ """
+ Test that splitting of calendar objects works when outside invites are processed.
+ """
+ self.patch(config.Scheduling.Options.Splitting, "Enabled", True)
+ self.patch(config.Scheduling.Options.Splitting, "Size", 1024)
+ self.patch(config.Scheduling.Options.Splitting, "PastDays", 14)
+ self.patch(config.Scheduling.Options.Splitting, "Delay", 2)
+
+ # Create one event from outside organizer that will not split
+ calendar = yield self.calendarUnderTest(name="calendar", home="user01")
+
+ data = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:%(now_fwd10)s
+DTSTART:%(now_fwd10)s
+DURATION:PT1H
+ATTENDEE;PARTSTAT=TENTATIVE:mailto:cuser01 at example.org
+ATTENDEE;PARTSTAT=TENTATIVE:mailto:user01 at example.com
+DTSTAMP:20051222T210507Z
+ORGANIZER;SCHEDULE-AGENT=NONE:mailto:cuser01 at example.org
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:now_fwd10
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+"""
+
+ itip1 = """BEGIN:VCALENDAR
+VERSION:2.0
+METHOD:REQUEST
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+X-CALENDARSERVER-SPLIT-OLDER-UID:C4526F4C-4324-4893-B769-BD766E4A4E7C
+X-CALENDARSERVER-SPLIT-RID;VALUE=DATE-TIME:%(now_back14)s
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:%(now_fwd10)s
+DTSTART:%(now_fwd10)s
+DURATION:PT1H
+ATTENDEE;PARTSTAT=TENTATIVE:mailto:cuser01 at example.org
+ATTENDEE;PARTSTAT=TENTATIVE:mailto:user01 at example.com
+DTSTAMP:20051222T210507Z
+ORGANIZER:mailto:cuser01 at example.org
+SEQUENCE:1
+END:VEVENT
+END:VCALENDAR
+"""
+
+ data_future = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:%(now_fwd10)s
+DTSTART:%(now_fwd10)s
+DURATION:PT1H
+ATTENDEE;PARTSTAT=TENTATIVE:mailto:cuser01 at example.org
+ATTENDEE;CN=User 01;EMAIL=user01 at example.com;PARTSTAT=TENTATIVE:urn:uuid:user01
+DTSTAMP:20051222T210507Z
+ORGANIZER;SCHEDULE-AGENT=NONE:mailto:cuser01 at example.org
+RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:C4526F4C-4324-4893-B769-BD766E4A4E7C
+SEQUENCE:1
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:%(now_fwd10)s
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:now_fwd10
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+"""
+
+ component = Component.fromString(data % self.subs)
+ cobj = yield calendar.createCalendarObjectWithName("data.ics", component)
+ self.assertFalse(hasattr(cobj, "_workItems"))
+ yield self.commit()
+
+ # Now inject an iTIP with split
+ processor = ImplicitProcessor()
+ processor.getRecipientsCopy = lambda : succeed(None)
+
+ cobj = yield self.calendarObjectUnderTest(name="data.ics", calendar_name="calendar", home="user01")
+ processor.recipient_calendar_resource = cobj
+ processor.recipient_calendar = (yield cobj.componentForUser("user01"))
+ processor.message = Component.fromString(itip1 % self.subs)
+ processor.originator = RemoteCalendarUser("mailto:cuser01 at example.org")
+ processor.recipient = LocalCalendarUser("urn:uuid:user01", None)
+ processor.method = "REQUEST"
+ processor.uid = "12345-67890"
+
+ result = yield processor.doImplicitAttendee()
+ self.assertEqual(result, (True, False, False, None,))
+ yield self.commit()
+
+ # Get user01 data
+ cal = yield self.calendarUnderTest(name="calendar", home="user01")
+ cobjs = yield cal.calendarObjects()
+ self.assertEqual(len(cobjs), 1)
+ ical = yield cobjs[0].component()
+ ical_future = ical
+
+ # Verify user01 data
+ title = "user01"
+ self.assertEqual(normalize_iCalStr(ical_future), normalize_iCalStr(data_future) % self.subs, "Failed future: %s\n%s" % (title, diff_iCalStrs(ical_future, data_future % self.subs),))
+
+
+ @inlineCallbacks
+ def test_calendarObjectSplit_processing_one_past_and_one_future(self):
+ """
+ Test that splitting of calendar objects works when outside invites are processed.
+ """
+ self.patch(config.Scheduling.Options.Splitting, "Enabled", True)
+ self.patch(config.Scheduling.Options.Splitting, "Size", 1024)
+ self.patch(config.Scheduling.Options.Splitting, "PastDays", 14)
+ self.patch(config.Scheduling.Options.Splitting, "Delay", 2)
+
+ # Create one event from outside organizer that will not split
+ calendar = yield self.calendarUnderTest(name="calendar", home="user01")
+
+ data = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:%(now_back25)s
+DTSTART:%(now_back25)s
+DURATION:PT1H
+ATTENDEE;PARTSTAT=TENTATIVE:mailto:cuser01 at example.org
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user01 at example.com
+DTSTAMP:20051222T210507Z
+ORGANIZER;SCHEDULE-AGENT=NONE:mailto:cuser01 at example.org
+TRANSP:TRANSPARENT
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:now_back25
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:%(now_fwd10)s
+DTSTART:%(now_fwd10)s
+DURATION:PT1H
+ATTENDEE;PARTSTAT=TENTATIVE:mailto:cuser01 at example.org
+ATTENDEE;PARTSTAT=TENTATIVE:mailto:user01 at example.com
+DTSTAMP:20051222T210507Z
+ORGANIZER;SCHEDULE-AGENT=NONE:mailto:cuser01 at example.org
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:now_fwd10
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+"""
+
+ itip1 = """BEGIN:VCALENDAR
+VERSION:2.0
+METHOD:CANCEL
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+X-CALENDARSERVER-SPLIT-OLDER-UID:C4526F4C-4324-4893-B769-BD766E4A4E7C
+X-CALENDARSERVER-SPLIT-RID;VALUE=DATE-TIME:%(now_back14)s
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:%(now_back25)s
+DTSTART:%(now_back25)s
+DURATION:PT1H
+ATTENDEE;PARTSTAT=TENTATIVE:mailto:cuser01 at example.org
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user01 at example.com
+DTSTAMP:20051222T210507Z
+ORGANIZER;SCHEDULE-AGENT=NONE:mailto:cuser01 at example.org
+TRANSP:TRANSPARENT
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:now_back25
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+"""
+
+ data_future = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:%(now_fwd10)s
+DTSTART:%(now_fwd10)s
+DURATION:PT1H
+ATTENDEE;PARTSTAT=TENTATIVE:mailto:cuser01 at example.org
+ATTENDEE;CN=User 01;EMAIL=user01 at example.com;PARTSTAT=TENTATIVE:urn:uuid:user01
+DTSTAMP:20051222T210507Z
+ORGANIZER;SCHEDULE-AGENT=NONE:mailto:cuser01 at example.org
+RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:C4526F4C-4324-4893-B769-BD766E4A4E7C
+SEQUENCE:1
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:%(now_fwd10)s
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:now_fwd10
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+"""
+
+ data_past = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:C4526F4C-4324-4893-B769-BD766E4A4E7C
+RECURRENCE-ID:%(now_back25)s
+DTSTART:%(now_back25)s
+DURATION:PT1H
+ATTENDEE;PARTSTAT=TENTATIVE:mailto:cuser01 at example.org
+ATTENDEE;CN=User 01;EMAIL=user01 at example.com;PARTSTAT=NEEDS-ACTION:urn:uuid:user01
+DTSTAMP:20051222T210507Z
+ORGANIZER;SCHEDULE-AGENT=NONE:mailto:cuser01 at example.org
+RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:C4526F4C-4324-4893-B769-BD766E4A4E7C
+SEQUENCE:1
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:C4526F4C-4324-4893-B769-BD766E4A4E7C
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:%(now_back25)s
+TRANSP:TRANSPARENT
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:now_back25
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+"""
+
+ component = Component.fromString(data % self.subs)
+ cobj = yield calendar.createCalendarObjectWithName("data.ics", component)
+ self.assertFalse(hasattr(cobj, "_workItems"))
+ yield self.commit()
+
+ # Now inject an iTIP with split
+ processor = ImplicitProcessor()
+ processor.getRecipientsCopy = lambda : succeed(None)
+
+ cobj = yield self.calendarObjectUnderTest(name="data.ics", calendar_name="calendar", home="user01")
+ processor.recipient_calendar_resource = cobj
+ processor.recipient_calendar = (yield cobj.componentForUser("user01"))
+ processor.message = Component.fromString(itip1 % self.subs)
+ processor.originator = RemoteCalendarUser("mailto:cuser01 at example.org")
+ processor.recipient = LocalCalendarUser("urn:uuid:user01", None)
+ processor.method = "REQUEST"
+ processor.uid = "12345-67890"
+
+ result = yield processor.doImplicitAttendee()
+ self.assertEqual(result, (True, False, False, None,))
+ yield self.commit()
+
+ # Get user01 data
+ cal = yield self.calendarUnderTest(name="calendar", home="user01")
+ cobjs = yield cal.calendarObjects()
+ self.assertEqual(len(cobjs), 2)
+ for cobj in cobjs:
+ ical = yield cobj.component()
+ if ical.resourceUID() == "12345-67890":
+ ical_future = ical
+ else:
+ ical_past = ical
+
+ # Verify user01 data
+ title = "user01"
+ self.assertEqual(normalize_iCalStr(ical_future), normalize_iCalStr(data_future) % self.subs, "Failed future: %s\n%s" % (title, diff_iCalStrs(ical_future, data_future % self.subs),))
+ self.assertEqual(normalize_iCalStr(ical_past), normalize_iCalStr(data_past) % self.subs, "Failed past: %s\n%s" % (title, diff_iCalStrs(ical_past, data_past % self.subs),))
+
+
+ @inlineCallbacks
+ def test_calendarObjectSplit_processing_disabled(self):
+ """
+ Test that splitting of calendar objects works when outside invites are processed.
+ """
+ self.patch(config.Scheduling.Options.Splitting, "Enabled", False)
+ self.patch(config.Scheduling.Options.Splitting, "Size", 1024)
+ self.patch(config.Scheduling.Options.Splitting, "PastDays", 14)
+ self.patch(config.Scheduling.Options.Splitting, "Delay", 2)
+
+ # Create one event from outside organizer that will not split
+ calendar = yield self.calendarUnderTest(name="calendar", home="user01")
+
+ data = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:%(now_back30)s
+DURATION:PT1H
+ATTENDEE;PARTSTAT=ACCEPTED:mailto:cuser01 at example.org
+ATTENDEE;PARTSTAT=ACCEPTED:mailto:user01 at example.com
+DTSTAMP:20051222T210507Z
+ORGANIZER;SCHEDULE-AGENT=NONE:mailto:cuser01 at example.org
+RRULE:FREQ=DAILY
+SUMMARY:1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Master
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:%(now_back25)s
+DTSTART:%(now_back25)s
+DURATION:PT1H
+ATTENDEE;PARTSTAT=TENTATIVE:mailto:cuser01 at example.org
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user01 at example.com
+DTSTAMP:20051222T210507Z
+ORGANIZER;SCHEDULE-AGENT=NONE:mailto:cuser01 at example.org
+TRANSP:TRANSPARENT
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:now_back25
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:%(now_back24)s
+DTSTART:%(now_back24)s
+DURATION:PT1H
+ATTENDEE;PARTSTAT=DECLINED:mailto:cuser01 at example.org
+ATTENDEE;PARTSTAT=DECLINED:mailto:user01 at example.com
+DTSTAMP:20051222T210507Z
+ORGANIZER;SCHEDULE-AGENT=NONE:mailto:cuser01 at example.org
+TRANSP:TRANSPARENT
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:now_back24
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:%(now_fwd10)s
+DTSTART:%(now_fwd10)s
+DURATION:PT1H
+ATTENDEE;PARTSTAT=TENTATIVE:mailto:cuser01 at example.org
+ATTENDEE;PARTSTAT=TENTATIVE:mailto:user01 at example.com
+DTSTAMP:20051222T210507Z
+ORGANIZER;SCHEDULE-AGENT=NONE:mailto:cuser01 at example.org
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:now_fwd10
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+"""
+
+ itip1 = """BEGIN:VCALENDAR
+VERSION:2.0
+METHOD:REQUEST
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+X-CALENDARSERVER-SPLIT-OLDER-UID:C4526F4C-4324-4893-B769-BD766E4A4E7C
+X-CALENDARSERVER-SPLIT-RID;VALUE=DATE-TIME:%(now_back14)s
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:%(now_back14)s
+DURATION:PT1H
+ATTENDEE;PARTSTAT=ACCEPTED:mailto:cuser01 at example.org
+ATTENDEE;PARTSTAT=ACCEPTED:mailto:user01 at example.com
+DTSTAMP:20051222T210507Z
+ORGANIZER:mailto:cuser01 at example.org
+RRULE:FREQ=DAILY
+SEQUENCE:1
+SUMMARY:1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:%(now_fwd10)s
+DTSTART:%(now_fwd10)s
+DURATION:PT1H
+ATTENDEE;PARTSTAT=TENTATIVE:mailto:cuser01 at example.org
+ATTENDEE;PARTSTAT=TENTATIVE:mailto:user01 at example.com
+DTSTAMP:20051222T210507Z
+ORGANIZER:mailto:cuser01 at example.org
+SEQUENCE:1
+END:VEVENT
+END:VCALENDAR
+"""
+
+ itip2 = """BEGIN:VCALENDAR
+VERSION:2.0
+METHOD:REQUEST
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+X-CALENDARSERVER-SPLIT-NEWER-UID:12345-67890
+X-CALENDARSERVER-SPLIT-RID;VALUE=DATE-TIME:%(now_back14)s
+BEGIN:VEVENT
+UID:C4526F4C-4324-4893-B769-BD766E4A4E7C
+DTSTART:%(now_back30)s
+DURATION:PT1H
+ATTENDEE;PARTSTAT=ACCEPTED:mailto:cuser01 at example.org
+ATTENDEE;CN=User 01;EMAIL=user01 at example.com;PARTSTAT=ACCEPTED:urn:uuid:user01
+DTSTAMP:20051222T210507Z
+ORGANIZER;SCHEDULE-AGENT=NONE:mailto:cuser01 at example.org
+RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:C4526F4C-4324-4893-B769-BD766E4A4E7C
+RRULE:FREQ=DAILY;UNTIL=%(now_back14_1)s
+SEQUENCE:1
+SUMMARY:1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+END:VEVENT
+BEGIN:VEVENT
+UID:C4526F4C-4324-4893-B769-BD766E4A4E7C
+RECURRENCE-ID:%(now_back25)s
+DTSTART:%(now_back25)s
+DURATION:PT1H
+ATTENDEE;PARTSTAT=TENTATIVE:mailto:cuser01 at example.org
+ATTENDEE;CN=User 01;EMAIL=user01 at example.com;PARTSTAT=NEEDS-ACTION:urn:uuid:user01
+DTSTAMP:20051222T210507Z
+ORGANIZER;SCHEDULE-AGENT=NONE:mailto:cuser01 at example.org
+RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:C4526F4C-4324-4893-B769-BD766E4A4E7C
+SEQUENCE:1
+END:VEVENT
+BEGIN:VEVENT
+UID:C4526F4C-4324-4893-B769-BD766E4A4E7C
+RECURRENCE-ID:%(now_back24)s
+DTSTART:%(now_back24)s
+DURATION:PT1H
+ATTENDEE;PARTSTAT=DECLINED:mailto:cuser01 at example.org
+ATTENDEE;CN=User 01;EMAIL=user01 at example.com;PARTSTAT=DECLINED:urn:uuid:user01
+DTSTAMP:20051222T210507Z
+ORGANIZER;SCHEDULE-AGENT=NONE:mailto:cuser01 at example.org
+RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:C4526F4C-4324-4893-B769-BD766E4A4E7C
+SEQUENCE:1
+END:VEVENT
+END:VCALENDAR
+"""
+
+ component = Component.fromString(data % self.subs)
+ cobj = yield calendar.createCalendarObjectWithName("data.ics", component)
+ self.assertFalse(hasattr(cobj, "_workItems"))
+ yield self.commit()
+
+ # Now inject an iTIP with split
+ processor_action = [False, False, ]
+ def _doImplicitAttendeeRequest():
+ processor_action[0] = True
+ return succeed(True)
+ def _doImplicitAttendeeCancel():
+ processor_action[1] = True
+ return succeed(True)
+ processor = ImplicitProcessor()
+ processor.getRecipientsCopy = lambda : succeed(None)
+ processor.doImplicitAttendeeRequest = _doImplicitAttendeeRequest
+ processor.doImplicitAttendeeCancel = _doImplicitAttendeeCancel
+
+ cobj = yield self.calendarObjectUnderTest(name="data.ics", calendar_name="calendar", home="user01")
+ processor.recipient_calendar_resource = cobj
+ processor.recipient_calendar = (yield cobj.componentForUser("user01"))
+ processor.message = Component.fromString(itip1 % self.subs)
+ processor.originator = RemoteCalendarUser("mailto:cuser01 at example.org")
+ processor.recipient = LocalCalendarUser("urn:uuid:user01", None)
+ processor.method = "REQUEST"
+ processor.uid = "12345-67890"
+
+ yield processor.doImplicitAttendee()
+ self.assertTrue(processor_action[0])
+ self.assertFalse(processor_action[1])
+ yield self.commit()
+
+ # Now inject an iTIP with split
+ processor_action = [False, False, ]
+ processor.getRecipientsCopy = lambda : succeed(None)
+ processor.doImplicitAttendeeRequest = _doImplicitAttendeeRequest
+ processor.doImplicitAttendeeCancel = _doImplicitAttendeeCancel
+
+ processor.recipient_calendar_resource = None
+ processor.recipient_calendar = None
+ processor.message = Component.fromString(itip2 % self.subs)
+ processor.originator = RemoteCalendarUser("mailto:cuser01 at example.org")
+ processor.recipient = LocalCalendarUser("urn:uuid:user01", None)
+ processor.method = "REQUEST"
+ processor.uid = "C4526F4C-4324-4893-B769-BD766E4A4E7C"
+
+ yield processor.doImplicitAttendee()
+ self.assertTrue(processor_action[0])
+ self.assertFalse(processor_action[1])
+
+
+ @inlineCallbacks
+ def test_calendarObjectSplit_external(self):
+ """
+ Test that splitting of calendar objects works.
+ """
+ self.patch(config.Scheduling.Options.Splitting, "Enabled", True)
+ self.patch(config.Scheduling.Options.Splitting, "Size", 1024)
+ self.patch(config.Scheduling.Options.Splitting, "PastDays", 14)
+ self.patch(config.Scheduling.Options.Splitting, "Delay", 2)
+
+ # Create one event that will split
+ calendar = yield self.calendarUnderTest(name="calendar", home="user01")
+
+ data = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:%(now_back30)s
+DURATION:PT1H
+ATTENDEE;PARTSTAT=ACCEPTED:mailto:user01 at example.com
+ATTENDEE:mailto:user02 at example.com
+ATTENDEE:mailto:cuser01 at example.org
+DTSTAMP:20051222T210507Z
+ORGANIZER:mailto:user01 at example.com
+RRULE:FREQ=DAILY
+SUMMARY:1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:%(now_back25)s
+DTSTART:%(now_back25)s
+DURATION:PT1H
+ATTENDEE;PARTSTAT=ACCEPTED:mailto:user01 at example.com
+ATTENDEE:mailto:user02 at example.com
+ATTENDEE:mailto:cuser01 at example.org
+DTSTAMP:20051222T210507Z
+ORGANIZER:mailto:user01 at example.com
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:%(now_back24)s
+DTSTART:%(now_back24)s
+DURATION:PT1H
+ATTENDEE;PARTSTAT=ACCEPTED:mailto:user01 at example.com
+ATTENDEE:mailto:user02 at example.com
+DTSTAMP:20051222T210507Z
+ORGANIZER:mailto:user01 at example.com
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:%(now_fwd10)s
+DTSTART:%(now_fwd10)s
+DURATION:PT1H
+ATTENDEE;PARTSTAT=ACCEPTED:mailto:user01 at example.com
+ATTENDEE:mailto:cuser01 at example.org
+DTSTAMP:20051222T210507Z
+ORGANIZER:mailto:user01 at example.com
+END:VEVENT
+END:VCALENDAR
+"""
+
+ data_future = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:%(now_back14)s
+DURATION:PT1H
+ATTENDEE;CN=User 01;EMAIL=user01 at example.com;PARTSTAT=ACCEPTED:urn:uuid:user01
+ATTENDEE;CN=User 02;EMAIL=user02 at example.com;RSVP=TRUE;SCHEDULE-STATUS=1.2:urn:uuid:user02
+ATTENDEE;RSVP=TRUE;SCHEDULE-STATUS=3.7:mailto:cuser01 at example.org
+DTSTAMP:20051222T210507Z
+ORGANIZER;CN=User 01;EMAIL=user01 at example.com:urn:uuid:user01
+RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:%(relID)s
+RRULE:FREQ=DAILY
+SEQUENCE:1
+SUMMARY:1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:%(now_fwd10)s
+DTSTART:%(now_fwd10)s
+DURATION:PT1H
+ATTENDEE;CN=User 01;EMAIL=user01 at example.com;PARTSTAT=ACCEPTED:urn:uuid:user01
+ATTENDEE;RSVP=TRUE;SCHEDULE-STATUS=3.7:mailto:cuser01 at example.org
+DTSTAMP:20051222T210507Z
+ORGANIZER;CN=User 01;EMAIL=user01 at example.com:urn:uuid:user01
+RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:%(relID)s
+SEQUENCE:1
+END:VEVENT
+END:VCALENDAR
+"""
+
+ data_past = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:%(relID)s
+DTSTART:%(now_back30)s
+DURATION:PT1H
+ATTENDEE;CN=User 01;EMAIL=user01 at example.com;PARTSTAT=ACCEPTED:urn:uuid:user01
+ATTENDEE;CN=User 02;EMAIL=user02 at example.com;RSVP=TRUE;SCHEDULE-STATUS=1.2:urn:uuid:user02
+ATTENDEE;RSVP=TRUE;SCHEDULE-STATUS=3.7:mailto:cuser01 at example.org
+DTSTAMP:20051222T210507Z
+ORGANIZER;CN=User 01;EMAIL=user01 at example.com:urn:uuid:user01
+RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:%(relID)s
+RRULE:FREQ=DAILY;UNTIL=%(now_back14_1)s
+SEQUENCE:1
+SUMMARY:1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+END:VEVENT
+BEGIN:VEVENT
+UID:%(relID)s
+RECURRENCE-ID:%(now_back25)s
+DTSTART:%(now_back25)s
+DURATION:PT1H
+ATTENDEE;CN=User 01;EMAIL=user01 at example.com;PARTSTAT=ACCEPTED:urn:uuid:user01
+ATTENDEE;CN=User 02;EMAIL=user02 at example.com;RSVP=TRUE;SCHEDULE-STATUS=1.2:urn:uuid:user02
+ATTENDEE;RSVP=TRUE;SCHEDULE-STATUS=3.7:mailto:cuser01 at example.org
+DTSTAMP:20051222T210507Z
+ORGANIZER;CN=User 01;EMAIL=user01 at example.com:urn:uuid:user01
+RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:%(relID)s
+SEQUENCE:1
+END:VEVENT
+BEGIN:VEVENT
+UID:%(relID)s
+RECURRENCE-ID:%(now_back24)s
+DTSTART:%(now_back24)s
+DURATION:PT1H
+ATTENDEE;CN=User 01;EMAIL=user01 at example.com;PARTSTAT=ACCEPTED:urn:uuid:user01
+ATTENDEE;CN=User 02;EMAIL=user02 at example.com;RSVP=TRUE;SCHEDULE-STATUS=1.2:urn:uuid:user02
+DTSTAMP:20051222T210507Z
+ORGANIZER;CN=User 01;EMAIL=user01 at example.com:urn:uuid:user01
+RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:%(relID)s
+SEQUENCE:1
+END:VEVENT
+END:VCALENDAR
+"""
+
+ data_future2 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:%(now_back14)s
+DURATION:PT1H
+ATTENDEE;CN=User 01;EMAIL=user01 at example.com;PARTSTAT=ACCEPTED:urn:uuid:user01
+ATTENDEE;CN=User 02;EMAIL=user02 at example.com;RSVP=TRUE:urn:uuid:user02
+ATTENDEE;RSVP=TRUE:mailto:cuser01 at example.org
+DTSTAMP:20051222T210507Z
+EXDATE:%(now_fwd10)s
+ORGANIZER;CN=User 01;EMAIL=user01 at example.com:urn:uuid:user01
+RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:%(relID)s
+RRULE:FREQ=DAILY
+SEQUENCE:1
+SUMMARY:1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user02
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:TRANSPARENT
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+"""
+
+ data_past2 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:%(relID)s
+DTSTART:%(now_back30)s
+DURATION:PT1H
+ATTENDEE;CN=User 01;EMAIL=user01 at example.com;PARTSTAT=ACCEPTED:urn:uuid:user01
+ATTENDEE;CN=User 02;EMAIL=user02 at example.com;RSVP=TRUE:urn:uuid:user02
+ATTENDEE;RSVP=TRUE:mailto:cuser01 at example.org
+DTSTAMP:20051222T210507Z
+ORGANIZER;CN=User 01;EMAIL=user01 at example.com:urn:uuid:user01
+RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:%(relID)s
+RRULE:FREQ=DAILY;UNTIL=%(now_back14_1)s
+SEQUENCE:1
+SUMMARY:1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+END:VEVENT
+BEGIN:VEVENT
+UID:%(relID)s
+RECURRENCE-ID:%(now_back25)s
+DTSTART:%(now_back25)s
+DURATION:PT1H
+ATTENDEE;CN=User 01;EMAIL=user01 at example.com;PARTSTAT=ACCEPTED:urn:uuid:user01
+ATTENDEE;CN=User 02;EMAIL=user02 at example.com;RSVP=TRUE:urn:uuid:user02
+ATTENDEE;RSVP=TRUE:mailto:cuser01 at example.org
+DTSTAMP:20051222T210507Z
+ORGANIZER;CN=User 01;EMAIL=user01 at example.com:urn:uuid:user01
+RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:%(relID)s
+SEQUENCE:1
+END:VEVENT
+BEGIN:VEVENT
+UID:%(relID)s
+RECURRENCE-ID:%(now_back24)s
+DTSTART:%(now_back24)s
+DURATION:PT1H
+ATTENDEE;CN=User 01;EMAIL=user01 at example.com;PARTSTAT=ACCEPTED:urn:uuid:user01
+ATTENDEE;CN=User 02;EMAIL=user02 at example.com;RSVP=TRUE:urn:uuid:user02
+DTSTAMP:20051222T210507Z
+ORGANIZER;CN=User 01;EMAIL=user01 at example.com:urn:uuid:user01
+RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:%(relID)s
+SEQUENCE:1
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:%(relID)s
+X-CALENDARSERVER-PERUSER-UID:user02
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:TRANSPARENT
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+"""
+
+ data_inbox2 = """BEGIN:VCALENDAR
+VERSION:2.0
+METHOD:REQUEST
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:%(now_back14)s
+DURATION:PT1H
+ATTENDEE;CN=User 01;EMAIL=user01 at example.com;PARTSTAT=ACCEPTED:urn:uuid:user01
+ATTENDEE;CN=User 02;EMAIL=user02 at example.com;RSVP=TRUE:urn:uuid:user02
+ATTENDEE;RSVP=TRUE:mailto:cuser01 at example.org
+DTSTAMP:20051222T210507Z
+EXDATE:%(now_fwd10)s
+ORGANIZER;CN=User 01;EMAIL=user01 at example.com:urn:uuid:user01
+RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:%(relID)s
+RRULE:FREQ=DAILY
+SEQUENCE:1
+SUMMARY:1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+END:VEVENT
+END:VCALENDAR
+"""
+
+ data_future_external = """BEGIN:VCALENDAR
+VERSION:2.0
+METHOD:REQUEST
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+X-CALENDARSERVER-SPLIT-OLDER-UID:%(relID)s
+X-CALENDARSERVER-SPLIT-RID;VALUE=DATE-TIME:%(now_back14)s
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:%(now_back14)s
+DURATION:PT1H
+ATTENDEE;CN=User 01;EMAIL=user01 at example.com;PARTSTAT=ACCEPTED:urn:uuid:user01
+ATTENDEE;CN=User 02;EMAIL=user02 at example.com;RSVP=TRUE:urn:uuid:user02
+ATTENDEE;RSVP=TRUE:mailto:cuser01 at example.org
+DTSTAMP:20051222T210507Z
+ORGANIZER;CN=User 01;EMAIL=user01 at example.com:urn:uuid:user01
+RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:%(relID)s
+RRULE:FREQ=DAILY
+SEQUENCE:1
+SUMMARY:1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:%(now_fwd10)s
+DTSTART:%(now_fwd10)s
+DURATION:PT1H
+ATTENDEE;CN=User 01;EMAIL=user01 at example.com;PARTSTAT=ACCEPTED:urn:uuid:user01
+ATTENDEE;RSVP=TRUE:mailto:cuser01 at example.org
+DTSTAMP:20051222T210507Z
+ORGANIZER;CN=User 01;EMAIL=user01 at example.com:urn:uuid:user01
+RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:%(relID)s
+SEQUENCE:1
+END:VEVENT
+END:VCALENDAR
+"""
+
+ data_past_external = """BEGIN:VCALENDAR
+VERSION:2.0
+METHOD:REQUEST
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+X-CALENDARSERVER-SPLIT-NEWER-UID:12345-67890
+X-CALENDARSERVER-SPLIT-RID;VALUE=DATE-TIME:%(now_back14)s
+BEGIN:VEVENT
+UID:%(relID)s
+DTSTART:%(now_back30)s
+DURATION:PT1H
+ATTENDEE;CN=User 01;EMAIL=user01 at example.com;PARTSTAT=ACCEPTED:urn:uuid:user01
+ATTENDEE;CN=User 02;EMAIL=user02 at example.com;RSVP=TRUE:urn:uuid:user02
+ATTENDEE;RSVP=TRUE:mailto:cuser01 at example.org
+DTSTAMP:20051222T210507Z
+EXDATE:%(now_back24)s
+ORGANIZER;CN=User 01;EMAIL=user01 at example.com:urn:uuid:user01
+RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:%(relID)s
+RRULE:FREQ=DAILY;UNTIL=%(now_back14_1)s
+SEQUENCE:1
+SUMMARY:1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+END:VEVENT
+BEGIN:VEVENT
+UID:%(relID)s
+RECURRENCE-ID:%(now_back25)s
+DTSTART:%(now_back25)s
+DURATION:PT1H
+ATTENDEE;CN=User 01;EMAIL=user01 at example.com;PARTSTAT=ACCEPTED:urn:uuid:user01
+ATTENDEE;CN=User 02;EMAIL=user02 at example.com;RSVP=TRUE:urn:uuid:user02
+ATTENDEE;RSVP=TRUE:mailto:cuser01 at example.org
+DTSTAMP:20051222T210507Z
+ORGANIZER;CN=User 01;EMAIL=user01 at example.com:urn:uuid:user01
+RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:%(relID)s
+SEQUENCE:1
+END:VEVENT
+END:VCALENDAR
+"""
+
+ # Patch CalDAVScheduler to trap external schedules
+ details = []
+ def _doSchedulingViaPUT(self, originator, recipients, calendar, internal_request=False, suppress_refresh=False):
+ details.append((originator, recipients, calendar,))
+
+ responses = ScheduleResponseQueue("REQUEST", responsecode.OK)
+ for recipient in recipients:
+ responses.add(recipient, responsecode.OK, reqstatus=iTIPRequestStatus.MESSAGE_DELIVERED)
+ return succeed(responses)
+
+ component = Component.fromString(data % self.subs)
+ cobj = yield calendar.createCalendarObjectWithName("data1.ics", component)
+ self.assertTrue(hasattr(cobj, "_workItems"))
+ work = cobj._workItems[0]
+ yield self.commit()
+
+ self.patch(CalDAVScheduler, "doSchedulingViaPUT", _doSchedulingViaPUT)
+
+ w = schema.CALENDAR_OBJECT_SPLITTER_WORK
+ rows = yield Select(
+ [w.RESOURCE_ID, ],
+ From=w
+ ).on(self.transactionUnderTest())
+ self.assertEqual(len(rows), 1)
+ self.assertEqual(rows[0][0], cobj._resourceID)
+ yield self.abort()
+
+ # Wait for it to complete
+ yield work.whenExecuted()
+
+ rows = yield Select(
+ [w.RESOURCE_ID, ],
+ From=w
+ ).on(self.transactionUnderTest())
+ self.assertEqual(len(rows), 0)
+ yield self.abort()
+
+ # Get the existing and new object data
+ cobj1 = yield self.calendarObjectUnderTest(name="data1.ics", calendar_name="calendar", home="user01")
+ self.assertTrue(cobj1.isScheduleObject)
+ ical1 = yield cobj1.component()
+ newUID = ical1.masterComponent().propertyValue("RELATED-TO")
+
+ cobj2 = yield self.calendarObjectUnderTest(name="%s.ics" % (newUID,), calendar_name="calendar", home="user01")
+ self.assertTrue(cobj2 is not None)
+ self.assertTrue(cobj2.isScheduleObject)
+
+ ical_future = yield cobj1.component()
+ ical_past = yield cobj2.component()
+
+ # Verify user01 data
+ title = "user01"
+ relsubs = dict(self.subs)
+ relsubs["relID"] = newUID
+ self.assertEqual(normalize_iCalStr(ical_future), normalize_iCalStr(data_future) % relsubs, "Failed future: %s\n%s" % (title, diff_iCalStrs(ical_future, data_future % relsubs),))
+ self.assertEqual(normalize_iCalStr(ical_past), normalize_iCalStr(data_past) % relsubs, "Failed past: %s\n%s" % (title, diff_iCalStrs(ical_past, data_past % relsubs),))
+
+ # Get user02 data
+ cal = yield self.calendarUnderTest(name="calendar", home="user02")
+ cobjs = yield cal.calendarObjects()
+ self.assertEqual(len(cobjs), 2)
+ for cobj in cobjs:
+ ical = yield cobj.component()
+ if ical.resourceUID() == "12345-67890":
+ ical_future = ical
+ else:
+ ical_past = ical
+
+ cal = yield self.calendarUnderTest(name="inbox", home="user02")
+ cobjs = yield cal.calendarObjects()
+ self.assertEqual(len(cobjs), 1)
+ ical_inbox = yield cobjs[0].component()
+
+ # Verify user02 data
+ title = "user02"
+ self.assertEqual(normalize_iCalStr(ical_future), normalize_iCalStr(data_future2) % relsubs, "Failed future: %s\n%s" % (title, diff_iCalStrs(ical_future, data_future2 % relsubs),))
+ self.assertEqual(normalize_iCalStr(ical_past), normalize_iCalStr(data_past2) % relsubs, "Failed past: %s\n%s" % (title, diff_iCalStrs(ical_past, data_past2 % relsubs),))
+ self.assertEqual(normalize_iCalStr(ical_inbox), normalize_iCalStr(data_inbox2) % relsubs, "Failed past: %s\n%s" % (title, diff_iCalStrs(ical_inbox, data_inbox2 % relsubs),))
+
+ # Verify cuser02 data
+ self.assertEqual(len(details), 2)
+ self.assertEqual(details[0][0], "urn:uuid:user01")
+ self.assertEqual(details[0][1], ("mailto:cuser01 at example.org",))
+ self.assertEqual(normalize_iCalStr(details[0][2]), normalize_iCalStr(data_future_external) % relsubs, "Failed future: %s\n%s" % (title, diff_iCalStrs(details[0][2], data_future_external % relsubs),))
+
+ self.assertEqual(details[1][0], "urn:uuid:user01")
+ self.assertEqual(details[1][1], ("mailto:cuser01 at example.org",))
+ self.assertEqual(normalize_iCalStr(details[1][2]), normalize_iCalStr(data_past_external) % relsubs, "Failed past: %s\n%s" % (title, diff_iCalStrs(details[1][2], data_past_external % relsubs),))
Modified: CalendarServer/trunk/txdav/caldav/icalendarstore.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/icalendarstore.py 2013-08-05 17:51:24 UTC (rev 11578)
+++ CalendarServer/trunk/txdav/caldav/icalendarstore.py 2013-08-05 17:55:30 UTC (rev 11579)
@@ -860,9 +860,11 @@
ATTACHMENT_UPDATE - change to a managed attachment that is re-writing calendar data.
- SPLIT - calendar data is being split. Some validation and implicit scheduling is not done.
- Schedule-Tag is changed.
+ SPLIT_OWNER - owner calendar data is being split. Implicit is done with non-hosted attendees.
+ SPLIT_ATTENDEE - attendee calendar data is being split. No implicit done, but some extra processing
+ is done (more than RAW).
+
RAW - store the supplied data as-is without any processing or validation. This is used
for unit testing purposes only.
"""
@@ -872,7 +874,8 @@
ORGANIZER_ITIP_UPDATE = NamedConstant()
ATTENDEE_ITIP_UPDATE = NamedConstant()
ATTACHMENT_UPDATE = NamedConstant()
- SPLIT = NamedConstant()
+ SPLIT_OWNER = NamedConstant()
+ SPLIT_ATTENDEE = NamedConstant()
RAW = NamedConstant()
NORMAL.description = "normal"
@@ -880,7 +883,8 @@
ORGANIZER_ITIP_UPDATE.description = "organizer-update"
ATTENDEE_ITIP_UPDATE.description = "attendee-update"
ATTACHMENT_UPDATE.description = "attachment-update"
- SPLIT.description = "split"
+ SPLIT_OWNER.description = "split-owner"
+ SPLIT_ATTENDEE.description = "split-attendee"
RAW.description = "raw"
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20130805/bae8a24c/attachment-0001.html>
More information about the calendarserver-changes
mailing list