[CalendarServer-changes] [5387] CalendarServer/branches/users/cdaboo/shared-calendars-5187/ twistedcaldav

source_changes at macosforge.org source_changes at macosforge.org
Tue Mar 23 15:46:31 PDT 2010


Revision: 5387
          http://trac.macosforge.org/projects/calendarserver/changeset/5387
Author:   cdaboo at apple.com
Date:     2010-03-23 15:46:30 -0700 (Tue, 23 Mar 2010)
Log Message:
-----------
Per-user calendar data support. This includes per-user freebusy in the index (with a major
revision to the schema there).

Modified Paths:
--------------
    CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/datafilters/peruserdata.py
    CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/datafilters/test/test_peruserdata.py
    CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/ical.py
    CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/icaldav.py
    CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/index.py
    CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/method/delete_common.py
    CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/method/get.py
    CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/method/put_common.py
    CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/method/report_calquery.py
    CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/method/report_common.py
    CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/query/sqlgenerator.py
    CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/resource.py
    CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/scheduling/implicit.py
    CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/scheduling/itip.py
    CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/scheduling/processing.py
    CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/scheduling/utils.py
    CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/sql.py
    CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/static.py
    CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/test/test_icalendar.py
    CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/test/test_index.py

Modified: CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/datafilters/peruserdata.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/datafilters/peruserdata.py	2010-03-23 22:40:37 UTC (rev 5386)
+++ CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/datafilters/peruserdata.py	2010-03-23 22:46:30 UTC (rev 5387)
@@ -59,6 +59,7 @@
     """
 
     # If any of these change also change the vobject behaviors in this module's __init__.py
+    # and update usage in ical.py
     PERUSER_COMPONENT     = "X-CALENDARSERVER-PERUSER"
     PERUSER_UID           = "X-CALENDARSERVER-PERUSER-UID"
     PERINSTANCE_COMPONENT = "X-CALENDARSERVER-PERINSTANCE"
@@ -206,22 +207,21 @@
 
     def _splitPerUserData(self, ical):
         
-        peruser_component = None
-        perinstance_components = {}
-
         def init_peruser_component():
             peruser = Component(PerUserDataFilter.PERUSER_COMPONENT)
             peruser.addProperty(Property("UID", ical.resourceUID()))
             peruser.addProperty(Property(PerUserDataFilter.PERUSER_UID, self.uid))
             ical.addComponent(peruser)
             return peruser
-            
-        for component in ical.subcomponents():
+        
+        components = tuple(ical.subcomponents())
+        peruser_component = init_peruser_component() if self.uid else None
+        perinstance_components = {}
+
+        for component in components:
             if component.name() == "VTIMEZONE":
                 continue
 
-            perinstance_component = None
-            
             def init_perinstance_component():
                 peruser = Component(PerUserDataFilter.PERINSTANCE_COMPONENT)
                 rid = component.getRecurrenceIDUTC()
@@ -230,41 +230,38 @@
                 perinstance_components[rid] = peruser
                 return peruser
 
+            perinstance_component = init_perinstance_component() if self.uid else None
+            
             # 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 peruser_component is None:
-                        peruser_component = init_peruser_component()
-                    if perinstance_component is None:
-                        perinstance_component = init_perinstance_component()
-                    perinstance_component.addProperty(property)
+                    if self.uid:
+                        perinstance_component.addProperty(property)
                     component.removeProperty(property)
             
             # Transfer per-user components from main component to per-instance component
             for subcomponent in tuple(component.subcomponents()):
                 if subcomponent.name() in PerUserDataFilter.PERUSER_SUBCOMPONENTS or subcomponent.name().startswith("X-"):
-                    if peruser_component is None:
-                        peruser_component = init_peruser_component()
-                    if perinstance_component is None:
-                        perinstance_component = init_perinstance_component()
-                    perinstance_component.addComponent(subcomponent)
+                    if self.uid:
+                        perinstance_component.addComponent(subcomponent)
                     component.removeComponent(subcomponent)
             
-        # Add unique per-instance components into the per-user component
-        master_perinstance = perinstance_components.get(None)
-        master_perinstance_txt = str(master_perinstance)
-        if master_perinstance:
-            peruser_component.addComponent(master_perinstance)
-        for rid, perinstance in perinstance_components.iteritems():
-            if rid is None:
-                continue
-            perinstance_txt = str(perinstance)
-            perinstance_txt = "".join([line for line in perinstance_txt.splitlines(True) if not line.startswith("RECURRENCE-ID:")])
-            if master_perinstance is None or perinstance_txt != master_perinstance_txt:
-                peruser_component.addComponent(perinstance)
+        if self.uid:
+            # Add unique per-instance components into the per-user component
+            master_perinstance = perinstance_components.get(None)
+            master_perinstance_txt = str(master_perinstance)
+            if master_perinstance:
+                peruser_component.addComponent(master_perinstance)
+            for rid, perinstance in perinstance_components.iteritems():
+                if rid is None:
+                    continue
+                perinstance_txt = str(perinstance)
+                perinstance_txt = "".join([line for line in perinstance_txt.splitlines(True) if not line.startswith("RECURRENCE-ID:")])
+                if master_perinstance is None or perinstance_txt != master_perinstance_txt:
+                    peruser_component.addComponent(perinstance)
+    
+            self._compactInstances(ical)
 
-        self._compactInstances(ical)
-
     def _compactInstances(self, ical):
         """
         Remove recurrences instances that are the same as their master-derived counterparts. This gives the most

Modified: CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/datafilters/test/test_peruserdata.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/datafilters/test/test_peruserdata.py	2010-03-23 22:40:37 UTC (rev 5386)
+++ CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/datafilters/test/test_peruserdata.py	2010-03-23 22:46:30 UTC (rev 5387)
@@ -38,6 +38,8 @@
         
         for item in (data, Component.fromString(data),):
             self.assertEqual(str(PerUserDataFilter("user01").filter(item)), data)
+        for item in (data, Component.fromString(data),):
+            self.assertEqual(str(PerUserDataFilter("").filter(item)), data)
 
     def test_public_oneuser(self):
         
@@ -103,6 +105,8 @@
             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("").filter(item)), result02)
 
     def test_public_twousers(self):
         
@@ -201,6 +205,8 @@
             self.assertEqual(str(PerUserDataFilter("user02").filter(item)), result02)
         for item in (data, Component.fromString(data),):
             self.assertEqual(str(PerUserDataFilter("user03").filter(item)), result03)
+        for item in (data, Component.fromString(data),):
+            self.assertEqual(str(PerUserDataFilter("").filter(item)), result03)
 
 class PerUserDataFilterTestRecurring (twistedcaldav.test.util.TestCase):
 
@@ -232,6 +238,8 @@
         
         for item in (data, Component.fromString(data),):
             self.assertEqual(str(PerUserDataFilter("user01").filter(item)), data)
+        for item in (data, Component.fromString(data),):
+            self.assertEqual(str(PerUserDataFilter("").filter(item)), data)
 
     def test_public_oneuser_master(self):
         
@@ -333,6 +341,8 @@
             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("").filter(item)), result02)
 
     def test_public_oneuser_master_and_override(self):
         
@@ -443,6 +453,8 @@
             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("").filter(item)), result02)
 
     def test_public_oneuser_override(self):
         
@@ -539,6 +551,8 @@
             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("").filter(item)), result02)
 
     def test_public_oneuser_master_derived_override(self):
         
@@ -631,6 +645,8 @@
             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("").filter(item)), result02)
 
     def test_public_oneuser_master_derived_override_x2(self):
         
@@ -760,11 +776,27 @@
 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
+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)
+        for item in (data, Component.fromString(data),):
+            self.assertEqual(str(PerUserDataFilter("").filter(item)), result03)
 
     def test_public_oneuser_no_master_and_override(self):
         
@@ -834,6 +866,8 @@
             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("").filter(item)), result02)
 
 class PerUserDataMergeTestNewNotRecurring (twistedcaldav.test.util.TestCase):
 
@@ -852,9 +886,30 @@
 END:VEVENT
 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
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
         
         for item in (data, Component.fromString(data),):
-            self.assertEqual(str(PerUserDataFilter("user01").merge(item, None)), data)
+            self.assertEqual(str(PerUserDataFilter("user01").merge(item, None)), result01)
+        for item in (data, Component.fromString(data),):
+            self.assertEqual(str(PerUserDataFilter("").merge(item, None)), data)
 
     def test_public_oneuser(self):
         
@@ -902,9 +957,24 @@
 END:X-CALENDARSERVER-PERUSER
 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").merge(item, None)), result01)
+        for item in (data, Component.fromString(data),):
+            self.assertEqual(str(PerUserDataFilter("").merge(item, None)), result02)
 
 class PerUserDataMergeTestNewRecurring (twistedcaldav.test.util.TestCase):
 
@@ -933,9 +1003,40 @@
 END:VEVENT
 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
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
         
         for item in (data, Component.fromString(data),):
-            self.assertEqual(str(PerUserDataFilter("user01").merge(item, None)), data)
+            self.assertEqual(str(PerUserDataFilter("user01").merge(item, None)), result01)
+        for item in (data, Component.fromString(data),):
+            self.assertEqual(str(PerUserDataFilter("").merge(item, None)), data)
 
     def test_public_oneuser_master(self):
         
@@ -1009,9 +1110,34 @@
 END:X-CALENDARSERVER-PERUSER
 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").merge(item, None)), result01)
+        for item in (data, Component.fromString(data),):
+            self.assertEqual(str(PerUserDataFilter("").merge(item, None)), result02)
 
     def test_public_oneuser_master_and_override(self):
         
@@ -1094,9 +1220,34 @@
 END:X-CALENDARSERVER-PERUSER
 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").merge(item, None)), result01)
+        for item in (data, Component.fromString(data),):
+            self.assertEqual(str(PerUserDataFilter("").merge(item, None)), result02)
 
     def test_public_oneuser_override(self):
         
@@ -1154,6 +1305,8 @@
 UID:12345-67890
 X-CALENDARSERVER-PERUSER-UID:user01
 BEGIN:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERINSTANCE
+BEGIN:X-CALENDARSERVER-PERINSTANCE
 RECURRENCE-ID:20080602T120000Z
 TRANSP:TRANSPARENT
 BEGIN:VALARM
@@ -1165,9 +1318,34 @@
 END:X-CALENDARSERVER-PERUSER
 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").merge(item, None)), result01)
+        for item in (data, Component.fromString(data),):
+            self.assertEqual(str(PerUserDataFilter("").merge(item, None)), result02)
 
     def test_public_oneuser_master_compact_override(self):
         
@@ -1241,9 +1419,34 @@
 END:X-CALENDARSERVER-PERUSER
 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:20080602T120000Z
+DTEND:20080602T130000Z
+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").merge(item, None)), result01)
+        for item in (data, Component.fromString(data),):
+            self.assertEqual(str(PerUserDataFilter("").merge(item, None)), result02)
 
     def test_public_oneuser_master_noncompact_override(self):
         
@@ -1326,9 +1529,34 @@
 END:X-CALENDARSERVER-PERUSER
 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").merge(item, None)), result01)
+        for item in (data, Component.fromString(data),):
+            self.assertEqual(str(PerUserDataFilter("").merge(item, None)), result02)
 
 class PerUserDataMergeTestExistingNotRecurring (twistedcaldav.test.util.TestCase):
 
@@ -1347,6 +1575,25 @@
 END:VEVENT
 END:VCALENDAR
 """.replace("\n", "\r\n")
+        newresult = """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
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
         olddata = """BEGIN:VCALENDAR
 VERSION:2.0
 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
@@ -1363,7 +1610,7 @@
         
         for olditem in (olddata, Component.fromString(olddata),):
             for newitem in (newdata, Component.fromString(newdata),):
-                self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), newdata)
+                self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), newresult)
 
     def test_public_oneuser(self):
         
@@ -1606,6 +1853,12 @@
 END:VEVENT
 BEGIN:X-CALENDARSERVER-PERUSER
 UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
 X-CALENDARSERVER-PERUSER-UID:user02
 BEGIN:X-CALENDARSERVER-PERINSTANCE
 TRANSP:OPAQUE
@@ -1641,6 +1894,26 @@
 END:VEVENT
 END:VCALENDAR
 """.replace("\n", "\r\n")
+        newresult = """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
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
         olddata = """BEGIN:VCALENDAR
 VERSION:2.0
 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
@@ -1657,7 +1930,7 @@
         
         for olditem in (olddata, Component.fromString(olddata),):
             for newitem in (newdata, Component.fromString(newdata),):
-                self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), newdata)
+                self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), newresult)
 
     def test_public_noperuser_master_with_override(self):
         
@@ -1684,6 +1957,35 @@
 END:VEVENT
 END:VCALENDAR
 """.replace("\n", "\r\n")
+        newresult = """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
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
         olddata = """BEGIN:VCALENDAR
 VERSION:2.0
 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
@@ -1700,7 +2002,7 @@
         
         for olditem in (olddata, Component.fromString(olddata),):
             for newitem in (newdata, Component.fromString(newdata),):
-                self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), newdata)
+                self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), newresult)
 
     def test_public_noperuser_only_override(self):
         
@@ -1718,6 +2020,27 @@
 END:VEVENT
 END:VCALENDAR
 """.replace("\n", "\r\n")
