<!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>[14417] CalendarServer/branches/users/cdaboo/pod2pod-migration</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/14417">14417</a></dd>
<dt>Author</dt> <dd>cdaboo@apple.com</dd>
<dt>Date</dt> <dd>2015-02-16 12:56:21 -0800 (Mon, 16 Feb 2015)</dd>
</dl>

<h3>Log Message</h3>
<pre>Checkpoint: re-factor groups/delegates store API to use DAL Record objects to make it easier to serialize/deserialize them for pod2pod.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#CalendarServerbranchesuserscdaboopod2podmigrationcalendarservertoolsprincipalspy">CalendarServer/branches/users/cdaboo/pod2pod-migration/calendarserver/tools/principals.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboopod2podmigrationcalendarserverwebadminworkpy">CalendarServer/branches/users/cdaboo/pod2pod-migration/calendarserver/webadmin/work.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboopod2podmigrationrequirementsstabletxt">CalendarServer/branches/users/cdaboo/pod2pod-migration/requirements-stable.txt</a></li>
<li><a href="#CalendarServerbranchesuserscdaboopod2podmigrationtxdavcaldavdatastoreschedulingtesttest_workpy">CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/caldav/datastore/scheduling/test/test_work.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboopod2podmigrationtxdavcaldavdatastoreschedulingworkpy">CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/caldav/datastore/scheduling/work.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboopod2podmigrationtxdavcaldavdatastoresqlpy">CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/caldav/datastore/sql.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboopod2podmigrationtxdavcaldavdatastoresql_externalpy">CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/caldav/datastore/sql_external.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboopod2podmigrationtxdavcommondatastorepoddingattachmentspy">CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/attachments.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboopod2podmigrationtxdavcommondatastorepoddingmigrationhome_syncpy">CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/migration/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="#CalendarServerbranchesuserscdaboopod2podmigrationtxdavcommondatastorepoddingtesttest_store_apipy">CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/test/test_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="#CalendarServerbranchesuserscdaboopod2podmigrationtxdavcommondatastoresqlpy">CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql.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="#CalendarServerbranchesuserscdaboopod2podmigrationtxdavwhodelegatespy">CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/who/delegates.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboopod2podmigrationtxdavwhogroupspy">CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/who/groups.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboopod2podmigrationtxdavwhotesttest_delegatespy">CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/who/test/test_delegates.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboopod2podmigrationtxdavwhotesttest_group_attendeespy">CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/who/test/test_group_attendees.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboopod2podmigrationtxdavwhotesttest_groupspy">CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/who/test/test_groups.py</a></li>
</ul>

