[CalendarServer-changes] [12082] CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav

source_changes at macosforge.org source_changes at macosforge.org
Wed Mar 12 11:19:14 PDT 2014


Revision: 12082
          http://trac.calendarserver.org//changeset/12082
Author:   cdaboo at apple.com
Date:     2013-12-12 20:00:05 -0800 (Thu, 12 Dec 2013)
Log Message:
-----------
Checkpoint: more complete cross-pod api calls.

Modified Paths:
--------------
    CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/sql.py
    CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/sql_external.py
    CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/sql.py
    CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/sql_external.py
    CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/conduit.py
    CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/test/test_conduit.py
    CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/sql.py
    CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/sql_external.py

Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/sql.py
===================================================================
--- 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)
@@ -1516,6 +1516,7 @@
     implements(ICalendarObject)
 
     _objectSchema = schema.CALENDAR_OBJECT
+    _componentClass = VComponent
 
     def __init__(self, calendar, name, uid, resourceID=None, options=None):
 
@@ -1539,7 +1540,7 @@
     @inlineCallbacks
     def _createInternal(cls, parent, name, component, internal_state, options=None, split_details=None):
 
-        child = (yield cls.objectWithName(parent, name, None))
+        child = (yield cls.objectWithName(parent, name))
         if child:
             raise ObjectResourceNameAlreadyExistsError(name)
 

Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/sql_external.py
===================================================================
--- 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)
@@ -162,6 +162,9 @@
 
 class CalendarObjectExternal(CommonObjectResourceExternal, CalendarObject):
     """
-    SQL-based implementation of L{ICalendar}.
+    SQL-based implementation of L{ICalendarObject}.
     """
     pass
+
+
+CalendarExternal._objectResourceClass = CalendarObjectExternal

Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/sql.py
===================================================================
--- 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)
@@ -1443,6 +1443,8 @@
     _objectSchema = schema.ADDRESSBOOK_OBJECT
     _bindSchema = schema.SHARED_GROUP_BIND
 
+    _componentClass = VCard
+
     # used by CommonHomeChild._childrenAndMetadataForHomeID() only
     # _homeChildSchema = schema.ADDRESSBOOK_OBJECT
     # _homeChildMetaDataSchema = schema.ADDRESSBOOK_OBJECT
@@ -1576,10 +1578,6 @@
 
         self._kind = None
         self._ownerAddressBookResourceID = None
-        # _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
         self._bindMode = None
         self._bindStatus = None
         self._bindMessage = None
@@ -1676,7 +1674,8 @@
         yield super(AddressBookObject, self).remove()
         self._kind = None
         self._ownerAddressBookResourceID = None
-        self._component = None
+        self._objectText = None
+        self._cachedComponent = None
 
 
     @inlineCallbacks
@@ -1763,7 +1762,7 @@
 
     @classmethod
     @inlineCallbacks
-    def _objectWithNameOrID(cls, parent, name, uid, resourceID):
+    def objectWith(cls, parent, name=None, uid=None, resourceID=None):
 
         row, groupBindRow = yield cls._getDBData(parent, name, uid, resourceID)
 
@@ -2111,7 +2110,7 @@
             self._objectText = componentText
 
         self._size = len(self._objectText)
-        self._component = component
+        self._cachedComponent = component
         self._md5 = hashlib.md5(componentText).hexdigest()
         self._componentChanged = originalComponentText != componentText
 
@@ -2228,7 +2227,7 @@
         only allowed in good data.
         """
 
-        if self._component is None:
+        if self._cachedComponent is None:
 
             if self.isGroupForSharedAddressBook():
                 component = yield self.addressbook()._groupForSharedAddressBookComponent()
@@ -2294,9 +2293,9 @@
                     component.addProperty(Property("X-ADDRESSBOOKSERVER-KIND", "group"))
                     component.addProperty(Property("UID", self._uid))
 
-            self._component = component
+            self._cachedComponent = component
 
-        returnValue(self._component)
+        returnValue(self._cachedComponent)
 
 
     def moveValidation(self, destination, name):

Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/sql_external.py
===================================================================
--- 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)
@@ -76,6 +76,8 @@
 
 class AddressBookObjectExternal(CommonObjectResourceExternal, AddressBookObject):
     """
