<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[13591] CalendarServer/branches/release/CalendarServer-5.3-dev</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://trac.calendarserver.org//changeset/13591">13591</a></dd>
<dt>Author</dt> <dd>cdaboo@apple.com</dd>
<dt>Date</dt> <dd>2014-06-01 19:47:48 -0700 (Sun, 01 Jun 2014)</dd>
</dl>

<h3>Log Message</h3>
<pre>Merge schedule-agent fix from trunk.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#CalendarServerbranchesreleaseCalendarServer53devtwistedcaldavicalpy">CalendarServer/branches/release/CalendarServer-5.3-dev/twistedcaldav/ical.py</a></li>
<li><a href="#CalendarServerbranchesreleaseCalendarServer53devtwistedcaldavtesttest_icalendarpy">CalendarServer/branches/release/CalendarServer-5.3-dev/twistedcaldav/test/test_icalendar.py</a></li>
<li><a href="#CalendarServerbranchesreleaseCalendarServer53devtxdavcaldavdatastoreschedulingcuaddresspy">CalendarServer/branches/release/CalendarServer-5.3-dev/txdav/caldav/datastore/scheduling/cuaddress.py</a></li>
<li><a href="#CalendarServerbranchesreleaseCalendarServer53devtxdavcaldavdatastoreschedulingimplicitpy">CalendarServer/branches/release/CalendarServer-5.3-dev/txdav/caldav/datastore/scheduling/implicit.py</a></li>
<li><a href="#CalendarServerbranchesreleaseCalendarServer53devtxdavcaldavdatastoreschedulingprocessingpy">CalendarServer/branches/release/CalendarServer-5.3-dev/txdav/caldav/datastore/scheduling/processing.py</a></li>
<li><a href="#CalendarServerbranchesreleaseCalendarServer53devtxdavcaldavdatastoreschedulingtesttest_implicitpy">CalendarServer/branches/release/CalendarServer-5.3-dev/txdav/caldav/datastore/scheduling/test/test_implicit.py</a></li>
</ul>

<h3>Added Paths</h3>
<ul>
<li><a href="#CalendarServerbranchesreleaseCalendarServer53devtxdavcaldavdatastoreschedulingtesttest_processingpy">CalendarServer/branches/release/CalendarServer-5.3-dev/txdav/caldav/datastore/scheduling/test/test_processing.py</a></li>
</ul>

