[CalendarServer-changes] [8256] CalendarServer/branches/users/cdaboo/component-set-fixes/txdav

source_changes at macosforge.org source_changes at macosforge.org
Thu Nov 3 12:59:18 PDT 2011


Revision: 8256
          http://trac.macosforge.org/projects/calendarserver/changeset/8256
Author:   cdaboo at apple.com
Date:     2011-11-03 12:59:15 -0700 (Thu, 03 Nov 2011)
Log Message:
-----------
Support splitting of calendars by component type. This involves detecting multiple components types, creating new
calendars and moving specific objects into those, whilst preserving sharing state, moving over properties, and
making sure sync state is adjusted properly.

Modified Paths:
--------------
    CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/base/propertystore/base.py
    CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/base/propertystore/sql.py
    CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/base/propertystore/test/base.py
    CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/base/propertystore/test/test_sql.py
    CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/base/propertystore/test/test_xattr.py
    CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/base/propertystore/xattr.py
    CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/caldav/datastore/file.py
    CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/caldav/datastore/index_file.py
    CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/caldav/datastore/sql.py
    CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/caldav/datastore/test/common.py
    CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/caldav/datastore/test/test_scheduling.py
    CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/caldav/datastore/test/test_sql.py
    CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/common/datastore/sql.py
    CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/common/datastore/test/util.py

Added Paths:
-----------
    CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/caldav/datastore/test/calendar_store/ho/me/home_splits/
    CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/caldav/datastore/test/calendar_store/ho/me/home_splits/calendar_1/
    CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/caldav/datastore/test/calendar_store/ho/me/home_splits/calendar_1/1.ics
    CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/caldav/datastore/test/calendar_store/ho/me/home_splits/calendar_1/2.ics
    CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/caldav/datastore/test/calendar_store/ho/me/home_splits/calendar_1/3.ics
    CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/caldav/datastore/test/calendar_store/ho/me/home_splits/calendar_2/
    CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/caldav/datastore/test/calendar_store/ho/me/home_splits/calendar_2/1.ics
    CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/caldav/datastore/test/calendar_store/ho/me/home_splits/calendar_2/2.ics
    CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/caldav/datastore/test/calendar_store/ho/me/home_splits/calendar_2/3.ics
    CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/caldav/datastore/test/calendar_store/ho/me/home_splits/calendar_2/4.ics
    CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/caldav/datastore/test/calendar_store/ho/me/home_splits/calendar_2/5.ics
    CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/caldav/datastore/test/calendar_store/ho/me/home_splits_shared/
    CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/caldav/datastore/test/calendar_store/ho/me/home_splits_shared/calendar_1/

Modified: CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/base/propertystore/base.py
===================================================================
--- CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/base/propertystore/base.py	2011-11-02 19:05:30 UTC (rev 8255)
+++ CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/base/propertystore/base.py	2011-11-03 19:59:15 UTC (rev 8256)
@@ -212,6 +212,13 @@
     def isGlobalProperty(self, key):
         return key in self._globalKeys
 
+    def copyAllProperties(self, other):
+        """
+        Copy all the properties from another store into this one. This needs to be done
+        independently of the UID. Each underlying store will need to implement this.
+        """
+        pass
+        
 # FIXME: Actually, we should replace this with calls to IPropertyName()
 def validKey(key):
     # Used by implementations to verify that keys are valid

Modified: CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/base/propertystore/sql.py
===================================================================
--- CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/base/propertystore/sql.py	2011-11-02 19:05:30 UTC (rev 8255)
+++ CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/base/propertystore/sql.py	2011-11-03 19:59:15 UTC (rev 8256)
@@ -235,3 +235,27 @@
         self._deleteResourceQuery.on(self._txn, resourceID=self._resourceID)
         self._cacher.delete(str(self._resourceID))
 
+    @inlineCallbacks
+    def copyAllProperties(self, other):
+        """
+        Copy all the properties from another store into this one. This needs to be done
+        independently of the UID.
+        """
+
+        rows = yield other._allWithID.on(other._txn, resourceID=other._resourceID)
+        for key_str, uid, value_str in rows:
+            wasCached = [(key_str, uid) in self._cached]
+            if wasCached[0]:
+                yield self._updateQuery.on(
+                    self._txn, resourceID=self._resourceID, value=value_str,
+                    name=key_str, uid=uid)
+            else:
+                yield self._insertQuery.on(
+                    self._txn, resourceID=self._resourceID, value=value_str,
+                    name=key_str, uid=uid)
+                
+
+        # Reload from the DB
+        self._cached = {}
+        self._cacher.delete(str(self._resourceID))
+        yield self._refresh(self._txn)

Modified: CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/base/propertystore/test/base.py
===================================================================
--- CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/base/propertystore/test/base.py	2011-11-02 19:05:30 UTC (rev 8255)
+++ CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/base/propertystore/test/base.py	2011-11-03 19:59:15 UTC (rev 8256)
@@ -304,8 +304,7 @@
         yield self._changed(self.propertyStore1)
 
         self.failUnless(name in self.propertyStore2.keys())
-
-
+ 
 def propertyName(name):
     return PropertyName("http://calendarserver.org/ns/test/", name)
 

Modified: CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/base/propertystore/test/test_sql.py
===================================================================
--- CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/base/propertystore/test/test_sql.py	2011-11-02 19:05:30 UTC (rev 8255)
+++ CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/base/propertystore/test/test_sql.py	2011-11-03 19:59:15 UTC (rev 8256)
@@ -133,7 +133,7 @@
         race = []
         def tiebreaker(label):
             # Let's not get into the business of figuring out who the database
-            # concurrency rules are suppsoed to pick; it might differ.  We just
+            # concurrency rules are supposed to pick; it might differ.  We just
             # take the answer we're given for who gets to be the final writer,
             # and make sure that matches the property read in the next
             # transaction.
@@ -151,8 +151,51 @@
                   'b': pval2}[race[-1]]
         self.assertEquals(self.propertyStore[pname], winner)
 
+    @inlineCallbacks
+    def test_copy(self):
 
+        # Existing store
+        store1_user1 = yield PropertyStore.load("user01", self._txn, 2)
+        store1_user2 = yield PropertyStore.load("user01", self._txn, 2)
+        store1_user2._setPerUserUID("user02")
 
