<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[12419] CalendarServer/branches/users/cdaboo/pod-migration/txdav</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://trac.calendarserver.org//changeset/12419">12419</a></dd>
<dt>Author</dt> <dd>cdaboo@apple.com</dd>
<dt>Date</dt> <dd>2014-01-22 10:11:52 -0800 (Wed, 22 Jan 2014)</dd>
</dl>

<h3>Log Message</h3>
<pre>Checkpoint: sync of home metadata.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#CalendarServerbranchesuserscdaboopodmigrationtxdavcaldavdatastoresqlpy">CalendarServer/branches/users/cdaboo/pod-migration/txdav/caldav/datastore/sql.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboopodmigrationtxdavcaldavdatastoresql_externalpy">CalendarServer/branches/users/cdaboo/pod-migration/txdav/caldav/datastore/sql_external.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboopodmigrationtxdavcommondatastorepoddingconduitpy">CalendarServer/branches/users/cdaboo/pod-migration/txdav/common/datastore/podding/conduit.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboopodmigrationtxdavcommondatastorepoddingtesttest_conduitpy">CalendarServer/branches/users/cdaboo/pod-migration/txdav/common/datastore/podding/test/test_conduit.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboopodmigrationtxdavcommondatastorepoddingtesttest_migrationpy">CalendarServer/branches/users/cdaboo/pod-migration/txdav/common/datastore/podding/test/test_migration.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboopodmigrationtxdavcommondatastoresqlpy">CalendarServer/branches/users/cdaboo/pod-migration/txdav/common/datastore/sql.py</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="CalendarServerbranchesuserscdaboopodmigrationtxdavcaldavdatastoresqlpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/pod-migration/txdav/caldav/datastore/sql.py (12418 => 12419)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -936,6 +936,42 @@
</span><span class="cx">             yield inbox.notifyPropertyChanged()
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
+    def postSyncChildren(self, externalHome, final):
+        &quot;&quot;&quot;
+        Synchronize the metadata from the external side.
+        &quot;&quot;&quot;
+
+        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)
+
+
</ins><span class="cx"> CalendarHome._register(ECALENDARTYPE)
</span><span class="cx"> 
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboopodmigrationtxdavcaldavdatastoresql_externalpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/pod-migration/txdav/caldav/datastore/sql_external.py (12418 => 12419)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -110,27 +110,6 @@
</span><span class="cx">         raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def defaultCalendar(self, componentType, create=True):
-        &quot;&quot;&quot;
-        No children.
-        &quot;&quot;&quot;
-        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
-
-
-    def isDefaultCalendar(self, calendar):
-        &quot;&quot;&quot;
-        No children.
-        &quot;&quot;&quot;
-        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
-
-
-    def getDefaultAlarm(self, vevent, timed):
-        &quot;&quot;&quot;
-        No children.
-        &quot;&quot;&quot;
-        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
-
-
</del><span class="cx">     def setDefaultAlarm(self, alarm, vevent, timed):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         No children.
</span><span class="lines">@@ -138,13 +117,6 @@
</span><span class="cx">         raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def getAvailability(self):
-        &quot;&quot;&quot;
-        No children.
-        &quot;&quot;&quot;
-        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
-
-
</del><span class="cx">     def setAvailability(self, availability):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         No children.
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboopodmigrationtxdavcommondatastorepoddingconduitpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/pod-migration/txdav/common/datastore/podding/conduit.py (12418 => 12419)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -993,8 +993,11 @@
</span><span class="cx"> 
</span><span class="cx">         target = yield self._migrate_recv(txn, message, actionName)
</span><span class="cx">         try:
</span><del>-            # Operate on the L{CommonHomeChild}
-            value = yield getattr(target, method)(*message.get(&quot;arguments&quot;, ()), **message.get(&quot;keywords&quot;, {}))
</del><ins>+            # Operate on the target
+            if method is not None:
+                value = yield getattr(target, method)(*message.get(&quot;arguments&quot;, ()), **message.get(&quot;keywords&quot;, {}))
+            else:
+                value = target
</ins><span class="cx">         except Exception as e:
</span><span class="cx">             returnValue({
</span><span class="cx">                 &quot;result&quot;: &quot;exception&quot;,
</span><span class="lines">@@ -1024,6 +1027,7 @@
</span><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx"> # Migrate calls
</span><ins>+PoddingConduit._make_migrate_action(&quot;get&quot;, None, transform_recv=PoddingConduit._to_externalize)
</ins><span class="cx"> PoddingConduit._make_migrate_action(&quot;loadchildren&quot;, &quot;loadChildren&quot;, transform_recv=PoddingConduit._to_externalize_list)
</span><span class="cx"> 
</span><span class="cx"> # Calls on L{CommonHomeChild} objects
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboopodmigrationtxdavcommondatastorepoddingtesttest_conduitpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/pod-migration/txdav/common/datastore/podding/test/test_conduit.py (12418 => 12419)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -47,6 +47,7 @@
</span><span class="cx"> from txdav.common.icommondatastore import ObjectResourceNameAlreadyExistsError, \
</span><span class="cx">     ObjectResourceNameNotAllowedError
</span><span class="cx"> from txdav.common.idirectoryservice import DirectoryRecordNotFoundError
</span><ins>+from txdav.caldav.datastore.sql_external import CalendarHomeExternal
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> class TestConduit (CommonCommonTests, txweb2.dav.test.util.TestCase):
</span><span class="lines">@@ -1091,6 +1092,23 @@
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><ins>+    def test_get(self):
+        &quot;&quot;&quot;
+        Test that action=loadallobjects works.
+        &quot;&quot;&quot;
+
+        yield self.homeUnderTest(txn=self.newOtherTransaction(), name=&quot;puser01&quot;, create=True)
+        yield self.otherCommit()
+
+        remote_home = yield self.homeUnderTest(name=&quot;puser01&quot;, 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
</ins><span class="cx">     def test_loadallobjects(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Test that action=loadallobjects works.
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboopodmigrationtxdavcommondatastorepoddingtesttest_migrationpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/pod-migration/txdav/common/datastore/podding/test/test_migration.py (12418 => 12419)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -21,12 +21,103 @@
</span><span class="cx"> from txdav.common.datastore.podding.migration import MigrationController, \
</span><span class="cx">     UserAlreadyBeingMigrated
</span><span class="cx"> from txdav.common.datastore.sql import ECALENDARTYPE
</span><ins>+from twistedcaldav.ical import Component
</ins><span class="cx"> 
</span><span class="cx"> class TestCalendarMigration(MultiStoreConduitTest):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Test that the migration api works for migration.
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx"> 
</span><ins>+    alarmhome1 = &quot;&quot;&quot;BEGIN:VALARM
+ACTION:AUDIO
+TRIGGER;RELATED=START:-PT1M
+END:VALARM
+&quot;&quot;&quot;
+
+    alarmhome2 = &quot;&quot;&quot;BEGIN:VALARM
+ACTION:AUDIO
+TRIGGER;RELATED=START:-PT2M
+END:VALARM
+&quot;&quot;&quot;
+
+    alarmhome3 = &quot;&quot;&quot;BEGIN:VALARM
+ACTION:AUDIO
+TRIGGER;RELATED=START:-PT3M
+END:VALARM
+&quot;&quot;&quot;
+
+    alarmhome4 = &quot;&quot;&quot;BEGIN:VALARM
+ACTION:AUDIO
+TRIGGER;RELATED=START:-PT4M
+END:VALARM
+&quot;&quot;&quot;
+
+    av1 = Component.fromString(&quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//calendarserver.org//Zonal//EN
+BEGIN:VAVAILABILITY
+ORGANIZER:mailto:user01@example.com
+UID:1@example.com
+DTSTAMP:20061005T133225Z
+DTEND:20140101T000000Z
+BEGIN:AVAILABLE
+UID:1-1@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
+&quot;&quot;&quot;)
+
+    alarmhome1_changed = &quot;&quot;&quot;BEGIN:VALARM
+ACTION:AUDIO
+TRIGGER;RELATED=START:-PT5M
+END:VALARM
+&quot;&quot;&quot;
+
+    alarmhome2_changed = &quot;&quot;&quot;BEGIN:VALARM
+ACTION:AUDIO
+TRIGGER;RELATED=START:-PT6M
+END:VALARM
+&quot;&quot;&quot;
+
+    alarmhome3_changed = &quot;&quot;&quot;BEGIN:VALARM
+ACTION:AUDIO
+TRIGGER;RELATED=START:-PT7M
+END:VALARM
+&quot;&quot;&quot;
+
+    alarmhome4_changed = &quot;&quot;&quot;BEGIN:VALARM
+ACTION:AUDIO
+TRIGGER;RELATED=START:-PT8M
+END:VALARM
+&quot;&quot;&quot;
+
+    av1_changed = Component.fromString(&quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//calendarserver.org//Zonal//EN
+BEGIN:VAVAILABILITY
+ORGANIZER:mailto:user01@example.com
+UID:2@example.com
+DTSTAMP:20061005T133225Z
+DTEND:20140101T000000Z
+BEGIN:AVAILABLE
+UID:2-1@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
+&quot;&quot;&quot;)
+
</ins><span class="cx">     @inlineCallbacks
</span><span class="cx">     def _provision_remote(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="lines">@@ -50,8 +141,7 @@
</span><span class="cx"> 
</span><span class="cx">         # Verify local home is not visible to normal api calls
</span><span class="cx">         local_home = yield self.homeUnderTest(name=&quot;puser01&quot;)
</span><del>-        self.assertTrue(local_home is not None)
-        self.assertTrue(local_home.external())
</del><ins>+        self.assertTrue(local_home is None)
</ins><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><span class="cx">         # Verify local migrating items exist
</span><span class="lines">@@ -64,6 +154,57 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><ins>+    def test_step1_home_metadata(self):
+        &quot;&quot;&quot;
+        Test that step1 works for sync'ing home metadata.
+        &quot;&quot;&quot;
+
+        yield self._provision_remote()
+
+        # Setup remote metadata
+        txn = self.otherTransactionUnderTest()
+        remote_home = yield self.homeUnderTest(txn, name=&quot;puser01&quot;)
+        new_calendar = yield remote_home.createCalendarWithName(&quot;new_calendar&quot;)
+        remote_home.setDefaultCalendar(new_calendar, &quot;VEVENT&quot;)
+        tasks_calendar = yield remote_home.calendarWithName(&quot;tasks&quot;)
+        remote_home.setDefaultCalendar(tasks_calendar, &quot;VTODO&quot;)
+        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(&quot;puser01&quot;)
+
+        # Verify local home is not visible to normal api calls
+        local_home = yield self.homeUnderTest(name=&quot;puser01&quot;)
+        self.assertTrue(local_home is None)
+        yield self.commit()
+
+        # Verify local migrating items exist
+        local_home = yield self.homeUnderTest(name=&quot;puser01&quot;, 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((&quot;new_calendar&quot;, &quot;calendar&quot;, &quot;tasks&quot;, &quot;inbox&quot;,)))
+
+        # Verify metadata
+        self.assertTrue(local_home.defaultCalendar(&quot;VEVENT&quot;), results[&quot;new_calendar&quot;].id())
+        self.assertTrue(local_home.defaultCalendar(&quot;VTODO&quot;), results[&quot;tasks&quot;].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
</ins><span class="cx">     def test_step1_twice(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Test that step1 fails a second time.
</span><span class="lines">@@ -87,7 +228,7 @@
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def test_step2_no_change(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        Test that step1 fails a second time.
</del><ins>+        Test that step2 works when there are no remote changes.
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx">         yield self._provision_remote()
</span><span class="lines">@@ -115,7 +256,7 @@
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def test_step2_changes(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        Test that step1 fails a second time.
</del><ins>+        Test that step2 syncs changes.
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx">         yield self._provision_remote()
</span><span class="lines">@@ -198,3 +339,87 @@
</span><span class="cx">         self.assertEqual(rids, rids2)
</span><span class="cx">         tasks = yield local_home.calendarWithName(&quot;todos&quot;)
</span><span class="cx">         self.assertTrue(tasks.isUsedForFreeBusy())
</span><ins>+
+
+    @inlineCallbacks
+    def test_step2_changes_home_metadata(self):
+        &quot;&quot;&quot;
+        Test that step2 syncs home metadata changes.
+        &quot;&quot;&quot;
+
+        yield self._provision_remote()
+
+        # Setup remote metadata
+        txn = self.otherTransactionUnderTest()
+        remote_home = yield self.homeUnderTest(txn, name=&quot;puser01&quot;)
+        event_calendar = yield remote_home.calendarWithName(&quot;calendar&quot;)
+        remote_home.setDefaultCalendar(event_calendar, &quot;VEVENT&quot;)
+        tasks_calendar = yield remote_home.calendarWithName(&quot;tasks&quot;)
+        remote_home.setDefaultCalendar(tasks_calendar, &quot;VTODO&quot;)
+        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(&quot;puser01&quot;)
+
+        # Verify local migrating items exist
+        local_home = yield self.homeUnderTest(name=&quot;puser01&quot;, 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((&quot;calendar&quot;, &quot;tasks&quot;, &quot;inbox&quot;,)))
+
+        # Verify metadata
+        self.assertTrue(local_home.defaultCalendar(&quot;VEVENT&quot;), results[&quot;calendar&quot;].id())
+        self.assertTrue(local_home.defaultCalendar(&quot;VTODO&quot;), results[&quot;tasks&quot;].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=&quot;puser01&quot;)
+        new_calendar = yield remote_home.createCalendarWithName(&quot;new_calendar&quot;)
+        remote_home.setDefaultCalendar(new_calendar, &quot;VEVENT&quot;)
+        tasks_calendar = yield remote_home.calendarWithName(&quot;tasks&quot;)
+        remote_home.setDefaultCalendar(tasks_calendar, &quot;VTODO&quot;)
+        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(&quot;puser01&quot;)
+
+        # Verify local migrating items exist
+        local_home = yield self.homeUnderTest(name=&quot;puser01&quot;, 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((&quot;new_calendar&quot;, &quot;calendar&quot;, &quot;tasks&quot;, &quot;inbox&quot;,)))
+
+        # Verify metadata
+        self.assertTrue(local_home.defaultCalendar(&quot;VEVENT&quot;), results[&quot;new_calendar&quot;].id())
+        self.assertTrue(local_home.defaultCalendar(&quot;VTODO&quot;), results[&quot;tasks&quot;].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)
</ins></span></pre></div>
<a id="CalendarServerbranchesuserscdaboopodmigrationtxdavcommondatastoresqlpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/pod-migration/txdav/common/datastore/sql.py (12418 => 12419)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -1640,13 +1640,13 @@
</span><span class="cx"> 
</span><span class="cx">     @classmethod
</span><span class="cx">     @inlineCallbacks
</span><del>-    def makeClass(cls, transaction, homeData, metadataData):
</del><ins>+    def makeClass(cls, txn, homeData, metadataData):
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Build the actual home class taking into account the possibility that we might need to
</span><span class="cx">         switch in the external version of the class.
</span><span class="cx"> 
</span><del>-        @param transaction: transaction
-        @type transaction: L{CommonStoreTransaction}
</del><ins>+        @param txn: transaction
+        @type txn: L{CommonStoreTransaction}
</ins><span class="cx">         @param ownerUID: owner UID of home to load
</span><span class="cx">         @type ownerUID: C{str}
</span><span class="cx">         @param migration: migration status for home to load
</span><span class="lines">@@ -1663,9 +1663,9 @@
</span><span class="cx">         # If the status is external we need to convert this object to a CommonHomeExternal class which will
</span><span class="cx">         # have the right behavior for non-hosted external users.
</span><span class="cx">         if status == _HOME_STATUS_EXTERNAL and migration == _MIGRATION_STATUS_NONE:
</span><del>-            home = cls._externalClass(transaction, ownerUID, resourceID)
</del><ins>+            home = cls._externalClass(txn, ownerUID, resourceID)
</ins><span class="cx">         else:
</span><del>-            home = cls(transaction, ownerUID, migration=migration)
</del><ins>+            home = cls(txn, ownerUID, migration=migration)
</ins><span class="cx"> 
</span><span class="cx">         for attr, value in zip(cls.homeAttributes(), homeData):
</span><span class="cx">             setattr(home, attr, value)
</span><span class="lines">@@ -1675,7 +1675,7 @@
</span><span class="cx"> 
</span><span class="cx">         yield home._loadPropertyStore()
</span><span class="cx"> 
</span><del>-        for factory_type, factory in transaction._notifierFactories.items():
</del><ins>+        for factory_type, factory in txn._notifierFactories.items():
</ins><span class="cx">             home.addNotifier(factory_type, factory.newNotifier(home))
</span><span class="cx"> 
</span><span class="cx">         yield home.made()
</span><span class="lines">@@ -1685,12 +1685,12 @@
</span><span class="cx"> 
</span><span class="cx">     @classmethod
</span><span class="cx">     @inlineCallbacks
</span><del>-    def _getDBData(cls, transaction, ownerUID, migration=_MIGRATION_STATUS_NONE, no_cache=False):
</del><ins>+    def _getDBData(cls, txn, ownerUID, migration=_MIGRATION_STATUS_NONE, no_cache=False):
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Given a set of identifying information, load the metadataData rows for the object.
</span><span class="cx"> 
</span><del>-        @param transaction: transaction
-        @type transaction: L{CommonStoreTransaction}
</del><ins>+        @param txn: transaction
+        @type txn: L{CommonStoreTransaction}
</ins><span class="cx">         @param ownerUID: owner UID of home to load
</span><span class="cx">         @type ownerUID: C{str}
</span><span class="cx">         @param migration: migration status for home to load
</span><span class="lines">@@ -1699,14 +1699,14 @@
</span><span class="cx">         @type no_cache: C{bool}
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-        queryCacher = transaction._queryCacher
</del><ins>+        queryCacher = txn._queryCacher
</ins><span class="cx">         homeData = None
</span><span class="cx">         if queryCacher:
</span><span class="cx">             cacheKey = queryCacher.keyForHomeData(cls._homeType, ownerUID, migration)
</span><span class="cx">             homeData = yield queryCacher.get(cacheKey)
</span><span class="cx"> 
</span><span class="cx">         if homeData is None:
</span><del>-            homeData = yield cls._homeColumnsFromOwnerQuery.on(transaction, ownerUID=ownerUID, migration=migration)
</del><ins>+            homeData = yield cls._homeColumnsFromOwnerQuery.on(txn, ownerUID=ownerUID, migration=migration)
</ins><span class="cx">             if homeData:
</span><span class="cx">                 homeData = homeData[0]
</span><span class="cx">                 if not no_cache and queryCacher:
</span><span class="lines">@@ -1725,20 +1725,20 @@
</span><span class="cx"> 
</span><span class="cx">         if metadataData is None:
</span><span class="cx">             # Don't have a cached copy
</span><del>-            metadataData = (yield cls._metaDataQuery.on(transaction, resourceID=resourceID))
</del><ins>+            metadataData = (yield cls._metaDataQuery.on(txn, resourceID=resourceID))
</ins><span class="cx">             if metadataData:
</span><span class="cx">                 metadataData = metadataData[0]
</span><span class="cx">             else:
</span><span class="cx">                 metadataData = None
</span><span class="cx">             if queryCacher:
</span><span class="cx">                 # Cache the metadataData
</span><del>-                yield queryCacher.setAfterCommit(transaction, cacheKey, metadataData)
</del><ins>+                yield queryCacher.setAfterCommit(txn, cacheKey, metadataData)
</ins><span class="cx"> 
</span><span class="cx">         returnValue((homeData, metadataData))
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def __init__(self, transaction, ownerUID, migration=_MIGRATION_STATUS_NONE):
-        self._txn = transaction
</del><ins>+    def __init__(self, txn, ownerUID, migration=_MIGRATION_STATUS_NONE):
+        self._txn = txn
</ins><span class="cx">         self._ownerUID = ownerUID
</span><span class="cx">         self._resourceID = None
</span><span class="cx">         self._status = _HOME_STATUS_NORMAL
</span><span class="lines">@@ -1770,6 +1770,34 @@
</span><span class="cx">         return succeed(None)
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def externalize(self):
+        &quot;&quot;&quot;
+        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.
+        &quot;&quot;&quot;
+        serialized = {}
+        serialized[&quot;home&quot;] = dict([(attr[1:], getattr(self, attr, None)) for attr in self.homeAttributes()])
+        serialized[&quot;metadata&quot;] = dict([(attr[1:], getattr(self, attr, None)) for attr in self.metadataAttributes()])
+        return serialized
+
+
+    @classmethod
+    @inlineCallbacks
+    def internalize(cls, txn, mapping):
+        &quot;&quot;&quot;
+        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.
+        &quot;&quot;&quot;
+
+        home = [mapping[&quot;home&quot;].get(row[1:]) for row in cls.homeAttributes()]
+        metadata = [mapping[&quot;metadata&quot;].get(row[1:]) for row in cls.metadataAttributes()]
+        child = yield cls.makeClass(txn, home, metadata)
+        returnValue(child)
+
+
</ins><span class="cx">     def quotaAllowedBytes(self):
</span><span class="cx">         return self._txn.store().quota
</span><span class="cx"> 
</span><span class="lines">@@ -2714,19 +2742,66 @@
</span><span class="cx"> 
</span><span class="cx">         assert self._migration == _MIGRATION_STATUS_MIGRATING
</span><span class="cx"> 
</span><del>-        # 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
</del><ins>+        # Get external home for the user. This is a &quot;virtual&quot; 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
</ins><span class="cx"> 
</span><span class="cx">         # Force the external home to look like it is migrating. This will enable certain external API calls
</span><span class="cx">         # that are normally disabled for sharing (e.g., ability to load all child resources).
</span><del>-        otherHome._migration = _MIGRATION_STATUS_MIGRATING
</del><ins>+        externalHome._migration = _MIGRATION_STATUS_MIGRATING
</ins><span class="cx"> 
</span><ins>+        # 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):
+        &quot;&quot;&quot;
+        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}.
+        &quot;&quot;&quot;
+        remote = yield self._txn.store().conduit.send_get(self)
+        remote[&quot;home&quot;][&quot;status&quot;] = _HOME_STATUS_EXTERNAL
+        remote = yield self.internalize(self._txn, remote)
+        returnValue(remote)
+
+
+    def preSyncChildren(self, externalHome, final):
+        &quot;&quot;&quot;
+        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.
+        &quot;&quot;&quot;
+        return succeed(None)
+
+
+    def postSyncChildren(self, externalHome, final):
+        &quot;&quot;&quot;
+        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).
+        &quot;&quot;&quot;
+
+        # TODO: quota setting
+        return succeed(None)
+
+
+    @inlineCallbacks
+    def syncChildren(self, externalHome, final):
+        &quot;&quot;&quot;
+        Synchronize the remote children of the local external home.
+        &quot;&quot;&quot;
+
</ins><span class="cx">         local_children = yield self.loadChildren()
</span><span class="cx">         local_children = dict([(child.external_id(), child) for child in local_children if child.owned()])
</span><span class="cx"> 
</span><span class="cx">         # Get list of owned child collections
</span><del>-        remote_children = yield otherHome.loadChildren()
</del><ins>+        remote_children = yield externalHome.loadChildren()
</ins><span class="cx">         remote_children = dict([(child.id(), child) for child in remote_children if child.owned()])
</span><span class="cx"> 
</span><span class="cx">         # Remove local ones to longer present on remote
</span></span></pre>
</div>
</div>

</body>
</html>