<h3>Added Paths</h3>
<ul>
<li><a href="#CalendarServerbranchesuserscdaboopod2podmigrationtxdavcommondatastoresql_directorypy">CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql_directory.py</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="CalendarServerbranchesuserscdaboopod2podmigrationcalendarservertoolsprincipalspy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/calendarserver/tools/principals.py (14416 => 14417)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/pod2pod-migration/calendarserver/tools/principals.py        2015-02-16 20:33:55 UTC (rev 14416)
+++ CalendarServer/branches/users/cdaboo/pod2pod-migration/calendarserver/tools/principals.py        2015-02-16 20:56:21 UTC (rev 14417)
</span><span class="lines">@@ -775,15 +775,11 @@
</span><span class="cx">                 groupUIDs.append(record.uid)
</span><span class="cx"> 
</span><span class="cx">     for groupUID in groupUIDs:
</span><del>-        (
-            groupID, name, _ignore_membershipHash, modified, _ignore_extant
-        ) = yield txn.groupByUID(
-            groupUID
-        )
-        print(&quot;Group: \&quot;{name}\&quot; ({uid})&quot;.format(name=name, uid=groupUID))
</del><ins>+        group = yield txn.groupByUID(groupUID)
+        print(&quot;Group: \&quot;{name}\&quot; ({uid})&quot;.format(name=group.name, uid=group.groupUID))
</ins><span class="cx"> 
</span><span class="cx">         for txt, readWrite in ((&quot;read-only&quot;, False), (&quot;read-write&quot;, True)):
</span><del>-            delegatorUIDs = yield txn.delegatorsToGroup(groupID, readWrite)
</del><ins>+            delegatorUIDs = yield txn.delegatorsToGroup(group.groupID, readWrite)
</ins><span class="cx">             for delegatorUID in delegatorUIDs:
</span><span class="cx">                 delegator = yield directory.recordWithUID(delegatorUID)
</span><span class="cx">                 print(
</span><span class="lines">@@ -793,12 +789,12 @@
</span><span class="cx">                 )
</span><span class="cx"> 
</span><span class="cx">         print(&quot;Group members:&quot;)
</span><del>-        memberUIDs = yield txn.groupMemberUIDs(groupID)
</del><ins>+        memberUIDs = yield txn.groupMemberUIDs(group.groupID)
</ins><span class="cx">         for memberUID in memberUIDs:
</span><span class="cx">             record = yield directory.recordWithUID(memberUID)
</span><span class="cx">             print(prettyRecord(record))
</span><span class="cx"> 
</span><del>-        print(&quot;Last cached: {} GMT&quot;.format(modified))
</del><ins>+        print(&quot;Last cached: {} GMT&quot;.format(group.modified))
</ins><span class="cx">         print()
</span><span class="cx"> 
</span><span class="cx">     yield txn.commit()
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboopod2podmigrationcalendarserverwebadminworkpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/calendarserver/webadmin/work.py (14416 => 14417)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/pod2pod-migration/calendarserver/webadmin/work.py        2015-02-16 20:33:55 UTC (rev 14416)
+++ CalendarServer/branches/users/cdaboo/pod2pod-migration/calendarserver/webadmin/work.py        2015-02-16 20:56:21 UTC (rev 14417)
</span><span class="lines">@@ -157,13 +157,13 @@
</span><span class="cx">                     if workType == PushNotificationWork:
</span><span class="cx">                         attrs += (&quot;pushID&quot;, &quot;priority&quot;)
</span><span class="cx">                     elif workType == ScheduleOrganizerWork:
</span><del>-                        attrs += (&quot;icalendarUid&quot;, &quot;attendeeCount&quot;)
</del><ins>+                        attrs += (&quot;icalendarUID&quot;, &quot;attendeeCount&quot;)
</ins><span class="cx">                     elif workType == ScheduleRefreshWork:
</span><del>-                        attrs += (&quot;icalendarUid&quot;, &quot;attendeeCount&quot;)
</del><ins>+                        attrs += (&quot;icalendarUID&quot;, &quot;attendeeCount&quot;)
</ins><span class="cx">                     elif workType == ScheduleReplyWork:
</span><del>-                        attrs += (&quot;icalendarUid&quot;,)
</del><ins>+                        attrs += (&quot;icalendarUID&quot;,)
</ins><span class="cx">                     elif workType == ScheduleAutoReplyWork:
</span><del>-                        attrs += (&quot;icalendarUid&quot;,)
</del><ins>+                        attrs += (&quot;icalendarUID&quot;,)
</ins><span class="cx">                     elif workType == GroupCacherPollingWork:
</span><span class="cx">                         attrs += ()
</span><span class="cx">                     elif workType == IMIPPollingWork:
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboopod2podmigrationrequirementsstabletxt"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/requirements-stable.txt (14416 => 14417)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/pod2pod-migration/requirements-stable.txt        2015-02-16 20:33:55 UTC (rev 14416)
+++ CalendarServer/branches/users/cdaboo/pod2pod-migration/requirements-stable.txt        2015-02-16 20:56:21 UTC (rev 14417)
</span><span class="lines">@@ -36,7 +36,7 @@
</span><span class="cx">             #pyOpenSSL
</span><span class="cx">         pycrypto==2.6.1
</span><span class="cx"> 
</span><del>-    --editable svn+http://svn.calendarserver.org/repository/calendarserver/twext/trunk@14404#egg=twextpy
</del><ins>+    --editable svn+http://svn.calendarserver.org/repository/calendarserver/twext/branches/users/cdaboo/pod2pod-migration@14416#egg=twextpy
</ins><span class="cx">         cffi==0.8.6
</span><span class="cx">             pycparser==2.10
</span><span class="cx">         #twisted
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboopod2podmigrationtxdavcaldavdatastoreschedulingtesttest_workpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/caldav/datastore/scheduling/test/test_work.py (14416 => 14417)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/caldav/datastore/scheduling/test/test_work.py        2015-02-16 20:33:55 UTC (rev 14416)
+++ CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/caldav/datastore/scheduling/test/test_work.py        2015-02-16 20:56:21 UTC (rev 14417)
</span><span class="lines">@@ -250,7 +250,7 @@
</span><span class="cx"> 
</span><span class="cx">         work = yield jobs[0].workItem()
</span><span class="cx">         self.assertTrue(isinstance(work, ScheduleOrganizerWork))
</span><del>-        self.assertEqual(work.icalendarUid, &quot;12345-67890&quot;)
</del><ins>+        self.assertEqual(work.icalendarUID, &quot;12345-67890&quot;)
</ins><span class="cx">         self.assertEqual(scheduleActionFromSQL[work.scheduleAction], &quot;create&quot;)
</span><span class="cx"> 
</span><span class="cx">         yield work.delete()
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboopod2podmigrationtxdavcaldavdatastoreschedulingworkpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/caldav/datastore/scheduling/work.py (14416 => 14417)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/caldav/datastore/scheduling/work.py        2015-02-16 20:33:55 UTC (rev 14416)
+++ CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/caldav/datastore/scheduling/work.py        2015-02-16 20:56:21 UTC (rev 14417)
</span><span class="lines">@@ -77,7 +77,7 @@
</span><span class="cx"> 
</span><span class="cx">         baseargs = {
</span><span class="cx">             &quot;jobID&quot;: kwargs.pop(&quot;jobID&quot;),
</span><del>-            &quot;icalendarUid&quot;: kwargs.pop(&quot;icalendarUid&quot;),
</del><ins>+            &quot;icalendarUID&quot;: kwargs.pop(&quot;icalendarUID&quot;),
</ins><span class="cx">             &quot;workType&quot;: cls.workType()
</span><span class="cx">         }
</span><span class="cx"> 
</span><span class="lines">@@ -121,7 +121,7 @@
</span><span class="cx">         # cause deadlocks if done in the wrong order
</span><span class="cx"> 
</span><span class="cx">         # Row level lock on this item
</span><del>-        locked = yield self.baseWork.trylock(ScheduleWork.icalendarUid == self.icalendarUid)
</del><ins>+        locked = yield self.baseWork.trylock(ScheduleWork.icalendarUID == self.icalendarUID)
</ins><span class="cx">         if locked:
</span><span class="cx">             yield self.trylock()
</span><span class="cx">         returnValue(locked)
</span><span class="lines">@@ -136,7 +136,7 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         self.__dict__[&quot;baseWork&quot;] = baseWork
</span><span class="cx">         self.__dict__[&quot;jobID&quot;] = baseWork.jobID
</span><del>-        self.__dict__[&quot;icalendarUid&quot;] = baseWork.icalendarUid
</del><ins>+        self.__dict__[&quot;icalendarUID&quot;] = baseWork.icalendarUID
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def delete(self):
</span><span class="lines">@@ -174,7 +174,7 @@
</span><span class="cx">         if self.workType() == ScheduleOrganizerSendWork.workType():
</span><span class="cx">             all = yield self.baseWork.query(
</span><span class="cx">                 self.transaction,
</span><del>-                (ScheduleWork.icalendarUid == self.icalendarUid).And(ScheduleWork.workID != self.workID),
</del><ins>+                (ScheduleWork.icalendarUID == self.icalendarUID).And(ScheduleWork.workID != self.workID),
</ins><span class="cx">                 order=ScheduleWork.workID,
</span><span class="cx">                 limit=1,
</span><span class="cx">             )
</span><span class="lines">@@ -183,7 +183,7 @@
</span><span class="cx">                 if work.workType == self.workType():
</span><span class="cx">                     job = yield JobItem.load(self.transaction, work.jobID)
</span><span class="cx">                     yield job.update(notBefore=datetime.datetime.utcnow())
</span><del>-                    log.debug(&quot;ScheduleOrganizerSendWork - promoted job: {id}, UID: '{uid}'&quot;, id=work.workID, uid=self.icalendarUid)
</del><ins>+                    log.debug(&quot;ScheduleOrganizerSendWork - promoted job: {id}, UID: '{uid}'&quot;, id=work.workID, uid=self.icalendarUID)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @classmethod
</span><span class="lines">@@ -323,7 +323,7 @@
</span><span class="cx">         proposal = (yield txn.enqueue(
</span><span class="cx">             cls,
</span><span class="cx">             notBefore=notBefore,
</span><del>-            icalendarUid=uid,
</del><ins>+            icalendarUID=uid,
</ins><span class="cx">             scheduleAction=scheduleActionToSQL[action],
</span><span class="cx">             homeResourceID=home.id(),
</span><span class="cx">             resourceID=resource.id() if resource else None,
</span><span class="lines">@@ -347,10 +347,10 @@
</span><span class="cx">             calendar_old = Component.fromString(self.icalendarTextOld) if self.icalendarTextOld else None
</span><span class="cx">             calendar_new = Component.fromString(self.icalendarTextNew) if self.icalendarTextNew else None
</span><span class="cx"> 
</span><del>-            log.debug(&quot;ScheduleOrganizerWork - running for ID: {id}, UID: {uid}, organizer: {org}&quot;, id=self.workID, uid=self.icalendarUid, org=organizer)
</del><ins>+            log.debug(&quot;ScheduleOrganizerWork - running for ID: {id}, UID: {uid}, organizer: {org}&quot;, id=self.workID, uid=self.icalendarUID, org=organizer)
</ins><span class="cx"> 
</span><span class="cx">             # We need to get the UID lock for implicit processing.
</span><del>-            yield NamedLock.acquire(self.transaction, &quot;ImplicitUIDLock:%s&quot; % (hashlib.md5(self.icalendarUid).hexdigest(),))
</del><ins>+            yield NamedLock.acquire(self.transaction, &quot;ImplicitUIDLock:%s&quot; % (hashlib.md5(self.icalendarUID).hexdigest(),))
</ins><span class="cx"> 
</span><span class="cx">             from txdav.caldav.datastore.scheduling.implicit import ImplicitScheduler
</span><span class="cx">             scheduler = ImplicitScheduler()
</span><span class="lines">@@ -359,7 +359,7 @@
</span><span class="cx">                 scheduleActionFromSQL[self.scheduleAction],
</span><span class="cx">                 home,
</span><span class="cx">                 resource,
</span><del>-                self.icalendarUid,
</del><ins>+                self.icalendarUID,
</ins><span class="cx">                 calendar_old,
</span><span class="cx">                 calendar_new,
</span><span class="cx">                 self.smartMerge
</span><span class="lines">@@ -368,15 +368,15 @@
</span><span class="cx">             self._dequeued()
</span><span class="cx"> 
</span><span class="cx">         except Exception, e:
</span><del>-            log.debug(&quot;ScheduleOrganizerWork - exception ID: {id}, UID: '{uid}', {err}&quot;, id=self.workID, uid=self.icalendarUid, err=str(e))
</del><ins>+            log.debug(&quot;ScheduleOrganizerWork - exception ID: {id}, UID: '{uid}', {err}&quot;, id=self.workID, uid=self.icalendarUID, err=str(e))
</ins><span class="cx">             log.debug(traceback.format_exc())
</span><span class="cx">             raise
</span><span class="cx">         except:
</span><del>-            log.debug(&quot;ScheduleOrganizerWork - bare exception ID: {id}, UID: '{uid}'&quot;, id=self.workID, uid=self.icalendarUid)
</del><ins>+            log.debug(&quot;ScheduleOrganizerWork - bare exception ID: {id}, UID: '{uid}'&quot;, id=self.workID, uid=self.icalendarUID)
</ins><span class="cx">             log.debug(traceback.format_exc())
</span><span class="cx">             raise
</span><span class="cx"> 
</span><del>-        log.debug(&quot;ScheduleOrganizerWork - done for ID: {id}, UID: {uid}, organizer: {org}&quot;, id=self.workID, uid=self.icalendarUid, org=organizer)
</del><ins>+        log.debug(&quot;ScheduleOrganizerWork - done for ID: {id}, UID: {uid}, organizer: {org}&quot;, id=self.workID, uid=self.icalendarUID, org=organizer)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -418,7 +418,7 @@
</span><span class="cx">         proposal = (yield txn.enqueue(
</span><span class="cx">             cls,
</span><span class="cx">             notBefore=notBefore,
</span><del>-            icalendarUid=uid,
</del><ins>+            icalendarUID=uid,
</ins><span class="cx">             scheduleAction=scheduleActionToSQL[action],
</span><span class="cx">             homeResourceID=home.id(),
</span><span class="cx">             resourceID=resource.id() if resource else None,
</span><span class="lines">@@ -449,13 +449,13 @@
</span><span class="cx">             log.debug(
</span><span class="cx">                 &quot;ScheduleOrganizerSendWork - running for ID: {id}, UID: {uid}, organizer: {org}, attendee: {att}&quot;,
</span><span class="cx">                 id=self.workID,
</span><del>-                uid=self.icalendarUid,
</del><ins>+                uid=self.icalendarUID,
</ins><span class="cx">                 org=organizer,
</span><span class="cx">                 att=self.attendee
</span><span class="cx">             )
</span><span class="cx"> 
</span><span class="cx">             # We need to get the UID lock for implicit processing.
</span><del>-            yield NamedLock.acquire(self.transaction, &quot;ImplicitUIDLock:%s&quot; % (hashlib.md5(self.icalendarUid).hexdigest(),))
</del><ins>+            yield NamedLock.acquire(self.transaction, &quot;ImplicitUIDLock:%s&quot; % (hashlib.md5(self.icalendarUID).hexdigest(),))
</ins><span class="cx"> 
</span><span class="cx">             from txdav.caldav.datastore.scheduling.implicit import ImplicitScheduler
</span><span class="cx">             scheduler = ImplicitScheduler()
</span><span class="lines">@@ -464,7 +464,7 @@
</span><span class="cx">                 scheduleActionFromSQL[self.scheduleAction],
</span><span class="cx">                 home,
</span><span class="cx">                 resource,
</span><del>-                self.icalendarUid,
</del><ins>+                self.icalendarUID,
</ins><span class="cx">                 organizer,
</span><span class="cx">                 self.attendee,
</span><span class="cx">                 itipmsg,
</span><span class="lines">@@ -486,18 +486,18 @@
</span><span class="cx">             self._dequeued()
</span><span class="cx"> 
</span><span class="cx">         except Exception, e:
</span><del>-            log.debug(&quot;ScheduleOrganizerSendWork - exception ID: {id}, UID: '{uid}', {err}&quot;, id=self.workID, uid=self.icalendarUid, err=str(e))
</del><ins>+            log.debug(&quot;ScheduleOrganizerSendWork - exception ID: {id}, UID: '{uid}', {err}&quot;, id=self.workID, uid=self.icalendarUID, err=str(e))
</ins><span class="cx">             log.debug(traceback.format_exc())
</span><span class="cx">             raise
</span><span class="cx">         except:
</span><del>-            log.debug(&quot;ScheduleOrganizerSendWork - bare exception ID: {id}, UID: '{uid}'&quot;, id=self.workID, uid=self.icalendarUid)
</del><ins>+            log.debug(&quot;ScheduleOrganizerSendWork - bare exception ID: {id}, UID: '{uid}'&quot;, id=self.workID, uid=self.icalendarUID)
</ins><span class="cx">             log.debug(traceback.format_exc())
</span><span class="cx">             raise
</span><span class="cx"> 
</span><span class="cx">         log.debug(
</span><span class="cx">             &quot;ScheduleOrganizerSendWork - for ID: {id}, UID: {uid}, organizer: {org}, attendee: {att}&quot;,
</span><span class="cx">             id=self.workID,
</span><del>-            uid=self.icalendarUid,
</del><ins>+            uid=self.icalendarUID,
</ins><span class="cx">             org=organizer,
</span><span class="cx">             att=self.attendee
</span><span class="cx">         )
</span><span class="lines">@@ -521,7 +521,7 @@
</span><span class="cx">         proposal = (yield txn.enqueue(
</span><span class="cx">             cls,
</span><span class="cx">             notBefore=notBefore,
</span><del>-            icalendarUid=uid,
</del><ins>+            icalendarUID=uid,
</ins><span class="cx">             homeResourceID=home.id(),
</span><span class="cx">             resourceID=resource.id() if resource else None,
</span><span class="cx">             itipMsg=itipmsg.getTextWithTimezones(includeTimezones=not config.EnableTimezonesByReference),
</span><span class="lines">@@ -649,7 +649,7 @@
</span><span class="cx">         notBefore = datetime.datetime.utcnow() + datetime.timedelta(seconds=config.Scheduling.Options.WorkQueues.AttendeeRefreshBatchDelaySeconds)
</span><span class="cx">         proposal = (yield txn.enqueue(
</span><span class="cx">             cls,
</span><del>-            icalendarUid=organizer_resource.uid(),
</del><ins>+            icalendarUID=organizer_resource.uid(),
</ins><span class="cx">             homeResourceID=organizer_resource._home.id(),
</span><span class="cx">             resourceID=organizer_resource.id(),
</span><span class="cx">             attendeeCount=len(attendees),
</span><span class="lines">@@ -676,7 +676,7 @@
</span><span class="cx">             log.debug(&quot;Schedule refresh for resource-id: {rid} - ignored&quot;, rid=self.resourceID)
</span><span class="cx">             returnValue(None)
</span><span class="cx"> 
</span><del>-        log.debug(&quot;ScheduleRefreshWork - running for ID: {id}, UID: {uid}&quot;, id=self.workID, uid=self.icalendarUid)
</del><ins>+        log.debug(&quot;ScheduleRefreshWork - running for ID: {id}, UID: {uid}&quot;, id=self.workID, uid=self.icalendarUID)
</ins><span class="cx"> 
</span><span class="cx">         # Get the unique list of pending attendees and split into batch to process
</span><span class="cx">         # TODO: do a DELETE ... and rownum &lt;= N returning attendee - but have to fix Oracle to
</span><span class="lines">@@ -707,7 +707,7 @@
</span><span class="cx">             notBefore = datetime.datetime.utcnow() + datetime.timedelta(seconds=config.Scheduling.Options.WorkQueues.AttendeeRefreshBatchIntervalSeconds)
</span><span class="cx">             yield self.transaction.enqueue(
</span><span class="cx">                 self.__class__,
</span><del>-                icalendarUid=self.icalendarUid,
</del><ins>+                icalendarUID=self.icalendarUID,
</ins><span class="cx">                 homeResourceID=self.homeResourceID,
</span><span class="cx">                 resourceID=self.resourceID,
</span><span class="cx">                 attendeeCount=len(pendingAttendees),
</span><span class="lines">@@ -721,7 +721,7 @@
</span><span class="cx"> 
</span><span class="cx">         self._dequeued()
</span><span class="cx"> 
</span><del>-        log.debug(&quot;ScheduleRefreshWork - done for ID: {id}, UID: {uid}&quot;, id=self.workID, uid=self.icalendarUid)
</del><ins>+        log.debug(&quot;ScheduleRefreshWork - done for ID: {id}, UID: {uid}&quot;, id=self.workID, uid=self.icalendarUID)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -790,7 +790,7 @@
</span><span class="cx">         notBefore = datetime.datetime.utcnow() + datetime.timedelta(seconds=config.Scheduling.Options.WorkQueues.AutoReplyDelaySeconds)
</span><span class="cx">         proposal = (yield txn.enqueue(
</span><span class="cx">             cls,
</span><del>-            icalendarUid=resource.uid(),
</del><ins>+            icalendarUID=resource.uid(),
</ins><span class="cx">             homeResourceID=resource._home.id(),
</span><span class="cx">             resourceID=resource.id(),
</span><span class="cx">             partstat=partstat,
</span><span class="lines">@@ -803,7 +803,7 @@
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def doWork(self):
</span><span class="cx"> 
</span><del>-        log.debug(&quot;ScheduleAutoReplyWork - running for ID: {id}, UID: {uid}&quot;, id=self.workID, uid=self.icalendarUid)
</del><ins>+        log.debug(&quot;ScheduleAutoReplyWork - running for ID: {id}, UID: {uid}&quot;, id=self.workID, uid=self.icalendarUID)
</ins><span class="cx"> 
</span><span class="cx">         # Delete all other work items with the same pushID
</span><span class="cx">         yield Delete(
</span><span class="lines">@@ -816,7 +816,7 @@
</span><span class="cx"> 
</span><span class="cx">         self._dequeued()
</span><span class="cx"> 
</span><del>-        log.debug(&quot;ScheduleAutoReplyWork - done for ID: {id}, UID: {uid}&quot;, id=self.workID, uid=self.icalendarUid)
</del><ins>+        log.debug(&quot;ScheduleAutoReplyWork - done for ID: {id}, UID: {uid}&quot;, id=self.workID, uid=self.icalendarUID)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboopod2podmigrationtxdavcaldavdatastoresqlpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/caldav/datastore/sql.py (14416 => 14417)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/caldav/datastore/sql.py        2015-02-16 20:33:55 UTC (rev 14416)
+++ CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/caldav/datastore/sql.py        2015-02-16 20:56:21 UTC (rev 14417)
</span><span class="lines">@@ -1878,8 +1878,8 @@
</span><span class="cx"> 
</span><span class="cx">         # First check that the actual group membership has changed
</span><span class="cx">         if (yield self.updateShareeGroupLink(groupUID)):
</span><del>-            groupID = (yield self._txn.groupByUID(groupUID))[0]
-            memberUIDs = yield self._txn.groupMemberUIDs(groupID)
</del><ins>+            group = yield self._txn.groupByUID(groupUID)
+            memberUIDs = yield self._txn.groupMemberUIDs(group.groupID)
</ins><span class="cx">             boundUIDs = set()
</span><span class="cx"> 
</span><span class="cx">             home = self._homeSchema
</span><span class="lines">@@ -2029,39 +2029,36 @@
</span><span class="cx">         update schema.GROUP_SHAREE
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         changed = False
</span><del>-        (
-            groupID, _ignore_name, membershipHash, _ignore_modDate,
-            _ignore_extant
-        ) = yield self._txn.groupByUID(groupUID)
</del><ins>+        group = yield self._txn.groupByUID(groupUID)
</ins><span class="cx"> 
</span><span class="cx">         gs = schema.GROUP_SHAREE
</span><span class="cx">         rows = yield Select(
</span><span class="cx">             [gs.MEMBERSHIP_HASH, gs.GROUP_BIND_MODE],
</span><span class="cx">             From=gs,
</span><span class="cx">             Where=(gs.CALENDAR_ID == self._resourceID).And(
</span><del>-                gs.GROUP_ID == groupID)
</del><ins>+                gs.GROUP_ID == group.groupID)
</ins><span class="cx">         ).on(self._txn)
</span><span class="cx">         if rows:
</span><span class="cx">             [[gsMembershipHash, gsMode]] = rows
</span><span class="cx">             updateMap = {}
</span><del>-            if gsMembershipHash != membershipHash:
-                updateMap[gs.MEMBERSHIP_HASH] = membershipHash
</del><ins>+            if gsMembershipHash != group.membershipHash:
+                updateMap[gs.MEMBERSHIP_HASH] = group.membershipHash
</ins><span class="cx">             if mode is not None and gsMode != mode:
</span><span class="cx">                 updateMap[gs.GROUP_BIND_MODE] = mode
</span><span class="cx">             if updateMap:
</span><span class="cx">                 yield Update(
</span><span class="cx">                     updateMap,
</span><span class="cx">                     Where=(gs.CALENDAR_ID == self._resourceID).And(
</span><del>-                        gs.GROUP_ID == groupID
</del><ins>+                        gs.GROUP_ID == group.groupID
</ins><span class="cx">                     )
</span><span class="cx">                 ).on(self._txn)
</span><span class="cx">                 changed = True
</span><span class="cx">         else:
</span><span class="cx">             yield Insert({
</span><del>-                gs.MEMBERSHIP_HASH: membershipHash,
</del><ins>+                gs.MEMBERSHIP_HASH: group.membershipHash,
</ins><span class="cx">                 gs.GROUP_BIND_MODE: mode,
</span><span class="cx">                 gs.CALENDAR_ID: self._resourceID,
</span><del>-                gs.GROUP_ID: groupID,
</del><ins>+                gs.GROUP_ID: group.groupID,
</ins><span class="cx">             }).on(self._txn)
</span><span class="cx">             changed = True
</span><span class="cx"> 
</span><span class="lines">@@ -2148,8 +2145,8 @@
</span><span class="cx"> 
</span><span class="cx">         # invite every member of group
</span><span class="cx">         shareeViews = []
</span><del>-        groupID = (yield self._txn.groupByUID(shareeUID))[0]
-        memberUIDs = yield self._txn.groupMemberUIDs(groupID)
</del><ins>+        group = yield self._txn.groupByUID(shareeUID)
+        memberUIDs = yield self._txn.groupMemberUIDs(group.groupID)
</ins><span class="cx">         for memberUID in memberUIDs:
</span><span class="cx">             if memberUID != self._home.uid():
</span><span class="cx">                 shareeView = yield self.shareeView(memberUID)
</span><span class="lines">@@ -2496,9 +2493,9 @@
</span><span class="cx">             groupRecord = yield self.directoryService().recordWithCalendarUserAddress(groupCUA)
</span><span class="cx">             if groupRecord:
</span><span class="cx">                 # get members
</span><del>-                groupID = (yield self._txn.groupByUID(groupRecord.uid))[0]
-                if groupID is not None:
-                    members = yield self._txn.groupMembers(groupID)
</del><ins>+                group = yield self._txn.groupByUID(groupRecord.uid)
+                if group is not None:
+                    members = yield self._txn.groupMembers(group.groupID)
</ins><span class="cx">                     groupCUAToAttendeeMemberPropMap[groupRecord.canonicalCalendarUserAddress()] = tuple(
</span><span class="cx">                         [member.attendeeProperty(params={&quot;MEMBER&quot;: groupCUA}) for member in sorted(members, key=lambda x: x.uid)]
</span><span class="cx">                     )
</span><span class="lines">@@ -2551,26 +2548,23 @@
</span><span class="cx">                 groupUID = groupRecord.uid
</span><span class="cx">             else:
</span><span class="cx">                 groupUID = uidFromCalendarUserAddress(groupCUA)
</span><del>-            (
-                groupID, _ignore_name, membershipHash, _ignore_modDate,
-                _ignore_extant
-            ) = yield self._txn.groupByUID(groupUID)
</del><ins>+            group = yield self._txn.groupByUID(groupUID)
</ins><span class="cx"> 
</span><span class="cx">             ga = schema.GROUP_ATTENDEE
</span><del>-            if groupID in groupIDToMembershipHashMap:
-                if groupIDToMembershipHashMap[groupID] != membershipHash:
</del><ins>+            if group.groupID in groupIDToMembershipHashMap:
+                if groupIDToMembershipHashMap[group.groupID] != group.membershipHash:
</ins><span class="cx">                     yield Update(
</span><del>-                        {ga.MEMBERSHIP_HASH: membershipHash, },
</del><ins>+                        {ga.MEMBERSHIP_HASH: group.membershipHash, },
</ins><span class="cx">                         Where=(ga.RESOURCE_ID == self._resourceID).And(
</span><del>-                            ga.GROUP_ID == groupID)
</del><ins>+                            ga.GROUP_ID == group.groupID)
</ins><span class="cx">                     ).on(self._txn)
</span><span class="cx">                     changed = True
</span><del>-                del groupIDToMembershipHashMap[groupID]
</del><ins>+                del groupIDToMembershipHashMap[group.groupID]
</ins><span class="cx">             else:
</span><span class="cx">                 yield Insert({
</span><span class="cx">                     ga.RESOURCE_ID: self._resourceID,
</span><del>-                    ga.GROUP_ID: groupID,
-                    ga.MEMBERSHIP_HASH: membershipHash,
</del><ins>+                    ga.GROUP_ID: group.groupID,
+                    ga.MEMBERSHIP_HASH: group.membershipHash,
</ins><span class="cx">                 }).on(self._txn)
</span><span class="cx">                 changed = True
</span><span class="cx"> 
</span><span class="lines">@@ -5135,7 +5129,7 @@
</span><span class="cx">             setattr(self, attr, None)
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def externalize(self):
</del><ins>+    def serialize(self):
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Create a dictionary mapping key attributes so this object can be sent over a cross-pod call
</span><span class="cx">         and reconstituted at the other end. Note that the other end may have a different schema so
</span><span class="lines">@@ -5145,9 +5139,9 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @classmethod
</span><del>-    def internalize(cls, txn, mapping):
</del><ins>+    def deserialize(cls, txn, mapping):
</ins><span class="cx">         &quot;&quot;&quot;
</span><del>-        Given a mapping generated by L{externalize}, convert the values into an array of database
</del><ins>+        Given a mapping generated by L{serialize}, convert the values into an array of database
</ins><span class="cx">         like items that conforms to the ordering of L{_allColumns} so it can be fed into L{makeClass}.
</span><span class="cx">         Note that there may be a schema mismatch with the external data, so treat missing items as
</span><span class="cx">         C{None} and ignore extra items.
</span><span class="lines">@@ -5288,7 +5282,7 @@
</span><span class="cx">         returnValue(cls.makeClass(home._txn, rows[0]) if len(rows) == 1 else None)
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def externalize(self):
</del><ins>+    def serialize(self):
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Create a dictionary mapping key attributes so this object can be sent over a cross-pod call
</span><span class="cx">         and reconstituted at the other end. Note that the other end may have a different schema so
</span><span class="lines">@@ -5300,9 +5294,9 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @classmethod
</span><del>-    def internalize(cls, txn, mapping):
</del><ins>+    def deserialize(cls, txn, mapping):
</ins><span class="cx">         &quot;&quot;&quot;
</span><del>-        Given a mapping generated by L{externalize}, convert the values into an array of database
</del><ins>+        Given a mapping generated by L{serialize}, convert the values into an array of database
</ins><span class="cx">         like items that conforms to the ordering of L{_allColumns} so it can be fed into L{makeClass}.
</span><span class="cx">         Note that there may be a schema mismatch with the external data, so treat missing items as
</span><span class="cx">         C{None} and ignore extra items.
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboopod2podmigrationtxdavcaldavdatastoresql_externalpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/caldav/datastore/sql_external.py (14416 => 14417)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/caldav/datastore/sql_external.py        2015-02-16 20:33:55 UTC (rev 14416)
+++ CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/caldav/datastore/sql_external.py        2015-02-16 20:56:21 UTC (rev 14417)
</span><span class="lines">@@ -69,7 +69,7 @@
</span><span class="cx">         Needed during migration.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         raw_results = yield self._txn.store().conduit.send_home_get_all_attachments(self)
</span><del>-        returnValue([Attachment.internalize(self._txn, attachment) for attachment in raw_results])
</del><ins>+        returnValue([Attachment.deserialize(self._txn, attachment) for attachment in raw_results])
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -89,7 +89,7 @@
</span><span class="cx">         Needed during migration only.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         raw_results = yield self._txn.store().conduit.send_home_get_attachment_links(self)
</span><del>-        returnValue([AttachmentLink.internalize(self._txn, attachment) for attachment in raw_results])
</del><ins>+        returnValue([AttachmentLink.deserialize(self._txn, attachment) for attachment in raw_results])
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def getAllDropboxIDs(self):
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboopod2podmigrationtxdavcommondatastorepoddingattachmentspy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/attachments.py (14416 => 14417)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/attachments.py        2015-02-16 20:33:55 UTC (rev 14416)
+++ CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/attachments.py        2015-02-16 20:56:21 UTC (rev 14417)
</span><span class="lines">@@ -196,5 +196,5 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> # Calls on L{CommonHome} objects
</span><del>-UtilityConduitMixin._make_simple_action(AttachmentsConduitMixin, &quot;home_get_all_attachments&quot;, &quot;getAllAttachments&quot;, classMethod=False, transform_recv_result=UtilityConduitMixin._to_externalize_list)
-UtilityConduitMixin._make_simple_action(AttachmentsConduitMixin, &quot;home_get_attachment_links&quot;, &quot;getAttachmentLinks&quot;, classMethod=False, transform_recv_result=UtilityConduitMixin._to_externalize_list)
</del><ins>+UtilityConduitMixin._make_simple_action(AttachmentsConduitMixin, &quot;home_get_all_attachments&quot;, &quot;getAllAttachments&quot;, classMethod=False, transform_recv_result=UtilityConduitMixin._to_serialize_list)
+UtilityConduitMixin._make_simple_action(AttachmentsConduitMixin, &quot;home_get_attachment_links&quot;, &quot;getAttachmentLinks&quot;, classMethod=False, transform_recv_result=UtilityConduitMixin._to_serialize_list)
</ins></span></pre></div>
<a id="CalendarServerbranchesuserscdaboopod2podmigrationtxdavcommondatastorepoddingmigrationhome_syncpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/migration/home_sync.py (14416 => 14417)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/migration/home_sync.py        2015-02-16 20:33:55 UTC (rev 14416)
+++ CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/migration/home_sync.py        2015-02-16 20:56:21 UTC (rev 14417)
</span><span class="lines">@@ -176,10 +176,10 @@
</span><span class="cx">         # TODO: Re-write attachment URIs - not sure if we need this as reverse proxy may take care of it
</span><span class="cx">         pass
</span><span class="cx"> 
</span><del>-        # TODO: group attendee reconcile
</del><ins>+        # TODO: shared collections reconcile
</ins><span class="cx">         pass
</span><span class="cx"> 
</span><del>-        # TODO: shared collections reconcile
</del><ins>+        # TODO: group attendee reconcile
</ins><span class="cx">         pass
</span><span class="cx"> 
</span><span class="cx">         # TODO: group sharee reconcile
</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 (14416 => 14417)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/store_api.py        2015-02-16 20:33:55 UTC (rev 14416)
+++ CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/store_api.py        2015-02-16 20:56:21 UTC (rev 14417)
</span><span class="lines">@@ -133,8 +133,8 @@
</span><span class="cx"> 
</span><span class="cx"> # Calls on L{CommonHomeChild} objects
</span><span class="cx"> UtilityConduitMixin._make_simple_action(StoreAPIConduitMixin, &quot;homechild_listobjects&quot;, &quot;listObjects&quot;, classMethod=True)
</span><del>-UtilityConduitMixin._make_simple_action(StoreAPIConduitMixin, &quot;homechild_loadallobjects&quot;, &quot;loadAllObjects&quot;, classMethod=True, transform_recv_result=UtilityConduitMixin._to_externalize_list)
-UtilityConduitMixin._make_simple_action(StoreAPIConduitMixin, &quot;homechild_objectwith&quot;, &quot;objectWith&quot;, classMethod=True, transform_recv_result=UtilityConduitMixin._to_externalize)
</del><ins>+UtilityConduitMixin._make_simple_action(StoreAPIConduitMixin, &quot;homechild_loadallobjects&quot;, &quot;loadAllObjects&quot;, classMethod=True, transform_recv_result=UtilityConduitMixin._to_serialize_list)
+UtilityConduitMixin._make_simple_action(StoreAPIConduitMixin, &quot;homechild_objectwith&quot;, &quot;objectWith&quot;, classMethod=True, transform_recv_result=UtilityConduitMixin._to_serialize)
</ins><span class="cx"> UtilityConduitMixin._make_simple_action(StoreAPIConduitMixin, &quot;homechild_movehere&quot;, &quot;moveObjectResourceHere&quot;)
</span><span class="cx"> UtilityConduitMixin._make_simple_action(StoreAPIConduitMixin, &quot;homechild_moveaway&quot;, &quot;moveObjectResourceAway&quot;)
</span><span class="cx"> UtilityConduitMixin._make_simple_action(StoreAPIConduitMixin, &quot;homechild_synctokenrevision&quot;, &quot;syncTokenRevision&quot;)
</span><span class="lines">@@ -142,14 +142,14 @@
</span><span class="cx"> UtilityConduitMixin._make_simple_action(StoreAPIConduitMixin, &quot;homechild_search&quot;, &quot;search&quot;)
</span><span class="cx"> 
</span><span class="cx"> # Calls on L{CommonObjectResource} objects
</span><del>-UtilityConduitMixin._make_simple_action(StoreAPIConduitMixin, &quot;objectresource_loadallobjects&quot;, &quot;loadAllObjects&quot;, classMethod=True, transform_recv_result=UtilityConduitMixin._to_externalize_list)
-UtilityConduitMixin._make_simple_action(StoreAPIConduitMixin, &quot;objectresource_loadallobjectswithnames&quot;, &quot;loadAllObjectsWithNames&quot;, classMethod=True, transform_recv_result=UtilityConduitMixin._to_externalize_list)
</del><ins>+UtilityConduitMixin._make_simple_action(StoreAPIConduitMixin, &quot;objectresource_loadallobjects&quot;, &quot;loadAllObjects&quot;, classMethod=True, transform_recv_result=UtilityConduitMixin._to_serialize_list)
+UtilityConduitMixin._make_simple_action(StoreAPIConduitMixin, &quot;objectresource_loadallobjectswithnames&quot;, &quot;loadAllObjectsWithNames&quot;, classMethod=True, transform_recv_result=UtilityConduitMixin._to_serialize_list)
</ins><span class="cx"> UtilityConduitMixin._make_simple_action(StoreAPIConduitMixin, &quot;objectresource_listobjects&quot;, &quot;listObjects&quot;, classMethod=True)
</span><span class="cx"> UtilityConduitMixin._make_simple_action(StoreAPIConduitMixin, &quot;objectresource_countobjects&quot;, &quot;countObjects&quot;, classMethod=True)
</span><del>-UtilityConduitMixin._make_simple_action(StoreAPIConduitMixin, &quot;objectresource_objectwith&quot;, &quot;objectWith&quot;, classMethod=True, transform_recv_result=UtilityConduitMixin._to_externalize)
</del><ins>+UtilityConduitMixin._make_simple_action(StoreAPIConduitMixin, &quot;objectresource_objectwith&quot;, &quot;objectWith&quot;, classMethod=True, transform_recv_result=UtilityConduitMixin._to_serialize)
</ins><span class="cx"> UtilityConduitMixin._make_simple_action(StoreAPIConduitMixin, &quot;objectresource_resourcenameforuid&quot;, &quot;resourceNameForUID&quot;, classMethod=True)
</span><span class="cx"> UtilityConduitMixin._make_simple_action(StoreAPIConduitMixin, &quot;objectresource_resourceuidforname&quot;, &quot;resourceUIDForName&quot;, classMethod=True)
</span><del>-UtilityConduitMixin._make_simple_action(StoreAPIConduitMixin, &quot;objectresource_create&quot;, &quot;create&quot;, classMethod=True, transform_recv_result=UtilityConduitMixin._to_externalize)
</del><ins>+UtilityConduitMixin._make_simple_action(StoreAPIConduitMixin, &quot;objectresource_create&quot;, &quot;create&quot;, classMethod=True, transform_recv_result=UtilityConduitMixin._to_serialize)
</ins><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></span></pre></div>
<a id="CalendarServerbranchesuserscdaboopod2podmigrationtxdavcommondatastorepoddingtesttest_store_apipy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/test/test_store_api.py (14416 => 14417)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/test/test_store_api.py        2015-02-16 20:33:55 UTC (rev 14416)
+++ CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/test/test_store_api.py        2015-02-16 20:56:21 UTC (rev 14417)
</span><span class="lines">@@ -104,7 +104,7 @@
</span><span class="cx"> 
</span><span class="cx">         from txdav.caldav.datastore.sql_external import CalendarHomeExternal
</span><span class="cx">         recipient = yield txn.store().directoryService().recordWithUID(uid)
</span><del>-        resourceID = yield txn.store().conduit.send_home_resource_id(self, recipient)
</del><ins>+        resourceID = yield txn.store().conduit.send_home_resource_id(txn, recipient)
</ins><span class="cx">         home = CalendarHomeExternal(txn, recipient.uid, resourceID) if resourceID is not None else None
</span><span class="cx">         if home:
</span><span class="cx">             home._childClass = home._childClass._externalClass
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboopod2podmigrationtxdavcommondatastorepoddingutilpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/util.py (14416 => 14417)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/util.py        2015-02-16 20:33:55 UTC (rev 14416)
+++ CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/util.py        2015-02-16 20:56:21 UTC (rev 14417)
</span><span class="lines">@@ -212,19 +212,19 @@
</span><span class="cx">     # Transforms for returned data
</span><span class="cx">     #
</span><span class="cx">     @staticmethod
</span><del>-    def _to_externalize(value):
</del><ins>+    def _to_serialize(value):
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Convert the value to the external (JSON-based) representation.
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        return value.externalize() if value is not None else None
</del><ins>+        return value.serialize() if value is not None else None
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @staticmethod
</span><del>-    def _to_externalize_list(value):
</del><ins>+    def _to_serialize_list(value):
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Convert the value to the external (JSON-based) representation.
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        return [v.externalize() for v in value]
</del><ins>+        return [v.serialize() for v in value]
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @staticmethod
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboopod2podmigrationtxdavcommondatastoresqlpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql.py (14416 => 14417)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql.py        2015-02-16 20:33:55 UTC (rev 14416)
+++ CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql.py        2015-02-16 20:56:21 UTC (rev 14417)
</span><span class="lines">@@ -61,6 +61,8 @@
</span><span class="cx"> from txdav.carddav.iaddressbookstore import IAddressBookTransaction
</span><span class="cx"> from txdav.common.datastore.common import HomeChildBase
</span><span class="cx"> from txdav.common.datastore.podding.conduit import PoddingConduit
</span><ins>+from txdav.common.datastore.sql_directory import DelegatesAPIMixin, \
+    GroupsAPIMixin, GroupCacherAPIMixin
</ins><span class="cx"> from txdav.common.datastore.sql_tables import _BIND_MODE_DIRECT, \
</span><span class="cx">     _BIND_MODE_INDIRECT, _BIND_MODE_OWN, _BIND_STATUS_ACCEPTED, \
</span><span class="cx">     _BIND_STATUS_DECLINED, _BIND_STATUS_DELETED, _BIND_STATUS_INVALID, \
</span><span class="lines">@@ -68,7 +70,7 @@
</span><span class="cx">     _HOME_STATUS_PURGING, schema, splitSQLString, _HOME_STATUS_MIGRATING
</span><span class="cx"> from txdav.common.icommondatastore import ConcurrentModification, \
</span><span class="cx">     RecordNotAllowedError, ExternalShareFailed, ShareNotAllowed, \
</span><del>-    IndexedSearchException, NotFoundError
</del><ins>+    IndexedSearchException
</ins><span class="cx"> from txdav.common.icommondatastore import HomeChildNameNotAllowedError, \
</span><span class="cx">     HomeChildNameAlreadyExistsError, NoSuchHomeChildError, \
</span><span class="cx">     ObjectResourceNameNotAllowedError, ObjectResourceNameAlreadyExistsError, \
</span><span class="lines">@@ -80,7 +82,6 @@
</span><span class="cx"> from txdav.common.inotifications import INotificationCollection, \
</span><span class="cx">     INotificationObject
</span><span class="cx"> from txdav.idav import ChangeCategory
</span><del>-from txdav.who.delegates import Delegates
</del><span class="cx"> from txdav.xml import element
</span><span class="cx"> 
</span><span class="cx"> from uuid import uuid4, UUID
</span><span class="lines">@@ -88,7 +89,6 @@
</span><span class="cx"> from zope.interface import implements, directlyProvides
</span><span class="cx"> 
</span><span class="cx"> from collections import namedtuple
</span><del>-import datetime
</del><span class="cx"> import inspect
</span><span class="cx"> import itertools
</span><span class="cx"> import json
</span><span class="lines">@@ -565,7 +565,7 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-class CommonStoreTransaction(object):
</del><ins>+class CommonStoreTransaction(GroupsAPIMixin, GroupCacherAPIMixin, DelegatesAPIMixin):
</ins><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Transaction implementation for SQL database.
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="lines">@@ -1036,1033 +1036,6 @@
</span><span class="cx">     # End of IMIP
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    # Groups
-
-    @classproperty
-    def _addGroupQuery(cls):
-        gr = schema.GROUPS
-        return Insert(
-            {
-                gr.NAME: Parameter(&quot;name&quot;),
-                gr.GROUP_UID: Parameter(&quot;groupUID&quot;),
-                gr.MEMBERSHIP_HASH: Parameter(&quot;membershipHash&quot;)
-            },
-            Return=gr.GROUP_ID
-        )
-
-
-    @classproperty
-    def _updateGroupQuery(cls):
-        gr = schema.GROUPS
-        return Update(
-            {
-                gr.MEMBERSHIP_HASH: Parameter(&quot;membershipHash&quot;),
-                gr.NAME: Parameter(&quot;name&quot;),
-                gr.MODIFIED: Parameter(&quot;timestamp&quot;),
-                gr.EXTANT: Parameter(&quot;extant&quot;),
-            },
-            Where=(gr.GROUP_UID == Parameter(&quot;groupUID&quot;))
-        )
-
-
-    @classproperty
-    def _groupByUID(cls):
-        gr = schema.GROUPS
-        return Select(
-            [gr.GROUP_ID, gr.NAME, gr.MEMBERSHIP_HASH, gr.MODIFIED, gr.EXTANT],
-            From=gr,
-            Where=(gr.GROUP_UID == Parameter(&quot;groupUID&quot;))
-        )
-
-
-    @classproperty
-    def _groupByID(cls):
-        gr = schema.GROUPS
-        return Select(
-            [gr.GROUP_UID, gr.NAME, gr.MEMBERSHIP_HASH, gr.EXTANT],
-            From=gr,
-            Where=(gr.GROUP_ID == Parameter(&quot;groupID&quot;))
-        )
-
-
-    @classproperty
-    def _deleteGroup(cls):
-        gr = schema.GROUPS
-        return Delete(
-            From=gr,
-            Where=(gr.GROUP_ID == Parameter(&quot;groupID&quot;))
-        )
-
-
-    @inlineCallbacks
-    def addGroup(self, groupUID, name, membershipHash):
-        &quot;&quot;&quot;
-        @type groupUID: C{unicode}
-        @type name: C{unicode}
-        @type membershipHash: C{str}
-        &quot;&quot;&quot;
-        record = yield self.directoryService().recordWithUID(groupUID)
-        if record is None:
-            returnValue(None)
-
-        groupID = (yield self._addGroupQuery.on(
-            self,
-            name=name.encode(&quot;utf-8&quot;),
-            groupUID=groupUID.encode(&quot;utf-8&quot;),
-            membershipHash=membershipHash
-        ))[0][0]
-
-        yield self.refreshGroup(
-            groupUID, record, groupID, name.encode(&quot;utf-8&quot;), membershipHash, True
-        )
-        returnValue(groupID)
-
-
-    def updateGroup(self, groupUID, name, membershipHash, extant=True):
-        &quot;&quot;&quot;
-        @type groupUID: C{unicode}
-        @type name: C{unicode}
-        @type membershipHash: C{str}
-        @type extant: C{boolean}
-        &quot;&quot;&quot;
-        timestamp = datetime.datetime.utcnow()
-        return self._updateGroupQuery.on(
-            self,
-            name=name.encode(&quot;utf-8&quot;),
-            groupUID=groupUID.encode(&quot;utf-8&quot;),
-            timestamp=timestamp,
-            membershipHash=membershipHash,
-            extant=(1 if extant else 0)
-        )
-
-
-    @inlineCallbacks
-    def groupByUID(self, groupUID, create=True):
-        &quot;&quot;&quot;
-        Return or create a record for the group UID.
-
-        @type groupUID: C{unicode}
-
-        @return: Deferred firing with tuple of group ID C{str}, group name
-            C{unicode}, membership hash C{str}, modified timestamp, and
-            extant C{boolean}
-        &quot;&quot;&quot;
-        results = (
-            yield self._groupByUID.on(
-                self, groupUID=groupUID.encode(&quot;utf-8&quot;)
-            )
-        )
-        if results:
-            returnValue((
-                results[0][0],  # group id
-                results[0][1].decode(&quot;utf-8&quot;),  # name
-                results[0][2],  # membership hash
-                results[0][3],  # modified timestamp
-                bool(results[0][4]),  # extant
-            ))
-        elif create:
-            savepoint = SavepointAction(&quot;groupByUID&quot;)
-            yield savepoint.acquire(self)
-            try:
-                groupID = yield self.addGroup(groupUID, u&quot;&quot;, &quot;&quot;)
-                if groupID is None:
-                    # The record does not actually exist within the directory
-                    yield savepoint.release(self)
-                    returnValue((None, None, None, None, None))
-
-            except Exception:
-                yield savepoint.rollback(self)
-                results = (
-                    yield self._groupByUID.on(
-                        self, groupUID=groupUID.encode(&quot;utf-8&quot;)
-                    )
-                )
-                if results:
-                    returnValue((
-                        results[0][0],  # group id
-                        results[0][1].decode(&quot;utf-8&quot;),  # name
-                        results[0][2],  # membership hash
-                        results[0][3],  # modified timestamp
-                        bool(results[0][4]),  # extant
-                    ))
-                else:
-                    returnValue((None, None, None, None, None))
-            else:
-                yield savepoint.release(self)
-                results = (
-                    yield self._groupByUID.on(
-                        self, groupUID=groupUID.encode(&quot;utf-8&quot;)
-                    )
-                )
-                if results:
-                    returnValue((
-                        results[0][0],  # group id
-                        results[0][1].decode(&quot;utf-8&quot;),  # name
-                        results[0][2],  # membership hash
-                        results[0][3],  # modified timestamp
-                        bool(results[0][4]),  # extant
-                    ))
-                else:
-                    returnValue((None, None, None, None, None))
-        else:
-            returnValue((None, None, None, None, None))
-
-
-    @inlineCallbacks
-    def groupByID(self, groupID):
-        &quot;&quot;&quot;
-        Given a group ID, return the group UID, or raise NotFoundError
-
-        @type groupID: C{str}
-        @return: Deferred firing with a tuple of group UID C{unicode},
-            group name C{unicode}, membership hash C{str}, and extant C{boolean}
-        &quot;&quot;&quot;
-        try:
-            results = (yield self._groupByID.on(self, groupID=groupID))[0]
-            if results:
-                results = (
-                    results[0].decode(&quot;utf-8&quot;),
-                    results[1].decode(&quot;utf-8&quot;),
-                    results[2],
-                    bool(results[3])
-                )
-            returnValue(results)
-        except IndexError:
-            raise NotFoundError
-
-
-    def deleteGroup(self, groupID):
-        return self._deleteGroup.on(self, groupID=groupID)
-
-    # End of Groups
-
-
-    # Group Members
-
-    @classproperty
-    def _addMemberToGroupQuery(cls):
-        gm = schema.GROUP_MEMBERSHIP
-        return Insert(
-            {
-                gm.GROUP_ID: Parameter(&quot;groupID&quot;),
-                gm.MEMBER_UID: Parameter(&quot;memberUID&quot;)
-            }
-        )
-
-
-    @classproperty
-    def _removeMemberFromGroupQuery(cls):
-        gm = schema.GROUP_MEMBERSHIP
-        return Delete(
-            From=gm,
-            Where=(
-                gm.GROUP_ID == Parameter(&quot;groupID&quot;)
-            ).And(
-                gm.MEMBER_UID == Parameter(&quot;memberUID&quot;)
-            )
-        )
-
-
-    @classproperty
-    def _selectGroupMembersQuery(cls):
-        gm = schema.GROUP_MEMBERSHIP
-        return Select(
-            [gm.MEMBER_UID],
-            From=gm,
-            Where=(
-                gm.GROUP_ID == Parameter(&quot;groupID&quot;)
-            )
-        )
-
-
-    @classproperty
-    def _selectGroupsForQuery(cls):
-        gr = schema.GROUPS
-        gm = schema.GROUP_MEMBERSHIP
-
-        return Select(
-            [gr.GROUP_UID],
-            From=gr,
-            Where=(
-                gr.GROUP_ID.In(
-                    Select(
-                        [gm.GROUP_ID],
-                        From=gm,
-                        Where=(
-                            gm.MEMBER_UID == Parameter(&quot;uid&quot;)
-                        )
-                    )
-                )
-            )
-        )
-
-
-    def addMemberToGroup(self, memberUID, groupID):
-        return self._addMemberToGroupQuery.on(
-            self, groupID=groupID, memberUID=memberUID.encode(&quot;utf-8&quot;)
-        )
-
-
-    def removeMemberFromGroup(self, memberUID, groupID):
-        return self._removeMemberFromGroupQuery.on(
-            self, groupID=groupID, memberUID=memberUID.encode(&quot;utf-8&quot;)
-        )
-
-
-    @inlineCallbacks
-    def groupMemberUIDs(self, groupID):
-        &quot;&quot;&quot;
-        Returns the cached set of UIDs for members of the given groupID.
-        Sub-groups are not returned in the results but their members are,
-        because the group membership has already been expanded/flattened
-        before storing in the db.
-
-        @param groupID: the group ID
-        @type groupID: C{int}
-        @return: the set of member UIDs
-        @rtype: a Deferred which fires with a set() of C{str} UIDs
-        &quot;&quot;&quot;
-        members = set()
-        results = (yield self._selectGroupMembersQuery.on(self, groupID=groupID))
-        for row in results:
-            members.add(row[0].decode(&quot;utf-8&quot;))
-        returnValue(members)
-
-
-    @inlineCallbacks
-    def refreshGroup(self, groupUID, record, groupID, cachedName, cachedMembershipHash, cachedExtant):
-        &quot;&quot;&quot;
-        @param groupUID: the directory record
-        @type groupUID: C{unicode}
-        @param record: the directory record
-        @type record: C{iDirectoryRecord}
-        @param groupID: group resource id
-        @type groupID: C{str}
-        @param cachedName: group name in the database
-        @type cachedName: C{unicode}
-        @param cachedMembershipHash: membership hash in the database
-        @type cachedMembershipHash: C{str}
-        @param cachedExtant: extent field from in the database
-        @type cachedExtant: C{bool}
-
-        @return: Deferred firing with membershipChanged C{boolean}
-
-        &quot;&quot;&quot;
-        if record is not None:
-            memberUIDs = yield record.expandedMemberUIDs()
-            name = record.displayName
-            extant = True
-        else:
-            memberUIDs = frozenset()
-            name = cachedName
-            extant = False
-
-        membershipHashContent = hashlib.md5()
-        for memberUID in sorted(memberUIDs):
-            membershipHashContent.update(str(memberUID))
-        membershipHash = membershipHashContent.hexdigest()
-
-        if cachedMembershipHash != membershipHash:
-            membershipChanged = True
-            log.debug(
-                &quot;Group '{group}' changed&quot;, group=name
-            )
-        else:
-            membershipChanged = False
-
-        if membershipChanged or extant != cachedExtant:
-            # also updates group mod date
-            yield self.updateGroup(
-                groupUID, name, membershipHash, extant=extant
-            )
-
-        if membershipChanged:
-            addedUIDs, removedUIDs = yield self.synchronizeMembers(groupID, set(memberUIDs))
-        else:
-            addedUIDs = removedUIDs = None
-
-        returnValue((membershipChanged, addedUIDs, removedUIDs,))
-
-
-    @inlineCallbacks
-    def synchronizeMembers(self, groupID, newMemberUIDs):
-        &quot;&quot;&quot;
-        Update the group membership table in the database to match the new membership list. This
-        method will diff the existing set with the new set and apply the changes. It also calls out
-        to a groupChanged() method with the set of added and removed members so that other modules
-        that depend on groups can monitor the changes.
-
-        @param groupID: group id of group to update
-        @type groupID: L{str}
-        @param newMemberUIDs: set of new member UIDs in the group
-        @type newMemberUIDs: L{set} of L{str}
-        &quot;&quot;&quot;
-        cachedMemberUIDs = (yield self.groupMemberUIDs(groupID))
-
-        removed = cachedMemberUIDs - newMemberUIDs
-        for memberUID in removed:
-            yield self.removeMemberFromGroup(memberUID, groupID)
-
-        added = newMemberUIDs - cachedMemberUIDs
-        for memberUID in added:
-            yield self.addMemberToGroup(memberUID, groupID)
-
-        yield self.groupChanged(groupID, added, removed)
-
-        returnValue((added, removed,))
-
-
-    @inlineCallbacks
-    def groupChanged(self, groupID, addedUIDs, removedUIDs):
-        &quot;&quot;&quot;
-        Called when membership of a group changes.
-
-        @param groupID: group id of group that changed
-        @type groupID: L{str}
-        @param addedUIDs: set of new member UIDs added to the group
-        @type addedUIDs: L{set} of L{str}
-        @param removedUIDs: set of old member UIDs removed from the group
-        @type removedUIDs: L{set} of L{str}
-        &quot;&quot;&quot;
-        yield Delegates.groupChanged(self, groupID, addedUIDs, removedUIDs)
-
-
-    @inlineCallbacks
-    def groupMembers(self, groupID):
-        &quot;&quot;&quot;
-        The members of the given group as recorded in the db
-        &quot;&quot;&quot;
-        members = set()
-        memberUIDs = (yield self.groupMemberUIDs(groupID))
-        for uid in memberUIDs:
-            record = (yield self.directoryService().recordWithUID(uid))
-            if record is not None:
-                members.add(record)
-        returnValue(members)
-
-
-    @inlineCallbacks
-    def groupUIDsFor(self, uid):
-        &quot;&quot;&quot;
-        Returns the cached set of UIDs for the groups this given uid is
-        a member of.
-
-        @param uid: the uid
-        @type uid: C{unicode}
-        @return: the set of group IDs
-        @rtype: a Deferred which fires with a set() of C{int} group IDs
-        &quot;&quot;&quot;
-        groups = set()
-        results = (
-            yield self._selectGroupsForQuery.on(
-                self, uid=uid.encode(&quot;utf-8&quot;)
-            )
-        )
-        for row in results:
-            groups.add(row[0].decode(&quot;utf-8&quot;))
-        returnValue(groups)
-
-    # End of Group Members
-
-    # Delegates
-
-
-    @classproperty
-    def _addDelegateQuery(cls):
-        de = schema.DELEGATES
-        return Insert({de.DELEGATOR: Parameter(&quot;delegator&quot;),
-                       de.DELEGATE: Parameter(&quot;delegate&quot;),
-                       de.READ_WRITE: Parameter(&quot;readWrite&quot;),
-                       })
-
-
-    @classproperty
-    def _addDelegateGroupQuery(cls):
-        ds = schema.DELEGATE_GROUPS
-        return Insert({ds.DELEGATOR: Parameter(&quot;delegator&quot;),
-                       ds.GROUP_ID: Parameter(&quot;groupID&quot;),
-                       ds.READ_WRITE: Parameter(&quot;readWrite&quot;),
-                       ds.IS_EXTERNAL: Parameter(&quot;isExternal&quot;),
-                       })
-
-
-    @classproperty
-    def _removeDelegateQuery(cls):
-        de = schema.DELEGATES
-        return Delete(
-            From=de,
-            Where=(
-                de.DELEGATOR == Parameter(&quot;delegator&quot;)
-            ).And(
-                de.DELEGATE == Parameter(&quot;delegate&quot;)
-            ).And(
-                de.READ_WRITE == Parameter(&quot;readWrite&quot;)
-            )
-        )
-
-
-    @classproperty
-    def _removeDelegatesQuery(cls):
-        de = schema.DELEGATES
-        return Delete(
-            From=de,
-            Where=(
-                de.DELEGATOR == Parameter(&quot;delegator&quot;)
-            ).And(
-                de.READ_WRITE == Parameter(&quot;readWrite&quot;)
-            )
-        )
-
-
-    @classproperty
-    def _removeDelegateGroupQuery(cls):
-        ds = schema.DELEGATE_GROUPS
-        return Delete(
-            From=ds,
-            Where=(
-                ds.DELEGATOR == Parameter(&quot;delegator&quot;)
-            ).And(
-                ds.GROUP_ID == Parameter(&quot;groupID&quot;)
-            ).And(
-                ds.READ_WRITE == Parameter(&quot;readWrite&quot;)
-            )
-        )
-
-
-    @classproperty
-    def _removeDelegateGroupsQuery(cls):
-        ds = schema.DELEGATE_GROUPS
-        return Delete(
-            From=ds,
-            Where=(
-                ds.DELEGATOR == Parameter(&quot;delegator&quot;)
-            ).And(
-                ds.READ_WRITE == Parameter(&quot;readWrite&quot;)
-            )
-        )
-
-
-    @classproperty
-    def _selectDelegatesQuery(cls):
-        de = schema.DELEGATES
-        return Select(
-            [de.DELEGATE],
-            From=de,
-            Where=(
-                de.DELEGATOR == Parameter(&quot;delegator&quot;)
-            ).And(
-                de.READ_WRITE == Parameter(&quot;readWrite&quot;)
-            )
-        )
-
-
-    @classproperty
-    def _selectDelegatorsToGroupQuery(cls):
-        dg = schema.DELEGATE_GROUPS
-        return Select(
-            [dg.DELEGATOR],
-            From=dg,
-            Where=(
-                dg.GROUP_ID == Parameter(&quot;delegateGroup&quot;)
-            ).And(
-                dg.READ_WRITE == Parameter(&quot;readWrite&quot;)
-            )
-        )
-
-
-    @classproperty
-    def _selectDelegateGroupsQuery(cls):
-        ds = schema.DELEGATE_GROUPS
-        gr = schema.GROUPS
-
-        return Select(
-            [gr.GROUP_UID],
-            From=gr,
-            Where=(
-                gr.GROUP_ID.In(
-                    Select(
-                        [ds.GROUP_ID],
-                        From=ds,
-                        Where=(
-                            ds.DELEGATOR == Parameter(&quot;delegator&quot;)
-                        ).And(
-                            ds.READ_WRITE == Parameter(&quot;readWrite&quot;)
-                        )
-                    )
-                )
-            )
-        )
-
-
-    @classproperty
-    def _selectDirectDelegatorsQuery(cls):
-        de = schema.DELEGATES
-        return Select(
-            [de.DELEGATOR],
-            From=de,
-            Where=(
-                de.DELEGATE == Parameter(&quot;delegate&quot;)
-            ).And(
-                de.READ_WRITE == Parameter(&quot;readWrite&quot;)
-            )
-        )
-
-
-    @classproperty
-    def _selectIndirectDelegatorsQuery(cls):
-        dg = schema.DELEGATE_GROUPS
-        gm = schema.GROUP_MEMBERSHIP
-
-        return Select(
-            [dg.DELEGATOR],
-            From=dg,
-            Where=(
-                dg.GROUP_ID.In(
-                    Select(
-                        [gm.GROUP_ID],
-                        From=gm,
-                        Where=(gm.MEMBER_UID == Parameter(&quot;delegate&quot;))
-                    )
-                ).And(
-                    dg.READ_WRITE == Parameter(&quot;readWrite&quot;)
-                )
-            )
-        )
-
-
-    @classproperty
-    def _selectIndirectDelegatesQuery(cls):
-        dg = schema.DELEGATE_GROUPS
-        gm = schema.GROUP_MEMBERSHIP
-
-        return Select(
-            [gm.MEMBER_UID],
-            From=gm,
-            Where=(
-                gm.GROUP_ID.In(
-                    Select(
-                        [dg.GROUP_ID],
-                        From=dg,
-                        Where=(dg.DELEGATOR == Parameter(&quot;delegator&quot;)).And(
-                            dg.READ_WRITE == Parameter(&quot;readWrite&quot;))
-                    )
-                )
-            )
-        )
-
-
-    @classproperty
-    def _selectExternalDelegateGroupsQuery(cls):
-        edg = schema.EXTERNAL_DELEGATE_GROUPS
-        return Select(
-            [edg.DELEGATOR, edg.GROUP_UID_READ, edg.GROUP_UID_WRITE],
-            From=edg
-        )
-
-
-    @classproperty
-    def _removeExternalDelegateGroupsPairQuery(cls):
-        edg = schema.EXTERNAL_DELEGATE_GROUPS
-        return Delete(
-            From=edg,
-            Where=(
-                edg.DELEGATOR == Parameter(&quot;delegator&quot;)
-            )
-        )
-
-
-    @classproperty
-    def _storeExternalDelegateGroupsPairQuery(cls):
-        edg = schema.EXTERNAL_DELEGATE_GROUPS
-        return Insert(
-            {
-                edg.DELEGATOR: Parameter(&quot;delegator&quot;),
-                edg.GROUP_UID_READ: Parameter(&quot;readDelegate&quot;),
-                edg.GROUP_UID_WRITE: Parameter(&quot;writeDelegate&quot;),
-            }
-        )
-
-
-    @classproperty
-    def _removeExternalDelegateGroupsQuery(cls):
-        ds = schema.DELEGATE_GROUPS
-        return Delete(
-            From=ds,
-            Where=(
-                ds.DELEGATOR == Parameter(&quot;delegator&quot;)
-            ).And(
-                ds.IS_EXTERNAL == 1
-            )
-        )
-
-
-    @inlineCallbacks
-    def addDelegate(self, delegator, delegate, readWrite):
-        &quot;&quot;&quot;
-        Adds a row to the DELEGATES table.  The delegate should not be a
-        group.  To delegate to a group, call addDelegateGroup() instead.
-
-        @param delegator: the UID of the delegator
-        @type delegator: C{unicode}
-        @param delegate: the UID of the delegate
-        @type delegate: C{unicode}
-        @param readWrite: grant read and write access if True, otherwise
-            read-only access
-        @type readWrite: C{boolean}
-        &quot;&quot;&quot;
-
-        def _addDelegate(subtxn):
-            return self._addDelegateQuery.on(
-                subtxn,
-                delegator=delegator.encode(&quot;utf-8&quot;),
-                delegate=delegate.encode(&quot;utf-8&quot;),
-                readWrite=1 if readWrite else 0
-            )
-
-        try:
-            yield self.subtransaction(_addDelegate, retries=0, failureOK=True)
-        except AllRetriesFailed:
-            pass
-
-
-    @inlineCallbacks
-    def addDelegateGroup(self, delegator, delegateGroupID, readWrite,
-                         isExternal=False):
-        &quot;&quot;&quot;
-        Adds a row to the DELEGATE_GROUPS table.  The delegate should be a
-        group.  To delegate to a person, call addDelegate() instead.
-
-        @param delegator: the UID of the delegator
-        @type delegator: C{unicode}
-        @param delegateGroupID: the GROUP_ID of the delegate group
-        @type delegateGroupID: C{int}
-        @param readWrite: grant read and write access if True, otherwise
-            read-only access
-        @type readWrite: C{boolean}
-        &quot;&quot;&quot;
-
-        def _addDelegateGroup(subtxn):
-            return self._addDelegateGroupQuery.on(
-                subtxn,
-                delegator=delegator.encode(&quot;utf-8&quot;),
-                groupID=delegateGroupID,
-                readWrite=1 if readWrite else 0,
-                isExternal=1 if isExternal else 0
-            )
-
-        try:
-            yield self.subtransaction(_addDelegateGroup, retries=0, failureOK=True)
-        except AllRetriesFailed:
-            pass
-
-
-    def removeDelegate(self, delegator, delegate, readWrite):
-        &quot;&quot;&quot;
-        Removes a row from the DELEGATES table.  The delegate should not be a
-        group.  To remove a delegate group, call removeDelegateGroup() instead.
-
-        @param delegator: the UID of the delegator
-        @type delegator: C{unicode}
-        @param delegate: the UID of the delegate
-        @type delegate: C{unicode}
-        @param readWrite: remove read and write access if True, otherwise
-            read-only access
-        @type readWrite: C{boolean}
-        &quot;&quot;&quot;
-        return self._removeDelegateQuery.on(
-            self,
-            delegator=delegator.encode(&quot;utf-8&quot;),
-            delegate=delegate.encode(&quot;utf-8&quot;),
-            readWrite=1 if readWrite else 0
-        )
-
-
-    def removeDelegates(self, delegator, readWrite):
-        &quot;&quot;&quot;
-        Removes all rows for this delegator/readWrite combination from the
-        DELEGATES table.
-
-        @param delegator: the UID of the delegator
-        @type delegator: C{unicode}
-        @param readWrite: remove read and write access if True, otherwise
-            read-only access
-        @type readWrite: C{boolean}
-        &quot;&quot;&quot;
-        return self._removeDelegatesQuery.on(
-            self,
-            delegator=delegator.encode(&quot;utf-8&quot;),
-            readWrite=1 if readWrite else 0
-        )
-
-
-    def removeDelegateGroup(self, delegator, delegateGroupID, readWrite):
-        &quot;&quot;&quot;
-        Removes a row from the DELEGATE_GROUPS table.  The delegate should be a
-        group.  To remove a delegate person, call removeDelegate() instead.
-
-        @param delegator: the UID of the delegator
-        @type delegator: C{unicode}
-        @param delegateGroupID: the GROUP_ID of the delegate group
-        @type delegateGroupID: C{int}
-        @param readWrite: remove read and write access if True, otherwise
-            read-only access
-        @type readWrite: C{boolean}
-        &quot;&quot;&quot;
-        return self._removeDelegateGroupQuery.on(
-            self,
-            delegator=delegator.encode(&quot;utf-8&quot;),
-            groupID=delegateGroupID,
-            readWrite=1 if readWrite else 0
-        )
-
-
-    def removeDelegateGroups(self, delegator, readWrite):
-        &quot;&quot;&quot;
-        Removes all rows for this delegator/readWrite combination from the
-        DELEGATE_GROUPS table.
-
-        @param delegator: the UID of the delegator
-        @type delegator: C{unicode}
-        @param readWrite: remove read and write access if True, otherwise
-            read-only access
-        @type readWrite: C{boolean}
-        &quot;&quot;&quot;
-        return self._removeDelegateGroupsQuery.on(
-            self,
-            delegator=delegator.encode(&quot;utf-8&quot;),
-            readWrite=1 if readWrite else 0
-        )
-
-
-    @inlineCallbacks
-    def delegates(self, delegator, readWrite, expanded=False):
-        &quot;&quot;&quot;
-        Returns the UIDs of all delegates for the given delegator.  If
-        expanded is False, only the direct delegates (users and groups)
-        are returned.  If expanded is True, the expanded membership is
-        returned, not including the groups themselves.
-
-        @param delegator: the UID of the delegator
-        @type delegator: C{unicode}
-        @param readWrite: the access-type to check for; read and write
-            access if True, otherwise read-only access
-        @type readWrite: C{boolean}
-        @returns: the UIDs of the delegates (for the specified access
-            type)
-        @rtype: a Deferred resulting in a set
-        &quot;&quot;&quot;
-        delegates = set()
-        delegatorU = delegator.encode(&quot;utf-8&quot;)
-
-        # First get the direct delegates
-        results = (
-            yield self._selectDelegatesQuery.on(
-                self,
-                delegator=delegatorU,
-                readWrite=1 if readWrite else 0
-            )
-        )
-        delegates.update([row[0].decode(&quot;utf-8&quot;) for row in results])
-
-        if expanded:
-            # Get those who are in groups which have been delegated to
-            results = (
-                yield self._selectIndirectDelegatesQuery.on(
-                    self,
-                    delegator=delegatorU,
-                    readWrite=1 if readWrite else 0
-                )
-            )
-            # Skip the delegator if they are in one of the groups
-            delegates.update([row[0].decode(&quot;utf-8&quot;) for row in results if row[0] != delegatorU])
-
-        else:
-            # Get the directly-delegated-to groups
-            results = (
-                yield self._selectDelegateGroupsQuery.on(
-                    self,
-                    delegator=delegatorU,
-                    readWrite=1 if readWrite else 0
-                )
-            )
-            delegates.update([row[0].decode(&quot;utf-8&quot;) for row in results])
-
-        returnValue(delegates)
-
-
-    @inlineCallbacks
-    def delegators(self, delegate, readWrite):
-        &quot;&quot;&quot;
-        Returns the UIDs of all delegators which have granted access to
-        the given delegate, either directly or indirectly via groups.
-
-        @param delegate: the UID of the delegate
-        @type delegate: C{unicode}
-        @param readWrite: the access-type to check for; read and write
-            access if True, otherwise read-only access
-        @type readWrite: C{boolean}
-        @returns: the UIDs of the delegators (for the specified access
-            type)
-        @rtype: a Deferred resulting in a set
-        &quot;&quot;&quot;
-        delegators = set()
-        delegateU = delegate.encode(&quot;utf-8&quot;)
-
-        # First get the direct delegators
-        results = (
-            yield self._selectDirectDelegatorsQuery.on(
-                self,
-                delegate=delegateU,
-                readWrite=1 if readWrite else 0
-            )
-        )
-        delegators.update([row[0].decode(&quot;utf-8&quot;) for row in results])
-
-        # Finally get those who have delegated to groups the delegate
-        # is a member of
-        results = (
-            yield self._selectIndirectDelegatorsQuery.on(
-                self,
-                delegate=delegateU,
-                readWrite=1 if readWrite else 0
-            )
-        )
-        # Skip the delegator if they are in one of the groups
-        delegators.update([row[0].decode(&quot;utf-8&quot;) for row in results if row[0] != delegateU])
-
-        returnValue(delegators)
-
-
-    @inlineCallbacks
-    def delegatorsToGroup(self, delegateGroupID, readWrite):
-        &quot;&quot;&quot;
-        Return the UIDs of those who have delegated to the given group with the
-        given access level.
-
-        @param delegateGroupID: the group ID of the delegate group
-        @type delegateGroupID: C{int}
-        @param readWrite: the access-type to check for; read and write
-            access if True, otherwise read-only access
-        @type readWrite: C{boolean}
-        @returns: the UIDs of the delegators (for the specified access
-            type)
-        @rtype: a Deferred resulting in a set
-
-        &quot;&quot;&quot;
-        results = (
-            yield self._selectDelegatorsToGroupQuery.on(
-                self,
-                delegateGroup=delegateGroupID,
-                readWrite=1 if readWrite else 0
-            )
-        )
-        delegators = set([row[0].decode(&quot;utf-8&quot;) for row in results])
-        returnValue(delegators)
-
-
-    @inlineCallbacks
-    def allGroupDelegates(self):
-        &quot;&quot;&quot;
-        Return the UIDs of all groups which have been delegated to.  Useful
-        for obtaining the set of groups which need to be synchronized from
-        the directory.
-
-        @returns: the UIDs of all delegated-to groups
-        @rtype: a Deferred resulting in a set
-        &quot;&quot;&quot;
-        gr = schema.GROUPS
-        dg = schema.DELEGATE_GROUPS
-
-        results = (yield Select(
-            [gr.GROUP_UID],
-            From=gr,
-            Where=(gr.GROUP_ID.In(Select([dg.GROUP_ID], From=dg, Where=None)))
-        ).on(self))
-        delegates = set()
-        for row in results:
-            delegates.add(row[0].decode(&quot;utf-8&quot;))
-
-        returnValue(delegates)
-
-
-    @inlineCallbacks
-    def externalDelegates(self):
-        &quot;&quot;&quot;
-        Returns a dictionary mapping delegate UIDs to (read-group, write-group)
-        tuples, including only those assignments that originated from the
-        directory.
-
-        @returns: dictionary mapping delegator uid to (readDelegateUID,
-            writeDelegateUID) tuples
-        @rtype: a Deferred resulting in a dictionary
-        &quot;&quot;&quot;
-        delegates = {}
-
-        # Get the externally managed delegates (which are all groups)
-        results = (yield self._selectExternalDelegateGroupsQuery.on(self))
-        for delegator, readDelegateUID, writeDelegateUID in results:
-            delegates[delegator.encode(&quot;utf-8&quot;)] = (
-                readDelegateUID.encode(&quot;utf-8&quot;) if readDelegateUID else None,
-                writeDelegateUID.encode(&quot;utf-8&quot;) if writeDelegateUID else None
-            )
-
-        returnValue(delegates)
-
-
-    @inlineCallbacks
-    def assignExternalDelegates(
-        self, delegator, readDelegateGroupID, writeDelegateGroupID,
-        readDelegateUID, writeDelegateUID
-    ):
-        &quot;&quot;&quot;
-        Update the external delegate group table so we can quickly identify
-        diffs next time, and update the delegate group table itself
-
-        @param delegator
-        @type delegator: C{UUID}
-        &quot;&quot;&quot;
-
-        # Delete existing external assignments for the delegator
-        yield self._removeExternalDelegateGroupsQuery.on(
-            self,
-            delegator=str(delegator)
-        )
-
-        # Remove from the external comparison table
-        yield self._removeExternalDelegateGroupsPairQuery.on(
-            self,
-            delegator=str(delegator)
-        )
-
-        # Store new assignments in the external comparison table
-        if readDelegateUID or writeDelegateUID:
-            readDelegateForDB = (
-                readDelegateUID.encode(&quot;utf-8&quot;) if readDelegateUID else &quot;&quot;
-            )
-            writeDelegateForDB = (
-                writeDelegateUID.encode(&quot;utf-8&quot;) if writeDelegateUID else &quot;&quot;
-            )
-            yield self._storeExternalDelegateGroupsPairQuery.on(
-                self,
-                delegator=str(delegator),
-                readDelegate=readDelegateForDB,
-                writeDelegate=writeDelegateForDB
-            )
-
-        # Apply new assignments
-        if readDelegateGroupID is not None:
-            yield self.addDelegateGroup(
-                delegator, readDelegateGroupID, False, isExternal=True
-            )
-        if writeDelegateGroupID is not None:
-            yield self.addDelegateGroup(
-                delegator, writeDelegateGroupID, True, isExternal=True
-            )
-
-
-    # End of Delegates
-
-
</del><span class="cx">     def preCommit(self, operation):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Run things before C{commit}.  (Note: only provided by SQL
</span><span class="lines">@@ -6033,7 +5006,7 @@
</span><span class="cx">         return self.ownerHome().externalClass()
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def externalize(self):
</del><ins>+    def serialize(self):
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Create a dictionary mapping key attributes so this object can be sent over a cross-pod call
</span><span class="cx">         and reconstituted at the other end. Note that the other end may have a different schema so
</span><span class="lines">@@ -6048,9 +5021,9 @@
</span><span class="cx"> 
</span><span class="cx">     @classmethod
</span><span class="cx">     @inlineCallbacks
</span><del>-    def internalize(cls, parent, mapping):
</del><ins>+    def deserialize(cls, parent, mapping):
</ins><span class="cx">         &quot;&quot;&quot;
</span><del>-        Given a mapping generated by L{externalize}, convert the values into an array of database
</del><ins>+        Given a mapping generated by L{serialize}, convert the values into an array of database
</ins><span class="cx">         like items that conforms to the ordering of L{_allColumns} so it can be fed into L{makeClass}.
</span><span class="cx">         Note that there may be a schema mismatch with the external data, so treat missing items as
</span><span class="cx">         C{None} and ignore extra items.
</span><span class="lines">@@ -7290,7 +6263,7 @@
</span><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def externalize(self):
</del><ins>+    def serialize(self):
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Create a dictionary mapping key attributes so this object can be sent over a cross-pod call
</span><span class="cx">         and reconstituted at the other end. Note that the other end may have a different schema so
</span><span class="lines">@@ -7301,9 +6274,9 @@
</span><span class="cx"> 
</span><span class="cx">     @classmethod
</span><span class="cx">     @inlineCallbacks
</span><del>-    def internalize(cls, parent, mapping):
</del><ins>+    def deserialize(cls, parent, mapping):
</ins><span class="cx">         &quot;&quot;&quot;
</span><del>-        Given a mapping generated by L{externalize}, convert the values into an array of database
</del><ins>+        Given a mapping generated by L{serialize}, convert the values into an array of database
</ins><span class="cx">         like items that conforms to the ordering of L{_allColumns} so it can be fed into L{makeClass}.
</span><span class="cx">         Note that there may be a schema mismatch with the external data, so treat missing items as
</span><span class="cx">         C{None} and ignore extra items.
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboopod2podmigrationtxdavcommondatastoresql_directorypy"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql_directory.py (0 => 14417)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql_directory.py                                (rev 0)
+++ CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql_directory.py        2015-02-16 20:56:21 UTC (rev 14417)
</span><span class="lines">@@ -0,0 +1,773 @@
</span><ins>+# -*- test-case-name: twext.enterprise.dal.test.test_record -*-
+##
+# Copyright (c) 2015 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from twext.enterprise.dal.record import SerializableRecord, fromTable
+from twext.enterprise.dal.syntax import SavepointAction
+from twext.python.log import Logger
+from twisted.internet.defer import inlineCallbacks, returnValue
+from txdav.common.datastore.sql_tables import schema
+from txdav.common.icommondatastore import AllRetriesFailed, NotFoundError
+import datetime
+import hashlib
+from txdav.who.delegates import Delegates
+
+log = Logger()
+
+&quot;&quot;&quot;
+Classes and methods that relate to directory objects in the SQL store. e.g.,
+delegates, groups etc
+&quot;&quot;&quot;
+
+class GroupsRecord(SerializableRecord, fromTable(schema.GROUPS)):
+    &quot;&quot;&quot;
+    @DynamicAttrs
+    L{Record} for L{schema.GROUPS}.
+    &quot;&quot;&quot;
+
+    @classmethod
+    def groupsForMember(cls, txn, memberUID):
+
+        return GroupsRecord.query(
+            txn,
+            GroupsRecord.groupID.In(
+                GroupMembershipRecord.queryExpr(
+                    GroupMembershipRecord.memberUID == memberUID.encode(&quot;utf-8&quot;),
+                    attributes=(GroupMembershipRecord.groupID,),
+                )
+            ),
+        )
+
+
+
+class GroupMembershipRecord(SerializableRecord, fromTable(schema.GROUP_MEMBERSHIP)):
+    &quot;&quot;&quot;
+    @DynamicAttrs
+    L{Record} for L{schema.GROUP_MEMBERSHIP}.
+    &quot;&quot;&quot;
+    pass
+
+
+
+class DelegateRecord(SerializableRecord, fromTable(schema.DELEGATES)):
+    &quot;&quot;&quot;
+    @DynamicAttrs
+    L{Record} for L{schema.DELEGATES}.
+    &quot;&quot;&quot;
+    pass
+
+
+
+class DelegateGroupsRecord(SerializableRecord, fromTable(schema.DELEGATE_GROUPS)):
+    &quot;&quot;&quot;
+    @DynamicAttrs
+    L{Record} for L{schema.DELEGATE_GROUPS}.
+    &quot;&quot;&quot;
+
+    @classmethod
+    def allGroupDelegates(cls, txn):
+        &quot;&quot;&quot;
+        Get the directly-delegated-to groups.
+        &quot;&quot;&quot;
+
+        return GroupsRecord.query(
+            txn,
+            GroupsRecord.groupID.In(
+                DelegateGroupsRecord.queryExpr(
+                    None,
+                    attributes=(DelegateGroupsRecord.groupID,),
+                )
+            ),
+        )
+
+
+    @classmethod
+    def delegateGroups(cls, txn, delegator, readWrite):
+        &quot;&quot;&quot;
+        Get the directly-delegated-to groups.
+        &quot;&quot;&quot;
+
+        return GroupsRecord.query(
+            txn,
+            GroupsRecord.groupID.In(
+                DelegateGroupsRecord.queryExpr(
+                    (DelegateGroupsRecord.delegator == delegator.encode(&quot;utf-8&quot;)).And(
+                        DelegateGroupsRecord.readWrite == (1 if readWrite else 0)
+                    ),
+                    attributes=(DelegateGroupsRecord.groupID,),
+                )
+            ),
+        )
+
+
+    @classmethod
+    def indirectDelegators(cls, txn, delegate, readWrite):
+        &quot;&quot;&quot;
+        Get delegators who have delegated to groups the delegate is a member of.
+        &quot;&quot;&quot;
+
+        return cls.query(
+            txn,
+            cls.groupID.In(
+                GroupMembershipRecord.queryExpr(
+                    GroupMembershipRecord.memberUID == delegate.encode(&quot;utf-8&quot;),
+                    attributes=(GroupMembershipRecord.groupID,),
+                )
+            ).And(cls.readWrite == (1 if readWrite else 0)),
+        )
+
+
+    @classmethod
+    def indirectDelegates(cls, txn, delegator, readWrite):
+        &quot;&quot;&quot;
+        Get delegates who are in groups which have been delegated to.
+        &quot;&quot;&quot;
+
+        return GroupMembershipRecord.query(
+            txn,
+            GroupMembershipRecord.groupID.In(
+                DelegateGroupsRecord.queryExpr(
+                    (DelegateGroupsRecord.delegator == delegator.encode(&quot;utf-8&quot;)).And(
+                        DelegateGroupsRecord.readWrite == (1 if readWrite else 0)
+                    ),
+                    attributes=(DelegateGroupsRecord.groupID,),
+                )
+            ),
+        )
+
+
+
+class ExternalDelegateGroupsRecord(SerializableRecord, fromTable(schema.EXTERNAL_DELEGATE_GROUPS)):
+    &quot;&quot;&quot;
+    @DynamicAttrs
+    L{Record} for L{schema.EXTERNAL_DELEGATE_GROUPS}.
+    &quot;&quot;&quot;
+    pass
+
+
+
+class GroupsAPIMixin(object):
+    &quot;&quot;&quot;
+    A mixin for L{CommonStoreTransaction} that covers the groups API.
+    &quot;&quot;&quot;
+
+    @inlineCallbacks
+    def addGroup(self, groupUID, name, membershipHash):
+        &quot;&quot;&quot;
+        @type groupUID: C{unicode}
+        @type name: C{unicode}
+        @type membershipHash: C{str}
+        &quot;&quot;&quot;
+        record = yield self.directoryService().recordWithUID(groupUID)
+        if record is None:
+            returnValue(None)
+
+        group = yield GroupsRecord.create(
+            self,
+            name=name.encode(&quot;utf-8&quot;),
+            groupUID=groupUID.encode(&quot;utf-8&quot;),
+            membershipHash=membershipHash,
+        )
+
+        yield self.refreshGroup(group, record)
+        returnValue(group)
+
+
+    def updateGroup(self, groupUID, name, membershipHash, extant=True):
+        &quot;&quot;&quot;
+        @type groupUID: C{unicode}
+        @type name: C{unicode}
+        @type membershipHash: C{str}
+        @type extant: C{boolean}
+        &quot;&quot;&quot;
+        timestamp = datetime.datetime.utcnow()
+        group = yield self.groupByUID(groupUID, create=False)
+        if group is not None:
+            yield group.update(
+                name=name.encode(&quot;utf-8&quot;),
+                membershipHash=membershipHash,
+                extant=(1 if extant else 0),
+                modified=timestamp,
+            )
+
+
+    @inlineCallbacks
+    def groupByUID(self, groupUID, create=True):
+        &quot;&quot;&quot;
+        Return or create a record for the group UID.
+
+        @type groupUID: C{unicode}
+
+        @return: Deferred firing with tuple of group ID C{str}, group name
+            C{unicode}, membership hash C{str}, modified timestamp, and
+            extant C{boolean}
+        &quot;&quot;&quot;
+        results = yield GroupsRecord.query(
+            self,
+            GroupsRecord.groupUID == groupUID.encode(&quot;utf-8&quot;)
+        )
+        if results:
+            returnValue(results[0])
+        elif create:
+            savepoint = SavepointAction(&quot;groupByUID&quot;)
+            yield savepoint.acquire(self)
+            try:
+                group = yield self.addGroup(groupUID, u&quot;&quot;, &quot;&quot;)
+                if group is None:
+                    # The record does not actually exist within the directory
+                    yield savepoint.release(self)
+                    returnValue(None)
+
+            except Exception:
+                yield savepoint.rollback(self)
+                results = yield GroupsRecord.query(
+                    self,
+                    GroupsRecord.groupUID == groupUID.encode(&quot;utf-8&quot;)
+                )
+                returnValue(results[0] if results else None)
+            else:
+                yield savepoint.release(self)
+                returnValue(group)
+        else:
+            returnValue(None)
+
+
+    @inlineCallbacks
+    def groupByID(self, groupID):
+        &quot;&quot;&quot;
+        Given a group ID, return the group UID, or raise NotFoundError
+
+        @type groupID: C{str}
+        @return: Deferred firing with a tuple of group UID C{unicode},
+            group name C{unicode}, membership hash C{str}, and extant C{boolean}
+        &quot;&quot;&quot;
+        results = yield GroupsRecord.query(
+            self,
+            GroupsRecord.groupID == groupID,
+        )
+        if results:
+            returnValue(results[0])
+        else:
+            raise NotFoundError
+
+
+
+class GroupCacherAPIMixin(object):
+    &quot;&quot;&quot;
+    A mixin for L{CommonStoreTransaction} that covers the group cacher API.
+    &quot;&quot;&quot;
+
+    def addMemberToGroup(self, memberUID, groupID):
+        return GroupMembershipRecord.create(self, groupID=groupID, memberUID=memberUID.encode(&quot;utf-8&quot;))
+
+
+    def removeMemberFromGroup(self, memberUID, groupID):
+        return GroupMembershipRecord.deletematch(
+            self, groupID=groupID, memberUID=memberUID.encode(&quot;utf-8&quot;)
+        )
+
+
+    @inlineCallbacks
+    def groupMemberUIDs(self, groupID):
+        &quot;&quot;&quot;
+        Returns the cached set of UIDs for members of the given groupID.
+        Sub-groups are not returned in the results but their members are,
+        because the group membership has already been expanded/flattened
+        before storing in the db.
+
+        @param groupID: the group ID
+        @type groupID: C{int}
+        @return: the set of member UIDs
+        @rtype: a Deferred which fires with a set() of C{str} UIDs
+        &quot;&quot;&quot;
+
+        members = yield GroupMembershipRecord.query(self, GroupMembershipRecord.groupID == groupID)
+        returnValue(set([record.memberUID.decode(&quot;utf-8&quot;) for record in members]))
+
+
+    @inlineCallbacks
+    def refreshGroup(self, group, record):
+        &quot;&quot;&quot;
+        @param group: the group record
+        @type group: L{GroupsRecord}
+        @param record: the directory record
+        @type record: C{iDirectoryRecord}
+
+        @return: Deferred firing with membershipChanged C{boolean}
+
+        &quot;&quot;&quot;
+
+        if record is not None:
+            memberUIDs = yield record.expandedMemberUIDs()
+            name = record.displayName
+            extant = True
+        else:
+            memberUIDs = frozenset()
+            name = group.name
+            extant = False
+
+        membershipHashContent = hashlib.md5()
+        for memberUID in sorted(memberUIDs):
+            membershipHashContent.update(str(memberUID))
+        membershipHash = membershipHashContent.hexdigest()
+
+        if group.membershipHash != membershipHash:
+            membershipChanged = True
+            log.debug(
+                &quot;Group '{group}' changed&quot;, group=name
+            )
+        else:
+            membershipChanged = False
+
+        if membershipChanged or extant != group.extant:
+            # also updates group mod date
+            yield group.update(
+                name=name,
+                membershipHash=membershipHash,
+                extant=(1 if extant else 0),
+            )
+
+        if membershipChanged:
+            addedUIDs, removedUIDs = yield self.synchronizeMembers(group.groupID, set(memberUIDs))
+        else:
+            addedUIDs = removedUIDs = None
+
+        returnValue((membershipChanged, addedUIDs, removedUIDs,))
+
+
+    @inlineCallbacks
+    def synchronizeMembers(self, groupID, newMemberUIDs):
+        &quot;&quot;&quot;
+        Update the group membership table in the database to match the new membership list. This
+        method will diff the existing set with the new set and apply the changes. It also calls out
+        to a groupChanged() method with the set of added and removed members so that other modules
+        that depend on groups can monitor the changes.
+
+        @param groupID: group id of group to update
+        @type groupID: L{str}
+        @param newMemberUIDs: set of new member UIDs in the group
+        @type newMemberUIDs: L{set} of L{str}
+        &quot;&quot;&quot;
+        cachedMemberUIDs = yield self.groupMemberUIDs(groupID)
+
+        removed = cachedMemberUIDs - newMemberUIDs
+        for memberUID in removed:
+            yield self.removeMemberFromGroup(memberUID, groupID)
+
+        added = newMemberUIDs - cachedMemberUIDs
+        for memberUID in added:
+            yield self.addMemberToGroup(memberUID, groupID)
+
+        yield self.groupChanged(groupID, added, removed)
+
+        returnValue((added, removed,))
+
+
+    @inlineCallbacks
+    def groupChanged(self, groupID, addedUIDs, removedUIDs):
+        &quot;&quot;&quot;
+        Called when membership of a group changes.
+
+        @param groupID: group id of group that changed
+        @type groupID: L{str}
+        @param addedUIDs: set of new member UIDs added to the group
+        @type addedUIDs: L{set} of L{str}
+        @param removedUIDs: set of old member UIDs removed from the group
+        @type removedUIDs: L{set} of L{str}
+        &quot;&quot;&quot;
+        yield Delegates.groupChanged(self, groupID, addedUIDs, removedUIDs)
+
+
+    @inlineCallbacks
+    def groupMembers(self, groupID):
+        &quot;&quot;&quot;
+        The members of the given group as recorded in the db
+        &quot;&quot;&quot;
+        members = set()
+        memberUIDs = (yield self.groupMemberUIDs(groupID))
+        for uid in memberUIDs:
+            record = (yield self.directoryService().recordWithUID(uid))
+            if record is not None:
+                members.add(record)
+        returnValue(members)
+
+
+    @inlineCallbacks
+    def groupUIDsFor(self, uid):
+        &quot;&quot;&quot;
+        Returns the cached set of UIDs for the groups this given uid is
+        a member of.
+
+        @param uid: the uid
+        @type uid: C{unicode}
+        @return: the set of group IDs
+        @rtype: a Deferred which fires with a set() of C{int} group IDs
+        &quot;&quot;&quot;
+        groups = yield GroupsRecord.groupsForMember(self, uid)
+        returnValue(set([group.groupUID.decode(&quot;utf-8&quot;) for group in groups]))
+
+
+
+class DelegatesAPIMixin(object):
+    &quot;&quot;&quot;
+    A mixin for L{CommonStoreTransaction} that covers the delegates API.
+    &quot;&quot;&quot;
+
+    @inlineCallbacks
+    def addDelegate(self, delegator, delegate, readWrite):
+        &quot;&quot;&quot;
+        Adds a row to the DELEGATES table.  The delegate should not be a
+        group.  To delegate to a group, call addDelegateGroup() instead.
+
+        @param delegator: the UID of the delegator
+        @type delegator: C{unicode}
+        @param delegate: the UID of the delegate
+        @type delegate: C{unicode}
+        @param readWrite: grant read and write access if True, otherwise
+            read-only access
+        @type readWrite: C{boolean}
+        &quot;&quot;&quot;
+
+        def _addDelegate(subtxn):
+            return DelegateRecord.create(
+                subtxn,
+                delegator=delegator.encode(&quot;utf-8&quot;),
+                delegate=delegate.encode(&quot;utf-8&quot;),
+                readWrite=1 if readWrite else 0
+            )
+
+        try:
+            yield self.subtransaction(_addDelegate, retries=0, failureOK=True)
+        except AllRetriesFailed:
+            pass
+
+
+    @inlineCallbacks
+    def addDelegateGroup(self, delegator, delegateGroupID, readWrite,
+                         isExternal=False):
+        &quot;&quot;&quot;
+        Adds a row to the DELEGATE_GROUPS table.  The delegate should be a
+        group.  To delegate to a person, call addDelegate() instead.
+
+        @param delegator: the UID of the delegator
+        @type delegator: C{unicode}
+        @param delegateGroupID: the GROUP_ID of the delegate group
+        @type delegateGroupID: C{int}
+        @param readWrite: grant read and write access if True, otherwise
+            read-only access
+        @type readWrite: C{boolean}
+        &quot;&quot;&quot;
+
+        def _addDelegateGroup(subtxn):
+            return DelegateGroupsRecord.create(
+                subtxn,
+                delegator=delegator.encode(&quot;utf-8&quot;),
+                groupID=delegateGroupID,
+                readWrite=1 if readWrite else 0,
+                isExternal=1 if isExternal else 0
+            )
+
+        try:
+            yield self.subtransaction(_addDelegateGroup, retries=0, failureOK=True)
+        except AllRetriesFailed:
+            pass
+
+
+    def removeDelegate(self, delegator, delegate, readWrite):
+        &quot;&quot;&quot;
+        Removes a row from the DELEGATES table.  The delegate should not be a
+        group.  To remove a delegate group, call removeDelegateGroup() instead.
+
+        @param delegator: the UID of the delegator
+        @type delegator: C{unicode}
+        @param delegate: the UID of the delegate
+        @type delegate: C{unicode}
+        @param readWrite: remove read and write access if True, otherwise
+            read-only access
+        @type readWrite: C{boolean}
+        &quot;&quot;&quot;
+        return DelegateRecord.deletesome(
+            self,
+            (DelegateRecord.delegator == delegator.encode(&quot;utf-8&quot;)).And(
+                DelegateRecord.delegate == delegate.encode(&quot;utf-8&quot;)).And(
+                DelegateRecord.readWrite == (1 if readWrite else 0)
+            ),
+        )
+
+
+    def removeDelegates(self, delegator, readWrite):
+        &quot;&quot;&quot;
+        Removes all rows for this delegator/readWrite combination from the
+        DELEGATES table.
+
+        @param delegator: the UID of the delegator
+        @type delegator: C{unicode}
+        @param readWrite: remove read and write access if True, otherwise
+            read-only access
+        @type readWrite: C{boolean}
+        &quot;&quot;&quot;
+        return DelegateRecord.deletesome(
+            self,
+            (DelegateRecord.delegator == delegator.encode(&quot;utf-8&quot;)).And(
+                DelegateRecord.readWrite == (1 if readWrite else 0)
+            ),
+        )
+
+
+    def removeDelegateGroup(self, delegator, delegateGroupID, readWrite):
+        &quot;&quot;&quot;
+        Removes a row from the DELEGATE_GROUPS table.  The delegate should be a
+        group.  To remove a delegate person, call removeDelegate() instead.
+
+        @param delegator: the UID of the delegator
+        @type delegator: C{unicode}
+        @param delegateGroupID: the GROUP_ID of the delegate group
+        @type delegateGroupID: C{int}
+        @param readWrite: remove read and write access if True, otherwise
+            read-only access
+        @type readWrite: C{boolean}
+        &quot;&quot;&quot;
+        return DelegateGroupsRecord.deletesome(
+            self,
+            (DelegateGroupsRecord.delegator == delegator.encode(&quot;utf-8&quot;)).And(
+                DelegateGroupsRecord.groupID == delegateGroupID).And(
+                DelegateGroupsRecord.readWrite == (1 if readWrite else 0)
+            ),
+        )
+
+
+    def removeDelegateGroups(self, delegator, readWrite):
+        &quot;&quot;&quot;
+        Removes all rows for this delegator/readWrite combination from the
+        DELEGATE_GROUPS table.
+
+        @param delegator: the UID of the delegator
+        @type delegator: C{unicode}
+        @param readWrite: remove read and write access if True, otherwise
+            read-only access
+        @type readWrite: C{boolean}
+        &quot;&quot;&quot;
+        return DelegateGroupsRecord.deletesome(
+            self,
+            (DelegateGroupsRecord.delegator == delegator.encode(&quot;utf-8&quot;)).And(
+                DelegateGroupsRecord.readWrite == (1 if readWrite else 0)
+            ),
+        )
+
+
+    @inlineCallbacks
+    def delegates(self, delegator, readWrite, expanded=False):
+        &quot;&quot;&quot;
+        Returns the UIDs of all delegates for the given delegator.  If
+        expanded is False, only the direct delegates (users and groups)
+        are returned.  If expanded is True, the expanded membership is
+        returned, not including the groups themselves.
+
+        @param delegator: the UID of the delegator
+        @type delegator: C{unicode}
+        @param readWrite: the access-type to check for; read and write
+            access if True, otherwise read-only access
+        @type readWrite: C{boolean}
+        @returns: the UIDs of the delegates (for the specified access
+            type)
+        @rtype: a Deferred resulting in a set
+        &quot;&quot;&quot;
+        delegates = set()
+        delegatorU = delegator.encode(&quot;utf-8&quot;)
+
+        # First get the direct delegates
+        results = yield DelegateRecord.query(
+            self,
+            (DelegateRecord.delegator == delegatorU).And(
+                DelegateRecord.readWrite == (1 if readWrite else 0)
+            )
+        )
+        delegates.update([record.delegate.decode(&quot;utf-8&quot;) for record in results])
+
+        if expanded:
+            # Get those who are in groups which have been delegated to
+            results = yield DelegateGroupsRecord.indirectDelegates(
+                self, delegator, readWrite
+            )
+            # Skip the delegator if they are in one of the groups
+            delegates.update([record.memberUID.decode(&quot;utf-8&quot;) for record in results if record.memberUID != delegatorU])
+
+        else:
+            # Get the directly-delegated-to groups
+            results = yield DelegateGroupsRecord.delegateGroups(
+                self, delegator, readWrite,
+            )
+            delegates.update([record.groupUID.decode(&quot;utf-8&quot;) for record in results])
+
+        returnValue(delegates)
+
+
+    @inlineCallbacks
+    def delegators(self, delegate, readWrite):
+        &quot;&quot;&quot;
+        Returns the UIDs of all delegators which have granted access to
+        the given delegate, either directly or indirectly via groups.
+
+        @param delegate: the UID of the delegate
+        @type delegate: C{unicode}
+        @param readWrite: the access-type to check for; read and write
+            access if True, otherwise read-only access
+        @type readWrite: C{boolean}
+        @returns: the UIDs of the delegators (for the specified access
+            type)
+        @rtype: a Deferred resulting in a set
+        &quot;&quot;&quot;
+        delegators = set()
+        delegateU = delegate.encode(&quot;utf-8&quot;)
+
+        # First get the direct delegators
+        results = yield DelegateRecord.query(
+            self,
+            (DelegateRecord.delegate == delegateU).And(
+                DelegateRecord.readWrite == (1 if readWrite else 0)
+            )
+        )
+        delegators.update([record.delegator.decode(&quot;utf-8&quot;) for record in results])
+
+        # Finally get those who have delegated to groups the delegate
+        # is a member of
+        results = yield DelegateGroupsRecord.indirectDelegators(
+            self, delegate, readWrite
+        )
+        # Skip the delegator if they are in one of the groups
+        delegators.update([record.delegator.decode(&quot;utf-8&quot;) for record in results if record.delegator != delegateU])
+
+        returnValue(delegators)
+
+
+    @inlineCallbacks
+    def delegatorsToGroup(self, delegateGroupID, readWrite):
+        &quot;&quot;&quot;
+        Return the UIDs of those who have delegated to the given group with the
+        given access level.
+
+        @param delegateGroupID: the group ID of the delegate group
+        @type delegateGroupID: C{int}
+        @param readWrite: the access-type to check for; read and write
+            access if True, otherwise read-only access
+        @type readWrite: C{boolean}
+        @returns: the UIDs of the delegators (for the specified access
+            type)
+        @rtype: a Deferred resulting in a set
+
+        &quot;&quot;&quot;
+        results = yield DelegateGroupsRecord.query(
+            self,
+            (DelegateGroupsRecord.groupID == delegateGroupID).And(
+                DelegateGroupsRecord.readWrite == (1 if readWrite else 0)
+            )
+        )
+        delegators = set([record.delegator.decode(&quot;utf-8&quot;) for record in results])
+        returnValue(delegators)
+
+
+    @inlineCallbacks
+    def allGroupDelegates(self):
+        &quot;&quot;&quot;
+        Return the UIDs of all groups which have been delegated to.  Useful
+        for obtaining the set of groups which need to be synchronized from
+        the directory.
+
+        @returns: the UIDs of all delegated-to groups
+        @rtype: a Deferred resulting in a set
+        &quot;&quot;&quot;
+
+        results = yield DelegateGroupsRecord.allGroupDelegates(self)
+        delegates = set([record.groupUID.decode(&quot;utf-8&quot;) for record in results])
+
+        returnValue(delegates)
+
+
+    @inlineCallbacks
+    def externalDelegates(self):
+        &quot;&quot;&quot;
+        Returns a dictionary mapping delegate UIDs to (read-group, write-group)
+        tuples, including only those assignments that originated from the
+        directory.
+
+        @returns: dictionary mapping delegator uid to (readDelegateUID,
+            writeDelegateUID) tuples
+        @rtype: a Deferred resulting in a dictionary
+        &quot;&quot;&quot;
+        delegates = {}
+
+        # Get the externally managed delegates (which are all groups)
+        results = yield ExternalDelegateGroupsRecord.all(self)
+        for record in results:
+            delegates[record.delegator.encode(&quot;utf-8&quot;)] = (
+                record.groupUIDRead.encode(&quot;utf-8&quot;) if record.groupUIDRead else None,
+                record.groupUIDWrite.encode(&quot;utf-8&quot;) if record.groupUIDWrite else None
+            )
+
+        returnValue(delegates)
+
+
+    @inlineCallbacks
+    def assignExternalDelegates(
+        self, delegator, readDelegateGroupID, writeDelegateGroupID,
+        readDelegateUID, writeDelegateUID
+    ):
+        &quot;&quot;&quot;
+        Update the external delegate group table so we can quickly identify
+        diffs next time, and update the delegate group table itself
+
+        @param delegator
+        @type delegator: C{UUID}
+        &quot;&quot;&quot;
+
+        # Delete existing external assignments for the delegator
+        yield DelegateGroupsRecord.deletesome(
+            self,
+            (DelegateGroupsRecord.delegator == str(delegator)).And(
+                DelegateGroupsRecord.isExternal == 1
+            )
+        )
+
+        # Remove from the external comparison table
+        yield ExternalDelegateGroupsRecord.deletesome(
+            self,
+            ExternalDelegateGroupsRecord.delegator == str(delegator)
+        )
+
+        # Store new assignments in the external comparison table
+        if readDelegateUID or writeDelegateUID:
+            readDelegateForDB = (
+                readDelegateUID.encode(&quot;utf-8&quot;) if readDelegateUID else &quot;&quot;
+            )
+            writeDelegateForDB = (
+                writeDelegateUID.encode(&quot;utf-8&quot;) if writeDelegateUID else &quot;&quot;
+            )
+            yield ExternalDelegateGroupsRecord.create(
+                self,
+                delegator=str(delegator),
+                groupUIDRead=readDelegateForDB,
+                groupUIDWrite=writeDelegateForDB,
+            )
+
+        # Apply new assignments
+        if readDelegateGroupID is not None:
+            yield self.addDelegateGroup(
+                delegator, readDelegateGroupID, False, isExternal=True
+            )
+        if writeDelegateGroupID is not None:
+            yield self.addDelegateGroup(
+                delegator, writeDelegateGroupID, True, isExternal=True
+            )
</ins></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 (14416 => 14417)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql_external.py        2015-02-16 20:33:55 UTC (rev 14416)
+++ CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql_external.py        2015-02-16 20:56:21 UTC (rev 14417)
</span><span class="lines">@@ -209,7 +209,7 @@
</span><span class="cx"> 
</span><span class="cx">         results = []
</span><span class="cx">         for mapping in raw_results:
</span><del>-            child = yield cls.internalize(home, mapping)
</del><ins>+            child = yield cls.deserialize(home, mapping)
</ins><span class="cx">             results.append(child)
</span><span class="cx">         returnValue(results)
</span><span class="cx"> 
</span><span class="lines">@@ -220,7 +220,7 @@
</span><span class="cx">         mapping = yield home._txn.store().conduit.send_homechild_objectwith(home, name, resourceID, externalID, accepted)
</span><span class="cx"> 
</span><span class="cx">         if mapping:
</span><del>-            child = yield cls.internalize(home, mapping)
</del><ins>+            child = yield cls.deserialize(home, mapping)
</ins><span class="cx">             returnValue(child)
</span><span class="cx">         else:
</span><span class="cx">             returnValue(None)
</span><span class="lines">@@ -357,7 +357,7 @@
</span><span class="cx">         results = []
</span><span class="cx">         if mapping_list:
</span><span class="cx">             for mapping in mapping_list:
</span><del>-                child = yield cls.internalize(parent, mapping)
</del><ins>+                child = yield cls.deserialize(parent, mapping)
</ins><span class="cx">                 results.append(child)
</span><span class="cx">         returnValue(results)
</span><span class="cx"> 
</span><span class="lines">@@ -370,7 +370,7 @@
</span><span class="cx">         results = []
</span><span class="cx">         if mapping_list:
</span><span class="cx">             for mapping in mapping_list:
</span><del>-                child = yield cls.internalize(parent, mapping)
</del><ins>+                child = yield cls.deserialize(parent, mapping)
</ins><span class="cx">                 results.append(child)
</span><span class="cx">         returnValue(results)
</span><span class="cx"> 
</span><span class="lines">@@ -395,7 +395,7 @@
</span><span class="cx">         mapping = yield parent._txn.store().conduit.send_objectresource_objectwith(parent, name, uid, resourceID)
</span><span class="cx"> 
</span><span class="cx">         if mapping:
</span><del>-            child = yield cls.internalize(parent, mapping)
</del><ins>+            child = yield cls.deserialize(parent, mapping)
</ins><span class="cx">             returnValue(child)
</span><span class="cx">         else:
</span><span class="cx">             returnValue(None)
</span><span class="lines">@@ -421,7 +421,7 @@
</span><span class="cx">         mapping = yield parent._txn.store().conduit.send_objectresource_create(parent, name, str(component), options=options)
</span><span class="cx"> 
</span><span class="cx">         if mapping:
</span><del>-            child = yield cls.internalize(parent, mapping)
</del><ins>+            child = yield cls.deserialize(parent, mapping)
</ins><span class="cx">             returnValue(child)
</span><span class="cx">         else:
</span><span class="cx">             returnValue(None)
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboopod2podmigrationtxdavwhodelegatespy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/who/delegates.py (14416 => 14417)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/who/delegates.py        2015-02-16 20:33:55 UTC (rev 14416)
+++ CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/who/delegates.py        2015-02-16 20:56:21 UTC (rev 14417)
</span><span class="lines">@@ -353,13 +353,8 @@
</span><span class="cx"> 
</span><span class="cx">         if delegate.recordType == BaseRecordType.group:
</span><span class="cx">             # find the groupID
</span><del>-            (
-                groupID, _ignore_name, _ignore_membershipHash, _ignore_modified,
-                _ignore_extant
-            ) = yield txn.groupByUID(
-                delegate.uid
-            )
-            yield txn.addDelegateGroup(delegator.uid, groupID, readWrite)
</del><ins>+            group = yield txn.groupByUID(delegate.uid)
+            yield txn.addDelegateGroup(delegator.uid, group.groupID, readWrite)
</ins><span class="cx">         else:
</span><span class="cx">             yield txn.addDelegate(delegator.uid, delegate.uid, readWrite)
</span><span class="cx"> 
</span><span class="lines">@@ -393,13 +388,8 @@
</span><span class="cx"> 
</span><span class="cx">         if delegate.recordType == BaseRecordType.group:
</span><span class="cx">             # find the groupID
</span><del>-            (
-                groupID, _ignore_name, _ignore_membershipHash, _ignore_modified,
-                _ignore_extant
-            ) = yield txn.groupByUID(
-                delegate.uid
-            )
-            yield txn.removeDelegateGroup(delegator.uid, groupID, readWrite)
</del><ins>+            group = yield txn.groupByUID(delegate.uid)
+            yield txn.removeDelegateGroup(delegator.uid, group.groupID, readWrite)
</ins><span class="cx">         else:
</span><span class="cx">             yield txn.removeDelegate(delegator.uid, delegate.uid, readWrite)
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboopod2podmigrationtxdavwhogroupspy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/who/groups.py (14416 => 14417)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/who/groups.py        2015-02-16 20:33:55 UTC (rev 14416)
+++ CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/who/groups.py        2015-02-16 20:56:21 UTC (rev 14417)
</span><span class="lines">@@ -85,7 +85,7 @@
</span><span class="cx"> 
</span><span class="cx"> class GroupRefreshWork(AggregatedWorkItem, fromTable(schema.GROUP_REFRESH_WORK)):
</span><span class="cx"> 
</span><del>-    group = property(lambda self: (self.table.GROUP_UID == self.groupUid))
</del><ins>+    group = property(lambda self: (self.table.GROUP_UID == self.groupUID))
</ins><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def doWork(self):
</span><span class="lines">@@ -94,27 +94,27 @@
</span><span class="cx"> 
</span><span class="cx">             try:
</span><span class="cx">                 yield groupCacher.refreshGroup(
</span><del>-                    self.transaction, self.groupUid.decode(&quot;utf-8&quot;)
</del><ins>+                    self.transaction, self.groupUID.decode(&quot;utf-8&quot;)
</ins><span class="cx">                 )
</span><span class="cx">             except Exception, e:
</span><span class="cx">                 log.error(
</span><span class="cx">                     &quot;Failed to refresh group {group} {err}&quot;,
</span><del>-                    group=self.groupUid, err=e
</del><ins>+                    group=self.groupUID, err=e
</ins><span class="cx">                 )
</span><span class="cx"> 
</span><span class="cx">         else:
</span><span class="cx">             log.debug(
</span><span class="cx">                 &quot;Rescheduling group refresh for {group}: {when}&quot;,
</span><del>-                group=self.groupUid,
</del><ins>+                group=self.groupUID,
</ins><span class="cx">                 when=datetime.datetime.utcnow() + datetime.timedelta(seconds=10)
</span><span class="cx">             )
</span><del>-            yield self.reschedule(self.transaction, 10, groupUID=self.groupUid)
</del><ins>+            yield self.reschedule(self.transaction, 10, groupUID=self.groupUID)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> class GroupDelegateChangesWork(AggregatedWorkItem, fromTable(schema.GROUP_DELEGATE_CHANGES_WORK)):
</span><span class="cx"> 
</span><del>-    delegator = property(lambda self: (self.table.DELEGATOR_UID == self.delegatorUid))
</del><ins>+    delegator = property(lambda self: (self.table.DELEGATOR_UID == self.delegatorUID))
</ins><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def doWork(self):
</span><span class="lines">@@ -124,14 +124,14 @@
</span><span class="cx">             try:
</span><span class="cx">                 yield groupCacher.applyExternalAssignments(
</span><span class="cx">                     self.transaction,
</span><del>-                    self.delegatorUid.decode(&quot;utf-8&quot;),
-                    self.readDelegateUid.decode(&quot;utf-8&quot;),
-                    self.writeDelegateUid.decode(&quot;utf-8&quot;)
</del><ins>+                    self.delegatorUID.decode(&quot;utf-8&quot;),
+                    self.readDelegateUID.decode(&quot;utf-8&quot;),
+                    self.writeDelegateUID.decode(&quot;utf-8&quot;)
</ins><span class="cx">                 )
</span><span class="cx">             except Exception, e:
</span><span class="cx">                 log.error(
</span><span class="cx">                     &quot;Failed to apply external delegates for {uid} {err}&quot;,
</span><del>-                    uid=self.delegatorUid, err=e
</del><ins>+                    uid=self.delegatorUID, err=e
</ins><span class="cx">                 )
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -182,8 +182,8 @@
</span><span class="cx">             homeID = rows[0][0]
</span><span class="cx">             home = yield self.transaction.calendarHomeWithResourceID(homeID)
</span><span class="cx">             calendar = yield home.childWithID(self.calendarID)
</span><del>-            groupUID = ((yield self.transaction.groupByID(self.groupID)))[0]
-            yield calendar.reconcileGroupSharee(groupUID)
</del><ins>+            group = (yield self.transaction.groupByID(self.groupID))
+            yield calendar.reconcileGroupSharee(group.groupUID)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -302,7 +302,7 @@
</span><span class="cx">         # For each of those groups, create a per-group refresh work item
</span><span class="cx">         for groupUID in set(groupUIDs) - set(deletedGroupUIDs):
</span><span class="cx">             self.log.debug(&quot;Enqueuing group refresh for {u}&quot;, u=groupUID)
</span><del>-            yield GroupRefreshWork.reschedule(txn, 0, groupUid=groupUID)
</del><ins>+            yield GroupRefreshWork.reschedule(txn, 0, groupUID=groupUID)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -335,9 +335,9 @@
</span><span class="cx">                     )
</span><span class="cx">                 else:
</span><span class="cx">                     yield GroupDelegateChangesWork.reschedule(
</span><del>-                        txn, 0, delegatorUid=delegatorUID,
-                        readDelegateUid=readDelegateUID,
-                        writeDelegateUid=writeDelegateUID
</del><ins>+                        txn, 0, delegatorUID=delegatorUID,
+                        readDelegateUID=readDelegateUID,
+                        writeDelegateUID=writeDelegateUID
</ins><span class="cx">                     )
</span><span class="cx">         if removed:
</span><span class="cx">             for delegatorUID in removed:
</span><span class="lines">@@ -351,8 +351,8 @@
</span><span class="cx">                     )
</span><span class="cx">                 else:
</span><span class="cx">                     yield GroupDelegateChangesWork.reschedule(
</span><del>-                        txn, 0, delegatorUid=delegatorUID,
-                        readDelegateUid=&quot;&quot;, writeDelegateUid=&quot;&quot;
</del><ins>+                        txn, 0, delegatorUID=delegatorUID,
+                        readDelegateUID=&quot;&quot;, writeDelegateUID=&quot;&quot;
</ins><span class="cx">                     )
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -367,26 +367,20 @@
</span><span class="cx">         readDelegateGroupID = writeDelegateGroupID = None
</span><span class="cx"> 
</span><span class="cx">         if readDelegateUID:
</span><del>-            (
-                readDelegateGroupID, _ignore_name, _ignore_hash,
-                _ignore_modified, _ignore_extant
-            ) = (
-                yield txn.groupByUID(readDelegateUID)
-            )
-            if readDelegateGroupID is None:
</del><ins>+            readDelegateGroup = yield txn.groupByUID(readDelegateUID)
+            if readDelegateGroup is None:
</ins><span class="cx">                 # The group record does not actually exist
</span><span class="cx">                 readDelegateUID = None
</span><ins>+            else:
+                readDelegateGroupID = readDelegateGroup.groupID
</ins><span class="cx"> 
</span><span class="cx">         if writeDelegateUID:
</span><del>-            (
-                writeDelegateGroupID, _ignore_name, _ignore_hash,
-                _ignore_modified, _ignore_extant
-            ) = (
-                yield txn.groupByUID(writeDelegateUID)
-            )
-            if writeDelegateGroupID is None:
</del><ins>+            writeDelegateGroup = yield txn.groupByUID(writeDelegateUID)
+            if writeDelegateGroup is None:
</ins><span class="cx">                 # The group record does not actually exist
</span><span class="cx">                 writeDelegateUID = None
</span><ins>+            else:
+                writeDelegateGroupID = writeDelegateGroup.groupID
</ins><span class="cx"> 
</span><span class="cx">         yield txn.assignExternalDelegates(
</span><span class="cx">             delegatorUID, readDelegateGroupID, writeDelegateGroupID,
</span><span class="lines">@@ -411,45 +405,36 @@
</span><span class="cx">         else:
</span><span class="cx">             self.log.debug(&quot;Got group record: {u}&quot;, u=record.uid)
</span><span class="cx"> 
</span><del>-        (
-            groupID, cachedName, cachedMembershipHash, _ignore_modified,
-            cachedExtant
-        ) = yield txn.groupByUID(
-            groupUID,
-            create=(record is not None)
-        )
</del><ins>+        group = yield txn.groupByUID(groupUID, create=(record is not None))
</ins><span class="cx"> 
</span><del>-        if groupID:
-            membershipChanged, addedUIDs, removedUIDs = yield txn.refreshGroup(
-                groupUID, record, groupID,
-                cachedName, cachedMembershipHash, cachedExtant
-            )
</del><ins>+        if group:
+            membershipChanged, addedUIDs, removedUIDs = yield txn.refreshGroup(group, record)
</ins><span class="cx"> 
</span><span class="cx">             if membershipChanged:
</span><span class="cx">                 self.log.info(
</span><span class="cx">                     &quot;Membership changed for group {uid} {name}:\n\tadded {added}\n\tremoved {removed}&quot;,
</span><del>-                    uid=groupUID,
-                    name=cachedName,
</del><ins>+                    uid=group.groupUID,
+                    name=group.name,
</ins><span class="cx">                     added=&quot;,&quot;.join(addedUIDs),
</span><span class="cx">                     removed=&quot;,&quot;.join(removedUIDs),
</span><span class="cx">                 )
</span><span class="cx"> 
</span><span class="cx">                 # Send cache change notifications
</span><span class="cx">                 if self.cacheNotifier is not None:
</span><del>-                    self.cacheNotifier.changed(groupUID)
</del><ins>+                    self.cacheNotifier.changed(group.groupUID)
</ins><span class="cx">                     for uid in itertools.chain(addedUIDs, removedUIDs):
</span><span class="cx">                         self.cacheNotifier.changed(uid)
</span><span class="cx"> 
</span><span class="cx">                 # Notifier other store APIs of changes
</span><del>-                wpsAttendee = yield self.scheduleGroupAttendeeReconciliations(txn, groupID)
-                wpsShareee = yield self.scheduleGroupShareeReconciliations(txn, groupID)
</del><ins>+                wpsAttendee = yield self.scheduleGroupAttendeeReconciliations(txn, group.groupID)
+                wpsShareee = yield self.scheduleGroupShareeReconciliations(txn, group.groupID)
</ins><span class="cx"> 
</span><span class="cx">                 returnValue(wpsAttendee + wpsShareee)
</span><span class="cx">             else:
</span><span class="cx">                 self.log.debug(
</span><span class="cx">                     &quot;No membership change for group {uid} {name}&quot;,
</span><del>-                    uid=groupUID,
-                    name=cachedName
</del><ins>+                    uid=group.groupUID,
+                    name=group.name
</ins><span class="cx">                 )
</span><span class="cx"> 
</span><span class="cx">         returnValue(tuple())
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboopod2podmigrationtxdavwhotesttest_delegatespy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/who/test/test_delegates.py (14416 => 14417)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/who/test/test_delegates.py        2015-02-16 20:33:55 UTC (rev 14416)
+++ CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/who/test/test_delegates.py        2015-02-16 20:56:21 UTC (rev 14417)
</span><span class="lines">@@ -19,6 +19,8 @@
</span><span class="cx"> &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx"> from txdav.common.datastore.sql import CommonStoreTransaction
</span><ins>+from txdav.common.datastore.sql_directory import DelegateRecord, \
+    DelegateGroupsRecord
</ins><span class="cx"> from txdav.who.delegates import Delegates, RecordType as DelegateRecordType
</span><span class="cx"> from txdav.who.groups import GroupCacher
</span><span class="cx"> from twext.who.idirectory import RecordType
</span><span class="lines">@@ -211,12 +213,9 @@
</span><span class="cx">                 yield self.directory.recordWithShortName(RecordType.user, name)
</span><span class="cx">             )
</span><span class="cx">             newSet.add(record.uid)
</span><del>-        (
-            groupID, name, _ignore_membershipHash, _ignore_modified,
-            _ignore_extant
-        ) = (yield txn.groupByUID(group1.uid))
</del><ins>+        group = yield txn.groupByUID(group1.uid)
</ins><span class="cx">         _ignore_added, _ignore_removed = (
</span><del>-            yield self.groupCacher.synchronizeMembers(txn, groupID, newSet)
</del><ins>+            yield self.groupCacher.synchronizeMembers(txn, group.groupID, newSet)
</ins><span class="cx">         )
</span><span class="cx">         delegates = (yield Delegates.delegatesOf(txn, delegator, True, expanded=True))
</span><span class="cx">         self.assertEquals(
</span><span class="lines">@@ -261,15 +260,14 @@
</span><span class="cx">         yield txn.commit()
</span><span class="cx"> 
</span><span class="cx">         txn = self.store.newTransaction(label=&quot;test_noDuplication&quot;)
</span><del>-        results = (
-            yield txn._selectDelegatesQuery.on(
-                txn,
-                delegator=delegator.uid.encode(&quot;utf-8&quot;),
-                readWrite=1
</del><ins>+        results = yield DelegateRecord.query(
+            txn,
+            (DelegateRecord.delegator == delegator.uid.encode(&quot;utf-8&quot;)).And(
+                DelegateRecord.readWrite == 1
</ins><span class="cx">             )
</span><span class="cx">         )
</span><span class="cx">         yield txn.commit()
</span><del>-        self.assertEquals([[&quot;__sagen1__&quot;]], results)
</del><ins>+        self.assertEquals([&quot;__sagen1__&quot;, ], [record.delegate for record in results])
</ins><span class="cx"> 
</span><span class="cx">         # Delegate groups:
</span><span class="cx">         group1 = yield self.directory.recordWithUID(u&quot;__top_group_1__&quot;)
</span><span class="lines">@@ -283,15 +281,13 @@
</span><span class="cx">         yield txn.commit()
</span><span class="cx"> 
</span><span class="cx">         txn = self.store.newTransaction(label=&quot;test_noDuplication&quot;)
</span><del>-        results = (
-            yield txn._selectDelegateGroupsQuery.on(
-                txn,
-                delegator=delegator.uid.encode(&quot;utf-8&quot;),
-                readWrite=1
-            )
</del><ins>+        results = yield DelegateGroupsRecord.delegateGroups(
+            txn,
+            delegator.uid,
+            True,
</ins><span class="cx">         )
</span><span class="cx">         yield txn.commit()
</span><del>-        self.assertEquals([[&quot;__top_group_1__&quot;]], results)
</del><ins>+        self.assertEquals([&quot;__top_group_1__&quot;, ], [record.groupUID for record in results])
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboopod2podmigrationtxdavwhotesttest_group_attendeespy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/who/test/test_group_attendees.py (14416 => 14417)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/who/test/test_group_attendees.py        2015-02-16 20:33:55 UTC (rev 14416)
+++ CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/who/test/test_group_attendees.py        2015-02-16 20:56:21 UTC (rev 14417)
</span><span class="lines">@@ -871,14 +871,11 @@
</span><span class="cx">         # finally, simulate an event that has become old
</span><span class="cx">         self.patch(CalendarDirectoryRecordMixin, &quot;expandedMembers&quot;, unpatchedExpandedMembers)
</span><span class="cx"> 
</span><del>-        (
-            groupID, _ignore_name, _ignore_membershipHash, _ignore_modDate,
-            _ignore_extant
-        ) = yield self.transactionUnderTest().groupByUID(&quot;group01&quot;)
</del><ins>+        group = yield self.transactionUnderTest().groupByUID(&quot;group01&quot;)
</ins><span class="cx">         ga = schema.GROUP_ATTENDEE
</span><span class="cx">         yield Insert({
</span><span class="cx">             ga.RESOURCE_ID: cobj._resourceID,
</span><del>-            ga.GROUP_ID: groupID,
</del><ins>+            ga.GROUP_ID: group.groupID,
</ins><span class="cx">             ga.MEMBERSHIP_HASH: (-1),
</span><span class="cx">         }).on(self.transactionUnderTest())
</span><span class="cx">         wps = yield groupCacher.refreshGroup(self.transactionUnderTest(), &quot;group01&quot;)
</span><span class="lines">@@ -1033,14 +1030,11 @@
</span><span class="cx">         # finally, simulate an event that has become old
</span><span class="cx">         self.patch(CalendarDirectoryRecordMixin, &quot;expandedMembers&quot;, unpatchedExpandedMembers)
</span><span class="cx"> 
</span><del>-        (
-            groupID, _ignore_name, _ignore_membershipHash, _ignore_modDate,
-            _ignore_extant
-        ) = yield self.transactionUnderTest().groupByUID(&quot;group01&quot;)
</del><ins>+        group = yield self.transactionUnderTest().groupByUID(&quot;group01&quot;)
</ins><span class="cx">         ga = schema.GROUP_ATTENDEE
</span><span class="cx">         yield Insert({
</span><span class="cx">             ga.RESOURCE_ID: cobj._resourceID,
</span><del>-            ga.GROUP_ID: groupID,
</del><ins>+            ga.GROUP_ID: group.groupID,
</ins><span class="cx">             ga.MEMBERSHIP_HASH: (-1),
</span><span class="cx">         }).on(self.transactionUnderTest())
</span><span class="cx">         wps = yield groupCacher.refreshGroup(self.transactionUnderTest(), &quot;group01&quot;)
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboopod2podmigrationtxdavwhotesttest_groupspy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/who/test/test_groups.py (14416 => 14417)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/who/test/test_groups.py        2015-02-16 20:33:55 UTC (rev 14416)
+++ CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/who/test/test_groups.py        2015-02-16 20:56:21 UTC (rev 14417)
</span><span class="lines">@@ -67,27 +67,24 @@
</span><span class="cx">         record = yield self.directory.recordWithUID(u&quot;__top_group_1__&quot;)
</span><span class="cx">         yield self.groupCacher.refreshGroup(txn, record.uid)
</span><span class="cx"> 
</span><del>-        (
-            groupID, _ignore_name, membershipHash, _ignore_modified,
-            extant
-        ) = (yield txn.groupByUID(record.uid))
</del><ins>+        group = (yield txn.groupByUID(record.uid))
</ins><span class="cx"> 
</span><del>-        self.assertEquals(extant, True)
-        self.assertEquals(membershipHash, &quot;553eb54e3bbb26582198ee04541dbee4&quot;)
</del><ins>+        self.assertEquals(group.extant, True)
+        self.assertEquals(group.membershipHash, &quot;553eb54e3bbb26582198ee04541dbee4&quot;)
</ins><span class="cx"> 
</span><del>-        groupUID, name, membershipHash, extant = (yield txn.groupByID(groupID))
-        self.assertEquals(groupUID, record.uid)
-        self.assertEquals(name, u&quot;Top Group 1&quot;)
-        self.assertEquals(membershipHash, &quot;553eb54e3bbb26582198ee04541dbee4&quot;)
-        self.assertEquals(extant, True)
</del><ins>+        group = yield txn.groupByID(group.groupID)
+        self.assertEquals(group.groupUID, record.uid)
+        self.assertEquals(group.name, u&quot;Top Group 1&quot;)
+        self.assertEquals(group.membershipHash, &quot;553eb54e3bbb26582198ee04541dbee4&quot;)
+        self.assertEquals(group.extant, True)
</ins><span class="cx"> 
</span><del>-        members = (yield txn.groupMemberUIDs(groupID))
</del><ins>+        members = (yield txn.groupMemberUIDs(group.groupID))
</ins><span class="cx">         self.assertEquals(
</span><span class="cx">             set([u'__cdaboo1__', u'__glyph1__', u'__sagen1__', u'__wsanchez1__']),
</span><span class="cx">             members
</span><span class="cx">         )
</span><span class="cx"> 
</span><del>-        records = (yield self.groupCacher.cachedMembers(txn, groupID))
</del><ins>+        records = (yield self.groupCacher.cachedMembers(txn, group.groupID))
</ins><span class="cx">         self.assertEquals(
</span><span class="cx">             set([r.uid for r in records]),
</span><span class="cx">             set([u'__cdaboo1__', u'__glyph1__', u'__sagen1__', u'__wsanchez1__'])
</span><span class="lines">@@ -116,10 +113,7 @@
</span><span class="cx">         # Refresh the group so it's assigned a group_id
</span><span class="cx">         uid = u&quot;__top_group_1__&quot;
</span><span class="cx">         yield self.groupCacher.refreshGroup(txn, uid)
</span><del>-        (
-            groupID, name, _ignore_membershipHash, _ignore_modified,
-            _ignore_extant
-        ) = yield txn.groupByUID(uid)
</del><ins>+        group = yield txn.groupByUID(uid)
</ins><span class="cx"> 
</span><span class="cx">         # Remove two members, and add one member
</span><span class="cx">         newSet = set()
</span><span class="lines">@@ -133,12 +127,12 @@
</span><span class="cx">             newSet.add(record.uid)
</span><span class="cx">         added, removed = (
</span><span class="cx">             yield self.groupCacher.synchronizeMembers(
</span><del>-                txn, groupID, newSet
</del><ins>+                txn, group.groupID, newSet
</ins><span class="cx">             )
</span><span class="cx">         )
</span><span class="cx">         self.assertEquals(added, set([&quot;__dre1__&quot;, ]))
</span><span class="cx">         self.assertEquals(removed, set([&quot;__glyph1__&quot;, &quot;__sagen1__&quot;, ]))
</span><del>-        records = (yield self.groupCacher.cachedMembers(txn, groupID))
</del><ins>+        records = (yield self.groupCacher.cachedMembers(txn, group.groupID))
</ins><span class="cx">         self.assertEquals(
</span><span class="cx">             set([r.shortNames[0] for r in records]),
</span><span class="cx">             set([&quot;wsanchez1&quot;, &quot;cdaboo1&quot;, &quot;dre1&quot;])
</span><span class="lines">@@ -146,11 +140,11 @@
</span><span class="cx"> 
</span><span class="cx">         # Remove all members
</span><span class="cx">         added, removed = (
</span><del>-            yield self.groupCacher.synchronizeMembers(txn, groupID, set())
</del><ins>+            yield self.groupCacher.synchronizeMembers(txn, group.groupID, set())
</ins><span class="cx">         )
</span><span class="cx">         self.assertEquals(added, set())
</span><span class="cx">         self.assertEquals(removed, set([&quot;__wsanchez1__&quot;, &quot;__cdaboo1__&quot;, &quot;__dre1__&quot;, ]))
</span><del>-        records = (yield self.groupCacher.cachedMembers(txn, groupID))
</del><ins>+        records = (yield self.groupCacher.cachedMembers(txn, group.groupID))
</ins><span class="cx">         self.assertEquals(len(records), 0)
</span><span class="cx"> 
</span><span class="cx">         yield txn.commit()
</span><span class="lines">@@ -168,12 +162,12 @@
</span><span class="cx">         uid = u&quot;__top_group_1__&quot;
</span><span class="cx">         hash = &quot;553eb54e3bbb26582198ee04541dbee4&quot;
</span><span class="cx">         yield self.groupCacher.refreshGroup(txn, uid)
</span><del>-        (
-            groupID, _ignore_name, _ignore_membershipHash, _ignore_modified,
-            _ignore_extant
-        ) = yield txn.groupByUID(uid)
-        results = yield txn.groupByID(groupID)
-        self.assertEquals((uid, u&quot;Top Group 1&quot;, hash, True), results)
</del><ins>+        group = yield txn.groupByUID(uid)
+        group = yield txn.groupByID(group.groupID)
+        self.assertEqual(group.groupUID, uid)
+        self.assertEqual(group.name, u&quot;Top Group 1&quot;)
+        self.assertEqual(group.membershipHash, hash)
+        self.assertEqual(group.extant, True)
</ins><span class="cx"> 
</span><span class="cx">         yield txn.commit()
</span><span class="cx"> 
</span><span class="lines">@@ -683,31 +677,25 @@
</span><span class="cx"> 
</span><span class="cx">             txn = store.newTransaction()
</span><span class="cx">             yield self.groupCacher.refreshGroup(txn, uid)
</span><del>-            (
-                _ignore_groupID, _ignore_name, _ignore_membershipHash, _ignore_modified,
-                extant
-            ) = (yield txn.groupByUID(uid))
</del><ins>+            group = yield txn.groupByUID(uid)
</ins><span class="cx">             yield txn.commit()
</span><span class="cx"> 
</span><del>-            self.assertTrue(extant)
</del><ins>+            self.assertTrue(group.extant)
</ins><span class="cx"> 
</span><span class="cx">             # Remove the group
</span><span class="cx">             yield self.directory.removeRecords([uid])
</span><span class="cx"> 
</span><span class="cx">             txn = store.newTransaction()
</span><span class="cx">             yield self.groupCacher.refreshGroup(txn, uid)
</span><del>-            (
-                groupID, _ignore_name, _ignore_membershipHash, _ignore_modified,
-                extant
-            ) = (yield txn.groupByUID(uid))
</del><ins>+            group = (yield txn.groupByUID(uid))
</ins><span class="cx">             yield txn.commit()
</span><span class="cx"> 
</span><span class="cx">             # Extant = False
</span><del>-            self.assertFalse(extant)
</del><ins>+            self.assertFalse(group.extant)
</ins><span class="cx"> 
</span><span class="cx">             # The list of members stored in the DB for this group is now empty
</span><span class="cx">             txn = store.newTransaction()
</span><del>-            members = yield txn.groupMemberUIDs(groupID)
</del><ins>+            members = yield txn.groupMemberUIDs(group.groupID)
</ins><span class="cx">             yield txn.commit()
</span><span class="cx">             self.assertEquals(members, set())
</span><span class="cx"> 
</span><span class="lines">@@ -732,18 +720,15 @@
</span><span class="cx"> 
</span><span class="cx">             txn = store.newTransaction()
</span><span class="cx">             yield self.groupCacher.refreshGroup(txn, uid)
</span><del>-            (
-                groupID, _ignore_name, _ignore_membershipHash, _ignore_modified,
-                extant
-            ) = (yield txn.groupByUID(uid))
</del><ins>+            group = (yield txn.groupByUID(uid))
</ins><span class="cx">             yield txn.commit()
</span><span class="cx"> 
</span><span class="cx">             # Extant = True
</span><del>-            self.assertTrue(extant)
</del><ins>+            self.assertTrue(group.extant)
</ins><span class="cx"> 
</span><span class="cx">             # The list of members stored in the DB for this group has 100 users
</span><span class="cx">             txn = store.newTransaction()
</span><del>-            members = yield txn.groupMemberUIDs(groupID)
</del><ins>+            members = yield txn.groupMemberUIDs(group.groupID)
</ins><span class="cx">             yield txn.commit()
</span><span class="cx">             self.assertEquals(len(members), 100 if uid == u&quot;testgroup&quot; else 0)
</span><span class="cx"> 
</span><span class="lines">@@ -760,27 +745,27 @@
</span><span class="cx"> 
</span><span class="cx">             txn = store.newTransaction()
</span><span class="cx">             yield self.groupCacher.refreshGroup(txn, uid)
</span><del>-            groupID = (yield txn.groupByUID(uid, create=False))[0]
</del><ins>+            group = yield txn.groupByUID(uid, create=False)
</ins><span class="cx">             yield txn.commit()
</span><span class="cx"> 
</span><del>-            self.assertNotEqual(groupID, None)
</del><ins>+            self.assertNotEqual(group, None)
</ins><span class="cx"> 
</span><span class="cx">             txn = store.newTransaction()
</span><span class="cx">             yield self.groupCacher.update(txn)
</span><del>-            groupID = (yield txn.groupByUID(uid, create=False))[0]
</del><ins>+            group = yield txn.groupByUID(uid, create=False)
</ins><span class="cx">             yield txn.commit()
</span><span class="cx"> 
</span><del>-            self.assertEqual(groupID, None)
</del><ins>+            self.assertEqual(group, None)
</ins><span class="cx"> 
</span><span class="cx">         # delegate groups not deleted
</span><span class="cx">         for uid in (u&quot;testgroup&quot;, u&quot;emptygroup&quot;,):
</span><span class="cx"> 
</span><span class="cx">             txn = store.newTransaction()
</span><del>-            groupID = (yield txn.groupByUID(uid))[0]
-            yield txn.addDelegateGroup(delegator=u&quot;sagen&quot;, delegateGroupID=groupID, readWrite=True)
</del><ins>+            group = yield txn.groupByUID(uid)
+            yield txn.addDelegateGroup(delegator=u&quot;sagen&quot;, delegateGroupID=group.groupID, readWrite=True)
</ins><span class="cx">             yield txn.commit()
</span><span class="cx"> 
</span><del>-            self.assertNotEqual(groupID, None)
</del><ins>+            self.assertNotEqual(group, None)
</ins><span class="cx"> 
</span><span class="cx">             txn = store.newTransaction()
</span><span class="cx">             yield self.groupCacher.update(txn)
</span><span class="lines">@@ -788,21 +773,21 @@
</span><span class="cx">             yield JobItem.waitEmpty(store.newTransaction, reactor, 60)
</span><span class="cx"> 
</span><span class="cx">             txn = store.newTransaction()
</span><del>-            groupID = (yield txn.groupByUID(uid, create=False))[0]
</del><ins>+            group = yield txn.groupByUID(uid, create=False)
</ins><span class="cx">             yield txn.commit()
</span><span class="cx"> 
</span><del>-            self.assertNotEqual(groupID, None)
</del><ins>+            self.assertNotEqual(group, None)
</ins><span class="cx"> 
</span><span class="cx">         # delegate group is deleted. unused group is deleted
</span><span class="cx">         txn = store.newTransaction()
</span><del>-        testGroupID = (yield txn.groupByUID(u&quot;testgroup&quot;, create=False))[0]
-        yield txn.removeDelegateGroup(delegator=u&quot;sagen&quot;, delegateGroupID=testGroupID, readWrite=True)
-        testGroupID = (yield txn.groupByUID(u&quot;testgroup&quot;, create=False))[0]
-        emptyGroupID = (yield txn.groupByUID(u&quot;emptygroup&quot;, create=False))[0]
</del><ins>+        testGroup = yield txn.groupByUID(u&quot;testgroup&quot;, create=False)
+        yield txn.removeDelegateGroup(delegator=u&quot;sagen&quot;, delegateGroupID=testGroup.groupID, readWrite=True)
+        testGroup = yield txn.groupByUID(u&quot;testgroup&quot;, create=False)
+        emptyGroup = yield txn.groupByUID(u&quot;emptygroup&quot;, create=False)
</ins><span class="cx">         yield txn.commit()
</span><span class="cx"> 
</span><del>-        self.assertNotEqual(testGroupID, None)
-        self.assertNotEqual(emptyGroupID, None)
</del><ins>+        self.assertNotEqual(testGroup, None)
+        self.assertNotEqual(emptyGroup, None)
</ins><span class="cx"> 
</span><span class="cx">         txn = store.newTransaction()
</span><span class="cx">         yield self.groupCacher.update(txn)
</span><span class="lines">@@ -810,12 +795,12 @@
</span><span class="cx">         yield JobItem.waitEmpty(store.newTransaction, reactor, 60)
</span><span class="cx"> 
</span><span class="cx">         txn = store.newTransaction()
</span><del>-        testGroupID = (yield txn.groupByUID(u&quot;testgroup&quot;, create=False))[0]
-        emptyGroupID = (yield txn.groupByUID(u&quot;emptygroup&quot;, create=False))[0]
</del><ins>+        testGroup = yield txn.groupByUID(u&quot;testgroup&quot;, create=False)
+        emptyGroup = yield txn.groupByUID(u&quot;emptygroup&quot;, create=False)
</ins><span class="cx">         yield txn.commit()
</span><span class="cx"> 
</span><del>-        self.assertEqual(testGroupID, None)
-        self.assertNotEqual(emptyGroupID, None)
</del><ins>+        self.assertEqual(testGroup, None)
+        self.assertNotEqual(emptyGroup, None)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -831,42 +816,33 @@
</span><span class="cx"> 
</span><span class="cx">             config.AutomaticPurging.GroupPurgeIntervalSeconds = oldGroupPurgeIntervalSeconds
</span><span class="cx">             txn = store.newTransaction()
</span><del>-            groupID = (yield txn.groupByUID(uid))[0]
-            yield txn.addDelegateGroup(delegator=u&quot;sagen&quot;, delegateGroupID=groupID, readWrite=True)
-            (
-                groupID, _ignore_name, _ignore_membershipHash, _ignore_modified,
-                extant
-            ) = yield txn.groupByUID(uid, create=False)
</del><ins>+            group = yield txn.groupByUID(uid)
+            yield txn.addDelegateGroup(delegator=u&quot;sagen&quot;, delegateGroupID=group.groupID, readWrite=True)
+            group = yield txn.groupByUID(uid, create=False)
</ins><span class="cx">             yield txn.commit()
</span><span class="cx"> 
</span><del>-            self.assertTrue(extant)
-            self.assertNotEqual(groupID, None)
</del><ins>+            self.assertNotEqual(group, None)
+            self.assertTrue(group.extant)
</ins><span class="cx"> 
</span><span class="cx">             # Remove the group, still cached
</span><span class="cx">             yield self.directory.removeRecords([uid])
</span><span class="cx">             txn = store.newTransaction()
</span><span class="cx">             yield self.groupCacher.update(txn)
</span><del>-            (
-                groupID, _ignore_name, _ignore_membershipHash, _ignore_modified,
-                extant
-            ) = yield txn.groupByUID(uid, create=False)
</del><ins>+            group = yield txn.groupByUID(uid, create=False)
</ins><span class="cx">             yield txn.commit()
</span><span class="cx">             yield JobItem.waitEmpty(store.newTransaction, reactor, 60)
</span><span class="cx"> 
</span><span class="cx">             txn = store.newTransaction()
</span><del>-            (
-                groupID, _ignore_name, _ignore_membershipHash, _ignore_modified,
-                extant
-            ) = yield txn.groupByUID(uid, create=False)
</del><ins>+            group = yield txn.groupByUID(uid, create=False)
</ins><span class="cx">             yield txn.commit()
</span><del>-            self.assertNotEqual(groupID, None)
-            self.assertFalse(extant)
</del><ins>+            self.assertNotEqual(group, None)
+            self.assertFalse(group.extant)
</ins><span class="cx"> 
</span><span class="cx">             # delete the group
</span><span class="cx">             config.AutomaticPurging.GroupPurgeIntervalSeconds = &quot;0.0&quot;
</span><span class="cx"> 
</span><span class="cx">             txn = store.newTransaction()
</span><span class="cx">             yield self.groupCacher.update(txn)
</span><del>-            groupID = (yield txn.groupByUID(uid, create=False))[0]
</del><ins>+            group = yield txn.groupByUID(uid, create=False)
</ins><span class="cx">             yield txn.commit()
</span><del>-            self.assertEqual(groupID, None)
</del><ins>+            self.assertEqual(group, None)
</ins></span></pre>
</div>
</div>

</body>
</html>