[CalendarServer-changes] [9791] CalendarServer/trunk

source_changes at macosforge.org source_changes at macosforge.org
Fri Sep 7 12:16:37 PDT 2012


Revision: 9791
          http://trac.macosforge.org/projects/calendarserver/changeset/9791
Author:   cdaboo at apple.com
Date:     2012-09-07 12:16:36 -0700 (Fri, 07 Sep 2012)
Log Message:
-----------
Make sure attendee deleted resources stay deleted if incoming iTIP still shows the attendee as declined.

Modified Paths:
--------------
    CalendarServer/trunk/twistedcaldav/datafilters/peruserdata.py
    CalendarServer/trunk/twistedcaldav/ical.py
    CalendarServer/trunk/twistedcaldav/method/get.py
    CalendarServer/trunk/twistedcaldav/method/report_common.py
    CalendarServer/trunk/twistedcaldav/resource.py
    CalendarServer/trunk/twistedcaldav/scheduling/icaldiff.py
    CalendarServer/trunk/twistedcaldav/scheduling/implicit.py
    CalendarServer/trunk/twistedcaldav/scheduling/itip.py
    CalendarServer/trunk/twistedcaldav/scheduling/processing.py
    CalendarServer/trunk/twistedcaldav/scheduling/test/test_icaldiff.py
    CalendarServer/trunk/txdav/caldav/datastore/util.py

Added Paths:
-----------
    CalendarServer/trunk/twistedcaldav/datafilters/hiddeninstance.py
    CalendarServer/trunk/twistedcaldav/datafilters/test/test_hiddeninstances.py

Added: CalendarServer/trunk/twistedcaldav/datafilters/hiddeninstance.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/datafilters/hiddeninstance.py	                        (rev 0)
+++ CalendarServer/trunk/twistedcaldav/datafilters/hiddeninstance.py	2012-09-07 19:16:36 UTC (rev 9791)
@@ -0,0 +1,65 @@
+##
+# Copyright (c) 2012 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from twistedcaldav.datafilters.filter import CalendarFilter
+from twistedcaldav.ical import Component, ignoredComponents, Property
+
+__all__ = [
+    "HiddenInstanceFilter",
+]
+
+class HiddenInstanceFilter(CalendarFilter):
+    """
+    Filter overridden components in an event marked by a specific property to remove the component and add
+    a matching EXDATE.
+    """
+
+    def filter(self, ical):
+        """
+        Filter the supplied iCalendar object using the request information.
+
+        @param ical: iCalendar object
+        @type ical: L{Component} or C{str}
+        
+        @return: L{Component} for the filtered calendar data
+        """
+        
+        master = ical.masterComponent()
+        if master is None:
+            return ical
+        for component in tuple(ical.subcomponents()):
+            if component.name() in ignoredComponents:
+                continue
+            rid = component.getRecurrenceIDUTC()
+            if rid is None:
+                continue
+            if component.hasProperty(Component.HIDDEN_INSTANCE_PROPERTY):
+                rid = component.getRecurrenceIDUTC()
+                ical.removeComponent(component)
+                
+                # Add EXDATE and try to preserve same timezone as DTSTART
+                dtstart = master.getProperty("DTSTART")
+                if dtstart is not None and not dtstart.value().isDateOnly() and dtstart.value().local():
+                    rid.adjustTimezone(dtstart.value().getTimezone())
+                master.addProperty(Property("EXDATE", [rid,]))
+        
+        return ical
+   
+    def merge(self, icalnew, icalold):
+        """
+        Private event merging does not happen
+        """
+        raise NotImplementedError

Modified: CalendarServer/trunk/twistedcaldav/datafilters/peruserdata.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/datafilters/peruserdata.py	2012-09-07 19:11:26 UTC (rev 9790)
+++ CalendarServer/trunk/twistedcaldav/datafilters/peruserdata.py	2012-09-07 19:16:36 UTC (rev 9791)
@@ -65,6 +65,7 @@
 
     PERUSER_PROPERTIES    = ("TRANSP",)
     PERUSER_SUBCOMPONENTS = ("VALARM",)