-    SQL-based implementation of L{ICalendar}.
+    SQL-based implementation of L{IAddressBookObject}.
     """
     pass
+
+AddressBookExternal._objectResourceClass = AddressBookObjectExternal

Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/conduit.py
===================================================================
--- 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)
@@ -21,6 +21,7 @@
 from txdav.common.datastore.podding.request import ConduitRequest
 from txdav.common.idirectoryservice import DirectoryRecordNotFoundError
 from txdav.common.icommondatastore import ExternalShareFailed
+from twisted.python.reflect import namedClass
 
 
 __all__ = [
@@ -375,18 +376,18 @@
     # Sharer data access related apis
     #
 
-    def _send(self, action, shareeView):
+    def _send(self, action, parent, child=None):
         """
-        Base behavior for an operation on a sharee resource.
+        Base behavior for an operation on a L{CommonHomeChild}.
 
         @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()
+        homeType = parent.ownerHome()._homeType
+        ownerUID = parent.ownerHome().uid()
+        ownerID = parent.external_id()
+        shareeUID = parent.viewerHome().uid()
 
         _ignore_sender, recipient = self.validRequst(shareeUID, ownerUID)
 
@@ -397,6 +398,8 @@
             "owner_id": ownerID,
             "sharee": shareeUID,
         }
+        if child is not None:
+            result["resource_id"] = child.id()
         return result, recipient
 
 
@@ -421,16 +424,24 @@
         if ownerHomeChild is None:
             FailedCrossPodRequestError("Invalid owner shared resource specified")
 
-        returnValue((ownerHome, ownerHomeChild))
+        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
 
+        returnValue((ownerHome, ownerHomeChild, objectResource,))
 
+
     #
     # 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):
+    def _simple_send(self, actionName, shareeView, objectResource=None, args=None, kwargs=None):
         """
         A simple send operation that returns a value.
 
@@ -438,23 +449,28 @@
         @type actionName: C{str}
         @param shareeView: sharee resource being operated on.
         @type shareeView: L{CommonHomeChildExternal}
+        @param objectResource: the resource being operated on, or C{None} for classmethod.
+        @type objectResource: L{CommonObjectResourceExternal}
         @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)
+        action, recipient = self._send(actionName, shareeView, objectResource)
         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"])
+        if result["result"] == "ok":
+            returnValue(result["value"])
+        elif result["result"] == "exception":
+            raise namedClass(result["class"])(result["message"])
 
 
     @inlineCallbacks
-    def _simple_recv(self, txn, actionName, message, method):
+    def _simple_recv(self, txn, actionName, message, method, onHomeChild=True, transform=None):
         """
         A simple recv operation that returns a value. We also look for an optional set of arguments/keywords
         and include those only if present.
@@ -465,25 +481,76 @@
         @type message: C{dict}
         @param method: name of the method to execute on the shared resource to get the result.
         @type method: C{str}
+        @param transform: method to call on returned JSON value to convert it to something useful.
+        @type transform: C{callable}
         """
 
-        _ignore_ownerHome, ownerHomeChild = yield self._recv(txn, message, actionName)
-        value = yield getattr(ownerHomeChild, method)(*message.get("arguments", ()), **message.get("keywords", {}))
+        _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)
+
         returnValue({
             "result": "ok",
             "value": value,
         })
 
 
+    @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
+
+
     @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))
+    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))
         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")
+    @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")

Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/test/test_conduit.py
===================================================================
--- 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)
@@ -29,7 +29,9 @@
     FakeConduitRequest
 from txdav.common.datastore.sql_tables import _BIND_STATUS_ACCEPTED
 from pycalendar.datetime import DateTime
-from twistedcaldav.ical import Component
+from twistedcaldav.ical import Component, normalize_iCalStr
+from txdav.common.icommondatastore import ObjectResourceNameAlreadyExistsError, \
+    ObjectResourceNameNotAllowedError
 
 class TestConduit (CommonCommonTests, twext.web2.dav.test.util.TestCase):
 
@@ -208,12 +210,28 @@
 END:VCALENDAR
 """.replace("\n", "\r\n").format(**nowYear)
 
