<!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>[14464] CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore</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/14464">14464</a></dd>
<dt>Author</dt> <dd>cdaboo@apple.com</dd>
<dt>Date</dt> <dd>2015-02-20 13:21:21 -0800 (Fri, 20 Feb 2015)</dd>
</dl>

<h3>Log Message</h3>
<pre>Checkpoint: migration final sync notifications.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#CalendarServerbranchesuserscdaboopod2podmigrationtxdavcommondatastorepoddingmigrationhome_syncpy">CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/migration/home_sync.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboopod2podmigrationtxdavcommondatastorepoddingmigrationtesttest_home_syncpy">CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/migration/test/test_home_sync.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboopod2podmigrationtxdavcommondatastorepoddingstore_apipy">CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/store_api.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboopod2podmigrationtxdavcommondatastorepoddingutilpy">CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/util.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboopod2podmigrationtxdavcommondatastoresql_externalpy">CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql_external.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboopod2podmigrationtxdavcommondatastoresql_notificationpy">CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql_notification.py</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="CalendarServerbranchesuserscdaboopod2podmigrationtxdavcommondatastorepoddingmigrationhome_syncpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/migration/home_sync.py (14463 => 14464)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/migration/home_sync.py        2015-02-20 19:28:09 UTC (rev 14463)
+++ CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/migration/home_sync.py        2015-02-20 21:21:21 UTC (rev 14464)
</span><span class="lines">@@ -23,6 +23,9 @@
</span><span class="cx"> from txdav.common.datastore.podding.migration.sync_metadata import CalendarMigrationRecord, \
</span><span class="cx">     CalendarObjectMigrationRecord, AttachmentMigrationRecord
</span><span class="cx"> from txdav.caldav.datastore.sql import ManagedAttachment
</span><ins>+from txdav.common.datastore.sql_external import NotificationCollectionExternal
+from txdav.common.datastore.sql_notification import NotificationCollection
+from txdav.common.datastore.sql_tables import _HOME_STATUS_EXTERNAL
</ins><span class="cx"> from txdav.common.idirectoryservice import DirectoryRecordNotFoundError
</span><span class="cx"> 
</span><span class="cx"> import uuid
</span><span class="lines">@@ -186,7 +189,7 @@
</span><span class="cx">         pass
</span><span class="cx"> 
</span><span class="cx">         # TODO: notifications
</span><del>-        pass
</del><ins>+        yield self.notificationsReconcile()
</ins><span class="cx"> 
</span><span class="cx">         # TODO: work items
</span><span class="cx">         pass
</span><span class="lines">@@ -210,7 +213,7 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx">         # TODO: implement API on CommonHome to rename the ownerUID column and
</span><del>-        # change the status column.
</del><ins>+        # change the status column. Also adjust NotificationCollection.
</ins><span class="cx">         pass
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -729,7 +732,7 @@
</span><span class="cx"> 
</span><span class="cx">         # Batch setting links for the local home
</span><span class="cx">         len_links = len(links)
</span><del>-        while len(links):
</del><ins>+        while links:
</ins><span class="cx">             yield self.makeAttachmentLinks(links[:50], attachmentIDMap, objectIDMap)
</span><span class="cx">             links = links[50:]
</span><span class="cx"> 
</span><span class="lines">@@ -890,3 +893,53 @@
</span><span class="cx">             except KeyError:
</span><span class="cx">                 continue
</span><span class="cx">             yield groupAttendee.insert(txn)
</span><ins>+
+
+    @inlineCallbacks
+    def notificationsReconcile(self):
+        &quot;&quot;&quot;
+        Sync all the existing L{NotificationObject} resources from the remote store.
+        &quot;&quot;&quot;
+
+        records = yield self.notificationRecords()
+
+        # Batch setting resources for the local home
+        len_records = len(records)
+        while records:
+            yield self.makeNotifications(records[:50])
+            records = records[50:]
+
+        returnValue(len_records)
+
+
+    @inTransactionWrapper
+    @inlineCallbacks
+    def notificationRecords(self, txn):
+        &quot;&quot;&quot;
+        Get all the existing L{NotificationObjectRecord}'s from the remote store.
+        &quot;&quot;&quot;
+
+        notifications = yield NotificationCollectionExternal.notificationsWithUID(txn, self.diruid, True)
+        records = yield notifications.notificationObjectRecords()
+        for record in records:
+            # This needs to be reset when added to the local store
+            del record.resourceID
+
+            # Map the remote id to the local one.
+            record.notificationHomeResourceID = notifications.id()
+
+        returnValue(records)
+
+
+    @inTransactionWrapper
+    @inlineCallbacks
+    def makeNotifications(self, txn, records):
+        &quot;&quot;&quot;
+        Create L{NotificationObjectRecord} records in the local store.
+        &quot;&quot;&quot;
+
+        notifications = yield NotificationCollection.notificationsWithUID(txn, self.diruid, True, _HOME_STATUS_EXTERNAL)
+        for record in records:
+            # Do this via the &quot;write&quot; API so that sync revisions are updated properly, rather than just
+            # inserting the records directly.
+            yield notifications.writeNotificationObject(record.notificationUID, record.notificationType, record.notificationData)
</ins></span></pre></div>
<a id="CalendarServerbranchesuserscdaboopod2podmigrationtxdavcommondatastorepoddingmigrationtesttest_home_syncpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/migration/test/test_home_sync.py (14463 => 14464)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/migration/test/test_home_sync.py        2015-02-20 19:28:09 UTC (rev 14463)
+++ CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/migration/test/test_home_sync.py        2015-02-20 21:21:21 UTC (rev 14464)
</span><span class="lines">@@ -29,11 +29,14 @@
</span><span class="cx"> from txdav.common.datastore.podding.test.util import MultiStoreConduitTest
</span><span class="cx"> from txdav.common.datastore.sql_directory import DelegateRecord, \
</span><span class="cx">     ExternalDelegateGroupsRecord, DelegateGroupsRecord
</span><del>-from txdav.common.datastore.sql_tables import schema
</del><ins>+from txdav.common.datastore.sql_notification import NotificationCollection
+from txdav.common.datastore.sql_tables import schema, _HOME_STATUS_EXTERNAL
</ins><span class="cx"> from txdav.common.datastore.test.util import populateCalendarsFrom
</span><span class="cx"> from txdav.who.delegates import Delegates
</span><span class="cx"> from txweb2.http_headers import MimeType
</span><span class="cx"> from txweb2.stream import MemoryStream
</span><ins>+from uuid import uuid4
+import json
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> class TestCrossPodHomeSync(MultiStoreConduitTest):
</span><span class="lines">@@ -926,7 +929,53 @@
</span><span class="cx">         yield self.commitTransaction(1)
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
+    def test_notifications_reconcile(self):
+        &quot;&quot;&quot;
+        Test that L{delegateReconcile} copies over the full set of delegates and caches associated groups..
+        &quot;&quot;&quot;
</ins><span class="cx"> 
</span><ins>+        # Create remote home - and add some fake notifications
+        yield self.homeUnderTest(txn=self.theTransactionUnderTest(0), name=&quot;user01&quot;, create=True)
+        notifications = yield self.theTransactionUnderTest(0).notificationsWithUID(&quot;user01&quot;)
+        uid1 = str(uuid4())
+        obj1 = yield notifications.writeNotificationObject(uid1, &quot;type1&quot;, &quot;data1&quot;)
+        id1 = obj1.id()
+        uid2 = str(uuid4())
+        obj2 = yield notifications.writeNotificationObject(uid2, &quot;type2&quot;, &quot;data2&quot;)
+        id2 = obj2.id()
+        yield self.commitTransaction(0)
+
+        # Sync from remote side
+        syncer = CrossPodHomeSync(self.theStoreUnderTest(1), &quot;user01&quot;)
+        yield syncer.loadRecord()
+        syncer.homeId = yield syncer.prepareCalendarHome()
+        changes = yield syncer.notificationsReconcile()
+        self.assertEqual(changes, 2)
+
+        # Now have local notifications
+        notifications = yield NotificationCollection.notificationsWithUID(
+            self.theTransactionUnderTest(1),
+            &quot;user01&quot;,
+            True,
+            _HOME_STATUS_EXTERNAL
+        )
+        results = yield notifications.notificationObjects()
+        self.assertEqual(len(results), 2)
+        for result in results:
+            for test_uid, test_id, test_type, test_data in ((uid1, id1, &quot;type1&quot;, &quot;data1&quot;,), (uid2, id2, &quot;type2&quot;, &quot;data2&quot;,),):
+                if result.uid() == test_uid:
+                    self.assertNotEqual(result.id(), test_id)
+                    self.assertEqual(json.loads(result.notificationType()), test_type)
+                    data = yield result.notificationData()
+                    self.assertEqual(json.loads(data), test_data)
+                    break
+            else:
+                self.fail(&quot;Notification uid {} not found&quot;.format(result.uid()))
+        yield self.commitTransaction(1)
+
+
+
</ins><span class="cx"> class TestGroupAttendeeSync(MultiStoreConduitTest):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     GroupAttendeeReconciliation tests
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboopod2podmigrationtxdavcommondatastorepoddingstore_apipy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/store_api.py (14463 => 14464)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/store_api.py        2015-02-20 19:28:09 UTC (rev 14463)
+++ CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/store_api.py        2015-02-20 21:21:21 UTC (rev 14464)
</span><span class="lines">@@ -163,3 +163,6 @@
</span><span class="cx"> UtilityConduitMixin._make_simple_action(StoreAPIConduitMixin, &quot;objectresource_setcomponent&quot;, &quot;setComponent&quot;)
</span><span class="cx"> UtilityConduitMixin._make_simple_action(StoreAPIConduitMixin, &quot;objectresource_component&quot;, &quot;component&quot;, transform_recv_result=UtilityConduitMixin._to_string)
</span><span class="cx"> UtilityConduitMixin._make_simple_action(StoreAPIConduitMixin, &quot;objectresource_remove&quot;, &quot;remove&quot;)
</span><ins>+
+# Calls on L{NotificationCollection} objects
+UtilityConduitMixin._make_simple_action(StoreAPIConduitMixin, &quot;notification_all_records&quot;, &quot;notificationObjectRecords&quot;, classMethod=False, transform_recv_result=UtilityConduitMixin._to_serialize_list)
</ins></span></pre></div>
<a id="CalendarServerbranchesuserscdaboopod2podmigrationtxdavcommondatastorepoddingutilpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/util.py (14463 => 14464)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/util.py        2015-02-20 19:28:09 UTC (rev 14463)
+++ CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/util.py        2015-02-20 21:21:21 UTC (rev 14464)
</span><span class="lines">@@ -17,6 +17,8 @@
</span><span class="cx"> from twisted.internet.defer import inlineCallbacks, returnValue
</span><span class="cx"> 
</span><span class="cx"> from txdav.common.datastore.podding.base import FailedCrossPodRequestError
</span><ins>+from txdav.common.datastore.sql_notification import NotificationCollection, \
+    NotificationObject
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> class UtilityConduitMixin(object):
</span><span class="lines">@@ -56,6 +58,7 @@
</span><span class="cx">         viewer_home = None
</span><span class="cx">         home_child = None
</span><span class="cx">         object_resource = None
</span><ins>+        notification = None
</ins><span class="cx">         if isinstance(storeObject, CommonObjectResource):
</span><span class="cx">             owner_home = storeObject.ownerHome()
</span><span class="cx">             viewer_home = storeObject.viewerHome()
</span><span class="lines">@@ -71,22 +74,31 @@
</span><span class="cx">             viewer_home = storeObject
</span><span class="cx">             txn = storeObject._txn
</span><span class="cx">             result[&quot;classMethod&quot;] = classMethod
</span><ins>+        elif isinstance(storeObject, NotificationCollection):
+            notification = storeObject
+            txn = storeObject._txn
+            result[&quot;classMethod&quot;] = classMethod
</ins><span class="cx"> 
</span><span class="cx">         # Add store object identities to JSON request
</span><del>-        result[&quot;homeType&quot;] = viewer_home._homeType
-        result[&quot;homeUID&quot;] = viewer_home.uid()
-        if home_child:
-            if home_child.owned():
-                result[&quot;homeChildID&quot;] = home_child.id()
-            else:
-                result[&quot;homeChildSharedID&quot;] = home_child.name()
-        if object_resource:
-            result[&quot;objectResourceID&quot;] = object_resource.id()
</del><ins>+        if viewer_home:
+            result[&quot;homeType&quot;] = viewer_home._homeType
+            result[&quot;homeUID&quot;] = viewer_home.uid()
+            if home_child:
+                if home_child.owned():
+                    result[&quot;homeChildID&quot;] = home_child.id()
+                else:
+                    result[&quot;homeChildSharedID&quot;] = home_child.name()
+            if object_resource:
+                result[&quot;objectResourceID&quot;] = object_resource.id()
</ins><span class="cx"> 
</span><del>-        # Note that the owner_home is always the ownerHome() because in the sharing case
-        # a viewer is accessing the owner's data on another pod.
-        recipient = yield self.store.directoryService().recordWithUID(owner_home.uid())
</del><ins>+            # Note that the owner_home is always the ownerHome() because in the sharing case
+            # a viewer is accessing the owner's data on another pod.
+            recipient = yield self.store.directoryService().recordWithUID(owner_home.uid())
</ins><span class="cx"> 
</span><ins>+        elif notification:
+            result[&quot;notificationUID&quot;] = notification.uid()
+            recipient = yield self.store.directoryService().recordWithUID(notification.uid())
+
</ins><span class="cx">         returnValue((txn, result, recipient.server(),))
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -129,6 +141,15 @@
</span><span class="cx">                 raise FailedCrossPodRequestError(&quot;Invalid object resource specified&quot;)
</span><span class="cx">             returnObject = objectResource
</span><span class="cx"> 
</span><ins>+        if &quot;notificationUID&quot; in request:
+            notification = yield txn.notificationsWithUID(request[&quot;notificationUID&quot;])
+            if notification is None:
+                raise FailedCrossPodRequestError(&quot;Invalid notification UID specified&quot;)
+            notification._internalRequest = False
+            returnObject = notification
+            if request.get(&quot;classMethod&quot;, False):
+                classObject = NotificationObject
+
</ins><span class="cx">         returnValue((returnObject, classObject,))
</span><span class="cx"> 
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboopod2podmigrationtxdavcommondatastoresql_externalpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql_external.py (14463 => 14464)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql_external.py        2015-02-20 19:28:09 UTC (rev 14463)
+++ CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql_external.py        2015-02-20 21:21:21 UTC (rev 14464)
</span><span class="lines">@@ -26,6 +26,8 @@
</span><span class="cx"> from txdav.base.propertystore.sql import PropertyStore
</span><span class="cx"> from txdav.common.datastore.sql import CommonHome, CommonHomeChild, \
</span><span class="cx">     CommonObjectResource
</span><ins>+from txdav.common.datastore.sql_notification import NotificationCollection, \
+    NotificationObjectRecord
</ins><span class="cx"> from txdav.common.datastore.sql_tables import _HOME_STATUS_EXTERNAL
</span><span class="cx"> from txdav.common.icommondatastore import NonExistentExternalShare, \
</span><span class="cx">     ExternalShareFailed
</span><span class="lines">@@ -199,7 +201,6 @@
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx">     @classmethod
</span><del>-    @inlineCallbacks
</del><span class="cx">     def listObjects(cls, home):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Retrieve the names of the children that exist in the given home.
</span><span class="lines">@@ -207,8 +208,7 @@
</span><span class="cx">         @return: an iterable of C{str}s.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-        results = yield home._txn.store().conduit.send_homechild_listobjects(home)
-        returnValue(results)
</del><ins>+        return home._txn.store().conduit.send_homechild_listobjects(home)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @classmethod
</span><span class="lines">@@ -385,17 +385,13 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @classmethod
</span><del>-    @inlineCallbacks
</del><span class="cx">     def listObjects(cls, parent):
</span><del>-        results = yield parent._txn.store().conduit.send_objectresource_listobjects(parent)
-        returnValue(results)
</del><ins>+        return parent._txn.store().conduit.send_objectresource_listobjects(parent)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @classmethod
</span><del>-    @inlineCallbacks
</del><span class="cx">     def countObjects(cls, parent):
</span><del>-        result = yield parent._txn.store().conduit.send_objectresource_countobjects(parent)
-        returnValue(result)
</del><ins>+        return parent._txn.store().conduit.send_objectresource_countobjects(parent)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @classmethod
</span><span class="lines">@@ -411,17 +407,13 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @classmethod
</span><del>-    @inlineCallbacks
</del><span class="cx">     def resourceNameForUID(cls, parent, uid):
</span><del>-        result = yield parent._txn.store().conduit.send_objectresource_resourcenameforuid(parent, uid)
-        returnValue(result)
</del><ins>+        return parent._txn.store().conduit.send_objectresource_resourcenameforuid(parent, uid)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @classmethod
</span><del>-    @inlineCallbacks
</del><span class="cx">     def resourceUIDForName(cls, parent, name):
</span><del>-        result = yield parent._txn.store().conduit.send_objectresource_resourceuidforname(parent, name)
-        returnValue(result)
</del><ins>+        return parent._txn.store().conduit.send_objectresource_resourceuidforname(parent, name)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @classmethod
</span><span class="lines">@@ -452,6 +444,23 @@
</span><span class="cx">         returnValue(self._cachedComponent)
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def remove(self):
+        return self._txn.store().conduit.send_objectresource_remove(self)
+
+
+
+class NotificationCollectionExternal(NotificationCollection):
+    &quot;&quot;&quot;
+    A NotificationCollection for a resource not hosted on this system, but on another pod. This will forward
+    specific apis to the other pod using cross-pod requests.
+    &quot;&quot;&quot;
+
+    @classmethod
+    def notificationsWithUID(cls, txn, uid, create):
+        return super(NotificationCollectionExternal, cls).notificationsWithUID(txn, uid, create, expected_status=_HOME_STATUS_EXTERNAL)
+
+
</ins><span class="cx">     @inlineCallbacks
</span><del>-    def remove(self):
-        yield self._txn.store().conduit.send_objectresource_remove(self)
</del><ins>+    def notificationObjectRecords(self):
+        results = yield self._txn.store().conduit.send_notification_all_records(self)
+        returnValue(map(NotificationObjectRecord.deserialize, results))
</ins></span></pre></div>
<a id="CalendarServerbranchesuserscdaboopod2podmigrationtxdavcommondatastoresql_notificationpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql_notification.py (14463 => 14464)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql_notification.py        2015-02-20 19:28:09 UTC (rev 14463)
+++ CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql_notification.py        2015-02-20 21:21:21 UTC (rev 14464)
</span><span class="lines">@@ -15,6 +15,7 @@
</span><span class="cx"> # limitations under the License.
</span><span class="cx"> ##
</span><span class="cx"> 
</span><ins>+from twext.enterprise.dal.record import SerializableRecord, fromTable
</ins><span class="cx"> from twext.enterprise.dal.syntax import Select, Parameter, Insert, \
</span><span class="cx">     SavepointAction, Delete, Max, Len, Update
</span><span class="cx"> from twext.enterprise.util import parseSQLTimestamp
</span><span class="lines">@@ -56,11 +57,12 @@
</span><span class="cx">     _homeSchema = schema.NOTIFICATION_HOME
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def __init__(self, txn, uid, resourceID):
</del><ins>+    def __init__(self, txn, uid, resourceID, status):
</ins><span class="cx"> 
</span><span class="cx">         self._txn = txn
</span><span class="cx">         self._uid = uid
</span><span class="cx">         self._resourceID = resourceID
</span><ins>+        self._status = status
</ins><span class="cx">         self._dataVersion = None
</span><span class="cx">         self._notifications = {}
</span><span class="cx">         self._notificationNames = None
</span><span class="lines">@@ -71,15 +73,22 @@
</span><span class="cx">         self._notifiers = dict([(factory_name, factory.newNotifier(self),) for factory_name, factory in txn._notifierFactories.items()])
</span><span class="cx"> 
</span><span class="cx">     _resourceIDFromUIDQuery = Select(
</span><del>-        [_homeSchema.RESOURCE_ID], From=_homeSchema,
-        Where=_homeSchema.OWNER_UID == Parameter(&quot;uid&quot;))
</del><ins>+        [_homeSchema.RESOURCE_ID, _homeSchema.STATUS],
+        From=_homeSchema,
+        Where=_homeSchema.OWNER_UID == Parameter(&quot;uid&quot;)
+    )
</ins><span class="cx"> 
</span><span class="cx">     _UIDFromResourceIDQuery = Select(
</span><del>-        [_homeSchema.OWNER_UID], From=_homeSchema,
-        Where=_homeSchema.RESOURCE_ID == Parameter(&quot;rid&quot;))
</del><ins>+        [_homeSchema.OWNER_UID],
+        From=_homeSchema,
+        Where=_homeSchema.RESOURCE_ID == Parameter(&quot;rid&quot;)
+    )
</ins><span class="cx"> 
</span><span class="cx">     _provisionNewNotificationsQuery = Insert(
</span><del>-        {_homeSchema.OWNER_UID: Parameter(&quot;uid&quot;)},
</del><ins>+        {
+            _homeSchema.OWNER_UID: Parameter(&quot;uid&quot;),
+            _homeSchema.STATUS: Parameter(&quot;status&quot;),
+        },
</ins><span class="cx">         Return=_homeSchema.RESOURCE_ID
</span><span class="cx">     )
</span><span class="cx"> 
</span><span class="lines">@@ -95,7 +104,7 @@
</span><span class="cx"> 
</span><span class="cx">     @classmethod
</span><span class="cx">     @inlineCallbacks
</span><del>-    def notificationsWithUID(cls, txn, uid, create):
</del><ins>+    def notificationsWithUID(cls, txn, uid, create, expected_status=_HOME_STATUS_NORMAL):
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         @param uid: I'm going to assume uid is utf-8 encoded bytes
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="lines">@@ -103,6 +112,9 @@
</span><span class="cx"> 
</span><span class="cx">         if rows:
</span><span class="cx">             resourceID = rows[0][0]
</span><ins>+            status = rows[0][1]
+            if status != expected_status:
+                raise RecordNotAllowedError(&quot;Notifications status mismatch: {} != {}&quot;.format(status, expected_status))
</ins><span class="cx">             created = False
</span><span class="cx">         elif create:
</span><span class="cx">             # Determine if the user is local or external
</span><span class="lines">@@ -110,9 +122,9 @@
</span><span class="cx">             if record is None:
</span><span class="cx">                 raise DirectoryRecordNotFoundError(&quot;Cannot create home for UID since no directory record exists: {}&quot;.format(uid))
</span><span class="cx"> 
</span><del>-            state = _HOME_STATUS_NORMAL if record.thisServer() else _HOME_STATUS_EXTERNAL
-            if state == _HOME_STATUS_EXTERNAL:
-                raise RecordNotAllowedError(&quot;Cannot store notifications for external user: {}&quot;.format(uid))
</del><ins>+            status = _HOME_STATUS_NORMAL if record.thisServer() else _HOME_STATUS_EXTERNAL
+            if status != expected_status:
+                raise RecordNotAllowedError(&quot;Notifications status mismatch: {} != {}&quot;.format(status, expected_status))
</ins><span class="cx"> 
</span><span class="cx">             # Use savepoint so we can do a partial rollback if there is a race
</span><span class="cx">             # condition where this row has already been inserted
</span><span class="lines">@@ -121,7 +133,7 @@
</span><span class="cx"> 
</span><span class="cx">             try:
</span><span class="cx">                 resourceID = str((
</span><del>-                    yield cls._provisionNewNotificationsQuery.on(txn, uid=uid)
</del><ins>+                    yield cls._provisionNewNotificationsQuery.on(txn, uid=uid, status=status)
</ins><span class="cx">                 )[0][0])
</span><span class="cx">             except Exception:
</span><span class="cx">                 # FIXME: Really want to trap the pg.DatabaseError but in a non-
</span><span class="lines">@@ -132,6 +144,9 @@
</span><span class="cx">                 rows = yield cls._resourceIDFromUIDQuery.on(txn, uid=uid)
</span><span class="cx">                 if rows:
</span><span class="cx">                     resourceID = rows[0][0]
</span><ins>+                    status = rows[0][1]
+                    if status != expected_status:
+                        raise RecordNotAllowedError(&quot;Notifications status mismatch: {} != {}&quot;.format(status, expected_status))
</ins><span class="cx">                     created = False
</span><span class="cx">                 else:
</span><span class="cx">                     raise
</span><span class="lines">@@ -140,7 +155,7 @@
</span><span class="cx">                 yield savepoint.release(txn)
</span><span class="cx">         else:
</span><span class="cx">             returnValue(None)
</span><del>-        collection = cls(txn, uid, resourceID)
</del><ins>+        collection = cls(txn, uid, resourceID, status)
</ins><span class="cx">         yield collection._loadPropertyStore()
</span><span class="cx">         if created:
</span><span class="cx">             yield collection._initSyncToken()
</span><span class="lines">@@ -224,6 +239,10 @@
</span><span class="cx">         return self._home
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def notificationObjectRecords(self):
+        return NotificationObjectRecord.querysimple(self._txn, notificationHomeResourceID=self.id())
+
+
</ins><span class="cx">     @inlineCallbacks
</span><span class="cx">     def notificationObjects(self):
</span><span class="cx">         results = (yield NotificationObject.loadAllObjects(self))
</span><span class="lines">@@ -292,6 +311,7 @@
</span><span class="cx">         else:
</span><span class="cx">             yield self._updateRevision(&quot;%s.xml&quot; % (uid,))
</span><span class="cx">         yield self.notifyChanged()
</span><ins>+        returnValue(notificationObject)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def removeNotificationObjectWithName(self, name):
</span><span class="lines">@@ -441,6 +461,15 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+class NotificationObjectRecord(SerializableRecord, fromTable(schema.NOTIFICATION)):
+    &quot;&quot;&quot;
+    @DynamicAttrs
+    L{Record} for L{schema.NOTIFICATION}.
+    &quot;&quot;&quot;
+    pass
+
+
+
</ins><span class="cx"> class NotificationObject(FancyEqMixin, object):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     This used to store XML data and an XML element for the type. But we are now switching it
</span></span></pre>
</div>
</div>

</body>
</html>