+        # Populate current store with data
+        props_user1 = (
+            (propertyName("dummy1"), propertyValue("value1-user1")),
+            (propertyName("dummy2"), propertyValue("value2-user1")),
+        )
+        props_user2 = (
+            (propertyName("dummy1"), propertyValue("value1-user2")),
+            (propertyName("dummy3"), propertyValue("value3-user2")),
+        )
+
+        for name, value in props_user1:
+            store1_user1[name] = value
+        for name, value in props_user2:
+            store1_user2[name] = value
+
+        yield self._txn.commit()
+
+        self._txn = self.store.newTransaction()
+
+        # Existing store
+        store1_user1 = yield PropertyStore.load("user01", self._txn, 2)
+
+        # New store
+        store2_user1 = yield PropertyStore.load("user01", self._txn, 3)
+
+        # Do copy and check results
+        yield store2_user1.copyAllProperties(store1_user1)
+        
+        self.assertEqual(store1_user1.keys(), store2_user1.keys())
+
+        store1_user2 = yield PropertyStore.load("user01", self._txn, 2)
+        store1_user2._setPerUserUID("user02")
+        store2_user2 = yield PropertyStore.load("user01", self._txn, 3)
+        store2_user2._setPerUserUID("user02")
+        self.assertEqual(store1_user2.keys(), store2_user2.keys())
+
+
 if PropertyStore is None:
     PropertyStoreTest.skip = importErrorMessage
 

Modified: CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/base/propertystore/test/test_xattr.py
===================================================================
--- CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/base/propertystore/test/test_xattr.py	2011-11-02 19:05:30 UTC (rev 8255)
+++ CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/base/propertystore/test/test_xattr.py	2011-11-03 19:59:15 UTC (rev 8256)
@@ -13,13 +13,13 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 ##
-from twext.web2.dav.element.base import WebDAVTextElement
 
 """
 Property store tests.
 """
 
 from twext.python.filepath import CachingFilePath as FilePath
+from twext.web2.dav.element.base import WebDAVTextElement
 from txdav.base.propertystore.base import PropertyName
 from txdav.base.propertystore.test import base
 
@@ -82,5 +82,58 @@
         self.assertEqual(self.propertyStore[name], DummyProperty.fromString("data"))
         self.assertRaises(KeyError, lambda: self.propertyStore.attrs[uncompressedKey])
 
+    def test_copy(self):
+
+        tempDir = FilePath(self.mktemp())
+        tempDir.makedirs()
+        tempFile1 = tempDir.child("test1")
+        tempFile1.touch()
+        tempFile2 = tempDir.child("test2")
+        tempFile2.touch()
+        
+        # Existing store
+        store1_user1 = PropertyStore("user01", lambda : tempFile1)
+        store1_user2 = PropertyStore("user01", lambda : tempFile1)
+        store1_user2._setPerUserUID("user02")
+
+        # New store
+        store2_user1 = PropertyStore("user01", lambda : tempFile2)
+        store2_user2 = PropertyStore("user01", lambda : tempFile2)
+        store2_user2._setPerUserUID("user02")
+
+        # Populate current store with data
+        class DummyProperty1(WebDAVTextElement):
+            namespace = "http://calendarserver.org/ns/"
+            name = "dummy1"
+        class DummyProperty2(WebDAVTextElement):
+            namespace = "http://calendarserver.org/ns/"
+            name = "dummy2"
+        class DummyProperty3(WebDAVTextElement):
+            namespace = "http://calendarserver.org/ns/"
+            name = "dummy3"
+
+        props_user1 = (
+            DummyProperty1.fromString("value1-user1"),
+            DummyProperty2.fromString("value2-user1"),
+        )
+        props_user2 = (
+            DummyProperty1.fromString("value1-user2"),
+            DummyProperty3.fromString("value3-user2"),
+        )
+
+        for prop in props_user1:
+            store1_user1[PropertyName.fromElement(prop)] = prop
+        for prop in props_user2:
+            store1_user2[PropertyName.fromElement(prop)] = prop
+        store1_user1.flush()
+        store1_user2.flush()
+
+        # Do copy and check results
+        store2_user1.copyAllProperties(store1_user1)
+        store2_user1.flush()
+        
+        self.assertEqual(store1_user1.attrs.items(), store2_user1.attrs.items())
+        self.assertEqual(store1_user2.attrs.items(), store2_user2.attrs.items())
+
 if PropertyStore is None:
     PropertyStoreTest.skip = importErrorMessage

Modified: CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/base/propertystore/xattr.py
===================================================================
--- CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/base/propertystore/xattr.py	2011-11-02 19:05:30 UTC (rev 8255)
+++ CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/base/propertystore/xattr.py	2011-11-03 19:59:15 UTC (rev 8256)
@@ -312,3 +312,20 @@
     def abort(self):
         self.removed.clear()
         self.modified.clear()
+
+    def copyAllProperties(self, other):
+        """
+        Copy all the properties from another store into this one. This needs to be done
+        independently of the UID.
+        """
+        try:
+            iterattr = iter(other.attrs)
+        except IOError, e:
+            if e.errno != errno.ENOENT:
+                raise
+            iterattr = iter(())
+
+        for key in iterattr:
+            if not key.startswith(self.deadPropertyXattrPrefix):
+                continue
+            self.attrs[key] = other.attrs[key]

Modified: CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/caldav/datastore/file.py
===================================================================
--- CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/caldav/datastore/file.py	2011-11-02 19:05:30 UTC (rev 8255)
+++ CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/caldav/datastore/file.py	2011-11-03 19:59:15 UTC (rev 8256)
@@ -294,8 +294,55 @@
         """
         return MimeType.fromString("text/calendar; charset=utf-8")
 
+    def splitCollectionByComponentTypes(self):
+        """
+        If the calendar contains iCalendar data with different component types, then split it into separate collections
+        each containing only one component type. When doing this make sure properties and sharing state are preserved
+        on any new calendars created.
+        """
+        
+        # TODO: implement this for filestore
+        pass
 
+    def _countComponentTypes(self):
+        """
+        Count each component type in this calendar.
+        
+        @return: a C{tuple} of C{tuple} containing the component type name and count. 
+        """
 
+        rows = self._index._oldIndex.componentTypeCounts()
+        result = tuple([(componentType, componentCount) for componentType, componentCount in sorted(rows, key=lambda x:x[0])])
+        return result
+
+    def _splitComponentType(self, component):
+        """
+        Create a new calendar and move all components of the specified component type into the new one.
+        Make sure properties and sharing state is preserved on the new calendar.
+        
+        @param component: Component type to split out
+        @type component: C{str}
+        """
+        
+        # TODO: implement this for filestore
+        pass
+
+    def _transferSharingDetails(self, newcalendar, component):
+        """
+        If the current calendar is shared, make the new calendar shared in the same way, but tweak the name.
+        """
+        
+        # TODO: implement this for filestore
+        pass
+    
+    def _transferCalendarObjects(self, newcalendar, component):
+        """
+        Move all calendar components of the specified type to the specified calendar.
+        """
+        
+        # TODO: implement this for filestore
+        pass
+
 class CalendarObject(CommonObjectResource, CalendarObjectBase):
     """
     @ivar _path: The path of the .ics file on disk