+        newresult = """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
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
         olddata = """BEGIN:VCALENDAR
 VERSION:2.0
 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
@@ -1734,7 +2057,7 @@
         
         for olditem in (olddata, Component.fromString(olddata),):
             for newitem in (newdata, Component.fromString(newdata),):
-                self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), newdata)
+                self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), newresult)
 
     def test_public_oneuser_master(self):
         
@@ -2401,6 +2724,12 @@
 END:VEVENT
 BEGIN:X-CALENDARSERVER-PERUSER
 UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
 X-CALENDARSERVER-PERUSER-UID:user02
 BEGIN:X-CALENDARSERVER-PERINSTANCE
 TRANSP:OPAQUE
@@ -2503,6 +2832,12 @@
 END:VEVENT
 BEGIN:X-CALENDARSERVER-PERUSER
 UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
 X-CALENDARSERVER-PERUSER-UID:user02
 BEGIN:X-CALENDARSERVER-PERINSTANCE
 TRANSP:OPAQUE
@@ -2586,6 +2921,13 @@
 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
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
 END:VCALENDAR
 """.replace("\n", "\r\n")
         
@@ -2611,6 +2953,26 @@
 END:VEVENT
 END:VCALENDAR
 """.replace("\n", "\r\n")
+        newresult = """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
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
         olddata = """BEGIN:VCALENDAR
 VERSION:2.0
 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
@@ -2628,7 +2990,7 @@
         
         for olditem in (olddata, Component.fromString(olddata),):
             for newitem in (newdata, Component.fromString(newdata),):
-                self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), newdata)
+                self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), newresult)
 
     def test_public_noperuser_master_with_override(self):
         
@@ -2646,6 +3008,26 @@
 END:VEVENT
 END:VCALENDAR
 """.replace("\n", "\r\n")
+        newresult = """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
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
         olddata = """BEGIN:VCALENDAR
 VERSION:2.0
 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
@@ -2672,7 +3054,7 @@
         
         for olditem in (olddata, Component.fromString(olddata),):
             for newitem in (newdata, Component.fromString(newdata),):
-                self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), newdata)
+                self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), newresult)
 
     def test_public_noperuser_only_override(self):
         
@@ -2689,6 +3071,25 @@
 END:VEVENT
 END:VCALENDAR
 """.replace("\n", "\r\n")
+        newresult = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T110000Z
+DTEND:20080601T120000Z
+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
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
         olddata = """BEGIN:VCALENDAR
 VERSION:2.0
 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
@@ -2706,7 +3107,7 @@
         
         for olditem in (olddata, Component.fromString(olddata),):
             for newitem in (newdata, Component.fromString(newdata),):
-                self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), newdata)
+                self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), newresult)
 
     def test_public_oneuser_master(self):
         
@@ -3336,6 +3737,12 @@
 END:VEVENT
 BEGIN:X-CALENDARSERVER-PERUSER
 UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
 X-CALENDARSERVER-PERUSER-UID:user02
 BEGIN:X-CALENDARSERVER-PERINSTANCE
 TRANSP:OPAQUE
@@ -3438,6 +3845,12 @@
 END:VEVENT
 BEGIN:X-CALENDARSERVER-PERUSER
 UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
 X-CALENDARSERVER-PERUSER-UID:user02
 BEGIN:X-CALENDARSERVER-PERINSTANCE
 TRANSP:OPAQUE
@@ -3522,6 +3935,12 @@
 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
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
 END:VCALENDAR
 """.replace("\n", "\r\n")
         
@@ -3547,6 +3966,26 @@
 END:VEVENT
 END:VCALENDAR
 """.replace("\n", "\r\n")
+        newresult = """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
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
         olddata = """BEGIN:VCALENDAR
 VERSION:2.0
 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
@@ -3564,7 +4003,7 @@
         
         for olditem in (olddata, Component.fromString(olddata),):
             for newitem in (newdata, Component.fromString(newdata),):
-                self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), newdata)
+                self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), newresult)
 
     def test_public_oneuser(self):
         
@@ -3816,6 +4255,12 @@
 END:VEVENT
 BEGIN:X-CALENDARSERVER-PERUSER
 UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
 X-CALENDARSERVER-PERUSER-UID:user02
 BEGIN:X-CALENDARSERVER-PERINSTANCE
 TRANSP:OPAQUE
@@ -3910,6 +4355,12 @@
 END:VEVENT
 BEGIN:X-CALENDARSERVER-PERUSER
 UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
 X-CALENDARSERVER-PERUSER-UID:user02
 BEGIN:X-CALENDARSERVER-PERINSTANCE
 TRANSP:OPAQUE
@@ -3954,6 +4405,35 @@
 END:VEVENT
 END:VCALENDAR
 """.replace("\n", "\r\n")
+        newresult = """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
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
         olddata = """BEGIN:VCALENDAR
 VERSION:2.0
 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
@@ -3980,7 +4460,7 @@
         
         for olditem in (olddata, Component.fromString(olddata),):
             for newitem in (newdata, Component.fromString(newdata),):
-                self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), newdata)
+                self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), newresult)
 
     def test_public_oneuser(self):
         
@@ -4388,6 +4868,12 @@
 END:VEVENT
 BEGIN:X-CALENDARSERVER-PERUSER
 UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
 X-CALENDARSERVER-PERUSER-UID:user02
 BEGIN:X-CALENDARSERVER-PERINSTANCE
 TRANSP:OPAQUE
@@ -4423,6 +4909,27 @@
 END:VEVENT
 END:VCALENDAR
 """.replace("\n", "\r\n")
+        newresult = """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
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
         olddata = """BEGIN:VCALENDAR
 VERSION:2.0
 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
@@ -4440,7 +4947,7 @@
         
         for olditem in (olddata, Component.fromString(olddata),):
             for newitem in (newdata, Component.fromString(newdata),):
-                self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), newdata)
+                self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), newresult)
 
     def test_public_oneuser(self):
         
@@ -4685,6 +5192,13 @@
 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
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
 END:VCALENDAR
 """.replace("\n", "\r\n")
         

Modified: CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/ical.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/ical.py	2010-03-23 22:40:37 UTC (rev 5386)
+++ CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/ical.py	2010-03-23 22:46:30 UTC (rev 5387)
@@ -131,6 +131,8 @@
     "ORGANIZER":    normalizeCUAddr,
 }
 
+ignoredComponents = ("VTIMEZONE", "X-CALENDARSERVER-PERUSER",)
+
 class InvalidICalendarDataError(ValueError):
     pass
 
@@ -423,7 +425,7 @@
         
         mtype = None
         for component in self.subcomponents():
-            if component.name() == "VTIMEZONE" or component.name().startswith("X-"):
+            if component.name() in ignoredComponents:
                 continue
             elif mtype and (mtype != component.name()):
                 raise InvalidICalendarDataError("Component contains more than one type of primary type: %r" % (self,))
@@ -442,7 +444,7 @@
         
         result = None
         for component in self.subcomponents():
-            if component.name() == "VTIMEZONE" or component.name().startswith("X-"):
+            if component.name() in ignoredComponents:
                 continue
             elif not allow_multiple and (result is not None):
                 raise InvalidICalendarDataError("Calendar contains more than one primary component: %r" % (self,))
@@ -462,7 +464,7 @@
         assert self.name() == "VCALENDAR", "Must be a VCALENDAR: %r" % (self,)
         
         for component in self.subcomponents():
-            if component.name() == "VTIMEZONE" or component.name().startswith("X-"):
+            if component.name() in ignoredComponents:
                 continue
             if not component.hasProperty("RECURRENCE-ID"):
                 return component
@@ -481,7 +483,7 @@
         assert self.name() == "VCALENDAR", "Must be a VCALENDAR: %r" % (self,)
         
         for component in self.subcomponents():
-            if component.name() == "VTIMEZONE" or component.name().startswith("X-"):
+            if component.name() in ignoredComponents:
                 continue
             rid = component.getRecurrenceIDUTC()
             if rid and recurrence_id and dateordatetime(rid) == recurrence_id:
@@ -758,10 +760,6 @@
         if self.name() not in ("VEVENT", ):
             return "FREE"
         
-        # If it is TRANSPARENT we always ignore it
-        if self.propertyValue("TRANSP") == "TRANSPARENT":
-            return "FREE"
-        
         # Handle status
         status = self.propertyValue("STATUS")
         if status == "CANCELLED":
@@ -1023,7 +1021,7 @@
         if self.name() == "VCALENDAR":
             result = ()
             for component in self.subcomponents():
-                if component.name() != "VTIMEZONE" and not component.name().startswith("X-"):
+                if component.name() not in ignoredComponents:
                     result += component.getComponentInstances()
             return result
         else:
@@ -1038,7 +1036,7 @@
         # Extract appropriate sub-component if this is a VCALENDAR
         if self.name() == "VCALENDAR":
             for component in self.subcomponents():
-                if component.name() != "VTIMEZONE" and not component.name().startswith("X-") and component.isRecurring():
+                if component.name() not in ignoredComponents and component.isRecurring():
                     return True
         else:
             for propname in ("RRULE", "RDATE", "EXDATE", "RECURRENCE-ID",):
@@ -1211,7 +1209,7 @@
 
         if not hasattr(self, "_resource_uid"):
             for subcomponent in self.subcomponents():
-                if subcomponent.name() != "VTIMEZONE" and not subcomponent.name().startswith("X-"):
+                if subcomponent.name() not in ignoredComponents:
                     self._resource_uid = subcomponent.propertyValue("UID")
                     break
             else:
@@ -1233,7 +1231,7 @@
                 name = subcomponent.name()
                 if name == "VTIMEZONE":
                     has_timezone = True
-                elif subcomponent.name().startswith("X-"):
+                elif subcomponent.name() in ignoredComponents:
                     continue
                 else:
                     self._resource_type = name
@@ -1306,6 +1304,8 @@
         
             if subcomponent.name() == "VTIMEZONE":
                 timezones.add(subcomponent.propertyValue("TZID"))
+            elif subcomponent.name() in ignoredComponents:
+                continue
             else:
                 if ctype is None:
                     ctype = subcomponent.name()
@@ -1524,7 +1524,7 @@
         # Extract appropriate sub-component if this is a VCALENDAR
         if self.name() == "VCALENDAR":
             for component in self.subcomponents():
-                if component.name() != "VTIMEZONE":
+                if component.name() not in ignoredComponents:
                     return component.getOrganizer()
         else:
             try:
@@ -1546,7 +1546,7 @@
         if self.name() == "VCALENDAR":
             result = ()
             for component in self.subcomponents():
-                if component.name() != "VTIMEZONE":
+                if component.name() not in ignoredComponents:
                     result += component.getOrganizersByInstance()
             return result
         else:
@@ -1570,7 +1570,7 @@
         # Extract appropriate sub-component if this is a VCALENDAR
         if self.name() == "VCALENDAR":
             for component in self.subcomponents():
-                if component.name() != "VTIMEZONE":
+                if component.name() not in ignoredComponents:
                     return component.getOrganizerProperty()
         else:
             try:
@@ -1604,7 +1604,7 @@
         # Extract appropriate sub-component if this is a VCALENDAR
         if self.name() == "VCALENDAR":
             for component in self.subcomponents():
-                if component.name() != "VTIMEZONE":
+                if component.name() not in ignoredComponents:
                     return component.getAttendees()
         else:
             # Find the property values
@@ -1627,7 +1627,7 @@
         if self.name() == "VCALENDAR":
             result = ()
             for component in self.subcomponents():
-                if component.name() != "VTIMEZONE":
+                if component.name() not in ignoredComponents:
                     result += component.getAttendeesByInstance(makeUnique, onlyScheduleAgentServer)
             return result
         else:
@@ -1665,7 +1665,7 @@
         # Extract appropriate sub-component if this is a VCALENDAR
         if self.name() == "VCALENDAR":
             for component in self.subcomponents():
-                if component.name() != "VTIMEZONE":
+                if component.name() not in ignoredComponents:
                     attendee = component.getAttendeeProperty(match)
                     if attendee is not None:
                         return attendee
@@ -1690,7 +1690,7 @@
         # Extract appropriate sub-component if this is a VCALENDAR
         results = []
         for component in self.subcomponents():
-            if component.name() != "VTIMEZONE":
+            if component.name() not in ignoredComponents:
                 attendee = component.getAttendeeProperty(match)
                 if attendee:
                     results.append(attendee)
@@ -1707,7 +1707,7 @@
         # Extract appropriate sub-component if this is a VCALENDAR
         if self.name() == "VCALENDAR":
             for component in self.subcomponents():
-                if component.name() != "VTIMEZONE":
+                if component.name() not in ignoredComponents:
                     for attendee in component.getAllAttendeeProperties():
                         yield attendee
         else:
@@ -1726,7 +1726,7 @@
         # Extract appropriate sub-component if this is a VCALENDAR
         if self.name() == "VCALENDAR":
             for component in self.subcomponents():
-                if component.name() != "VTIMEZONE":
+                if component.name() not in ignoredComponents:
                     return component.getMaskUID()
         else:
             try:
