[CalendarServer-changes] [4693] CalendarServer/branches/users/cdaboo/per-user-icalendar-4669/ twistedcaldav

source_changes at macosforge.org source_changes at macosforge.org
Mon Nov 2 13:47:42 PST 2009


Revision: 4693
          http://trac.macosforge.org/projects/calendarserver/changeset/4693
Author:   cdaboo at apple.com
Date:     2009-11-02 13:47:40 -0800 (Mon, 02 Nov 2009)
Log Message:
-----------
Filtering (reading only) of per-user calendar data. This defines the object model used in the iCalendar data
to represent per-user data. Next piece is to do the merge (write) behavior.

Modified Paths:
--------------
    CalendarServer/branches/users/cdaboo/per-user-icalendar-4669/twistedcaldav/datafilters/__init__.py
    CalendarServer/branches/users/cdaboo/per-user-icalendar-4669/twistedcaldav/datafilters/calendardata.py
    CalendarServer/branches/users/cdaboo/per-user-icalendar-4669/twistedcaldav/datafilters/filter.py
    CalendarServer/branches/users/cdaboo/per-user-icalendar-4669/twistedcaldav/datafilters/privateevents.py
    CalendarServer/branches/users/cdaboo/per-user-icalendar-4669/twistedcaldav/ical.py

Added Paths:
-----------
    CalendarServer/branches/users/cdaboo/per-user-icalendar-4669/twistedcaldav/datafilters/peruserdata.py
    CalendarServer/branches/users/cdaboo/per-user-icalendar-4669/twistedcaldav/datafilters/test/test_peruserdata.py

Modified: CalendarServer/branches/users/cdaboo/per-user-icalendar-4669/twistedcaldav/datafilters/__init__.py
===================================================================
--- CalendarServer/branches/users/cdaboo/per-user-icalendar-4669/twistedcaldav/datafilters/__init__.py	2009-11-02 19:11:51 UTC (rev 4692)
+++ CalendarServer/branches/users/cdaboo/per-user-icalendar-4669/twistedcaldav/datafilters/__init__.py	2009-11-02 21:47:40 UTC (rev 4693)
@@ -14,6 +14,34 @@
 # limitations under the License.
 ##
 
+from vobject.base import registerBehavior
+from vobject.icalendar import VCalendarComponentBehavior, VCalendar2_0
+
 """
 Data filtering module.
 """
+
+# This is where we register our special components with vobject
+
+class X_CALENDARSERVER_PERUSER(VCalendarComponentBehavior):
+    name='X-CALENDARSERVER-PERUSER'
+    description='A component used to encapsulate per-user data.'
+    sortFirst = ('uid', 'x-calendarserver-peruser-uid')
+    knownChildren = {
+        'UID':                            (1, 1, None),#min, max, behaviorRegistry id
+        'X-CALENDARSERVER-PERUSER':       (1, 1, None),
+        'X-CALENDARSERVER-PERINSTANCE':   (0, None, None),
+    }
+      
+registerBehavior(X_CALENDARSERVER_PERUSER)
+VCalendar2_0.knownChildren['X-CALENDARSERVER-PERUSER'] = (0, None, None)
+
+class X_CALENDARSERVER_PERINSTANCE(VCalendarComponentBehavior):
+    name='X-CALENDARSERVER-PERINSTANCE'
+    description='A component used to encapsulate per-user instance data.'
+    sortFirst = ('recurrence-id',)
+    knownChildren = {
+        'RECURRENCE-ID':(0, 1, None),#min, max, behaviorRegistry id
+    }
+      
+registerBehavior(X_CALENDARSERVER_PERINSTANCE)

Modified: CalendarServer/branches/users/cdaboo/per-user-icalendar-4669/twistedcaldav/datafilters/calendardata.py
===================================================================
--- CalendarServer/branches/users/cdaboo/per-user-icalendar-4669/twistedcaldav/datafilters/calendardata.py	2009-11-02 19:11:51 UTC (rev 4692)
+++ CalendarServer/branches/users/cdaboo/per-user-icalendar-4669/twistedcaldav/datafilters/calendardata.py	2009-11-02 21:47:40 UTC (rev 4693)
@@ -14,16 +14,17 @@
 # limitations under the License.
 ##
 
