<!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>[12082] 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/12082">12082</a></dd>
<dt>Author</dt> <dd>cdaboo@apple.com</dd>
<dt>Date</dt> <dd>2013-12-12 20:00:05 -0800 (Thu, 12 Dec 2013)</dd>
</dl>
<h3>Log Message</h3>
<pre>Checkpoint: more complete cross-pod api calls.</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="#CalendarServerbranchesuserscdaboocrosspodsharingtxdavcaldavdatastoresql_externalpy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/sql_external.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtxdavcarddavdatastoresqlpy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/sql.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="#CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastorepoddingconduitpy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/conduit.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="#CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastoresqlpy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/sql.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 (12081 => 12082)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/sql.py        2013-12-13 03:03:31 UTC (rev 12081)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/sql.py        2013-12-13 04:00:05 UTC (rev 12082)
</span><span class="lines">@@ -1516,6 +1516,7 @@
</span><span class="cx"> implements(ICalendarObject)
</span><span class="cx">
</span><span class="cx"> _objectSchema = schema.CALENDAR_OBJECT
</span><ins>+ _componentClass = VComponent
</ins><span class="cx">
</span><span class="cx"> def __init__(self, calendar, name, uid, resourceID=None, options=None):
</span><span class="cx">
</span><span class="lines">@@ -1539,7 +1540,7 @@
</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="cx">
</span><del>- child = (yield cls.objectWithName(parent, name, None))
</del><ins>+ child = (yield cls.objectWithName(parent, name))
</ins><span class="cx"> if child:
</span><span class="cx"> raise ObjectResourceNameAlreadyExistsError(name)
</span><span class="cx">
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtxdavcaldavdatastoresql_externalpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/sql_external.py (12081 => 12082)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/sql_external.py        2013-12-13 03:03:31 UTC (rev 12081)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/sql_external.py        2013-12-13 04:00:05 UTC (rev 12082)
</span><span class="lines">@@ -162,6 +162,9 @@
</span><span class="cx">
</span><span class="cx"> class CalendarObjectExternal(CommonObjectResourceExternal, CalendarObject):
</span><span class="cx"> """
</span><del>- SQL-based implementation of L{ICalendar}.
</del><ins>+ SQL-based implementation of L{ICalendarObject}.
</ins><span class="cx"> """
</span><span class="cx"> pass
</span><ins>+
+
+CalendarExternal._objectResourceClass = CalendarObjectExternal
</ins></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtxdavcarddavdatastoresqlpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/sql.py (12081 => 12082)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/sql.py        2013-12-13 03:03:31 UTC (rev 12081)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/sql.py        2013-12-13 04:00:05 UTC (rev 12082)
</span><span class="lines">@@ -1443,6 +1443,8 @@
</span><span class="cx"> _objectSchema = schema.ADDRESSBOOK_OBJECT
</span><span class="cx"> _bindSchema = schema.SHARED_GROUP_BIND
</span><span class="cx">
</span><ins>+ _componentClass = VCard
+
</ins><span class="cx"> # used by CommonHomeChild._childrenAndMetadataForHomeID() only
</span><span class="cx"> # _homeChildSchema = schema.ADDRESSBOOK_OBJECT
</span><span class="cx"> # _homeChildMetaDataSchema = schema.ADDRESSBOOK_OBJECT
</span><span class="lines">@@ -1576,10 +1578,6 @@
</span><span class="cx">
</span><span class="cx"> self._kind = None
</span><span class="cx"> self._ownerAddressBookResourceID = None
</span><del>- # _self._component is the cached, current component
- # super._objectText now contains the text as read of the database only,
- # not including group member text
- self._component = None
</del><span class="cx"> self._bindMode = None
</span><span class="cx"> self._bindStatus = None
</span><span class="cx"> self._bindMessage = None
</span><span class="lines">@@ -1676,7 +1674,8 @@
</span><span class="cx"> yield super(AddressBookObject, self).remove()
</span><span class="cx"> self._kind = None
</span><span class="cx"> self._ownerAddressBookResourceID = None
</span><del>- self._component = None
</del><ins>+ self._objectText = None
+ self._cachedComponent = None
</ins><span class="cx">
</span><span class="cx">
</span><span class="cx"> @inlineCallbacks
</span><span class="lines">@@ -1763,7 +1762,7 @@
</span><span class="cx">
</span><span class="cx"> @classmethod
</span><span class="cx"> @inlineCallbacks
</span><del>- def _objectWithNameOrID(cls, parent, name, uid, resourceID):
</del><ins>+ def objectWith(cls, parent, name=None, uid=None, resourceID=None):
</ins><span class="cx">
</span><span class="cx"> row, groupBindRow = yield cls._getDBData(parent, name, uid, resourceID)
</span><span class="cx">
</span><span class="lines">@@ -2111,7 +2110,7 @@
</span><span class="cx"> self._objectText = componentText
</span><span class="cx">
</span><span class="cx"> self._size = len(self._objectText)
</span><del>- self._component = component
</del><ins>+ self._cachedComponent = component
</ins><span class="cx"> self._md5 = hashlib.md5(componentText).hexdigest()
</span><span class="cx"> self._componentChanged = originalComponentText != componentText
</span><span class="cx">
</span><span class="lines">@@ -2228,7 +2227,7 @@
</span><span class="cx"> only allowed in good data.
</span><span class="cx"> """
</span><span class="cx">
</span><del>- if self._component is None:
</del><ins>+ if self._cachedComponent is None:
</ins><span class="cx">
</span><span class="cx"> if self.isGroupForSharedAddressBook():
</span><span class="cx"> component = yield self.addressbook()._groupForSharedAddressBookComponent()
</span><span class="lines">@@ -2294,9 +2293,9 @@
</span><span class="cx"> component.addProperty(Property("X-ADDRESSBOOKSERVER-KIND", "group"))
</span><span class="cx"> component.addProperty(Property("UID", self._uid))
</span><span class="cx">
</span><del>- self._component = component
</del><ins>+ self._cachedComponent = component
</ins><span class="cx">
</span><del>- returnValue(self._component)
</del><ins>+ returnValue(self._cachedComponent)
</ins><span class="cx">
</span><span class="cx">
</span><span class="cx"> def moveValidation(self, destination, name):
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtxdavcarddavdatastoresql_externalpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/sql_external.py (12081 => 12082)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/sql_external.py        2013-12-13 03:03:31 UTC (rev 12081)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/sql_external.py        2013-12-13 04:00:05 UTC (rev 12082)
</span><span class="lines">@@ -76,6 +76,8 @@
</span><span class="cx">
</span><span class="cx"> class AddressBookObjectExternal(CommonObjectResourceExternal, AddressBookObject):
</span><span class="cx"> """
</span><del>- SQL-based implementation of L{ICalendar}.
</del><ins>+ SQL-based implementation of L{IAddressBookObject}.
</ins><span class="cx"> """
</span><span class="cx"> pass
</span><ins>+
+AddressBookExternal._objectResourceClass = AddressBookObjectExternal
</ins></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 (12081 => 12082)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/conduit.py        2013-12-13 03:03:31 UTC (rev 12081)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/conduit.py        2013-12-13 04:00:05 UTC (rev 12082)
</span><span class="lines">@@ -21,6 +21,7 @@
</span><span class="cx"> from txdav.common.datastore.podding.request import ConduitRequest
</span><span class="cx"> from txdav.common.idirectoryservice import DirectoryRecordNotFoundError
</span><span class="cx"> from txdav.common.icommondatastore import ExternalShareFailed
</span><ins>+from twisted.python.reflect import namedClass
</ins><span class="cx">
</span><span class="cx">
</span><span class="cx"> __all__ = [
</span><span class="lines">@@ -375,18 +376,18 @@
</span><span class="cx"> # Sharer data access related apis
</span><span class="cx"> #
</span><span class="cx">
</span><del>- def _send(self, action, shareeView):
</del><ins>+ def _send(self, action, parent, child=None):
</ins><span class="cx"> """
</span><del>- Base behavior for an operation on a sharee resource.
</del><ins>+ Base behavior for an operation on a L{CommonHomeChild}.
</ins><span class="cx">
</span><span class="cx"> @param shareeView: sharee resource being operated on.
</span><span class="cx"> @type shareeView: L{CommonHomeChildExternal}
</span><span class="cx"> """
</span><span class="cx">
</span><del>- homeType = shareeView.ownerHome()._homeType
- ownerUID = shareeView.ownerHome().uid()
- ownerID = shareeView.external_id()
- shareeUID = shareeView.viewerHome().uid()
</del><ins>+ homeType = parent.ownerHome()._homeType
+ ownerUID = parent.ownerHome().uid()
+ ownerID = parent.external_id()
+ shareeUID = parent.viewerHome().uid()
</ins><span class="cx">
</span><span class="cx"> _ignore_sender, recipient = self.validRequst(shareeUID, ownerUID)
</span><span class="cx">
</span><span class="lines">@@ -397,6 +398,8 @@
</span><span class="cx"> "owner_id": ownerID,
</span><span class="cx"> "sharee": shareeUID,
</span><span class="cx"> }
</span><ins>+ if child is not None:
+ result["resource_id"] = child.id()
</ins><span class="cx"> return result, recipient
</span><span class="cx">
</span><span class="cx">
</span><span class="lines">@@ -421,16 +424,24 @@
</span><span class="cx"> if ownerHomeChild is None:
</span><span class="cx"> FailedCrossPodRequestError("Invalid owner shared resource specified")
</span><span class="cx">
</span><del>- returnValue((ownerHome, ownerHomeChild))
</del><ins>+ resourceID = message.get("resource_id", None)
+ if resourceID is not None:
+ objectResource = yield ownerHomeChild.objectResourceWithID(resourceID)
+ if objectResource is None:
+ FailedCrossPodRequestError("Invalid owner shared object resource specified")
+ else:
+ objectResource = None
</ins><span class="cx">
</span><ins>+ returnValue((ownerHome, ownerHomeChild, objectResource,))
</ins><span class="cx">
</span><ins>+
</ins><span class="cx"> #
</span><span class="cx"> # Simple calls are ones where there is no argument and a single return value. We can simplify
</span><span class="cx"> # code generation for these by dynamically generating the appropriate class methods.
</span><span class="cx"> #
</span><span class="cx">
</span><span class="cx"> @inlineCallbacks
</span><del>- def _simple_send(self, actionName, shareeView, args=None, kwargs=None):
</del><ins>+ def _simple_send(self, actionName, shareeView, objectResource=None, args=None, kwargs=None):
</ins><span class="cx"> """
</span><span class="cx"> A simple send operation that returns a value.
</span><span class="cx">
</span><span class="lines">@@ -438,23 +449,28 @@
</span><span class="cx"> @type actionName: C{str}
</span><span class="cx"> @param shareeView: sharee resource being operated on.
</span><span class="cx"> @type shareeView: L{CommonHomeChildExternal}
</span><ins>+ @param objectResource: the resource being operated on, or C{None} for classmethod.
+ @type objectResource: L{CommonObjectResourceExternal}
</ins><span class="cx"> @param args: list of optional arguments.
</span><span class="cx"> @type args: C{list}
</span><span class="cx"> @param kwargs: optional keyword arguments.
</span><span class="cx"> @type kwargs: C{dict}
</span><span class="cx"> """
</span><span class="cx">
</span><del>- action, recipient = self._send(actionName, shareeView)
</del><ins>+ action, recipient = self._send(actionName, shareeView, objectResource)
</ins><span class="cx"> if args is not None:
</span><span class="cx"> action["arguments"] = args
</span><span class="cx"> if kwargs is not None:
</span><span class="cx"> action["keywords"] = kwargs
</span><span class="cx"> result = yield self.sendRequest(shareeView._txn, recipient, action)
</span><del>- returnValue(result["value"])
</del><ins>+ if result["result"] == "ok":
+ returnValue(result["value"])
+ elif result["result"] == "exception":
+ raise namedClass(result["class"])(result["message"])
</ins><span class="cx">
</span><span class="cx">
</span><span class="cx"> @inlineCallbacks
</span><del>- def _simple_recv(self, txn, actionName, message, method):
</del><ins>+ def _simple_recv(self, txn, actionName, message, method, onHomeChild=True, transform=None):
</ins><span class="cx"> """
</span><span class="cx"> A simple recv operation that returns a value. We also look for an optional set of arguments/keywords
</span><span class="cx"> and include those only if present.
</span><span class="lines">@@ -465,25 +481,76 @@
</span><span class="cx"> @type message: C{dict}
</span><span class="cx"> @param method: name of the method to execute on the shared resource to get the result.
</span><span class="cx"> @type method: C{str}
</span><ins>+ @param transform: method to call on returned JSON value to convert it to something useful.
+ @type transform: C{callable}
</ins><span class="cx"> """
</span><span class="cx">
</span><del>- _ignore_ownerHome, ownerHomeChild = yield self._recv(txn, message, actionName)
- value = yield getattr(ownerHomeChild, method)(*message.get("arguments", ()), **message.get("keywords", {}))
</del><ins>+ _ignore_ownerHome, ownerHomeChild, objectResource = yield self._recv(txn, message, actionName)
+ try:
+ if onHomeChild:
+ # Operate on the L{CommonHomeChild}
+ value = yield getattr(ownerHomeChild, method)(*message.get("arguments", ()), **message.get("keywords", {}))
+ else:
+ # Operate on the L{CommonObjectResource}
+ if objectResource is not None:
+ value = yield getattr(objectResource, method)(*message.get("arguments", ()), **message.get("keywords", {}))
+ else:
+ # classmethod call
+ value = yield getattr(ownerHomeChild._objectResourceClass, method)(ownerHomeChild, *message.get("arguments", ()), **message.get("keywords", {}))
+ except Exception as e:
+ returnValue({
+ "result": "exception",
+ "class": ".".join((e.__class__.__module__, e.__class__.__name__,)),
+ "message": str(e),
+ })
+ if transform is not None:
+ value = transform(value, ownerHomeChild, objectResource)
+
</ins><span class="cx"> returnValue({
</span><span class="cx"> "result": "ok",
</span><span class="cx"> "value": value,
</span><span class="cx"> })
</span><span class="cx">
</span><span class="cx">
</span><ins>+ @staticmethod
+ def _transform_string(value, ownerHomeChild, objectResource):
+ return str(value)
+
+
+ @staticmethod
+ def _transform_externalize(value, ownerHomeChild, objectResource):
+ if isinstance(value, ownerHomeChild._objectResourceClass):
+ value = value.externalize()
+ elif value is not None:
+ value = [v.externalize() for v in value]
+ return value
+
+
</ins><span class="cx"> @classmethod
</span><del>- def _make_simple_action(cls, action, method):
- setattr(cls, "send_{}".format(action), lambda self, shareeView, *args, **kwargs: self._simple_send(action, shareeView, args, kwargs))
</del><ins>+ def _make_simple_homechild_action(cls, action, method):
+ setattr(cls, "send_{}".format(action), lambda self, shareeView, *args, **kwargs: self._simple_send(action, shareeView, args=args, kwargs=kwargs))
</ins><span class="cx"> setattr(cls, "recv_{}".format(action), lambda self, txn, message: self._simple_recv(txn, action, message, method))
</span><span class="cx">
</span><span class="cx">
</span><del>-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")
</del><ins>+ @classmethod
+ def _make_simple_object_action(cls, action, method, transform_result=None):
+ setattr(cls, "send_{}".format(action), lambda self, shareeView, objectResource, *args, **kwargs: self._simple_send(action, shareeView, objectResource, args=args, kwargs=kwargs))
+ setattr(cls, "recv_{}".format(action), lambda self, txn, message: self._simple_recv(txn, action, message, method, onHomeChild=False, transform=transform_result))
+
+
+# Calls on L{CommonHomeChild} objects
+PoddingConduit._make_simple_homechild_action("countobjects", "countObjectResources")
+PoddingConduit._make_simple_homechild_action("listobjects", "listObjectResources")
+PoddingConduit._make_simple_homechild_action("synctoken", "syncToken")
+PoddingConduit._make_simple_homechild_action("resourcenamessincerevision", "resourceNamesSinceRevision")
+PoddingConduit._make_simple_homechild_action("resourceuidforname", "resourceUIDForName")
+PoddingConduit._make_simple_homechild_action("resourcenameforuid", "resourceNameForUID")
+
+# Calls on L{CommonObjectResource} objects
+PoddingConduit._make_simple_object_action("loadallobjects", "loadAllObjects", transform_result=PoddingConduit._transform_externalize)
+PoddingConduit._make_simple_object_action("loadallobjectswithnames", "loadAllObjectsWithNames", transform_result=PoddingConduit._transform_externalize)
+PoddingConduit._make_simple_object_action("objectwith", "objectWith", transform_result=PoddingConduit._transform_externalize)
+PoddingConduit._make_simple_object_action("create", "create", transform_result=PoddingConduit._transform_externalize)
+PoddingConduit._make_simple_object_action("setcomponent", "setComponentText")
+PoddingConduit._make_simple_object_action("component", "component", transform_result=PoddingConduit._transform_string)
+PoddingConduit._make_simple_object_action("remove", "remove")
</ins></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 (12081 => 12082)</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-13 03:03:31 UTC (rev 12081)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/test/test_conduit.py        2013-12-13 04:00:05 UTC (rev 12082)
</span><span class="lines">@@ -29,7 +29,9 @@
</span><span class="cx"> FakeConduitRequest
</span><span class="cx"> from txdav.common.datastore.sql_tables import _BIND_STATUS_ACCEPTED
</span><span class="cx"> from pycalendar.datetime import DateTime
</span><del>-from twistedcaldav.ical import Component
</del><ins>+from twistedcaldav.ical import Component, normalize_iCalStr
+from txdav.common.icommondatastore import ObjectResourceNameAlreadyExistsError, \
+ ObjectResourceNameNotAllowedError
</ins><span class="cx">
</span><span class="cx"> class TestConduit (CommonCommonTests, twext.web2.dav.test.util.TestCase):
</span><span class="cx">
</span><span class="lines">@@ -208,12 +210,28 @@
</span><span class="cx"> END:VCALENDAR
</span><span class="cx"> """.replace("\n", "\r\n").format(**nowYear)
</span><span class="cx">
</span><ins>+ caldata1_changed = """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:uid1
+DTSTART:{now:04d}0102T150000Z
+DURATION:PT1H
+CREATED:20060102T190000Z
+DTSTAMP:20051222T210507Z
+RRULE:FREQ=WEEKLY
+SUMMARY:instance changed
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n").format(**nowYear)
+
</ins><span class="cx"> caldata2 = """BEGIN:VCALENDAR
</span><span class="cx"> VERSION:2.0
</span><span class="cx"> CALSCALE:GREGORIAN
</span><span class="cx"> PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
</span><span class="cx"> BEGIN:VEVENT
</span><del>-UID:ui2
</del><ins>+UID:uid2
</ins><span class="cx"> DTSTART:{now:04d}0102T160000Z
</span><span class="cx"> DURATION:PT1H
</span><span class="cx"> CREATED:20060102T190000Z
</span><span class="lines">@@ -224,6 +242,22 @@
</span><span class="cx"> END:VCALENDAR
</span><span class="cx"> """.replace("\n", "\r\n").format(**nowYear)
</span><span class="cx">
</span><ins>+ caldata3 = """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:uid3
+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)
+
</ins><span class="cx"> @inlineCallbacks
</span><span class="cx"> def test_basic_share(self):
</span><span class="cx"> """
</span><span class="lines">@@ -493,3 +527,360 @@
</span><span class="cx"> uid = yield shared.resourceNameForUID("uid2")
</span><span class="cx"> self.assertTrue(uid is None)
</span><span class="cx"> yield self.otherCommit()
</span><ins>+
+
+ @inlineCallbacks
+ def test_loadallobjects(self):
+ """
+ Test that action=loadallobjects works.
+ """
+
+ yield self.createShare("user01", "puser01")
+
+ calendar1 = yield self.calendarUnderTest(home="user01", name="calendar")
+ resource1 = yield calendar1.createCalendarObjectWithName("1.ics", Component.fromString(self.caldata1))
+ resource_id1 = resource1.id()
+ resource2 = yield calendar1.createCalendarObjectWithName("2.ics", Component.fromString(self.caldata2))
+ resource_id2 = resource2.id()
+ yield self.commit()
+
+ shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home="puser01", name="shared-calendar")
+ resources = yield shared.objectResources()
+ byname = dict([(resource.name(), resource) for resource in resources])
+ byuid = dict([(resource.uid(), resource) for resource in resources])
+ self.assertEqual(len(resources), 2)
+ self.assertEqual(set([resource.name() for resource in resources]), set(("1.ics", "2.ics",)))
+ self.assertEqual(set([resource.uid() for resource in resources]), set(("uid1", "uid2",)))
+ self.assertEqual(set([resource.id() for resource in resources]), set((resource_id1, resource_id2,)))
+ resource = yield shared.objectResourceWithName("1.ics")
+ self.assertTrue(resource is byname["1.ics"])
+ resource = yield shared.objectResourceWithName("2.ics")
+ self.assertTrue(resource is byname["2.ics"])
+ resource = yield shared.objectResourceWithName("Missing.ics")
+ self.assertTrue(resource is None)
+ resource = yield shared.objectResourceWithUID("uid1")
+ self.assertTrue(resource is byuid["uid1"])
+ resource = yield shared.objectResourceWithUID("uid2")
+ self.assertTrue(resource is byuid["uid2"])
+ resource = yield shared.objectResourceWithUID("uid-missing")
+ self.assertTrue(resource is None)
+ resource = yield shared.objectResourceWithID(resource_id1)
+ self.assertTrue(resource is byname["1.ics"])
+ resource = yield shared.objectResourceWithID(resource_id2)
+ self.assertTrue(resource is byname["2.ics"])
+ resource = yield shared.objectResourceWithID(0)
+ self.assertTrue(resource is None)
+ 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()
+ yield self.commit()
+
+ shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home="puser01", name="shared-calendar")
+ resources = yield shared.objectResources()
+ byname = dict([(resource.name(), resource) for resource in resources])
+ byuid = dict([(resource.uid(), resource) for resource in resources])
+ self.assertEqual(len(resources), 1)
+ self.assertEqual(set([resource.name() for resource in resources]), set(("2.ics",)))
+ self.assertEqual(set([resource.uid() for resource in resources]), set(("uid2",)))
+ self.assertEqual(set([resource.id() for resource in resources]), set((resource_id2,)))
+ resource = yield shared.objectResourceWithName("1.ics")
+ self.assertTrue(resource is None)
+ resource = yield shared.objectResourceWithName("2.ics")
+ self.assertTrue(resource is byname["2.ics"])
+ resource = yield shared.objectResourceWithName("Missing.ics")
+ self.assertTrue(resource is None)
+ resource = yield shared.objectResourceWithUID("uid1")
+ self.assertTrue(resource is None)
+ resource = yield shared.objectResourceWithUID("uid2")
+ self.assertTrue(resource is byuid["uid2"])
+ resource = yield shared.objectResourceWithUID("uid-missing")
+ self.assertTrue(resource is None)
+ resource = yield shared.objectResourceWithID(resource_id1)
+ self.assertTrue(resource is None)
+ resource = yield shared.objectResourceWithID(resource_id2)
+ self.assertTrue(resource is byname["2.ics"])
+ resource = yield shared.objectResourceWithID(0)
+ self.assertTrue(resource is None)
+ yield self.otherCommit()
+
+
+ @inlineCallbacks
+ def test_loadallobjectswithnames(self):
+ """
+ Test that action=loadallobjectswithnames works.
+ """
+
+ yield self.createShare("user01", "puser01")
+
+ calendar1 = yield self.calendarUnderTest(home="user01", name="calendar")
+ resource1 = yield calendar1.createCalendarObjectWithName("1.ics", Component.fromString(self.caldata1))
+ resource_id1 = resource1.id()
+ yield calendar1.createCalendarObjectWithName("2.ics", Component.fromString(self.caldata2))
+ resource3 = yield calendar1.createCalendarObjectWithName("3.ics", Component.fromString(self.caldata3))
+ resource_id3 = resource3.id()
+ yield self.commit()
+
+ shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home="puser01", name="shared-calendar")
+ resources = yield shared.objectResources()
+ self.assertEqual(len(resources), 3)
+ yield self.otherCommit()
+
+ shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home="puser01", name="shared-calendar")
+ resources = yield shared.objectResourcesWithNames(("1.ics", "3.ics",))
+ byname = dict([(resource.name(), resource) for resource in resources])
+ byuid = dict([(resource.uid(), resource) for resource in resources])
+ self.assertEqual(len(resources), 2)
+ self.assertEqual(set([resource.name() for resource in resources]), set(("1.ics", "3.ics",)))
+ self.assertEqual(set([resource.uid() for resource in resources]), set(("uid1", "uid3",)))
+ self.assertEqual(set([resource.id() for resource in resources]), set((resource_id1, resource_id3,)))
+ resource = yield shared.objectResourceWithName("1.ics")
+ self.assertTrue(resource is byname["1.ics"])
+ resource = yield shared.objectResourceWithName("3.ics")
+ self.assertTrue(resource is byname["3.ics"])
+ resource = yield shared.objectResourceWithName("Missing.ics")
+ self.assertTrue(resource is None)
+ resource = yield shared.objectResourceWithUID("uid1")
+ self.assertTrue(resource is byuid["uid1"])
+ resource = yield shared.objectResourceWithUID("uid3")
+ self.assertTrue(resource is byuid["uid3"])
+ resource = yield shared.objectResourceWithUID("uid-missing")
+ self.assertTrue(resource is None)
+ resource = yield shared.objectResourceWithID(resource_id1)
+ self.assertTrue(resource is byname["1.ics"])
+ resource = yield shared.objectResourceWithID(resource_id3)
+ self.assertTrue(resource is byname["3.ics"])
+ resource = yield shared.objectResourceWithID(0)
+ self.assertTrue(resource is None)
+ 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()
+ yield self.commit()
+
+ shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home="puser01", name="shared-calendar")
+ resources = yield shared.objectResourcesWithNames(("1.ics", "3.ics",))
+ byname = dict([(resource.name(), resource) for resource in resources])
+ byuid = dict([(resource.uid(), resource) for resource in resources])
+ self.assertEqual(len(resources), 1)
+ self.assertEqual(set([resource.name() for resource in resources]), set(("3.ics",)))
+ self.assertEqual(set([resource.uid() for resource in resources]), set(("uid3",)))
+ self.assertEqual(set([resource.id() for resource in resources]), set((resource_id3,)))
+ resource = yield shared.objectResourceWithName("1.ics")
+ self.assertTrue(resource is None)
+ resource = yield shared.objectResourceWithName("3.ics")
+ self.assertTrue(resource is byname["3.ics"])
+ resource = yield shared.objectResourceWithName("Missing.ics")
+ self.assertTrue(resource is None)
+ resource = yield shared.objectResourceWithUID("uid1")
+ self.assertTrue(resource is None)
+ resource = yield shared.objectResourceWithUID("uid3")
+ self.assertTrue(resource is byuid["uid3"])
+ resource = yield shared.objectResourceWithUID("uid-missing")
+ self.assertTrue(resource is None)
+ resource = yield shared.objectResourceWithID(resource_id1)
+ self.assertTrue(resource is None)
+ resource = yield shared.objectResourceWithID(resource_id3)
+ self.assertTrue(resource is byname["3.ics"])
+ resource = yield shared.objectResourceWithID(0)
+ self.assertTrue(resource is None)
+ yield self.otherCommit()
+
+
+ @inlineCallbacks
+ def test_objectwith(self):
+ """
+ Test that action=objectwith works.
+ """
+
+ yield self.createShare("user01", "puser01")
+
+ calendar1 = yield self.calendarUnderTest(home="user01", name="calendar")
+ resource = yield calendar1.createCalendarObjectWithName("1.ics", Component.fromString(self.caldata1))
+ resource_id = resource.id()
+ yield self.commit()
+
+ shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home="puser01", name="shared-calendar")
+ resource = yield shared.objectResourceWithName("1.ics")
+ self.assertTrue(resource is not None)
+ self.assertEqual(resource.name(), "1.ics")
+ self.assertEqual(resource.uid(), "uid1")
+
+ resource = yield shared.objectResourceWithName("2.ics")
+ self.assertTrue(resource is None)
+
+ yield self.otherCommit()
+
+ shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home="puser01", name="shared-calendar")
+ resource = yield shared.objectResourceWithUID("uid1")
+ self.assertTrue(resource is not None)
+ self.assertEqual(resource.name(), "1.ics")
+ self.assertEqual(resource.uid(), "uid1")
+
+ resource = yield shared.objectResourceWithUID("uid2")
+ self.assertTrue(resource is None)
+
+ yield self.otherCommit()
+
+ shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home="puser01", name="shared-calendar")
+ resource = yield shared.objectResourceWithID(resource_id)
+ self.assertTrue(resource is not None)
+ self.assertEqual(resource.name(), "1.ics")
+ self.assertEqual(resource.uid(), "uid1")
+
+ resource = yield shared.objectResourceWithID(0)
+ self.assertTrue(resource is None)
+
+ 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()
+ yield self.commit()
+
+ shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home="puser01", name="shared-calendar")
+ resource = yield shared.objectResourceWithName("1.ics")
+ self.assertTrue(resource is None)
+ yield self.otherCommit()
+
+ shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home="puser01", name="shared-calendar")
+ resource = yield shared.objectResourceWithUID("uid1")
+ self.assertTrue(resource is None)
+ yield self.otherCommit()
+
+ shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home="puser01", name="shared-calendar")
+ resource = yield shared.objectResourceWithID(resource_id)
+ self.assertTrue(resource is None)
+ yield self.otherCommit()
+
+
+ @inlineCallbacks
+ def test_create(self):
+ """
+ Test that action=create works.
+ """
+
+ yield self.createShare("user01", "puser01")
+
+ shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home="puser01", name="shared-calendar")
+ resource = yield shared.createCalendarObjectWithName("1.ics", Component.fromString(self.caldata1))
+ resource_id = resource.id()
+ self.assertTrue(resource is not None)
+ self.assertEqual(resource.name(), "1.ics")
+ self.assertEqual(resource.uid(), "uid1")
+ yield self.otherCommit()
+
+ shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home="puser01", name="shared-calendar")
+ resource = yield shared.objectResourceWithUID("uid1")
+ self.assertTrue(resource is not None)
+ self.assertEqual(resource.name(), "1.ics")
+ self.assertEqual(resource.uid(), "uid1")
+ self.assertEqual(resource.id(), resource_id)
+ yield self.otherCommit()
+
+ object1 = yield self.calendarObjectUnderTest(home="user01", calendar_name="calendar", name="1.ics")
+ self.assertTrue(object1 is not None)
+ self.assertEqual(object1.name(), "1.ics")
+ self.assertEqual(object1.uid(), "uid1")
+ self.assertEqual(object1.id(), resource_id)
+ yield self.commit()
+
+
+ @inlineCallbacks
+ def test_create_exception(self):
+ """
+ Test that action=create fails when a duplicate name is used.
+ """
+
+ 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()
+
+ shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home="puser01", name="shared-calendar")
+ yield self.failUnlessFailure(shared.createCalendarObjectWithName("1.ics", Component.fromString(self.caldata1)), ObjectResourceNameAlreadyExistsError)
+ yield self.otherAbort()
+
+ shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home="puser01", name="shared-calendar")
+ yield self.failUnlessFailure(shared.createCalendarObjectWithName(".2.ics", Component.fromString(self.caldata2)), ObjectResourceNameNotAllowedError)
+ yield self.otherAbort()
+
+
+ @inlineCallbacks
+ def test_setcomponent(self):
+ """
+ Test that action=setcomponent 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()
+
+ shared_object = yield self.calendarObjectUnderTest(txn=self.newOtherTransaction(), home="puser01", calendar_name="shared-calendar", name="1.ics")
+ ical = yield shared_object.component()
+ self.assertTrue(isinstance(ical, Component))
+ self.assertEqual(normalize_iCalStr(str(ical)), normalize_iCalStr(self.caldata1))
+ yield self.otherCommit()
+
+ shared_object = yield self.calendarObjectUnderTest(txn=self.newOtherTransaction(), home="puser01", calendar_name="shared-calendar", name="1.ics")
+ changed = yield shared_object.setComponent(Component.fromString(self.caldata1_changed))
+ self.assertFalse(changed)
+ ical = yield shared_object.component()
+ self.assertTrue(isinstance(ical, Component))
+ self.assertEqual(normalize_iCalStr(str(ical)), normalize_iCalStr(self.caldata1_changed))
+ yield self.otherCommit()
+
+ object1 = yield self.calendarObjectUnderTest(home="user01", calendar_name="calendar", name="1.ics")
+ ical = yield object1.component()
+ self.assertTrue(isinstance(ical, Component))
+ self.assertEqual(normalize_iCalStr(str(ical)), normalize_iCalStr(self.caldata1_changed))
+ yield self.commit()
+
+
+ @inlineCallbacks
+ def test_component(self):
+ """
+ Test that action=component 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()
+
+ shared_object = yield self.calendarObjectUnderTest(txn=self.newOtherTransaction(), home="puser01", calendar_name="shared-calendar", name="1.ics")
+ ical = yield shared_object.component()
+ self.assertTrue(isinstance(ical, Component))
+ self.assertEqual(normalize_iCalStr(str(ical)), normalize_iCalStr(self.caldata1))
+ yield self.otherCommit()
+
+
+ @inlineCallbacks
+ def test_remove(self):
+ """
+ Test that action=create 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()
+
+ shared_object = yield self.calendarObjectUnderTest(txn=self.newOtherTransaction(), home="puser01", calendar_name="shared-calendar", name="1.ics")
+ yield shared_object.remove()
+ yield self.otherCommit()
+
+ shared_object = yield self.calendarObjectUnderTest(txn=self.newOtherTransaction(), home="puser01", calendar_name="shared-calendar", name="1.ics")
+ self.assertTrue(shared_object is None)
+ yield self.otherCommit()
+
+ object1 = yield self.calendarObjectUnderTest(home="user01", calendar_name="calendar", name="1.ics")
+ self.assertTrue(object1 is None)
+ yield self.commit()
</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 (12081 => 12082)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/sql.py        2013-12-13 03:03:31 UTC (rev 12081)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/sql.py        2013-12-13 04:00:05 UTC (rev 12082)
</span><span class="lines">@@ -4102,33 +4102,39 @@
</span><span class="cx">
</span><span class="cx"> @classmethod
</span><span class="cx"> def objectWithName(cls, home, name, accepted=True):
</span><del>- return cls._objectWithNameOrID(home, name=name, accepted=accepted)
</del><ins>+ return cls.objectWith(home, name=name, accepted=accepted)
</ins><span class="cx">
</span><span class="cx">
</span><span class="cx"> @classmethod
</span><span class="cx"> def objectWithID(cls, home, resourceID, accepted=True):
</span><del>- return cls._objectWithNameOrID(home, resourceID=resourceID, accepted=accepted)
</del><ins>+ return cls.objectWith(home, resourceID=resourceID, accepted=accepted)
</ins><span class="cx">
</span><span class="cx">
</span><span class="cx"> @classmethod
</span><span class="cx"> def objectWithExternalID(cls, home, externalID, accepted=True):
</span><del>- return cls._objectWithNameOrID(home, externalID=externalID, accepted=accepted)
</del><ins>+ return cls.objectWith(home, externalID=externalID, accepted=accepted)
</ins><span class="cx">
</span><span class="cx">
</span><span class="cx"> @classmethod
</span><span class="cx"> @inlineCallbacks
</span><del>- def _objectWithNameOrID(cls, home, name=None, resourceID=None, externalID=None, accepted=True):
- # replaces objectWithName()
</del><ins>+ def objectWith(cls, home, name=None, resourceID=None, externalID=None, accepted=True):
</ins><span class="cx"> """
</span><del>- Retrieve the child with the given C{name} or C{resourceID} contained in the given
- C{home}.
</del><ins>+ Create the object using one of the specified arguments as the key to load it. One
+ and only one of the keyword arguments must be set.
</ins><span class="cx">
</span><del>- @param home: a L{CommonHome}.
</del><ins>+ @param parent: parent collection
+ @type parent: L{CommonHomeChild}
+ @param name: name of the resource, or C{None}
+ @type name: C{str}
+ @param uid: resource data UID, or C{None}
+ @type uid: C{str}
+ @param resourceID: resource id
+ @type resourceID: C{int}
+ @param accepted: if C{True} only load owned or accepted share items
+ @type accepted: C{bool}
</ins><span class="cx">
</span><del>- @param name: a string; the name of the L{CommonHomeChild} to retrieve.
-
- @return: an L{CommonHomeChild} or C{None} if no such child
- exists.
</del><ins>+ @return: the new object or C{None} if not found
+ @rtype: C{CommonHomeChild}
</ins><span class="cx"> """
</span><span class="cx">
</span><span class="cx"> dbData = yield cls._getDBData(home, name, resourceID, externalID)
</span><span class="lines">@@ -4376,6 +4382,7 @@
</span><span class="cx"> for result in results:
</span><span class="cx"> self._objects[result.name()] = result
</span><span class="cx"> self._objects[result.uid()] = result
</span><ins>+ self._objects[result.id()] = result
</ins><span class="cx"> self._objectNames = sorted([result.name() for result in results])
</span><span class="cx"> returnValue(results)
</span><span class="cx">
</span><span class="lines">@@ -4389,6 +4396,7 @@
</span><span class="cx"> for result in results:
</span><span class="cx"> self._objects[result.name()] = result
</span><span class="cx"> self._objects[result.uid()] = result
</span><ins>+ self._objects[result.id()] = result
</ins><span class="cx"> self._objectNames = sorted([result.name() for result in results])
</span><span class="cx"> returnValue(results)
</span><span class="cx">
</span><span class="lines">@@ -4457,18 +4465,13 @@
</span><span class="cx"> We create the empty object first then have it initialize itself from the
</span><span class="cx"> store.
</span><span class="cx"> """
</span><del>- if resourceID:
- objectResource = (
- yield self._objectResourceClass.objectWithID(self, resourceID)
- )
- else:
- objectResource = (
- yield self._objectResourceClass.objectWithName(self, name, uid)
- )
</del><ins>+ objectResource = (
+ yield self._objectResourceClass.objectWith(self, name=name, uid=uid, resourceID=resourceID)
+ )
</ins><span class="cx"> if objectResource:
</span><span class="cx"> self._objects[objectResource.name()] = objectResource
</span><span class="cx"> self._objects[objectResource.uid()] = objectResource
</span><del>- self._objects[objectResource._resourceID] = objectResource
</del><ins>+ self._objects[objectResource.id()] = objectResource
</ins><span class="cx"> else:
</span><span class="cx"> if resourceID:
</span><span class="cx"> self._objects[resourceID] = None
</span><span class="lines">@@ -4558,6 +4561,7 @@
</span><span class="cx"> )
</span><span class="cx"> self._objects[objectResource.name()] = objectResource
</span><span class="cx"> self._objects[objectResource.uid()] = objectResource
</span><ins>+ self._objects[objectResource.id()] = objectResource
</ins><span class="cx">
</span><span class="cx"> # Note: create triggers a notification when the component is set, so we
</span><span class="cx"> # don't need to call notify() here like we do for object removal.
</span><span class="lines">@@ -4568,6 +4572,7 @@
</span><span class="cx"> def removedObjectResource(self, child):
</span><span class="cx"> self._objects.pop(child.name(), None)
</span><span class="cx"> self._objects.pop(child.uid(), None)
</span><ins>+ self._objects.pop(child.id(), None)
</ins><span class="cx"> if self._objectNames and child.name() in self._objectNames:
</span><span class="cx"> self._objectNames.remove(child.name())
</span><span class="cx"> yield self._deleteRevision(child.name())
</span><span class="lines">@@ -4639,7 +4644,7 @@
</span><span class="cx"> # Clean this collections cache and signal sync change
</span><span class="cx"> self._objects.pop(name, None)
</span><span class="cx"> self._objects.pop(uid, None)
</span><del>- self._objects.pop(child._resourceID, None)
</del><ins>+ self._objects.pop(child.id(), None)
</ins><span class="cx"> yield self._deleteRevision(name)
</span><span class="cx"> yield self.notifyChanged()
</span><span class="cx">
</span><span class="lines">@@ -4670,7 +4675,7 @@
</span><span class="cx"> # Signal sync change on new collection
</span><span class="cx"> newparent._objects.pop(name, None)
</span><span class="cx"> newparent._objects.pop(uid, None)
</span><del>- newparent._objects.pop(child._resourceID, None)
</del><ins>+ newparent._objects.pop(child.id(), None)
</ins><span class="cx"> yield newparent._insertRevision(newname)
</span><span class="cx"> yield newparent.notifyChanged()
</span><span class="cx">
</span><span class="lines">@@ -4876,6 +4881,7 @@
</span><span class="cx">
</span><span class="cx"> _externalClass = None
</span><span class="cx"> _objectSchema = None
</span><ins>+ _componentClass = None
</ins><span class="cx">
</span><span class="cx"> BATCH_LOAD_SIZE = 50
</span><span class="cx">
</span><span class="lines">@@ -4961,7 +4967,8 @@
</span><span class="cx"> self._size = None
</span><span class="cx"> self._created = None
</span><span class="cx"> self._modified = None
</span><del>- self._notificationData = None
</del><ins>+ self._textData = None
+ self._cachedComponent = None
</ins><span class="cx">
</span><span class="cx"> self._locked = False
</span><span class="cx">
</span><span class="lines">@@ -5023,7 +5030,9 @@
</span><span class="cx"> @inlineCallbacks
</span><span class="cx"> def loadAllObjectsWithNames(cls, parent, names):
</span><span class="cx"> """
</span><del>- Load all child objects with the specified names, doing so in batches.
</del><ins>+ Load all child objects with the specified names, doing so in batches (because we need to match
+ using SQL "resource_name in (...)" where there might be a character length limit on the number
+ of items in the set).
</ins><span class="cx"> """
</span><span class="cx"> names = tuple(names)
</span><span class="cx"> results = []
</span><span class="lines">@@ -5062,7 +5071,7 @@
</span><span class="cx">
</span><span class="cx"> # Optimize case of single name to load
</span><span class="cx"> if len(names) == 1:
</span><del>- obj = yield cls.objectWithName(parent, names[0], None)
</del><ins>+ obj = yield cls.objectWithName(parent, names[0])
</ins><span class="cx"> returnValue([obj] if obj else [])
</span><span class="cx">
</span><span class="cx"> results = []
</span><span class="lines">@@ -5093,19 +5102,40 @@
</span><span class="cx">
</span><span class="cx">
</span><span class="cx"> @classmethod
</span><del>- def objectWithName(cls, parent, name, uid):
- return cls._objectWithNameOrID(parent, name, uid, None)
</del><ins>+ def objectWithName(cls, parent, name):
+ return cls.objectWith(parent, name=name)
</ins><span class="cx">
</span><span class="cx">
</span><span class="cx"> @classmethod
</span><ins>+ def objectWithUID(cls, parent, uid):
+ return cls.objectWith(parent, uid=uid)
+
+
+ @classmethod
</ins><span class="cx"> def objectWithID(cls, parent, resourceID):
</span><del>- return cls._objectWithNameOrID(parent, None, None, resourceID)
</del><ins>+ return cls.objectWith(parent, resourceID=resourceID)
</ins><span class="cx">
</span><span class="cx">
</span><span class="cx"> @classmethod
</span><span class="cx"> @inlineCallbacks
</span><del>- def _objectWithNameOrID(cls, parent, name, uid, resourceID):
</del><ins>+ def objectWith(cls, parent, name=None, uid=None, resourceID=None):
+ """
+ Create the object using one of the specified arguments as the key to load it. One
+ and only one of the keyword arguments must be set.
</ins><span class="cx">
</span><ins>+ @param parent: parent collection
+ @type parent: L{CommonHomeChild}
+ @param name: name of the resource, or C{None}
+ @type name: C{str}
+ @param uid: resource data UID, or C{None}
+ @type uid: C{str}
+ @param resourceID: resource id
+ @type resourceID: C{int}
+
+ @return: the new object or C{None} if not found
+ @rtype: C{CommonObjectResource}
+ """
+
</ins><span class="cx"> row = yield cls._getDBData(parent, name, uid, resourceID)
</span><span class="cx">
</span><span class="cx"> if row:
</span><span class="lines">@@ -5195,6 +5225,27 @@
</span><span class="cx"> )
</span><span class="cx">
</span><span class="cx">
</span><ins>+ def externalize(self):
+ """
+ Create a dictionary mapping key attributes so this object can be sent over a cross-pod call
+ and reconstituted at the other end. Note that the other end may have a different schema so
+ the attributes may not match exactly and will need to be processed accordingly.
+ """
+ return dict([(attr[1:], getattr(self, attr)) for attr in self._rowAttributes()])
+
+
+ @classmethod
+ def internalize(cls, mapping):
+ """
+ Given a mapping generated by L{externalize}, convert the values into an array of database
+ like items that conforms to the ordering of L{_allColumns} so it can be fed into L{makeClass}.
+ Note that there may be a schema mismatch with the external data, so treat missing items as
+ C{None} and ignore extra items.
+ """
+
+ return [mapping.get(row[1:]) for row in cls._rowAttributes()]
+
+
</ins><span class="cx"> @inlineCallbacks
</span><span class="cx"> def _loadPropertyStore(self, props=None, created=False):
</span><span class="cx"> if props is None:
</span><span class="lines">@@ -5309,6 +5360,14 @@
</span><span class="cx"> raise NotImplementedError
</span><span class="cx">
</span><span class="cx">
</span><ins>+ def setComponentText(self, component_text, inserting=False, options=None):
+ """
+ This api is needed for cross-pod calls where the component is serialized as a str and we need
+ to convert it back to the actual component class.
+ """
+ return self.setComponent(self._componentClass.fromString(component_text), inserting, options)
+
+
</ins><span class="cx"> def component(self):
</span><span class="cx"> raise NotImplementedError
</span><span class="cx">
</span><span class="lines">@@ -5361,7 +5420,8 @@
</span><span class="cx"> self._size = None
</span><span class="cx"> self._created = None
</span><span class="cx"> self._modified = None
</span><del>- self._notificationData = None
</del><ins>+ self._textData = None
+ self._cachedComponent = None
</ins><span class="cx">
</span><span class="cx">
</span><span class="cx"> def removeNotifyCategory(self):
</span><span class="lines">@@ -5417,19 +5477,19 @@
</span><span class="cx">
</span><span class="cx"> @inlineCallbacks
</span><span class="cx"> def _text(self):
</span><del>- if self._notificationData is None:
</del><ins>+ if self._textData is None:
</ins><span class="cx"> texts = (
</span><span class="cx"> yield self._textByIDQuery.on(self._txn,
</span><span class="cx"> resourceID=self._resourceID)
</span><span class="cx"> )
</span><span class="cx"> if texts:
</span><span class="cx"> text = texts[0][0]
</span><del>- self._notificationData = text
</del><ins>+ self._textData = text
</ins><span class="cx"> returnValue(text)
</span><span class="cx"> else:
</span><span class="cx"> raise ConcurrentModification()
</span><span class="cx"> else:
</span><del>- returnValue(self._notificationData)
</del><ins>+ returnValue(self._textData)
</ins><span class="cx">
</span><span class="cx">
</span><span class="cx">
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastoresql_externalpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/sql_external.py (12081 => 12082)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/sql_external.py        2013-12-13 03:03:31 UTC (rev 12081)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/sql_external.py        2013-12-13 04:00:05 UTC (rev 12082)
</span><span class="lines">@@ -225,16 +225,6 @@
</span><span class="cx">
</span><span class="cx">
</span><span class="cx"> @inlineCallbacks
</span><del>- def objectResources(self):
- raise NotImplementedError("TODO: external resource")
-
-
- @inlineCallbacks
- def objectResourcesWithNames(self, names):
- raise NotImplementedError("TODO: external resource")
-
-
- @inlineCallbacks
</del><span class="cx"> def listObjectResources(self):
</span><span class="cx"> if self._objectNames is None:
</span><span class="cx"> try:
</span><span class="lines">@@ -258,18 +248,6 @@
</span><span class="cx"> returnValue(len(self._objectNames))
</span><span class="cx">
</span><span class="cx">
</span><del>- 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")
-
-
</del><span class="cx"> @inlineCallbacks
</span><span class="cx"> def resourceNameForUID(self, uid):
</span><span class="cx"> try:
</span><span class="lines">@@ -314,11 +292,21 @@
</span><span class="cx">
</span><span class="cx"> @inlineCallbacks
</span><span class="cx"> def createObjectResourceWithName(self, name, component, options=None):
</span><ins>+ """
+ Actually I think we can defer this to the object resource class's .create()
+ """
</ins><span class="cx"> raise NotImplementedError("TODO: external resource")
</span><span class="cx">
</span><span class="cx">
</span><span class="cx"> @inlineCallbacks
</span><span class="cx"> def moveObjectResource(self, child, newparent, newname=None):
</span><ins>+ """
+ The base class does an optimization to avoid removing/re-creating
+ the actual object resource data. That might not always be possible
+ with external shares if the shared resource is moved to a collection
+ that is not shared or shared by someone else on a different (third)
+ pod. The best bet here is to treat the move as a delete/create.
+ """
</ins><span class="cx"> raise NotImplementedError("TODO: external resource")
</span><span class="cx">
</span><span class="cx">
</span><span class="lines">@@ -351,4 +339,81 @@
</span><span class="cx"> A CommonObjectResource for a resource not hosted on this system, but on another pod. This will forward
</span><span class="cx"> specific apis to the other pod using cross-pod requests.
</span><span class="cx"> """
</span><del>- pass
</del><ins>+
+ @classmethod
+ @inlineCallbacks
+ def loadAllObjects(cls, parent):
+ mapping_list = yield parent._txn.store().conduit.send_loadallobjects(parent, None)
+
+ results = []
+ if mapping_list:
+ for mapping in mapping_list:
+ child = yield cls.makeClass(parent, cls.internalize(mapping))
+ results.append(child)
+ returnValue(results)
+
+
+ @classmethod
+ @inlineCallbacks
+ def loadAllObjectsWithNames(cls, parent, names):
+ mapping_list = yield parent._txn.store().conduit.send_loadallobjectswithnames(parent, None, names)
+
+ results = []
+ if mapping_list:
+ for mapping in mapping_list:
+ child = yield cls.makeClass(parent, cls.internalize(mapping))
+ results.append(child)
+ returnValue(results)
+
+
+ @classmethod
+ @inlineCallbacks
+ def objectWith(cls, parent, name=None, uid=None, resourceID=None):
+ mapping = yield parent._txn.store().conduit.send_objectwith(parent, None, name, uid, resourceID)
+
+ if mapping:
+ child = yield cls.makeClass(parent, cls.internalize(mapping))
+ returnValue(child)
+ else:
+ returnValue(None)
+
+
+ @classmethod
+ @inlineCallbacks
+ def create(cls, parent, name, component, options=None):
+ mapping = yield parent._txn.store().conduit.send_create(parent, None, name, component, options=options)
+
+ if mapping:
+ child = yield cls.makeClass(parent, cls.internalize(mapping))
+ returnValue(child)
+ else:
+ returnValue(None)
+
+
+ @inlineCallbacks
+ def setComponent(self, component, inserting=False, options=None):
+ changed = yield self._txn.store().conduit.send_setcomponent(self.parentCollection(), self, str(component), inserting, options)
+ self._cachedComponent = None
+ returnValue(changed)
+
+
+ @inlineCallbacks
+ def component(self):
+ if self._cachedComponent is None:
+ text = yield self._txn.store().conduit.send_component(self.parentCollection(), self)
+ self._cachedComponent = self._componentClass.fromString(text)
+
+ returnValue(self._cachedComponent)
+
+
+ @inlineCallbacks
+ def moveTo(self, destination, name=None):
+ """
+ Probably OK to leave this to the base implementation which calls up to the parent after some validation.
+ """
+ raise NotImplementedError
+
+
+ @inlineCallbacks
+ def remove(self):
+ yield self._txn.store().conduit.send_remove(self.parentCollection(), self)
</ins></span></pre>
</div>
</div>
</body>
</html>