+    IGNORE_X_PROPERTIES   = ("X-CALENDARSERVER-HIDDEN-INSTANCE",)
 
     def __init__(self, uid):
         """
@@ -239,7 +240,7 @@
 
             # Transfer per-user properties from main component to per-instance component
             for property in tuple(component.properties()):
-                if property.name() in PerUserDataFilter.PERUSER_PROPERTIES or property.name().startswith("X-"):
+                if property.name() in PerUserDataFilter.PERUSER_PROPERTIES or property.name().startswith("X-") and property.name() not in PerUserDataFilter.IGNORE_X_PROPERTIES:
                     if self.uid:
                         perinstance_component.addProperty(property)
                     component.removeProperty(property)

Added: CalendarServer/trunk/twistedcaldav/datafilters/test/test_hiddeninstances.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/datafilters/test/test_hiddeninstances.py	                        (rev 0)
+++ CalendarServer/trunk/twistedcaldav/datafilters/test/test_hiddeninstances.py	2012-09-07 19:16:36 UTC (rev 9791)
@@ -0,0 +1,435 @@
+##
+# Copyright (c) 2009 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from twistedcaldav.datafilters.hiddeninstance import HiddenInstanceFilter
+from twistedcaldav.ical import Component
+import twistedcaldav.test.util
+
+class HiddenInstanceFilterTest (twistedcaldav.test.util.TestCase):
+
+    def test_public_default(self):
+        
+        data = (
+            (
+                "Nothing hidden, no recurrence",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+            ),
+            (
+                "Nothing hidden, recurrence",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T123000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080603T120000Z
+DTSTART:20080603T123000Z
+DTEND:20080601T133000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T123000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080603T120000Z
+DTSTART:20080603T123000Z
+DTEND:20080601T133000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+            ),
+            (
+                "One hidden",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T123000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080603T120000Z
+DTSTART:20080603T123000Z
+DTEND:20080601T133000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+X-CALENDARSERVER-HIDDEN-INSTANCE:T
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+EXDATE:20080603T120000Z
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T123000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+            ),
+            (
+                "Two hidden",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T123000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+X-CALENDARSERVER-HIDDEN-INSTANCE:T
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080603T120000Z
+DTSTART:20080603T123000Z
+DTEND:20080601T133000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+X-CALENDARSERVER-HIDDEN-INSTANCE:T
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+EXDATE:20080602T120000Z
+EXDATE:20080603T120000Z
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+END:VCALENDAR
+""",
+            ),
+            (
+                "One hidden with timezone",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VTIMEZONE
+LAST-MODIFIED:20040110T032845Z
+TZID:US/Eastern
+BEGIN:DAYLIGHT
+DTSTART:20000404T020000
+RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4
+TZNAME:EDT
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+DTSTART:20001026T020000
+RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
+TZNAME:EST
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+END:VTIMEZONE
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART;TZID=US/Eastern:20080601T120000
+DTEND;TZID=US/Eastern:20080601T130000
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID;TZID=US/Eastern:20080602T120000
+DTSTART;TZID=US/Eastern:20080602T123000
+DTEND;TZID=US/Eastern:20080601T130000
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID;TZID=US/Eastern:20080603T120000
+DTSTART;TZID=US/Eastern:20080603T123000
+DTEND;TZID=US/Eastern:20080601T133000
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+X-CALENDARSERVER-HIDDEN-INSTANCE:T
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VTIMEZONE
+TZID:US/Eastern
+LAST-MODIFIED:20040110T032845Z
+BEGIN:DAYLIGHT
+DTSTART:20000404T020000
+RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4
+TZNAME:EDT
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+DTSTART:20001026T020000
+RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
+TZNAME:EST
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+END:VTIMEZONE
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART;TZID=US/Eastern:20080601T120000
+DTEND;TZID=US/Eastern:20080601T130000
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+EXDATE;TZID=US/Eastern:20080603T120000
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID;TZID=US/Eastern:20080602T120000
+DTSTART;TZID=US/Eastern:20080602T123000
+DTEND;TZID=US/Eastern:20080601T130000
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+            ),
+            (
+                "No master, no hidden",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T123000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080603T120000Z
+DTSTART:20080603T123000Z
+DTEND:20080601T133000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T123000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080603T120000Z
+DTSTART:20080603T123000Z
+DTEND:20080601T133000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+            ),
+            (
+                "No master, one hidden - not really",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T123000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080603T120000Z
+DTSTART:20080603T123000Z
+DTEND:20080601T133000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+X-CALENDARSERVER-HIDDEN-INSTANCE:T
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T123000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080603T120000Z
+DTSTART:20080603T123000Z
+DTEND:20080601T133000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+X-CALENDARSERVER-HIDDEN-INSTANCE:T
+END:VEVENT
+END:VCALENDAR
+""",
+            ),
+        )
+        
+        for title, test, result in data:
+            ics = Component.fromString(test.replace("\n", "\r\n"))
+            self.assertEqual(str(HiddenInstanceFilter().filter(ics)), result.replace("\n", "\r\n"), msg="Failed: %s" % (title,))

Modified: CalendarServer/trunk/twistedcaldav/ical.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/ical.py	2012-09-07 19:11:26 UTC (rev 9790)
+++ CalendarServer/trunk/twistedcaldav/ical.py	2012-09-07 19:16:36 UTC (rev 9791)
@@ -325,6 +325,9 @@
     }
     extraRestrictedProperties = ("SUMMARY", "LOCATION",)
 
+    # Hidden instance.
+    HIDDEN_INSTANCE_PROPERTY = "X-CALENDARSERVER-HIDDEN-INSTANCE"
+
     @classmethod
     def allFromString(clazz, string):
         """
@@ -762,7 +765,17 @@
                 return (range == "THISANDFUTURE")
 
         return False
-            
+    
+    def getExdates(self):
+        """
+        Get the set of all EXDATEs in this (master) component.
+        """
+        exdates = set()
+        for property in self.properties("EXDATE"):
+            for exdate in property.value():
+                exdates.add(exdate.getValue())
+        return exdates
+
     def getTriggerDetails(self):
         """
         Return the trigger information for the specified alarm component.

Modified: CalendarServer/trunk/twistedcaldav/method/get.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/get.py	2012-09-07 19:11:26 UTC (rev 9790)
+++ CalendarServer/trunk/twistedcaldav/method/get.py	2012-09-07 19:16:36 UTC (rev 9791)
@@ -32,6 +32,7 @@
 
 from twistedcaldav.config import config
 from twistedcaldav.customxml import calendarserver_namespace
+from twistedcaldav.datafilters.hiddeninstance import HiddenInstanceFilter
 from twistedcaldav.datafilters.privateevents import PrivateEventFilter
 from twistedcaldav.resource import isPseudoCalendarCollectionResource,\
     CalDAVResource
@@ -79,6 +80,9 @@
     
                 caldata = (yield self.iCalendarForUser(request))
     
+                # Filter any attendee hidden instances        
+                caldata = HiddenInstanceFilter().filter(caldata)
+
                 if self.accessMode:
             
                     # Non DAV:owner's have limited access to the data
@@ -86,7 +90,7 @@
                     
                     # Now "filter" the resource calendar data
                     caldata = PrivateEventFilter(self.accessMode, isowner).filter(caldata)
-        
+
                 response = Response()
                 response.stream = MemoryStream(caldata.getTextWithTimezones(includeTimezones=not config.EnableTimezonesByReference))
                 response.headers.setHeader("content-type", MimeType.fromString("text/calendar; charset=utf-8"))

Modified: CalendarServer/trunk/twistedcaldav/method/report_common.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/report_common.py	2012-09-07 19:11:26 UTC (rev 9790)
+++ CalendarServer/trunk/twistedcaldav/method/report_common.py	2012-09-07 19:16:36 UTC (rev 9791)
@@ -56,6 +56,7 @@
 from twistedcaldav.carddavxml import AddressData
 from twistedcaldav.config import config
 from twistedcaldav.datafilters.calendardata import CalendarDataFilter
+from twistedcaldav.datafilters.hiddeninstance import HiddenInstanceFilter
 from twistedcaldav.datafilters.privateevents import PrivateEventFilter
 from twistedcaldav.datafilters.addressdata import AddressDataFilter
 from twistedcaldav.dateops import clipPeriod, normalizePeriodList, timeRangesOverlap,\
@@ -344,7 +345,8 @@
             # Handle private events access restrictions
             if calendar is None:
                 calendar = (yield resource.iCalendarForUser(request))
-            filtered = PrivateEventFilter(resource.accessMode, isowner).filter(calendar)
+            filtered = HiddenInstanceFilter().filter(calendar)
+            filtered = PrivateEventFilter(resource.accessMode, isowner).filter(filtered)
             filtered = CalendarDataFilter(property, timezone).filter(filtered)
             propvalue = CalendarData().fromCalendar(filtered)
             properties_by_status[responsecode.OK].append(propvalue)

Modified: CalendarServer/trunk/twistedcaldav/resource.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/resource.py	2012-09-07 19:11:26 UTC (rev 9790)
+++ CalendarServer/trunk/twistedcaldav/resource.py	2012-09-07 19:16:36 UTC (rev 9791)
@@ -65,6 +65,7 @@
 from twistedcaldav.carddavxml import carddav_namespace
 from twistedcaldav.config import config
 from twistedcaldav.customxml import calendarserver_namespace
+from twistedcaldav.datafilters.hiddeninstance import HiddenInstanceFilter
 from twistedcaldav.datafilters.peruserdata import PerUserDataFilter
 from twistedcaldav.datafilters.privateevents import PrivateEventFilter
 from twistedcaldav.directory.internal import InternalDirectoryRecord
@@ -1575,11 +1576,11 @@
     def iCalendarFiltered(self, isowner, accessUID=None):
 
         # Now "filter" the resource calendar data
-        caldata = PrivateEventFilter(self.accessMode, isowner).filter(
-            (yield self.iCalendar())
-        )
+        caldata = (yield self.iCalendar())
         if accessUID:
             caldata = PerUserDataFilter(accessUID).filter(caldata)
+        caldata = HiddenInstanceFilter().filter(caldata)
+        caldata = PrivateEventFilter(self.accessMode, isowner).filter(caldata)
         returnValue(caldata)
 
 

Modified: CalendarServer/trunk/twistedcaldav/scheduling/icaldiff.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/scheduling/icaldiff.py	2012-09-07 19:11:26 UTC (rev 9790)
+++ CalendarServer/trunk/twistedcaldav/scheduling/icaldiff.py	2012-09-07 19:16:36 UTC (rev 9791)
@@ -323,9 +323,17 @@
                 # Attendee may decline by EXDATE'ing an instance - we need to handle that
                 if exdatesnew is None or rid in exdatesnew:
                     # Mark Attendee as DECLINED in the server instance
-                    if self._attendeeDecline(returnCalendar.overriddenComponent(rid)):
+                    overridden = returnCalendar.overriddenComponent(rid)
+                    if self._attendeeDecline(overridden):
                         changeCausesReply = True
                         changedRids.append(rid.getText() if rid else "")
+                        
+                    # When a master component is present we keep the missing override in place but mark it as hidden.
+                    # When no master is present we remove the override,
+                    if exdatesnew is not None:
+                        overridden.replaceProperty(Property(Component.HIDDEN_INSTANCE_PROPERTY, "T"))
+                    else:
+                        returnCalendar.removeComponent(overridden)
                 else:
                     # We used to generate a 403 here - but instead we now ignore this error and let the server data
                     # override the client
@@ -411,10 +419,15 @@
             if not overridden:
                 overridden = returnCalendar.deriveInstance(decline)
                 if overridden:
-                    returnCalendar.addComponent(overridden)
                     if self._attendeeDecline(overridden):
                         changeCausesReply = True
                         changedRids.append(decline.getText() if decline else "")
+                        
+                    # When a master component is present we keep the missing override in place but mark it as hidden.
+                    # When no master is present we remove the override,
+                    if exdatesnew is not None:
+                        overridden.replaceProperty(Property(Component.HIDDEN_INSTANCE_PROPERTY, "T"))
+                        returnCalendar.addComponent(overridden)
                 else:
                     self._logDiffError("attendeeMerge: Unable to override an instance to mark as DECLINED: %s" % (decline,))
                     return False, False, (), None

Modified: CalendarServer/trunk/twistedcaldav/scheduling/implicit.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/scheduling/implicit.py	2012-09-07 19:11:26 UTC (rev 9790)
+++ CalendarServer/trunk/twistedcaldav/scheduling/implicit.py	2012-09-07 19:16:36 UTC (rev 9791)
@@ -958,8 +958,14 @@
                 log.debug("Implicit - attendee '%s' is removing cancelled UID: '%s'" % (self.attendee, self.uid))
                 # Nothing else to do
             elif doScheduling:
-                log.debug("Implicit - attendee '%s' is cancelling UID: '%s'" % (self.attendee, self.uid))
-                yield self.scheduleCancelWithOrganizer()
+                # If attendee is already marked as declined in all components - nothing to do
+                attendees = self.calendar.getAttendeeProperties((self.attendee,))
+                if all([attendee.parameterValue("PARTSTAT", "NEEDS-ACTION") == "DECLINED" for attendee in attendees]):
+                    log.debug("Implicit - attendee '%s' is removing fully declined UID: '%s'" % (self.attendee, self.uid))
+                    # Nothing else to do
+                else:
+                    log.debug("Implicit - attendee '%s' is cancelling UID: '%s'" % (self.attendee, self.uid))
+                    yield self.scheduleCancelWithOrganizer()
             else:
                 log.debug("Implicit - attendee '%s' is removing UID without server scheduling: '%s'" % (self.attendee, self.uid))
                 # Nothing else to do

Modified: CalendarServer/trunk/twistedcaldav/scheduling/itip.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/scheduling/itip.py	2012-09-07 19:11:26 UTC (rev 9790)
+++ CalendarServer/trunk/twistedcaldav/scheduling/itip.py	2012-09-07 19:16:36 UTC (rev 9791)
@@ -47,7 +47,7 @@
 class iTipProcessing(object):
 
     @staticmethod
-    def processNewRequest(itip_message, recipient=None):
+    def processNewRequest(itip_message, recipient=None, creating=False):
         """
         Process a METHOD=REQUEST for a brand new calendar object.
         
@@ -65,6 +65,20 @@
 
         if recipient:
             iTipProcessing.addTranspForNeedsAction(calendar.subcomponents(), recipient)
+                
+            # Check for incoming DECLINED
+            if creating:
+                master = calendar.masterComponent()
+                for component in tuple(calendar.subcomponents()):
+                    if component in ignoredComponents or component is master:
+                        continue
+                    attendee = component.getAttendeeProperty((recipient,))
+                    if attendee and attendee.parameterValue("PARTSTAT", "NEEDS-ACTION") == "DECLINED":
+                        # Mark as hidden if we have a master, otherwise remove
+                        if master is not None:
+                            component.addProperty(Property(Component.HIDDEN_INSTANCE_PROPERTY, "T"))
+                        else:
+                            calendar.removeComponent(component)
 
         return calendar
         
@@ -131,12 +145,12 @@
                 if organizer:
                     organizer.setParameter("SCHEDULE-STATUS", organizer_schedule_status)
                 
-            # Now try to match recurrences
-            for component in new_calendar.subcomponents():
+            # Now try to match recurrences in the new calendar
+            for component in tuple(new_calendar.subcomponents()):
                 if component.name() != "VTIMEZONE" and component.getRecurrenceIDUTC() is not None:
-                    iTipProcessing.transferItems(calendar, master_valarms, private_comments, transps, completeds, organizer_schedule_status, component)
+                    iTipProcessing.transferItems(calendar, master_valarms, private_comments, transps, completeds, organizer_schedule_status, component, recipient)
             
-            # Now try to match recurrences
+            # Now try to match recurrences from the old calendar
             for component in calendar.subcomponents():
                 if component.name() != "VTIMEZONE" and component.getRecurrenceIDUTC() is not None:
                     rid = component.getRecurrenceIDUTC()
@@ -145,7 +159,7 @@
                         new_component = new_calendar.deriveInstance(rid, allowCancelled=allowCancelled)
                         if new_component:
                             new_calendar.addComponent(new_component)
-                            iTipProcessing.transferItems(calendar, master_valarms, private_comments, transps, completeds, organizer_schedule_status, new_component)
+                            iTipProcessing.transferItems(calendar, master_valarms, private_comments, transps, completeds, organizer_schedule_status, new_component, recipient)
             
             # Replace the entire object
             return new_calendar, rids
@@ -162,10 +176,11 @@
                         calendar.addComponent(component)
                 else:
                     component = component.duplicate()
-                    iTipProcessing.transferItems(calendar, master_valarms, private_comments, transps, completeds, organizer_schedule_status, component, remove_matched=True)
-                    calendar.addComponent(component)
-                    if recipient:
-                        iTipProcessing.addTranspForNeedsAction((component,), recipient)
+                    missingDeclined = iTipProcessing.transferItems(calendar, master_valarms, private_comments, transps, completeds, organizer_schedule_status, component, recipient, remove_matched=True)
+                    if not missingDeclined:
+                        calendar.addComponent(component)
+                        if recipient:
+                            iTipProcessing.addTranspForNeedsAction((component,), recipient)
 
             # Write back the modified object
             return calendar, rids
@@ -468,11 +483,13 @@
         return attendee.value(), partstat_changed, private_comment_changed
 
     @staticmethod
-    def transferItems(from_calendar, master_valarms, private_comments, transps, completeds, organizer_schedule_status, to_component, remove_matched=False):
+    def transferItems(from_calendar, master_valarms, private_comments, transps, completeds, organizer_schedule_status, to_component, recipient, remove_matched=False):
         """
         Transfer properties from a calendar to a component by first trying to match the component in the original calendar and
         use the properties from that, or use the values provided as arguments (which have been derived from the original calendar's
         master component).
+        
+        @return: C{True} if an EXDATE match occurred requiring the incoming component to be removed.
         """
 
         rid = to_component.getRecurrenceIDUTC()
@@ -497,7 +514,19 @@
             if remove_matched:
                 from_calendar.removeComponent(matched)
                 
+            # Check for incoming DECLINED
+            attendee = to_component.getAttendeeProperty((recipient,))
+            if attendee and attendee.parameterValue("PARTSTAT", "NEEDS-ACTION") == "DECLINED":
+                # If existing item has HIDDEN property copy that over
+                if matched.hasProperty(Component.HIDDEN_INSTANCE_PROPERTY):
+                    to_component.addProperty(Property(Component.HIDDEN_INSTANCE_PROPERTY, "T"))
+
         else:
+            # Check for incoming DECLINED
+            attendee = to_component.getAttendeeProperty((recipient,))
+            if attendee and attendee.parameterValue("PARTSTAT", "NEEDS-ACTION") == "DECLINED":
+                return True
+                    
             # It is a new override - copy any valarms on the existing master component
             # into the new one.
             [to_component.addComponent(alarm) for alarm in master_valarms]
@@ -508,6 +537,8 @@
                 organizer = to_component.getProperty("ORGANIZER")
                 if organizer:
                     organizer.setParameter("SCHEDULE-STATUS", organizer_schedule_status)
+                
+        return False
     
     @staticmethod
     def addTranspForNeedsAction(components, recipient):

Modified: CalendarServer/trunk/twistedcaldav/scheduling/processing.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/scheduling/processing.py	2012-09-07 19:11:26 UTC (rev 9790)
+++ CalendarServer/trunk/twistedcaldav/scheduling/processing.py	2012-09-07 19:16:36 UTC (rev 9791)
@@ -460,10 +460,20 @@
 
     @inlineCallbacks
     def doImplicitAttendeeRequest(self):
+        """
+        @return: C{tuple} of (processed, auto-processed, store inbox item, changes)
+        """
 
         # If there is no existing copy, then look for default calendar and copy it here
         if self.new_resource:
             
+            # Check if the incoming data has the recipient declined in all instances. In that case we will not create
+            # a new resource as chances are the recipient previously deleted the resource and we want to keep it deleted.
+            attendees = self.message.getAttendeeProperties((self.recipient.cuaddr,))
+            if all([attendee.parameterValue("PARTSTAT", "NEEDS-ACTION") == "DECLINED" for attendee in attendees]):
+                log.debug("ImplicitProcessing - originator '%s' to recipient '%s' processing METHOD:REQUEST, UID: '%s' - ignoring all declined" % (self.originator.cuaddr, self.recipient.cuaddr, self.uid))
+                returnValue((True, False, False, None,))
+            
             # Check for default calendar
             default = (yield self.recipient.inbox.defaultCalendar(self.request, self.message.mainType()))
             if default is None:
@@ -471,7 +481,7 @@
                 raise ImplicitProcessorException(iTIPRequestStatus.NO_USER_SUPPORT)
 
             log.debug("ImplicitProcessing - originator '%s' to recipient '%s' processing METHOD:REQUEST, UID: '%s' - new processed" % (self.originator.cuaddr, self.recipient.cuaddr, self.uid))
-            new_calendar = iTipProcessing.processNewRequest(self.message, self.recipient.cuaddr)
+            new_calendar = iTipProcessing.processNewRequest(self.message, self.recipient.cuaddr, creating=True)
             
             # Handle auto-reply behavior
             if self.recipient.principal.canAutoSchedule():

Modified: CalendarServer/trunk/twistedcaldav/scheduling/test/test_icaldiff.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/scheduling/test/test_icaldiff.py	2012-09-07 19:11:26 UTC (rev 9790)
+++ CalendarServer/trunk/twistedcaldav/scheduling/test/test_icaldiff.py	2012-09-07 19:16:36 UTC (rev 9791)
@@ -1738,6 +1738,7 @@
 ATTENDEE:mailto:user1 at example.com
 ATTENDEE;PARTSTAT=DECLINED:mailto:user2 at example.com
 ORGANIZER;CN=User 01:mailto:user1 at example.com
+X-CALENDARSERVER-HIDDEN-INSTANCE:T
 END:VEVENT
 END:VCALENDAR
 """)
@@ -1794,6 +1795,7 @@
 ATTENDEE:mailto:user1 at example.com
 ATTENDEE;PARTSTAT=DECLINED:mailto:user2 at example.com
 ORGANIZER;CN=User 01:mailto:user1 at example.com
+X-CALENDARSERVER-HIDDEN-INSTANCE:T
 END:VEVENT
 BEGIN:VEVENT
 UID:12345-67890
@@ -1803,6 +1805,7 @@
 ATTENDEE:mailto:user1 at example.com
 ATTENDEE;PARTSTAT=DECLINED:mailto:user2 at example.com
 ORGANIZER;CN=User 01:mailto:user1 at example.com
+X-CALENDARSERVER-HIDDEN-INSTANCE:T
 END:VEVENT
 END:VCALENDAR
 """)