-from twistedcaldav.ical import Component
-from twistedcaldav.dateops import clipPeriod
 from twistedcaldav.caldavxml import LimitRecurrenceSet, Expand, AllComponents,\
     AllProperties
+from twistedcaldav.datafilters.filter import CalendarFilter
+from twistedcaldav.dateops import clipPeriod
+from twistedcaldav.ical import Component
 
 __all__ = [
     "CalendarDataFilter",
 ]
 
-class CalendarDataFilter(object):
+class CalendarDataFilter(CalendarFilter):
     """
     Filter using the CALDAV:calendar-data element specification
     """
@@ -54,16 +55,9 @@
         if not self.calendardata.children:
             return ical
 
-        # If we were passed a string, parse it out as a Component
-        if isinstance(ical, str):
-            try:
-                ical = Component.fromString(ical)
-            except ValueError:
-                raise ValueError("Not a calendar: %r" % (ical,))
+        # Make sure input is valid
+        ical = self.validCalendar(ical)
 
-        if ical is None or ical.name() != "VCALENDAR":
-            raise ValueError("Not a calendar: %r" % (ical,))
-
         # Pre-process the calendar data based on expand and limit options
         if self.calendardata.freebusy_set:
             ical = self.limitFreeBusy(ical)

Modified: CalendarServer/branches/users/cdaboo/per-user-icalendar-4669/twistedcaldav/datafilters/filter.py
===================================================================
--- CalendarServer/branches/users/cdaboo/per-user-icalendar-4669/twistedcaldav/datafilters/filter.py	2009-11-02 19:11:51 UTC (rev 4692)
+++ CalendarServer/branches/users/cdaboo/per-user-icalendar-4669/twistedcaldav/datafilters/filter.py	2009-11-02 21:47:40 UTC (rev 4693)
@@ -13,12 +13,13 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 ##
+from twistedcaldav.ical import Component
 
 __all__ = [
-    "ICalendarFilter",
+    "CalendarFilter",
 ]
 
-class ICalendarFilter(object):
+class CalendarFilter(object):
     """
     Abstract class that defines an iCalendar filter/merge object
     """