Modified: CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/caldav/datastore/index_file.py
===================================================================
--- CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/caldav/datastore/index_file.py	2011-11-02 19:05:30 UTC (rev 8255)
+++ CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/caldav/datastore/index_file.py	2011-11-03 19:59:15 UTC (rev 8256)
@@ -185,6 +185,12 @@
 
         return uid
 
+    def componentTypeCounts(self):
+        """
+        Count each type of component.
+        """
+        return self._db_execute("select TYPE, COUNT(TYPE) from RESOURCE group by TYPE")
+
     def addResource(self, name, calendar, fast=False, reCreate=False):
         """
         Adding or updating an existing resource.

Modified: CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/caldav/datastore/sql.py
===================================================================
--- CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/caldav/datastore/sql.py	2011-11-02 19:05:30 UTC (rev 8255)
+++ CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/caldav/datastore/sql.py	2011-11-03 19:59:15 UTC (rev 8256)
@@ -25,6 +25,7 @@
     "CalendarObject",
 ]
 
+from twext.python.clsprop import classproperty
 from twext.python.vcomponent import VComponent
 from twext.web2.dav.element.rfc2518 import ResourceType
 from twext.web2.http_headers import MimeType, generateContentType
@@ -59,7 +60,7 @@
     CALENDAR_HOME_TABLE, CALENDAR_HOME_METADATA_TABLE,\
     CALENDAR_AND_CALENDAR_BIND, CALENDAR_OBJECT_REVISIONS_AND_BIND_TABLE,\
     CALENDAR_OBJECT_AND_BIND_TABLE, schema
-from twext.enterprise.dal.syntax import Select
+from twext.enterprise.dal.syntax import Select, Count, ColumnSyntax
 from twext.enterprise.dal.syntax import Insert
 from twext.enterprise.dal.syntax import Update
 from twext.enterprise.dal.syntax import Delete
@@ -72,7 +73,8 @@
 
 from txdav.caldav.datastore.util import StorageTransportBase
 from txdav.common.icommondatastore import IndexedSearchException,\
-    InternalDataStoreError
+    InternalDataStoreError, HomeChildNameAlreadyExistsError,\
+    HomeChildNameNotAllowedError
 
 from pycalendar.datetime import PyCalendarDateTime
 from pycalendar.duration import PyCalendarDuration
@@ -240,6 +242,7 @@
     _homeChildSchema = schema.CALENDAR
     _revisionsSchema = schema.CALENDAR_OBJECT_REVISIONS
     _objectSchema = schema.CALENDAR_OBJECT
+    _timeRangeSchema = schema.TIME_RANGE
 
     # string mappings (old, removing)
     _bindTable = CALENDAR_BIND_TABLE
@@ -378,6 +381,174 @@
         """
         return MimeType.fromString("text/calendar; charset=utf-8")
 