<h3>Removed Paths</h3>
<ul>
<li><a href="#CalendarServerbranchesreleaseCalendarServer53devtxdavcaldavdatastoreschedulingtesttest_pocessingpy">CalendarServer/branches/release/CalendarServer-5.3-dev/txdav/caldav/datastore/scheduling/test/test_pocessing.py</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="CalendarServerbranchesreleaseCalendarServer53devtwistedcaldavicalpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/release/CalendarServer-5.3-dev/twistedcaldav/ical.py (13590 => 13591)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/release/CalendarServer-5.3-dev/twistedcaldav/ical.py        2014-06-02 02:10:46 UTC (rev 13590)
+++ CalendarServer/branches/release/CalendarServer-5.3-dev/twistedcaldav/ical.py        2014-06-02 02:47:48 UTC (rev 13591)
</span><span class="lines">@@ -2156,6 +2156,26 @@
</span><span class="cx">         return is_server
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def cleanOrganizerScheduleAgent(self):
+        &quot;&quot;&quot;
+        Remove components whose ORGANIZER property does not have
+        SCHEDULE-AGENT=SERVER.
+        &quot;&quot;&quot;
+
+        changed = False
+        for component in tuple(self.subcomponents()):
+            if component.name() in ignoredComponents:
+                continue
+
+            organizerProp = component.getOrganizerProperty()
+            if organizerProp is not None:
+                if organizerProp.parameterValue(&quot;SCHEDULE-AGENT&quot;, &quot;SERVER&quot;) != &quot;SERVER&quot;:
+                    self.removeComponent(component)
+                    changed = True
+
+        return changed
+
+
</ins><span class="cx">     def getAttendees(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Get the attendee value. Works on either a VCALENDAR or on a component.
</span></span></pre></div>
<a id="CalendarServerbranchesreleaseCalendarServer53devtwistedcaldavtesttest_icalendarpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/release/CalendarServer-5.3-dev/twistedcaldav/test/test_icalendar.py (13590 => 13591)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/release/CalendarServer-5.3-dev/twistedcaldav/test/test_icalendar.py        2014-06-02 02:10:46 UTC (rev 13590)
+++ CalendarServer/branches/release/CalendarServer-5.3-dev/twistedcaldav/test/test_icalendar.py        2014-06-02 02:47:48 UTC (rev 13591)
</span><span class="lines">@@ -10429,3 +10429,163 @@
</span><span class="cx">             changed = component.hasDuplicatePrivateComments(doFix=True)
</span><span class="cx">             self.assertEqual(sorted(normalize_iCalStr(component).splitlines()), sorted(normalize_iCalStr(result).splitlines()), msg=title)
</span><span class="cx">             self.assertEqual(changed, result_changed, msg=title)
</span><ins>+
+
+    def test_cleanOrganizerScheduleAgent(self):
+        &quot;&quot;&quot;
+        Test that L{Component.cleanOrganizerScheduleAgent} correctly removes components.
+        &quot;&quot;&quot;
+
+        data = (
+            (
+                &quot;All SERVER - master only&quot;,
+                &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DURATION:PT1H
+DTSTAMP:20080601T120000Z
+ORGANIZER:mailto:user01@example.com
+ATTENDEE:mailto:user01@example.com
+ATTENDEE:mailto:user02@example.com
+RRULE:FREQ=DAILY
+SUMMARY:Test
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;,
+                &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DURATION:PT1H
+DTSTAMP:20080601T120000Z
+ORGANIZER:mailto:user01@example.com
+ATTENDEE:mailto:user01@example.com
+ATTENDEE:mailto:user02@example.com
+RRULE:FREQ=DAILY
+SUMMARY:Test
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;,
+                False,
+            ),
+            (
+                &quot;All SERVER - master and overrides&quot;,
+                &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DURATION:PT1H
+DTSTAMP:20080601T120000Z
+ORGANIZER:mailto:user01@example.com
+ATTENDEE:mailto:user01@example.com
+ATTENDEE:mailto:user02@example.com
+RRULE:FREQ=DAILY
+SUMMARY:Test
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T120000Z
+DURATION:PT1H
+DTSTAMP:20080601T120000Z
+ORGANIZER:mailto:user01@example.com
+ATTENDEE:mailto:user01@example.com
+ATTENDEE:mailto:user02@example.com
+RRULE:FREQ=DAILY
+SUMMARY:Test
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;,
+                &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DURATION:PT1H
+DTSTAMP:20080601T120000Z
+ORGANIZER:mailto:user01@example.com
+ATTENDEE:mailto:user01@example.com
+ATTENDEE:mailto:user02@example.com
+RRULE:FREQ=DAILY
+SUMMARY:Test
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T120000Z
+DURATION:PT1H
+DTSTAMP:20080601T120000Z
+ORGANIZER:mailto:user01@example.com
+ATTENDEE:mailto:user01@example.com
+ATTENDEE:mailto:user02@example.com
+RRULE:FREQ=DAILY
+SUMMARY:Test
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;,
+                False,
+            ),
+            (
+                &quot;Master CLIENT and override SERVER&quot;,
+                &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DURATION:PT1H
+DTSTAMP:20080601T120000Z
+ORGANIZER;SCHEDULE-AGENT=CLIENT:mailto:user01@example.com
+ATTENDEE:mailto:user01@example.com
+ATTENDEE:mailto:user02@example.com
+RRULE:FREQ=DAILY
+SUMMARY:Test
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T120000Z
+DURATION:PT1H
+DTSTAMP:20080601T120000Z
+ORGANIZER:mailto:user01@example.com
+ATTENDEE:mailto:user01@example.com
+ATTENDEE:mailto:user02@example.com
+RRULE:FREQ=DAILY
+SUMMARY:Test
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;,
+                &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T120000Z
+DURATION:PT1H
+DTSTAMP:20080601T120000Z
+ORGANIZER:mailto:user01@example.com
+ATTENDEE:mailto:user01@example.com
+ATTENDEE:mailto:user02@example.com
+RRULE:FREQ=DAILY
+SUMMARY:Test
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;,
+                True,
+            ),
+        )
+
+        for title, txt, result, result_changed in data:
+            component = Component.fromString(txt)
+            changed = component.cleanOrganizerScheduleAgent()
+            self.assertEqual(sorted(normalize_iCalStr(component).splitlines()), sorted(normalize_iCalStr(result).splitlines()), msg=title)
+            self.assertEqual(changed, result_changed, msg=title)
</ins></span></pre></div>
<a id="CalendarServerbranchesreleaseCalendarServer53devtxdavcaldavdatastoreschedulingcuaddresspy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/release/CalendarServer-5.3-dev/txdav/caldav/datastore/scheduling/cuaddress.py (13590 => 13591)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/release/CalendarServer-5.3-dev/txdav/caldav/datastore/scheduling/cuaddress.py        2014-06-02 02:10:46 UTC (rev 13590)
+++ CalendarServer/branches/release/CalendarServer-5.3-dev/txdav/caldav/datastore/scheduling/cuaddress.py        2014-06-02 02:47:48 UTC (rev 13591)
</span><span class="lines">@@ -38,7 +38,14 @@
</span><span class="cx">         self.serviceType = None
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def hosted(self):
+        &quot;&quot;&quot;
+        Is this user hosted on this service (this pod or any other)
+        &quot;&quot;&quot;
+        return False
</ins><span class="cx"> 
</span><ins>+
+
</ins><span class="cx"> class LocalCalendarUser(CalendarUser):
</span><span class="cx"> 
</span><span class="cx">     def __init__(self, cuaddr, principal, inbox=None):
</span><span class="lines">@@ -52,7 +59,14 @@
</span><span class="cx">         return &quot;Local calendar user: %s&quot; % (self.cuaddr,)
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def hosted(self):
+        &quot;&quot;&quot;
+        Is this user hosted on this service (this pod or any other)
+        &quot;&quot;&quot;
+        return True
</ins><span class="cx"> 
</span><ins>+
+
</ins><span class="cx"> class PartitionedCalendarUser(CalendarUser):
</span><span class="cx"> 
</span><span class="cx">     def __init__(self, cuaddr, principal):
</span><span class="lines">@@ -65,7 +79,14 @@
</span><span class="cx">         return &quot;Partitioned calendar user: %s&quot; % (self.cuaddr,)
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def hosted(self):
+        &quot;&quot;&quot;
+        Is this user hosted on this service (this pod or any other)
+        &quot;&quot;&quot;
+        return True
</ins><span class="cx"> 
</span><ins>+
+
</ins><span class="cx"> class OtherServerCalendarUser(CalendarUser):
</span><span class="cx"> 
</span><span class="cx">     def __init__(self, cuaddr, principal):
</span><span class="lines">@@ -78,7 +99,14 @@
</span><span class="cx">         return &quot;Other server calendar user: %s&quot; % (self.cuaddr,)
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def hosted(self):
+        &quot;&quot;&quot;
+        Is this user hosted on this service (this pod or any other)
+        &quot;&quot;&quot;
+        return True
</ins><span class="cx"> 
</span><ins>+
+
</ins><span class="cx"> class RemoteCalendarUser(CalendarUser):
</span><span class="cx"> 
</span><span class="cx">     def __init__(self, cuaddr):
</span></span></pre></div>
<a id="CalendarServerbranchesreleaseCalendarServer53devtxdavcaldavdatastoreschedulingimplicitpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/release/CalendarServer-5.3-dev/txdav/caldav/datastore/scheduling/implicit.py (13590 => 13591)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/release/CalendarServer-5.3-dev/txdav/caldav/datastore/scheduling/implicit.py        2014-06-02 02:10:46 UTC (rev 13590)
+++ CalendarServer/branches/release/CalendarServer-5.3-dev/txdav/caldav/datastore/scheduling/implicit.py        2014-06-02 02:47:48 UTC (rev 13591)
</span><span class="lines">@@ -141,6 +141,7 @@
</span><span class="cx"> 
</span><span class="cx">             # If the new data has no organizer, then there must also be no attendees
</span><span class="cx">             if self.organizer is None and self.attendees:
</span><ins>+                log.error(&quot;organizer-allowed: Organizer removal also requires attendees to be removed for UID: %s&quot; % (self.uid,))
</ins><span class="cx">                 raise HTTPError(ErrorResponse(
</span><span class="cx">                     responsecode.FORBIDDEN,
</span><span class="cx">                     (caldav_namespace, &quot;organizer-allowed&quot;),
</span><span class="lines">@@ -163,6 +164,11 @@
</span><span class="cx">             (existing_type != new_type) and
</span><span class="cx">             existing_resource
</span><span class="cx">         ):
</span><ins>+            log.error(&quot;valid-attendee-change: Cannot change scheduling object mode for %s to %s for UID: %s&quot; % (
+                existing_type,
+                new_type,
+                self.uid
+            ))
</ins><span class="cx">             raise HTTPError(ErrorResponse(
</span><span class="cx">                 responsecode.FORBIDDEN,
</span><span class="cx">                 (caldav_namespace, &quot;valid-attendee-change&quot;),
</span><span class="lines">@@ -171,6 +177,7 @@
</span><span class="cx"> 
</span><span class="cx">         # Organizer events must have a master component
</span><span class="cx">         if self.state == &quot;organizer&quot; and self.calendar.masterComponent() is None:
</span><ins>+            log.error(&quot;organizer-allowed: Organizer cannot schedule without a master component for UID: %s&quot; % (self.uid,))
</ins><span class="cx">             raise HTTPError(ErrorResponse(
</span><span class="cx">                 responsecode.FORBIDDEN,
</span><span class="cx">                 (caldav_namespace, &quot;organizer-allowed&quot;),
</span><span class="lines">@@ -251,7 +258,7 @@
</span><span class="cx">         # to create new scheduling resources.
</span><span class="cx">         if self.action == &quot;create&quot;:
</span><span class="cx">             if self.organizerPrincipal and not self.organizerPrincipal.enabledAsOrganizer():
</span><del>-                log.error(&quot;ORGANIZER not allowed to be an Organizer: %s&quot; % (self.organizer,))
</del><ins>+                log.error(&quot;organizer-allowed: ORGANIZER not allowed to be an Organizer: %s&quot; % (self.organizer,))
</ins><span class="cx">                 raise HTTPError(ErrorResponse(
</span><span class="cx">                     responsecode.FORBIDDEN,
</span><span class="cx">                     (caldav_namespace, &quot;organizer-allowed&quot;),
</span><span class="lines">@@ -427,7 +434,7 @@
</span><span class="cx">             self.organizer = self.calendar.validOrganizerForScheduling()
</span><span class="cx">         except ValueError:
</span><span class="cx">             # We have different ORGANIZERs in the same iCalendar object - this is an error
</span><del>-            log.error(&quot;Only one ORGANIZER is allowed in an iCalendar object:\n%s&quot; % (self.calendar,))
</del><ins>+            log.error(&quot;single-organizer: Only one ORGANIZER is allowed in an iCalendar object:\n%s&quot; % (self.calendar,))
</ins><span class="cx">             raise HTTPError(ErrorResponse(
</span><span class="cx">                 responsecode.FORBIDDEN,
</span><span class="cx">                 (caldav_namespace, &quot;single-organizer&quot;),
</span><span class="lines">@@ -715,7 +722,7 @@
</span><span class="cx">                 oldOrganizer = _normalizeCUAddress(self.oldcalendar.getOrganizer())
</span><span class="cx">                 newOrganizer = _normalizeCUAddress(self.calendar.getOrganizer())
</span><span class="cx">                 if oldOrganizer != newOrganizer:
</span><del>-                    log.error(&quot;Cannot change ORGANIZER: UID:%s&quot; % (self.uid,))
</del><ins>+                    log.error(&quot;valid-organizer-change: Cannot change ORGANIZER: UID:%s&quot; % (self.uid,))
</ins><span class="cx">                     raise HTTPError(ErrorResponse(
</span><span class="cx">                         responsecode.FORBIDDEN,
</span><span class="cx">                         (caldav_namespace, &quot;valid-organizer-change&quot;),
</span><span class="lines">@@ -1118,7 +1125,7 @@
</span><span class="cx">                 oldOrganizer = self.oldcalendar.getOrganizer()
</span><span class="cx">                 newOrganizer = self.calendar.getOrganizer()
</span><span class="cx">                 if oldOrganizer != newOrganizer:
</span><del>-                    log.error(&quot;Cannot change ORGANIZER: UID:%s&quot; % (self.uid,))
</del><ins>+                    log.error(&quot;valid-attendee-change: Cannot change ORGANIZER: UID:%s&quot; % (self.uid,))
</ins><span class="cx">                     raise HTTPError(ErrorResponse(
</span><span class="cx">                         responsecode.FORBIDDEN,
</span><span class="cx">                         (caldav_namespace, &quot;valid-attendee-change&quot;),
</span><span class="lines">@@ -1133,13 +1140,24 @@
</span><span class="cx"> 
</span><span class="cx">                 # If Organizer copy exists we cannot allow SCHEDULE-AGENT=CLIENT or NONE
</span><span class="cx">                 if not doScheduling:
</span><del>-                    log.error(&quot;Attendee '%s' is not allowed to change SCHEDULE-AGENT on organizer: UID:%s&quot; % (self.attendeePrincipal, self.uid,))
-                    raise HTTPError(ErrorResponse(
-                        responsecode.FORBIDDEN,
-                        (caldav_namespace, &quot;valid-attendee-change&quot;),
-                        &quot;Cannot alter organizer&quot;,
-                    ))
</del><ins>+                    # If an existing resource is present and it does not have SCHEDULE-AGENT=SERVER, then
+                    # try and fix the situation by using the organizer's copy of the event and stripping
+                    # the incoming attendee copy of any SCHEDULE-AGENT=CLIENT components. That should allow
+                    # a fixed version of the data to be stored and proper scheduling to occur.
+                    if self.oldcalendar is not None and not self.oldcalendar.getOrganizerScheduleAgent():
+                        self.oldcalendar = self.organizer_calendar.duplicate()
+                        self.oldcalendar.attendeesView((self.attendee,), onlyScheduleAgentServer=True)
+                        self.calendar.cleanOrganizerScheduleAgent()
+                        doScheduling = True
</ins><span class="cx"> 
</span><ins>+                    if not doScheduling:
+                        log.error(&quot;valid-attendee-change: Attendee '%s' is not allowed to change SCHEDULE-AGENT on organizer: UID:%s&quot; % (self.attendeePrincipal, self.uid,))
+                        raise HTTPError(ErrorResponse(
+                            responsecode.FORBIDDEN,
+                            (caldav_namespace, &quot;valid-attendee-change&quot;),
+                            &quot;Cannot alter organizer&quot;,
+                        ))
+
</ins><span class="cx">                 # Determine whether the current change is allowed
</span><span class="cx">                 changeAllowed, doITipReply, changedRids, newCalendar = self.isAttendeeChangeInsignificant()
</span><span class="cx">                 if changeAllowed:
</span><span class="lines">@@ -1151,7 +1169,7 @@
</span><span class="cx">                         self.return_status = ImplicitScheduler.STATUS_ORPHANED_EVENT
</span><span class="cx">                         returnValue(None)
</span><span class="cx">                     else:
</span><del>-                        log.error(&quot;Attendee '%s' is not allowed to make an unauthorized change to an organized event: UID:%s&quot; % (self.attendeePrincipal, self.uid,))
</del><ins>+                        log.error(&quot;valid-attendee-change: Attendee '%s' is not allowed to make an unauthorized change to an organized event: UID:%s&quot; % (self.attendeePrincipal, self.uid,))
</ins><span class="cx">                         raise HTTPError(ErrorResponse(
</span><span class="cx">                             responsecode.FORBIDDEN,
</span><span class="cx">                             (caldav_namespace, &quot;valid-attendee-change&quot;),
</span><span class="lines">@@ -1183,7 +1201,7 @@
</span><span class="cx">                         if self.oldcalendar:
</span><span class="cx">                             oldScheduling = self.oldcalendar.getOrganizerScheduleAgent()
</span><span class="cx">                             if not oldScheduling:
</span><del>-                                log.error(&quot;Attendee '%s' is not allowed to set SCHEDULE-AGENT=SERVER on organizer: UID:%s&quot; % (self.attendeePrincipal, self.uid,))
</del><ins>+                                log.error(&quot;valid-attendee-change: Attendee '%s' is not allowed to set SCHEDULE-AGENT=SERVER on organizer: UID:%s&quot; % (self.attendeePrincipal, self.uid,))
</ins><span class="cx">                                 raise HTTPError(ErrorResponse(
</span><span class="cx">                                     responsecode.FORBIDDEN,
</span><span class="cx">                                     (caldav_namespace, &quot;valid-attendee-change&quot;),
</span><span class="lines">@@ -1240,7 +1258,7 @@
</span><span class="cx">                 oldOrganizer = self.oldcalendar.getOrganizer()
</span><span class="cx">                 newOrganizer = self.calendar.getOrganizer()
</span><span class="cx">                 if oldOrganizer != newOrganizer and self.oldcalendar.getOrganizerScheduleAgent():
</span><del>-                    log.error(&quot;Cannot change ORGANIZER: UID:%s&quot; % (self.uid,))
</del><ins>+                    log.error(&quot;valid-attendee-change: Cannot change ORGANIZER: UID:%s&quot; % (self.uid,))
</ins><span class="cx">                     raise HTTPError(ErrorResponse(
</span><span class="cx">                         responsecode.FORBIDDEN,
</span><span class="cx">                         (caldav_namespace, &quot;valid-attendee-change&quot;),
</span><span class="lines">@@ -1260,7 +1278,7 @@
</span><span class="cx">                             break
</span><span class="cx"> 
</span><span class="cx">                     if found_old:
</span><del>-                        log.error(&quot;Cannot remove ATTENDEE: UID:%s&quot; % (self.uid,))
</del><ins>+                        log.error(&quot;valid-attendee-change: Cannot remove ATTENDEE: UID:%s&quot; % (self.uid,))
</ins><span class="cx">                         raise HTTPError(ErrorResponse(
</span><span class="cx">                             responsecode.FORBIDDEN,
</span><span class="cx">                             (caldav_namespace, &quot;valid-attendee-change&quot;),
</span></span></pre></div>
<a id="CalendarServerbranchesreleaseCalendarServer53devtxdavcaldavdatastoreschedulingprocessingpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/release/CalendarServer-5.3-dev/txdav/caldav/datastore/scheduling/processing.py (13590 => 13591)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/release/CalendarServer-5.3-dev/txdav/caldav/datastore/scheduling/processing.py        2014-06-02 02:10:46 UTC (rev 13590)
+++ CalendarServer/branches/release/CalendarServer-5.3-dev/txdav/caldav/datastore/scheduling/processing.py        2014-06-02 02:47:48 UTC (rev 13591)
</span><span class="lines">@@ -460,8 +460,11 @@
</span><span class="cx">             new_organizer = normalizeCUAddr(self.message.getOrganizer())
</span><span class="cx">             new_organizer = normalizeCUAddr(new_organizer) if new_organizer else &quot;&quot;
</span><span class="cx">             if existing_organizer != new_organizer:
</span><del>-                log.debug(&quot;ImplicitProcessing - originator '%s' to recipient '%s' ignoring UID: '%s' - organizer has no copy&quot; % (self.originator.cuaddr, self.recipient.cuaddr, self.uid))
-                raise ImplicitProcessorException(&quot;5.3;Organizer change not allowed&quot;)
</del><ins>+                # Additional check - if the existing organizer is missing and the originator
+                # is local to the server - then allow the change
+                if not (existing_organizer == &quot;&quot; and self.originator.hosted()):
+                    log.debug(&quot;ImplicitProcessing - originator '%s' to recipient '%s' ignoring UID: '%s' - organizer has no copy&quot; % (self.originator.cuaddr, self.recipient.cuaddr, self.uid))
+                    raise ImplicitProcessorException(&quot;5.3;Organizer change not allowed&quot;)
</ins><span class="cx"> 
</span><span class="cx">         # Handle splitting of data early so we can preserve per-attendee data
</span><span class="cx">         if self.message.hasProperty(&quot;X-CALENDARSERVER-SPLIT-OLDER-UID&quot;):
</span></span></pre></div>
<a id="CalendarServerbranchesreleaseCalendarServer53devtxdavcaldavdatastoreschedulingtesttest_implicitpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/release/CalendarServer-5.3-dev/txdav/caldav/datastore/scheduling/test/test_implicit.py (13590 => 13591)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/release/CalendarServer-5.3-dev/txdav/caldav/datastore/scheduling/test/test_implicit.py        2014-06-02 02:10:46 UTC (rev 13590)
+++ CalendarServer/branches/release/CalendarServer-5.3-dev/txdav/caldav/datastore/scheduling/test/test_implicit.py        2014-06-02 02:47:48 UTC (rev 13591)
</span><span class="lines">@@ -1624,3 +1624,324 @@
</span><span class="cx">         self.assertEqual(len(list2), 2)
</span><span class="cx">         self.assertTrue(list2[0].startswith(hashlib.md5(&quot;12345-67890&quot;).hexdigest()))
</span><span class="cx">         self.assertTrue(list2[1].startswith(hashlib.md5(&quot;12345-67890&quot;).hexdigest()))
</span><ins>+
+
+
+class ScheduleAgentFixBase(CommonCommonTests, TestCase):
+    &quot;&quot;&quot;
+    Test txdav.caldav.datastore.scheduling.implicit.
+    &quot;&quot;&quot;
+
+    @inlineCallbacks
+    def setUp(self):
+        yield super(ScheduleAgentFixBase, self).setUp()
+        self._sqlCalendarStore = yield buildCalendarStore(self, self.notifierFactory)
+        yield self.populate()
+        self.patch(config.Scheduling.Options, &quot;AttendeeRefreshBatch&quot;, 0)
+
+
+    @inlineCallbacks
+    def populate(self):
+        yield populateCalendarsFrom(self.requirements, self.storeUnderTest())
+        self.notifierFactory.reset()
+
+
+    def storeUnderTest(self):
+        &quot;&quot;&quot;
+        Create and return a L{CalendarStore} for testing.
+        &quot;&quot;&quot;
+        return self._sqlCalendarStore
+
+    metadata = {
+        &quot;accessMode&quot;: &quot;PUBLIC&quot;,
+        &quot;isScheduleObject&quot;: True,
+        &quot;scheduleTag&quot;: &quot;abc&quot;,
+        &quot;scheduleEtags&quot;: (),
+        &quot;hasPrivateComment&quot;: False,
+    }
+
+    @classproperty(cache=False)
+    def requirements(cls): #@NoSelf
+        return {
+        &quot;user01&quot;: {
+            &quot;calendar_1&quot;: {
+                &quot;organizer.ics&quot;: (cls.organizer_data, cls.metadata),
+            },
+            &quot;inbox&quot;: {
+            },
+        },
+        &quot;user02&quot;: {
+            &quot;calendar_1&quot;: {
+                &quot;attendee2.ics&quot;: (cls.attendee2_data, cls.metadata),
+            },
+            &quot;inbox&quot;: {
+            },
+        },
+        &quot;user03&quot;: {
+            &quot;calendar_1&quot;: {
+                &quot;attendee3.ics&quot;: (cls.attendee3_data, cls.metadata),
+            },
+            &quot;inbox&quot;: {
+            },
+        },
+    }
+
+
+
+class ScheduleAgentFix(ScheduleAgentFixBase):
+    &quot;&quot;&quot;
+    Test that implicit scheduling where an attendee has S-A=CLIENT and S-A=SERVER is
+    corrected when the attendee updates.
+    &quot;&quot;&quot;
+
+    organizer_data = &quot;&quot;&quot;BEGIN:VCALENDAR
+CALSCALE:GREGORIAN
+PRODID:-//Example Inc.//Example Calendar//EN
+VERSION:2.0
+BEGIN:VEVENT
+DTSTAMP:20051222T205953Z
+CREATED:20060101T150000Z
+DTSTART:20140101T100000Z
+DURATION:PT1H
+SUMMARY:event 1
+UID:event1@ninevah.local
+ORGANIZER:urn:uuid:user01
+ATTENDEE:urn:uuid:user01
+ATTENDEE:urn:uuid:user03
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+DTSTAMP:20051222T205953Z
+CREATED:20060101T150000Z
+RECURRENCE-ID:20140102T100000Z
+DTSTART:20140102T100000Z
+DURATION:PT1H
+SUMMARY:event 1
+UID:event1@ninevah.local
+ORGANIZER:urn:uuid:user01
+ATTENDEE:urn:uuid:user01
+ATTENDEE:urn:uuid:user02
+ATTENDEE:urn:uuid:user03
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;
+
+    attendee2_data = &quot;&quot;&quot;BEGIN:VCALENDAR
+CALSCALE:GREGORIAN
+PRODID:-//Example Inc.//Example Calendar//EN
+VERSION:2.0
+BEGIN:VEVENT
+DTSTAMP:20051222T205953Z
+CREATED:20060101T150000Z
+DTSTART:20140101T100000Z
+DURATION:PT1H
+SUMMARY:event 1
+UID:event1@ninevah.local
+ORGANIZER;SCHEDULE-AGENT=CLIENT:urn:uuid:user01
+ATTENDEE:urn:uuid:user01
+ATTENDEE:urn:uuid:user03
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+DTSTAMP:20051222T205953Z
+CREATED:20060101T150000Z
+RECURRENCE-ID:20140102T100000Z
+DTSTART:20140102T100000Z
+DURATION:PT1H
+SUMMARY:event 1
+UID:event1@ninevah.local
+ORGANIZER;SCHEDULE-AGENT=SERVER:urn:uuid:user01
+ATTENDEE:urn:uuid:user01
+ATTENDEE:urn:uuid:user02
+ATTENDEE:urn:uuid:user03
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;
+
+    attendee2_update_data = &quot;&quot;&quot;BEGIN:VCALENDAR
+CALSCALE:GREGORIAN
+PRODID:-//Example Inc.//Example Calendar//EN
+VERSION:2.0
+BEGIN:VEVENT
+DTSTAMP:20051222T205953Z
+CREATED:20060101T150000Z
+DTSTART:20140101T100000Z
+DURATION:PT1H
+SUMMARY:event 1
+UID:event1@ninevah.local
+ORGANIZER;SCHEDULE-AGENT=CLIENT:urn:uuid:user01
+ATTENDEE:urn:uuid:user01
+ATTENDEE:urn:uuid:user03
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+DTSTAMP:20051222T205953Z
+CREATED:20060101T150000Z
+RECURRENCE-ID:20140102T100000Z
+DTSTART:20140102T100000Z
+DURATION:PT1H
+SUMMARY:event 1
+UID:event1@ninevah.local
+ORGANIZER;SCHEDULE-AGENT=SERVER:urn:uuid:user01
+ATTENDEE:urn:uuid:user01
+ATTENDEE;PARTSTAT=ACCEPTED:urn:uuid:user02
+ATTENDEE:urn:uuid:user03
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;
+
+    attendee3_data = &quot;&quot;&quot;BEGIN:VCALENDAR
+CALSCALE:GREGORIAN
+PRODID:-//Example Inc.//Example Calendar//EN
+VERSION:2.0
+BEGIN:VEVENT
+DTSTAMP:20051222T205953Z
+CREATED:20060101T150000Z
+DTSTART:20140101T100000Z
+DURATION:PT1H
+SUMMARY:event 1
+UID:event1@ninevah.local
+ORGANIZER:urn:uuid:user01
+ATTENDEE:urn:uuid:user01
+ATTENDEE:urn:uuid:user03
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+DTSTAMP:20051222T205953Z
+CREATED:20060101T150000Z
+RECURRENCE-ID:20140102T100000Z
+DTSTART:20140102T100000Z
+DURATION:PT1H
+SUMMARY:event 1
+UID:event1@ninevah.local
+ORGANIZER:urn:uuid:user01
+ATTENDEE:urn:uuid:user01
+ATTENDEE:urn:uuid:user02
+ATTENDEE:urn:uuid:user03
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;
+
+
+    @inlineCallbacks
+    def test_doImplicitScheduling(self):
+        &quot;&quot;&quot;
+        Test that doImplicitScheduling fixes an inconsistent schedule-agent state when an
+        attendee stores their data.
+        &quot;&quot;&quot;
+
+        cobj = yield self.calendarObjectUnderTest(home=&quot;user02&quot;, name=&quot;attendee2.ics&quot;)
+        yield cobj.setComponent(Component.fromString(self.attendee2_update_data))
+        yield self.commit()
+
+        cobj = yield self.calendarObjectUnderTest(home=&quot;user02&quot;, name=&quot;attendee2.ics&quot;)
+        comp = yield cobj.component()
+        self.assertTrue(comp.masterComponent() is None)
+        self.assertTrue(comp.getOrganizerScheduleAgent())
+
+        inbox = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;inbox&quot;)
+        cobjs = yield inbox.calendarObjects()
+        self.assertTrue(len(cobjs) == 1)
+
+
+
+class MissingOrganizerFix(ScheduleAgentFixBase):
+    &quot;&quot;&quot;
+    Test that an attendee with a copy of an event without any organizer or attendee
+    properties is corrected when the organizer updates.
+    &quot;&quot;&quot;
+
+    organizer_data = &quot;&quot;&quot;BEGIN:VCALENDAR
+CALSCALE:GREGORIAN
+PRODID:-//Example Inc.//Example Calendar//EN
+VERSION:2.0
+BEGIN:VEVENT
+DTSTAMP:20051222T205953Z
+CREATED:20060101T150000Z
+DTSTART:20140101T100000Z
+DURATION:PT1H
+SUMMARY:event 1
+UID:event1@ninevah.local
+ORGANIZER:urn:uuid:user01
+ATTENDEE:urn:uuid:user01
+ATTENDEE:urn:uuid:user03
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;
+
+    organizer_update_data = &quot;&quot;&quot;BEGIN:VCALENDAR
+CALSCALE:GREGORIAN
+PRODID:-//Example Inc.//Example Calendar//EN
+VERSION:2.0
+BEGIN:VEVENT
+DTSTAMP:20051222T205953Z
+CREATED:20060101T150000Z
+DTSTART:20140101T100000Z
+DURATION:PT1H
+SUMMARY:event 1
+UID:event1@ninevah.local
+ORGANIZER:urn:uuid:user01
+ATTENDEE:urn:uuid:user01
+ATTENDEE:urn:uuid:user02
+ATTENDEE:urn:uuid:user03
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;
+
+    attendee2_data = &quot;&quot;&quot;BEGIN:VCALENDAR
+CALSCALE:GREGORIAN
+PRODID:-//Example Inc.//Example Calendar//EN
+VERSION:2.0
+BEGIN:VEVENT
+DTSTAMP:20051222T205953Z
+CREATED:20060101T150000Z
+DTSTART:20140101T100000Z
+DURATION:PT1H
+SUMMARY:event 1
+UID:event1@ninevah.local
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;
+
+    attendee3_data = &quot;&quot;&quot;BEGIN:VCALENDAR
+CALSCALE:GREGORIAN
+PRODID:-//Example Inc.//Example Calendar//EN
+VERSION:2.0
+BEGIN:VEVENT
+DTSTAMP:20051222T205953Z
+CREATED:20060101T150000Z
+DTSTART:20140101T100000Z
+DURATION:PT1H
+SUMMARY:event 1
+UID:event1@ninevah.local
+ORGANIZER:urn:uuid:user01
+ATTENDEE:urn:uuid:user01
+ATTENDEE:urn:uuid:user03
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;
+
+
+    @inlineCallbacks
+    def test_doImplicitScheduling(self):
+        &quot;&quot;&quot;
+        Test that doImplicitScheduling fixes an inconsistent schedule-agent state when an
+        attendee stores their data.
+        &quot;&quot;&quot;
+
+        cobj = yield self.calendarObjectUnderTest(home=&quot;user02&quot;, name=&quot;attendee2.ics&quot;)
+        comp = yield cobj.component()
+        self.assertTrue(comp.getOrganizer() is None)
+        yield self.commit()
+
+        cobj = yield self.calendarObjectUnderTest(home=&quot;user01&quot;, name=&quot;organizer.ics&quot;)
+        yield cobj.setComponent(Component.fromString(self.organizer_update_data))
+        yield self.commit()
+
+        cobj = yield self.calendarObjectUnderTest(home=&quot;user02&quot;, name=&quot;attendee2.ics&quot;)
+        comp = yield cobj.component()
+        self.assertTrue(comp.getOrganizer() is not None)
+
+        inbox = yield self.calendarUnderTest(home=&quot;user02&quot;, name=&quot;inbox&quot;)
+        cobjs = yield inbox.calendarObjects()
+        self.assertTrue(len(cobjs) == 1)
</ins></span></pre></div>
<a id="CalendarServerbranchesreleaseCalendarServer53devtxdavcaldavdatastoreschedulingtesttest_pocessingpy"></a>
<div class="delfile"><h4>Deleted: CalendarServer/branches/release/CalendarServer-5.3-dev/txdav/caldav/datastore/scheduling/test/test_pocessing.py (13590 => 13591)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/release/CalendarServer-5.3-dev/txdav/caldav/datastore/scheduling/test/test_pocessing.py        2014-06-02 02:10:46 UTC (rev 13590)
+++ CalendarServer/branches/release/CalendarServer-5.3-dev/txdav/caldav/datastore/scheduling/test/test_pocessing.py        2014-06-02 02:47:48 UTC (rev 13591)
</span><span class="lines">@@ -1,232 +0,0 @@
</span><del>-##
-# Copyright (c) 2005-2014 Apple Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-##
-
-from twisted.internet.defer import inlineCallbacks, succeed
-from twisted.trial import unittest
-
-from twistedcaldav import memcacher
-from twistedcaldav.ical import Component
-from twistedcaldav.stdconfig import config
-
-from txdav.caldav.datastore.scheduling.processing import ImplicitProcessor
-from txdav.caldav.datastore.scheduling.cuaddress import LocalCalendarUser
-
-
-class FakeImplicitProcessor(ImplicitProcessor):
-    &quot;&quot;&quot;
-    A fake ImplicitProcessor that tracks batch refreshes.
-    &quot;&quot;&quot;
-
-    def __init__(self):
-        self.batches = 0
-
-
-    def _enqueueBatchRefresh(self):
-        self.batches += 1
-
-
-    def writeCalendarResource(self, collection, resource, calendar):
-        return succeed(FakeResource())
-
-
-
-class FakePrincipal(object):
-
-    def __init__(self, cuaddr):
-        self.cuaddr = cuaddr
-
-
-    def calendarUserAddresses(self):
-        return (self.cuaddr,)
-
-
-
-class FakeResource(object):
-
-    def parentCollection(self):
-        return self
-
-
-    def ownerHome(self):
-        return self
-
-
-    def uid(self):
-        return None
-
-
-    def id(self):
-        return None
-
-
-
-class BatchRefresh (unittest.TestCase):
-    &quot;&quot;&quot;
-    iCalendar support tests
-    &quot;&quot;&quot;
-
-    def setUp(self):
-        super(BatchRefresh, self).setUp()
-        config.Memcached.Pools.Default.ClientEnabled = False
-        config.Memcached.Pools.Default.ServerEnabled = False
-        memcacher.Memcacher.allowTestCache = True
-        memcacher.Memcacher.memoryCacheInstance = None
-
-
-    @inlineCallbacks
-    def test_queueAttendeeUpdate_no_refresh(self):
-
-        self.patch(config.Scheduling.Options, &quot;AttendeeRefreshBatch&quot;, 5)
-
-        calendar = Component.fromString(&quot;&quot;&quot;BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ORGANIZER:urn:uuid:user01
-ATTENDEE:urn:uuid:user01
-ATTENDEE:urn:uuid:user02
-END:VEVENT
-END:VCALENDAR
-&quot;&quot;&quot;)
-        processor = FakeImplicitProcessor()
-        processor.txn = &quot;&quot;
-        processor.uid = &quot;12345-67890&quot;
-        processor.recipient_calendar = calendar
-        yield processor.queueAttendeeUpdate((&quot;urn:uuid:user02&quot;, &quot;urn:uuid:user01&quot;,))
-        self.assertEqual(processor.batches, 0)
-
-
-    @inlineCallbacks
-    def test_queueAttendeeUpdate_with_refresh(self):
-
-        self.patch(config.Scheduling.Options, &quot;AttendeeRefreshBatch&quot;, 5)
-
-        calendar = Component.fromString(&quot;&quot;&quot;BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ORGANIZER:urn:uuid:user01
-ATTENDEE:urn:uuid:user01
-ATTENDEE:urn:uuid:user02
-ATTENDEE:urn:uuid:user03
-END:VEVENT
-END:VCALENDAR
-&quot;&quot;&quot;)
-        processor = FakeImplicitProcessor()
-        processor.txn = &quot;&quot;
-        processor.uid = &quot;12345-67890&quot;
-        processor.recipient_calendar = calendar
-        yield processor.queueAttendeeUpdate((&quot;urn:uuid:user02&quot;, &quot;urn:uuid:user01&quot;,))
-        self.assertEqual(processor.batches, 1)
-
-
-    @inlineCallbacks
-    def test_queueAttendeeUpdate_count_suppressed(self):
-
-        self.patch(config.Scheduling.Options, &quot;AttendeeRefreshCountLimit&quot;, 5)
-        self.patch(config.Scheduling.Options, &quot;AttendeeRefreshBatch&quot;, 5)
-
-        calendar_small = Component.fromString(&quot;&quot;&quot;BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ORGANIZER:urn:uuid:user01
-ATTENDEE:urn:uuid:user01
-ATTENDEE:urn:uuid:user02
-ATTENDEE:urn:uuid:user03
-ATTENDEE:urn:uuid:user04
-END:VEVENT
-END:VCALENDAR
-&quot;&quot;&quot;)
-        itip_small = Component.fromString(&quot;&quot;&quot;BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-METHOD:REPLY
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ORGANIZER:urn:uuid:user01
-ATTENDEE;PARTSTAT=&quot;ACCEPTED&quot;:urn:uuid:user02
-END:VEVENT
-END:VCALENDAR
-&quot;&quot;&quot;)
-        calendar_large = Component.fromString(&quot;&quot;&quot;BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ORGANIZER:urn:uuid:user01
-ATTENDEE:urn:uuid:user01
-ATTENDEE:urn:uuid:user02
-ATTENDEE:urn:uuid:user03
-ATTENDEE:urn:uuid:user04
-ATTENDEE:urn:uuid:user05
-ATTENDEE:urn:uuid:user06
-ATTENDEE:urn:uuid:user07
-ATTENDEE:urn:uuid:user08
-ATTENDEE:urn:uuid:user09
-ATTENDEE:urn:uuid:user10
-END:VEVENT
-END:VCALENDAR
-&quot;&quot;&quot;)
-        itip_large = Component.fromString(&quot;&quot;&quot;BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-METHOD:REPLY
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ORGANIZER:urn:uuid:user01
-ATTENDEE;PARTSTAT=&quot;ACCEPTED&quot;:urn:uuid:user02
-END:VEVENT
-END:VCALENDAR
-&quot;&quot;&quot;)
-
-        for count, calendar, itip, result, msg in (
-            (5, calendar_small, itip_small, 1, &quot;Small, count=5&quot;),
-            (5, calendar_large, itip_large, 0, &quot;Large, count=5&quot;),
-            (0, calendar_small, itip_small, 1, &quot;Small, count=0&quot;),
-            (0, calendar_large, itip_large, 1, &quot;Large, count=0&quot;),
-        ):
-            config.Scheduling.Options.AttendeeRefreshCountLimit = count
-            processor = FakeImplicitProcessor()
-            processor.txn = &quot;&quot;
-            processor.recipient_calendar = calendar.duplicate()
-            processor.uid = processor.recipient_calendar.newUID()
-            processor.recipient_calendar_resource = None
-            processor.message = itip.duplicate()
-            processor.message.newUID(processor.uid)
-            processor.originator = LocalCalendarUser(None, None)
-            processor.recipient = LocalCalendarUser(None, None)
-            processor.uid = calendar.resourceUID()
-            processor.noAttendeeRefresh = False
-
-            processed = yield processor.doImplicitOrganizerUpdate()
-            self.assertTrue(processed[3] is not None, msg=msg)
-            self.assertEqual(processor.batches, result, msg=msg)
</del></span></pre></div>
<a id="CalendarServerbranchesreleaseCalendarServer53devtxdavcaldavdatastoreschedulingtesttest_processingpyfromrev13299CalendarServerbranchesreleaseCalendarServer53devtxdavcaldavdatastoreschedulingtesttest_pocessingpy"></a>
<div class="copfile"><h4>Copied: CalendarServer/branches/release/CalendarServer-5.3-dev/txdav/caldav/datastore/scheduling/test/test_processing.py (from rev 13299, CalendarServer/branches/release/CalendarServer-5.3-dev/txdav/caldav/datastore/scheduling/test/test_pocessing.py) (0 => 13591)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/release/CalendarServer-5.3-dev/txdav/caldav/datastore/scheduling/test/test_processing.py                                (rev 0)
+++ CalendarServer/branches/release/CalendarServer-5.3-dev/txdav/caldav/datastore/scheduling/test/test_processing.py        2014-06-02 02:47:48 UTC (rev 13591)
</span><span class="lines">@@ -0,0 +1,232 @@
</span><ins>+##
+# Copyright (c) 2005-2014 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from twisted.internet.defer import inlineCallbacks, succeed
+from twisted.trial import unittest
+
+from twistedcaldav import memcacher
+from twistedcaldav.ical import Component
+from twistedcaldav.stdconfig import config
+
+from txdav.caldav.datastore.scheduling.processing import ImplicitProcessor
+from txdav.caldav.datastore.scheduling.cuaddress import LocalCalendarUser
+
+
+class FakeImplicitProcessor(ImplicitProcessor):
+    &quot;&quot;&quot;
+    A fake ImplicitProcessor that tracks batch refreshes.
+    &quot;&quot;&quot;
+
+    def __init__(self):
+        self.batches = 0
+
+
+    def _enqueueBatchRefresh(self):
+        self.batches += 1
+
+
+    def writeCalendarResource(self, collection, resource, calendar):
+        return succeed(FakeResource())
+
+
+
+class FakePrincipal(object):
+
+    def __init__(self, cuaddr):
+        self.cuaddr = cuaddr
+
+
+    def calendarUserAddresses(self):
+        return (self.cuaddr,)
+
+
+
+class FakeResource(object):
+
+    def parentCollection(self):
+        return self
+
+
+    def ownerHome(self):
+        return self
+
+
+    def uid(self):
+        return None
+
+
+    def id(self):
+        return None
+
+
+
+class BatchRefresh (unittest.TestCase):
+    &quot;&quot;&quot;
+    iCalendar support tests
+    &quot;&quot;&quot;
+
+    def setUp(self):
+        super(BatchRefresh, self).setUp()
+        config.Memcached.Pools.Default.ClientEnabled = False
+        config.Memcached.Pools.Default.ServerEnabled = False
+        memcacher.Memcacher.allowTestCache = True
+        memcacher.Memcacher.memoryCacheInstance = None
+
+
+    @inlineCallbacks
+    def test_queueAttendeeUpdate_no_refresh(self):
+
+        self.patch(config.Scheduling.Options, &quot;AttendeeRefreshBatch&quot;, 5)
+
+        calendar = Component.fromString(&quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER:urn:uuid:user01
+ATTENDEE:urn:uuid:user01
+ATTENDEE:urn:uuid:user02
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;)
+        processor = FakeImplicitProcessor()
+        processor.txn = &quot;&quot;
+        processor.uid = &quot;12345-67890&quot;
+        processor.recipient_calendar = calendar
+        yield processor.queueAttendeeUpdate((&quot;urn:uuid:user02&quot;, &quot;urn:uuid:user01&quot;,))
+        self.assertEqual(processor.batches, 0)
+
+
+    @inlineCallbacks
+    def test_queueAttendeeUpdate_with_refresh(self):
+
+        self.patch(config.Scheduling.Options, &quot;AttendeeRefreshBatch&quot;, 5)
+
+        calendar = Component.fromString(&quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER:urn:uuid:user01
+ATTENDEE:urn:uuid:user01
+ATTENDEE:urn:uuid:user02
+ATTENDEE:urn:uuid:user03
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;)
+        processor = FakeImplicitProcessor()
+        processor.txn = &quot;&quot;
+        processor.uid = &quot;12345-67890&quot;
+        processor.recipient_calendar = calendar
+        yield processor.queueAttendeeUpdate((&quot;urn:uuid:user02&quot;, &quot;urn:uuid:user01&quot;,))
+        self.assertEqual(processor.batches, 1)
+
+
+    @inlineCallbacks
+    def test_queueAttendeeUpdate_count_suppressed(self):
+
+        self.patch(config.Scheduling.Options, &quot;AttendeeRefreshCountLimit&quot;, 5)
+        self.patch(config.Scheduling.Options, &quot;AttendeeRefreshBatch&quot;, 5)
+
+        calendar_small = Component.fromString(&quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER:urn:uuid:user01
+ATTENDEE:urn:uuid:user01
+ATTENDEE:urn:uuid:user02
+ATTENDEE:urn:uuid:user03
+ATTENDEE:urn:uuid:user04
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;)
+        itip_small = Component.fromString(&quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+METHOD:REPLY
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER:urn:uuid:user01
+ATTENDEE;PARTSTAT=&quot;ACCEPTED&quot;:urn:uuid:user02
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;)
+        calendar_large = Component.fromString(&quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER:urn:uuid:user01
+ATTENDEE:urn:uuid:user01
+ATTENDEE:urn:uuid:user02
+ATTENDEE:urn:uuid:user03
+ATTENDEE:urn:uuid:user04
+ATTENDEE:urn:uuid:user05
+ATTENDEE:urn:uuid:user06
+ATTENDEE:urn:uuid:user07
+ATTENDEE:urn:uuid:user08
+ATTENDEE:urn:uuid:user09
+ATTENDEE:urn:uuid:user10
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;)
+        itip_large = Component.fromString(&quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+METHOD:REPLY
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER:urn:uuid:user01
+ATTENDEE;PARTSTAT=&quot;ACCEPTED&quot;:urn:uuid:user02
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;)
+
+        for count, calendar, itip, result, msg in (
+            (5, calendar_small, itip_small, 1, &quot;Small, count=5&quot;),
+            (5, calendar_large, itip_large, 0, &quot;Large, count=5&quot;),
+            (0, calendar_small, itip_small, 1, &quot;Small, count=0&quot;),
+            (0, calendar_large, itip_large, 1, &quot;Large, count=0&quot;),
+        ):
+            config.Scheduling.Options.AttendeeRefreshCountLimit = count
+            processor = FakeImplicitProcessor()
+            processor.txn = &quot;&quot;
+            processor.recipient_calendar = calendar.duplicate()
+            processor.uid = processor.recipient_calendar.newUID()
+            processor.recipient_calendar_resource = None
+            processor.message = itip.duplicate()
+            processor.message.newUID(processor.uid)
+            processor.originator = LocalCalendarUser(None, None)
+            processor.recipient = LocalCalendarUser(None, None)
+            processor.uid = calendar.resourceUID()
+            processor.noAttendeeRefresh = False
+
+            processed = yield processor.doImplicitOrganizerUpdate()
+            self.assertTrue(processed[3] is not None, msg=msg)
+            self.assertEqual(processor.batches, result, msg=msg)
</ins></span></pre>
</div>
</div>

</body>
</html>