@@ -48,3 +49,17 @@
         @type icalold: L{Component}
         """
         raise NotImplementedError
+
+    def validCalendar(self, ical):
+
+        # If we were passed a string, parse it out as a Component
+        if isinstance(ical, str):
+            try:
+                ical = Component.fromString(ical)
+            except ValueError:
+                raise ValueError("Not a calendar: %r" % (ical,))
+        
+        if ical is None or ical.name() != "VCALENDAR":
+            raise ValueError("Not a calendar: %r" % (ical,))
+        
+        return ical

Added: CalendarServer/branches/users/cdaboo/per-user-icalendar-4669/twistedcaldav/datafilters/peruserdata.py
===================================================================
--- CalendarServer/branches/users/cdaboo/per-user-icalendar-4669/twistedcaldav/datafilters/peruserdata.py	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/per-user-icalendar-4669/twistedcaldav/datafilters/peruserdata.py	2009-11-02 21:47:40 UTC (rev 4693)
@@ -0,0 +1,180 @@
+##
+# 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.filter import CalendarFilter
+
+__all__ = [
+    "PerUserDataFilter",
+]
+
+"""
+Object model for calendar data is as follows:
+
+VCALENDAR
+  VTIMEZONE*
+  VEVENT* / VTODO* / VJOURNAL*
+  BEGIN:X-CALENDARSERVER-PERUSER*
+    X-CALENDARSERVER-PERUSER-UID
+    UID
+    BEGIN:X-CALENDARSERVER-PERINSTANCE
+      RECURRENCE-ID?
+      TRANSP?
+      VALARM*
+
+So we will store per user data inside the top-level component (alongside VEVENT, VTODO etc). That new component will
+contain properties to identify the user and the UID of the VEVENT, VTODO it affects. It will contain sub-components
+for each instance overridden by the per-user data. These per-user overridden components may not correspond to an
+actual overridden component. In that situation the server has to re-construct the per-user data appropriately:
+
+e.g., 
+
+1. VEVENT contains an overridden instance, but X-CALENDARSERVER-PERUSER does not - server uses the must instance
+X-CALENDARSERVER-PERUSER data (if any) for the overridden instance.
+
+2. VEVENT does not contain an overridden instance, but X-CALENDARSERVER-PERUSER does - server synthesizes an
+overridden instance to match the X-CALENDARSERVER-PERUSER one.
+
+3. VEVENT contains overridden instance and X-CALENDARSERVER-PERUSER does - server merges X-CALENDARSERVER-PERUSER
+data into overridden instance.
+
+"""
+
+class PerUserDataFilter(CalendarFilter):
+    """
+    Filter per-user data
+    """
+
+    PERUSER_COMPONENT     = "X-CALENDARSERVER-PERUSER"
+    PERUSER_UID           = "X-CALENDARSERVER-PERUSER-UID"
+    PERINSTANCE_COMPONENT = "X-CALENDARSERVER-PERINSTANCE"
+
+    def __init__(self, uid):
+        """
+        
+        @param uid: unique identifier of the user for whom the data is being filtered 
+        @type uid: C{str}
+        """
+        
+        self.uid = uid
+    
+    def filter(self, ical):
+        """
+        Filter the supplied iCalendar (vobject) data using the request information.
+        Assume that the object is a CalDAV calendar resource.
+
+        @param ical: iCalendar object - this will be modified and returned
+        @type ical: L{Component} or C{str}
+        
+        @return: L{Component} for the filtered calendar data
+        """
+        
+        # Make sure input is valid
+        ical = self.validCalendar(ical)
+
+        # Look for matching per-user sub-component, removing all the others
+        peruser_component = None
+        for component in tuple(ical.subcomponents()):
+            if component.name() == PerUserDataFilter.PERUSER_COMPONENT:
+                
+                # Check user id - remove if not matches
+                if component.propertyValue(PerUserDataFilter.PERUSER_UID) != self.uid:
+                    ical.removeComponent(component)
+                elif peruser_component is None:
+                    peruser_component = component
+                    ical.removeComponent(component)
+                else:
+                    raise AssertionError("Can't have two X-CALENDARSERVER-PERUSER components for the same user")
+
+        # Now transfer any components over
+        if peruser_component:
+            self._mergeBack(ical, peruser_component)
+
+        return ical
+
+    def merge(self, icalnew, icalold):
+        """
+        Private event merging does not happen
+        """
+        raise NotImplementedError
+
+    def _mergeBack(self, ical, peruser):
+        """
+        Merge the per-user data back into the main calendar data.
+
+        @param ical: main calendar data to merge into
+        @type ical: L{Component}
+        @param peruser: the per-user data to merge in
+        @type peruser: L{Component}
+        """
+        
+        # Iterate over each instance in the per-user data and build mapping
+        peruser_recurrence_map = {}
+        for subcomponent in peruser.subcomponents():
+            if subcomponent.name() != PerUserDataFilter.PERINSTANCE_COMPONENT:
+                raise AssertionError("Wrong sub-component '%s' in a X-CALENDARSERVER-PERUSER component" % (subcomponent.name(),))
+            peruser_recurrence_map[subcomponent.getRecurrenceIDUTC()] = subcomponent
+            
+        ical_recurrence_set = set(ical.getComponentInstances())
+        peruser_recurrence_set = set(peruser_recurrence_map.keys())
+        
+        # Set operations to find union and differences
+        union_set = ical_recurrence_set.intersection(peruser_recurrence_set)
+        ical_only_set = ical_recurrence_set.difference(peruser_recurrence_set)
+        peruser_only_set = peruser_recurrence_set.difference(ical_recurrence_set)
+        
+        # For ones in per-user data but no main data, we synthesize an instance and copy over per-user data
+        # NB We have to do this before we do any merge that may change the master
+        if ical.masterComponent() is not None:
+            for rid in peruser_only_set:
+                ical_component = ical.deriveInstance(rid)
+                peruser_component = peruser_recurrence_map[rid]
+                self._mergeBackComponent(ical_component, peruser_component)
+                ical.addComponent(ical_component)
+        elif peruser_only_set:
+            raise AssertionError("Cannot derive a per-user instance when there is no master component.")
+                    
+        # Process the unions by merging in per-user data
+        for rid in union_set:
+            ical_component = ical.overriddenComponent(rid)
+            peruser_component = peruser_recurrence_map[rid]
+            self._mergeBackComponent(ical_component, peruser_component)
+
+        # For ones in main data but no per-user data, we try and copy over the master per-user data
+        if ical_only_set:
+            peruser_master = peruser_recurrence_map.get(None)
+            if peruser_master:
+                for rid in ical_only_set:
+                    ical_component = ical.overriddenComponent(rid)
+                    self._mergeBackComponent(ical_component, peruser_master)
+                    
+    def _mergeBackComponent(self, ical, peruser):
+        """
+        Copy all properties and sub-components from per-user data into the main component
+        @param ical:
+        @type ical:
+        @param peruser:
+        @type peruser:
+        """
+        
+        # Each sub-component
+        for subcomponent in peruser.subcomponents():
+            ical.addComponent(subcomponent)
+        
+        # Each property except RECURRENCE-ID
+        for property in peruser.properties():
+            if property.name() == "RECURRENCE-ID":
+                continue
+            ical.addProperty(property)

Modified: CalendarServer/branches/users/cdaboo/per-user-icalendar-4669/twistedcaldav/datafilters/privateevents.py
===================================================================
--- CalendarServer/branches/users/cdaboo/per-user-icalendar-4669/twistedcaldav/datafilters/privateevents.py	2009-11-02 19:11:51 UTC (rev 4692)
+++ CalendarServer/branches/users/cdaboo/per-user-icalendar-4669/twistedcaldav/datafilters/privateevents.py	2009-11-02 21:47:40 UTC (rev 4693)
@@ -19,13 +19,14 @@
 from twistedcaldav.caldavxml import Property, CalendarData, CalendarComponent,\
     AllProperties, AllComponents
 from twistedcaldav.datafilters.calendardata import CalendarDataFilter
+from twistedcaldav.datafilters.filter import CalendarFilter
 from twistedcaldav.ical import Component
 
 __all__ = [
     "PrivateEventFilter",
 ]
 
-class PrivateEventFilter(object):
+class PrivateEventFilter(CalendarFilter):
     """
     Filter a private event to match the rights of the non-owner user accessing the data
     """

Added: CalendarServer/branches/users/cdaboo/per-user-icalendar-4669/twistedcaldav/datafilters/test/test_peruserdata.py
===================================================================
--- CalendarServer/branches/users/cdaboo/per-user-icalendar-4669/twistedcaldav/datafilters/test/test_peruserdata.py	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/per-user-icalendar-4669/twistedcaldav/datafilters/test/test_peruserdata.py	2009-11-02 21:47:40 UTC (rev 4693)
@@ -0,0 +1,837 @@
+##
+# 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.
+##
+
+import twistedcaldav.test.util
+from twistedcaldav.ical import Component
+from twistedcaldav.datafilters.peruserdata import PerUserDataFilter
+
+class PerUserDataTestNotRecurring (twistedcaldav.test.util.TestCase):
+
+    def test_public_noperuser(self):
+        
+        data = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+        
+        for item in (data, Component.fromString(data),):
+            self.assertEqual(str(PerUserDataFilter("user01").filter(item)), data)
+
+    def test_public_oneuser(self):
+        
+        data = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+TRANSP:OPAQUE
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+        result01 = """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
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+        result02 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+        
+        for item in (data, Component.fromString(data),):
+            self.assertEqual(str(PerUserDataFilter("user01").filter(item)), result01)
+        for item in (data, Component.fromString(data),):
+            self.assertEqual(str(PerUserDataFilter("user02").filter(item)), result02)
+
+    def test_public_twousers(self):
+        
+        data = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test01
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+TRANSP:OPAQUE
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user02
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test02
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+TRANSP:TRANSPARENT
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+        result01 = """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
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test01
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+        result02 = """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
+TRANSP:TRANSPARENT
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test02
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+        result03 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+        for item in (data, Component.fromString(data),):
+            self.assertEqual(str(PerUserDataFilter("user01").filter(item)), result01)
+        for item in (data, Component.fromString(data),):
+            self.assertEqual(str(PerUserDataFilter("user02").filter(item)), result02)
+        for item in (data, Component.fromString(data),):
+            self.assertEqual(str(PerUserDataFilter("user03").filter(item)), result03)
+
+class PerUserDataTestRecurring (twistedcaldav.test.util.TestCase):
+
+    def test_public_noperuser(self):
+        
+        data = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+        
+        for item in (data, Component.fromString(data),):
+            self.assertEqual(str(PerUserDataFilter("user01").filter(item)), data)
+
+    def test_public_oneuser_master(self):
+        
+        data = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+TRANSP:OPAQUE
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+        result01 = """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
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+        result02 = """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:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+        
+        for item in (data, Component.fromString(data),):
+            self.assertEqual(str(PerUserDataFilter("user01").filter(item)), result01)
+        for item in (data, Component.fromString(data),):
+            self.assertEqual(str(PerUserDataFilter("user02").filter(item)), result02)
+
+    def test_public_oneuser_master_and_override(self):
+        
+        data = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-master
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+TRANSP:OPAQUE
+END:X-CALENDARSERVER-PERINSTANCE
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20080602T120000Z
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-override
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+TRANSP:TRANSPARENT
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+        result01 = """BEGIN:VCALENDAR
+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
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-master
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+TRANSP:TRANSPARENT
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-override
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+        result02 = """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:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+        
+        for item in (data, Component.fromString(data),):
+            self.assertEqual(str(PerUserDataFilter("user01").filter(item)), result01)
+        for item in (data, Component.fromString(data),):
+            self.assertEqual(str(PerUserDataFilter("user02").filter(item)), result02)
+
+    def test_public_oneuser_override(self):
+        
+        data = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20080602T120000Z
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-override
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+TRANSP:TRANSPARENT
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+        result01 = """BEGIN:VCALENDAR
+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:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+TRANSP:TRANSPARENT
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-override
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+        result02 = """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:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+        
+        for item in (data, Component.fromString(data),):
+            self.assertEqual(str(PerUserDataFilter("user01").filter(item)), result01)
+        for item in (data, Component.fromString(data),):
+            self.assertEqual(str(PerUserDataFilter("user02").filter(item)), result02)
+
+    def test_public_oneuser_master_derived_override(self):
+        
+        data = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-master
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+TRANSP:OPAQUE
+END:X-CALENDARSERVER-PERINSTANCE
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20080602T120000Z
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-override
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+TRANSP:TRANSPARENT
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+        result01 = """BEGIN:VCALENDAR
+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
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-master
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+TRANSP:TRANSPARENT
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-override
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+        result02 = """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
+END:VCALENDAR
+""".replace("\n", "\r\n")
+        
+        for item in (data, Component.fromString(data),):
+            self.assertEqual(str(PerUserDataFilter("user01").filter(item)), result01)
+        for item in (data, Component.fromString(data),):
+            self.assertEqual(str(PerUserDataFilter("user02").filter(item)), result02)
+
+    def test_public_oneuser_master_derived_override_x2(self):
+        
+        data = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-master-1
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+TRANSP:OPAQUE
+END:X-CALENDARSERVER-PERINSTANCE
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20080602T120000Z
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-override-1
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+TRANSP:TRANSPARENT
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user02
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-master-2
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+TRANSP:OPAQUE
+END:X-CALENDARSERVER-PERINSTANCE
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20080603T120000Z
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-override-2
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+TRANSP:TRANSPARENT
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+        result01 = """BEGIN:VCALENDAR
+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
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-master-1
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+TRANSP:TRANSPARENT
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-override-1
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+        result02 = """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
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-master-2
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080603T120000Z
+DTSTART:20080603T120000Z
+DTEND:20080603T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+TRANSP:TRANSPARENT
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-override-2
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+        
+        for item in (data, Component.fromString(data),):
+            self.assertEqual(str(PerUserDataFilter("user01").filter(item)), result01)
+        for item in (data, Component.fromString(data),):
+            self.assertEqual(str(PerUserDataFilter("user02").filter(item)), result02)
+
+    def test_public_oneuser_no_master_and_override(self):
+        
+        data = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20080602T120000Z
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-override
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+TRANSP:TRANSPARENT
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+        result01 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+TRANSP:TRANSPARENT
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-override
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+        result02 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+        
+        for item in (data, Component.fromString(data),):
+            self.assertEqual(str(PerUserDataFilter("user01").filter(item)), result01)
+        for item in (data, Component.fromString(data),):
+            self.assertEqual(str(PerUserDataFilter("user02").filter(item)), result02)
+

Modified: CalendarServer/branches/users/cdaboo/per-user-icalendar-4669/twistedcaldav/ical.py
===================================================================
--- CalendarServer/branches/users/cdaboo/per-user-icalendar-4669/twistedcaldav/ical.py	2009-11-02 19:11:51 UTC (rev 4692)
+++ CalendarServer/branches/users/cdaboo/per-user-icalendar-4669/twistedcaldav/ical.py	2009-11-02 21:47:40 UTC (rev 4693)
@@ -418,7 +418,7 @@
         
         mtype = None
         for component in self.subcomponents():
-            if component.name() == "VTIMEZONE":
+            if component.name() == "VTIMEZONE" or component.name().startswith("X-"):
                 continue
             elif mtype and (mtype != component.name()):
                 raise ValueError("Component contains more than one type of primary type: %r" % (self,))
@@ -437,7 +437,7 @@
         
         result = None
         for component in self.subcomponents():
-            if component.name() == "VTIMEZONE":
+            if component.name() == "VTIMEZONE" or component.name().startswith("X-"):
                 continue
             elif not allow_multiple and (result is not None):
                 raise ValueError("Calendar contains more than one primary component: %r" % (self,))
@@ -457,7 +457,7 @@
         assert self.name() == "VCALENDAR", "Must be a VCALENDAR: %r" % (self,)
         
         for component in self.subcomponents():
-            if component.name() == "VTIMEZONE":
+            if component.name() == "VTIMEZONE" or component.name().startswith("X-"):
                 continue
             if not component.hasProperty("RECURRENCE-ID"):
                 return component
@@ -476,7 +476,7 @@
         assert self.name() == "VCALENDAR", "Must be a VCALENDAR: %r" % (self,)
         
         for component in self.subcomponents():
-            if component.name() == "VTIMEZONE":
+            if component.name() == "VTIMEZONE" or component.name().startswith("X-"):
                 continue
             rid = component.getRecurrenceIDUTC()
             if rid and recurrence_id and compareDateTime(rid, recurrence_id) == 0:
@@ -1018,7 +1018,7 @@
         if self.name() == "VCALENDAR":
             result = ()
             for component in self.subcomponents():
-                if component.name() != "VTIMEZONE":
+                if component.name() != "VTIMEZONE" and not component.name().startswith("X-"):
                     result += component.getComponentInstances()
             return result
         else:
@@ -1033,7 +1033,7 @@
         # Extract appropriate sub-component if this is a VCALENDAR
         if self.name() == "VCALENDAR":
             for component in self.subcomponents():
-                if component.name() != "VTIMEZONE" and component.isRecurring():
+                if component.name() != "VTIMEZONE" and not component.name().startswith("X-") and component.isRecurring():
                     return True
         else:
             for propname in ("RRULE", "RDATE", "EXDATE", "RECURRENCE-ID",):
@@ -1161,7 +1161,7 @@
 
         if not hasattr(self, "_resource_uid"):
             for subcomponent in self.subcomponents():
-                if subcomponent.name() != "VTIMEZONE":
+                if subcomponent.name() != "VTIMEZONE" and not subcomponent.name().startswith("X-"):
                     self._resource_uid = subcomponent.propertyValue("UID")
                     break
             else:
@@ -1183,6 +1183,8 @@
                 name = subcomponent.name()
                 if name == "VTIMEZONE":
                     has_timezone = True
+                elif subcomponent.name().startswith("X-"):
+                    continue
                 else:
                     self._resource_type = name
                     break
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20091102/5f2ca6ed/attachment-0001.html>


More information about the calendarserver-changes mailing list