@@ -1752,7 +1752,7 @@
         """
         
         for component in self.subcomponents():
-            if component.name() == "VTIMEZONE":
+            if component.name() in ignoredComponents:
                 continue
             for property in component.properties(propname):
                 if propvalue is None or property.value() == propvalue:
@@ -1785,7 +1785,7 @@
         """
 
         for component in self.subcomponents():
-            if component.name() == "VTIMEZONE":
+            if component.name() in ignoredComponents:
                 continue
             found = component.getProperty(property.name())
             if not found or found.value() != property.value():
@@ -1802,7 +1802,7 @@
         """
         
         for component in self.subcomponents():
-            if component.name() == "VTIMEZONE":
+            if component.name() in ignoredComponents:
                 continue
             component.addProperty(property)
 
@@ -1813,7 +1813,7 @@
         """
         
         for component in self.subcomponents():
-            if component.name() == "VTIMEZONE":
+            if component.name() in ignoredComponents:
                 continue
             component.replaceProperty(property)
     
@@ -1832,7 +1832,7 @@
         
         if self.name() == "VCALENDAR":
             for component in self.subcomponents():
-                if component.name() == "VTIMEZONE":
+                if component.name() in ignoredComponents:
                     continue
                 component.transferProperties(from_calendar, properties)
         else:
@@ -1862,7 +1862,7 @@
         master_component = None
         removed_master = False
         for component in self.subcomponents():
-            if component.name() == "VTIMEZONE":
+            if component.name() in ignoredComponents:
                 continue
             found_all_attendees = True
             for attendee in attendees:
@@ -1906,7 +1906,7 @@
         components = tuple(self.subcomponents())
         remaining = len(components)
         for component in components:
-            if component.name() == "VTIMEZONE":
+            if component.name() in ignoredComponents:
                 remaining -= 1
                 continue
             rid = component.getRecurrenceIDUTC()
@@ -1924,7 +1924,7 @@
         assert self.name() == "VCALENDAR", "Not a calendar: %r" % (self,)
 
         for component in self.subcomponents():
-            if component.name() == "VTIMEZONE":
+            if component.name() in ignoredComponents:
                 continue
             [component.removeProperty(p) for p in tuple(component.properties("ATTENDEE")) if p.value().lower() != attendee.lower()]
             
@@ -1935,7 +1935,7 @@
 
         if self.name() == "VCALENDAR":
             for component in self.subcomponents():
-                if component.name() == "VTIMEZONE":
+                if component.name() in ignoredComponents:
                     continue
                 component.removeAlarms()
         else:
@@ -1952,13 +1952,22 @@
             for component in self.subcomponents():
                 component.filterProperties(remove, keep, do_subcomponents=False)
         else:
-            if self.name() == "VTIMEZONE":
+            if self.name() in ignoredComponents:
                 return
             
             for p in tuple(self.properties()):
                 if (keep and p.name() not in keep) or (remove and p.name() in remove):
                     self.removeProperty(p)
                 
+    def removeXComponents(self, keep_components=()):
+        """
+        Remove all X- properties except the specified ones
+        """
+
+        for component in tuple(self.subcomponents()):
+            if component.name().startswith("X-") and component.name() not in keep_components:
+                self.removeComponent(component)
+            
     def removeXProperties(self, keep_properties=(), remove_x_parameters=True, do_subcomponents=True):
         """
         Remove all X- properties except the specified ones
@@ -1968,7 +1977,7 @@
             for component in self.subcomponents():
                 component.removeXProperties(keep_properties, remove_x_parameters, do_subcomponents=False)
         else:
-            if self.name() == "VTIMEZONE":
+            if self.name() in ignoredComponents:
                 return
             for p in tuple(self.properties()):
                 xpname = p.name().startswith("X-")
@@ -1986,7 +1995,7 @@
 
         if self.name() == "VCALENDAR":
             for component in self.subcomponents():
-                if component.name() == "VTIMEZONE":
+                if component.name() in ignoredComponents:
                     continue
                 component.removePropertyParameters(property, params)
         else:
@@ -2005,7 +2014,7 @@
 
         if self.name() == "VCALENDAR":
             for component in self.subcomponents():
-                if component.name() == "VTIMEZONE":
+                if component.name() in ignoredComponents:
                     continue
                 component.removePropertyParametersByValue(property, paramvalues)
         else:
@@ -2126,7 +2135,7 @@
         
         if self.name() == "VCALENDAR":
             for component in self.subcomponents():
-                if component.name() == "VTIMEZONE":
+                if component.name() in ignoredComponents:
                     continue
                 component.normalizePropertyValueLists(propname)
         else:
@@ -2143,7 +2152,7 @@
         
         if self.name() == "VCALENDAR":
             for component in self.subcomponents():
-                if component.name() == "VTIMEZONE":
+                if component.name() in ignoredComponents:
                     continue
                 component.normalizeAttachments()
         else:
@@ -2165,7 +2174,7 @@
         @type lookupFunction: L{Function}
         """
         for component in self.subcomponents():