@@ -1868,6 +1871,7 @@
 ATTENDEE:mailto:user1 at example.com
 ATTENDEE;PARTSTAT=DECLINED;RSVP=TRUE:mailto:user2 at example.com
 ORGANIZER;CN=User 01:mailto:user1 at example.com
+X-CALENDARSERVER-HIDDEN-INSTANCE:T
 END:VEVENT
 END:VCALENDAR
 """)
@@ -1934,6 +1938,7 @@
 ATTENDEE:mailto:user1 at example.com
 ATTENDEE;PARTSTAT=DECLINED;RSVP=TRUE:mailto:user2 at example.com
 ORGANIZER;CN=User 01:mailto:user1 at example.com
+X-CALENDARSERVER-HIDDEN-INSTANCE:T
 END:VEVENT
 BEGIN:VEVENT
 UID:12345-67890
@@ -1943,6 +1948,7 @@
 ATTENDEE:mailto:user1 at example.com
 ATTENDEE;PARTSTAT=DECLINED;RSVP=TRUE:mailto:user2 at example.com
 ORGANIZER;CN=User 01:mailto:user1 at example.com
+X-CALENDARSERVER-HIDDEN-INSTANCE:T
 END:VEVENT
 END:VCALENDAR
 """)
@@ -1992,15 +1998,6 @@
 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
 BEGIN:VEVENT
 UID:12345-67890