+    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)
+
     caldata2 = """BEGIN:VCALENDAR
 VERSION:2.0
 CALSCALE:GREGORIAN
 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
 BEGIN:VEVENT
-UID:ui2
+UID:uid2
 DTSTART:{now:04d}0102T160000Z
 DURATION:PT1H
 CREATED:20060102T190000Z
@@ -224,6 +242,22 @@
 END:VCALENDAR
 """.replace("\n", "\r\n").format(**nowYear)
 
+    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)
+
     @inlineCallbacks
     def test_basic_share(self):
         """
@@ -493,3 +527,360 @@
         uid = yield shared.resourceNameForUID("uid2")
         self.assertTrue(uid is None)
         yield self.otherCommit()
+
+
+    @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()

Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/sql.py
===================================================================
--- 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)
@@ -4102,33 +4102,39 @@
 
     @classmethod
     def objectWithName(cls, home, name, accepted=True):
-        return cls._objectWithNameOrID(home, name=name, accepted=accepted)
+        return cls.objectWith(home, name=name, accepted=accepted)
 
 
     @classmethod
     def objectWithID(cls, home, resourceID, accepted=True):
-        return cls._objectWithNameOrID(home, resourceID=resourceID, accepted=accepted)
+        return cls.objectWith(home, resourceID=resourceID, accepted=accepted)
 
 
     @classmethod
     def objectWithExternalID(cls, home, externalID, accepted=True):
-        return cls._objectWithNameOrID(home, externalID=externalID, accepted=accepted)
+        return cls.objectWith(home, externalID=externalID, accepted=accepted)
 
 
     @classmethod
     @inlineCallbacks
-    def _objectWithNameOrID(cls, home, name=None, resourceID=None, externalID=None, accepted=True):
-        # replaces objectWithName()
+    def objectWith(cls, home, name=None, resourceID=None, externalID=None, accepted=True):
         """
-        Retrieve the child with the given C{name} or C{resourceID} contained in the given
-        C{home}.
+        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.
 
-        @param home: a L{CommonHome}.
+        @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}
 
-        @param name: a string; the name of the L{CommonHomeChild} to retrieve.
-
-        @return: an L{CommonHomeChild} or C{None} if no such child
-            exists.
+        @return: the new object or C{None} if not found
+        @rtype: C{CommonHomeChild}
         """
 
         dbData = yield cls._getDBData(home, name, resourceID, externalID)
@@ -4376,6 +4382,7 @@
         for result in results:
             self._objects[result.name()] = result
             self._objects[result.uid()] = result
+            self._objects[result.id()] = result
         self._objectNames = sorted([result.name() for result in results])
         returnValue(results)
 
@@ -4389,6 +4396,7 @@
         for result in results:
             self._objects[result.name()] = result
             self._objects[result.uid()] = result
+            self._objects[result.id()] = result
         self._objectNames = sorted([result.name() for result in results])
         returnValue(results)
 
@@ -4457,18 +4465,13 @@
         We create the empty object first then have it initialize itself from the
         store.
         """
-        if resourceID:
-            objectResource = (
-                yield self._objectResourceClass.objectWithID(self, resourceID)
-            )
-        else:
-            objectResource = (
-                yield self._objectResourceClass.objectWithName(self, name, uid)
-            )
+        objectResource = (
+            yield self._objectResourceClass.objectWith(self, name=name, uid=uid, resourceID=resourceID)
+        )
         if objectResource:
             self._objects[objectResource.name()] = objectResource
             self._objects[objectResource.uid()] = objectResource
-            self._objects[objectResource._resourceID] = objectResource
+            self._objects[objectResource.id()] = objectResource
         else:
             if resourceID:
                 self._objects[resourceID] = None
@@ -4558,6 +4561,7 @@
         )
         self._objects[objectResource.name()] = objectResource
         self._objects[objectResource.uid()] = objectResource
+        self._objects[objectResource.id()] = objectResource
 
         # Note: create triggers a notification when the component is set, so we
         # don't need to call notify() here like we do for object removal.
@@ -4568,6 +4572,7 @@
     def removedObjectResource(self, child):
         self._objects.pop(child.name(), None)
         self._objects.pop(child.uid(), None)
+        self._objects.pop(child.id(), None)
         if self._objectNames and child.name() in self._objectNames:
             self._objectNames.remove(child.name())
         yield self._deleteRevision(child.name())
@@ -4639,7 +4644,7 @@
         # Clean this collections cache and signal sync change
         self._objects.pop(name, None)
         self._objects.pop(uid, None)
