<!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>[12051] CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav</title>
</head>
<body>
<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; }
#msg dl a { font-weight: bold}
#msg dl a:link { color:#fc3; }
#msg dl a:active { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://trac.calendarserver.org//changeset/12051">12051</a></dd>
<dt>Author</dt> <dd>cdaboo@apple.com</dd>
<dt>Date</dt> <dd>2013-12-11 14:16:01 -0800 (Wed, 11 Dec 2013)</dd>
</dl>
<h3>Log Message</h3>
<pre>Check point. Implemented "external" classes that can proxy various apis through the conduit to get results
from another pod.</pre>
<h3>Modified Paths</h3>
<ul>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtxdavcaldavdatastoresqlpy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/sql.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtxdavcaldavdatastoretestcommonpy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/test/common.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtxdavcaldavdatastoretesttest_sqlpy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/test/test_sql.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtxdavcaldavdatastoretesttest_utilpy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/test/test_util.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtxdavcaldavdatastoretestutilpy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/test/util.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtxdavcarddavdatastoresqlpy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/sql.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtxdavcarddavdatastoretesttest_sqlpy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/test/test_sql.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastorepoddingconduitpy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/conduit.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastorepoddingrequestpy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/request.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastorepoddingresourcepy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/resource.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastorepoddingtesttest_conduitpy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/test/test_conduit.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastorepoddingtesttest_external_homepy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/test/test_external_home.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastorepoddingtesttest_resourcepy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/test/test_resource.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastoresqlpy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/sql.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastoretesttest_sqlpy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/test/test_sql.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastoretestutilpy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/test/util.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastoreupgradetesttest_migratepy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/upgrade/test/test_migrate.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommonicommondatastorepy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/icommondatastore.py</a></li>
</ul>
<h3>Added Paths</h3>
<ul>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtxdavcaldavdatastoresql_externalpy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/sql_external.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtxdavcarddavdatastoresql_externalpy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/sql_external.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastorepoddingtestutilpy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/test/util.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastoresql_externalpy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/sql_external.py</a></li>
</ul>
</div>
<div id="patch">
<h3>Diff</h3>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtxdavcaldavdatastoresqlpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/sql.py (12050 => 12051)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/sql.py        2013-12-11 15:40:55 UTC (rev 12050)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/sql.py        2013-12-11 22:16:01 UTC (rev 12051)
</span><span class="lines">@@ -75,7 +75,7 @@
</span><span class="cx"> InvalidAttachmentOperation, DuplicatePrivateCommentsError
</span><span class="cx"> from txdav.caldav.icalendarstore import QuotaExceeded
</span><span class="cx"> from txdav.common.datastore.sql import CommonHome, CommonHomeChild, \
</span><del>- CommonObjectResource, ECALENDARTYPE, CommonHomeExternal
</del><ins>+ CommonObjectResource, ECALENDARTYPE
</ins><span class="cx"> from txdav.common.datastore.sql_legacy import PostgresLegacyIndexEmulator, \
</span><span class="cx"> PostgresLegacyInboxIndexEmulator
</span><span class="cx"> from txdav.common.datastore.sql_tables import _ATTACHMENTS_MODE_NONE, \
</span><span class="lines">@@ -431,13 +431,6 @@
</span><span class="cx"> "VPOLL": "_default_polls",
</span><span class="cx"> }
</span><span class="cx">
</span><del>- def __init__(self, transaction, ownerUID):
-
- self._externalClass = CalendarHomeExternal
- self._childClass = Calendar
- super(CalendarHome, self).__init__(transaction, ownerUID)
-
-
</del><span class="cx"> @classmethod
</span><span class="cx"> def metadataColumns(cls):
</span><span class="cx"> """
</span><span class="lines">@@ -941,127 +934,6 @@
</span><span class="cx">
</span><span class="cx">
</span><span class="cx">
</span><del>-class CalendarHomeExternal(CommonHomeExternal, CalendarHome):
-
- def __init__(self, transaction, ownerUID, resourceID):
-
- CalendarHome.__init__(self, transaction, ownerUID)
- CommonHomeExternal.__init__(self, transaction, ownerUID, resourceID)
-
-
- def hasCalendarResourceUIDSomewhereElse(self, uid, ok_object, mode):
- """
- No children.
- """
- raise AssertionError("CommonHomeExternal: not supported")
-
-
- def getCalendarResourcesForUID(self, uid):
- """
- No children.
- """
- raise AssertionError("CommonHomeExternal: not supported")
-
-
- def calendarObjectWithDropboxID(self, dropboxID):
- """
- No children.
- """
- raise AssertionError("CommonHomeExternal: not supported")
-
-
- def getAllDropboxIDs(self):
- """
- No children.
- """
- raise AssertionError("CommonHomeExternal: not supported")
-
-
- def getAllAttachmentNames(self):
- """
- No children.
- """
- raise AssertionError("CommonHomeExternal: not supported")
-
-
- def getAllManagedIDs(self):
- """
- No children.
- """
- raise AssertionError("CommonHomeExternal: not supported")
-
-
- def createdHome(self):
- """
- No children - make this a no-op.
- """
- return succeed(None)
-
-
- def splitCalendars(self):
- """
- No children.
- """
- raise AssertionError("CommonHomeExternal: not supported")
-
-
- def ensureDefaultCalendarsExist(self):
- """
- No children.
- """
- raise AssertionError("CommonHomeExternal: not supported")
-
-
- def setDefaultCalendar(self, calendar, componentType):
- """
- No children.
- """
- raise AssertionError("CommonHomeExternal: not supported")
-
-
- def defaultCalendar(self, componentType, create=True):
- """
- No children.
- """
- raise AssertionError("CommonHomeExternal: not supported")
-
-
- def isDefaultCalendar(self, calendar):
- """
- No children.
- """
- raise AssertionError("CommonHomeExternal: not supported")
-
-
- def getDefaultAlarm(self, vevent, timed):
- """
- No children.
- """
- raise AssertionError("CommonHomeExternal: not supported")
-
-
- def setDefaultAlarm(self, alarm, vevent, timed):
- """
- No children.
- """
- raise AssertionError("CommonHomeExternal: not supported")
-
-
- def getAvailability(self):
- """
- No children.
- """
- raise AssertionError("CommonHomeExternal: not supported")
-
-
- def setAvailability(self, availability):
- """
- No children.
- """
- raise AssertionError("CommonHomeExternal: not supported")
-
-
-
</del><span class="cx"> class Calendar(CommonHomeChild):
</span><span class="cx"> """
</span><span class="cx"> SQL-based implementation of L{ICalendar}.
</span><span class="lines">@@ -1662,24 +1534,7 @@
</span><span class="cx"> self._cachedComponent = None
</span><span class="cx"> self._cachedCommponentPerUser = {}
</span><span class="cx">
</span><del>- _allColumns = [
- _objectSchema.RESOURCE_ID,
- _objectSchema.RESOURCE_NAME,
- _objectSchema.UID,
- _objectSchema.MD5,
- Len(_objectSchema.TEXT),
- _objectSchema.ATTACHMENTS_MODE,
- _objectSchema.DROPBOX_ID,
- _objectSchema.ACCESS,
- _objectSchema.SCHEDULE_OBJECT,
- _objectSchema.SCHEDULE_TAG,
- _objectSchema.SCHEDULE_ETAGS,
- _objectSchema.PRIVATE_COMMENTS,
- _objectSchema.CREATED,
- _objectSchema.MODIFIED
- ]
</del><span class="cx">
</span><del>-
</del><span class="cx"> @classmethod
</span><span class="cx"> @inlineCallbacks
</span><span class="cx"> def _createInternal(cls, parent, name, component, internal_state, options=None, split_details=None):
</span><span class="lines">@@ -1691,7 +1546,8 @@
</span><span class="cx"> if name.startswith("."):
</span><span class="cx"> raise ObjectResourceNameNotAllowedError(name)
</span><span class="cx">
</span><del>- objectResource = cls(parent, name, None, None, options=options)
</del><ins>+ c = cls._externalClass if parent.external() else cls
+ objectResource = c(parent, name, None, None, options=options)
</ins><span class="cx"> yield objectResource._setComponentInternal(component, inserting=True, internal_state=internal_state, split_details=split_details)
</span><span class="cx"> yield objectResource._loadPropertyStore(created=True)
</span><span class="cx">
</span><span class="lines">@@ -1701,27 +1557,51 @@
</span><span class="cx"> returnValue(objectResource)
</span><span class="cx">
</span><span class="cx">
</span><del>- def _initFromRow(self, row):
</del><ins>+ @classmethod
+ def _allColumns(cls): #@NoSelf
</ins><span class="cx"> """
</span><del>- Given a select result using the columns from L{_allColumns}, initialize
- the calendar object resource state.
</del><ins>+ Full set of columns in the object table that need to be loaded to
+ initialize the object resource state.
</ins><span class="cx"> """
</span><del>- (self._resourceID,
- self._name,
- self._uid,
- self._md5,
- self._size,
- self._attachment,
- self._dropboxID,
- self._access,
- self._schedule_object,
- self._schedule_tag,
- self._schedule_etags,
- self._private_comments,
- self._created,
- self._modified,) = tuple(row)
</del><ins>+ obj = cls._objectSchema
+ return [
+ obj.RESOURCE_ID,
+ obj.RESOURCE_NAME,
+ obj.UID,
+ obj.MD5,
+ Len(obj.TEXT),
+ obj.ATTACHMENTS_MODE,
+ obj.DROPBOX_ID,
+ obj.ACCESS,
+ obj.SCHEDULE_OBJECT,
+ obj.SCHEDULE_TAG,
+ obj.SCHEDULE_ETAGS,
+ obj.PRIVATE_COMMENTS,
+ obj.CREATED,
+ obj.MODIFIED
+ ]
</ins><span class="cx">
</span><span class="cx">
</span><ins>+ @classmethod
+ def _rowAttributes(cls): #@NoSelf
+ return (
+ "_resourceID",
+ "_name",
+ "_uid",
+ "_md5",
+ "_size",
+ "_attachment",
+ "_dropboxID",
+ "_access",
+ "_schedule_object",
+ "_schedule_tag",
+ "_schedule_etags",
+ "_private_comments",
+ "_created",
+ "_modified",
+ )
+
+
</ins><span class="cx"> @property
</span><span class="cx"> def _calendar(self):
</span><span class="cx"> return self._parentCollection
</span><span class="lines">@@ -4499,4 +4379,10 @@
</span><span class="cx">
</span><span class="cx"> returnValue(location)
</span><span class="cx">
</span><ins>+# Hook-up class relationships at the end after they have all been defined
+from txdav.caldav.datastore.sql_external import CalendarHomeExternal, CalendarExternal, CalendarObjectExternal
+CalendarHome._externalClass = CalendarHomeExternal
+CalendarHome._childClass = Calendar
+Calendar._externalClass = CalendarExternal
</ins><span class="cx"> Calendar._objectResourceClass = CalendarObject
</span><ins>+CalendarObject._externalClass = CalendarObjectExternal
</ins></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtxdavcaldavdatastoresql_externalpy"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/sql_external.py (0 => 12051)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/sql_external.py         (rev 0)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/sql_external.py        2013-12-11 22:16:01 UTC (rev 12051)
</span><span class="lines">@@ -0,0 +1,167 @@
</span><ins>+# -*- test-case-name: txdav.caldav.datastore.test.test_sql -*-
+##
+# Copyright (c) 2013 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# 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 "AS IS" 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.
+##
+"""
+SQL backend for CalDAV storage when resources are external.
+"""
+
+from twisted.internet.defer import succeed
+
+from twext.python.log import Logger
+
+from txdav.caldav.datastore.sql import CalendarHome, Calendar, CalendarObject
+from txdav.common.datastore.sql_external import CommonHomeExternal, CommonHomeChildExternal, \
+ CommonObjectResourceExternal
+
+log = Logger()
+
+class CalendarHomeExternal(CommonHomeExternal, CalendarHome):
+ """
+ Wrapper for a CalendarHome that is external and only supports a limited set of operations.
+ """
+
+ def __init__(self, transaction, ownerUID, resourceID):
+
+ CalendarHome.__init__(self, transaction, ownerUID)
+ CommonHomeExternal.__init__(self, transaction, ownerUID, resourceID)
+
+
+ def hasCalendarResourceUIDSomewhereElse(self, uid, ok_object, mode):
+ """
+ No children.
+ """
+ raise AssertionError("CommonHomeExternal: not supported")
+
+
+ def getCalendarResourcesForUID(self, uid):
+ """
+ No children.
+ """
+ raise AssertionError("CommonHomeExternal: not supported")
+
+
+ def calendarObjectWithDropboxID(self, dropboxID):
+ """
+ No children.
+ """
+ raise AssertionError("CommonHomeExternal: not supported")
+
+
+ def getAllDropboxIDs(self):
+ """
+ No children.
+ """
+ raise AssertionError("CommonHomeExternal: not supported")
+
+
+ def getAllAttachmentNames(self):
+ """
+ No children.
+ """
+ raise AssertionError("CommonHomeExternal: not supported")
+
+
+ def getAllManagedIDs(self):
+ """
+ No children.
+ """
+ raise AssertionError("CommonHomeExternal: not supported")
+
+
+ def createdHome(self):
+ """
+ No children - make this a no-op.
+ """
+ return succeed(None)
+
+
+ def splitCalendars(self):
+ """
+ No children.
+ """
+ raise AssertionError("CommonHomeExternal: not supported")
+
+
+ def ensureDefaultCalendarsExist(self):
+ """
+ No children.
+ """
+ raise AssertionError("CommonHomeExternal: not supported")
+
+
+ def setDefaultCalendar(self, calendar, componentType):
+ """
+ No children.
+ """
+ raise AssertionError("CommonHomeExternal: not supported")
+
+
+ def defaultCalendar(self, componentType, create=True):
+ """
+ No children.
+ """
+ raise AssertionError("CommonHomeExternal: not supported")
+
+
+ def isDefaultCalendar(self, calendar):
+ """
+ No children.
+ """
+ raise AssertionError("CommonHomeExternal: not supported")
+
+
+ def getDefaultAlarm(self, vevent, timed):
+ """
+ No children.
+ """
+ raise AssertionError("CommonHomeExternal: not supported")
+
+
+ def setDefaultAlarm(self, alarm, vevent, timed):
+ """
+ No children.
+ """
+ raise AssertionError("CommonHomeExternal: not supported")
+
+
+ def getAvailability(self):
+ """
+ No children.
+ """
+ raise AssertionError("CommonHomeExternal: not supported")
+
+
+ def setAvailability(self, availability):
+ """
+ No children.
+ """
+ raise AssertionError("CommonHomeExternal: not supported")
+
+
+
+class CalendarExternal(CommonHomeChildExternal, Calendar):
+ """
+ SQL-based implementation of L{ICalendar}.
+ """
+ pass
+
+
+
+class CalendarObjectExternal(CommonObjectResourceExternal, CalendarObject):
+ """
+ SQL-based implementation of L{ICalendar}.
+ """
+ pass
</ins></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtxdavcaldavdatastoretestcommonpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/test/common.py (12050 => 12051)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/test/common.py        2013-12-11 15:40:55 UTC (rev 12050)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/test/common.py        2013-12-11 22:16:01 UTC (rev 12051)
</span><span class="lines">@@ -1703,7 +1703,7 @@
</span><span class="cx"> L{ICalendarStore.withEachCalendarHomeDo} executes its C{action}
</span><span class="cx"> argument repeatedly with all homes that have been created.
</span><span class="cx"> """
</span><del>- additionalUIDs = set('alpha-uid home2 home3 beta-uid'.split())
</del><ins>+ additionalUIDs = set('user01 home2 home3 uid1'.split())
</ins><span class="cx"> txn = self.transactionUnderTest()
</span><span class="cx"> for name in additionalUIDs:
</span><span class="cx"> yield txn.calendarHomeWithUID(name, create=True)
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtxdavcaldavdatastoretesttest_sqlpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/test/test_sql.py (12050 => 12051)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/test/test_sql.py        2013-12-11 15:40:55 UTC (rev 12050)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/test/test_sql.py        2013-12-11 22:16:01 UTC (rev 12051)
</span><span class="lines">@@ -1542,8 +1542,8 @@
</span><span class="cx">
</span><span class="cx"> # Tests on inbox - resources with properties
</span><span class="cx"> txn = self.transactionUnderTest()
</span><del>- yield txn.homeWithUID(ECALENDARTYPE, "byNameTest", create=True)
- inbox = yield self.calendarUnderTest(txn=txn, name="inbox", home="byNameTest")
</del><ins>+ yield txn.homeWithUID(ECALENDARTYPE, "user01", create=True)
+ inbox = yield self.calendarUnderTest(txn=txn, name="inbox", home="user01")
</ins><span class="cx"> caldata = """BEGIN:VCALENDAR
</span><span class="cx"> VERSION:2.0
</span><span class="cx"> CALSCALE:GREGORIAN
</span><span class="lines">@@ -1574,7 +1574,7 @@
</span><span class="cx"> yield _createInboxItem("4.ics", "p4")
</span><span class="cx"> yield self.commit()
</span><span class="cx">
</span><del>- inbox = yield self.calendarUnderTest(name="inbox", home="byNameTest")
</del><ins>+ inbox = yield self.calendarUnderTest(name="inbox", home="user01")
</ins><span class="cx"> yield _tests(inbox)
</span><span class="cx">
</span><span class="cx"> resources = yield inbox.objectResourcesWithNames(("1.ics",))
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtxdavcaldavdatastoretesttest_utilpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/test/test_util.py (12050 => 12051)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/test/test_util.py        2013-12-11 15:40:55 UTC (rev 12050)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/test/test_util.py        2013-12-11 22:16:01 UTC (rev 12051)
</span><span class="lines">@@ -323,7 +323,12 @@
</span><span class="cx"> @inlineCallbacks
</span><span class="cx"> def setUp(self):
</span><span class="cx"> yield super(HomeMigrationTests, self).setUp()
</span><del>- self.theStore = yield buildCalendarStore(self, self.notifierFactory, homes=("conflict1", "conflict2",))
</del><ins>+ self.theStore = yield buildCalendarStore(self, self.notifierFactory, homes=(
+ "conflict1",
+ "conflict2",
+ "empty_home",
+ "non_empty_home",
+ ))
</ins><span class="cx">
</span><span class="cx">
</span><span class="cx"> def storeUnderTest(self):
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtxdavcaldavdatastoretestutilpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/test/util.py (12050 => 12051)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/test/util.py        2013-12-11 15:40:55 UTC (rev 12050)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/test/util.py        2013-12-11 22:16:01 UTC (rev 12051)
</span><span class="lines">@@ -62,16 +62,12 @@
</span><span class="cx"> calendarUserAddresses,
</span><span class="cx"> cutype="INDIVIDUAL",
</span><span class="cx"> thisServer=True,
</span><ins>+ server=None,
</ins><span class="cx"> ):
</span><span class="cx">
</span><del>- super(TestCalendarStoreDirectoryRecord, self).__init__(uid, shortNames, fullName)
- self.uid = uid
- self.shortNames = shortNames
- self.fullName = fullName
- self.displayName = self.fullName if self.fullName else self.shortNames[0]
</del><ins>+ super(TestCalendarStoreDirectoryRecord, self).__init__(uid, shortNames, fullName, thisServer, server)
</ins><span class="cx"> self.calendarUserAddresses = calendarUserAddresses
</span><span class="cx"> self.cutype = cutype
</span><del>- self._thisServer = thisServer
</del><span class="cx">
</span><span class="cx">
</span><span class="cx"> def canonicalCalendarUserAddress(self):
</span><span class="lines">@@ -90,10 +86,6 @@
</span><span class="cx"> return cua
</span><span class="cx">
</span><span class="cx">
</span><del>- def thisServer(self):
- return self._thisServer
-
-
</del><span class="cx"> def calendarsEnabled(self):
</span><span class="cx"> return True
</span><span class="cx">
</span><span class="lines">@@ -148,12 +140,19 @@
</span><span class="cx"> homes.update((
</span><span class="cx"> "home1",
</span><span class="cx"> "home2",
</span><del>- "Home_attachments",
</del><ins>+ "home3",
+ "home_attachments",
</ins><span class="cx"> "home_bad",
</span><span class="cx"> "home_defaults",
</span><span class="cx"> "home_no_splits",
</span><ins>+ "home_provision1",
+ "home_provision2",
</ins><span class="cx"> "home_splits",
</span><span class="cx"> "home_splits_shared",
</span><ins>+ "uid1",
+ "uid2",
+ "new-home",
+ "xyzzy",
</ins><span class="cx"> ))
</span><span class="cx"> for uid in homes:
</span><span class="cx"> directory.addRecord(buildDirectoryRecord(uid))
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtxdavcarddavdatastoresqlpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/sql.py (12050 => 12051)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/sql.py        2013-12-11 15:40:55 UTC (rev 12050)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/sql.py        2013-12-11 22:16:01 UTC (rev 12051)
</span><span class="lines">@@ -14,7 +14,6 @@
</span><span class="cx"> # See the License for the specific language governing permissions and
</span><span class="cx"> # limitations under the License.
</span><span class="cx"> # #
</span><del>-from txdav.xml import element
</del><span class="cx">
</span><span class="cx"> """
</span><span class="cx"> SQL backend for CardDAV storage.
</span><span class="lines">@@ -62,6 +61,7 @@
</span><span class="cx"> InvalidUIDError, UIDExistsError, ObjectResourceTooBigError, \
</span><span class="cx"> InvalidObjectResourceError, InvalidComponentForStoreError, \
</span><span class="cx"> AllRetriesFailed, ObjectResourceNameAlreadyExistsError
</span><ins>+from txdav.xml import element
</ins><span class="cx">
</span><span class="cx"> from zope.interface.declarations import implements
</span><span class="cx">
</span><span class="lines">@@ -86,7 +86,6 @@
</span><span class="cx">
</span><span class="cx"> def __init__(self, transaction, ownerUID):
</span><span class="cx">
</span><del>- self._childClass = AddressBook
</del><span class="cx"> super(AddressBookHome, self).__init__(transaction, ownerUID)
</span><span class="cx"> self._addressbookPropertyStoreID = None
</span><span class="cx"> self._addressbook = None
</span><span class="lines">@@ -441,9 +440,81 @@
</span><span class="cx"> _objectSchema = schema.ADDRESSBOOK_OBJECT
</span><span class="cx">
</span><span class="cx">
</span><del>- def __init__(self, home, name, resourceID, mode, status, revision=0, message=None, ownerHome=None, ownerName=None):
</del><ins>+ @classmethod
+ @inlineCallbacks
+ def _getDBDataIndirect(cls, home, name, resourceID, externalID):
+
+ # Get the bind row data
+ row = None
+
+ # TODO: query cacher
+
+ rows = None
+ ownerHome = None
+
+ # TODO: add queryCacher support
+
+ if rows is None:
+ # No cached copy
+ if name:
+ ownerHome = yield home._txn.addressbookHomeWithUID(name)
+ if ownerHome is None:
+ returnValue(None)
+ resourceID = ownerHome.addressbook()._resourceID
+ rows = yield AddressBookObject._bindForHomeIDAndAddressBookID.on(
+ home._txn, homeID=home._resourceID, addressbookID=resourceID
+ )
+
+ if not rows:
+ returnValue(None)
+
+ groupID = None
+ overallBindStatus = _BIND_STATUS_INVITED
+ minBindRevision = None
+ for row in rows:
+ bindMode, homeID, resourceGroupID, externalID, name, bindStatus, bindRevision, bindMessage = row[:cls.bindColumnCount] #@UnusedVariable
+ if groupID is None:
+ groupID = resourceGroupID
+ minBindRevision = min(minBindRevision, bindRevision) if minBindRevision is not None else bindRevision
+ if bindStatus == _BIND_STATUS_ACCEPTED:
+ overallBindStatus = _BIND_STATUS_ACCEPTED
+
+ if ownerHome is None:
+ ownerAddressBookID = yield AddressBookObject.ownerAddressBookIDFromGroupID(home._txn, groupID)
+ ownerHome = yield home.ownerHomeWithChildID(ownerAddressBookID)
+
+ bindData = row[:cls.bindColumnCount]
+ additionalBindData = row[cls.bindColumnCount:cls.bindColumnCount + len(cls.additionalBindColumns())]
+
+ # Adjust for aggregate values
+ bindData[cls.bindColumns().index(cls._bindSchema.RESOURCE_ID)] = resourceID
+ bindData[cls.bindColumns().index(cls._bindSchema.RESOURCE_NAME)] = ownerHome.uid()
+ bindData[cls.bindColumns().index(cls._bindSchema.BIND_MODE)] = _BIND_MODE_INDIRECT
+ bindData[cls.bindColumns().index(cls._bindSchema.BIND_STATUS)] = overallBindStatus
+ bindData[cls.bindColumns().index(cls._bindSchema.BIND_REVISION)] = minBindRevision
+ bindData[cls.bindColumns().index(cls._bindSchema.MESSAGE)] = ""
+
+ # Get the matching metadata data
+ metadataData = None
+ queryCacher = home._txn._queryCacher
+ if queryCacher:
+ # Retrieve from cache
+ cacheKey = queryCacher.keyForHomeChildMetaData(resourceID)
+ metadataData = yield queryCacher.get(cacheKey)
+
+ if metadataData is None:
+ # No cached copy
+ metadataData = (yield cls._metadataByIDQuery.on(home._txn, resourceID=resourceID))[0]
+ if queryCacher:
+ # Cache the results
+ yield queryCacher.setAfterCommit(home._txn, cacheKey, metadataData)
+
+ returnValue((bindData, additionalBindData, metadataData, ownerHome,))
+
+
+ def __init__(self, home, name, resourceID, mode, status, revision=0, message=None, ownerHome=None, ownerName=None, externalID=None):
</ins><span class="cx"> ownerName = ownerHome.addressbook().name() if ownerHome else None
</span><del>- super(AddressBook, self).__init__(home, name, resourceID, mode, status, revision=revision, message=message, ownerHome=ownerHome, ownerName=ownerName)
</del><ins>+ super(AddressBook, self).__init__(home, name, resourceID, mode, status, revision=revision, message=message, ownerHome=ownerHome, ownerName=ownerName, externalID=externalID)
</ins><span class="cx"> self._index = PostgresLegacyABIndexEmulator(self)
</span><span class="cx">
</span><span class="cx">
</span><span class="lines">@@ -505,7 +576,7 @@
</span><span class="cx">
</span><span class="cx">
</span><span class="cx"> @classmethod
</span><del>- def create(cls, home, name):
</del><ins>+ def create(cls, home, name, externalID=None):
</ins><span class="cx"> if name == home.addressbook().name():
</span><span class="cx"> # raise HomeChildNameAlreadyExistsError
</span><span class="cx"> pass
</span><span class="lines">@@ -767,32 +838,17 @@
</span><span class="cx">
</span><span class="cx"> # Create the actual objects merging in properties
</span><span class="cx"> for ownerHome, dataRow in ownerHomeToDataRowMap.iteritems():
</span><del>- bindMode, homeID, resourceID, externalID, name, bindStatus, bindRevision, bindMessage = dataRow[:cls.bindColumnCount] #@UnusedVariable
- additionalBind = dataRow[cls.bindColumnCount:cls.bindColumnCount + len(cls.additionalBindColumns())]
- metadata = dataRow[cls.bindColumnCount + len(cls.additionalBindColumns()):]
</del><ins>+ bindData = dataRow[:cls.bindColumnCount]
+ additionalBindData = dataRow[cls.bindColumnCount:cls.bindColumnCount + len(cls.additionalBindColumns())]
+ metadataData = dataRow[cls.bindColumnCount + len(cls.additionalBindColumns()):]
+ propstore = propertyStores.get(ownerHome._addressbookPropertyStoreID, None)
</ins><span class="cx">
</span><del>- child = cls(
- home=home,
- name=ownerHome.uid(),
- resourceID=ownerHome._resourceID,
- mode=bindMode,
- status=bindStatus,
- revision=bindRevision,
- message=bindMessage,
- ownerHome=ownerHome,
- )
</del><ins>+ # Some adjustments for addressbook share model
+ bindData[cls.bindColumns().index(cls._bindSchema.RESOURCE_ID)] = ownerHome._resourceID
+ bindData[cls.bindColumns().index(cls._bindSchema.RESOURCE_NAME)] = ownerHome.uid()
</ins><span class="cx">
</span><del>- for attr, value in zip(cls.additionalBindAttributes(), additionalBind):
- setattr(child, attr, value)
- for attr, value in zip(cls.metadataAttributes(), metadata):
- setattr(child, attr, value)
</del><ins>+ child = yield cls.makeClass(home, bindData, additionalBindData, metadataData, propstore, ownerHome)
</ins><span class="cx"> child._syncTokenRevision = revisions[child._resourceID]
</span><del>- propstore = propertyStores.get(ownerHome._addressbookPropertyStoreID, None)
- # We have to re-adjust the property store object to account for possible shared
- # collections as previously we loaded them all as if they were owned
- if propstore:
- propstore._setDefaultUserUID(ownerHome.uid())
- yield child._loadPropertyStore(propstore)
</del><span class="cx"> results.append(child)
</span><span class="cx">
</span><span class="cx"> returnValue(results)
</span><span class="lines">@@ -855,7 +911,7 @@
</span><span class="cx">
</span><span class="cx"> @classmethod
</span><span class="cx"> @inlineCallbacks
</span><del>- def _indirectObjectWithNameOrID(cls, home, name=None, resourceID=None, accepted=True):
</del><ins>+ def _indirectObjectWithNameOrID(cls, home, name=None, resourceID=None, externalID=None, accepted=True):
</ins><span class="cx"> # replaces objectWithName()
</span><span class="cx"> """
</span><span class="cx"> Synthesize and indirect child for matching name or id based on whether shared groups exist.
</span><span class="lines">@@ -867,56 +923,17 @@
</span><span class="cx"> @return: an L{CommonHomeChild} or C{None} if no such child
</span><span class="cx"> exists.
</span><span class="cx"> """
</span><del>- rows = None
- ownerHome = None
</del><span class="cx">
</span><del>- # TODO: add queryCacher support
-
- if rows is None:
- # No cached copy
- if name:
- ownerHome = yield home._txn.addressbookHomeWithUID(name)
- if ownerHome is None:
- returnValue(None)
- resourceID = ownerHome.addressbook()._resourceID
- rows = yield AddressBookObject._bindForHomeIDAndAddressBookID.on(
- home._txn, homeID=home._resourceID, addressbookID=resourceID
- )
-
- if not rows:
</del><ins>+ dbData = yield cls._getDBDataIndirect(home, name, resourceID, externalID)
+ if dbData is None:
</ins><span class="cx"> returnValue(None)
</span><ins>+ bindData, additionalBindData, metadataData, ownerHome = dbData
</ins><span class="cx">
</span><del>- groupID = None
- overallBindStatus = _BIND_STATUS_INVITED
- minBindRevision = None
- for row in rows:
- bindMode, homeID, resourceGroupID, externalID, name, bindStatus, bindRevision, bindMessage = row[:cls.bindColumnCount] #@UnusedVariable
- if groupID is None:
- groupID = resourceGroupID
- minBindRevision = min(minBindRevision, bindRevision) if minBindRevision is not None else bindRevision
- if bindStatus == _BIND_STATUS_ACCEPTED:
- overallBindStatus = _BIND_STATUS_ACCEPTED
-
- if accepted is not None and (overallBindStatus == _BIND_STATUS_ACCEPTED) != bool(accepted):
</del><ins>+ bindStatus = bindData[cls.bindColumns().index(cls._bindSchema.BIND_STATUS)]
+ if accepted is not None and (bindStatus == _BIND_STATUS_ACCEPTED) != bool(accepted):
</ins><span class="cx"> returnValue(None)
</span><del>- additionalBind = row[cls.bindColumnCount:cls.bindColumnCount + len(cls.additionalBindColumns())]
</del><span class="cx">
</span><del>- if ownerHome is None:
- ownerAddressBookID = yield AddressBookObject.ownerAddressBookIDFromGroupID(home._txn, groupID)
- ownerHome = yield home.ownerHomeWithChildID(ownerAddressBookID)
-
- child = cls(
- home=home,
- name=ownerHome.uid(),
- resourceID=resourceID,
- mode=_BIND_MODE_INDIRECT,
- status=overallBindStatus,
- revision=minBindRevision,
- message="",
- ownerHome=ownerHome,
- ownerName=ownerHome.uid()
- )
- yield child.initFromStore(additionalBind)
</del><ins>+ child = yield cls.makeClass(home, bindData, additionalBindData, metadataData, None, ownerHome)
</ins><span class="cx"> returnValue(child)
</span><span class="cx">
</span><span class="cx">
</span><span class="lines">@@ -1142,7 +1159,7 @@
</span><span class="cx">
</span><span class="cx">
</span><span class="cx"> @inlineCallbacks
</span><del>- def shareWith(self, shareeHome, mode, status=None, summary=None):
</del><ins>+ def shareWith(self, shareeHome, mode, status=None, summary=None, shareName=None):
</ins><span class="cx"> """
</span><span class="cx"> Share this (owned) L{AddressBookObject} with another home.
</span><span class="cx">
</span><span class="lines">@@ -1170,11 +1187,12 @@
</span><span class="cx">
</span><span class="cx"> @inlineCallbacks
</span><span class="cx"> def doInsert(subt):
</span><del>- newName = self.newShareName()
</del><ins>+ newName = shareName if shareName is not None else self.newShareName()
</ins><span class="cx"> yield self._bindInsertQuery.on(
</span><span class="cx"> subt,
</span><span class="cx"> homeID=shareeHome._resourceID,
</span><span class="cx"> resourceID=self._resourceID,
</span><ins>+ externalID=None,
</ins><span class="cx"> name=newName,
</span><span class="cx"> mode=mode,
</span><span class="cx"> bindStatus=status,
</span><span class="lines">@@ -1210,14 +1228,14 @@
</span><span class="cx">
</span><span class="cx">
</span><span class="cx"> @inlineCallbacks
</span><del>- def createShare(self, shareeUID, mode, summary=None):
</del><ins>+ def createShare(self, shareeUID, mode, summary=None, shareName=None):
</ins><span class="cx"> """
</span><span class="cx"> Create a new shared resource. If the mode is direct, the share is created in accepted state,
</span><span class="cx"> otherwise the share is created in invited state.
</span><span class="cx"> """
</span><span class="cx">
</span><span class="cx"> if self._kind == _ABO_KIND_GROUP:
</span><del>- shareeView = yield super(AddressBookObjectSharingMixIn, self).createShare(shareeUID, mode, summary)
</del><ins>+ shareeView = yield super(AddressBookObjectSharingMixIn, self).createShare(shareeUID, mode, summary, shareName)
</ins><span class="cx"> returnValue(shareeView)
</span><span class="cx"> else:
</span><span class="cx"> returnValue(None)
</span><span class="lines">@@ -1430,6 +1448,130 @@
</span><span class="cx"> # _homeChildMetaDataSchema = schema.ADDRESSBOOK_OBJECT
</span><span class="cx">
</span><span class="cx">
</span><ins>+ @classmethod
+ @inlineCallbacks
+ def makeClass(cls, parent, objectData, groupBindData=None, propstore=None):
+ """
+ Given the various database rows, build the actual class.
+
+ @param parent: the parent collection object
+ @type parent: L{AddressBook}
+ @param objectData: the standard set of object columns
+ @type objectData: C{list}
+ @param groupBindData: additional group bind data
+ @type groupBindData: C{list}
+ @param propstore: a property store to use, or C{None} to load it automatically
+ @type propstore: L{PropertyStore}
+
+ @return: the constructed child class
+ @rtype: L{CommonHomeChild}
+ """
+
+ c = cls._externalClass if parent.external() else cls
+ child = c(
+ parent,
+ objectData[cls._allColumns().index(cls._objectSchema.RESOURCE_NAME)],
+ objectData[cls._allColumns().index(cls._objectSchema.UID)],
+ )
+
+ for attr, value in zip(child._rowAttributes(), objectData):
+ setattr(child, attr, value)
+
+ yield child._loadPropertyStore(propstore)
+
+ if groupBindData:
+ bindMode, homeID, resourceID, externalID, bindName, bindStatus, bindRevision, bindMessage = groupBindData[:AddressBookObject.bindColumnCount] #@UnusedVariable
+ child._bindMode = bindMode
+ child._bindStatus = bindStatus
+ child._bindMessage = bindMessage
+ child._bindName = bindName
+ else:
+ invites = yield child.sharingInvites()
+ if len(invites):
+ child._bindMessage = "shared"
+
+ returnValue(child)
+
+
+ @classmethod
+ @inlineCallbacks
+ def _getDBData(cls, parent, name, uid, resourceID):
+ """
+ Given a set of identifying information, load the data rows for the object. Only one of
+ L{name}, L{uid} or L{resourceID} is specified - others are C{None}.
+
+ @param parent: the parent collection object
+ @type parent: L{AddressBook}
+ @param name: the resource name
+ @type name: C{str}
+ @param uid: the UID of the data
+ @type uid: C{str}
+ @param resourceID: the resource ID
+ @type resourceID: C{int}
+ """
+
+ row = None
+ groupBindRow = None
+
+ if parent.owned() or parent.fullyShared(): # owned or fully shared
+ row = yield super(AddressBookObject, cls)._getDBData(parent, name, uid, resourceID)
+
+ # Might be special group
+ if row is None and parent.fullyShared():
+ if name:
+ if name == parent._groupForSharedAddressBookName():
+ row = parent._groupForSharedAddressBookRow()
+ elif uid:
+ if uid == (yield parent._groupForSharedAddressBookUID()):
+ row = parent._groupForSharedAddressBookRow()
+ elif resourceID:
+ if resourceID == parent.id():
+ rows = parent._groupForSharedAddressBookRow()
+
+ else:
+ acceptedGroupIDs = yield parent.acceptedGroupIDs()
+ allowedObjectIDs = yield parent.expandGroupIDs(parent._txn, acceptedGroupIDs)
+ rows = None
+ if name:
+ if allowedObjectIDs:
+ rows = (yield cls._allColumnsWithResourceIDsAndName(allowedObjectIDs).on(
+ parent._txn,
+ name=name,
+ resourceIDs=allowedObjectIDs,
+ ))
+ elif uid:
+ if allowedObjectIDs:
+ rows = (yield cls._allColumnsWithResourceIDsAndUID(allowedObjectIDs).on(
+ parent._txn,
+ uid=uid,
+ resourceIDs=allowedObjectIDs,
+ ))
+ elif resourceID:
+ if resourceID not in allowedObjectIDs:
+ # allow invited groups
+ allowedObjectIDs = yield parent.unacceptedGroupIDs()
+ if resourceID in allowedObjectIDs:
+ rows = (yield cls._allColumnsWithResourceID.on(
+ parent._txn,
+ resourceID=resourceID,
+ ))
+ if rows:
+ row = rows[0]
+
+ if row is not None:
+ if row[cls._allColumns().index(cls._objectSchema.KIND)] == _ABO_KIND_GROUP:
+
+ resourceID = row[cls._allColumns().index(cls._objectSchema.RESOURCE_ID)]
+ groupBindRows = yield AddressBookObject._bindForResourceIDAndHomeID.on(
+ parent._txn, resourceID=resourceID, homeID=parent._home._resourceID
+ )
+
+ if groupBindRows:
+ groupBindRow = groupBindRows[0]
+
+ returnValue((row, groupBindRow,))
+
+
</ins><span class="cx"> def __init__(self, addressbook, name, uid, resourceID=None, options=None):
</span><span class="cx">
</span><span class="cx"> self._kind = None
</span><span class="lines">@@ -1443,6 +1585,7 @@
</span><span class="cx"> self._bindMessage = None
</span><span class="cx"> self._bindName = None
</span><span class="cx"> super(AddressBookObject, self).__init__(addressbook, name, uid, resourceID, options)
</span><ins>+ self._externalID = None
</ins><span class="cx"> self._options = {} if options is None else options
</span><span class="cx">
</span><span class="cx">
</span><span class="lines">@@ -1467,6 +1610,15 @@
</span><span class="cx"> return self._resourceID == self.addressbook()._resourceID
</span><span class="cx">
</span><span class="cx">
</span><ins>+ def external(self):
+ """
+ Is this an external object.
+
+ @return: a string.
+ """
+ return self.addressbook().external()
+
+
</ins><span class="cx"> @classmethod
</span><span class="cx"> def _deleteMembersWithMemberIDAndGroupIDsQuery(cls, memberID, groupIDs):
</span><span class="cx"> aboMembers = schema.ABO_MEMBERS
</span><span class="lines">@@ -1553,7 +1705,7 @@
</span><span class="cx"> """
</span><span class="cx"> obj = cls._objectSchema
</span><span class="cx"> return Select(
</span><del>- cls._allColumns, From=obj,
</del><ins>+ cls._allColumns(), From=obj,
</ins><span class="cx"> Where=(column == Parameter(paramName)).And(
</span><span class="cx"> obj.RESOURCE_ID.In(Parameter("resourceIDs", len(resourceIDs)))),
</span><span class="cx"> )
</span><span class="lines">@@ -1573,7 +1725,7 @@
</span><span class="cx"> def _allColumnsWithResourceID(cls): #@NoSelf
</span><span class="cx"> obj = cls._objectSchema
</span><span class="cx"> return Select(
</span><del>- cls._allColumns, From=obj,
</del><ins>+ cls._allColumns(), From=obj,
</ins><span class="cx"> Where=obj.RESOURCE_ID == Parameter("resourceID"),)
</span><span class="cx">
</span><span class="cx">
</span><span class="lines">@@ -1609,92 +1761,20 @@
</span><span class="cx"> returnValue(None)
</span><span class="cx">
</span><span class="cx">
</span><ins>+ @classmethod
</ins><span class="cx"> @inlineCallbacks
</span><del>- def initFromStore(self):
- """
- Initialise this object from the store. We read in and cache all the
- extra metadata from the DB to avoid having to do DB queries for those
- individually later. Either the name or uid is present, so we have to
- tweak the query accordingly.
</del><ins>+ def _objectWithNameOrID(cls, parent, name, uid, resourceID):
</ins><span class="cx">
</span><del>- @return: L{self} if object exists in the DB, else C{None}
- """
- abo = None
- if self.owned() or self.addressbook().fullyShared(): # owned or fully shared
- abo = yield super(AddressBookObject, self).initFromStore()
</del><ins>+ row, groupBindRow = yield cls._getDBData(parent, name, uid, resourceID)
</ins><span class="cx">
</span><del>- # Might be special group
- if abo is None and self.addressbook().fullyShared():
- rows = None
- if self._name:
- if self._name == self.addressbook()._groupForSharedAddressBookName():
- rows = [self.addressbook()._groupForSharedAddressBookRow()]
- elif self._uid:
- if self._uid == (yield self.addressbook()._groupForSharedAddressBookUID()):
- rows = [self.addressbook()._groupForSharedAddressBookRow()]
- elif self._resourceID:
- if self.isGroupForSharedAddressBook():
- rows = [self.addressbook()._groupForSharedAddressBookRow()]
-
- if rows:
- self._initFromRow(tuple(rows[0]))
- yield self._loadPropertyStore()
- abo = self
-
</del><ins>+ if row:
+ child = yield cls.makeClass(parent, row, groupBindRow)
+ returnValue(child)
</ins><span class="cx"> else:
</span><del>- acceptedGroupIDs = yield self.addressbook().acceptedGroupIDs()
- allowedObjectIDs = yield self.addressbook().expandGroupIDs(self._txn, acceptedGroupIDs)
- rows = None
- if self._name:
- if allowedObjectIDs:
- rows = (yield self._allColumnsWithResourceIDsAndName(allowedObjectIDs).on(
- self._txn, name=self._name,
- resourceIDs=allowedObjectIDs,
- ))
- elif self._uid:
- if allowedObjectIDs:
- rows = (yield self._allColumnsWithResourceIDsAndUID(allowedObjectIDs).on(
- self._txn, uid=self._uid,
- resourceIDs=allowedObjectIDs,
- ))
- elif self._resourceID:
- if self._resourceID not in allowedObjectIDs:
- # allow invited groups
- allowedObjectIDs = yield self.addressbook().unacceptedGroupIDs()
- if self._resourceID in allowedObjectIDs:
- rows = (yield self._allColumnsWithResourceID.on(
- self._txn, resourceID=self._resourceID,
- ))
- if rows:
- self._initFromRow(tuple(rows[0]))
- yield self._loadPropertyStore()
- abo = self
-
- if abo is not None:
- if self._kind == _ABO_KIND_GROUP:
-
- groupBindRows = yield AddressBookObject._bindForResourceIDAndHomeID.on(
- self._txn, resourceID=self._resourceID, homeID=self._home._resourceID
- )
-
- if groupBindRows:
- groupBindRow = groupBindRows[0]
- bindMode, homeID, resourceID, externalID, bindName, bindStatus, bindRevision, bindMessage = groupBindRow[:AddressBookObject.bindColumnCount] #@UnusedVariable
- self._bindMode = bindMode
- self._bindStatus = bindStatus
- self._bindMessage = bindMessage
- self._bindName = bindName
- else:
- invites = yield self.sharingInvites()
- if len(invites):
- self._bindMessage = "shared"
-
- returnValue(self)
- else:
</del><span class="cx"> returnValue(None)
</span><span class="cx">
</span><span class="cx">
</span><del>- @classproperty
</del><ins>+ @classmethod
</ins><span class="cx"> def _allColumns(cls): #@NoSelf
</span><span class="cx"> """
</span><span class="cx"> Full set of columns in the object table that need to be loaded to
</span><span class="lines">@@ -1710,24 +1790,23 @@
</span><span class="cx"> obj.MD5,
</span><span class="cx"> Len(obj.TEXT),
</span><span class="cx"> obj.CREATED,
</span><del>- obj.MODIFIED,
</del><ins>+ obj.MODIFIED
</ins><span class="cx"> ]
</span><span class="cx">
</span><span class="cx">
</span><del>- def _initFromRow(self, row):
- """
- Given a select result using the columns from L{_allColumns}, initialize
- the object resource state.
- """
- (self._ownerAddressBookResourceID,
- self._resourceID,
- self._name,
- self._uid,
- self._kind,
- self._md5,
- self._size,
- self._created,
- self._modified,) = tuple(row)
</del><ins>+ @classmethod
+ def _rowAttributes(cls): #@NoSelf
+ return (
+ "_ownerAddressBookResourceID",
+ "_resourceID",
+ "_name",
+ "_uid",
+ "_kind",
+ "_md5",
+ "_size",
+ "_created",
+ "_modified",
+ )
</ins><span class="cx">
</span><span class="cx">
</span><span class="cx"> @classmethod
</span><span class="lines">@@ -1750,7 +1829,7 @@
</span><span class="cx"> else:
</span><span class="cx"> acceptedGroupIDs = yield addressbook.acceptedGroupIDs()
</span><span class="cx"> allowedObjectIDs = yield addressbook.expandGroupIDs(addressbook._txn, acceptedGroupIDs)
</span><del>- rows = yield cls._columnsWithResourceIDsQuery(cls._allColumns, allowedObjectIDs).on(
</del><ins>+ rows = yield cls._columnsWithResourceIDsQuery(cls._allColumns(), allowedObjectIDs).on(
</ins><span class="cx"> addressbook._txn, resourceIDs=allowedObjectIDs
</span><span class="cx"> )
</span><span class="cx"> returnValue(rows)
</span><span class="lines">@@ -1759,7 +1838,7 @@
</span><span class="cx"> @classmethod
</span><span class="cx"> def _allColumnsWithResourceIDsAndNamesQuery(cls, resourceIDs, names):
</span><span class="cx"> obj = cls._objectSchema
</span><del>- return Select(cls._allColumns, From=obj,
</del><ins>+ return Select(cls._allColumns(), From=obj,
</ins><span class="cx"> Where=(obj.RESOURCE_ID.In(Parameter("resourceIDs", len(resourceIDs))).And(
</span><span class="cx"> obj.RESOURCE_NAME.In(Parameter("names", len(names))))),)
</span><span class="cx">
</span><span class="lines">@@ -2336,4 +2415,10 @@
</span><span class="cx"> )
</span><span class="cx">
</span><span class="cx">
</span><ins>+# Hook-up class relationships at the end after they have all been defined
+from txdav.carddav.datastore.sql_external import AddressBookHomeExternal, AddressBookExternal, AddressBookObjectExternal
+AddressBookHome._externalClass = AddressBookHomeExternal
+AddressBookHome._childClass = AddressBook
+AddressBook._externalClass = AddressBookExternal
</ins><span class="cx"> AddressBook._objectResourceClass = AddressBookObject
</span><ins>+AddressBookObject._externalClass = AddressBookObjectExternal
</ins></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtxdavcarddavdatastoresql_externalpy"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/sql_external.py (0 => 12051)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/sql_external.py         (rev 0)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/sql_external.py        2013-12-11 22:16:01 UTC (rev 12051)
</span><span class="lines">@@ -0,0 +1,81 @@
</span><ins>+# -*- test-case-name: txdav.caldav.datastore.test.test_sql -*-
+##
+# Copyright (c) 2013 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# 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 "AS IS" 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.
+##
+"""
+SQL backend for CardDAV storage when resources are external.
+"""
+
+from twisted.internet.defer import succeed
+
+from twext.python.log import Logger
+
+from txdav.carddav.datastore.sql import AddressBookHome, AddressBook, \
+ AddressBookObject
+from txdav.common.datastore.sql_external import CommonHomeExternal, CommonHomeChildExternal, \
+ CommonObjectResourceExternal
+
+log = Logger()
+
+class AddressBookHomeExternal(CommonHomeExternal, AddressBookHome):
+
+ def __init__(self, transaction, ownerUID, resourceID):
+
+ AddressBookHome.__init__(self, transaction, ownerUID)
+ CommonHomeExternal.__init__(self, transaction, ownerUID, resourceID)
+
+
+ def hasAddressBookResourceUIDSomewhereElse(self, uid, ok_object, mode):
+ """
+ No children.
+ """
+ raise AssertionError("CommonHomeExternal: not supported")
+
+
+ def getAddressBookResourcesForUID(self, uid):
+ """
+ No children.
+ """
+ raise AssertionError("CommonHomeExternal: not supported")
+
+
+ def createdHome(self):
+ """
+ No children - make this a no-op.
+ """
+ return succeed(None)
+
+
+ def addressbook(self):
+ """
+ No children.
+ """
+ raise AssertionError("CommonHomeExternal: not supported")
+
+
+
+class AddressBookExternal(CommonHomeChildExternal, AddressBook):
+ """
+ SQL-based implementation of L{IAddressBook}.
+ """
+ pass
+
+
+
+class AddressBookObjectExternal(CommonObjectResourceExternal, AddressBookObject):
+ """
+ SQL-based implementation of L{ICalendar}.
+ """
+ pass
</ins></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtxdavcarddavdatastoretesttest_sqlpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/test/test_sql.py (12050 => 12051)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/test/test_sql.py        2013-12-11 15:40:55 UTC (rev 12050)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/test/test_sql.py        2013-12-11 22:16:01 UTC (rev 12051)
</span><span class="lines">@@ -41,7 +41,7 @@
</span><span class="cx"> from txdav.common.icommondatastore import NoSuchObjectResourceError
</span><span class="cx"> from txdav.common.datastore.sql import EADDRESSBOOKTYPE, CommonObjectResource
</span><span class="cx"> from txdav.common.datastore.sql_tables import _ABO_KIND_PERSON, _ABO_KIND_GROUP, schema
</span><del>-from txdav.common.datastore.test.util import buildStore
</del><ins>+from txdav.common.datastore.test.util import buildStore, cleanStore
</ins><span class="cx">
</span><span class="cx"> from txdav.xml.rfc2518 import GETContentLanguage, ResourceType
</span><span class="cx">
</span><span class="lines">@@ -55,7 +55,22 @@
</span><span class="cx"> @inlineCallbacks
</span><span class="cx"> def setUp(self):
</span><span class="cx"> yield super(AddressBookSQLStorageTests, self).setUp()
</span><del>- self._sqlStore = yield buildStore(self, self.notifierFactory)
</del><ins>+ self._sqlStore = yield buildStore(
+ self,
+ self.notifierFactory,
+ homes=(
+ "home1",
+ "home2",
+ "home3",
+ "home_bad",
+ "home_empty",
+ "homeNew",
+ "new-home",
+ "uid1",
+ "uid2",
+ "xyzzy",
+ )
+ )
</ins><span class="cx"> yield self.populate()
</span><span class="cx">
</span><span class="cx">
</span><span class="lines">@@ -288,7 +303,7 @@
</span><span class="cx"> Test that two concurrent attempts to PUT different address book object resources to the
</span><span class="cx"> same address book home does not cause a deadlock.
</span><span class="cx"> """
</span><del>- addressbookStore = yield buildStore(self, self.notifierFactory)
</del><ins>+ addressbookStore = self._sqlStore
</ins><span class="cx">
</span><span class="cx"> # Provision the home and addressbook now
</span><span class="cx"> txn = addressbookStore.newTransaction()
</span><span class="lines">@@ -394,7 +409,7 @@
</span><span class="cx"> """
</span><span class="cx"> Test that kind property UID is stored correctly in database
</span><span class="cx"> """
</span><del>- addressbookStore = yield buildStore(self, self.notifierFactory)
</del><ins>+ addressbookStore = self._sqlStore
</ins><span class="cx">
</span><span class="cx"> # Provision the home and addressbook, one user and one group
</span><span class="cx"> txn = addressbookStore.newTransaction()
</span><span class="lines">@@ -440,7 +455,7 @@
</span><span class="cx"> """
</span><span class="cx"> Test that kind property vCard is stored correctly in database
</span><span class="cx"> """
</span><del>- addressbookStore = yield buildStore(self, self.notifierFactory)
</del><ins>+ addressbookStore = self._sqlStore
</ins><span class="cx">
</span><span class="cx"> # Provision the home and addressbook, one user and one group
</span><span class="cx"> txn = addressbookStore.newTransaction()
</span><span class="lines">@@ -531,7 +546,8 @@
</span><span class="cx"> """
</span><span class="cx"> Test that kind property vCard is stored correctly in database
</span><span class="cx"> """
</span><del>- addressbookStore = yield buildStore(self, self.notifierFactory)
</del><ins>+ addressbookStore = self._sqlStore
+ cleanStore(self, addressbookStore)
</ins><span class="cx">
</span><span class="cx"> # Provision the home and addressbook, one user and one group
</span><span class="cx"> txn = addressbookStore.newTransaction()
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastorepoddingconduitpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/conduit.py (12050 => 12051)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/conduit.py        2013-12-11 15:40:55 UTC (rev 12050)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/conduit.py        2013-12-11 22:16:01 UTC (rev 12051)
</span><span class="lines">@@ -14,6 +14,8 @@
</span><span class="cx"> # limitations under the License.
</span><span class="cx"> ##
</span><span class="cx">
</span><ins>+from twext.python.log import Logger
+
</ins><span class="cx"> from twisted.internet.defer import inlineCallbacks, returnValue
</span><span class="cx">
</span><span class="cx"> from txdav.common.datastore.podding.request import ConduitRequest
</span><span class="lines">@@ -25,21 +27,17 @@
</span><span class="cx"> "PoddingConduitResource",
</span><span class="cx"> ]
</span><span class="cx">
</span><del>-class BadMessageError(Exception):
- pass
</del><ins>+log = Logger()
</ins><span class="cx">
</span><span class="cx">
</span><del>-
-class InvalidCrossPodRequestError(Exception):
</del><ins>+class FailedCrossPodRequestError(RuntimeError):
+ """
+ Request returned an error.
+ """
</ins><span class="cx"> pass
</span><span class="cx">
</span><span class="cx">
</span><span class="cx">
</span><del>-class FailedCrossPodRequestError(Exception):
- pass
-
-
-
</del><span class="cx"> class PoddingConduit(object):
</span><span class="cx"> """
</span><span class="cx"> This class is the API/RPC bridge between cross-pod requests and the store.
</span><span class="lines">@@ -51,7 +49,7 @@
</span><span class="cx"> the other keys are arguments to that call.
</span><span class="cx">
</span><span class="cx"> Each response C{dict} has a "result" key that indicates the call result, and other
</span><del>- optional keys for any parameters returned by the call.
</del><ins>+ optional keys for any values returned by the call.
</ins><span class="cx">
</span><span class="cx"> The conduit provides two methods for each action: one for the sending side and one for
</span><span class="cx"> the receiving side, called "send_{action}" and "recv_{action}", respectively, where
</span><span class="lines">@@ -63,10 +61,14 @@
</span><span class="cx"> The "recv_{action}" calls take a single C{dict} argument that is the deserialized JSON
</span><span class="cx"> data from the incoming request. The return value is a C{dict} with the result.
</span><span class="cx">
</span><ins>+ Some simple forms of send_/recv_ methods can be auto-generated to simplify coding.
+
</ins><span class="cx"> Right now this conduit is used for cross-pod sharing operations. In the future we will
</span><span class="cx"> likely use it for cross-pod migration.
</span><span class="cx"> """
</span><span class="cx">
</span><ins>+ conduitRequestClass = ConduitRequest
+
</ins><span class="cx"> def __init__(self, store):
</span><span class="cx"> """
</span><span class="cx"> @param store: the L{CommonDataStore} in use.
</span><span class="lines">@@ -91,18 +93,73 @@
</span><span class="cx"> if source is None:
</span><span class="cx"> raise DirectoryRecordNotFoundError("Cross-pod source: {}".format(source_guid))
</span><span class="cx"> if not source.thisServer():
</span><del>- raise InvalidCrossPodRequestError("Cross-pod source not on this server: {}".format(source_guid))
</del><ins>+ raise FailedCrossPodRequestError("Cross-pod source not on this server: {}".format(source_guid))
</ins><span class="cx">
</span><span class="cx"> destination = self.store.directoryService().recordWithUID(destination_guid)
</span><span class="cx"> if destination is None:
</span><span class="cx"> raise DirectoryRecordNotFoundError("Cross-pod destination: {}".format(destination_guid))
</span><span class="cx"> if destination.thisServer():
</span><del>- raise InvalidCrossPodRequestError("Cross-pod destination on this server: {}".format(destination_guid))
</del><ins>+ raise FailedCrossPodRequestError("Cross-pod destination on this server: {}".format(destination_guid))
</ins><span class="cx">
</span><span class="cx"> return (source, destination,)
</span><span class="cx">
</span><span class="cx">
</span><span class="cx"> @inlineCallbacks
</span><ins>+ def sendRequest(self, txn, recipient, data):
+
+ request = self.conduitRequestClass(recipient.server(), data)
+ try:
+ response = (yield request.doRequest(txn))
+ except Exception as e:
+ raise FailedCrossPodRequestError("Failed cross-pod request: {}".format(e))
+ returnValue(response)
+
+
+ @inlineCallbacks
+ def processRequest(self, data):
+ """
+ Process the request.
+
+ @param data: the JSON data to process
+ @type data: C{dict}
+ """
+ # Must have a dict with an "action" key
+ try:
+ action = data["action"]
+ except (KeyError, TypeError) as e:
+ log.error("JSON data must have an object as its root with an 'action' attribute: {ex}\n{json}", ex=e, json=data)
+ raise FailedCrossPodRequestError("JSON data must have an object as its root with an 'action' attribute: {}\n{}".format(e, data,))
+
+ if action == "ping":
+ result = {"result": "ok"}
+ returnValue(result)
+
+ method = "recv_{}".format(action)
+ if not hasattr(self, method):
+ log.error("Unsupported action: {action}", action=action)
+ raise FailedCrossPodRequestError("Unsupported action: {}".format(action))
+
+ # Need a transaction to work with
+ txn = self.store.newTransaction(repr("Conduit request"))
+
+ # Do the actual request processing
+ try:
+ result = (yield getattr(self, method)(txn, data))
+ except Exception as e:
+ yield txn.abort()
+ log.error("Failed action: {action}, {ex}", action=action, ex=e)
+ raise FailedCrossPodRequestError("Failed action: {}, {}".format(action, e))
+
+ yield txn.commit()
+
+ returnValue(result)
+
+
+ #
+ # Invite related apis
+ #
+
+ @inlineCallbacks
</ins><span class="cx"> def send_shareinvite(self, txn, homeType, ownerUID, ownerID, ownerName, shareeUID, shareUID, bindMode, summary, supported_components):
</span><span class="cx"> """
</span><span class="cx"> Send a sharing invite cross-pod message.
</span><span class="lines">@@ -127,7 +184,7 @@
</span><span class="cx"> @type supported_components: C{str}
</span><span class="cx"> """
</span><span class="cx">
</span><del>- _ignore_owner, sharee = self.validRequst(ownerUID, shareeUID)
</del><ins>+ _ignore_sender, recipient = self.validRequst(ownerUID, shareeUID)
</ins><span class="cx">
</span><span class="cx"> action = {
</span><span class="cx"> "action": "shareinvite",
</span><span class="lines">@@ -143,10 +200,8 @@
</span><span class="cx"> if supported_components is not None:
</span><span class="cx"> action["supported-components"] = supported_components
</span><span class="cx">
</span><del>- request = ConduitRequest(sharee.server(), action)
- response = (yield request.doRequest(txn))
- if response["result"] != "ok":
- raise FailedCrossPodRequestError(response["description"])
</del><ins>+ result = yield self.sendRequest(txn, recipient, action)
+ returnValue(result)
</ins><span class="cx">
</span><span class="cx">
</span><span class="cx"> @inlineCallbacks
</span><span class="lines">@@ -159,15 +214,12 @@
</span><span class="cx"> """
</span><span class="cx">
</span><span class="cx"> if message["action"] != "shareinvite":
</span><del>- raise BadMessageError("Wrong action '{}' for recv_shareinvite".format(message["action"]))
</del><ins>+ raise FailedCrossPodRequestError("Wrong action '{}' for recv_shareinvite".format(message["action"]))
</ins><span class="cx">
</span><span class="cx"> # Create a share
</span><span class="cx"> shareeHome = yield txn.homeWithUID(message["type"], message["sharee"], create=True)
</span><span class="cx"> if shareeHome is None or shareeHome.external():
</span><del>- returnValue({
- "result": "bad",
- "description": "Invalid sharee UID specified",
- })
</del><ins>+ raise FailedCrossPodRequestError("Invalid sharee UID specified")
</ins><span class="cx">
</span><span class="cx"> try:
</span><span class="cx"> yield shareeHome.processExternalInvite(
</span><span class="lines">@@ -180,14 +232,10 @@
</span><span class="cx"> supported_components=message.get("supported-components")
</span><span class="cx"> )
</span><span class="cx"> except ExternalShareFailed as e:
</span><del>- returnValue({
- "result": "bad",
- "description": str(e),
- })
</del><ins>+ raise FailedCrossPodRequestError(str(e))
</ins><span class="cx">
</span><span class="cx"> returnValue({
</span><span class="cx"> "result": "ok",
</span><del>- "description": "Success"
</del><span class="cx"> })
</span><span class="cx">
</span><span class="cx">
</span><span class="lines">@@ -208,7 +256,7 @@
</span><span class="cx"> @type shareUID: C{str}
</span><span class="cx"> """
</span><span class="cx">
</span><del>- _ignore_owner, sharee = self.validRequst(ownerUID, shareeUID)
</del><ins>+ _ignore_sender, recipient = self.validRequst(ownerUID, shareeUID)
</ins><span class="cx">
</span><span class="cx"> action = {
</span><span class="cx"> "action": "shareuninvite",
</span><span class="lines">@@ -219,10 +267,8 @@
</span><span class="cx"> "share_id": shareUID,
</span><span class="cx"> }
</span><span class="cx">
</span><del>- request = ConduitRequest(sharee.server(), action)
- response = (yield request.doRequest(txn))
- if response["result"] != "ok":
- raise FailedCrossPodRequestError(response["description"])
</del><ins>+ result = yield self.sendRequest(txn, recipient, action)
+ returnValue(result)
</ins><span class="cx">
</span><span class="cx">
</span><span class="cx"> @inlineCallbacks
</span><span class="lines">@@ -235,15 +281,12 @@
</span><span class="cx"> """
</span><span class="cx">
</span><span class="cx"> if message["action"] != "shareuninvite":
</span><del>- raise BadMessageError("Wrong action '{}' for recv_shareuninvite".format(message["action"]))
</del><ins>+ raise FailedCrossPodRequestError("Wrong action '{}' for recv_shareuninvite".format(message["action"]))
</ins><span class="cx">
</span><span class="cx"> # Create a share
</span><span class="cx"> shareeHome = yield txn.homeWithUID(message["type"], message["sharee"], create=True)
</span><span class="cx"> if shareeHome is None or shareeHome.external():
</span><del>- returnValue({
- "result": "bad",
- "description": "Invalid sharee UID specified",
- })
</del><ins>+ FailedCrossPodRequestError("Invalid sharee UID specified")
</ins><span class="cx">
</span><span class="cx"> try:
</span><span class="cx"> yield shareeHome.processExternalUninvite(
</span><span class="lines">@@ -252,14 +295,10 @@
</span><span class="cx"> message["share_id"],
</span><span class="cx"> )
</span><span class="cx"> except ExternalShareFailed as e:
</span><del>- returnValue({
- "result": "bad",
- "description": str(e),
- })
</del><ins>+ FailedCrossPodRequestError(str(e))
</ins><span class="cx">
</span><span class="cx"> returnValue({
</span><span class="cx"> "result": "ok",
</span><del>- "description": "Success"
</del><span class="cx"> })
</span><span class="cx">
</span><span class="cx">
</span><span class="lines">@@ -272,9 +311,9 @@
</span><span class="cx"> @type homeType: C{int}
</span><span class="cx"> @param ownerUID: GUID of the sharer.
</span><span class="cx"> @type ownerUID: C{str}
</span><del>- @param shareeUID: GUID of the sharee
</del><ins>+ @param shareeUID: GUID of the recipient
</ins><span class="cx"> @type shareeUID: C{str}
</span><del>- @param shareUID: Resource/invite ID for sharee
</del><ins>+ @param shareUID: Resource/invite ID for recipient
</ins><span class="cx"> @type shareUID: C{str}
</span><span class="cx"> @param bindStatus: bind mode for the share
</span><span class="cx"> @type bindStatus: C{str}
</span><span class="lines">@@ -282,7 +321,7 @@
</span><span class="cx"> @type summary: C{str}
</span><span class="cx"> """
</span><span class="cx">
</span><del>- _ignore_owner, sharee = self.validRequst(shareeUID, ownerUID)
</del><ins>+ _ignore_sender, recipient = self.validRequst(shareeUID, ownerUID)
</ins><span class="cx">
</span><span class="cx"> action = {
</span><span class="cx"> "action": "sharereply",
</span><span class="lines">@@ -295,10 +334,8 @@
</span><span class="cx"> if summary is not None:
</span><span class="cx"> action["summary"] = summary
</span><span class="cx">
</span><del>- request = ConduitRequest(sharee.server(), action)
- response = (yield request.doRequest(txn))
- if response["result"] != "ok":
- raise FailedCrossPodRequestError(response["description"])
</del><ins>+ result = yield self.sendRequest(txn, recipient, action)
+ returnValue(result)
</ins><span class="cx">
</span><span class="cx">
</span><span class="cx"> @inlineCallbacks
</span><span class="lines">@@ -311,15 +348,12 @@
</span><span class="cx"> """
</span><span class="cx">
</span><span class="cx"> if message["action"] != "sharereply":
</span><del>- raise BadMessageError("Wrong action '{}' for recv_sharereply".format(message["action"]))
</del><ins>+ raise FailedCrossPodRequestError("Wrong action '{}' for recv_sharereply".format(message["action"]))
</ins><span class="cx">
</span><span class="cx"> # Create a share
</span><span class="cx"> ownerHome = yield txn.homeWithUID(message["type"], message["owner"])
</span><span class="cx"> if ownerHome is None or ownerHome.external():
</span><del>- returnValue({
- "result": "bad",
- "description": "Invalid owner UID specified",
- })
</del><ins>+ FailedCrossPodRequestError("Invalid owner UID specified")
</ins><span class="cx">
</span><span class="cx"> try:
</span><span class="cx"> yield ownerHome.processExternalReply(
</span><span class="lines">@@ -330,12 +364,126 @@
</span><span class="cx"> summary=message.get("summary")
</span><span class="cx"> )
</span><span class="cx"> except ExternalShareFailed as e:
</span><del>- returnValue({
- "result": "bad",
- "description": str(e),
- })
</del><ins>+ FailedCrossPodRequestError(str(e))
</ins><span class="cx">
</span><span class="cx"> returnValue({
</span><span class="cx"> "result": "ok",
</span><del>- "description": "Success"
</del><span class="cx"> })
</span><ins>+
+
+ #
+ # Sharer data access related apis
+ #
+
+ def _send(self, action, shareeView):
+ """
+ Base behavior for an operation on a sharee resource.
+
+ @param shareeView: sharee resource being operated on.
+ @type shareeView: L{CommonHomeChildExternal}
+ """
+
+ homeType = shareeView.ownerHome()._homeType
+ ownerUID = shareeView.ownerHome().uid()
+ ownerID = shareeView.external_id()
+ shareeUID = shareeView.viewerHome().uid()
+
+ _ignore_sender, recipient = self.validRequst(shareeUID, ownerUID)
+
+ result = {
+ "action": action,
+ "type": homeType,
+ "owner": ownerUID,
+ "owner_id": ownerID,
+ "sharee": shareeUID,
+ }
+ return result, recipient
+
+
+ @inlineCallbacks
+ def _recv(self, txn, message, expected_action):
+ """
+ Base behavior for sharer data access.
+
+ @param message: message arguments
+ @type message: C{dict}
+ """
+
+ if message["action"] != expected_action:
+ raise FailedCrossPodRequestError("Wrong action '{}' for recv_{}".format(message["action"], expected_action))
+
+ # Create a share
+ ownerHome = yield txn.homeWithUID(message["type"], message["owner"], create=True)
+ if ownerHome is None or ownerHome.external():
+ FailedCrossPodRequestError("Invalid owner UID specified")
+
+ ownerHomeChild = yield ownerHome.childWithID(message["owner_id"])
+ if ownerHomeChild is None:
+ FailedCrossPodRequestError("Invalid owner shared resource specified")
+
+ returnValue((ownerHome, ownerHomeChild))
+
+
+ #
+ # Simple calls are ones where there is no argument and a single return value. We can simplify
+ # code generation for these by dynamically generating the appropriate class methods.
+ #
+
+ @inlineCallbacks
+ def _simple_send(self, actionName, shareeView, args=None, kwargs=None):
+ """
+ A simple send operation that returns a value.
+
+ @param actionName: name of the action.
+ @type actionName: C{str}
+ @param shareeView: sharee resource being operated on.
+ @type shareeView: L{CommonHomeChildExternal}
+ @param args: list of optional arguments.
+ @type args: C{list}
+ @param kwargs: optional keyword arguments.
+ @type kwargs: C{dict}
+ """
+
+ action, recipient = self._send(actionName, shareeView)
+ if args is not None:
+ action["arguments"] = args
+ if kwargs is not None:
+ action["keywords"] = kwargs
+ result = yield self.sendRequest(shareeView._txn, recipient, action)
+ returnValue(result["value"])
+
+
+ @inlineCallbacks
+ def _simple_recv(self, txn, actionName, message, method):
+ """
+ A simple recv operation that returns a value. We also look for an optional set of arguments/keywords
+ and include those only if present.
+
+ @param actionName: name of the action.
+ @type actionName: C{str}
+ @param message: message arguments
+ @type message: C{dict}
+ @param method: name of the method to execute on the shared resource to get the result.
+ @type method: C{str}
+ """
+
+ _ignore_ownerHome, ownerHomeChild = yield self._recv(txn, message, actionName)
+ value = yield getattr(ownerHomeChild, method)(*message.get("arguments", ()), **message.get("keywords", {}))
+ returnValue({
+ "result": "ok",
+ "value": value,
+ })
+
+
+ @classmethod
+ def _make_simple_action(cls, action, method):
+ setattr(cls, "send_{}".format(action), lambda self, shareeView, *args, **kwargs: self._simple_send(action, shareeView, args, kwargs))
+ setattr(cls, "recv_{}".format(action), lambda self, txn, message: self._simple_recv(txn, action, message, method))
+
+
+PoddingConduit._make_simple_action("countobjects", "countObjectResources")
+PoddingConduit._make_simple_action("listobjects", "listObjectResources")
+PoddingConduit._make_simple_action("synctoken", "syncToken")
+PoddingConduit._make_simple_action("resourcenamessincerevision", "resourceNamesSinceRevision")
+PoddingConduit._make_simple_action("resourceuidforname", "resourceUIDForName")
+PoddingConduit._make_simple_action("resourcenameforuid", "resourceNameForUID")
</ins></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastorepoddingrequestpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/request.py (12050 => 12051)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/request.py        2013-12-11 15:40:55 UTC (rev 12050)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/request.py        2013-12-11 22:16:01 UTC (rev 12051)
</span><span class="lines">@@ -73,7 +73,7 @@
</span><span class="cx"> except Exception as e:
</span><span class="cx"> # Request failed
</span><span class="cx"> log.error("Could not do cross-pod request : {request} {ex}", request=self, ex=e)
</span><del>- raise ValueError("Failed cross-pod request: {}".format(response.code))
</del><ins>+ raise ValueError("Failed cross-pod request: {}".format(e))
</ins><span class="cx">
</span><span class="cx"> returnValue(data)
</span><span class="cx">
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastorepoddingresourcepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/resource.py (12050 => 12051)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/resource.py        2013-12-11 15:40:55 UTC (rev 12050)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/resource.py        2013-12-11 22:16:01 UTC (rev 12051)
</span><span class="lines">@@ -32,6 +32,7 @@
</span><span class="cx">
</span><span class="cx"> from txdav.xml import element as davxml
</span><span class="cx"> from txdav.caldav.datastore.scheduling.ischedule.localservers import Servers
</span><ins>+from txdav.common.datastore.podding.conduit import FailedCrossPodRequestError
</ins><span class="cx">
</span><span class="cx"> __all__ = [
</span><span class="cx"> "ConduitResource",
</span><span class="lines">@@ -138,36 +139,14 @@
</span><span class="cx"> self.log.error("Invalid JSON data in request: {ex}\n{body}", ex=e, body=body)
</span><span class="cx"> raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, "Invalid JSON data in request: {}\n{}".format(e, body)))
</span><span class="cx">
</span><del>- # Must have a dict with an "action" key
</del><ins>+ # Get the conduit to process the data
</ins><span class="cx"> try:
</span><del>- action = j["action"]
- except (KeyError, TypeError) as e:
- self.log.error("JSON data must have an object as its root with an 'action' attribute: {ex}\n{json}", ex=e, json=j)
- raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, "JSON data must have an object as its root with an 'action' attribute: {}\n{}".format(e, j,)))
-
- if action == "ping":
- result = {"result": "ok"}
- response = JSONResponse(responsecode.OK, result)
- returnValue(response)
-
- method = "recv_{}".format(action)
- if not hasattr(self.store.conduit, method):
- self.log.error("Unsupported action: {action}", action=action)
- raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, "Unsupported action: {}".format(action)))
-
- # Need a transaction to work with
- txn = self.store.newTransaction(repr(request))
-
- # Do the POST processing treating this as a non-local schedule
- try:
- result = (yield getattr(self.store.conduit, method)(txn, j))
</del><ins>+ result = yield self.store.conduit.processRequest(j)
+ except FailedCrossPodRequestError as e:
+ raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, str(e)))
</ins><span class="cx"> except Exception as e:
</span><del>- yield txn.abort()
- self.log.error("Failed action: {action}, {ex}", action=action, ex=e)
- raise HTTPError(StatusResponse(responsecode.INTERNAL_SERVER_ERROR, "Failed action: {}, {}".format(action, e)))
</del><ins>+ raise HTTPError(StatusResponse(responsecode.INTERNAL_SERVER_ERROR, str(e)))
</ins><span class="cx">
</span><del>- yield txn.commit()
-
</del><span class="cx"> response = JSONResponse(responsecode.OK, result)
</span><span class="cx"> returnValue(response)
</span><span class="cx">
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastorepoddingtesttest_conduitpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/test/test_conduit.py (12050 => 12051)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/test/test_conduit.py        2013-12-11 15:40:55 UTC (rev 12050)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/test/test_conduit.py        2013-12-11 22:16:01 UTC (rev 12051)
</span><span class="lines">@@ -16,17 +16,22 @@
</span><span class="cx">
</span><span class="cx"> from twext.python.clsprop import classproperty
</span><span class="cx"> import twext.web2.dav.test.util
</span><del>-from twisted.internet.defer import inlineCallbacks, succeed
</del><ins>+from twisted.internet.defer import inlineCallbacks, succeed, returnValue
</ins><span class="cx"> from txdav.caldav.datastore.scheduling.ischedule.localservers import Servers, Server
</span><span class="cx"> from txdav.caldav.datastore.test.util import buildCalendarStore, \
</span><span class="cx"> TestCalendarStoreDirectoryRecord
</span><span class="cx"> from txdav.common.datastore.podding.resource import ConduitResource
</span><span class="cx"> from txdav.common.datastore.test.util import populateCalendarsFrom, CommonCommonTests
</span><span class="cx"> from txdav.common.datastore.podding.conduit import PoddingConduit, \
</span><del>- InvalidCrossPodRequestError
</del><ins>+ FailedCrossPodRequestError
</ins><span class="cx"> from txdav.common.idirectoryservice import DirectoryRecordNotFoundError
</span><ins>+from txdav.common.datastore.podding.test.util import MultiStoreConduitTest, \
+ FakeConduitRequest
+from txdav.common.datastore.sql_tables import _BIND_STATUS_ACCEPTED
+from pycalendar.datetime import DateTime
+from twistedcaldav.ical import Component
</ins><span class="cx">
</span><del>-class Conduit (CommonCommonTests, twext.web2.dav.test.util.TestCase):
</del><ins>+class TestConduit (CommonCommonTests, twext.web2.dav.test.util.TestCase):
</ins><span class="cx">
</span><span class="cx"> class FakeConduit(object):
</span><span class="cx">
</span><span class="lines">@@ -40,7 +45,7 @@
</span><span class="cx">
</span><span class="cx"> @inlineCallbacks
</span><span class="cx"> def setUp(self):
</span><del>- yield super(Conduit, self).setUp()
</del><ins>+ yield super(TestConduit, self).setUp()
</ins><span class="cx"> self._sqlCalendarStore = yield buildCalendarStore(self, self.notifierFactory)
</span><span class="cx"> self.directory = self._sqlCalendarStore.directoryService()
</span><span class="cx">
</span><span class="lines">@@ -113,4 +118,378 @@
</span><span class="cx">
</span><span class="cx"> self.assertRaises(DirectoryRecordNotFoundError, conduit.validRequst, "bogus01", "user02")
</span><span class="cx"> self.assertRaises(DirectoryRecordNotFoundError, conduit.validRequst, "user01", "bogus02")
</span><del>- self.assertRaises(InvalidCrossPodRequestError, conduit.validRequst, "user01", "user02")
</del><ins>+ self.assertRaises(FailedCrossPodRequestError, conduit.validRequst, "user01", "user02")
+
+
+
+class TestConduitToConduit(MultiStoreConduitTest):
+
+ class FakeConduit(PoddingConduit):
+
+ @inlineCallbacks
+ def send_fake(self, txn, ownerUID, shareeUID):
+ _ignore_owner, sharee = self.validRequst(ownerUID, shareeUID)
+ action = {
+ "action": "fake",
+ "echo": "bravo"
+ }
+
+ result = yield self.sendRequest(txn, sharee, action)
+ returnValue(result)
+
+
+ def recv_fake(self, txn, j):
+ return succeed({
+ "result": "ok",
+ "back2u": j["echo"],
+ "more": "bits",
+ })
+
+
+ def makeConduit(self, store):
+ """
+ Use our own variant.
+ """
+ conduit = self.FakeConduit(store)
+ conduit.conduitRequestClass = FakeConduitRequest
+ return conduit
+
+
+ @inlineCallbacks
+ def test_fake_action(self):
+ """
+ Cross-pod request works when conduit does support the action.
+ """
+
+ txn = self.transactionUnderTest()
+ store1 = self.storeUnderTest()
+ response = yield store1.conduit.send_fake(txn, "user01", "puser01")
+ self.assertTrue("result" in response)
+ self.assertEqual(response["result"], "ok")
+ self.assertTrue("back2u" in response)
+ self.assertEqual(response["back2u"], "bravo")
+ self.assertTrue("more" in response)
+ self.assertEqual(response["more"], "bits")
+ yield txn.commit()
+
+ store2 = self.otherStoreUnderTest()
+ txn = store2.newTransaction()
+ response = yield store2.conduit.send_fake(txn, "puser01", "user01")
+ self.assertTrue("result" in response)
+ self.assertEqual(response["result"], "ok")
+ self.assertTrue("back2u" in response)
+ self.assertEqual(response["back2u"], "bravo")
+ self.assertTrue("more" in response)
+ self.assertEqual(response["more"], "bits")
+ yield txn.commit()
+
+
+
+class TestConduitAPI(MultiStoreConduitTest):
+ """
+ Test that the conduit api works.
+ """
+
+ nowYear = {"now": DateTime.getToday().getYear()}
+
+ caldata1 = """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:uid1
+DTSTART:{now:04d}0102T140000Z
+DURATION:PT1H
+CREATED:20060102T190000Z
+DTSTAMP:20051222T210507Z
+RRULE:FREQ=WEEKLY
+SUMMARY:instance
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n").format(**nowYear)
+
+ caldata2 = """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:ui2
+DTSTART:{now:04d}0102T160000Z
+DURATION:PT1H
+CREATED:20060102T190000Z
+DTSTAMP:20051222T210507Z
+RRULE:FREQ=WEEKLY
+SUMMARY:instance
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n").format(**nowYear)
+
+ @inlineCallbacks
+ def test_basic_share(self):
+ """
+ Test that basic invite/uninvite works.
+ """
+
+ yield self.createShare("user01", "puser01")
+
+ calendar1 = yield self.calendarUnderTest(home="user01", name="calendar")
+ shared = yield calendar1.shareeView("puser01")
+ self.assertEqual(shared.shareStatus(), _BIND_STATUS_ACCEPTED)
+ yield self.commit()
+
+ shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home="puser01", name="shared-calendar")
+ self.assertTrue(shared is not None)
+ self.assertTrue(shared.external())
+ yield self.otherCommit()
+
+ calendar1 = yield self.calendarUnderTest(home="user01", name="calendar")
+ yield calendar1.uninviteUserFromShare("puser01")
+ yield self.commit()
+
+ shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home="puser01", name="shared-calendar")
+ self.assertTrue(shared is None)
+ yield self.otherCommit()
+
+
+ @inlineCallbacks
+ def test_countobjects(self):
+ """
+ Test that action=countobjects works.
+ """
+
+ yield self.createShare("user01", "puser01")
+
+ shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home="puser01", name="shared-calendar")
+ count = yield shared.countObjectResources()
+ self.assertEqual(count, 0)
+ yield self.otherCommit()
+
+ calendar1 = yield self.calendarUnderTest(home="user01", name="calendar")
+ yield calendar1.createCalendarObjectWithName("1.ics", Component.fromString(self.caldata1))
+ count = yield calendar1.countObjectResources()
+ self.assertEqual(count, 1)
+ yield self.commit()
+
+ shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home="puser01", name="shared-calendar")
+ count = yield shared.countObjectResources()
+ self.assertEqual(count, 1)
+ yield self.otherCommit()
+
+ calendar1 = yield self.calendarUnderTest(home="user01", name="calendar")
+ object1 = yield self.calendarObjectUnderTest(home="user01", calendar_name="calendar", name="1.ics")
+ yield object1.remove()
+ count = yield calendar1.countObjectResources()
+ self.assertEqual(count, 0)
+ yield self.commit()
+
+ shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home="puser01", name="shared-calendar")
+ count = yield shared.countObjectResources()
+ self.assertEqual(count, 0)
+ yield self.otherCommit()
+
+
+ @inlineCallbacks
+ def test_listobjects(self):
+ """
+ Test that action=listobjects works.
+ """
+
+ yield self.createShare("user01", "puser01")
+
+ shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home="puser01", name="shared-calendar")
+ objects = yield shared.listObjectResources()
+ self.assertEqual(set(objects), set())
+ yield self.otherCommit()
+
+ calendar1 = yield self.calendarUnderTest(home="user01", name="calendar")
+ yield calendar1.createCalendarObjectWithName("1.ics", Component.fromString(self.caldata1))
+ yield calendar1.createCalendarObjectWithName("2.ics", Component.fromString(self.caldata2))
+ objects = yield calendar1.listObjectResources()
+ self.assertEqual(set(objects), set(("1.ics", "2.ics",)))
+ yield self.commit()
+
+ shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home="puser01", name="shared-calendar")
+ objects = yield shared.listObjectResources()
+ self.assertEqual(set(objects), set(("1.ics", "2.ics",)))
+ yield self.otherCommit()
+
+ calendar1 = yield self.calendarUnderTest(home="user01", name="calendar")
+ object1 = yield self.calendarObjectUnderTest(home="user01", calendar_name="calendar", name="1.ics")
+ yield object1.remove()
+ objects = yield calendar1.listObjectResources()
+ self.assertEqual(set(objects), set(("2.ics",)))
+ yield self.commit()
+
+ shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home="puser01", name="shared-calendar")
+ objects = yield shared.listObjectResources()
+ self.assertEqual(set(objects), set(("2.ics",)))
+ yield self.otherCommit()
+
+
+ @inlineCallbacks
+ def test_synctoken(self):
+ """
+ Test that action=synctoken works.
+ """
+
+ yield self.createShare("user01", "puser01")
+
+ calendar1 = yield self.calendarUnderTest(home="user01", name="calendar")
+ token1_1 = yield calendar1.syncToken()
+ yield self.commit()
+
+ shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home="puser01", name="shared-calendar")
+ token2_1 = yield shared.syncToken()
+ yield self.otherCommit()
+
+ self.assertEqual(token1_1, token2_1)
+
+ calendar1 = yield self.calendarUnderTest(home="user01", name="calendar")
+ yield calendar1.createCalendarObjectWithName("1.ics", Component.fromString(self.caldata1))
+ yield self.commit()
+
+ calendar1 = yield self.calendarUnderTest(home="user01", name="calendar")
+ token1_2 = yield calendar1.syncToken()
+ yield self.commit()
+
+ shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home="puser01", name="shared-calendar")
+ token2_2 = yield shared.syncToken()
+ yield self.otherCommit()
+
+ self.assertNotEqual(token1_1, token1_2)
+ self.assertEqual(token1_2, token2_2)
+
+ calendar1 = yield self.calendarUnderTest(home="user01", name="calendar")
+ object1 = yield self.calendarObjectUnderTest(home="user01", calendar_name="calendar", name="1.ics")
+ yield object1.remove()
+ count = yield calendar1.countObjectResources()
+ self.assertEqual(count, 0)
+ yield self.commit()
+
+ calendar1 = yield self.calendarUnderTest(home="user01", name="calendar")
+ token1_3 = yield calendar1.syncToken()
+ yield self.commit()
+
+ shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home="puser01", name="shared-calendar")
+ token2_3 = yield shared.syncToken()
+ yield self.otherCommit()
+
+ self.assertNotEqual(token1_1, token1_3)
+ self.assertNotEqual(token1_2, token1_3)
+ self.assertEqual(token1_3, token2_3)
+
+
+ @inlineCallbacks
+ def test_resourcenamessincerevision(self):
+ """
+ Test that action=synctoken works.
+ """
+
+ yield self.createShare("user01", "puser01")
+
+ calendar1 = yield self.calendarUnderTest(home="user01", name="calendar")
+ token1_1 = yield calendar1.syncToken()
+ yield self.commit()
+
+ shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home="puser01", name="shared-calendar")
+ token2_1 = yield shared.syncToken()
+ yield self.otherCommit()
+
+ calendar1 = yield self.calendarUnderTest(home="user01", name="calendar")
+ yield calendar1.createCalendarObjectWithName("1.ics", Component.fromString(self.caldata1))
+ yield self.commit()
+
+ calendar1 = yield self.calendarUnderTest(home="user01", name="calendar")
+ token1_2 = yield calendar1.syncToken()
+ names1 = yield calendar1.resourceNamesSinceToken(token1_1)
+ self.assertEqual(names1, (["1.ics"], [],))
+ yield self.commit()
+
+ shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home="puser01", name="shared-calendar")
+ token2_2 = yield shared.syncToken()
+ names2 = yield shared.resourceNamesSinceToken(token2_1)
+ self.assertEqual(names2, (["1.ics"], [],))
+ yield self.otherCommit()
+
+ calendar1 = yield self.calendarUnderTest(home="user01", name="calendar")
+ object1 = yield self.calendarObjectUnderTest(home="user01", calendar_name="calendar", name="1.ics")
+ yield object1.remove()
+ count = yield calendar1.countObjectResources()
+ self.assertEqual(count, 0)
+ yield self.commit()
+
+ calendar1 = yield self.calendarUnderTest(home="user01", name="calendar")
+ token1_3 = yield calendar1.syncToken()
+ names1 = yield calendar1.resourceNamesSinceToken(token1_2)
+ self.assertEqual(names1, ([], ["1.ics"],))
+ yield self.commit()
+
+ shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home="puser01", name="shared-calendar")
+ token2_3 = yield shared.syncToken()
+ names2 = yield shared.resourceNamesSinceToken(token2_2)
+ self.assertEqual(names2, ([], ["1.ics"],))
+ yield self.otherCommit()
+
+ calendar1 = yield self.calendarUnderTest(home="user01", name="calendar")
+ names1 = yield calendar1.resourceNamesSinceToken(token1_3)
+ self.assertEqual(names1, ([], [],))
+ yield self.commit()
+
+ shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home="puser01", name="shared-calendar")
+ names2 = yield shared.resourceNamesSinceToken(token2_3)
+ self.assertEqual(names2, ([], [],))
+ yield self.otherCommit()
+
+
+ @inlineCallbacks
+ def test_resourceuidforname(self):
+ """
+ Test that action=resourceuidforname works.
+ """
+
+ yield self.createShare("user01", "puser01")
+
+ calendar1 = yield self.calendarUnderTest(home="user01", name="calendar")
+ yield calendar1.createCalendarObjectWithName("1.ics", Component.fromString(self.caldata1))
+ yield self.commit()
+
+ calendar1 = yield self.calendarUnderTest(home="user01", name="calendar")
+ uid = yield calendar1.resourceUIDForName("1.ics")
+ self.assertEqual(uid, "uid1")
+ uid = yield calendar1.resourceUIDForName("2.ics")
+ self.assertTrue(uid is None)
+ yield self.commit()
+
+ shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home="puser01", name="shared-calendar")
+ uid = yield shared.resourceUIDForName("1.ics")
+ self.assertEqual(uid, "uid1")
+ uid = yield shared.resourceUIDForName("2.ics")
+ self.assertTrue(uid is None)
+ yield self.otherCommit()
+
+
+ @inlineCallbacks
+ def test_resourcenameforuid(self):
+ """
+ Test that action=resourcenameforuid works.
+ """
+
+ yield self.createShare("user01", "puser01")
+
+ calendar1 = yield self.calendarUnderTest(home="user01", name="calendar")
+ yield calendar1.createCalendarObjectWithName("1.ics", Component.fromString(self.caldata1))
+ yield self.commit()
+
+ calendar1 = yield self.calendarUnderTest(home="user01", name="calendar")
+ uid = yield calendar1.resourceNameForUID("uid1")
+ self.assertEqual(uid, "1.ics")
+ uid = yield calendar1.resourceNameForUID("uid2")
+ self.assertTrue(uid is None)
+ yield self.commit()
+
+ shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home="puser01", name="shared-calendar")
+ uid = yield shared.resourceNameForUID("uid1")
+ self.assertEqual(uid, "1.ics")
+ uid = yield shared.resourceNameForUID("uid2")
+ self.assertTrue(uid is None)
+ yield self.otherCommit()
</ins></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastorepoddingtesttest_external_homepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/test/test_external_home.py (12050 => 12051)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/test/test_external_home.py        2013-12-11 15:40:55 UTC (rev 12050)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/test/test_external_home.py        2013-12-11 22:16:01 UTC (rev 12051)
</span><span class="lines">@@ -86,7 +86,8 @@
</span><span class="cx"> home = yield self.transactionUnderTest().calendarHomeWithUID("puser{:02d}".format(i), create=True)
</span><span class="cx"> self.assertTrue(home is not None)
</span><span class="cx"> self.assertEqual(home._status, _HOME_STATUS_EXTERNAL)
</span><del>- self.assertRaises(AssertionError, home.childWithName, "calendar")
</del><ins>+ calendar = yield home.childWithName("calendar")
+ self.assertTrue(calendar is None)
</ins><span class="cx">
</span><span class="cx">
</span><span class="cx"> @inlineCallbacks
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastorepoddingtesttest_resourcepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/test/test_resource.py (12050 => 12051)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/test/test_resource.py        2013-12-11 15:40:55 UTC (rev 12050)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/test/test_resource.py        2013-12-11 22:16:01 UTC (rev 12051)
</span><span class="lines">@@ -25,12 +25,13 @@
</span><span class="cx"> from txdav.common.datastore.podding.resource import ConduitResource
</span><span class="cx"> from txdav.common.datastore.test.util import populateCalendarsFrom, CommonCommonTests
</span><span class="cx"> import json
</span><ins>+from txdav.common.datastore.podding.conduit import PoddingConduit
</ins><span class="cx">
</span><span class="cx"> class ConduitPOST (CommonCommonTests, twext.web2.dav.test.util.TestCase):
</span><span class="cx">
</span><del>- class FakeConduit(object):
</del><ins>+ class FakeConduit(PoddingConduit):
</ins><span class="cx">
</span><del>- def recv_fake(self, j):
</del><ins>+ def recv_fake(self, txn, j):
</ins><span class="cx"> return succeed({
</span><span class="cx"> "result": "ok",
</span><span class="cx"> "back2u": j["echo"],
</span><span class="lines">@@ -214,7 +215,8 @@
</span><span class="cx"> Cross-pod request fails when conduit does not support the action.
</span><span class="cx"> """
</span><span class="cx">
</span><del>- self.patch(self.storeUnderTest(), "conduit", self.FakeConduit())
</del><ins>+ store = self.storeUnderTest()
+ self.patch(store, "conduit", self.FakeConduit(store))
</ins><span class="cx">
</span><span class="cx"> request = SimpleRequest(
</span><span class="cx"> self.site,
</span><span class="lines">@@ -242,7 +244,8 @@
</span><span class="cx"> Cross-pod request works when conduit does support the action.
</span><span class="cx"> """
</span><span class="cx">
</span><del>- self.patch(self.storeUnderTest(), "conduit", self.FakeConduit())
</del><ins>+ store = self.storeUnderTest()
+ self.patch(store, "conduit", self.FakeConduit(store))
</ins><span class="cx">
</span><span class="cx"> request = SimpleRequest(
</span><span class="cx"> self.site,
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastorepoddingtestutilpy"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/test/util.py (0 => 12051)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/test/util.py         (rev 0)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/test/util.py        2013-12-11 22:16:01 UTC (rev 12051)
</span><span class="lines">@@ -0,0 +1,202 @@
</span><ins>+##
+# Copyright (c) 2013 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# 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 "AS IS" 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 twisted.internet.defer import inlineCallbacks, returnValue
+
+from txdav.caldav.datastore.scheduling.ischedule.localservers import Server, \
+ Servers
+from txdav.caldav.datastore.test.util import \
+ TestCalendarStoreDirectoryRecord, TestCalendarStoreDirectoryService
+from txdav.common.datastore.podding.conduit import PoddingConduit
+from txdav.common.datastore.test.util import CommonCommonTests, SQLStoreBuilder,\
+ theStoreBuilder
+
+import twext.web2.dav.test.util
+from txdav.common.datastore.sql_tables import _BIND_MODE_WRITE
+
+class FakeConduitRequest(object):
+ """
+ A conduit request that sends messages internally rather than using HTTP
+ """
+
+ storeMap = {}
+
+ @classmethod
+ def addServerStore(cls, server, store):
+ """
+ Add a store mapped to a server. These mappings are used to "deliver" conduit
+ requests to the appropriate store.
+
+ @param uri: the server
+ @type uri: L{Server}
+ @param store: the store
+ @type store: L{ICommonDataStore}
+ """
+
+ cls.storeMap[server.details()] = store
+
+
+ def __init__(self, server, data):
+
+ self.server = server
+ self.data = data
+
+
+ @inlineCallbacks
+ def doRequest(self, txn):
+
+ # Generate an HTTP client request
+ try:
+ response = (yield self._processRequest())
+ except Exception as e:
+ raise ValueError("Failed cross-pod request: {}".format(e))
+
+ returnValue(response)
+
+
+ @inlineCallbacks
+ def _processRequest(self):
+ """
+ Process the request by sending it to the relevant server.
+
+ @return: the HTTP response.
+ @rtype: L{Response}
+ """
+
+ store = self.storeMap[self.server.details()]
+ result = yield store.conduit.processRequest(self.data)
+ returnValue(result)
+
+
+
+class MultiStoreConduitTest(CommonCommonTests, twext.web2.dav.test.util.TestCase):
+
+ theStoreBuilder2 = SQLStoreBuilder(secondary=True)
+ otherTransaction = None
+
+ @inlineCallbacks
+ def setUp(self):
+ yield super(MultiStoreConduitTest, self).setUp()
+
+ server1 = Server("A", "http://127.0.0.1:8008", "A", True)
+ Servers.addServer(server1)
+
+ server2 = Server("B", "http://127.0.0.1:8108", "B", False)
+ Servers.addServer(server2)
+
+ self._sqlCalendarStore1 = yield self.makeStore(theStoreBuilder, True, server1, server2)
+ self._sqlCalendarStore2 = yield self.makeStore(self.theStoreBuilder2, False, server1, server2)
+
+ FakeConduitRequest.addServerStore(server1, self._sqlCalendarStore1)
+ FakeConduitRequest.addServerStore(server2, self._sqlCalendarStore2)
+
+
+ def storeUnderTest(self):
+ """
+ Return a store for testing.
+ """
+ return self._sqlCalendarStore1
+
+
+ def otherStoreUnderTest(self):
+ """
+ Return a store for testing.
+ """
+ return self._sqlCalendarStore2
+
+
+ def newOtherTransaction(self):
+ assert self.otherTransaction is None
+ store2 = self.otherStoreUnderTest()
+ self.otherTransaction = store2.newTransaction()
+ return self.otherTransaction
+
+
+ @inlineCallbacks
+ def otherCommit(self):
+ assert self.otherTransaction is not None
+ yield self.otherTransaction.commit()
+ self.otherTransaction = None
+
+
+ @inlineCallbacks
+ def otherAbort(self):
+ assert self.otherTransaction is not None
+ yield self.otherTransaction.abort()
+ self.otherTransaction = None
+
+
+ @inlineCallbacks
+ def makeStore(self, builder, internal, server1, server2):
+
+ directory = self.makeDirectory(internal, server1, server2)
+ store = yield builder.buildStore(self, self.notifierFactory, directory)
+ store.queryCacher = None # Cannot use query caching
+ store.conduit = self.makeConduit(store)
+ returnValue(store)
+
+
+ def makeDirectory(self, internal, server1, server2):
+
+ directory = TestCalendarStoreDirectoryService()
+
+ # User accounts
+ for ctr in range(1, 100):
+ directory.addRecord(TestCalendarStoreDirectoryRecord(
+ "user%02d" % (ctr,),
+ ("user%02d" % (ctr,),),
+ "User %02d" % (ctr,),
+ frozenset((
+ "urn:uuid:user%02d" % (ctr,),
+ "mailto:user%02d@example.com" % (ctr,),
+ )),
+ thisServer=internal,
+ server=server1
+ ))
+
+ for ctr in range(1, 100):
+ directory.addRecord(TestCalendarStoreDirectoryRecord(
+ "puser{:02d}".format(ctr),
+ ("puser{:02d}".format(ctr),),
+ "Puser {:02d}".format(ctr),
+ frozenset((
+ "urn:uuid:puser{:02d}".format(ctr),
+ "mailto:puser{:02d}@example.com".format(ctr),
+ )),
+ thisServer=not internal,
+ server=server2
+ ))
+
+ return directory
+
+
+ def makeConduit(self, store):
+ conduit = PoddingConduit(store)
+ conduit.conduitRequestClass = FakeConduitRequest
+ return conduit
+
+
+ @inlineCallbacks
+ def createShare(self, ownerGUID, shareeGUID, name="calendar"):
+
+ home = yield self.homeUnderTest(name=ownerGUID, create=True)
+ calendar = yield home.calendarWithName("calendar")
+ yield calendar.inviteUserToShare(shareeGUID, _BIND_MODE_WRITE, "shared", shareName="shared-calendar")
+ yield self.commit()
+
+ home2 = yield self.homeUnderTest(txn=self.newOtherTransaction(), name=shareeGUID)
+ yield home2.acceptShare("shared-calendar")
+ yield self.otherCommit()
</ins></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastoresqlpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/sql.py (12050 => 12051)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/sql.py        2013-12-11 15:40:55 UTC (rev 12050)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/sql.py        2013-12-11 22:16:01 UTC (rev 12051)
</span><span class="lines">@@ -67,7 +67,7 @@
</span><span class="cx"> _BIND_MODE_INDIRECT, _HOME_STATUS_NORMAL, _HOME_STATUS_EXTERNAL
</span><span class="cx"> from txdav.common.datastore.sql_tables import schema, splitSQLString
</span><span class="cx"> from txdav.common.icommondatastore import ConcurrentModification, \
</span><del>- RecordNotAllowedError, ExternalShareFailed
</del><ins>+ RecordNotAllowedError, ExternalShareFailed, ShareNotAllowed
</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">@@ -477,7 +477,7 @@
</span><span class="cx"> self._label = label
</span><span class="cx"> self._migrating = migrating
</span><span class="cx"> self._primaryHomeType = None
</span><del>- self._disableCache = disableCache
</del><ins>+ self._disableCache = disableCache or not store.queryCachingEnabled()
</ins><span class="cx"> if disableCache:
</span><span class="cx"> self._queryCacher = None
</span><span class="cx"> else:
</span><span class="lines">@@ -1562,6 +1562,25 @@
</span><span class="cx">
</span><span class="cx"> _cacher = None # Initialize in derived classes
</span><span class="cx">
</span><ins>+ @classmethod
+ @inlineCallbacks
+ def makeClass(cls, transaction, ownerUID, no_cache=False):
+ """
+ Build the actual home class taking into account the possibility that we might need to
+ switch in the external version of the class.
+
+ @param transaction: transaction
+ @type transaction: L{CommonStoreTransaction}
+ @param ownerUID: owner UID of home to load
+ @type ownerUID: C{str}
+ @param no_cache: should cached query be used
+ @type no_cache: C{bool}
+ """
+ home = cls(transaction, ownerUID)
+ actualHome = yield home.initFromStore(no_cache)
+ returnValue(actualHome)
+
+
</ins><span class="cx"> def __init__(self, transaction, ownerUID):
</span><span class="cx"> self._txn = transaction
</span><span class="cx"> self._ownerUID = ownerUID
</span><span class="lines">@@ -1708,6 +1727,10 @@
</span><span class="cx"> actualHome = self
</span><span class="cx"> yield actualHome.initMetaDataFromStore()
</span><span class="cx"> yield actualHome._loadPropertyStore()
</span><ins>+
+ for factory_type, factory in self._txn._notifierFactories.items():
+ actualHome.addNotifier(factory_type, factory.newNotifier(actualHome))
+
</ins><span class="cx"> returnValue(actualHome)
</span><span class="cx"> else:
</span><span class="cx"> returnValue(None)
</span><span class="lines">@@ -1756,10 +1779,7 @@
</span><span class="cx"> @classmethod
</span><span class="cx"> @inlineCallbacks
</span><span class="cx"> def homeWithUID(cls, txn, uid, create=False):
</span><del>- homeObject = cls(txn, uid)
- for factory_type, factory in txn._notifierFactories.items():
- homeObject.addNotifier(factory_type, factory.newNotifier(homeObject))
- homeObject = (yield homeObject.initFromStore())
</del><ins>+ homeObject = yield cls.makeClass(txn, uid)
</ins><span class="cx"> if homeObject is not None:
</span><span class="cx"> returnValue(homeObject)
</span><span class="cx"> else:
</span><span class="lines">@@ -1769,7 +1789,7 @@
</span><span class="cx"> # Determine if the user is local or external
</span><span class="cx"> record = txn.directoryService().recordWithUID(uid)
</span><span class="cx"> if record is None:
</span><del>- raise DirectoryRecordNotFoundError("Cannot create home for UID since no directory record exists: {uid}".format(uid=uid))
</del><ins>+ raise DirectoryRecordNotFoundError("Cannot create home for UID since no directory record exists: {}".format(uid))
</ins><span class="cx">
</span><span class="cx"> state = _HOME_STATUS_NORMAL if record.thisServer() else _HOME_STATUS_EXTERNAL
</span><span class="cx">
</span><span class="lines">@@ -1794,10 +1814,7 @@
</span><span class="cx"> yield savepoint.rollback(txn)
</span><span class="cx">
</span><span class="cx"> # Retry the query - row may exist now, if not re-raise
</span><del>- homeObject = cls(txn, uid)
- for factory_type, factory in txn._notifierFactories.items():
- homeObject.addNotifier(factory_type, factory.newNotifier(homeObject))
- homeObject = (yield homeObject.initFromStore())
</del><ins>+ homeObject = yield cls.makeClass(txn, uid)
</ins><span class="cx"> if homeObject:
</span><span class="cx"> returnValue(homeObject)
</span><span class="cx"> else:
</span><span class="lines">@@ -1808,10 +1825,7 @@
</span><span class="cx"> # Note that we must not cache the owner_uid->resource_id
</span><span class="cx"> # mapping in _cacher when creating as we don't want that to appear
</span><span class="cx"> # until AFTER the commit
</span><del>- home = cls(txn, uid)
- for factory_type, factory in txn._notifierFactories.items():
- home.addNotifier(factory_type, factory.newNotifier(home))
- home = (yield home.initFromStore(no_cache=True))
</del><ins>+ home = yield cls.makeClass(txn, uid, no_cache=True)
</ins><span class="cx"> yield home.createdHome()
</span><span class="cx"> returnValue(home)
</span><span class="cx">
</span><span class="lines">@@ -2533,165 +2547,6 @@
</span><span class="cx">
</span><span class="cx">
</span><span class="cx">
</span><del>-class CommonHomeExternal(CommonHome):
- """
- A CommonHome for a user not hosted on this system, but on another pod. This is needed to provide a
- "reference" to the external user so we can share with them. Actual operations to list child resources, etc
- are all stubbed out since no data for the user is actually hosted in this store.
- """
-
- def __init__(self, transaction, ownerUID, resourceID):
- super(CommonHomeExternal, self).__init__(transaction, ownerUID)
- self._resourceID = resourceID
- self._status = _HOME_STATUS_EXTERNAL
-
-
- def initFromStore(self, no_cache=False):
- """
- Never called - this should be done by CommonHome.initFromStore only.
- """
- raise AssertionError("CommonHomeExternal: not supported")
-
-
- def external(self):
- """
- Is this an external home.
-
- @return: a string.
- """
- return True
-
-
- def children(self):
- """
- No children.
- """
- raise AssertionError("CommonHomeExternal: not supported")
-
-
- def loadChildren(self):
- """
- No children.
- """
- raise AssertionError("CommonHomeExternal: not supported")
-
-
- def listChildren(self):
- """
- No children.
- """
- raise AssertionError("CommonHomeExternal: not supported")
-
-
- def objectWithShareUID(self, shareUID):
- """
- No children.
- """
- raise AssertionError("CommonHomeExternal: not supported")
-
-
- def invitedObjectWithShareUID(self, shareUID):
- """
- No children.
- """
- raise AssertionError("CommonHomeExternal: not supported")
-
-
- @memoizedKey("name", "_children")
- @inlineCallbacks
- def createChildWithName(self, name, externalID=None):
- """
- No real children - only external ones.
- """
- if externalID is None:
- raise AssertionError("CommonHomeExternal: not supported")
- child = yield super(CommonHomeExternal, self).createChildWithName(name, externalID)
- returnValue(child)
-
-
- def removeChildWithName(self, name):
- """
- No children.
- """
- raise AssertionError("CommonHomeExternal: not supported")
-
-
- def syncToken(self):
- """
- No children.
- """
- raise AssertionError("CommonHomeExternal: not supported")
-
-
- def resourceNamesSinceRevision(self, revision, depth):
- """
- No children.
- """
- raise AssertionError("CommonHomeExternal: not supported")
-
-
- @inlineCallbacks
- def _loadPropertyStore(self):
- """
- No property store - stub to a NonePropertyStore.
- """
- props = yield PropertyStore.load(
- self.uid(),
- self.uid(),
- self._txn,
- self._resourceID,
- notifyCallback=self.notifyChanged
- )
- self._propertyStore = props
-
-
- def properties(self):
- return self._propertyStore
-
-
- def objectResourcesWithUID(self, uid, ignore_children=[], allowShared=True):
- """
- No children.
- """
- raise AssertionError("CommonHomeExternal: not supported")
-
-
- def objectResourceWithID(self, rid):
- """
- No children.
- """
- raise AssertionError("CommonHomeExternal: not supported")
-
-
- def notifyChanged(self):
- """
- Notifications are not handled for external homes - make this a no-op.
- """
- return succeed(None)
-
-
- def bumpModified(self):
- """
- No changes recorded for external homes - make this a no-op.
- """
- return succeed(None)
-
-
- def removeUnacceptedShares(self):
- """
- No children.
- """
- raise AssertionError("CommonHomeExternal: not supported")
-
-
-# def ownerHomeAndChildNameForChildID(self, resourceID):
-# """
-# No children.
-# """
-# raise AssertionError("CommonHomeExternal: not supported")
-
-
-
</del><span class="cx"> class _SharedSyncLogic(object):
</span><span class="cx"> """
</span><span class="cx"> Logic for maintaining sync-token shared between notification collections and
</span><span class="lines">@@ -3507,7 +3362,7 @@
</span><span class="cx"> subt,
</span><span class="cx"> homeID=shareeHome._resourceID,
</span><span class="cx"> resourceID=self._resourceID,
</span><del>- externalID=None,
</del><ins>+ externalID=self._externalID,
</ins><span class="cx"> name=newName,
</span><span class="cx"> mode=mode,
</span><span class="cx"> bindStatus=status,
</span><span class="lines">@@ -3997,6 +3852,7 @@
</span><span class="cx"> "_resourceID",
</span><span class="cx"> )
</span><span class="cx">
</span><ins>+ _externalClass = None
</ins><span class="cx"> _objectResourceClass = None
</span><span class="cx">
</span><span class="cx"> _bindSchema = None
</span><span class="lines">@@ -4007,6 +3863,142 @@
</span><span class="cx"> _objectSchema = None
</span><span class="cx">
</span><span class="cx">
</span><ins>+ @classmethod
+ @inlineCallbacks
+ def makeClass(cls, home, bindData, additionalBindData, metadataData, propstore=None, ownerHome=None):
+ """
+ Given the various database rows, build the actual class.
+
+ @param home: the parent home object
+ @type home: L{CommonHome}
+ @param bindData: the standard set of bind columns
+ @type bindData: C{list}
+ @param additionalBindData: additional bind data specific to sub-classes
+ @type additionalBindData: C{list}
+ @param metadataData: metadata data
+ @type metadataData: C{list}
+ @param propstore: a property store to use, or C{None} to load it automatically
+ @type propstore: L{PropertyStore}
+ @param ownerHome: the home of the owner, or C{None} to figure it out automatically
+ @type ownerHome: L{CommonHome}
+
+ @return: the constructed child class
+ @rtype: L{CommonHomeChild}
+ """
+
+ bindMode, _ignore_homeID, resourceID, externalID, name, bindStatus, bindRevision, bindMessage = bindData
+
+ if ownerHome is None:
+ if bindMode == _BIND_MODE_OWN:
+ ownerHome = home
+ ownerName = name
+ else:
+ ownerHome, ownerName = yield home.ownerHomeAndChildNameForChildID(resourceID)
+ else:
+ ownerName = None
+
+ c = cls._externalClass if ownerHome.external() else cls
+ child = c(
+ home=home,
+ name=name,
+ resourceID=resourceID,
+ mode=bindMode,
+ status=bindStatus,
+ revision=bindRevision,
+ message=bindMessage,
+ ownerHome=ownerHome,
+ ownerName=ownerName,
+ externalID=externalID,
+ )
+
+ if additionalBindData:
+ for attr, value in zip(child.additionalBindAttributes(), additionalBindData):
+ setattr(child, attr, value)
+
+ if metadataData:
+ for attr, value in zip(child.metadataAttributes(), metadataData):
+ setattr(child, attr, value)
+
+ # We have to re-adjust the property store object to account for possible shared
+ # collections as previously we loaded them all as if they were owned
+ if propstore and bindMode != _BIND_MODE_OWN:
+ propstore._setDefaultUserUID(ownerHome.uid())
+ yield child._loadPropertyStore(propstore)
+
+ returnValue(child)
+
+
+ @classmethod
+ @inlineCallbacks
+ def _getDBData(cls, home, name, resourceID, externalID):
+ """
+ Given a set of identifying information, load the data rows for the object. Only one of
+ L{name}, L{resourceID} or L{externalID} is specified - others are C{None}.
+
+ @param home: the parent home object
+ @type home: L{CommonHome}
+ @param name: the resource name
+ @type name: C{str}
+ @param resourceID: the resource ID
+ @type resourceID: C{int}
+ @param externalID: the resource ID of the external (cross-pod) referenced item
+ @type externalID: C{int}
+ """
+
+ # Get the bind row data
+ row = None
+ queryCacher = home._txn._queryCacher
+
+ if queryCacher:
+ # Retrieve data from cache
+ if name:
+ cacheKey = queryCacher.keyForObjectWithName(home._resourceID, name)
+ elif resourceID:
+ cacheKey = queryCacher.keyForObjectWithResourceID(home._resourceID, resourceID)
+ elif externalID:
+ cacheKey = queryCacher.keyForObjectWithExternalID(home._resourceID, externalID)
+ row = yield queryCacher.get(cacheKey)
+
+ if row is None:
+ # No cached copy
+ if name:
+ rows = yield cls._bindForNameAndHomeID.on(home._txn, name=name, homeID=home._resourceID)
+ elif resourceID:
+ rows = yield cls._bindForResourceIDAndHomeID.on(home._txn, resourceID=resourceID, homeID=home._resourceID)
+ elif externalID:
+ rows = yield cls._bindForExternalIDAndHomeID.on(home._txn, externalID=externalID, homeID=home._resourceID)
+ row = rows[0] if rows else None
+
+ if not row:
+ returnValue(None)
+
+ if queryCacher:
+ # Cache the result
+ queryCacher.setAfterCommit(home._txn, queryCacher.keyForObjectWithName(home._resourceID, name), row)
+ queryCacher.setAfterCommit(home._txn, queryCacher.keyForObjectWithResourceID(home._resourceID, resourceID), row)
+ queryCacher.setAfterCommit(home._txn, queryCacher.keyForObjectWithExternalID(home._resourceID, externalID), row)
+
+ bindData = row[:cls.bindColumnCount]
+ additionalBindData = row[cls.bindColumnCount:cls.bindColumnCount + len(cls.additionalBindColumns())]
+ resourceID = bindData[cls.bindColumns().index(cls._bindSchema.RESOURCE_ID)]
+
+ # Get the matching metadata data
+ metadataData = None
+ if queryCacher:
+ # Retrieve from cache
+ cacheKey = queryCacher.keyForHomeChildMetaData(resourceID)
+ metadataData = yield queryCacher.get(cacheKey)
+
+ if metadataData is None:
+ # No cached copy
+ metadataData = (yield cls._metadataByIDQuery.on(home._txn, resourceID=resourceID))[0]
+ if queryCacher:
+ # Cache the results
+ yield queryCacher.setAfterCommit(home._txn, cacheKey, metadataData)
+
+ returnValue((bindData, additionalBindData, metadataData,))
+
+
</ins><span class="cx"> def __init__(self, home, name, resourceID, mode, status, revision=0, message=None, ownerHome=None, ownerName=None, externalID=None):
</span><span class="cx">
</span><span class="cx"> self._home = home
</span><span class="lines">@@ -4062,7 +4054,7 @@
</span><span class="cx"> rows = yield cls._acceptedBindForHomeID.on(
</span><span class="cx"> home._txn, homeID=home._resourceID
</span><span class="cx"> )
</span><del>- names = [row[3] for row in rows]
</del><ins>+ names = [row[cls.bindColumns().index(cls._bindSchema.RESOURCE_NAME)] for row in rows]
</ins><span class="cx"> returnValue(names)
</span><span class="cx">
</span><span class="cx">
</span><span class="lines">@@ -4095,41 +4087,16 @@
</span><span class="cx">
</span><span class="cx"> # Create the actual objects merging in properties
</span><span class="cx"> for dataRow in dataRows:
</span><del>- bindMode, homeID, resourceID, externalID, bindName, bindStatus, bindRevision, bindMessage = dataRow[:cls.bindColumnCount] #@UnusedVariable
- additionalBind = dataRow[cls.bindColumnCount:cls.bindColumnCount + len(cls.additionalBindColumns())]
- metadata = dataRow[cls.bindColumnCount + len(cls.additionalBindColumns()):]
</del><ins>+ bindData = dataRow[:cls.bindColumnCount] #@UnusedVariable
+ resourceID = bindData[cls.bindColumns().index(cls._bindSchema.RESOURCE_ID)]
+ additionalBindData = dataRow[cls.bindColumnCount:cls.bindColumnCount + len(cls.additionalBindColumns())]
+ metadataData = dataRow[cls.bindColumnCount + len(cls.additionalBindColumns()):]
+ propstore = propertyStores.get(resourceID, None)
</ins><span class="cx">
</span><del>- if bindMode == _BIND_MODE_OWN:
- ownerHome = home
- ownerName = bindName
- else:
- #TODO: get all ownerHomeIDs at once
- ownerHome, ownerName = yield home.ownerHomeAndChildNameForChildID(resourceID)
-
- child = cls(
- home=home,
- name=bindName,
- resourceID=resourceID,
- mode=bindMode,
- status=bindStatus,
- revision=bindRevision,
- message=bindMessage,
- ownerHome=ownerHome,
- ownerName=ownerName,
- externalID=externalID,
- )
- for attr, value in zip(cls.additionalBindAttributes(), additionalBind):
- setattr(child, attr, value)
- for attr, value in zip(cls.metadataAttributes(), metadata):
- setattr(child, attr, value)
</del><ins>+ child = yield cls.makeClass(home, bindData, additionalBindData, metadataData, propstore)
</ins><span class="cx"> child._syncTokenRevision = revisions[resourceID]
</span><del>- propstore = propertyStores.get(resourceID, None)
- # We have to re-adjust the property store object to account for possible shared
- # collections as previously we loaded them all as if they were owned
- if propstore and bindMode != _BIND_MODE_OWN:
- propstore._setDefaultUserUID(ownerHome.uid())
- yield child._loadPropertyStore(propstore)
</del><span class="cx"> results.append(child)
</span><ins>+
</ins><span class="cx"> returnValue(results)
</span><span class="cx">
</span><span class="cx">
</span><span class="lines">@@ -4163,63 +4130,17 @@
</span><span class="cx"> @return: an L{CommonHomeChild} or C{None} if no such child
</span><span class="cx"> exists.
</span><span class="cx"> """
</span><del>- rows = None
- queryCacher = home._txn._queryCacher
</del><span class="cx">
</span><del>- if queryCacher:
- # Retrieve data from cache
- if name:
- cacheKey = queryCacher.keyForObjectWithName(home._resourceID, name)
- elif resourceID:
- cacheKey = queryCacher.keyForObjectWithResourceID(home._resourceID, resourceID)
- elif externalID:
- cacheKey = queryCacher.keyForObjectWithExternalID(home._resourceID, externalID)
- rows = yield queryCacher.get(cacheKey)
-
- if rows is None:
- # No cached copy
- if name:
- rows = yield cls._bindForNameAndHomeID.on(home._txn, name=name, homeID=home._resourceID)
- elif resourceID:
- rows = yield cls._bindForResourceIDAndHomeID.on(home._txn, resourceID=resourceID, homeID=home._resourceID)
- elif resourceID:
- rows = yield cls._bindForExternalIDAndHomeID.on(home._txn, externalID=externalID, homeID=home._resourceID)
-
- if not rows:
</del><ins>+ dbData = yield cls._getDBData(home, name, resourceID, externalID)
+ if dbData is None:
</ins><span class="cx"> returnValue(None)
</span><ins>+ bindData, additionalBindData, metadataData = dbData
</ins><span class="cx">
</span><del>- row = rows[0]
- bindMode, homeID, resourceID, externalID, name, bindStatus, bindRevision, bindMessage = row[:cls.bindColumnCount] #@UnusedVariable
-
- if queryCacher:
- # Cache the result
- queryCacher.setAfterCommit(home._txn, queryCacher.keyForObjectWithName(home._resourceID, name), rows)
- queryCacher.setAfterCommit(home._txn, queryCacher.keyForObjectWithResourceID(home._resourceID, resourceID), rows)
- queryCacher.setAfterCommit(home._txn, queryCacher.keyForObjectWithExternalID(home._resourceID, externalID), rows)
-
</del><ins>+ bindStatus = bindData[cls.bindColumns().index(cls._bindSchema.BIND_STATUS)]
</ins><span class="cx"> if accepted is not None and (bindStatus == _BIND_STATUS_ACCEPTED) != bool(accepted):
</span><span class="cx"> returnValue(None)
</span><del>- additionalBind = row[cls.bindColumnCount:cls.bindColumnCount + len(cls.additionalBindColumns())]
</del><span class="cx">
</span><del>- if bindMode == _BIND_MODE_OWN:
- ownerHome = home
- ownerName = name
- else:
- ownerHome, ownerName = yield home.ownerHomeAndChildNameForChildID(resourceID)
-
- child = cls(
- home=home,
- name=name,
- resourceID=resourceID,
- mode=bindMode,
- status=bindStatus,
- revision=bindRevision,
- message=bindMessage,
- ownerHome=ownerHome,
- ownerName=ownerName,
- externalID=externalID,
- )
- yield child.initFromStore(additionalBind)
</del><ins>+ child = yield cls.makeClass(home, bindData, additionalBindData, metadataData)
</ins><span class="cx"> returnValue(child)
</span><span class="cx">
</span><span class="cx">
</span><span class="lines">@@ -4289,36 +4210,6 @@
</span><span class="cx"> Where=child.RESOURCE_ID == Parameter("resourceID"))
</span><span class="cx">
</span><span class="cx">
</span><del>- @inlineCallbacks
- def initFromStore(self, additionalBind=None):
- """
- Initialise this object from the store, based on its already-populated
- resource ID. We read in and cache all the extra metadata from the DB to
- avoid having to do DB queries for those individually later.
- """
- queryCacher = self._txn._queryCacher
- if queryCacher:
- # Retrieve from cache
- cacheKey = queryCacher.keyForHomeChildMetaData(self._resourceID)
- dataRows = yield queryCacher.get(cacheKey)
- else:
- dataRows = None
- if dataRows is None:
- # No cached copy
- dataRows = (yield self._metadataByIDQuery.on(self._txn, resourceID=self._resourceID))[0]
- if queryCacher:
- # Cache the results
- yield queryCacher.setAfterCommit(self._txn, cacheKey, dataRows)
-
- if additionalBind:
- for attr, value in zip(self.additionalBindAttributes(), additionalBind):
- setattr(self, attr, value)
-
- for attr, value in zip(self.metadataAttributes(), dataRows):
- setattr(self, attr, value)
- yield self._loadPropertyStore()
-
-
</del><span class="cx"> def id(self):
</span><span class="cx"> """
</span><span class="cx"> Retrieve the store identifier for this collection.
</span><span class="lines">@@ -4329,6 +4220,15 @@
</span><span class="cx"> return self._resourceID
</span><span class="cx">
</span><span class="cx">
</span><ins>+ def external_id(self):
+ """
+ Retrieve the external store identifier for this collection.
+
+ @return: a string.
+ """
+ return self._externalID
+
+
</ins><span class="cx"> def external(self):
</span><span class="cx"> """
</span><span class="cx"> Is this an external home.
</span><span class="lines">@@ -4385,6 +4285,10 @@
</span><span class="cx">
</span><span class="cx"> @return: a L{Deferred} which fires when the modification is complete.
</span><span class="cx"> """
</span><ins>+
+ if self.isShared() or self.external():
+ raise ShareNotAllowed("Cannot rename a shared collection")
+
</ins><span class="cx"> oldName = self._name
</span><span class="cx">
</span><span class="cx"> yield self.invalidateQueryCache()
</span><span class="lines">@@ -4521,8 +4425,7 @@
</span><span class="cx"> @inlineCallbacks
</span><span class="cx"> def countObjectResources(self):
</span><span class="cx"> if self._objectNames is None:
</span><del>- rows = yield self._objectCountQuery.on(
- self._txn, resourceID=self._resourceID)
</del><ins>+ rows = yield self._objectCountQuery.on(self._txn, resourceID=self._resourceID)
</ins><span class="cx"> returnValue(rows[0][0])
</span><span class="cx"> returnValue(len(self._objectNames))
</span><span class="cx">
</span><span class="lines">@@ -4612,7 +4515,7 @@
</span><span class="cx"> obj = cls._objectSchema
</span><span class="cx"> return Select(
</span><span class="cx"> [obj.UID], From=obj,
</span><del>- Where=(obj.UID == Parameter("name")
</del><ins>+ Where=(obj.RESOURCE_NAME == Parameter("name")
</ins><span class="cx"> ).And(obj.PARENT_RESOURCE_ID == Parameter("resourceID")))
</span><span class="cx">
</span><span class="cx">
</span><span class="lines">@@ -4971,10 +4874,84 @@
</span><span class="cx"> "_parentCollection",
</span><span class="cx"> )
</span><span class="cx">
</span><ins>+ _externalClass = None
</ins><span class="cx"> _objectSchema = None
</span><span class="cx">
</span><span class="cx"> BATCH_LOAD_SIZE = 50
</span><span class="cx">
</span><ins>+
+ @classmethod
+ @inlineCallbacks
+ def makeClass(cls, parent, objectData, propstore=None):
+ """
+ Given the various database rows, build the actual class.
+
+ @param parent: the parent collection object
+ @type parent: L{CommonHomeChild}
+ @param objectData: the standard set of object columns
+ @type objectData: C{list}
+ @param propstore: a property store to use, or C{None} to load it automatically
+ @type propstore: L{PropertyStore}
+
+ @return: the constructed child class
+ @rtype: L{CommonHomeChild}
+ """
+
+ c = cls._externalClass if parent.external() else cls
+ child = c(
+ parent,
+ objectData[cls._allColumns().index(cls._objectSchema.RESOURCE_NAME)],
+ objectData[cls._allColumns().index(cls._objectSchema.UID)],
+ )
+
+ for attr, value in zip(child._rowAttributes(), objectData):
+ setattr(child, attr, value)
+
+ yield child._loadPropertyStore(propstore)
+
+ returnValue(child)
+
+
+ @classmethod
+ @inlineCallbacks
+ def _getDBData(cls, parent, name, uid, resourceID):
+ """
+ Given a set of identifying information, load the data rows for the object. Only one of
+ L{name}, L{uid} or L{resourceID} is specified - others are C{None}.
+
+ @param parent: the parent collection object
+ @type parent: L{CommonHomeChild}
+ @param name: the resource name
+ @type name: C{str}
+ @param uid: the UID of the data
+ @type uid: C{str}
+ @param resourceID: the resource ID
+ @type resourceID: C{int}
+ """
+
+ rows = None
+ if name:
+ rows = yield cls._allColumnsWithParentAndName.on(
+ parent._txn,
+ name=name,
+ parentID=parent._resourceID
+ )
+ elif uid:
+ rows = yield cls._allColumnsWithParentAndUID.on(
+ parent._txn,
+ uid=uid,
+ parentID=parent._resourceID
+ )
+ elif resourceID:
+ rows = yield cls._allColumnsWithParentAndID.on(
+ parent._txn,
+ resourceID=resourceID,
+ parentID=parent._resourceID
+ )
+
+ returnValue(rows[0] if rows else None)
+
+
</ins><span class="cx"> def __init__(self, parent, name, uid, resourceID=None, options=None): #@UnusedVariable
</span><span class="cx"> self._parentCollection = parent
</span><span class="cx"> self._resourceID = resourceID
</span><span class="lines">@@ -4992,7 +4969,7 @@
</span><span class="cx"> @classproperty
</span><span class="cx"> def _allColumnsWithParentQuery(cls): #@NoSelf
</span><span class="cx"> obj = cls._objectSchema
</span><del>- return Select(cls._allColumns, From=obj,
</del><ins>+ return Select(cls._allColumns(), From=obj,
</ins><span class="cx"> Where=obj.PARENT_RESOURCE_ID == Parameter("parentID"))
</span><span class="cx">
</span><span class="cx">
</span><span class="lines">@@ -5033,11 +5010,10 @@
</span><span class="cx">
</span><span class="cx"> # Create the actual objects merging in properties
</span><span class="cx"> for row in dataRows:
</span><del>- child = cls(parent, "", None)
- child._initFromRow(tuple(row))
- yield child._loadPropertyStore(
- props=propertyStores.get(child._resourceID, None)
- )
</del><ins>+ resourceID = row[cls._allColumns().index(cls._objectSchema.RESOURCE_ID)]
+ propstore = propertyStores.get(resourceID, None)
+
+ child = yield cls.makeClass(parent, row, propstore=propstore)
</ins><span class="cx"> results.append(child)
</span><span class="cx">
</span><span class="cx"> returnValue(results)
</span><span class="lines">@@ -5062,7 +5038,7 @@
</span><span class="cx"> @classmethod
</span><span class="cx"> def _allColumnsWithParentAndNamesQuery(cls, names):
</span><span class="cx"> obj = cls._objectSchema
</span><del>- return Select(cls._allColumns, From=obj,
</del><ins>+ return Select(cls._allColumns(), From=obj,
</ins><span class="cx"> Where=(obj.PARENT_RESOURCE_ID == Parameter("parentID")).And(
</span><span class="cx"> obj.RESOURCE_NAME.In(Parameter("names", len(names)))))
</span><span class="cx">
</span><span class="lines">@@ -5107,11 +5083,10 @@
</span><span class="cx">
</span><span class="cx"> # Create the actual objects merging in properties
</span><span class="cx"> for row in dataRows:
</span><del>- child = cls(parent, "", None)
- child._initFromRow(tuple(row))
- yield child._loadPropertyStore(
- props=propertyStores.get(child._resourceID, None)
- )
</del><ins>+ resourceID = row[cls._allColumns().index(cls._objectSchema.RESOURCE_ID)]
+ propstore = propertyStores.get(resourceID, None)
+
+ child = yield cls.makeClass(parent, row, propstore=propstore)
</ins><span class="cx"> results.append(child)
</span><span class="cx">
</span><span class="cx"> returnValue(results)
</span><span class="lines">@@ -5119,18 +5094,29 @@
</span><span class="cx">
</span><span class="cx"> @classmethod
</span><span class="cx"> def objectWithName(cls, parent, name, uid):
</span><del>- objectResource = cls(parent, name, uid, None)
- return objectResource.initFromStore()
</del><ins>+ return cls._objectWithNameOrID(parent, name, uid, None)
</ins><span class="cx">
</span><span class="cx">
</span><span class="cx"> @classmethod
</span><span class="cx"> def objectWithID(cls, parent, resourceID):
</span><del>- objectResource = cls(parent, None, None, resourceID)
- return objectResource.initFromStore()
</del><ins>+ return cls._objectWithNameOrID(parent, None, None, resourceID)
</ins><span class="cx">
</span><span class="cx">
</span><span class="cx"> @classmethod
</span><span class="cx"> @inlineCallbacks
</span><ins>+ def _objectWithNameOrID(cls, parent, name, uid, resourceID):
+
+ row = yield cls._getDBData(parent, name, uid, resourceID)
+
+ if row:
+ child = yield cls.makeClass(parent, row)
+ returnValue(child)
+ else:
+ returnValue(None)
+
+
+ @classmethod
+ @inlineCallbacks
</ins><span class="cx"> def create(cls, parent, name, component, options=None):
</span><span class="cx">
</span><span class="cx"> child = (yield parent.objectResourceWithName(name))
</span><span class="lines">@@ -5140,13 +5126,13 @@
</span><span class="cx"> if name.startswith("."):
</span><span class="cx"> raise ObjectResourceNameNotAllowedError(name)
</span><span class="cx">
</span><del>- objectResource = cls(parent, name, None, None, options=options)
</del><ins>+ c = cls._externalClass if parent.external() else cls
+ objectResource = c(parent, name, None, None, options=options)
</ins><span class="cx"> yield objectResource.setComponent(component, inserting=True)
</span><span class="cx"> yield objectResource._loadPropertyStore(created=True)
</span><span class="cx">
</span><span class="cx"> # Note: setComponent triggers a notification, so we don't need to
</span><span class="cx"> # call notify( ) here like we do for object removal.
</span><del>-
</del><span class="cx"> returnValue(objectResource)
</span><span class="cx">
</span><span class="cx">
</span><span class="lines">@@ -5157,7 +5143,7 @@
</span><span class="cx"> parameter and a given instance column matches a given parameter name.
</span><span class="cx"> """
</span><span class="cx"> return Select(
</span><del>- cls._allColumns, From=cls._objectSchema,
</del><ins>+ cls._allColumns(), From=cls._objectSchema,
</ins><span class="cx"> Where=(column == Parameter(paramName)).And(
</span><span class="cx"> cls._objectSchema.PARENT_RESOURCE_ID == Parameter("parentID"))
</span><span class="cx"> )
</span><span class="lines">@@ -5178,38 +5164,7 @@
</span><span class="cx"> return cls._allColumnsWithParentAnd(cls._objectSchema.RESOURCE_ID, "resourceID")
</span><span class="cx">
</span><span class="cx">
</span><del>- @inlineCallbacks
- def initFromStore(self):
- """
- Initialise this object from the store. We read in and cache all the
- extra metadata from the DB to avoid having to do DB queries for those
- individually later. Either the name or uid is present, so we have to
- tweak the query accordingly.
-
- @return: L{self} if object exists in the DB, else C{None}
- """
-
- if self._name:
- rows = yield self._allColumnsWithParentAndName.on(
- self._txn, name=self._name,
- parentID=self._parentCollection._resourceID)
- elif self._uid:
- rows = yield self._allColumnsWithParentAndUID.on(
- self._txn, uid=self._uid,
- parentID=self._parentCollection._resourceID)
- elif self._resourceID:
- rows = yield self._allColumnsWithParentAndID.on(
- self._txn, resourceID=self._resourceID,
- parentID=self._parentCollection._resourceID)
- if rows:
- self._initFromRow(tuple(rows[0]))
- yield self._loadPropertyStore()
- returnValue(self)
- else:
- returnValue(None)
-
-
- @classproperty
</del><ins>+ @classmethod
</ins><span class="cx"> def _allColumns(cls): #@NoSelf
</span><span class="cx"> """
</span><span class="cx"> Full set of columns in the object table that need to be loaded to
</span><span class="lines">@@ -5227,18 +5182,17 @@
</span><span class="cx"> ]
</span><span class="cx">
</span><span class="cx">
</span><del>- def _initFromRow(self, row):
- """
- Given a select result using the columns from L{_allColumns}, initialize
- the object resource state.
- """
- (self._resourceID,
- self._name,
- self._uid,
- self._md5,
- self._size,
- self._created,
- self._modified,) = tuple(row)
</del><ins>+ @classmethod
+ def _rowAttributes(cls): #@NoSelf
+ return (
+ "_resourceID",
+ "_name",
+ "_uid",
+ "_md5",
+ "_size",
+ "_created",
+ "_modified",
+ )
</ins><span class="cx">
</span><span class="cx">
</span><span class="cx"> @inlineCallbacks
</span><span class="lines">@@ -5542,11 +5496,11 @@
</span><span class="cx"> # Determine if the user is local or external
</span><span class="cx"> record = txn.directoryService().recordWithUID(uid)
</span><span class="cx"> if record is None:
</span><del>- raise DirectoryRecordNotFoundError("Cannot create home for UID since no directory record exists: {uid}".format(uid=uid))
</del><ins>+ raise DirectoryRecordNotFoundError("Cannot create home for UID since no directory record exists: {}".format(uid))
</ins><span class="cx">
</span><span class="cx"> state = _HOME_STATUS_NORMAL if record.thisServer() else _HOME_STATUS_EXTERNAL
</span><span class="cx"> if state == _HOME_STATUS_EXTERNAL:
</span><del>- raise RecordNotAllowedError("Cannot store notifications for external user: {uid}".format(uid=uid))
</del><ins>+ raise RecordNotAllowedError("Cannot store notifications for external user: {}".format(uid))
</ins><span class="cx">
</span><span class="cx"> # Use savepoint so we can do a partial rollback if there is a race
</span><span class="cx"> # condition where this row has already been inserted
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastoresql_externalpy"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/sql_external.py (0 => 12051)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/sql_external.py         (rev 0)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/sql_external.py        2013-12-11 22:16:01 UTC (rev 12051)
</span><span class="lines">@@ -0,0 +1,354 @@
</span><ins>+# -*- test-case-name: txdav.caldav.datastore.test.test_sql,txdav.carddav.datastore.test.test_sql -*-
+##
+# Copyright (c) 2013 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# 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 "AS IS" 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 txdav.common.icommondatastore import NonExistentExternalShare, \
+ ExternalShareFailed
+"""
+SQL data store.
+"""
+
+from twisted.internet.defer import inlineCallbacks, returnValue, succeed
+
+from twext.internet.decorate import memoizedKey
+from twext.python.log import Logger
+
+from txdav.base.propertystore.sql import PropertyStore
+from txdav.common.datastore.sql import CommonHome, CommonHomeChild, \
+ CommonObjectResource
+from txdav.common.datastore.sql_tables import _HOME_STATUS_EXTERNAL
+
+
+log = Logger()
+
+class CommonHomeExternal(CommonHome):
+ """
+ A CommonHome for a user not hosted on this system, but on another pod. This is needed to provide a
+ "reference" to the external user so we can share with them. Actual operations to list child resources, etc
+ are all stubbed out since no data for the user is actually hosted in this store.
+ """
+
+ def __init__(self, transaction, ownerUID, resourceID):
+ super(CommonHomeExternal, self).__init__(transaction, ownerUID)
+ self._resourceID = resourceID
+ self._status = _HOME_STATUS_EXTERNAL
+
+
+ def initFromStore(self, no_cache=False):
+ """
+ Never called - this should be done by CommonHome.initFromStore only.
+ """
+ raise AssertionError("CommonHomeExternal: not supported")
+
+
+ def external(self):
+ """
+ Is this an external home.
+
+ @return: a string.
+ """
+ return True
+
+
+ def children(self):
+ """
+ No children.
+ """
+ raise AssertionError("CommonHomeExternal: not supported")
+
+
+ def loadChildren(self):
+ """
+ No children.
+ """
+ raise AssertionError("CommonHomeExternal: not supported")
+
+
+ def listChildren(self):
+ """
+ No children.
+ """
+ raise AssertionError("CommonHomeExternal: not supported")
+
+
+ def objectWithShareUID(self, shareUID):
+ """
+ No children.
+ """
+ raise AssertionError("CommonHomeExternal: not supported")
+
+
+ def invitedObjectWithShareUID(self, shareUID):
+ """
+ No children.
+ """
+ raise AssertionError("CommonHomeExternal: not supported")
+
+
+ @memoizedKey("name", "_children")
+ @inlineCallbacks
+ def createChildWithName(self, name, externalID=None):
+ """
+ No real children - only external ones.
+ """
+ if externalID is None:
+ raise AssertionError("CommonHomeExternal: not supported")
+ child = yield super(CommonHomeExternal, self).createChildWithName(name, externalID)
+ returnValue(child)
+
+
+ def removeChildWithName(self, name):
+ """
+ No children.
+ """
+ raise AssertionError("CommonHomeExternal: not supported")
+
+
+ def syncToken(self):
+ """
+ No children.
+ """
+ raise AssertionError("CommonHomeExternal: not supported")
+
+
+ def resourceNamesSinceRevision(self, revision, depth):
+ """
+ No children.
+ """
+ raise AssertionError("CommonHomeExternal: not supported")
+
+
+ @inlineCallbacks
+ def _loadPropertyStore(self):
+ """
+ No property store - stub to a NonePropertyStore.
+ """
+ props = yield PropertyStore.load(
+ self.uid(),
+ self.uid(),
+ self._txn,
+ self._resourceID,
+ notifyCallback=self.notifyChanged
+ )
+ self._propertyStore = props
+
+
+ def properties(self):
+ return self._propertyStore
+
+
+ def objectResourcesWithUID(self, uid, ignore_children=[], allowShared=True):
+ """
+ No children.
+ """
+ raise AssertionError("CommonHomeExternal: not supported")
+
+
+ def objectResourceWithID(self, rid):
+ """
+ No children.
+ """
+ raise AssertionError("CommonHomeExternal: not supported")
+
+
+ def notifyChanged(self):
+ """
+ Notifications are not handled for external homes - make this a no-op.
+ """
+ return succeed(None)
+
+
+ def bumpModified(self):
+ """
+ No changes recorded for external homes - make this a no-op.
+ """
+ return succeed(None)
+
+
+ def removeUnacceptedShares(self):
+ """
+ No children.
+ """
+ raise AssertionError("CommonHomeExternal: not supported")
+
+
+# def ownerHomeAndChildNameForChildID(self, resourceID):
+# """
+# No children.
+# """
+# raise AssertionError("CommonHomeExternal: not supported")
+
+
+
+class CommonHomeChildExternal(CommonHomeChild):
+ """
+ A CommonHomeChild for a collection not hosted on this system, but on another pod. This will forward
+ specific apis to the other pod using cross-pod requests.
+ """
+
+ def external(self):
+ """
+ Is this an external home.
+
+ @return: a string.
+ """
+ return True
+
+
+ def fixNonExistentExternalShare(self):
+ """
+ An external request has returned and indicates the external share no longer exists. That
+ means this shared resource is an "orphan" and needs to be remove (uninvited) to clean things up.
+ """
+ log.error("Non-existent share detected and removed for {share}", share=self)
+ ownerView = yield self.ownerView()
+ yield ownerView.removeShare(self)
+
+
+ def remove(self, rid):
+ """
+ External shares are never removed directly - instead they must be "uninvited".
+ """
+ raise AssertionError("CommonHomeChildExternal: not supported")
+
+
+ @inlineCallbacks
+ def objectResources(self):
+ raise NotImplementedError("TODO: external resource")
+
+
+ @inlineCallbacks
+ def objectResourcesWithNames(self, names):
+ raise NotImplementedError("TODO: external resource")
+
+
+ @inlineCallbacks
+ def listObjectResources(self):
+ if self._objectNames is None:
+ try:
+ self._objectNames = yield self._txn.store().conduit.send_listobjects(self)
+ except NonExistentExternalShare:
+ yield self.fixNonExistentExternalShare()
+ raise ExternalShareFailed("External share does not exist")
+
+ returnValue(self._objectNames)
+
+
+ @inlineCallbacks
+ def countObjectResources(self):
+ if self._objectNames is None:
+ try:
+ count = yield self._txn.store().conduit.send_countobjects(self)
+ except NonExistentExternalShare:
+ yield self.fixNonExistentExternalShare()
+ raise ExternalShareFailed("External share does not exist")
+ returnValue(count)
+ returnValue(len(self._objectNames))
+
+
+ def objectResourceWithName(self, name):
+ raise NotImplementedError("TODO: external resource")
+
+
+ def objectResourceWithUID(self, uid):
+ raise NotImplementedError("TODO: external resource")
+
+
+ def objectResourceWithID(self, resourceID):
+ raise NotImplementedError("TODO: external resource")
+
+
+ @inlineCallbacks
+ def resourceNameForUID(self, uid):
+ try:
+ resource = self._objects[uid]
+ returnValue(resource.name() if resource else None)
+ except KeyError:
+ pass
+
+ try:
+ name = yield self._txn.store().conduit.send_resourcenameforuid(self, uid)
+ except NonExistentExternalShare:
+ yield self.fixNonExistentExternalShare()
+ raise ExternalShareFailed("External share does not exist")
+
+ if name:
+ returnValue(name)
+ else:
+ self._objects[uid] = None
+ returnValue(None)
+
+
+ @inlineCallbacks
+ def resourceUIDForName(self, name):
+ try:
+ resource = self._objects[name]
+ returnValue(resource.uid() if resource else None)
+ except KeyError:
+ pass
+
+ try:
+ uid = yield self._txn.store().conduit.send_resourceuidforname(self, name)
+ except NonExistentExternalShare:
+ yield self.fixNonExistentExternalShare()
+ raise ExternalShareFailed("External share does not exist")
+
+ if uid:
+ returnValue(uid)
+ else:
+ self._objects[name] = None
+ returnValue(None)
+
+
+ @inlineCallbacks
+ def createObjectResourceWithName(self, name, component, options=None):
+ raise NotImplementedError("TODO: external resource")
+
+
+ @inlineCallbacks
+ def moveObjectResource(self, child, newparent, newname=None):
+ raise NotImplementedError("TODO: external resource")
+
+
+ @inlineCallbacks
+ def syncToken(self):
+ if self._syncTokenRevision is None:
+ try:
+ token = yield self._txn.store().conduit.send_synctoken(self)
+ self._syncTokenRevision = self.revisionFromToken(token)
+ except NonExistentExternalShare:
+ yield self.fixNonExistentExternalShare()
+ raise ExternalShareFailed("External share does not exist")
+ returnValue(("%s_%s" % (self._externalID, self._syncTokenRevision,)))
+
+
+ @inlineCallbacks
+ def resourceNamesSinceRevision(self, revision):
+ try:
+ names = yield self._txn.store().conduit.send_resourcenamessincerevision(self, revision)
+ except NonExistentExternalShare:
+ yield self.fixNonExistentExternalShare()
+ raise ExternalShareFailed("External share does not exist")
+
+ returnValue(names)
+
+
+
+class CommonObjectResourceExternal(CommonObjectResource):
+ """
+ A CommonObjectResource for a resource not hosted on this system, but on another pod. This will forward
+ specific apis to the other pod using cross-pod requests.
+ """
+ pass
</ins></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastoretesttest_sqlpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/test/test_sql.py (12050 => 12051)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/test/test_sql.py        2013-12-11 15:40:55 UTC (rev 12050)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/test/test_sql.py        2013-12-11 22:16:01 UTC (rev 12051)
</span><span class="lines">@@ -13,6 +13,7 @@
</span><span class="cx"> # See the License for the specific language governing permissions and
</span><span class="cx"> # limitations under the License.
</span><span class="cx"> ##
</span><ins>+from txdav.caldav.datastore.test.util import buildDirectoryRecord
</ins><span class="cx">
</span><span class="cx"> """
</span><span class="cx"> Tests for L{txdav.common.datastore.sql}.
</span><span class="lines">@@ -34,6 +35,11 @@
</span><span class="cx"> from twext.enterprise.dal.syntax import Insert
</span><span class="cx"> from txdav.common.datastore.sql import fixUUIDNormalization
</span><span class="cx">
</span><ins>+from uuid import UUID
+exampleUID = UUID("a" * 32)
+denormalizedUID = str(exampleUID)
+normalizedUID = denormalizedUID.upper()
+
</ins><span class="cx"> class CommonSQLStoreTests(CommonCommonTests, TestCase):
</span><span class="cx"> """
</span><span class="cx"> Tests for shared functionality in L{txdav.common.datastore.sql}.
</span><span class="lines">@@ -46,6 +52,9 @@
</span><span class="cx"> """
</span><span class="cx"> yield super(CommonSQLStoreTests, self).setUp()
</span><span class="cx"> self._sqlStore = yield buildStore(self, self.notifierFactory)
</span><ins>+ self._sqlStore.directoryService().addRecord(buildDirectoryRecord(denormalizedUID))
+ self._sqlStore.directoryService().addRecord(buildDirectoryRecord(normalizedUID))
+ self._sqlStore.directoryService().addRecord(buildDirectoryRecord("uid"))
</ins><span class="cx">
</span><span class="cx">
</span><span class="cx"> def storeUnderTest(self):
</span><span class="lines">@@ -421,10 +430,3 @@
</span><span class="cx"> yield fixUUIDNormalization(self.storeUnderTest())
</span><span class="cx"> self.assertEqual((yield self.allHomeUIDs(schema.ADDRESSBOOK_HOME)),
</span><span class="cx"> [[normalizedUID]])
</span><del>-
-
-
-from uuid import UUID
-exampleUID = UUID("a" * 32)
-denormalizedUID = str(exampleUID)
-normalizedUID = denormalizedUID.upper()
</del></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastoretestutilpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/test/util.py (12050 => 12051)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/test/util.py        2013-12-11 15:40:55 UTC (rev 12050)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/test/util.py        2013-12-11 22:16:01 UTC (rev 12051)
</span><span class="lines">@@ -122,30 +122,68 @@
</span><span class="cx">
</span><span class="cx"> implements(IStoreDirectoryRecord)
</span><span class="cx">
</span><del>- def __init__(self, uid, shortNames, fullName):
</del><ins>+ def __init__(self, uid, shortNames, fullName, thisServer=True, server=None):
</ins><span class="cx"> self.uid = uid
</span><span class="cx"> self.shortNames = shortNames
</span><span class="cx"> self.fullName = fullName
</span><span class="cx"> self.displayName = self.fullName if self.fullName else self.shortNames[0]
</span><ins>+ self._thisServer = thisServer
+ self._server = server
</ins><span class="cx">
</span><span class="cx">
</span><ins>+ def thisServer(self):
+ return self._thisServer
</ins><span class="cx">
</span><ins>+
+ def server(self):
+ return self._server
+
+
+
+def buildDirectory(homes=None):
+
+ directory = TestStoreDirectoryService()
+
+ # User accounts
+ for ctr in range(1, 100):
+ directory.addRecord(TestStoreDirectoryRecord(
+ "user%02d" % (ctr,),
+ ("user%02d" % (ctr,),),
+ "User %02d" % (ctr,),
+ ))
+
+ homes = set(homes) if homes is not None else set()
+ for uid in homes:
+ directory.addRecord(buildDirectoryRecord(uid))
+
+ return directory
+
+
+
+def buildDirectoryRecord(uid):
+ return TestStoreDirectoryRecord(
+ uid,
+ (uid,),
+ uid.capitalize(),
+ )
+
+
+
</ins><span class="cx"> class SQLStoreBuilder(object):
</span><span class="cx"> """
</span><span class="cx"> Test-fixture-builder which can construct a PostgresStore.
</span><span class="cx"> """
</span><del>- sharedService = None
- currentTestID = None
</del><ins>+ def __init__(self, secondary=False):
+ self.sharedService = None
+ self.currentTestID = None
+ self.sharedDBPath = "_test_sql_db" + str(os.getpid()) + ("-2" if secondary else "")
</ins><span class="cx">
</span><del>- SHARED_DB_PATH = "_test_sql_db" + str(os.getpid())
</del><span class="cx">
</span><del>-
- @classmethod
- def createService(cls, serviceFactory):
</del><ins>+ def createService(self, serviceFactory):
</ins><span class="cx"> """
</span><span class="cx"> Create a L{PostgresService} to use for building a store.
</span><span class="cx"> """
</span><del>- dbRoot = CachingFilePath(cls.SHARED_DB_PATH)
</del><ins>+ dbRoot = CachingFilePath(self.sharedDBPath)
</ins><span class="cx"> return PostgresService(
</span><span class="cx"> dbRoot, serviceFactory, current_sql_schema, resetSchema=True,
</span><span class="cx"> databaseName="caldav",
</span><span class="lines">@@ -161,17 +199,15 @@
</span><span class="cx"> )
</span><span class="cx">
</span><span class="cx">
</span><del>- @classmethod
- def childStore(cls):
</del><ins>+ def childStore(self):
</ins><span class="cx"> """
</span><span class="cx"> Create a store suitable for use in a child process, that is hooked up
</span><span class="cx"> to the store that a parent test process is managing.
</span><span class="cx"> """
</span><span class="cx"> disableMemcacheForTest(TestCase())
</span><span class="cx"> staticQuota = 3000
</span><del>- attachmentRoot = (CachingFilePath(cls.SHARED_DB_PATH)
- .child("attachments"))
- stubsvc = cls.createService(lambda cf: Service())
</del><ins>+ attachmentRoot = (CachingFilePath(self.sharedDBPath).child("attachments"))
+ stubsvc = self.createService(lambda cf: Service())
</ins><span class="cx">
</span><span class="cx"> cp = ConnectionPool(stubsvc.produceConnection, maxConnections=1)
</span><span class="cx"> # Attach the service to the running reactor.
</span><span class="lines">@@ -187,17 +223,17 @@
</span><span class="cx"> return cds
</span><span class="cx">
</span><span class="cx">
</span><del>- def buildStore(self, testCase, notifierFactory, directoryService=None):
</del><ins>+ def buildStore(self, testCase, notifierFactory, directoryService=None, homes=None):
</ins><span class="cx"> """
</span><span class="cx"> Do the necessary work to build a store for a particular test case.
</span><span class="cx">
</span><span class="cx"> @return: a L{Deferred} which fires with an L{IDataStore}.
</span><span class="cx"> """
</span><span class="cx"> disableMemcacheForTest(testCase)
</span><del>- dbRoot = CachingFilePath(self.SHARED_DB_PATH)
</del><ins>+ dbRoot = CachingFilePath(self.sharedDBPath)
</ins><span class="cx"> attachmentRoot = dbRoot.child("attachments")
</span><span class="cx"> if directoryService is None:
</span><del>- directoryService = TestStoreDirectoryService()
</del><ins>+ directoryService = buildDirectory(homes=homes)
</ins><span class="cx"> if self.sharedService is None:
</span><span class="cx"> ready = Deferred()
</span><span class="cx"> def getReady(connectionFactory, storageService):
</span><span class="lines">@@ -244,8 +280,7 @@
</span><span class="cx"> attachmentRoot.createDirectory()
</span><span class="cx">
</span><span class="cx"> currentTestID = testCase.id()
</span><del>- cp = ConnectionPool(self.sharedService.produceConnection,
- maxConnections=5)
</del><ins>+ cp = ConnectionPool(self.sharedService.produceConnection, maxConnections=5)
</ins><span class="cx"> quota = deriveQuota(testCase)
</span><span class="cx"> store = CommonDataStore(
</span><span class="cx"> cp.connection,
</span><span class="lines">@@ -307,6 +342,7 @@
</span><span class="cx">
</span><span class="cx"> theStoreBuilder = SQLStoreBuilder()
</span><span class="cx"> buildStore = theStoreBuilder.buildStore
</span><ins>+cleanStore = theStoreBuilder.cleanStore
</ins><span class="cx">
</span><span class="cx">
</span><span class="cx"> _notSet = object()
</span><span class="lines">@@ -678,13 +714,13 @@
</span><span class="cx">
</span><span class="cx">
</span><span class="cx"> @inlineCallbacks
</span><del>- def homeUnderTest(self, txn=None, name="home1"):
</del><ins>+ def homeUnderTest(self, txn=None, name="home1", create=False):
</ins><span class="cx"> """
</span><span class="cx"> Get the calendar home detailed by C{requirements['home1']}.
</span><span class="cx"> """
</span><span class="cx"> if txn is None:
</span><span class="cx"> txn = self.transactionUnderTest()
</span><del>- returnValue((yield txn.calendarHomeWithUID(name)))
</del><ins>+ returnValue((yield txn.calendarHomeWithUID(name, create=create)))
</ins><span class="cx">
</span><span class="cx">
</span><span class="cx"> @inlineCallbacks
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastoreupgradetesttest_migratepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/upgrade/test/test_migrate.py (12050 => 12051)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/upgrade/test/test_migrate.py        2013-12-11 15:40:55 UTC (rev 12050)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/upgrade/test/test_migrate.py        2013-12-11 22:16:01 UTC (rev 12051)
</span><span class="lines">@@ -143,7 +143,17 @@
</span><span class="cx"> self.filesPath, {"push": StubNotifierFactory()}, TestStoreDirectoryService(), True, True
</span><span class="cx"> )
</span><span class="cx"> self.sqlStore = yield theStoreBuilder.buildStore(
</span><del>- self, StubNotifierFactory()
</del><ins>+ self,
+ StubNotifierFactory(),
+ homes=(
+ "home1",
+ "home2",
+ "home3",
+ "home_defaults",
+ "home_no_splits",
+ "home_splits",
+ "home_splits_shared",
+ )
</ins><span class="cx"> )
</span><span class="cx"> self.upgrader = UpgradeToDatabaseStep(self.fileStore, self.sqlStore)
</span><span class="cx">
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommonicommondatastorepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/icommondatastore.py (12050 => 12051)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/icommondatastore.py        2013-12-11 15:40:55 UTC (rev 12050)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/icommondatastore.py        2013-12-11 22:16:01 UTC (rev 12051)
</span><span class="lines">@@ -213,12 +213,28 @@
</span><span class="cx">
</span><span class="cx">
</span><span class="cx">
</span><ins>+class ShareNotAllowed(CommonStoreError):
+ """
+ An operation on a shared resource is not allowed.
+ """
+
+
+
</ins><span class="cx"> class ExternalShareFailed(CommonStoreError):
</span><span class="cx"> """
</span><span class="cx"> An external sharee operation failed.
</span><span class="cx"> """
</span><span class="cx">
</span><span class="cx">
</span><ins>+
+class NonExistentExternalShare(CommonStoreError):
+ """
+ An external sharee operation failed because the share does not exist on the
+ other pod. The caller of the external request receiving this exception should
+ remove the local external share to "heal" this mismatch.
+ """
+
+
</ins><span class="cx"> # Indexing / sync tokens
</span><span class="cx">
</span><span class="cx">
</span></span></pre>
</div>
</div>
</body>
</html>