<!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):
+ """
+ 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)
+
+
</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("CommonHomeExternal: not supported")
</span><span class="cx">
</span><span class="cx">
</span><del>- 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")
-
-
</del><span class="cx"> def setDefaultAlarm(self, alarm, vevent, timed):
</span><span class="cx"> """
</span><span class="cx"> No children.
</span><span class="lines">@@ -138,13 +117,6 @@
</span><span class="cx"> raise AssertionError("CommonHomeExternal: not supported")
</span><span class="cx">
</span><span class="cx">
</span><del>- def getAvailability(self):
- """
- No children.
- """
- raise AssertionError("CommonHomeExternal: not supported")
-
-
</del><span class="cx"> def setAvailability(self, availability):
</span><span class="cx"> """
</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("arguments", ()), **message.get("keywords", {}))
</del><ins>+ # Operate on the target
+ if method is not None:
+ value = yield getattr(target, method)(*message.get("arguments", ()), **message.get("keywords", {}))
+ else:
+ value = target
</ins><span class="cx"> except Exception as e:
</span><span class="cx"> returnValue({
</span><span class="cx"> "result": "exception",
</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("get", None, transform_recv=PoddingConduit._to_externalize)
</ins><span class="cx"> PoddingConduit._make_migrate_action("loadchildren", "loadChildren", 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"> """
</span><span class="cx">
</span><span class="cx"> @inlineCallbacks
</span><ins>+ 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
</ins><span class="cx"> def test_loadallobjects(self):
</span><span class="cx"> """
</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"> """
</span><span class="cx"> Test that the migration api works for migration.
</span><span class="cx"> """
</span><span class="cx">
</span><ins>+ 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@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
+""")
+
+ 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@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
+""")
+
</ins><span class="cx"> @inlineCallbacks
</span><span class="cx"> def _provision_remote(self):
</span><span class="cx"> """
</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="puser01")
</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):
+ """
+ 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
</ins><span class="cx"> def test_step1_twice(self):
</span><span class="cx"> """
</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"> """
</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"> """
</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"> """
</span><del>- Test that step1 fails a second time.
</del><ins>+ Test that step2 syncs changes.
</ins><span class="cx"> """
</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("todos")
</span><span class="cx"> self.assertTrue(tasks.isUsedForFreeBusy())
</span><ins>+
+
+ @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)
</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"> """
</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"> """
</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"> """
</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):
+ """
+ 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)
+
+
</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 "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
</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):
+ """
+ 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.
+ """
+
</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>