-        self._objects.pop(child._resourceID, None)
+        self._objects.pop(child.id(), None)
         yield self._deleteRevision(name)
         yield self.notifyChanged()
 
@@ -4670,7 +4675,7 @@
         # Signal sync change on new collection
         newparent._objects.pop(name, None)
         newparent._objects.pop(uid, None)
-        newparent._objects.pop(child._resourceID, None)
+        newparent._objects.pop(child.id(), None)
         yield newparent._insertRevision(newname)
         yield newparent.notifyChanged()
 
@@ -4876,6 +4881,7 @@
 
     _externalClass = None
     _objectSchema = None
+    _componentClass = None
 
     BATCH_LOAD_SIZE = 50
 
@@ -4961,7 +4967,8 @@
         self._size = None
         self._created = None
         self._modified = None
-        self._notificationData = None
+        self._textData = None
+        self._cachedComponent = None
 
         self._locked = False
 
@@ -5023,7 +5030,9 @@
     @inlineCallbacks
     def loadAllObjectsWithNames(cls, parent, names):
         """
-        Load all child objects with the specified names, doing so in batches.
+        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).
         """
         names = tuple(names)
         results = []
@@ -5062,7 +5071,7 @@
 
         # Optimize case of single name to load
         if len(names) == 1:
-            obj = yield cls.objectWithName(parent, names[0], None)
+            obj = yield cls.objectWithName(parent, names[0])
             returnValue([obj] if obj else [])
 
         results = []
@@ -5093,19 +5102,40 @@
 
 
     @classmethod
-    def objectWithName(cls, parent, name, uid):
-        return cls._objectWithNameOrID(parent, name, uid, None)
+    def objectWithName(cls, parent, name):
+        return cls.objectWith(parent, name=name)
 
 
     @classmethod
+    def objectWithUID(cls, parent, uid):
+        return cls.objectWith(parent, uid=uid)
+
+
+    @classmethod
     def objectWithID(cls, parent, resourceID):
-        return cls._objectWithNameOrID(parent, None, None, resourceID)
+        return cls.objectWith(parent, resourceID=resourceID)
 
 
     @classmethod
     @inlineCallbacks
-    def _objectWithNameOrID(cls, parent, name, uid, resourceID):
+    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.
 
+        @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}
+        """
+
         row = yield cls._getDBData(parent, name, uid, resourceID)
 
         if row:
@@ -5195,6 +5225,27 @@
         )
 
 
+    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()]
+
+
     @inlineCallbacks
     def _loadPropertyStore(self, props=None, created=False):
         if props is None:
@@ -5309,6 +5360,14 @@
         raise NotImplementedError
 
 
+    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)
+
+
     def component(self):
         raise NotImplementedError
 
@@ -5361,7 +5420,8 @@
         self._size = None
         self._created = None
         self._modified = None
-        self._notificationData = None
+        self._textData = None
+        self._cachedComponent = None
 
 
     def removeNotifyCategory(self):
@@ -5417,19 +5477,19 @@
 
     @inlineCallbacks
     def _text(self):
-        if self._notificationData is None:
+        if self._textData is None:
             texts = (
                 yield self._textByIDQuery.on(self._txn,
                                              resourceID=self._resourceID)
             )
             if texts:
                 text = texts[0][0]
-                self._notificationData = text
+                self._textData = text
                 returnValue(text)
             else:
                 raise ConcurrentModification()
         else:
-            returnValue(self._notificationData)
+            returnValue(self._textData)
 
 
 

Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/sql_external.py
===================================================================
--- 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)
@@ -225,16 +225,6 @@
 
 
     @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:
@@ -258,18 +248,6 @@
         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:
@@ -314,11 +292,21 @@
 
     @inlineCallbacks
     def createObjectResourceWithName(self, name, component, options=None):
+        """
+        Actually I think we can defer this to the object resource class's .create()
+        """
         raise NotImplementedError("TODO: external resource")
 
 
     @inlineCallbacks
     def moveObjectResource(self, child, newparent, newname=None):
+        """
+        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.
+        """
         raise NotImplementedError("TODO: external resource")
 
 
@@ -351,4 +339,81 @@
     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
+
+    @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)
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20140312/80026a16/attachment.html>


More information about the calendarserver-changes mailing list