[CalendarServer-changes] [12419] CalendarServer/branches/users/cdaboo/pod-migration/txdav

source_changes at macosforge.org source_changes at macosforge.org
Wed Mar 12 11:17:38 PDT 2014


Revision: 12419
          http://trac.calendarserver.org//changeset/12419
Author:   cdaboo at apple.com
Date:     2014-01-22 10:11:52 -0800 (Wed, 22 Jan 2014)
Log Message:
-----------
Checkpoint: sync of home metadata.

Modified Paths:
--------------
    CalendarServer/branches/users/cdaboo/pod-migration/txdav/caldav/datastore/sql.py
    CalendarServer/branches/users/cdaboo/pod-migration/txdav/caldav/datastore/sql_external.py
    CalendarServer/branches/users/cdaboo/pod-migration/txdav/common/datastore/podding/conduit.py
    CalendarServer/branches/users/cdaboo/pod-migration/txdav/common/datastore/podding/test/test_conduit.py
    CalendarServer/branches/users/cdaboo/pod-migration/txdav/common/datastore/podding/test/test_migration.py
    CalendarServer/branches/users/cdaboo/pod-migration/txdav/common/datastore/sql.py

Modified: CalendarServer/branches/users/cdaboo/pod-migration/txdav/caldav/datastore/sql.py
===================================================================
--- CalendarServer/branches/users/cdaboo/pod-migration/txdav/caldav/datastore/sql.py	2014-01-22 04:07:03 UTC (rev 12418)
+++ CalendarServer/branches/users/cdaboo/pod-migration/txdav/caldav/datastore/sql.py	2014-01-22 18:11:52 UTC (rev 12419)
@@ -936,6 +936,42 @@
             yield inbox.notifyPropertyChanged()
 
 
+    @inlineCallbacks
+    def postSyncChildren(self, externalHome, final):
+        """
+        Synchronize the metadata from the external side.
+        """
+
+        yield super(CalendarHome, self).postSyncChildren(externalHome, final)
+
+        # Default collections
+        children = yield self.loadChildren()
+        id_map = dict([(child.external_id(), child.id()) for child in children if child.owned()])
+
+        chm = self._homeMetaDataSchema
+        for component, attr in self._componentDefaultAttribute.items():
+            external_id = getattr(externalHome, attr)
+            internal_id = id_map.get(external_id)
+            if internal_id != getattr(self, attr):
+                setattr(self, attr, internal_id)
+                yield Update(
+                    {self._componentDefaultColumn[component]: internal_id},
+                    Where=chm.RESOURCE_ID == self._resourceID,
+                ).on(self._txn)
+
+        # Default alarms
+        for vevent in (True, False):
+            for timed in (True, False):
+                external_default = externalHome.getDefaultAlarm(vevent, timed)
+                if external_default != self.getDefaultAlarm(vevent, timed):
+                    yield self.setDefaultAlarm(external_default, vevent, timed)
+
+        # Availability
+        external_availability = externalHome.getAvailability()
+        if external_availability != self.getAvailability():
+            yield self.setAvailability(external_availability)
+
+
 CalendarHome._register(ECALENDARTYPE)
 
 

Modified: CalendarServer/branches/users/cdaboo/pod-migration/txdav/caldav/datastore/sql_external.py
===================================================================
--- CalendarServer/branches/users/cdaboo/pod-migration/txdav/caldav/datastore/sql_external.py	2014-01-22 04:07:03 UTC (rev 12418)
+++ CalendarServer/branches/users/cdaboo/pod-migration/txdav/caldav/datastore/sql_external.py	2014-01-22 18:11:52 UTC (rev 12419)
@@ -110,27 +110,6 @@
         raise AssertionError("CommonHomeExternal: not supported")
 
 