+    @inlineCallbacks
+    def splitCollectionByComponentTypes(self):
+        """
+        If the calendar contains iCalendar data with different component types, then split it into separate collections
+        each containing only one component type. When doing this make sure properties and sharing state are preserved
+        on any new calendars created. Also restrict the new calendars to only the one appropriate component type.
+        """
+        
+        # First see how many different component types there are
+        components = yield self._countComponentTypes()
+        if len(components) <= 1:
+
+            # Restrict calendar to single component type
+            if components:
+                component = components[0][0] if components else "VEVENT"
+            yield self.setSupportedComponents(component.upper())
+
+            returnValue(None)
+        
+        # We will leave the component type with the highest count in the current calendar and create new calendars
+        # for the others which will be moved over
+        maxComponent = max(components, key=lambda x:x[1])[0]
+        
+        for component, _ignore_count in components:
+            if component == maxComponent:
+                continue
+            yield self._splitComponentType(component)
+
+        # Restrict calendar to single component type
+        yield self.setSupportedComponents(maxComponent.upper())
+
+    @inlineCallbacks
+    def _countComponentTypes(self):
+        """
+        Count each component type in this calendar.
+        
+        @return: a C{tuple} of C{tuple} containing the component type name and count. 
+        """
+
+        ob = self._objectSchema
+        _componentsQuery = Select(
+            [ob.ICALENDAR_TYPE, Count(ob.ICALENDAR_TYPE)],
+            From=ob,
+            Where=ob.CALENDAR_RESOURCE_ID == Parameter('calID'),
+            GroupBy=ob.ICALENDAR_TYPE
+        )
+
+        rows = yield _componentsQuery.on(self._txn, calID=self._resourceID)
+        result = tuple([(componentType, componentCount) for componentType, componentCount in sorted(rows, key=lambda x:x[0])])
+        returnValue(result)
+        
+    @inlineCallbacks
+    def _splitComponentType(self, component):
+        """
+        Create a new calendar and move all components of the specified component type into the new one.
+        Make sure properties and sharing state is preserved on the new calendar.
+        
+        @param component: Component type to split out
+        @type component: C{str}
+        """
+        
+        # Create the new calendar
+        try:
+            newcalendar = yield self._home.createCalendarWithName("%s-%s" % (self._name, component.lower(),))
+        except HomeChildNameAlreadyExistsError:
+            # If the name we want exists, try repeating with up to ten more
+            for ctr in range(10):
+                try:
+                    newcalendar = yield self._home.createCalendarWithName("%s-%s-%d" % (self._name, component.lower(), ctr+1,))
+                except HomeChildNameAlreadyExistsError:
+                    continue
+            else:
+                # At this point we are stuck
+                raise HomeChildNameNotAllowedError
+        
+        # Restrict calendar to single component type
+        yield newcalendar.setSupportedComponents(component.upper())
+        
+        # Transfer properties over
+        yield self._properties.copyAllProperties(newcalendar._properties)
+        
+        # Transfer sharing
+        yield self._transferSharingDetails(newcalendar, component)
+        
+        # Now move calendar data over
+        yield self._transferCalendarObjects(newcalendar, component)
+        
+    @inlineCallbacks
+    def _transferSharingDetails(self, newcalendar, component):
+        """
+        If the current calendar is shared, make the new calendar shared in the same way, but tweak the name.
+        """
+
+        cb = self._bindSchema
+        columns = [ColumnSyntax(item) for item in self._bindSchema.model.columns]
+        _bindQuery = Select(
+            columns,
+            From=cb,
+            Where=(cb.CALENDAR_RESOURCE_ID == Parameter('calID')).And(
+                cb.CALENDAR_HOME_RESOURCE_ID != Parameter('homeID'))
+        )
+        
+        rows = yield _bindQuery.on(
+            self._txn,
+            calID=self._resourceID,
+            homeID=self._home._resourceID,
+        )
+        
+        if len(rows) == 0:
+            returnValue(None)
+        
+        for row in rows:
+            columnMap = dict(zip(columns, row))
+            columnMap[cb.CALENDAR_RESOURCE_ID] = newcalendar._resourceID
+            columnMap[cb.CALENDAR_RESOURCE_NAME] = "%s-%s" % (columnMap[cb.CALENDAR_RESOURCE_NAME], component.lower(),)
+            yield Insert(columnMap).on(self._txn)   
+
+    @inlineCallbacks
+    def _transferCalendarObjects(self, newcalendar, component):
+        """
+        Move all calendar components of the specified type to the specified calendar.
+        """
+
+        # Find resource-ids for all matching components
+        ob = self._objectSchema
+        _componentsQuery = Select(
+            [ob.RESOURCE_ID],
+            From=ob,
+            Where=(ob.CALENDAR_RESOURCE_ID == Parameter('calID')).And(
+                ob.ICALENDAR_TYPE == Parameter('componentType'))
+        )
+
+        rows = yield _componentsQuery.on(
+            self._txn,
+            calID=self._resourceID,
+            componentType=component,
+        )
+        
+        if len(rows) == 0:
+            returnValue(None)
+        
+        for row in rows:
+            resourceID = row[0]
+            child = yield self.objectResourceWithID(resourceID)
+            yield self.moveObjectResource(child, newcalendar)
+
+    @classproperty
+    def _moveTimeRangeUpdateQuery(cls): #@NoSelf
+        """
+        DAL query to update a child to be in a new parent.
+        """
+        tr = cls._timeRangeSchema
+        return Update(
+            {tr.CALENDAR_RESOURCE_ID: Parameter("newParentID")},
+            Where=tr.CALENDAR_OBJECT_RESOURCE_ID == Parameter("resourceID")
+        )
+
+    @inlineCallbacks
+    def _movedObjectResource(self, child, newparent):
+        """
+        Make sure time range entries have the new parent resource id.
+        """
+        yield self._moveTimeRangeUpdateQuery.on(
+            self._txn,
+            newParentID=newparent._resourceID,
+            resourceID=child._resourceID
+        )
+        
 icalfbtype_to_indexfbtype = {
     "UNKNOWN"         : 0,
     "FREE"            : 1,

Added: CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/caldav/datastore/test/calendar_store/ho/me/home_splits/calendar_1/1.ics
===================================================================
--- CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/caldav/datastore/test/calendar_store/ho/me/home_splits/calendar_1/1.ics	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/caldav/datastore/test/calendar_store/ho/me/home_splits/calendar_1/1.ics	2011-11-03 19:59:15 UTC (rev 8256)
@@ -0,0 +1,32 @@
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//iCal 4.0.1//EN
+CALSCALE:GREGORIAN
+BEGIN:VTIMEZONE
+TZID:US/Pacific
+BEGIN:DAYLIGHT
+TZOFFSETFROM:-0800
+RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
+DTSTART:20070311T020000
+TZNAME:PDT
+TZOFFSETTO:-0700
+END:DAYLIGHT
+BEGIN:STANDARD
+TZOFFSETFROM:-0700
+RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
+DTSTART:20071104T020000
+TZNAME:PST
+TZOFFSETTO:-0800
+END:STANDARD
+END:VTIMEZONE
+BEGIN:VEVENT
+DTEND;TZID=US/Pacific:20090324T124500
+UID:uid1
+DTSTAMP:20090326T145447Z
+X-APPLE-DROPBOX:/calendars/__uids__/user01/dropbox/uid1.dropbox
+ATTACH;VALUE=URI:/calendars/__uids__/user01/dropbox/uid1.dropbox/test.txt
+SUMMARY:CalDAV protocol updates
+DTSTART;TZID=US/Pacific:20090324T121500
+CREATED:20090326T145440Z
+END:VEVENT
+END:VCALENDAR

Added: CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/caldav/datastore/test/calendar_store/ho/me/home_splits/calendar_1/2.ics
===================================================================
--- CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/caldav/datastore/test/calendar_store/ho/me/home_splits/calendar_1/2.ics	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/caldav/datastore/test/calendar_store/ho/me/home_splits/calendar_1/2.ics	2011-11-03 19:59:15 UTC (rev 8256)
@@ -0,0 +1,32 @@
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//iCal 4.0.1//EN
+CALSCALE:GREGORIAN
+BEGIN:VTIMEZONE
+TZID:US/Pacific
+BEGIN:DAYLIGHT
+TZOFFSETFROM:-0800
+RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
+DTSTART:20070311T020000
+TZNAME:PDT
+TZOFFSETTO:-0700
+END:DAYLIGHT
+BEGIN:STANDARD
+TZOFFSETFROM:-0700
+RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
+DTSTART:20071104T020000
+TZNAME:PST
+TZOFFSETTO:-0800
+END:STANDARD
+END:VTIMEZONE
+BEGIN:VEVENT
+DTEND;TZID=US/Pacific:20090324T124500
+UID:uid2
+DTSTAMP:20090326T145447Z
+X-APPLE-DROPBOX:/calendars/__uids__/user01/dropbox/uid2.dropbox
+ATTACH;VALUE=URI:/calendars/__uids__/user01/dropbox/uid2.dropbox/test.txt
+SUMMARY:CalDAV protocol updates
+DTSTART;TZID=US/Pacific:20090324T121500
+CREATED:20090326T145440Z
+END:VEVENT
+END:VCALENDAR

Added: CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/caldav/datastore/test/calendar_store/ho/me/home_splits/calendar_1/3.ics
===================================================================
--- CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/caldav/datastore/test/calendar_store/ho/me/home_splits/calendar_1/3.ics	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/caldav/datastore/test/calendar_store/ho/me/home_splits/calendar_1/3.ics	2011-11-03 19:59:15 UTC (rev 8256)
@@ -0,0 +1,32 @@
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//iCal 4.0.1//EN
+CALSCALE:GREGORIAN
+BEGIN:VTIMEZONE
+TZID:US/Pacific
+BEGIN:DAYLIGHT
+TZOFFSETFROM:-0800
+RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
+DTSTART:20070311T020000
+TZNAME:PDT
+TZOFFSETTO:-0700
+END:DAYLIGHT
+BEGIN:STANDARD
+TZOFFSETFROM:-0700
+RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
+DTSTART:20071104T020000
+TZNAME:PST
+TZOFFSETTO:-0800
+END:STANDARD
+END:VTIMEZONE
+BEGIN:VEVENT
+DTEND;TZID=US/Pacific:20090324T124500
+UID:uid3
+DTSTAMP:20090326T145447Z
+X-APPLE-DROPBOX:/calendars/__uids__/user01/dropbox/uid2.dropbox
+ATTACH;VALUE=URI:/calendars/__uids__/user01/dropbox/uid2.dropbox/test.txt
+SUMMARY:CalDAV protocol updates
+DTSTART;TZID=US/Pacific:20090324T121500
+CREATED:20090326T145440Z
+END:VEVENT
+END:VCALENDAR

Added: CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/caldav/datastore/test/calendar_store/ho/me/home_splits/calendar_2/1.ics
===================================================================
--- CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/caldav/datastore/test/calendar_store/ho/me/home_splits/calendar_2/1.ics	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/caldav/datastore/test/calendar_store/ho/me/home_splits/calendar_2/1.ics	2011-11-03 19:59:15 UTC (rev 8256)
@@ -0,0 +1,32 @@
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//iCal 4.0.1//EN
+CALSCALE:GREGORIAN
+BEGIN:VTIMEZONE
+TZID:US/Pacific
+BEGIN:DAYLIGHT
+TZOFFSETFROM:-0800
+RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
+DTSTART:20070311T020000
+TZNAME:PDT
+TZOFFSETTO:-0700
+END:DAYLIGHT
+BEGIN:STANDARD
+TZOFFSETFROM:-0700
+RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
+DTSTART:20071104T020000
+TZNAME:PST
+TZOFFSETTO:-0800
+END:STANDARD
+END:VTIMEZONE
+BEGIN:VEVENT
+DTEND;TZID=US/Pacific:20090324T124500
+UID:uid2-1
+DTSTAMP:20090326T145447Z
+X-APPLE-DROPBOX:/calendars/__uids__/user01/dropbox/uid1.dropbox
+ATTACH;VALUE=URI:/calendars/__uids__/user01/dropbox/uid1.dropbox/test.txt
+SUMMARY:CalDAV protocol updates
+DTSTART;TZID=US/Pacific:20090324T121500
+CREATED:20090326T145440Z
+END:VEVENT
+END:VCALENDAR

Added: CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/caldav/datastore/test/calendar_store/ho/me/home_splits/calendar_2/2.ics
===================================================================
--- CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/caldav/datastore/test/calendar_store/ho/me/home_splits/calendar_2/2.ics	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/caldav/datastore/test/calendar_store/ho/me/home_splits/calendar_2/2.ics	2011-11-03 19:59:15 UTC (rev 8256)
@@ -0,0 +1,32 @@
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//iCal 4.0.1//EN
+CALSCALE:GREGORIAN
+BEGIN:VTIMEZONE
+TZID:US/Pacific
+BEGIN:DAYLIGHT
+TZOFFSETFROM:-0800
+RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
+DTSTART:20070311T020000
+TZNAME:PDT
+TZOFFSETTO:-0700
+END:DAYLIGHT
+BEGIN:STANDARD
+TZOFFSETFROM:-0700
+RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
+DTSTART:20071104T020000
+TZNAME:PST
+TZOFFSETTO:-0800
+END:STANDARD
+END:VTIMEZONE
+BEGIN:VEVENT
+DTEND;TZID=US/Pacific:20090324T124500
+UID:uid2-2
+DTSTAMP:20090326T145447Z
+X-APPLE-DROPBOX:/calendars/__uids__/user01/dropbox/uid2.dropbox
+ATTACH;VALUE=URI:/calendars/__uids__/user01/dropbox/uid2.dropbox/test.txt
+SUMMARY:CalDAV protocol updates
+DTSTART;TZID=US/Pacific:20090324T121500
+CREATED:20090326T145440Z
+END:VEVENT
+END:VCALENDAR

Added: CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/caldav/datastore/test/calendar_store/ho/me/home_splits/calendar_2/3.ics
===================================================================
--- CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/caldav/datastore/test/calendar_store/ho/me/home_splits/calendar_2/3.ics	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/caldav/datastore/test/calendar_store/ho/me/home_splits/calendar_2/3.ics	2011-11-03 19:59:15 UTC (rev 8256)
@@ -0,0 +1,29 @@
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//iCal 4.0.1//EN
+CALSCALE:GREGORIAN
+BEGIN:VTIMEZONE
+TZID:US/Pacific
+BEGIN:DAYLIGHT
+TZOFFSETFROM:-0800
+RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
+DTSTART:20070311T020000
+TZNAME:PDT
+TZOFFSETTO:-0700
+END:DAYLIGHT
+BEGIN:STANDARD
+TZOFFSETFROM:-0700
+RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
+DTSTART:20071104T020000
+TZNAME:PST
+TZOFFSETTO:-0800
+END:STANDARD
+END:VTIMEZONE
+BEGIN:VTODO
+UID:uid2-3
+DTSTAMP:20090326T145447Z
+SUMMARY:Do something...
+DUE;TZID=US/Pacific:20110324T121500
+CREATED:20090326T145440Z
+END:VTODO
+END:VCALENDAR

Added: CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/caldav/datastore/test/calendar_store/ho/me/home_splits/calendar_2/4.ics
===================================================================
--- CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/caldav/datastore/test/calendar_store/ho/me/home_splits/calendar_2/4.ics	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/caldav/datastore/test/calendar_store/ho/me/home_splits/calendar_2/4.ics	2011-11-03 19:59:15 UTC (rev 8256)
@@ -0,0 +1,30 @@
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//iCal 4.0.1//EN
+CALSCALE:GREGORIAN
+BEGIN:VTIMEZONE
+TZID:US/Pacific
+BEGIN:DAYLIGHT
+TZOFFSETFROM:-0800
+RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
+DTSTART:20070311T020000
+TZNAME:PDT
+TZOFFSETTO:-0700
+END:DAYLIGHT
+BEGIN:STANDARD
+TZOFFSETFROM:-0700
+RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
+DTSTART:20071104T020000
+TZNAME:PST
+TZOFFSETTO:-0800
+END:STANDARD
+END:VTIMEZONE
+BEGIN:VEVENT
+DTEND;TZID=US/Pacific:20100324T124500
+UID:uid4-2
+DTSTAMP:20090326T145447Z
+SUMMARY:CalDAV protocol updates 2010
+DTSTART;TZID=US/Pacific:20100324T121500
+CREATED:20090326T145440Z
+END:VEVENT
+END:VCALENDAR

Added: CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/caldav/datastore/test/calendar_store/ho/me/home_splits/calendar_2/5.ics
===================================================================
--- CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/caldav/datastore/test/calendar_store/ho/me/home_splits/calendar_2/5.ics	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/caldav/datastore/test/calendar_store/ho/me/home_splits/calendar_2/5.ics	2011-11-03 19:59:15 UTC (rev 8256)
@@ -0,0 +1,29 @@
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//iCal 4.0.1//EN
+CALSCALE:GREGORIAN
+BEGIN:VTIMEZONE
+TZID:US/Pacific
+BEGIN:DAYLIGHT
+TZOFFSETFROM:-0800
+RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
+DTSTART:20070311T020000
+TZNAME:PDT
+TZOFFSETTO:-0700
+END:DAYLIGHT
+BEGIN:STANDARD
+TZOFFSETFROM:-0700
+RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
+DTSTART:20071104T020000
+TZNAME:PST
+TZOFFSETTO:-0800
+END:STANDARD
+END:VTIMEZONE
+BEGIN:VTODO
+UID:uid2-5
+DTSTAMP:20090326T145447Z
+SUMMARY:Do something...2012
+DUE;TZID=US/Pacific:20120324T121500
+CREATED:20090326T145440Z
+END:VTODO
+END:VCALENDAR

Modified: CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/caldav/datastore/test/common.py
===================================================================
--- CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/caldav/datastore/test/common.py	2011-11-02 19:05:30 UTC (rev 8255)
+++ CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/caldav/datastore/test/common.py	2011-11-03 19:59:15 UTC (rev 8256)
@@ -61,9 +61,12 @@
 storePath = FilePath(__file__).parent().child("calendar_store")
 
 homeRoot = storePath.child("ho").child("me").child("home1")
-
 cal1Root = homeRoot.child("calendar_1")
 
+homeSplitsRoot = storePath.child("ho").child("me").child("home_splits")
+cal1SplitsRoot = homeSplitsRoot.child("calendar_1")
+cal2SplitsRoot = homeSplitsRoot.child("calendar_2")
+
 calendar1_objectNames = [
     "1.ics",
     "2.ics",
@@ -200,11 +203,27 @@
         "scheduleEtags": (),
         "hasPrivateComment": True,
     }
+    metadata4 = {
+        "accessMode": "PUBLIC",
+        "isScheduleObject": True,
+        "scheduleTag": "abc4",
+        "scheduleEtags": (),
+        "hasPrivateComment": False,
+    }
+    metadata5 = {
+        "accessMode": "PUBLIC",
+        "isScheduleObject": True,
+        "scheduleTag": "abc5",
+        "scheduleEtags": (),
+        "hasPrivateComment": False,
+    }
 
     md5Values = (
         hashlib.md5("1234").hexdigest(),
         hashlib.md5("5678").hexdigest(),
         hashlib.md5("9ABC").hexdigest(),
+        hashlib.md5("DEFG").hexdigest(),
+        hashlib.md5("HIJK").hexdigest(),
     )
     requirements = {
         "home1": {
@@ -217,7 +236,24 @@
             "calendar_empty": {},
             "not_a_calendar": None
         },
-        "not_a_home": None
+        "not_a_home": None,
+        "home_splits": {
+            "calendar_1": {
+                "1.ics": (cal1SplitsRoot.child("1.ics").getContent(), metadata1),
+                "2.ics": (cal1SplitsRoot.child("2.ics").getContent(), metadata2),
+                "3.ics": (cal1SplitsRoot.child("3.ics").getContent(), metadata3),
+            },
+            "calendar_2": {
+                "1.ics": (cal2SplitsRoot.child("1.ics").getContent(), metadata1),
+                "2.ics": (cal2SplitsRoot.child("2.ics").getContent(), metadata2),
+                "3.ics": (cal2SplitsRoot.child("3.ics").getContent(), metadata3),
+                "4.ics": (cal2SplitsRoot.child("4.ics").getContent(), metadata4),
+                "5.ics": (cal2SplitsRoot.child("5.ics").getContent(), metadata4),
+            },
+        },
+        "home_splits_shared": {
+            "calendar_1": {},
+        },
     }
     md5s = {
         "home1": {
@@ -230,7 +266,21 @@
             "calendar_empty": {},
             "not_a_calendar": None
         },
-        "not_a_home": None
+        "not_a_home": None,
+        "home_splits": {
+            "calendar_1": {
+                "1.ics": md5Values[0],
+                "2.ics": md5Values[1],
+                "3.ics": md5Values[2],
+            },
+            "calendar_2": {
+                "1.ics": md5Values[0],
+                "2.ics": md5Values[1],
+                "3.ics": md5Values[2],
+                "4.ics": md5Values[3],
+                "5.ics": md5Values[4],
+            },
+        },
     }
 
 
@@ -731,6 +781,23 @@
         self.assertEquals(result, None)
 
     @inlineCallbacks
+    def test_countComponentTypes(self):
+        """
+        Test Calendar._countComponentTypes to make sure correct counts are returned.
+        """
+        
+        tests = (
+            ("calendar_1", (("VEVENT", 3),)),
+            ("calendar_2", (("VEVENT", 3), ("VTODO", 2))),
+        )
+        
+        for calname, results in tests:
+            testalendar = yield (yield self.transactionUnderTest().calendarHomeWithUID(
+                "home_splits")).calendarWithName(calname)
+            result = yield maybeDeferred(testalendar._countComponentTypes)
+            self.assertEquals(result, results)
+
+    @inlineCallbacks
     def test_calendarObjects(self):
         """
         L{ICalendar.calendarObjects} will enumerate the calendar objects present

Modified: CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/caldav/datastore/test/test_scheduling.py
===================================================================
--- CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/caldav/datastore/test/test_scheduling.py	2011-11-02 19:05:30 UTC (rev 8255)
+++ CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/caldav/datastore/test/test_scheduling.py	2011-11-03 19:59:15 UTC (rev 8256)
@@ -61,6 +61,7 @@
         raise SkipTest("No private attribute tests.")
 
     test_calendarObjectsWithDotFile = skipit
+    test_countComponentTypes = skipit
     test_init = skipit
 
 del FileStorageTests

Modified: CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/caldav/datastore/test/test_sql.py
===================================================================
--- CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/caldav/datastore/test/test_sql.py	2011-11-02 19:05:30 UTC (rev 8255)
+++ CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/caldav/datastore/test/test_sql.py	2011-11-03 19:59:15 UTC (rev 8256)
@@ -25,7 +25,7 @@
 from twisted.python import hashlib
 from twisted.trial import unittest
 
-from twext.enterprise.dal.syntax import Select, Parameter
+from twext.enterprise.dal.syntax import Select, Parameter, Insert
 from twext.python.vcomponent import VComponent
 from twext.web2.dav.element.rfc2518 import GETContentLanguage, ResourceType
 
@@ -35,7 +35,8 @@
 from txdav.caldav.datastore.test.test_file import setUpCalendarStore
 from txdav.caldav.datastore.util import _migrateCalendar, migrateHome
 from txdav.common.datastore.sql import ECALENDARTYPE
-from txdav.common.datastore.sql_tables import schema
+from txdav.common.datastore.sql_tables import schema, _BIND_MODE_DIRECT,\
+    _BIND_STATUS_ACCEPTED
 from txdav.common.datastore.test.util import buildStore, populateCalendarsFrom
 
 from twistedcaldav import caldavxml
@@ -654,3 +655,124 @@
         yield d1
         yield d2
 
+    @inlineCallbacks
+    def test_transferSharingDetails(self):
+        """
+        Test Calendar._transferSharingDetails to make sure sharing details are transferred.
+        """
+        
+        shareeHome = yield self.transactionUnderTest().calendarHomeWithUID("home_splits_shared")
+
+        calendar = yield (yield self.transactionUnderTest().calendarHomeWithUID(
+            "home_splits")).calendarWithName("calendar_1")
+        
+        # Fake a shared binding on the original calendar
+        bind = calendar._bindSchema
+        _bindCreate = Insert({
+            bind.HOME_RESOURCE_ID: shareeHome._resourceID,
+            bind.RESOURCE_ID: calendar._resourceID, 
+            bind.RESOURCE_NAME: "shared_1",
+            bind.MESSAGE: "Shared to you",
+            bind.BIND_MODE: _BIND_MODE_DIRECT,
+            bind.BIND_STATUS: _BIND_STATUS_ACCEPTED,
+            bind.SEEN_BY_OWNER: True,
+            bind.SEEN_BY_SHAREE: True,
+        })
+        yield _bindCreate.on(self.transactionUnderTest())
+        sharedCalendar = yield shareeHome.sharedChildWithName("shared_1")
+        self.assertTrue(sharedCalendar is not None)
+        sharedCalendar = yield shareeHome.sharedChildWithName("shared_1_vtodo")
+        self.assertTrue(sharedCalendar is None)
+
+        # Now do the transfer and see if a new binding exists
+        newcalendar = yield (yield self.transactionUnderTest().calendarHomeWithUID(
+            "home_splits")).createCalendarWithName("calendar_new")
+        yield calendar._transferSharingDetails(newcalendar, "VTODO")
+
+        sharedCalendar = yield shareeHome.sharedChildWithName("shared_1")
+        self.assertTrue(sharedCalendar is not None)
+        self.assertEqual(sharedCalendar._resourceID, calendar._resourceID)
+
+        sharedCalendar = yield shareeHome.sharedChildWithName("shared_1-vtodo")
+        self.assertTrue(sharedCalendar is not None)
+        self.assertEqual(sharedCalendar._resourceID, newcalendar._resourceID)
+
+    @inlineCallbacks
+    def test_moveCalendarObjectResource(self):
+        """
+        Test Calendar._transferSharingDetails to make sure sharing details are transferred.
+        """
+        
+        calendar1 = yield (yield self.transactionUnderTest().calendarHomeWithUID(
+            "home_splits")).calendarWithName("calendar_1")
+        calendar2 = yield (yield self.transactionUnderTest().calendarHomeWithUID(
+            "home_splits")).calendarWithName("calendar_2")
+        
+        child = yield calendar2.calendarObjectWithName("5.ics")
+        
+        yield calendar2.moveObjectResource(child, calendar1)
+        
+        child = yield calendar2.calendarObjectWithName("5.ics")
+        self.assertTrue(child is None)
+        
+        child = yield calendar1.calendarObjectWithName("5.ics")
+        self.assertTrue(child is not None)
+
+    @inlineCallbacks
+    def test_splitCalendars(self):
+        """
+        Test Calendar.splitCollectionByComponentTypes to make sure components are split out,
+        sync information is updated.
+        """
+        
+        # calendar_1 no change
+        home = yield self.transactionUnderTest().calendarHomeWithUID("home_splits")
+        calendar1 = yield home.calendarWithName("calendar_1")
+        original_sync_token1 = yield calendar1.syncToken()
+        yield calendar1.splitCollectionByComponentTypes()
+        yield self.commit()
+
+        home = yield self.transactionUnderTest().calendarHomeWithUID("home_splits")
+
+        child = yield home.calendarWithName("calendar_1-vtodo")
+        self.assertTrue(child is None)
+
+        calendar1 = yield home.calendarWithName("calendar_1")        
+        children = yield calendar1.listCalendarObjects()
+        self.assertEqual(len(children), 3)
+        new_sync_token1 = yield calendar1.syncToken()
+        self.assertEqual(new_sync_token1, original_sync_token1)
+        result = yield calendar1.getSupportedComponents()
+        self.assertEquals(result, "VEVENT")
+
+        yield self.commit()
+
+        # calendar_2 does split
+        home = yield self.transactionUnderTest().calendarHomeWithUID("home_splits")
+        calendar2 = yield home.calendarWithName("calendar_2")        
+        original_sync_token2 = yield calendar2.syncToken()
+        yield calendar2.splitCollectionByComponentTypes()
+        yield self.commit()
+
+        home = yield self.transactionUnderTest().calendarHomeWithUID("home_splits")
+
+        calendar2_vtodo = yield home.calendarWithName("calendar_2-vtodo")
+        self.assertTrue(calendar2_vtodo is not None)
+        children = yield calendar2_vtodo.listCalendarObjects()
+        self.assertEqual(len(children), 2)
+        changed, deleted = yield calendar2_vtodo.resourceNamesSinceToken(None)
+        self.assertEqual(sorted(changed), ["3.ics", "5.ics"])
+        self.assertEqual(len(deleted), 0)
+        result = yield calendar2_vtodo.getSupportedComponents()
+        self.assertEquals(result, "VTODO")
+
+        calendar2 = yield home.calendarWithName("calendar_2")        
+        children = yield calendar2.listCalendarObjects()
+        self.assertEqual(len(children), 3)
+        new_sync_token2 = yield calendar2.syncToken()
+        self.assertNotEqual(new_sync_token2, original_sync_token2)
+        changed, deleted = yield calendar2.resourceNamesSinceToken(original_sync_token2)
+        self.assertEqual(len(changed), 0)
+        self.assertEqual(sorted(deleted), ["3.ics", "5.ics"])
+        result = yield calendar2.getSupportedComponents()
+        self.assertEquals(result, "VEVENT")

Modified: CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/common/datastore/sql.py
===================================================================
--- CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/common/datastore/sql.py	2011-11-02 19:05:30 UTC (rev 8255)
+++ CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/common/datastore/sql.py	2011-11-03 19:59:15 UTC (rev 8256)
@@ -1158,6 +1158,10 @@
                       Where=rev.RESOURCE_ID == Parameter("resourceID"))
 
 
+    def revisionFromToken(self, token):
+        _ignore_uuid, revision = token.split("_", 1)
+        return int(revision)
+
     @inlineCallbacks
     def syncToken(self):
         if self._syncTokenRevision is None:
@@ -1184,6 +1188,12 @@
 
     @inlineCallbacks
     def resourceNamesSinceToken(self, token):
+        
+        if token is None:
+            token = 0
+        elif isinstance(token, str):
+            token = self.revisionFromToken(token)
+
         results = [
             (name if name else "", deleted)
             for name, deleted in
@@ -2128,7 +2138,62 @@
             yield self._deleteRevision(name)
             self.notifyChanged()
 
+    @classproperty
+    def _moveParentUpdateQuery(cls): #@NoSelf
+        """
+        DAL query to update a child to be in a new parent.
+        """
+        obj = cls._objectSchema
+        return Update(
+            {obj.PARENT_RESOURCE_ID: Parameter("newParentID")},
+            Where=obj.RESOURCE_ID == Parameter("resourceID")
+        )
 
+    def _movedObjectResource(self, child, newparent):
+        """
+        Method that subclasses can override to do an extra DB adjustments when a resource
+        is moved.
+        """
+        return succeed(True)
+
+    @inlineCallbacks
+    def moveObjectResource(self, child, newparent):
+        """
+        Move a child of this collection into another collection without actually removing/re-inserting the data.
+        Make sure sync and cache details for both collections are updated.
+        
+        TODO: check that the resource name does not exist in the new parent, or that the UID
+        does not exist there too.
+
+        @param child: the child resource to move
+        @type child: L{CommonObjectResource}
+        @param newparent: the parent to move to
+        @type newparent: L{CommonHomeChild}
+        """
+
+        name = child.name()
+        uid = child.uid()
+
+        # Clean this collections cache and signal sync change
+        self._objects.pop(name, None)
+        self._objects.pop(uid, None)
+        self._objects.pop(child._resourceID, None)
+        yield self._deleteRevision(name)
+        self.notifyChanged()
+        
+        # Adjust the child to be a child of the new parent and update ancillary tables
+        yield self._moveParentUpdateQuery.on(
+            self._txn,
+            newParentID=newparent._resourceID,
+            resourceID=child._resourceID
+        )
+        yield self._movedObjectResource(child, newparent)
+        child._parentCollection = newparent
+
+        # Signal sync change on new collection
+        yield newparent._insertRevision(name)
+        newparent.notifyChanged()
+
     def objectResourcesHaveProperties(self):
         return False
 

Modified: CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/common/datastore/test/util.py
===================================================================
--- CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/common/datastore/test/util.py	2011-11-02 19:05:30 UTC (rev 8255)
+++ CalendarServer/branches/users/cdaboo/component-set-fixes/txdav/common/datastore/test/util.py	2011-11-03 19:59:15 UTC (rev 8256)
@@ -101,6 +101,11 @@
             self.sharedService = PostgresService(
                 dbRoot, getReady, current_sql_schema, resetSchema=True,
                 databaseName="caldav",
+                options = [
+                    "-c log_lock_waits=TRUE",
+                    "-c log_statement=all",
+                    "-c log_line_prefix='%p.%x '",
+                ],
                 testMode=True
             )
             self.sharedService.startService()
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20111103/4db33fdf/attachment-0001.html>


More information about the calendarserver-changes mailing list