-            if component.name() == "VTIMEZONE":
+            if component.name() in ignoredComponents:
                 continue
             for prop in itertools.chain(
                 component.properties("ORGANIZER"),
@@ -2250,7 +2259,49 @@
                         except KeyError:
                             pass
 
+
+    def allPerUserUIDs(self):
         
+        results = set()
+        for component in self.subcomponents():
+            if component.name() == "X-CALENDARSERVER-PERUSER":
+                results.add(component.propertyValue("X-CALENDARSERVER-PERUSER-UID"))
+        return results
+
+    def perUserTransparency(self, rid):
+        
+        # We will create a cache of all user/rid/transparency values as we will likely
+        # be calling this a lot
+        if not hasattr(self, "_perUserTransparency"):
+            self._perUserTransparency = {}
+            
+            # Do per-user data
+            for component in self.subcomponents():
+                if component.name() == "X-CALENDARSERVER-PERUSER":
+                    uid = component.propertyValue("X-CALENDARSERVER-PERUSER-UID")
+                    for subcomponent in component.subcomponents():
+                        if subcomponent.name() == "X-CALENDARSERVER-PERINSTANCE":
+                            instancerid = subcomponent.propertyValue("RECURRENCE-ID")
+                            transp = subcomponent.propertyValue("TRANSP") == "TRANSPARENT"                                
+                            self._perUserTransparency.setdefault(uid, {})[instancerid] = transp
+                elif component.name() not in ignoredComponents:
+                    instancerid = component.propertyValue("RECURRENCE-ID")
+                    transp = component.propertyValue("TRANSP") == "TRANSPARENT"                    
+                    self._perUserTransparency.setdefault("", {})[instancerid] = transp
+
+        # Now lookup in cache
+        results = []
+        for uid, cachedRids in sorted(self._perUserTransparency.items(), key=lambda x:x[0]):
+            lookupRid = rid
+            if lookupRid not in cachedRids:
+                lookupRid = None
+            if lookupRid in cachedRids:
+                results.append((uid, cachedRids[lookupRid],))
+            else:
+                results.append((uid, False,))     
+        
+        return tuple(results)
+
 ##
 # Dates and date-times
 ##

Modified: CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/icaldav.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/icaldav.py	2010-03-23 22:40:37 UTC (rev 5386)
+++ CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/icaldav.py	2010-03-23 22:46:30 UTC (rev 5387)
@@ -94,20 +94,6 @@
             of type C{"VCALENDAR"}.
         """
 
-    def iCalendarXML(name=None):
-        """
-        Constructs a CalDAV XML element representing this resource or its child
-        with the given name.
-        The behavior of this method is not specified if it is called on a
-        resource that is not a calendar collection or a calendar resource within
-        a calendar collection.
-        @param name: the name of the desired child of this resource, or None
-            if this resource is desired.  Must be None if this resource is
-            not a calendar collection.
-        @return: a L{twistedcaldav.caldavxml.CalendarData} containing the
-            iCalendar data for the requested resource.
-        """
-
 class ICalendarPrincipalResource(IDAVResource):
     """
     CalDAV principle resource.

Modified: CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/index.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/index.py	2010-03-23 22:40:37 UTC (rev 5386)
+++ CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/index.py	2010-03-23 22:46:30 UTC (rev 5387)
@@ -57,7 +57,7 @@
 log = Logger()
 
 db_basename = db_prefix + "sqlite"
-schema_version = "9"
+schema_version = "10"
 collection_types = {"Calendar": "Regular Calendar Collection", "iTIP": "iTIP Calendar Collection"}
 
 icalfbtype_to_indexfbtype = {
@@ -297,7 +297,7 @@
         
         return changed, deleted,
 
-    def indexedSearch(self, filter, fbtype=False):
+    def indexedSearch(self, filter, useruid="", fbtype=False):
         """
         Finds resources matching the given qualifiers.
         @param filter: the L{Filter} for the calendar-query to execute.
@@ -334,10 +334,25 @@
             rowiter = self._db_execute("select NAME, UID, TYPE from RESOURCE")
         else:
             if fbtype:
+                # Lookup the useruid - try the empty (default) one if needed
+                dbuseruid = self._db_value_for_sql(
+                    "select PERUSERID from PERUSER where USERUID == :1",
+                    useruid,
+                )
+                count = self._db_value_for_sql(
+                    "select COUNT(PERUSERID) from TRANSPARENCY where PERUSERID == :1",
+                    dbuseruid,
+                )
+                if dbuseruid is None or count == 0:
+                    dbuseruid = self._db_value_for_sql(
+                        "select PERUSERID from PERUSER where USERUID == :1",
+                        "",
+                    )
+                    
                 # For a free-busy time-range query we return all instances
                 rowiter = self._db_execute(
-                    "select RESOURCE.NAME, RESOURCE.UID, RESOURCE.TYPE, RESOURCE.ORGANIZER, TIMESPAN.FLOAT, TIMESPAN.START, TIMESPAN.END, TIMESPAN.FBTYPE" + 
-                    qualifiers[0],
+                    "select RESOURCE.NAME, RESOURCE.UID, RESOURCE.TYPE, RESOURCE.ORGANIZER, TIMESPAN.FLOAT, TIMESPAN.START, TIMESPAN.END, TIMESPAN.FBTYPE, TRANSPARENCY.TRANSPARENT" + 
+                    qualifiers[0] + " AND TRANSPARENCY.PERUSERID == '%s'" % (dbuseruid,),
                     *qualifiers[1]
                 )
             else:
@@ -429,6 +444,7 @@
         q.execute(
             """
             create table RESOURCE (
+                RESOURCEID     integer primary key autoincrement,
                 NAME           text unique,
                 UID            text%s,
                 TYPE           text,
@@ -454,11 +470,12 @@
         q.execute(
             """
             create table TIMESPAN (
-                NAME   text,
-                FLOAT  text(1),
-                START  date,
-                END    date,
-                FBTYPE text(1)
+                INSTANCEID   integer primary key autoincrement,
+                RESOURCEID   integer,
+                FLOAT        text(1),
+                START        date,
+                END          date,
+                FBTYPE       text(1)
             )
             """
         )
@@ -469,6 +486,41 @@
         )
 
         #
+        # PERUSER table tracks per-user ids
+        #   PERUSERID: autoincrement primary key
+        #   UID: User ID used in calendar data
+        #
+        q.execute(
+            """
+            create table PERUSER (
+                PERUSERID       integer primary key autoincrement,
+                USERUID         text
+            )
+            """
+        )
+        q.execute(
+            """
+            create index PERUSER_UID on PERUSER (USERUID)
+            """
+        )
+
+        #
+        # TRANSPARENCY table tracks per-user per-instance transparency
+        #   PERUSERID: user id key
+        #   INSTANCEID: instance id key
+        #   TRANSPARENT: Y if transparent, N if opaque
+        #
+        q.execute(
+            """
+            create table TRANSPARENCY (
+                PERUSERID       integer,
+                INSTANCEID      integer,
+                TRANSPARENT     text(1)
+            )
+            """
+        )
+
+        #
         # REVISIONS table tracks changes
         #   NAME: Last URI component (eg. <uid>.ics, RESOURCE primary key)
         #   REVISION: revision number
@@ -505,42 +557,42 @@
                 """
             )
 
+        # Cascading triggers to help on delete
+        q.execute(
+            """
+            create trigger resourceDelete after delete on RESOURCE
+            for each row
+            begin
+                delete from TIMESPAN where TIMESPAN.RESOURCEID = OLD.RESOURCEID;
+            end
+            """
+        )
+        q.execute(
+            """
+            create trigger timespanDelete after delete on TIMESPAN
+            for each row
+            begin
+                delete from TRANSPARENCY where INSTANCEID = OLD.INSTANCEID;
+            end
+            """
+        )
+        
     def _db_can_upgrade(self, old_version):
         """
         Can we do an in-place upgrade
         """
         
-        # Previous versions can be upgraded as per _db_upgrade_data_tables
-        return True
+        # v10 is a big change - no upgrade possible
+        return False
 
     def _db_upgrade_data_tables(self, q, old_version):
         """
         Upgrade the data from an older version of the DB.
         """
 
-        # When going to version 7+ all we need to do is add a column to the resource and timespan
-        if old_version < "7":
-            q.execute("alter table RESOURCE add column ORGANIZER text default '?'")
-            q.execute("alter table TIMESPAN add column FBTYPE text(1) default '?'")
+        # v10 is a big change - no upgrade possible
+        pass
 
-        # When going to version 8+ all we need to do is add an index
-        if old_version < "8":
-            q.execute("create index STARTENDFLOAT on TIMESPAN (START, END, FLOAT)")
-
-        # When going to version 9+ all we need to do is add revision table and index
-        if old_version < "9":
-            q.execute(
-                """
-                create table REVISIONS (
-                    NAME            text unique,
-                    REVISION        integer,
-                    CREATEDREVISION integer,
-                    WASDELETED      text(1)
-                )
-                """
-            )
-            q.execute("create index REVISION on REVISIONS (REVISION)")
-
     def notExpandedBeyond(self, minDate):
         """
         Gives all resources which have not been expanded beyond a given date
@@ -597,6 +649,35 @@
 
         self._delete_from_db(name, uid, None)
 
+        # Add RESOURCE item
+        self._db_execute(
+            """
+            insert into RESOURCE (NAME, UID, TYPE, RECURRANCE_MAX, ORGANIZER)
+            values (:1, :2, :3, :4, :5)
+            """, name, uid, calendar.resourceType(), instances.limit, organizer
+        )
+        resourceid = self.lastrowid
+
+        # Get a set of all referenced per-user UIDs and map those to entries already
+        # in the DB and add new ones as needed
+        useruids = calendar.allPerUserUIDs()
+        useruids.add("")
+        useruidmap = {}
+        for useruid in useruids:
+            peruserid = self._db_value_for_sql(
+                "select PERUSERID from PERUSER where USERUID = :1",
+                useruid
+            )
+            if peruserid is None:
+                self._db_execute(
+                    """
+                    insert into PERUSER (USERUID)
+                    values (:1)
+                    """, useruid
+                )
+                peruserid = self.lastrowid
+            useruidmap[useruid] = peruserid
+            
         for key in instances:
             instance = instances[key]
             start = instance.start.replace(tzinfo=utc)
@@ -604,10 +685,21 @@
             float = 'Y' if instance.start.tzinfo is None else 'N'
             self._db_execute(
                 """
-                insert into TIMESPAN (NAME, FLOAT, START, END, FBTYPE)
+                insert into TIMESPAN (RESOURCEID, FLOAT, START, END, FBTYPE)
                 values (:1, :2, :3, :4, :5)
-                """, name, float, start, end, icalfbtype_to_indexfbtype.get(instance.component.getFBType(), 'F')
+                """, resourceid, float, start, end, icalfbtype_to_indexfbtype.get(instance.component.getFBType(), 'F')
             )
+            instanceid = self.lastrowid
+            peruserdata = calendar.perUserTransparency(instance.rid)
+            for useruid, transp in peruserdata:
+                peruserid = useruidmap[useruid]
+                self._db_execute(
+                    """
+                    insert into TRANSPARENCY (PERUSERID, INSTANCEID, TRANSPARENT)
+                    values (:1, :2, :3)
+                    """, peruserid, instanceid, 'T' if transp else 'F'
+                )
+                    
 
         # Special - for unbounded recurrence we insert a value for "infinity"
         # that will allow an open-ended time-range to always match it.
@@ -617,18 +709,21 @@
             float = 'N'
             self._db_execute(
                 """
-                insert into TIMESPAN (NAME, FLOAT, START, END, FBTYPE)
+                insert into TIMESPAN (RESOURCEID, FLOAT, START, END, FBTYPE)
                 values (:1, :2, :3, :4, :5)
-                """, name, float, start, end, '?'
+                """, resourceid, float, start, end, '?'
             )
+            instanceid = self.lastrowid
+            peruserdata = calendar.perUserTransparency(None)
+            for useruid, transp in peruserdata:
+                peruserid = useruidmap[useruid]
+                self._db_execute(
+                    """
+                    insert into TRANSPARENCY (PERUSERID, INSTANCEID, TRANSPARENT)
+                    values (:1, :2, :3)
+                    """, peruserid, instanceid, 'T' if transp else 'F'
+                )
             
-        self._db_execute(
-            """
-            insert into RESOURCE (NAME, UID, TYPE, RECURRANCE_MAX, ORGANIZER)
-            values (:1, :2, :3, :4, :5)
-            """, name, uid, calendar.resourceType(), instances.limit, organizer
-        )
-
         if revision is not None:
             created = self._db_value_for_sql("select CREATEDREVISION from REVISIONS where NAME = :1", name)
             if created is None:
@@ -646,7 +741,6 @@
         @param name: the name of the resource to delete.
         @param uid: the uid of the resource to delete.
         """
-        self._db_execute("delete from TIMESPAN where NAME = :1", name)
         self._db_execute("delete from RESOURCE where NAME = :1", name)
         if revision is not None:
             self._db_execute(

Modified: CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/method/delete_common.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/method/delete_common.py	2010-03-23 22:40:37 UTC (rev 5386)
+++ CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/method/delete_common.py	2010-03-23 22:46:30 UTC (rev 5387)
@@ -143,7 +143,7 @@
         lock = None
         if not self.internal_request and self.allowImplicitSchedule:
             # Get data we need for implicit scheduling
-            calendar = delresource.iCalendar()
+            calendar = (yield delresource.iCalendarForUser(self.request))
             scheduler = ImplicitScheduler()
             do_implicit_action, _ignore = (yield scheduler.testImplicitSchedulingDELETE(self.request, delresource, calendar))
             if do_implicit_action:

Modified: CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/method/get.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/method/get.py	2010-03-23 22:40:37 UTC (rev 5386)
+++ CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/method/get.py	2010-03-23 22:46:30 UTC (rev 5387)
@@ -22,6 +22,7 @@
 
 from twisted.internet.defer import inlineCallbacks, returnValue
 from twext.web2.dav import davxml
+from twext.web2.dav.util import parentForURL
 from twext.web2.http import HTTPError
 from twext.web2.http import Response
 from twext.web2.http_headers import MimeType
@@ -30,41 +31,47 @@
 from twistedcaldav.caldavxml import ScheduleTag
 from twistedcaldav.customxml import TwistedCalendarAccessProperty
 from twistedcaldav.datafilters.privateevents import PrivateEventFilter
+from twistedcaldav.resource import isPseudoCalendarCollectionResource
 
 @inlineCallbacks
 def http_GET(self, request):
 
     # Look for calendar access restriction on existing resource.
     if self.exists():
-        try:
-            access = self.readDeadProperty(TwistedCalendarAccessProperty)
-        except HTTPError:
-            access = None
-            
-        if access:
+        parentURL = parentForURL(request.uri)
+        parent = (yield request.locateResource(parentURL))
+        if isPseudoCalendarCollectionResource(parent):
     
             # Check authorization first
             yield self.authorize(request, (davxml.Read(),))
 
-            # Non DAV:owner's have limited access to the data
-            isowner = (yield self.isOwner(request, adminprincipals=True, readprincipals=True))
-            
-            # Now "filter" the resource calendar data
-            caldata = PrivateEventFilter(access, isowner).filter(self.iCalendarText())
+            caldata = (yield self.iCalendarForUser(request))
 
+            try:
+                access = self.readDeadProperty(TwistedCalendarAccessProperty)
+            except HTTPError:
+                access = None
+                
+            if access:
+        
+                # Non DAV:owner's have limited access to the data
+                isowner = (yield self.isOwner(request, adminprincipals=True, readprincipals=True))
+                
+                # Now "filter" the resource calendar data
+                caldata = PrivateEventFilter(access, isowner).filter(caldata)
+    
             response = Response()
             response.stream = MemoryStream(str(caldata))
             response.headers.setHeader("content-type", MimeType.fromString("text/calendar; charset=utf-8"))
+    
+            # Add Schedule-Tag header if property is present
+            if self.hasDeadProperty(ScheduleTag):
+                scheduletag = self.readDeadProperty(ScheduleTag)
+                if scheduletag:
+                    response.headers.setHeader("Schedule-Tag", str(scheduletag))
+        
             returnValue(response)
 
-
     # Do normal GET behavior
     response = (yield super(CalDAVFile, self).http_GET(request))
-    
-    # Add Schedule-Tag header if property is present
-    if self.exists() and self.hasDeadProperty(ScheduleTag):
-        scheduletag = self.readDeadProperty(ScheduleTag)
-        if scheduletag:
-            response.headers.setHeader("Schedule-Tag", str(scheduletag))
-
     returnValue(response)

Modified: CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/method/put_common.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/method/put_common.py	2010-03-23 22:40:37 UTC (rev 5386)
+++ CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/method/put_common.py	2010-03-23 22:46:30 UTC (rev 5387)
@@ -55,6 +55,7 @@
     TwistedCalendarHasPrivateCommentsProperty, TwistedSchedulingObjectResource,\
     TwistedScheduleMatchETags
 from twistedcaldav.customxml import TwistedCalendarAccessProperty
+from twistedcaldav.datafilters.peruserdata import PerUserDataFilter
 from twistedcaldav.fileops import copyToWithXAttrs, copyXAttrs
 from twistedcaldav.fileops import putWithXAttrs
 from twistedcaldav.fileops import copyWithXAttrs
@@ -315,7 +316,11 @@
                         raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "supported-calendar-data")))
                 
                     # At this point we need the calendar data to do more tests
-                    self.calendar = self.source.iCalendar()
+                    try:
+                        self.calendar = (yield self.source.iCalendarForUser(self.request))
+                    except ValueError, e:
+                        log.err(str(e))
+                        raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "valid-calendar-data"), description="Can't parse calendar data"))
                 else:
                     try:
                         if type(self.calendar) in (types.StringType, types.UnicodeType,):
@@ -364,7 +369,7 @@
 
                 # FIXME: We need this here because we have to re-index the destination. Ideally it
                 # would be better to copy the index entries from the source and add to the destination.
-                self.calendar = self.source.iCalendar()
+                self.calendar = (yield self.source.iCalendarForUser(self.request))
 
             # Check access
             if self.destinationcal and config.EnablePrivateEvents:
@@ -375,7 +380,7 @@
 
         elif self.sourcecal:
             self.source_index = self.sourceparent.index()
-            self.calendar = self.source.iCalendar()
+            self.calendar = (yield self.source.iCalendarForUser(self.request))
     
     @inlineCallbacks
     def validCopyMoveOperation(self):
@@ -692,6 +697,7 @@
         else:
             return False
 
+    @inlineCallbacks
     def preservePrivateComments(self):
         # Check for private comments on the old resource and the new resource and re-insert
         # ones that are lost.
@@ -709,14 +715,14 @@
             if old_has_private_comments and not new_has_private_comments:
                 # Transfer old comments to new calendar
                 log.debug("Private Comments properties were entirely removed by the client. Restoring existing properties.")
-                old_calendar = self.destination.iCalendar()
+                old_calendar = (yield self.destination.iCalendarForUser(self.request))
                 self.calendar.transferProperties(old_calendar, (
                     "X-CALENDARSERVER-PRIVATE-COMMENT",
                     "X-CALENDARSERVER-ATTENDEE-COMMENT",
                 ))
                 self.calendardata = None
         
-        return new_has_private_comments
+        returnValue(new_has_private_comments)
 
     @inlineCallbacks
     def doImplicitScheduling(self):
@@ -789,7 +795,26 @@
         returnValue((is_scheduling_resource, data_changed, did_implicit_action,))
 
     @inlineCallbacks
+    def mergePerUserData(self):
+        
+        if self.calendar:
+            accessUID = (yield self.destination.resourceOwnerPrincipal(self.request))
+            accessUID = accessUID.principalUID() if accessUID else ""
+            oldCal = self.destination.iCalendar() if self.destination.exists() and self.destinationcal else None
+            
+            # Duplicate before we do the merge because someone else may "own" the calendar object
+            # and we should not change it. This is not ideal as we may duplicate it unnecessarily
+            # but we currently have no api to let the caller tell us whether it cares about the
+            # whether the calendar data is changed or not.
+            self.calendar = PerUserDataFilter(accessUID).merge(self.calendar.duplicate(), oldCal)
+            self.calendardata = None
+            
+    @inlineCallbacks
     def doStore(self, implicit):
+
+        # Always do the per-user data merge right before we store
+        yield self.mergePerUserData()
+
         # Do put or copy based on whether source exists
         if self.source is not None:
             if implicit:
@@ -966,7 +991,7 @@
             rruleChanged = self.truncateRecurrence()
 
             # Preserve private comments
-            new_has_private_comments = self.preservePrivateComments()
+            new_has_private_comments = (yield self.preservePrivateComments())
     
             # Do scheduling
             implicit_result = (yield self.doImplicitScheduling())

Modified: CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/method/report_calquery.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/method/report_calquery.py	2010-03-23 22:40:37 UTC (rev 5386)
+++ CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/method/report_calquery.py	2010-03-23 22:46:30 UTC (rev 5387)
@@ -202,7 +202,7 @@
                     child_path_name = urllib.unquote(child_uri_name)
                     
                     if generate_calendar_data or not index_query_ok:
-                        calendar = calresource.iCalendar(child_path_name)
+                        calendar = (yield calresource.iCalendarForUser(request, child_path_name))
                         assert calendar is not None, "Calendar %s is missing from calendar collection %r" % (child_uri_name, self)
                     else:
                         calendar = None
@@ -226,7 +226,7 @@
             # Check private events access status
             isowner = (yield calresource.isOwner(request, adminprincipals=True, readprincipals=True))
 
-            calendar = calresource.iCalendar()
+            calendar = (yield calresource.iCalendarForUser(request))
             yield queryCalendarObjectResource(calresource, uri, None, calendar, timezone)
 
         returnValue(True)

Modified: CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/method/report_common.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/method/report_common.py	2010-03-23 22:40:37 UTC (rev 5386)
+++ CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/method/report_common.py	2010-03-23 22:46:30 UTC (rev 5387)
@@ -344,7 +344,7 @@
                 access = None
 
             if calendar is None:
-                calendar = resource.iCalendarText()
+                calendar = (yield resource.iCalendarForUser(request))
             filtered = PrivateEventFilter(access, isowner).filter(calendar)
             filtered = CalendarDataFilter(property, timezone).filter(filtered)
             propvalue = CalendarData().fromCalendar(filtered)
@@ -453,13 +453,17 @@
     filteredaces = (yield calresource.inheritedACEsforChildren(request))
 
     try:
-        resources = calresource.index().indexedSearch(filter, fbtype=True)
+        useruid = (yield calresource.resourceOwnerPrincipal(request))
+        useruid = useruid.principalUID() if useruid else ""
+        resources = calresource.index().indexedSearch(filter, useruid=useruid, fbtype=True)
     except IndexedSearchException:
         resources = calresource.index().bruteForceSearch()
 
     # We care about separate instances for VEVENTs only
     aggregated_resources = {}
-    for name, uid, type, test_organizer, float, start, end, fbtype in resources:
+    for name, uid, type, test_organizer, float, start, end, fbtype, transp in resources:
+        if transp == 'T' and fbtype != '?':
+            fbtype = 'F'
         aggregated_resources.setdefault((name, uid, type, test_organizer,), []).append((float, start, end, fbtype,))
 
     for key in aggregated_resources.iterkeys():
@@ -519,7 +523,7 @@
                     fbinfo[fbtype_index_mapper.get(fbtype, 0)].append(clipped)
                 
         else:
-            calendar = calresource.iCalendar(name)
+            calendar = (yield calresource.iCalendarForUser(request, name))
             
             # The calendar may come back as None if the resource is being changed, or was deleted
             # between our initial index query and getting here. For now we will ignore this error, but in

Modified: CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/query/sqlgenerator.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/query/sqlgenerator.py	2010-03-23 22:40:37 UTC (rev 5386)
+++ CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/query/sqlgenerator.py	2010-03-23 22:46:30 UTC (rev 5387)
@@ -30,23 +30,26 @@
 
 class sqlgenerator(object):
     
-    FROM          =" from "
-    WHERE         =" where "
-    RESOURCEDB    = "RESOURCE"
-    TIMESPANDB    = "TIMESPAN"
-    NOTOP         = "NOT "
-    ANDOP         = " AND "
-    OROP          = " OR "
-    CONTAINSOP    = " GLOB "
-    NOTCONTAINSOP = " NOT GLOB "
-    ISOP          = " == "
-    ISNOTOP       = " != "
-    INOP          = " IN "
-    NOTINOP       = " NOT IN "
+    FROM           =" from "
+    WHERE          =" where "
+    RESOURCEDB     = "RESOURCE"
+    TIMESPANDB     = "TIMESPAN"
+    TRANSPARENCYDB = "TRANSPARENCY"
+    PERUSERDB      = "PERUSER"
+    NOTOP          = "NOT "
+    ANDOP          = " AND "
+    OROP           = " OR "
+    CONTAINSOP     = " GLOB "
+    NOTCONTAINSOP  = " NOT GLOB "
+    ISOP           = " == "
+    ISNOTOP        = " != "
+    INOP           = " IN "
+    NOTINOP        = " NOT IN "
 
-    TIMESPANTEST         = "((TIMESPAN.FLOAT == 'N' AND TIMESPAN.START < %s AND TIMESPAN.END > %s) OR (TIMESPAN.FLOAT == 'Y' AND TIMESPAN.START < %s AND TIMESPAN.END > %s)) AND TIMESPAN.NAME == RESOURCE.NAME"
-    TIMESPANTEST_NOEND   = "((TIMESPAN.FLOAT == 'N' AND TIMESPAN.END > %s) OR (TIMESPAN.FLOAT == 'Y' AND TIMESPAN.END > %s)) AND TIMESPAN.NAME == RESOURCE.NAME"
-    TIMESPANTEST_NOSTART = "((TIMESPAN.FLOAT == 'N' AND TIMESPAN.START < %s) OR (TIMESPAN.FLOAT == 'Y' AND TIMESPAN.START < %s)) AND TIMESPAN.NAME == RESOURCE.NAME"
+    TIMESPANTEST         = "((TIMESPAN.FLOAT == 'N' AND TIMESPAN.START < %s AND TIMESPAN.END > %s) OR (TIMESPAN.FLOAT == 'Y' AND TIMESPAN.START < %s AND TIMESPAN.END > %s))"
+    TIMESPANTEST_NOEND   = "((TIMESPAN.FLOAT == 'N' AND TIMESPAN.END > %s) OR (TIMESPAN.FLOAT == 'Y' AND TIMESPAN.END > %s))"
+    TIMESPANTEST_NOSTART = "((TIMESPAN.FLOAT == 'N' AND TIMESPAN.START < %s) OR (TIMESPAN.FLOAT == 'Y' AND TIMESPAN.START < %s))"
+    TIMESPANTEST_TAIL_PIECE = " AND TIMESPAN.RESOURCEID == RESOURCE.RESOURCEID AND TIMESPAN.INSTANCEID == TRANSPARENCY.INSTANCEID"
 
     def __init__(self, expr):
         self.expression = expr
@@ -72,7 +75,7 @@
         # Prefix with ' from ...' partial statement
         select = self.FROM + self.RESOURCEDB
         if self.usedtimespan:
-            select += ", " + self.TIMESPANDB
+            select += ", %s, %s, %s" % (self.TIMESPANDB, self.TRANSPARENCYDB, self.PERUSERDB,)
         select += self.sout.getvalue()
         return select, self.arguments
         
@@ -134,6 +137,7 @@
                 arg1 = self.setArgument(expr.end)
                 arg2 = self.setArgument(expr.endfloat)
                 test = self.TIMESPANTEST_NOSTART % (arg1, arg2)
+            test += self.TIMESPANTEST_TAIL_PIECE
             self.sout.write(test)
             self.usedtimespan = True
         

Modified: CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/resource.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/resource.py	2010-03-23 22:40:37 UTC (rev 5386)
+++ CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/resource.py	2010-03-23 22:46:30 UTC (rev 5387)
@@ -64,6 +64,7 @@
 from twistedcaldav.config import config
 from twistedcaldav.customxml import TwistedCalendarAccessProperty
 from twistedcaldav.customxml import calendarserver_namespace
+from twistedcaldav.datafilters.peruserdata import PerUserDataFilter
 from twistedcaldav.extensions import DAVResource, DAVPrincipalResource,\
     PropertyNotFoundError
 from twistedcaldav.ical import Component
@@ -829,6 +830,19 @@
         except ValueError:
             return None
 
+    @inlineCallbacks
+    def iCalendarForUser(self, request, name=None):
+        
+        caldata = self.iCalendar(name)
+        
+        accessUID = (yield self.resourceOwnerPrincipal(request))
+        if accessUID is None:
+            accessUID = ""
+        else:
+            accessUID = accessUID.principalUID()
+
+        returnValue(PerUserDataFilter(accessUID).filter(caldata))
+
     def iCalendarRolledup(self, request):
         """
         See L{ICalDAVResource.iCalendarRolledup}.
@@ -852,14 +866,6 @@
         """
         return str(self.iCalendar(name))
 
-    def iCalendarXML(self, name=None):
-        """
-        See L{ICalDAVResource.iCalendarXML}.
-        This implementation returns an XML element constructed from the object
-        returned by L{iCalendar} when given the same arguments.
-        """
-        return caldavxml.CalendarData.fromCalendar(self.iCalendar(name))
-
     def iCalendarAddressDoNormalization(self, ical):
         """
         Normalize calendar user addresses in the supplied iCalendar object into their

Modified: CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/scheduling/implicit.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/scheduling/implicit.py	2010-03-23 22:40:37 UTC (rev 5386)
+++ CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/scheduling/implicit.py	2010-03-23 22:46:30 UTC (rev 5387)
@@ -71,7 +71,8 @@
         self.internal_request = internal_request
 
         existing_resource = resource.exists()
-        existing_type = "schedule" if self.checkSchedulingObjectResource(resource) else "calendar"
+        is_scheduling_object = (yield self.checkSchedulingObjectResource(resource))
+        existing_type = "schedule" if is_scheduling_object else "calendar"
         new_type = "schedule" if (yield self.checkImplicitState()) else "calendar"
 
         if existing_type == "calendar":
@@ -88,7 +89,7 @@
             # Also make sure that we return the new calendar being written rather than the old one
             # when the implicit action is executed
             self.return_calendar = calendar
-            self.calendar = resource.iCalendar()
+            self.calendar = (yield resource.iCalendarForUser(request))
             yield self.checkImplicitState()
         
         # Attendees are not allowed to overwrite one type with another
@@ -108,8 +109,8 @@
         new_type = "schedule" if (yield self.checkImplicitState()) else "calendar"
 
         dest_exists = destresource.exists()
-        dest_is_implicit = self.checkSchedulingObjectResource(destresource)
-        src_is_implicit = self.checkSchedulingObjectResource(srcresource) or new_type == "schedule"
+        dest_is_implicit = (yield self.checkSchedulingObjectResource(destresource))
+        src_is_implicit = (yield self.checkSchedulingObjectResource(srcresource)) or new_type == "schedule"
 
         if srccal and destcal:
             if src_is_implicit and dest_exists or dest_is_implicit:
@@ -138,8 +139,8 @@
 
         new_type = "schedule" if (yield self.checkImplicitState()) else "calendar"
 
-        dest_is_implicit = self.checkSchedulingObjectResource(destresource)
-        src_is_implicit = self.checkSchedulingObjectResource(srcresource) or new_type == "schedule"
+        dest_is_implicit = (yield self.checkSchedulingObjectResource(destresource))
+        src_is_implicit = (yield self.checkSchedulingObjectResource(srcresource)) or new_type == "schedule"
 
         if srccal and destcal:
             if src_is_implicit or dest_is_implicit:
@@ -167,11 +168,13 @@
 
         yield self.checkImplicitState()
 
-        resource_type = "schedule" if self.checkSchedulingObjectResource(resource) else "calendar"
+        is_scheduling_object = (yield self.checkSchedulingObjectResource(resource))
+        resource_type = "schedule" if is_scheduling_object else "calendar"
         self.action = "remove" if resource_type == "schedule" else "none"
 
         returnValue((self.action != "none", False,))
 
+    @inlineCallbacks
     def checkSchedulingObjectResource(self, resource):
         
         if resource and resource.exists():
@@ -180,24 +183,24 @@
             except HTTPError:
                 implicit = None
             if implicit is not None:
-                return implicit != "false"
+                returnValue(implicit != "false")
             else:
-                calendar = resource.iCalendar()
+                calendar = (yield resource.iCalendarForUser(self.request))
                 # Get the ORGANIZER and verify it is the same for all components
                 try:
                     organizer = calendar.validOrganizerForScheduling()
                 except ValueError:
                     # We have different ORGANIZERs in the same iCalendar object - this is an error
-                    return False
+                    returnValue(False)
                 organizerPrincipal = resource.principalForCalendarUserAddress(organizer) if organizer else None
                 resource.writeDeadProperty(TwistedSchedulingObjectResource("true" if organizerPrincipal != None else "false"))
                 log.debug("Implicit - checked scheduling object resource state for UID: '%s', result: %s" % (
                     calendar.resourceUID(),
                     "true" if organizerPrincipal != None else "false",
                 ))
-                return organizerPrincipal != None
+                returnValue(organizerPrincipal != None)
 
-        return False
+        returnValue(False)
         
     @inlineCallbacks
     def checkImplicitState(self):
@@ -397,7 +400,8 @@
                 child = (yield self.request.locateResource(joinURL(collection_uri, rname)))
                 if child == check_resource:
                     returnValue(True)
-                matched_type = "schedule" if self.checkSchedulingObjectResource(child) else "calendar"
+                is_scheduling_object = (yield self.checkSchedulingObjectResource(child))
+                matched_type = "schedule" if is_scheduling_object else "calendar"
                 if (
                     collection_uri != check_parent_uri and
                     (type == "schedule" or matched_type == "schedule")
@@ -494,7 +498,7 @@
         elif self.action == "modify":
 
             # Read in existing data
-            self.oldcalendar = self.resource.iCalendar()
+            self.oldcalendar = (yield self.resource.iCalendarForUser(self.request))
             
             # Significant change
             no_change, self.changed_rids, reinvites, recurrence_reschedule = self.isOrganizerChangeInsignificant()
@@ -780,7 +784,7 @@
         else:
             # Make sure ORGANIZER is not changed
             if self.resource.exists():
-                self.oldcalendar = self.resource.iCalendar()
+                self.oldcalendar = (yield self.resource.iCalendarForUser(self.request))
                 oldOrganizer = self.oldcalendar.getOrganizer()
                 newOrganizer = self.calendar.getOrganizer()
                 if oldOrganizer != newOrganizer:
@@ -883,7 +887,7 @@
         self.organizer_calendar = None
         calendar_resource, _ignore_name, _ignore_collection, _ignore_uri = (yield getCalendarObjectForPrincipals(self.request, self.organizerPrincipal, self.uid))
         if calendar_resource:
-            self.organizer_calendar = calendar_resource.iCalendar()
+            self.organizer_calendar = (yield calendar_resource.iCalendarForUser(self.request))
         elif isinstance(self.organizerAddress, PartitionedCalendarUser):
             # For partitioning where the organizer is on a different node, we will assume that the attendee's copy
             # of the event is up to date and "authoritative". So we pretend that is the organizer copy

Modified: CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/scheduling/itip.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/scheduling/itip.py	2010-03-23 22:40:37 UTC (rev 5386)
+++ CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/scheduling/itip.py	2010-03-23 22:46:30 UTC (rev 5387)
@@ -667,12 +667,15 @@
         Remove properties and parameters that should not be sent in an iTIP message
         """
 
+        # All X- components go away
+        itip.removeXComponents()
+
         # Alarms
         itip.removeAlarms()
 
         # Top-level properties - remove all X-
         itip.removeXProperties(do_subcomponents=False)
-                
+
         # Component properties - remove all X- except for those specified
         if not reply:
             # Organizer properties that need to go to the Attendees

Modified: CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/scheduling/processing.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/scheduling/processing.py	2010-03-23 22:40:37 UTC (rev 5386)
+++ CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/scheduling/processing.py	2010-03-23 22:46:30 UTC (rev 5387)
@@ -136,7 +136,7 @@
         self.recipient_calendar_name = None
         calendar_resource, resource_name, calendar_collection, calendar_collection_uri = (yield getCalendarObjectForPrincipals(self.request, self.recipient.principal, self.uid))
         if calendar_resource:
-            self.recipient_calendar = calendar_resource.iCalendar()
+            self.recipient_calendar = (yield calendar_resource.iCalendarForUser(self.request))
             self.recipient_calendar_collection = calendar_collection
             self.recipient_calendar_collection_uri = calendar_collection_uri
             self.recipient_calendar_name = resource_name
@@ -709,7 +709,7 @@
         calendar_resource, _ignore_name, _ignore_collection, _ignore_uri = (yield getCalendarObjectForPrincipals(self.request, self.originator.principal, self.uid))
         if not calendar_resource:
             raise ImplicitProcessorException("5.1;Service unavailable")
-        originator_calendar = calendar_resource.iCalendar()
+        originator_calendar = (yield calendar_resource.iCalendarForUser(self.request))
 
         # Get attendee's view of that
         originator_calendar.attendeesView((self.recipient.cuaddr,))

Modified: CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/scheduling/utils.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/scheduling/utils.py	2010-03-23 22:40:37 UTC (rev 5386)
+++ CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/scheduling/utils.py	2010-03-23 22:46:30 UTC (rev 5387)
@@ -16,6 +16,7 @@
 
 from twisted.internet.defer import inlineCallbacks, succeed, returnValue
 from twistedcaldav.method import report_common
+from twext.web2.dav.util import joinURL
 
 @inlineCallbacks
 def getCalendarObjectForPrincipals(request, principal, uid):
@@ -43,7 +44,10 @@
         def queryCalendarCollection(collection, uri):
             rname = collection.index().resourceNameForUID(uid)
             if rname:
-                result["resource"] = collection.getChild(rname)
+                resource = collection.getChild(rname)
+                request._rememberResource(resource, joinURL(uri, rname))
+
+                result["resource"] = resource
                 result["resource_name"] = rname
                 result["calendar_collection"] = collection
                 result["calendar_collection_uri"] = uri

Modified: CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/sql.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/sql.py	2010-03-23 22:40:37 UTC (rev 5386)
+++ CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/sql.py	2010-03-23 22:46:30 UTC (rev 5387)
@@ -327,6 +327,7 @@
         q = self._db().cursor()
         try:
             q.execute(sql, query_params)
+            self.lastrowid = q.lastrowid
             return q.fetchall()
         except DatabaseError:
             log.err("Exception while executing SQL on DB %s: %r %r" % (self, sql, query_params))

Modified: CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/static.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/static.py	2010-03-23 22:40:37 UTC (rev 5386)
+++ CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/static.py	2010-03-23 22:46:30 UTC (rev 5387)
@@ -71,6 +71,7 @@
 from twistedcaldav.client.reverseproxy import ReverseProxyResource
 from twistedcaldav.config import config
 from twistedcaldav.customxml import TwistedCalendarAccessProperty, TwistedScheduleMatchETags
+from twistedcaldav.datafilters.peruserdata import PerUserDataFilter
 from twistedcaldav.extensions import DAVFile, CachingPropertyStore
 from twistedcaldav.memcachelock import MemcacheLock, MemcacheLockTimeoutError
 from twistedcaldav.memcacheprops import MemcachePropertyCollection
@@ -277,6 +278,7 @@
 
             tzids = set()
             isowner = (yield self.isOwner(request, adminprincipals=True, readprincipals=True))
+            accessPrincipal = (yield self.resourceOwnerPrincipal(request))
 
             for name, uid, type in self.index().bruteForceSearch(): #@UnusedVariable
                 try:
@@ -293,7 +295,7 @@
                         continue
 
                     # Get the access filtered view of the data
-                    caldata = child.iCalendarTextFiltered(isowner)
+                    caldata = child.iCalendarTextFiltered(isowner, accessPrincipal.principalUID() if accessPrincipal else "")
                     try:
                         subcalendar = iComponent.fromString(caldata)
                     except ValueError:
@@ -315,7 +317,7 @@
 
         raise HTTPError(ErrorResponse(responsecode.BAD_REQUEST))
 
-    def iCalendarTextFiltered(self, isowner):
+    def iCalendarTextFiltered(self, isowner, accessUID=None):
         try:
             access = self.readDeadProperty(TwistedCalendarAccessProperty)
         except HTTPError:
@@ -323,7 +325,8 @@
 
         # Now "filter" the resource calendar data
         caldata = PrivateEventFilter(access, isowner).filter(self.iCalendarText())
-
+        if accessUID:
+            caldata = PerUserDataFilter(accessUID).filter(caldata)
         return str(caldata)
 
     def iCalendarText(self, name=None):
@@ -354,9 +357,6 @@
 
         return calendar_data
 
-    def iCalendarXML(self, name=None):
-        return caldavxml.CalendarData.fromCalendar(self.iCalendarText(name))
-
     def createAddressBook(self, request):
         #
         # request object is required because we need to validate against parent

Modified: CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/test/test_icalendar.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/test/test_icalendar.py	2010-03-23 22:40:37 UTC (rev 5386)
+++ CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/test/test_icalendar.py	2010-03-23 22:46:30 UTC (rev 5387)
@@ -3459,3 +3459,519 @@
                 calendar.validateForCalDAV()
             except:
                 self.fail("Valid calendar should validate")
+
+    def test_allperuseruids(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
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user02
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:TRANSPARENT
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+        calendar = Component.fromString(data)
+        self.assertEqual(calendar.allPerUserUIDs(), set((
+            "user01",
+            "user02",
+        )))
+
+    def test_perUserTransparency(self):
+        data = (
+                    (
+                        "No per-user, not recurring 1.1",
+                        """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
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+                        (
+                            (
+                                None,
+                                (
+                                    ("", True,),
+                                ),
+                            ),
+                        ),
+                    ),
+                    (
+                        "Single user, not recurring 1.2",
+                        """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"),
+                        (
+                            (
+                                None,
+                                (
+                                    ("", False,),
+                                    ("user01", False,),
+                                ),
+                            ),
+                        ),
+                    ),
+                    (
+                        "Two users, not recurring 1.3",
+                        """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
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user02
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:TRANSPARENT
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+                        (
+                            (
+                                None,
+                                (
+                                    ("", False,),
+                                    ("user01", False,),
+                                    ("user02", True,),
+                                ),
+                            ),
+                        ),
+                    ),
+                    (
+                        "No per-user, simple recurring 2.1",
+                        """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"),
+                        (
+                            (
+                                None,
+                                (
+                                    ("", False,),
+                                ),
+                            ),
+                            (
+                                datetime.datetime(2008, 6, 2, 12, 0, 0, tzinfo=tzutc()),
+                                (
+                                    ("", False,),
+                                ),
+                            ),
+                        ),
+                    ),
+                    (
+                        "Single user, simple recurring 2.2",
+                        """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
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+TRANSP:OPAQUE
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+                        (
+                            (
+                                None,
+                                (
+                                    ("", False,),
+                                    ("user01", False,),
+                                ),
+                            ),
+                            (
+                                datetime.datetime(2008, 6, 2, 12, 0, 0, tzinfo=tzutc()),
+                                (
+                                    ("", False,),
+                                    ("user01", False,),
+                                ),
+                            ),
+                        ),
+                    ),
+                    (
+                        "Two users, simple recurring 2.3",
+                        """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
+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
+TRANSP:TRANSPARENT
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+                        (
+                            (
+                                None,
+                                (
+                                    ("", False,),
+                                    ("user01", False,),
+                                    ("user02", True,),
+                                ),
+                            ),
+                            (
+                                datetime.datetime(2008, 6, 2, 12, 0, 0, tzinfo=tzutc()),
+                                (
+                                    ("", False,),
+                                    ("user01", False,),
+                                    ("user02", True,),
+                                ),
+                            ),
+                        ),
+                    ),
+                    (
+                        "No per-user, complex recurring 3.1",
+                        """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:TRANSPARENT
+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
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+                        (
+                            (
+                                None,
+                                (
+                                    ("", True,),
+                                ),
+                            ),
+                            (
+                                datetime.datetime(2008, 6, 2, 12, 0, 0, tzinfo=tzutc()),
+                                (
+                                    ("", False,),
+                                ),
+                            ),
+                            (
+                                datetime.datetime(2008, 6, 3, 12, 0, 0, tzinfo=tzutc()),
+                                (
+                                    ("", True,),
+                                ),
+                            ),
+                            (
+                                datetime.datetime(2008, 6, 4, 12, 0, 0, tzinfo=tzutc()),
+                                (
+                                    ("", True,),
+                                ),
+                            ),
+                        ),
+                    ),
+                    (
+                        "Single user, complex recurring 3.2",
+                        """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
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20080602T120000Z
+TRANSP:TRANSPARENT
+END:X-CALENDARSERVER-PERINSTANCE
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20080603T120000Z
+TRANSP:TRANSPARENT
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+                        (
+                            (
+                                None,
+                                (
+                                    ("", False,),
+                                    ("user01", False,),
+                                ),
+                            ),
+                            (
+                                datetime.datetime(2008, 6, 2, 12, 0, 0, tzinfo=tzutc()),
+                                (
+                                    ("", False,),
+                                    ("user01", True,),
+                                ),
+                            ),
+                            (
+                                datetime.datetime(2008, 6, 3, 12, 0, 0, tzinfo=tzutc()),
+                                (
+                                    ("", False,),
+                                    ("user01", True,),
+                                ),
+                            ),
+                            (
+                                datetime.datetime(2008, 6, 4, 12, 0, 0, tzinfo=tzutc()),
+                                (
+                                    ("", False,),
+                                    ("user01", False,),
+                                ),
+                            ),
+                        ),
+                    ),
+                    (
+                        "Two users, complex recurring 3.3",
+                        """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
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20080602T120000Z
+TRANSP:TRANSPARENT
+END:X-CALENDARSERVER-PERINSTANCE
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20080603T120000Z
+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
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+TRANSP:TRANSPARENT
+END:X-CALENDARSERVER-PERINSTANCE
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20080602T120000Z
+TRANSP:OPAQUE
+END:X-CALENDARSERVER-PERINSTANCE
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20080604T120000Z
+TRANSP:TRANSPARENT
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+                        (
+                            (
+                                None,
+                                (
+                                    ("", False,),
+                                    ("user01", False,),
+                                    ("user02", True,),
+                                ),
+                            ),
+                            (
+                                datetime.datetime(2008, 6, 2, 12, 0, 0, tzinfo=tzutc()),
+                                (
+                                    ("", False,),
+                                    ("user01", True,),
+                                    ("user02", False,),
+                                ),
+                            ),
+                            (
+                                datetime.datetime(2008, 6, 3, 12, 0, 0, tzinfo=tzutc()),
+                                (
+                                    ("", False,),
+                                    ("user01", True,),
+                                    ("user02", True,),
+                                ),
+                            ),
+                            (
+                                datetime.datetime(2008, 6, 4, 12, 0, 0, tzinfo=tzutc()),
+                                (
+                                    ("", False,),
+                                    ("user01", False,),
+                                    ("user02", True,),
+                                ),
+                            ),
+                        ),
+                    ),
+                )
+
+        for title, text, results in data:
+            calendar = Component.fromString(text)
+            for rid, result in results:
+                self.assertEqual(calendar.perUserTransparency(rid), result, "Failed comparison: %s %s" % (title, rid,))

Modified: CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/test/test_index.py
===================================================================
--- CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/test/test_index.py	2010-03-23 22:40:37 UTC (rev 5386)
+++ CalendarServer/branches/users/cdaboo/shared-calendars-5187/twistedcaldav/test/test_index.py	2010-03-23 22:46:30 UTC (rev 5387)
@@ -280,7 +280,7 @@
 """,
                 "20080601T000000Z", "20080602T000000Z",
                 "mailto:user1 at example.com",
-                (('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B'),),
+                (('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'F'),),
             ),
             (
                 "#1.2 Simple component - transparent",
@@ -301,7 +301,7 @@
 """,
                 "20080602T000000Z", "20080603T000000Z",
                 "mailto:user1 at example.com",
-                (('N', "2008-06-02 12:00:00+00:00", "2008-06-02 13:00:00+00:00", 'F'),),
+                (('N', "2008-06-02 12:00:00+00:00", "2008-06-02 13:00:00+00:00", 'B', 'T'),),
             ),
             (
                 "#1.3 Simple component - canceled",
@@ -322,7 +322,7 @@
 """,
                 "20080603T000000Z", "20080604T000000Z",
                 "mailto:user1 at example.com",
-                (('N', "2008-06-03 12:00:00+00:00", "2008-06-03 13:00:00+00:00", 'F'),),
+                (('N', "2008-06-03 12:00:00+00:00", "2008-06-03 13:00:00+00:00", 'F', 'F'),),
             ),
             (
                 "#1.4 Simple component - tentative",
@@ -343,7 +343,7 @@
 """,
                 "20080604T000000Z", "20080605T000000Z",
                 "mailto:user1 at example.com",
-                (('N', "2008-06-04 12:00:00+00:00", "2008-06-04 13:00:00+00:00", 'T'),),
+                (('N', "2008-06-04 12:00:00+00:00", "2008-06-04 13:00:00+00:00", 'T', 'F'),),
             ),
             (
                 "#2.1 Recurring component - busy",
@@ -365,8 +365,8 @@
                 "20080605T000000Z", "20080607T000000Z",
                 "mailto:user1 at example.com",
                 (
-                    ('N', "2008-06-05 12:00:00+00:00", "2008-06-05 13:00:00+00:00", 'B'),
-                    ('N', "2008-06-06 12:00:00+00:00", "2008-06-06 13:00:00+00:00", 'B'),
+                    ('N', "2008-06-05 12:00:00+00:00", "2008-06-05 13:00:00+00:00", 'B', 'F'),
+                    ('N', "2008-06-06 12:00:00+00:00", "2008-06-06 13:00:00+00:00", 'B', 'F'),
                 ),
             ),
             (
@@ -399,8 +399,8 @@
                 "20080607T000000Z", "20080609T000000Z",
                 "mailto:user1 at example.com",
                 (
-                    ('N', "2008-06-07 12:00:00+00:00", "2008-06-07 13:00:00+00:00", 'B'),
-                    ('N', "2008-06-08 14:00:00+00:00", "2008-06-08 15:00:00+00:00", 'F'),
+                    ('N', "2008-06-07 12:00:00+00:00", "2008-06-07 13:00:00+00:00", 'B', 'F'),
+                    ('N', "2008-06-08 14:00:00+00:00", "2008-06-08 15:00:00+00:00", 'B', 'T'),
                 ),
             ),
         )