-    def defaultCalendar(self, componentType, create=True):
-        """
-        No children.
-        """
-        raise AssertionError("CommonHomeExternal: not supported")
-
-
-    def isDefaultCalendar(self, calendar):
-        """
-        No children.
-        """
-        raise AssertionError("CommonHomeExternal: not supported")
-
-
-    def getDefaultAlarm(self, vevent, timed):
-        """
-        No children.
-        """
-        raise AssertionError("CommonHomeExternal: not supported")
-
-
     def setDefaultAlarm(self, alarm, vevent, timed):
         """
         No children.
@@ -138,13 +117,6 @@
         raise AssertionError("CommonHomeExternal: not supported")
 
 
-    def getAvailability(self):
-        """
-        No children.
-        """
-        raise AssertionError("CommonHomeExternal: not supported")
-
-
     def setAvailability(self, availability):
         """
         No children.

Modified: CalendarServer/branches/users/cdaboo/pod-migration/txdav/common/datastore/podding/conduit.py
===================================================================
--- CalendarServer/branches/users/cdaboo/pod-migration/txdav/common/datastore/podding/conduit.py	2014-01-22 04:07:03 UTC (rev 12418)
+++ CalendarServer/branches/users/cdaboo/pod-migration/txdav/common/datastore/podding/conduit.py	2014-01-22 18:11:52 UTC (rev 12419)
@@ -993,8 +993,11 @@
 
         target = yield self._migrate_recv(txn, message, actionName)
         try:
-            # Operate on the L{CommonHomeChild}
-            value = yield getattr(target, method)(*message.get("arguments", ()), **message.get("keywords", {}))
+            # Operate on the target
+            if method is not None:
+                value = yield getattr(target, method)(*message.get("arguments", ()), **message.get("keywords", {}))
+            else:
+                value = target
         except Exception as e:
             returnValue({
                 "result": "exception",
@@ -1024,6 +1027,7 @@
         )
 
 # Migrate calls
+PoddingConduit._make_migrate_action("get", None, transform_recv=PoddingConduit._to_externalize)
 PoddingConduit._make_migrate_action("loadchildren", "loadChildren", transform_recv=PoddingConduit._to_externalize_list)
 
 # Calls on L{CommonHomeChild} objects

Modified: CalendarServer/branches/users/cdaboo/pod-migration/txdav/common/datastore/podding/test/test_conduit.py
===================================================================
--- CalendarServer/branches/users/cdaboo/pod-migration/txdav/common/datastore/podding/test/test_conduit.py	2014-01-22 04:07:03 UTC (rev 12418)
+++ CalendarServer/branches/users/cdaboo/pod-migration/txdav/common/datastore/podding/test/test_conduit.py	2014-01-22 18:11:52 UTC (rev 12419)
@@ -47,6 +47,7 @@
 from txdav.common.icommondatastore import ObjectResourceNameAlreadyExistsError, \
     ObjectResourceNameNotAllowedError
 from txdav.common.idirectoryservice import DirectoryRecordNotFoundError
+from txdav.caldav.datastore.sql_external import CalendarHomeExternal
 
 
 class TestConduit (CommonCommonTests, txweb2.dav.test.util.TestCase):
@@ -1091,6 +1092,23 @@
     """
 
     @inlineCallbacks
+    def test_get(self):
+        """
+        Test that action=loadallobjects works.
+        """
+
+        yield self.homeUnderTest(txn=self.newOtherTransaction(), name="puser01", create=True)
+        yield self.otherCommit()
+
+        remote_home = yield self.homeUnderTest(name="puser01", create=True)
+        remote_home._migration = _MIGRATION_STATUS_MIGRATING
+
+        result = yield remote_home.get()
+        self.assertTrue(isinstance(result, CalendarHomeExternal))
+        self.assertEqual(result.name(), remote_home.name())
+
+
+    @inlineCallbacks
     def test_loadallobjects(self):
         """
         Test that action=loadallobjects works.

Modified: CalendarServer/branches/users/cdaboo/pod-migration/txdav/common/datastore/podding/test/test_migration.py
===================================================================
--- CalendarServer/branches/users/cdaboo/pod-migration/txdav/common/datastore/podding/test/test_migration.py	2014-01-22 04:07:03 UTC (rev 12418)
+++ CalendarServer/branches/users/cdaboo/pod-migration/txdav/common/datastore/podding/test/test_migration.py	2014-01-22 18:11:52 UTC (rev 12419)
@@ -21,12 +21,103 @@
 from txdav.common.datastore.podding.migration import MigrationController, \
     UserAlreadyBeingMigrated
 from txdav.common.datastore.sql import ECALENDARTYPE
+from twistedcaldav.ical import Component
 
 class TestCalendarMigration(MultiStoreConduitTest):
     """
     Test that the migration api works for migration.
     """
 
+    alarmhome1 = """BEGIN:VALARM
+ACTION:AUDIO
+TRIGGER;RELATED=START:-PT1M
+END:VALARM
+"""
+
+    alarmhome2 = """BEGIN:VALARM
+ACTION:AUDIO
+TRIGGER;RELATED=START:-PT2M
+END:VALARM
+"""
+
+    alarmhome3 = """BEGIN:VALARM
+ACTION:AUDIO
+TRIGGER;RELATED=START:-PT3M
+END:VALARM
+"""
+
+    alarmhome4 = """BEGIN:VALARM
+ACTION:AUDIO
+TRIGGER;RELATED=START:-PT4M
+END:VALARM
+"""
+
+    av1 = Component.fromString("""BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//calendarserver.org//Zonal//EN
+BEGIN:VAVAILABILITY
+ORGANIZER:mailto:user01 at example.com
+UID:1 at example.com
+DTSTAMP:20061005T133225Z
+DTEND:20140101T000000Z
+BEGIN:AVAILABLE
+UID:1-1 at example.com
+DTSTAMP:20061005T133225Z
+SUMMARY:Monday to Friday from 9:00 to 17:00
+DTSTART:20130101T090000Z
+DTEND:20130101T170000Z
+RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR
+END:AVAILABLE
+END:VAVAILABILITY
+END:VCALENDAR
+""")
+
+    alarmhome1_changed = """BEGIN:VALARM
+ACTION:AUDIO
+TRIGGER;RELATED=START:-PT5M
+END:VALARM
+"""
+
+    alarmhome2_changed = """BEGIN:VALARM
+ACTION:AUDIO
+TRIGGER;RELATED=START:-PT6M
+END:VALARM
+"""
+
+    alarmhome3_changed = """BEGIN:VALARM
+ACTION:AUDIO
+TRIGGER;RELATED=START:-PT7M
+END:VALARM
+"""
+
+    alarmhome4_changed = """BEGIN:VALARM
+ACTION:AUDIO
+TRIGGER;RELATED=START:-PT8M
+END:VALARM
+"""
+
+    av1_changed = Component.fromString("""BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//calendarserver.org//Zonal//EN
+BEGIN:VAVAILABILITY
+ORGANIZER:mailto:user01 at example.com
+UID:2 at example.com
+DTSTAMP:20061005T133225Z
+DTEND:20140101T000000Z
+BEGIN:AVAILABLE
+UID:2-1 at example.com
+DTSTAMP:20061005T133225Z
+SUMMARY:Monday to Friday from 9:00 to 17:00
+DTSTART:20130101T090000Z
+DTEND:20130101T170000Z
+RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR
+END:AVAILABLE
+END:VAVAILABILITY
+END:VCALENDAR
+""")
+
     @inlineCallbacks
     def _provision_remote(self):
         """
@@ -50,8 +141,7 @@
 
         # Verify local home is not visible to normal api calls
         local_home = yield self.homeUnderTest(name="puser01")
-        self.assertTrue(local_home is not None)
-        self.assertTrue(local_home.external())
+        self.assertTrue(local_home is None)
         yield self.commit()
 
         # Verify local migrating items exist
@@ -64,6 +154,57 @@
 
 
     @inlineCallbacks
+    def test_step1_home_metadata(self):
+        """
+        Test that step1 works for sync'ing home metadata.
+        """
+
+        yield self._provision_remote()
+
+        # Setup remote metadata
+        txn = self.otherTransactionUnderTest()
+        remote_home = yield self.homeUnderTest(txn, name="puser01")
+        new_calendar = yield remote_home.createCalendarWithName("new_calendar")
+        remote_home.setDefaultCalendar(new_calendar, "VEVENT")
+        tasks_calendar = yield remote_home.calendarWithName("tasks")
+        remote_home.setDefaultCalendar(tasks_calendar, "VTODO")
+        yield remote_home.setDefaultAlarm(self.alarmhome1, True, True)
+        yield remote_home.setDefaultAlarm(self.alarmhome2, True, False)
+        yield remote_home.setDefaultAlarm(self.alarmhome3, False, True)
+        yield remote_home.setDefaultAlarm(self.alarmhome4, False, False)
+        yield remote_home.setAvailability(self.av1)
+        yield self.otherCommit()
+
+        migrator = MigrationController(self.storeUnderTest(), homeTypes=(ECALENDARTYPE,))
+        yield migrator.step1("puser01")
+
+        # Verify local home is not visible to normal api calls
+        local_home = yield self.homeUnderTest(name="puser01")
+        self.assertTrue(local_home is None)
+        yield self.commit()
+
+        # Verify local migrating items exist
+        local_home = yield self.homeUnderTest(name="puser01", migration=_MIGRATION_STATUS_MIGRATING)
+        self.assertTrue(local_home is not None)
+        self.assertTrue(not local_home.external())
+
+        results = yield local_home.loadChildren()
+        results = dict([(child.name(), child) for child in results])
+        self.assertEqual(set(results.keys()), set(("new_calendar", "calendar", "tasks", "inbox",)))
+
+        # Verify metadata
+        self.assertTrue(local_home.defaultCalendar("VEVENT"), results["new_calendar"].id())
+        self.assertTrue(local_home.defaultCalendar("VTODO"), results["tasks"].id())
+
+        self.assertTrue(local_home.getDefaultAlarm(True, True), self.alarmhome1)
+        self.assertTrue(local_home.getDefaultAlarm(True, False), self.alarmhome2)
+        self.assertTrue(local_home.getDefaultAlarm(False, True), self.alarmhome3)
+        self.assertTrue(local_home.getDefaultAlarm(False, False), self.alarmhome4)
+
+        self.assertTrue(local_home.getAvailability(), self.av1)
+
+
+    @inlineCallbacks
     def test_step1_twice(self):
         """
         Test that step1 fails a second time.
@@ -87,7 +228,7 @@
     @inlineCallbacks
     def test_step2_no_change(self):
         """
-        Test that step1 fails a second time.
+        Test that step2 works when there are no remote changes.
         """
 
         yield self._provision_remote()
@@ -115,7 +256,7 @@
     @inlineCallbacks
     def test_step2_changes(self):
         """
-        Test that step1 fails a second time.
+        Test that step2 syncs changes.
         """
 
         yield self._provision_remote()
@@ -198,3 +339,87 @@
         self.assertEqual(rids, rids2)
         tasks = yield local_home.calendarWithName("todos")
         self.assertTrue(tasks.isUsedForFreeBusy())
+
+
+    @inlineCallbacks
+    def test_step2_changes_home_metadata(self):
+        """
+        Test that step2 syncs home metadata changes.
+        """
+
+        yield self._provision_remote()
+
+        # Setup remote metadata
+        txn = self.otherTransactionUnderTest()
+        remote_home = yield self.homeUnderTest(txn, name="puser01")
+        event_calendar = yield remote_home.calendarWithName("calendar")
+        remote_home.setDefaultCalendar(event_calendar, "VEVENT")
+        tasks_calendar = yield remote_home.calendarWithName("tasks")
+        remote_home.setDefaultCalendar(tasks_calendar, "VTODO")
+        yield remote_home.setDefaultAlarm(self.alarmhome1, True, True)
+        yield remote_home.setDefaultAlarm(self.alarmhome2, True, False)
+        yield remote_home.setDefaultAlarm(self.alarmhome3, False, True)
+        yield remote_home.setDefaultAlarm(self.alarmhome4, False, False)
+        yield remote_home.setAvailability(self.av1)
+        yield self.otherCommit()
+
+        migrator = MigrationController(self.storeUnderTest(), homeTypes=(ECALENDARTYPE,))
+        yield migrator.step1("puser01")
+
+        # Verify local migrating items exist
+        local_home = yield self.homeUnderTest(name="puser01", migration=_MIGRATION_STATUS_MIGRATING)
+        self.assertTrue(local_home is not None)
+        self.assertTrue(not local_home.external())
+
+        results = yield local_home.loadChildren()
+        results = dict([(child.name(), child) for child in results])
+        self.assertEqual(set(results.keys()), set(("calendar", "tasks", "inbox",)))
+
+        # Verify metadata
+        self.assertTrue(local_home.defaultCalendar("VEVENT"), results["calendar"].id())
+        self.assertTrue(local_home.defaultCalendar("VTODO"), results["tasks"].id())
+
+        self.assertTrue(local_home.getDefaultAlarm(True, True), self.alarmhome1)
+        self.assertTrue(local_home.getDefaultAlarm(True, False), self.alarmhome2)
+        self.assertTrue(local_home.getDefaultAlarm(False, True), self.alarmhome3)
+        self.assertTrue(local_home.getDefaultAlarm(False, False), self.alarmhome4)
+
+        self.assertTrue(local_home.getAvailability(), self.av1)
+
+        # Create new calendar and change metadata
+        txn = self.otherTransactionUnderTest()
+        remote_home = yield self.homeUnderTest(txn, name="puser01")
+        new_calendar = yield remote_home.createCalendarWithName("new_calendar")
+        remote_home.setDefaultCalendar(new_calendar, "VEVENT")
+        tasks_calendar = yield remote_home.calendarWithName("tasks")
+        remote_home.setDefaultCalendar(tasks_calendar, "VTODO")
+        yield remote_home.setDefaultAlarm(self.alarmhome1_changed, True, True)
+        yield remote_home.setDefaultAlarm(self.alarmhome2_changed, True, False)
+        yield remote_home.setDefaultAlarm(self.alarmhome3_changed, False, True)
+        yield remote_home.setDefaultAlarm(self.alarmhome4_changed, False, False)
+        yield remote_home.setAvailability(self.av1_changed)
+
+        yield self.otherCommit()
+
+        migrator = MigrationController(self.storeUnderTest(), homeTypes=(ECALENDARTYPE,))
+        yield migrator.step2("puser01")
+
+        # Verify local migrating items exist
+        local_home = yield self.homeUnderTest(name="puser01", migration=_MIGRATION_STATUS_MIGRATING)
+        self.assertTrue(local_home is not None)
+        self.assertTrue(not local_home.external())
+
+        results = yield local_home.loadChildren()
+        results = dict([(child.name(), child) for child in results])
+        self.assertEqual(set(results.keys()), set(("new_calendar", "calendar", "tasks", "inbox",)))
+
+        # Verify metadata
+        self.assertTrue(local_home.defaultCalendar("VEVENT"), results["new_calendar"].id())
+        self.assertTrue(local_home.defaultCalendar("VTODO"), results["tasks"].id())
+
+        self.assertTrue(local_home.getDefaultAlarm(True, True), self.alarmhome1_changed)
+        self.assertTrue(local_home.getDefaultAlarm(True, False), self.alarmhome2_changed)
+        self.assertTrue(local_home.getDefaultAlarm(False, True), self.alarmhome3_changed)
+        self.assertTrue(local_home.getDefaultAlarm(False, False), self.alarmhome4_changed)
+
+        self.assertTrue(local_home.getAvailability(), self.av1_changed)

Modified: CalendarServer/branches/users/cdaboo/pod-migration/txdav/common/datastore/sql.py
===================================================================
--- CalendarServer/branches/users/cdaboo/pod-migration/txdav/common/datastore/sql.py	2014-01-22 04:07:03 UTC (rev 12418)
+++ CalendarServer/branches/users/cdaboo/pod-migration/txdav/common/datastore/sql.py	2014-01-22 18:11:52 UTC (rev 12419)
@@ -1640,13 +1640,13 @@
 
     @classmethod
     @inlineCallbacks
-    def makeClass(cls, transaction, homeData, metadataData):
+    def makeClass(cls, txn, homeData, metadataData):
         """
         Build the actual home class taking into account the possibility that we might need to
         switch in the external version of the class.
 
-        @param transaction: transaction
-        @type transaction: L{CommonStoreTransaction}
+        @param txn: transaction
+        @type txn: L{CommonStoreTransaction}
         @param ownerUID: owner UID of home to load
         @type ownerUID: C{str}
         @param migration: migration status for home to load
@@ -1663,9 +1663,9 @@
         # If the status is external we need to convert this object to a CommonHomeExternal class which will
         # have the right behavior for non-hosted external users.
         if status == _HOME_STATUS_EXTERNAL and migration == _MIGRATION_STATUS_NONE:
-            home = cls._externalClass(transaction, ownerUID, resourceID)
+            home = cls._externalClass(txn, ownerUID, resourceID)
         else:
-            home = cls(transaction, ownerUID, migration=migration)
+            home = cls(txn, ownerUID, migration=migration)
 
         for attr, value in zip(cls.homeAttributes(), homeData):
             setattr(home, attr, value)
@@ -1675,7 +1675,7 @@
 
         yield home._loadPropertyStore()
 
-        for factory_type, factory in transaction._notifierFactories.items():
+        for factory_type, factory in txn._notifierFactories.items():
             home.addNotifier(factory_type, factory.newNotifier(home))
 
         yield home.made()
@@ -1685,12 +1685,12 @@
 
     @classmethod
     @inlineCallbacks
-    def _getDBData(cls, transaction, ownerUID, migration=_MIGRATION_STATUS_NONE, no_cache=False):
+    def _getDBData(cls, txn, ownerUID, migration=_MIGRATION_STATUS_NONE, no_cache=False):
         """
         Given a set of identifying information, load the metadataData rows for the object.
 
-        @param transaction: transaction
-        @type transaction: L{CommonStoreTransaction}
+        @param txn: transaction
+        @type txn: L{CommonStoreTransaction}
         @param ownerUID: owner UID of home to load
         @type ownerUID: C{str}
         @param migration: migration status for home to load
@@ -1699,14 +1699,14 @@
         @type no_cache: C{bool}
         """
 
-        queryCacher = transaction._queryCacher
+        queryCacher = txn._queryCacher
         homeData = None
         if queryCacher:
             cacheKey = queryCacher.keyForHomeData(cls._homeType, ownerUID, migration)
             homeData = yield queryCacher.get(cacheKey)
 
         if homeData is None:
-            homeData = yield cls._homeColumnsFromOwnerQuery.on(transaction, ownerUID=ownerUID, migration=migration)
+            homeData = yield cls._homeColumnsFromOwnerQuery.on(txn, ownerUID=ownerUID, migration=migration)
             if homeData:
                 homeData = homeData[0]
                 if not no_cache and queryCacher:
@@ -1725,20 +1725,20 @@
 
         if metadataData is None:
             # Don't have a cached copy
-            metadataData = (yield cls._metaDataQuery.on(transaction, resourceID=resourceID))
+            metadataData = (yield cls._metaDataQuery.on(txn, resourceID=resourceID))
             if metadataData:
                 metadataData = metadataData[0]
             else:
                 metadataData = None
             if queryCacher:
                 # Cache the metadataData
-                yield queryCacher.setAfterCommit(transaction, cacheKey, metadataData)
+                yield queryCacher.setAfterCommit(txn, cacheKey, metadataData)
 
         returnValue((homeData, metadataData))
 
 
-    def __init__(self, transaction, ownerUID, migration=_MIGRATION_STATUS_NONE):
-        self._txn = transaction
+    def __init__(self, txn, ownerUID, migration=_MIGRATION_STATUS_NONE):
+        self._txn = txn
         self._ownerUID = ownerUID
         self._resourceID = None
         self._status = _HOME_STATUS_NORMAL
@@ -1770,6 +1770,34 @@
         return succeed(None)
 
 
+    def externalize(self):
+        """
+        Create a dictionary mapping key attributes so this object can be sent over a cross-pod call
+        and reconstituted at the other end. Note that the other end may have a different schema so
+        the attributes may not match exactly and will need to be processed accordingly.
+        """
+        serialized = {}
+        serialized["home"] = dict([(attr[1:], getattr(self, attr, None)) for attr in self.homeAttributes()])
+        serialized["metadata"] = dict([(attr[1:], getattr(self, attr, None)) for attr in self.metadataAttributes()])
+        return serialized
+
+
+    @classmethod
+    @inlineCallbacks
+    def internalize(cls, txn, mapping):
+        """
+        Given a mapping generated by L{externalize}, convert the values into an array of database
+        like items that conforms to the ordering of L{_allColumns} so it can be fed into L{makeClass}.
+        Note that there may be a schema mismatch with the external data, so treat missing items as
+        C{None} and ignore extra items.
+        """
+
+        home = [mapping["home"].get(row[1:]) for row in cls.homeAttributes()]
+        metadata = [mapping["metadata"].get(row[1:]) for row in cls.metadataAttributes()]
+        child = yield cls.makeClass(txn, home, metadata)
+        returnValue(child)
+
+
     def quotaAllowedBytes(self):
         return self._txn.store().quota
 
@@ -2714,19 +2742,66 @@
 
         assert self._migration == _MIGRATION_STATUS_MIGRATING
 
-        # Get external home for the user (create if needed)
-        otherHome = yield self._txn.homeWithUID(self._homeType, user, create=True)
-        assert otherHome._status == _HOME_STATUS_EXTERNAL
+        # Get external home for the user. This is a "virtual" home in that it does not exist in
+        # the local DB - it is a representation of the remote object.
+        externalHome = yield self.getExternal()
+        assert externalHome._status == _HOME_STATUS_EXTERNAL
 
         # Force the external home to look like it is migrating. This will enable certain external API calls
         # that are normally disabled for sharing (e.g., ability to load all child resources).
-        otherHome._migration = _MIGRATION_STATUS_MIGRATING
+        externalHome._migration = _MIGRATION_STATUS_MIGRATING
 
+        # Do sequence of sync operations - note that we may need to sync the home both before and after
+        # the children are sync'd
+        yield self.preSyncChildren(externalHome, final)
+        yield self.syncChildren(externalHome, final)
+        yield self.postSyncChildren(externalHome, final)
+
+
+    @inlineCallbacks
+    def getExternal(self):
+        """
+        Get a new L{CommonHomeExternal} object initialized to look like the remote object, i.e., with
+        all the remote attributes/metadata set as per the remote data.
+
+        @return: a L{CommonHomeExternal}.
+        """
+        remote = yield self._txn.store().conduit.send_get(self)
+        remote["home"]["status"] = _HOME_STATUS_EXTERNAL
+        remote = yield self.internalize(self._txn, remote)
+        returnValue(remote)
+
+
+    def preSyncChildren(self, externalHome, final):
+        """
+        Synchronize the external home with this home. This is called before the children are
+        sync'd - it needs to setup this home with attributes from the remote needed to sync children.
+        """
+        return succeed(None)
+
+
+    def postSyncChildren(self, externalHome, final):
+        """
+        Synchronize the external home with this home. This is called after the children are
+        sync'd - so attributes of the home that depend on child are valid and can be copied or
+        translated to their local equivalents (e.g., references to child resource-ids).
+        """
+
+        # TODO: quota setting
+        return succeed(None)
+
+
+    @inlineCallbacks
+    def syncChildren(self, externalHome, final):
+        """
+        Synchronize the remote children of the local external home.
+        """
+
         local_children = yield self.loadChildren()
         local_children = dict([(child.external_id(), child) for child in local_children if child.owned()])
 
         # Get list of owned child collections
-        remote_children = yield otherHome.loadChildren()
+        remote_children = yield externalHome.loadChildren()
         remote_children = dict([(child.id(), child) for child in remote_children if child.owned()])
 
         # Remove local ones to longer present on remote
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20140312/11dde774/attachment.html>


More information about the calendarserver-changes mailing list