-RECURRENCE-ID:20080601T120000Z
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE;PARTSTAT=DECLINED;RSVP=TRUE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890
 RECURRENCE-ID:20080604T120000Z
 DTSTART:20080604T130000Z
 DTEND:20080604T140000Z

Modified: CalendarServer/trunk/txdav/caldav/datastore/util.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/util.py	2012-09-07 19:11:26 UTC (rev 9790)
+++ CalendarServer/trunk/txdav/caldav/datastore/util.py	2012-09-07 19:16:36 UTC (rev 9791)
@@ -36,6 +36,7 @@
 from twext.python.vcomponent import InvalidICalendarDataError
 from twext.python.vcomponent import VComponent
 
+from twistedcaldav.datafilters.hiddeninstance import HiddenInstanceFilter
 from twistedcaldav.datafilters.peruserdata import PerUserDataFilter
 from twistedcaldav.datafilters.privateevents import PrivateEventFilter
 
@@ -406,8 +407,11 @@
         calendar = self.calendar()
         isOwner = asAdmin or (calendar._owned and
                               calendar.ownerCalendarHome().uid() == accessUID)
-        for data_filter in [PrivateEventFilter(self.accessMode, isOwner),
-                       PerUserDataFilter(accessUID)]:
+        for data_filter in [
+            PerUserDataFilter(accessUID),
+            HiddenInstanceFilter(),
+            PrivateEventFilter(self.accessMode, isOwner),
+        ]:
             component = data_filter.filter(component)
         returnValue(component)
 
@@ -501,7 +505,7 @@
                 fixes += 1
                 calprop.setValue(postval)
     for subc in component.subcomponents():
-        count, fixsubc = fixOneCalendarObject(subc)
+        count, _ignore_fixsubc = fixOneCalendarObject(subc)
         fixes += count
     return fixes, component
 
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20120907/eb7ae553/attachment-0001.html>


More information about the calendarserver-changes mailing list