@@ -434,12 +434,415 @@
 
             resources = self.db.indexedSearch(filter, fbtype=True)
             index_results = set()
-            for _ignore_name, _ignore_uid, type, test_organizer, float, start, end, fbtype in resources:
+            for _ignore_name, _ignore_uid, type, test_organizer, float, start, end, fbtype, transp in resources:
                 self.assertEqual(test_organizer, organizer, msg=description)
-                index_results.add((float, start, end, fbtype,))
+                index_results.add((float, start, end, fbtype, transp,))
 
             self.assertEqual(set(instances), index_results, msg=description)
 
+    def test_index_timespan_per_user(self):
+        data = (
+            (
+                "#1.1 Single per-user non-recurring component",
+                "1.1",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1.1
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890-1.1
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+TRANSP:TRANSPARENT
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""",
+                "20080601T000000Z", "20080602T000000Z",
+                "mailto:user1 at example.com",
+                (
+                    (
+                        "user01",
+                        (('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'T'),),
+                    ),
+                    (
+                        "user02",
+                        (('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'F'),),
+                    ),
+                ),
+            ),
+            (
+                "#1.2 Two per-user non-recurring component",
+                "1.2",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1.2
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890-1.2
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+TRANSP:TRANSPARENT
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890-1.2
+X-CALENDARSERVER-PERUSER-UID:user02
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""",
+                "20080601T000000Z", "20080602T000000Z",
+                "mailto:user1 at example.com",
+                (
+                    (
+                        "user01",
+                        (('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'T'),),
+                    ),
+                    (
+                        "user02",
+                        (('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'F'),),
+                    ),
+                    (
+                        "user03",
+                        (('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'F'),),
+                    ),
+                ),
+            ),
+            (
+                "#2.1 Single per-user simple recurring component",
+                "2.1",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1.1
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+RRULE:FREQ=DAILY;COUNT=10
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890-1.1
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+TRANSP:TRANSPARENT
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""",
+                "20080601T000000Z", "20080603T000000Z",
+                "mailto:user1 at example.com",
+                (
+                    (
+                        "user01",
+                        (
+                            ('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'T'),
+                            ('N', "2008-06-02 12:00:00+00:00", "2008-06-02 13:00:00+00:00", 'B', 'T'),
+                        ),
+                    ),
+                    (
+                        "user02",
+                        (
+                            ('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'F'),
+                            ('N', "2008-06-02 12:00:00+00:00", "2008-06-02 13:00:00+00:00", 'B', 'F'),
+                        ),
+                    ),
+                ),
+            ),
+            (
+                "#2.2 Two per-user simple recurring component",
+                "2.2",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1.2
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+RRULE:FREQ=DAILY;COUNT=10
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890-1.2
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+TRANSP:TRANSPARENT
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890-1.2
+X-CALENDARSERVER-PERUSER-UID:user02
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""",
+                "20080601T000000Z", "20080603T000000Z",
+                "mailto:user1 at example.com",
+                (
+                    (
+                        "user01",
+                        (
+                            ('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'T'),
+                            ('N', "2008-06-02 12:00:00+00:00", "2008-06-02 13:00:00+00:00", 'B', 'T'),
+                        ),
+                    ),
+                    (
+                        "user02",
+                        (
+                            ('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'F'),
+                            ('N', "2008-06-02 12:00:00+00:00", "2008-06-02 13:00:00+00:00", 'B', 'F'),
+                        ),
+                    ),
+                    (
+                        "user03",
+                        (
+                            ('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'F'),
+                            ('N', "2008-06-02 12:00:00+00:00", "2008-06-02 13:00:00+00:00", 'B', 'F'),
+                        ),
+                    ),
+                ),
+            ),
+            (
+                "#3.1 Single per-user complex recurring component",
+                "3.1",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1.1
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+RRULE:FREQ=DAILY;COUNT=10
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890-1.1
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890-1.1
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+TRANSP:TRANSPARENT
+END:X-CALENDARSERVER-PERINSTANCE
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20080602T120000Z
+TRANSP:OPAQUE
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""",
+                "20080601T000000Z", "20080604T000000Z",
+                "mailto:user1 at example.com",
+                (
+                    (
+                        "user01",
+                        (
+                            ('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'T'),
+                            ('N', "2008-06-02 13:00:00+00:00", "2008-06-02 14:00:00+00:00", 'B', 'F'),
+                            ('N', "2008-06-03 12:00:00+00:00", "2008-06-03 13:00:00+00:00", 'B', 'T'),
+                        ),
+                    ),
+                    (
+                        "user02",
+                        (
+                            ('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'F'),
+                            ('N', "2008-06-02 13:00:00+00:00", "2008-06-02 14:00:00+00:00", 'B', 'F'),
+                            ('N', "2008-06-03 12:00:00+00:00", "2008-06-03 13:00:00+00:00", 'B', 'F'),
+                        ),
+                    ),
+                ),
+            ),
+            (
+                "#3.2 Two per-user complex recurring component",
+                "3.2",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1.2
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+RRULE:FREQ=DAILY;COUNT=10
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890-1.2
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890-1.2
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+TRANSP:TRANSPARENT
+END:X-CALENDARSERVER-PERINSTANCE
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20080602T120000Z
+TRANSP:OPAQUE
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890-1.2
+X-CALENDARSERVER-PERUSER-UID:user02
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20080603T120000Z
+TRANSP:TRANSPARENT
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""",
+                "20080601T000000Z", "20080604T000000Z",
+                "mailto:user1 at example.com",
+                (
+                    (
+                        "user01",
+                        (
+                            ('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'T'),
+                            ('N', "2008-06-02 13:00:00+00:00", "2008-06-02 14:00:00+00:00", 'B', 'F'),
+                            ('N', "2008-06-03 12:00:00+00:00", "2008-06-03 13:00:00+00:00", 'B', 'T'),
+                        ),
+                    ),
+                    (
+                        "user02",
+                        (
+                            ('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'F'),
+                            ('N', "2008-06-02 13:00:00+00:00", "2008-06-02 14:00:00+00:00", 'B', 'F'),
+                            ('N', "2008-06-03 12:00:00+00:00", "2008-06-03 13:00:00+00:00", 'B', 'T'),
+                        ),
+                    ),
+                    (
+                        "user03",
+                        (
+                            ('N', "2008-06-01 12:00:00+00:00", "2008-06-01 13:00:00+00:00", 'B', 'F'),
+                            ('N', "2008-06-02 13:00:00+00:00", "2008-06-02 14:00:00+00:00", 'B', 'F'),
+                            ('N', "2008-06-03 12:00:00+00:00", "2008-06-03 13:00:00+00:00", 'B', 'F'),
+                        ),
+                    ),
+                ),
+            ),
+        )
+
+        revision = 0
+        for description, name, calendar_txt, trstart, trend, organizer, peruserinstances in data:
+            revision += 1
+            calendar = Component.fromString(calendar_txt)
+
+            f = open(os.path.join(self.site.resource.fp.path, name), "w")
+            f.write(calendar_txt)
+            del f
+
+            self.db.addResource(name, calendar, revision)
+            self.assertTrue(self.db.resourceExists(name), msg=description)
+
+            # Create fake filter element to match time-range
+            filter =  caldavxml.Filter(
+                  caldavxml.ComponentFilter(
+                      caldavxml.ComponentFilter(
+                          TimeRange(
+                              start=trstart,
+                              end=trend,
+                          ),
+                          name=("VEVENT", "VFREEBUSY", "VAVAILABILITY"),
+                      ),
+                      name="VCALENDAR",
+                   )
+              )
+            filter = queryfilter.Filter(filter)
+
+            for useruid, instances in peruserinstances:
+                resources = self.db.indexedSearch(filter, useruid=useruid, fbtype=True)
+                index_results = set()
+                for _ignore_name, _ignore_uid, type, test_organizer, float, start, end, fbtype, transp in resources:
+                    self.assertEqual(test_organizer, organizer, msg=description)
+                    index_results.add((str(float), str(start), str(end), str(fbtype), str(transp),))
+    
+                self.assertEqual(set(instances), index_results, msg="%s, user:%s" % (description, useruid,))
+
+            revision += 1
+            self.db.deleteResource(name, revision)
+
     def test_index_revisions(self):
         data1 = """BEGIN:VCALENDAR
 VERSION:2.0
@@ -503,424 +906,6 @@
         for revision, results in tests:
             self.assertEquals(self.db.whatchanged(revision), results, "Mismatched results for whatchanged with revision %d" % (revision,))
 
-class SQLIndexUpgradeTests (twistedcaldav.test.util.TestCase):
-    """
-    Test abstract SQL DB class
-    """
-
-    class OldIndexv6(Index):
-
-        def _db_version(self):
-            """
-            @return: the schema version assigned to this index.
-            """
-            return "6"
-
-        def _db_init_data_tables_base(self, q, uidunique):
-            """
-            Initialise the underlying database tables.
-            @param q:           a database cursor to use.
-            """
-            #
-            # RESOURCE table is the primary index table
-            #   NAME: Last URI component (eg. <uid>.ics, RESOURCE primary key)
-            #   UID: iCalendar UID (may or may not be unique)
-            #   TYPE: iCalendar component type
-            #   RECURRANCE_MAX: Highest date of recurrence expansion
-            #
-            if uidunique:
-                q.execute(
-                    """
-                    create table RESOURCE (
-                        NAME           text unique,
-                        UID            text unique,
-                        TYPE           text,
-                        RECURRANCE_MAX date
-                    )
-                    """
-                )
-            else:
-                q.execute(
-                    """
-                    create table RESOURCE (
-                        NAME           text unique,
-                        UID            text,
-                        TYPE           text,
-                        RECURRANCE_MAX date
-                    )
-                    """
-                )
-    
-            #
-            # TIMESPAN table tracks (expanded) timespans for resources
-            #   NAME: Related resource (RESOURCE foreign key)
-            #   FLOAT: 'Y' if start/end are floating, 'N' otherwise
-            #   START: Start date
-            #   END: End date
-            #
-            q.execute(
-                """
-                create table TIMESPAN (
-                    NAME  text,
-                    FLOAT text(1),
-                    START date,
-                    END   date
-                )
-                """
-            )
-    
-            if uidunique:
-                #
-                # RESERVED table tracks reserved UIDs
-                #   UID: The UID being reserved
-                #   TIME: When the reservation was made
-                #
-                q.execute(
-                    """
-                    create table RESERVED (
-                        UID  text unique,
-                        TIME date
-                    )
-                    """
-                )
-
-        def _db_upgrade(self, old_version):
-            """
-            Upgrade the database tables.
-            """
-            
-            return super(AbstractCalendarIndex, self)._db_upgrade(old_version)
-
-        def _add_to_db(self, name, calendar, cursor = None, expand_until=None, reCreate=False):
-            """
-            Records the given calendar resource in the index with the given name.
-            Resource names and UIDs must both be unique; only one resource name may
-            be associated with any given UID and vice versa.
-            NB This method does not commit the changes to the db - the caller
-            MUST take care of that
-            @param name: the name of the resource to add.
-            @param calendar: a L{Calendar} object representing the resource
-                contents.
-            """
-            uid = calendar.resourceUID()
-    
-            # Decide how far to expand based on the component
-            master = calendar.masterComponent()
-            if master is None or not calendar.isRecurring() and not calendar.isRecurringUnbounded():
-                # When there is no master we have a set of overridden components - index them all.
-                # When there is one instance - index it.
-                # When bounded - index all.
-                expand = datetime.datetime(2100, 1, 1, 0, 0, 0, tzinfo=utc)
-            else:
-                if expand_until:
-                    expand = expand_until
-                else:
-                    expand = datetime.date.today() + default_future_expansion_duration
-        
-                if expand > (datetime.date.today() + maximum_future_expansion_duration):
-                    raise IndexedSearchException
-    
-            try:
-                instances = calendar.expandTimeRanges(expand, ignoreInvalidInstances=reCreate)
-            except InvalidOverriddenInstanceError:
-                raise
-    
-            self._delete_from_db(name, uid, None)
-    
-            for key in instances:
-                instance = instances[key]
-                start = instance.start.replace(tzinfo=utc)
-                end = instance.end.replace(tzinfo=utc)
-                float = 'Y' if instance.start.tzinfo is None else 'N'
-                self._db_execute(
-                    """
-                    insert into TIMESPAN (NAME, FLOAT, START, END)
-                    values (:1, :2, :3, :4)
-                    """, name, float, start, end
-                )
-    
-            # Special - for unbounded recurrence we insert a value for "infinity"
-            # that will allow an open-ended time-range to always match it.
-            if calendar.isRecurringUnbounded():
-                start = datetime.datetime(2100, 1, 1, 0, 0, 0, tzinfo=utc)
-                end = datetime.datetime(2100, 1, 1, 1, 0, 0, tzinfo=utc)
-                float = 'N'
-                self._db_execute(
-                    """
-                    insert into TIMESPAN (NAME, FLOAT, START, END)
-                    values (:1, :2, :3, :4)
-                    """, name, float, start, end
-                )
-                 
-            self._db_execute(
-                """
-                insert into RESOURCE (NAME, UID, TYPE, RECURRANCE_MAX)
-                values (:1, :2, :3, :4)
-                """, name, uid, calendar.resourceType(), instances.limit
-            )
-
-    class OldIndexv7(Index):
-
-        def _db_version(self):
-            """
-            @return: the schema version assigned to this index.
-            """
-            return "7"
-
-        def _db_init_data_tables_base(self, q, uidunique):
-            """
-            Initialise the underlying database tables.
-            @param q:           a database cursor to use.
-            """
-            #
-            # RESOURCE table is the primary index table
-            #   NAME: Last URI component (eg. <uid>.ics, RESOURCE primary key)
-            #   UID: iCalendar UID (may or may not be unique)
-            #   TYPE: iCalendar component type
-            #   RECURRANCE_MAX: Highest date of recurrence expansion
-            #   ORGANIZER: cu-address of the Organizer of the event
-            #
-            if uidunique:
-                q.execute(
-                    """
-                    create table RESOURCE (
-                        NAME           text unique,
-                        UID            text unique,
-                        TYPE           text,
-                        RECURRANCE_MAX date,
-                        ORGANIZER      text
-                    )
-                    """
-                )
-            else:
-                q.execute(
-                    """
-                    create table RESOURCE (
-                        NAME           text unique,
-                        UID            text,
-                        TYPE           text,
-                        RECURRANCE_MAX date
-                    )
-                    """
-                )
-    
-            #
-            # TIMESPAN table tracks (expanded) time spans for resources
-            #   NAME: Related resource (RESOURCE foreign key)
-            #   FLOAT: 'Y' if start/end are floating, 'N' otherwise
-            #   START: Start date
-            #   END: End date
-            #   FBTYPE: FBTYPE value:
-            #     '?' - unknown
-            #     'F' - free
-            #     'B' - busy
-            #     'U' - busy-unavailable
-            #     'T' - busy-tentative
-            #
-            q.execute(
-                """
-                create table TIMESPAN (
-                    NAME  text,
-                    FLOAT text(1),
-                    START date,
-                    END   date,
-                    FBTYPE text(1)
-                )
-                """
-            )
-    
-            if uidunique:
-                #
-                # RESERVED table tracks reserved UIDs
-                #   UID: The UID being reserved
-                #   TIME: When the reservation was made
-                #
-                q.execute(
-                    """
-                    create table RESERVED (
-                        UID  text unique,
-                        TIME date
-                    )
-                    """
-                )
-
-        def _add_to_db(self, name, calendar, cursor = None, expand_until=None, reCreate=False):
-            """
-            Records the given calendar resource in the index with the given name.
-            Resource names and UIDs must both be unique; only one resource name may
-            be associated with any given UID and vice versa.
-            NB This method does not commit the changes to the db - the caller
-            MUST take care of that
-            @param name: the name of the resource to add.
-            @param calendar: a L{Calendar} object representing the resource
-                contents.
-            """
-            uid = calendar.resourceUID()
-            organizer = calendar.getOrganizer()
-            if not organizer:
-                organizer = ""
-    
-            # Decide how far to expand based on the component
-            master = calendar.masterComponent()
-            if master is None or not calendar.isRecurring() and not calendar.isRecurringUnbounded():
-                # When there is no master we have a set of overridden components - index them all.
-                # When there is one instance - index it.
-                # When bounded - index all.
-                expand = datetime.datetime(2100, 1, 1, 0, 0, 0, tzinfo=utc)
-            else:
-                if expand_until:
-                    expand = expand_until
-                else:
-                    expand = datetime.date.today() + default_future_expansion_duration
-        
-                if expand > (datetime.date.today() + maximum_future_expansion_duration):
-                    raise IndexedSearchException
-    
-            try:
-                instances = calendar.expandTimeRanges(expand, ignoreInvalidInstances=reCreate)
-            except InvalidOverriddenInstanceError:
-                raise
-    
-            self._delete_from_db(name, uid, None)
-    
-            for key in instances:
-                instance = instances[key]
-                start = instance.start.replace(tzinfo=utc)
-                end = instance.end.replace(tzinfo=utc)
-                float = 'Y' if instance.start.tzinfo is None else 'N'
-                self._db_execute(
-                    """
-                    insert into TIMESPAN (NAME, FLOAT, START, END, FBTYPE)
-                    values (:1, :2, :3, :4, :5)
-                    """, name, float, start, end, icalfbtype_to_indexfbtype.get(instance.component.getFBType(), 'F')
-                )
-    
-            # Special - for unbounded recurrence we insert a value for "infinity"
-            # that will allow an open-ended time-range to always match it.
-            if calendar.isRecurringUnbounded():
-                start = datetime.datetime(2100, 1, 1, 0, 0, 0, tzinfo=utc)
-                end = datetime.datetime(2100, 1, 1, 1, 0, 0, tzinfo=utc)
-                float = 'N'
-                self._db_execute(
-                    """
-                    insert into TIMESPAN (NAME, FLOAT, START, END, FBTYPE)
-                    values (:1, :2, :3, :4, :5)
-                    """, name, float, start, end, '?'
-                )
-                 
-            self._db_execute(
-                """
-                insert into RESOURCE (NAME, UID, TYPE, RECURRANCE_MAX, ORGANIZER)
-                values (:1, :2, :3, :4, :5)
-                """, name, uid, calendar.resourceType(), instances.limit, organizer
-            )
-
-    def setUp(self):
-        super(SQLIndexUpgradeTests, self).setUp()
-        self.site.resource.isCalendarCollection = lambda: True
-        self.db = Index(self.site.resource)
-        self.olddbv6 = SQLIndexUpgradeTests.OldIndexv6(self.site.resource)
-        self.olddbv7 = SQLIndexUpgradeTests.OldIndexv7(self.site.resource)
-
-    def prepareOldDB(self):
-        if os.path.exists(self.olddbv6.dbpath):
-            os.remove(self.olddbv6.dbpath)
-
-    def test_old_schema(self):
-        
-        for olddb in (self.olddbv6, self.olddbv7):
-            self.prepareOldDB()
-    
-            schema = olddb._db_value_for_sql(
-                """
-                select VALUE from CALDAV
-                 where KEY = 'SCHEMA_VERSION'
-                """)
-            self.assertEqual(schema, olddb._db_version())
-
-    def test_empty_upgrade(self):
-        
-        for olddb in (self.olddbv6, self.olddbv7):
-            self.prepareOldDB()
-    
-            schema = olddb._db_value_for_sql(
-                """
-                select VALUE from CALDAV
-                 where KEY = 'SCHEMA_VERSION'
-                """)
-            self.assertEqual(schema, olddb._db_version())
-    
-            if olddb._db_version() == "6":
-                self.assertRaises(sqlite3.OperationalError, olddb._db_value_for_sql, "select ORGANIZER from RESOURCE")
-                self.assertRaises(sqlite3.OperationalError, olddb._db_value_for_sql, "select FBTYPE from TIMESPAN")
-            elif olddb._db_version() == "7":
-                olddb._db_value_for_sql("select ORGANIZER from RESOURCE")
-                olddb._db_value_for_sql("select FBTYPE from TIMESPAN")
-            self.assertEqual(set([row[1] for row in olddb._db_execute("PRAGMA index_list(TIMESPAN)")]), set())
-    
-            schema = self.db._db_value_for_sql(
-                """
-                select VALUE from CALDAV
-                 where KEY = 'SCHEMA_VERSION'
-                """)
-            self.assertEqual(schema, self.db._db_version())
-    
-            value = self.db._db_value_for_sql("select ORGANIZER from RESOURCE")
-            self.assertEqual(value, None)
-            self.assertEqual(set([row[1] for row in self.db._db_execute("PRAGMA index_list(TIMESPAN)")]), set(("STARTENDFLOAT",)))
-
-    def test_basic_upgrade(self):
-        
-        for olddb in (self.olddbv6, self.olddbv7):
-            self.prepareOldDB()
-    
-            calendar_name = "1.ics"
-            calendar_data = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890-1.1
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-END:VEVENT
-END:VCALENDAR
-"""
-    
-            olddb.addResource(calendar_name, Component.fromString(calendar_data), 1)
-            self.assertTrue(olddb.resourceExists(calendar_name))
-    
-            if olddb._db_version() == "6":
-                self.assertRaises(sqlite3.OperationalError, olddb._db_value_for_sql, "select ORGANIZER from RESOURCE")
-                self.assertRaises(sqlite3.OperationalError, olddb._db_value_for_sql, "select FBTYPE from TIMESPAN")
-            elif olddb._db_version() == "7":
-                olddb._db_value_for_sql("select ORGANIZER from RESOURCE")
-                olddb._db_value_for_sql("select FBTYPE from TIMESPAN")
-            self.assertEqual(set([row[1] for row in olddb._db_execute("PRAGMA index_list(TIMESPAN)")]), set())
-    
-            value = self.db._db_value_for_sql("select ORGANIZER from RESOURCE where NAME = :1", calendar_name)
-            if olddb._db_version() == "6":
-                self.assertEqual(value, "?")
-            else:
-                self.assertEqual(value, "mailto:user1 at example.com")
-    
-            value = self.db._db_value_for_sql("select FBTYPE from TIMESPAN where NAME = :1", calendar_name)
-            if olddb._db_version() == "6":
-                self.assertEqual(value, "?")
-            else:
-                self.assertEqual(value, "B")
-    
-            self.db.addResource(calendar_name, Component.fromString(calendar_data), 2)
-            self.assertTrue(olddb.resourceExists(calendar_name))
-    
-            value = self.db._db_value_for_sql("select ORGANIZER from RESOURCE where NAME = :1", calendar_name)
-            self.assertEqual(value, "mailto:user1 at example.com")
-    
-            value = self.db._db_value_for_sql("select FBTYPE from TIMESPAN where NAME = :1", calendar_name)
-            self.assertEqual(value, "B")
-
 class MemcacheTests(SQLIndexTests):
     def setUp(self):
         super(MemcacheTests, self).setUp()
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20100323/588f5b87/attachment-0001.html>


More information about the calendarserver-changes mailing list