[CalendarServer-changes] [12111] CalendarServer/branches/users/cdaboo/cross-pod-sharing
source_changes at macosforge.org
source_changes at macosforge.org
Wed Mar 12 11:17:54 PDT 2014
Revision: 12111
http://trac.calendarserver.org//changeset/12111
Author: cdaboo at apple.com
Date: 2013-12-16 09:12:05 -0800 (Mon, 16 Dec 2013)
Log Message:
-----------
Checkpoint: fixes for freebusy and sync, plus other bits. Working through complete set of cross-pod CDT items. More to come.
Modified Paths:
--------------
CalendarServer/branches/users/cdaboo/cross-pod-sharing/twistedcaldav/resource.py
CalendarServer/branches/users/cdaboo/cross-pod-sharing/twistedcaldav/sharing.py
CalendarServer/branches/users/cdaboo/cross-pod-sharing/twistedcaldav/storebridge.py
CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/scheduling/freebusy.py
CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/scheduling/ischedule/xml.py
CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/sql.py
CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/test/common.py
CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/test/test_sql_sharing.py
CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/sql.py
CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/test/test_sql_sharing.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/resource.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/podding/test/util.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
Added Paths:
-----------
CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/test/test_sql_external.py
Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/twistedcaldav/resource.py
===================================================================
--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/twistedcaldav/resource.py 2013-12-14 06:28:16 UTC (rev 12110)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/twistedcaldav/resource.py 2013-12-16 17:12:05 UTC (rev 12111)
@@ -2667,10 +2667,9 @@
@inlineCallbacks
def _indexWhatChanged(self, revision, depth):
# The newstore implementation supports this directly
- changed, deleted = yield self._newStoreHome.resourceNamesSinceToken(
+ changed, deleted, notallowed = yield self._newStoreHome.resourceNamesSinceToken(
revision, depth
)
- notallowed = []
# Need to insert some addition items on first sync
if revision == 0:
@@ -2894,10 +2893,9 @@
@inlineCallbacks
def _indexWhatChanged(self, revision, depth):
# The newstore implementation supports this directly
- changed, deleted = yield self._newStoreHome.resourceNamesSinceToken(
+ changed, deleted, notallowed = yield self._newStoreHome.resourceNamesSinceToken(
revision, depth
)
- notallowed = []
# Need to insert some addition items on first sync
if revision == 0:
Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/twistedcaldav/sharing.py
===================================================================
--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/twistedcaldav/sharing.py 2013-12-14 06:28:16 UTC (rev 12110)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/twistedcaldav/sharing.py 2013-12-16 17:12:05 UTC (rev 12111)
@@ -805,6 +805,12 @@
# Accept the share
shareeView = yield self._newStoreHome.acceptShare(inviteUID, summary)
+ if shareeView is None:
+ raise HTTPError(ErrorResponse(
+ responsecode.FORBIDDEN,
+ (calendarserver_namespace, "invalid-share"),
+ "Invite UID not valid",
+ ))
# Return the URL of the shared collection
sharedAsURL = joinURL(self.url(), shareeView.shareName())
@@ -820,7 +826,13 @@
def declineShare(self, request, inviteUID):
# Remove it if it is in the DB
- yield self._newStoreHome.declineShare(inviteUID)
+ result = yield self._newStoreHome.declineShare(inviteUID)
+ if not result:
+ raise HTTPError(ErrorResponse(
+ responsecode.FORBIDDEN,
+ (calendarserver_namespace, "invalid-share"),
+ "Invite UID not valid",
+ ))
returnValue(Response(code=responsecode.NO_CONTENT))
Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/twistedcaldav/storebridge.py
===================================================================
--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/twistedcaldav/storebridge.py 2013-12-14 06:28:16 UTC (rev 12110)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/twistedcaldav/storebridge.py 2013-12-16 17:12:05 UTC (rev 12111)
@@ -462,6 +462,8 @@
if self.isShareeResource():
log.debug("Removing shared collection %s" % (self,))
yield self.removeShareeResource(request)
+ # Re-initialize to get stuff setup again now we have no object
+ self._initializeWithHomeChild(None, self._parentResource)
returnValue(NO_CONTENT)
log.debug("Deleting collection %s" % (self,))
@@ -3371,6 +3373,8 @@
if self.isShareeResource():
log.debug("Removing shared resource %s" % (self,))
yield self.removeShareeResource(request)
+ # Re-initialize to get stuff setup again now we have no object
+ self._initializeWithObject(None, self._newStoreParent)
returnValue(NO_CONTENT)
elif self._newStoreObject.isGroupForSharedAddressBook():
abCollectionResource = (yield request.locateResource(parentForURL(request.uri)))
Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/scheduling/freebusy.py
===================================================================
--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/scheduling/freebusy.py 2013-12-14 06:28:16 UTC (rev 12110)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/scheduling/freebusy.py 2013-12-16 17:12:05 UTC (rev 12111)
@@ -92,7 +92,6 @@
- at inlineCallbacks
def generateFreeBusyInfo(
calresource,
fbinfo,
@@ -107,6 +106,86 @@
logItems=None,
):
"""
+ Get freebusy information for a calendar. Different behavior for internal vs external calendars.
+
+ See L{_internalGenerateFreeBusyInfo} for argument description.
+ """
+
+ # TODO: this method really should be moved to L{CalendarObject} so the internal/external pieces
+ # can be split across L{CalendarObject} and L{CalendarObjectExternal}
+ if calresource.external():
+ return _externalGenerateFreeBusyInfo(
+ calresource,
+ fbinfo,
+ timerange,
+ matchtotal,
+ excludeuid,
+ organizer,
+ organizerPrincipal,
+ same_calendar_user,
+ servertoserver,
+ event_details,
+ logItems
+ )
+ else:
+ return _internalGenerateFreeBusyInfo(
+ calresource,
+ fbinfo,
+ timerange,
+ matchtotal,
+ excludeuid,
+ organizer,
+ organizerPrincipal,
+ same_calendar_user,
+ servertoserver,
+ event_details,
+ logItems
+ )
+
+
+
+ at inlineCallbacks
+def _externalGenerateFreeBusyInfo(
+ calresource,
+ fbinfo,
+ timerange,
+ matchtotal,
+ excludeuid=None,
+ organizer=None,
+ organizerPrincipal=None,
+ same_calendar_user=False,
+ servertoserver=False,
+ event_details=None,
+ logItems=None,
+):
+ """
+ Generate a freebusy response for an external (cross-pod) calendar by making a cross-pod call. This will bypass
+ any type of smart caching on this pod in favor of using caching on the pod hosting the actual calendar data.
+
+ See L{_internalGenerateFreeBusyInfo} for argument description.
+ """
+ fbresults, matchtotal = yield calresource._txn.store().conduit.send_freebusy(calresource, timerange, matchtotal, excludeuid, organizer, organizerPrincipal, same_calendar_user, servertoserver, event_details)
+ for i in range(3):
+ fbinfo[i].extend([Period.parseText(p) for p in fbresults[i]])
+ returnValue(matchtotal)
+
+
+
+ at inlineCallbacks
+def _internalGenerateFreeBusyInfo(
+ calresource,
+ fbinfo,
+ timerange,
+ matchtotal,
+ excludeuid=None,
+ organizer=None,
+ organizerPrincipal=None,
+ same_calendar_user=False,
+ servertoserver=False,
+ event_details=None,
+ logItems=None,
+):
+ """
Run a free busy report on the specified calendar collection
accumulating the free busy info for later processing.
@param calresource: the L{Calendar} for a calendar collection.
Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/scheduling/ischedule/xml.py
===================================================================
--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/scheduling/ischedule/xml.py 2013-12-14 06:28:16 UTC (rev 12110)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/scheduling/ischedule/xml.py 2013-12-16 17:12:05 UTC (rev 12111)
@@ -259,14 +259,18 @@
@classmethod
- def fromCalendar(clazz, calendar):
+ def fromCalendar(clazz, calendar, format=None):
+ attrs = {}
+ if format is not None and format != "text/calendar":
+ attrs["content-type"] = format
+
if isinstance(calendar, str):
if not calendar:
raise ValueError("Missing calendar data")
return clazz(PCDATAElement(calendar))
elif isinstance(calendar, iComponent):
assert calendar.name() == "VCALENDAR", "Not a calendar: %r" % (calendar,)
- return clazz(PCDATAElement(calendar.getTextWithTimezones(includeTimezones=not config.EnableTimezonesByReference)))
+ return clazz(PCDATAElement(calendar.getTextWithTimezones(includeTimezones=not config.EnableTimezonesByReference, format=format)))
else:
raise ValueError("Not a calendar: %s" % (calendar,))
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-14 06:28:16 UTC (rev 12110)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/sql.py 2013-12-16 17:12:05 UTC (rev 12111)
@@ -2147,6 +2147,8 @@
Scheduling will be done automatically.
"""
+ if isinstance(component, str) or isinstance(component, unicode):
+ component = self._componentClass.fromString(component)
return self._setComponentInternal(component, inserting, ComponentUpdateState.NORMAL, smart_merge)
Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/test/common.py
===================================================================
--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/test/common.py 2013-12-14 06:28:16 UTC (rev 12110)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/test/common.py 2013-12-16 17:12:05 UTC (rev 12111)
@@ -1602,7 +1602,7 @@
home = yield self.homeUnderTest()
- changed, deleted = yield home.resourceNamesSinceToken(
+ changed, deleted, invalid = yield home.resourceNamesSinceToken(
self.token2revision(st), "infinity")
self.assertEquals(set(changed), set(["calendar_1/",
@@ -1610,11 +1610,13 @@
"calendar_1/2.ics",
"other-calendar/"]))
self.assertEquals(set(deleted), set(["calendar_1/2.ics"]))
+ self.assertEquals(invalid, [])
- changed, deleted = yield home.resourceNamesSinceToken(
+ changed, deleted, invalid = yield home.resourceNamesSinceToken(
self.token2revision(st2), "infinity")
self.assertEquals(changed, [])
self.assertEquals(deleted, [])
+ self.assertEquals(invalid, [])
@inlineCallbacks
Added: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/test/test_sql_external.py
===================================================================
--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/test/test_sql_external.py (rev 0)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/test/test_sql_external.py 2013-12-16 17:12:05 UTC (rev 12111)
@@ -0,0 +1,680 @@
+##
+# Copyright (c) 2013 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+
+from twisted.internet.defer import inlineCallbacks
+
+from twext.python.clsprop import classproperty
+from txdav.common.datastore.test.util import populateCalendarsFrom
+from txdav.common.datastore.sql_tables import _BIND_MODE_READ, \
+ _BIND_STATUS_INVITED, _BIND_MODE_DIRECT, _BIND_STATUS_ACCEPTED
+from txdav.common.datastore.podding.test.util import MultiStoreConduitTest
+
+
+class BaseSharingTests(MultiStoreConduitTest):
+
+ """
+ Test store-based calendar sharing.
+ """
+
+ @inlineCallbacks
+ def setUp(self):
+ yield super(BaseSharingTests, self).setUp()
+ yield self.populate()
+
+
+ @inlineCallbacks
+ def populate(self):
+ yield populateCalendarsFrom(self.requirements, self.storeUnderTest())
+ self.notifierFactory.reset()
+
+ cal1 = """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:uid1
+DTSTART:20131122T140000
+DURATION:PT1H
+CREATED:20060102T190000Z
+DTSTAMP:20051222T210507Z
+SUMMARY:event 1
+END:VEVENT
+END:VCALENDAR
+"""
+
+ @classproperty(cache=False)
+ def requirements(cls): #@NoSelf
+ return {
+ "user01": {
+ "calendar": {
+ "cal1.ics": (cls.cal1, None,),
+ },
+ "inbox": {
+ },
+ },
+ "user02": {
+ "calendar": {
+ },
+ "inbox": {
+ },
+ },
+ "user03": {
+ "calendar": {
+ },
+ "inbox": {
+ },
+ },
+ }
+
+
+
+class CalendarSharing(BaseSharingTests):
+
+ @inlineCallbacks
+ def test_no_shares(self):
+ """
+ Test that initially there are no shares.
+ """
+
+ calendar = yield self.calendarUnderTest(home="user01", name="calendar")
+ invites = yield calendar.sharingInvites()
+ self.assertEqual(len(invites), 0)
+ self.assertFalse(calendar.isShared())
+
+
+ @inlineCallbacks
+ def test_invite_sharee(self):
+ """
+ Test invite/uninvite creates/removes shares and notifications.
+ """
+
+ # Invite
+ calendar = yield self.calendarUnderTest(home="user01", name="calendar")
+ invites = yield calendar.sharingInvites()
+ self.assertEqual(len(invites), 0)
+ self.assertFalse(calendar.isShared())
+
+ shareeView = yield calendar.inviteUserToShare("puser02", _BIND_MODE_READ, "summary")
+ invites = yield calendar.sharingInvites()
+ self.assertEqual(len(invites), 1)
+ self.assertEqual(invites[0].uid, shareeView.shareUID())
+ self.assertEqual(invites[0].ownerUID, "user01")
+ self.assertEqual(invites[0].shareeUID, "puser02")
+ self.assertEqual(invites[0].mode, _BIND_MODE_READ)
+ self.assertEqual(invites[0].status, _BIND_STATUS_INVITED)
+ self.assertEqual(invites[0].summary, "summary")
+
+ inviteUID = shareeView.shareUID()
+ sharedName = shareeView.name()
+
+ self.assertTrue(calendar.isShared())
+
+ yield self.commit()
+
+ shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home="puser02", name=sharedName)
+ self.assertTrue(shared is None)
+
+ notifyHome = yield self.otherTransactionUnderTest().notificationsWithUID("puser02")
+ notifications = yield notifyHome.listNotificationObjects()
+ self.assertEqual(notifications, [inviteUID, ])
+ yield self.otherCommit()
+
+ # Uninvite
+ calendar = yield self.calendarUnderTest(home="user01", name="calendar")
+ invites = yield calendar.sharingInvites()
+ self.assertEqual(len(invites), 1)
+
+ yield calendar.uninviteUserFromShare("puser02")
+ invites = yield calendar.sharingInvites()
+ self.assertEqual(len(invites), 0)
+
+ self.assertTrue(calendar.isShared())
+
+ yield self.commit()
+
+ notifyHome = yield self.otherTransactionUnderTest().notificationsWithUID("puser02")
+ notifications = yield notifyHome.listNotificationObjects()
+ self.assertEqual(notifications, [])
+ yield self.otherCommit()
+
+ calendar = yield self.calendarUnderTest(home="user01", name="calendar")
+ self.assertTrue(calendar.isShared())
+ yield calendar.setShared(False)
+ self.assertFalse(calendar.isShared())
+
+
+ @inlineCallbacks
+ def test_accept_share(self):
+ """
+ Test that invite+accept creates shares and notifications.
+ """
+
+ # Invite
+ calendar = yield self.calendarUnderTest(home="user01", name="calendar")
+ invites = yield calendar.sharingInvites()
+ self.assertEqual(len(invites), 0)
+ self.assertFalse(calendar.isShared())
+
+ shareeView = yield calendar.inviteUserToShare("puser02", _BIND_MODE_READ, "summary")
+ invites = yield calendar.sharingInvites()
+ self.assertEqual(len(invites), 1)
+
+ inviteUID = shareeView.shareUID()
+ sharedName = shareeView.name()
+
+ self.assertTrue(calendar.isShared())
+
+ yield self.commit()
+
+ shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home="puser02", name=sharedName)
+ self.assertTrue(shared is None)
+
+ notifyHome = yield self.otherTransactionUnderTest().notificationsWithUID("puser02")
+ notifications = yield notifyHome.listNotificationObjects()
+ self.assertEqual(len(notifications), 1)
+ yield self.otherCommit()
+
+ # Accept
+ txn2 = self.newOtherTransaction()
+ shareeHome = yield self.homeUnderTest(txn=txn2, name="puser02")
+ yield shareeHome.acceptShare(inviteUID)
+
+ shared = yield self.calendarUnderTest(txn=txn2, home="puser02", name=sharedName)
+ self.assertTrue(shared is not None)
+ yield self.otherCommit()
+
+ notifyHome = yield self.transactionUnderTest().notificationsWithUID("user01")
+ notifications = yield notifyHome.listNotificationObjects()
+ self.assertEqual(notifications, [inviteUID + "-reply", ])
+
+ calendar = yield self.calendarUnderTest(home="user01", name="calendar")
+ self.assertTrue(calendar.isShared())
+
+ yield self.commit()
+
+ # Re-accept
+ txn2 = self.newOtherTransaction()
+ shareeHome = yield self.homeUnderTest(txn=txn2, name="puser02")
+ yield shareeHome.acceptShare(inviteUID)
+
+ shared = yield self.calendarUnderTest(txn=txn2, home="puser02", name=sharedName)
+ self.assertTrue(shared is not None)
+ yield self.otherCommit()
+
+ notifyHome = yield self.transactionUnderTest().notificationsWithUID("user01")
+ notifications = yield notifyHome.listNotificationObjects()
+ self.assertEqual(notifications, [inviteUID + "-reply", ])
+
+ calendar = yield self.calendarUnderTest(home="user01", name="calendar")
+ self.assertTrue(calendar.isShared())
+
+
+ @inlineCallbacks
+ def test_decline_share(self):
+ """
+ Test that invite+decline does not create shares but does create notifications.
+ """
+
+ # Invite
+ calendar = yield self.calendarUnderTest(home="user01", name="calendar")
+ invites = yield calendar.sharingInvites()
+ self.assertEqual(len(invites), 0)
+ self.assertFalse(calendar.isShared())
+
+ shareeView = yield calendar.inviteUserToShare("puser02", _BIND_MODE_READ, "summary")
+ invites = yield calendar.sharingInvites()
+ self.assertEqual(len(invites), 1)
+
+ inviteUID = shareeView.shareUID()
+ sharedName = shareeView.name()
+
+ self.assertTrue(calendar.isShared())
+
+ yield self.commit()
+
+ txn2 = self.newOtherTransaction()
+ shared = yield self.calendarUnderTest(txn=txn2, home="puser02", name=sharedName)
+ self.assertTrue(shared is None)
+
+ notifyHome = yield txn2.notificationsWithUID("puser02")
+ notifications = yield notifyHome.listNotificationObjects()
+ self.assertEqual(len(notifications), 1)
+ yield self.otherCommit()
+
+ # Decline
+ txn2 = self.newOtherTransaction()
+ shareeHome = yield self.homeUnderTest(txn=txn2, name="puser02")
+ yield shareeHome.declineShare(inviteUID)
+
+ shared = yield self.calendarUnderTest(txn=txn2, home="puser02", name=sharedName)
+ self.assertTrue(shared is None)
+ yield self.otherCommit()
+
+ notifyHome = yield self.transactionUnderTest().notificationsWithUID("user01")
+ notifications = yield notifyHome.listNotificationObjects()
+ self.assertEqual(notifications, [inviteUID + "-reply", ])
+
+ calendar = yield self.calendarUnderTest(home="user01", name="calendar")
+ self.assertTrue(calendar.isShared())
+
+ yield self.commit()
+
+ # Redecline
+ txn2 = self.newOtherTransaction()
+ shareeHome = yield self.homeUnderTest(txn=txn2, name="puser02")
+ yield shareeHome.declineShare(inviteUID)
+
+ shared = yield self.calendarUnderTest(txn=txn2, home="puser02", name=sharedName)
+ self.assertTrue(shared is None)
+ yield self.otherCommit()
+
+ notifyHome = yield self.transactionUnderTest().notificationsWithUID("user01")
+ notifications = yield notifyHome.listNotificationObjects()
+ self.assertEqual(notifications, [inviteUID + "-reply", ])
+
+ calendar = yield self.calendarUnderTest(home="user01", name="calendar")
+ self.assertTrue(calendar.isShared())
+
+
+ @inlineCallbacks
+ def test_accept_decline_share(self):
+ """
+ Test that invite+accept/decline creates/removes shares and notifications.
+ Decline via the home.
+ """
+
+ # Invite
+ calendar = yield self.calendarUnderTest(home="user01", name="calendar")
+ invites = yield calendar.sharingInvites()
+ self.assertEqual(len(invites), 0)
+ self.assertFalse(calendar.isShared())
+
+ shareeView = yield calendar.inviteUserToShare("puser02", _BIND_MODE_READ, "summary")
+ invites = yield calendar.sharingInvites()
+ self.assertEqual(len(invites), 1)
+ inviteUID = shareeView.shareUID()
+
+ sharedName = shareeView.name()
+
+ self.assertTrue(calendar.isShared())
+
+ yield self.commit()
+
+ txn2 = self.newOtherTransaction()
+ shared = yield self.calendarUnderTest(txn=txn2, home="puser02", name=sharedName)
+ self.assertTrue(shared is None)
+
+ notifyHome = yield txn2.notificationsWithUID("puser02")
+ notifications = yield notifyHome.listNotificationObjects()
+ self.assertEqual(len(notifications), 1)
+ yield self.otherCommit()
+
+ # Accept
+ txn2 = self.newOtherTransaction()
+ shareeHome = yield self.homeUnderTest(txn=txn2, name="puser02")
+ yield shareeHome.acceptShare(inviteUID)
+
+ shared = yield self.calendarUnderTest(txn=txn2, home="puser02", name=sharedName)
+ self.assertTrue(shared is not None)
+ yield self.otherCommit()
+
+ notifyHome = yield self.transactionUnderTest().notificationsWithUID("user01")
+ notifications = yield notifyHome.listNotificationObjects()
+ self.assertEqual(notifications, [inviteUID + "-reply", ])
+
+ calendar = yield self.calendarUnderTest(home="user01", name="calendar")
+ self.assertTrue(calendar.isShared())
+
+ yield self.commit()
+
+ # Decline
+ txn2 = self.newOtherTransaction()
+ shareeHome = yield self.homeUnderTest(txn=txn2, name="puser02")
+ yield shareeHome.declineShare(inviteUID)
+
+ shared = yield self.calendarUnderTest(txn=txn2, home="puser02", name=sharedName)
+ self.assertTrue(shared is None)
+ yield self.otherCommit()
+
+ notifyHome = yield self.transactionUnderTest().notificationsWithUID("user01")
+ notifications = yield notifyHome.listNotificationObjects()
+ self.assertEqual(notifications, [inviteUID + "-reply", ])
+
+ calendar = yield self.calendarUnderTest(home="user01", name="calendar")
+ self.assertTrue(calendar.isShared())
+
+
+ @inlineCallbacks
+ def test_accept_remove_share(self):
+ """
+ Test that invite+accept/decline creates/removes shares and notifications.
+ Decline via the shared collection (removal).
+ """
+
+ # Invite
+ calendar = yield self.calendarUnderTest(home="user01", name="calendar")
+ invites = yield calendar.sharingInvites()
+ self.assertEqual(len(invites), 0)
+
+ shareeView = yield calendar.inviteUserToShare("puser02", _BIND_MODE_READ, "summary")
+ invites = yield calendar.sharingInvites()
+ self.assertEqual(len(invites), 1)
+
+ inviteUID = shareeView.shareUID()
+ sharedName = shareeView.name()
+
+ yield self.commit()
+
+ txn2 = self.newOtherTransaction()
+ shared = yield self.calendarUnderTest(txn=txn2, home="puser02", name=sharedName)
+ self.assertTrue(shared is None)
+
+ notifyHome = yield txn2.notificationsWithUID("puser02")
+ notifications = yield notifyHome.listNotificationObjects()
+ self.assertEqual(len(notifications), 1)
+ yield self.otherCommit()
+
+ # Accept
+ txn2 = self.newOtherTransaction()
+ shareeHome = yield self.homeUnderTest(txn=txn2, name="puser02")
+ yield shareeHome.acceptShare(inviteUID)
+
+ shared = yield self.calendarUnderTest(txn=txn2, home="puser02", name=sharedName)
+ self.assertTrue(shared is not None)
+ yield self.otherCommit()
+
+ notifyHome = yield self.transactionUnderTest().notificationsWithUID("user01")
+ notifications = yield notifyHome.listNotificationObjects()
+ self.assertEqual(notifications, [inviteUID + "-reply", ])
+
+ yield self.commit()
+
+ # Delete
+ txn2 = self.newOtherTransaction()
+ shared = yield self.calendarUnderTest(txn=txn2, home="puser02", name=sharedName)
+ yield shared.deleteShare()
+ yield self.otherCommit()
+
+ txn2 = self.newOtherTransaction()
+ shared = yield self.calendarUnderTest(txn=txn2, home="puser02", name=sharedName)
+ self.assertTrue(shared is None)
+ yield self.otherCommit()
+
+ notifyHome = yield self.transactionUnderTest().notificationsWithUID("user01")
+ notifications = yield notifyHome.listNotificationObjects()
+ self.assertEqual(notifications, [inviteUID + "-reply", ])
+
+
+ @inlineCallbacks
+ def test_accept_remove_accept(self):
+ yield self.createShare()
+ yield self.removeShare()
+ shared_name = yield self.createShare()
+
+ txn2 = self.newOtherTransaction()
+ otherCal = yield self.calendarUnderTest(txn=txn2, home="puser02", name=shared_name)
+ self.assertTrue(otherCal is not None)
+ yield self.otherCommit()
+
+
+ @inlineCallbacks
+ def test_accept_remove_accept_newcalendar(self):
+ """
+ Test that deleting and re-creating a share with the same sharer name works.
+ """
+
+ home = yield self.homeUnderTest(name="user01", create=True)
+ yield home.createCalendarWithName("shared")
+ yield self.commit()
+
+ shared_name = yield self.createShare(name="shared")
+
+ txn2 = self.newOtherTransaction()
+ otherCal = yield self.calendarUnderTest(txn=txn2, home="puser02", name=shared_name)
+ self.assertTrue(otherCal is not None)
+ yield self.otherCommit()
+
+ yield self.removeShare(name="shared")
+ home = yield self.homeUnderTest(name="user01", create=True)
+ yield home.removeCalendarWithName("shared")
+ yield self.commit()
+
+ txn2 = self.newOtherTransaction()
+ otherCal = yield self.calendarUnderTest(txn=txn2, home="puser02", name=shared_name)
+ self.assertTrue(otherCal is None)
+ yield self.otherCommit()
+
+ home = yield self.homeUnderTest(name="user01", create=True)
+ yield home.createCalendarWithName("shared")
+ yield self.commit()
+
+ shared_name = yield self.createShare(name="shared")
+
+ txn2 = self.newOtherTransaction()
+ otherCal = yield self.calendarUnderTest(txn=txn2, home="puser02", name=shared_name)
+ self.assertTrue(otherCal is not None)
+ yield self.otherCommit()
+
+
+ @inlineCallbacks
+ def test_inviteProperties(self):
+
+ calendar = yield self.calendarUnderTest(home="user01", name="calendar")
+ yield calendar.setUsedForFreeBusy(True)
+ yield self.commit()
+
+ shared_name = yield self.createShare()
+
+ txn2 = self.newOtherTransaction()
+ shared = yield self.calendarUnderTest(txn=txn2, home="puser02", name=shared_name)
+ self.assertFalse(shared.isUsedForFreeBusy())
+
+
+ @inlineCallbacks
+ def test_direct_sharee(self):
+ """
+ Test invite/uninvite creates/removes shares and notifications.
+ """
+
+ # Invite
+ calendar = yield self.calendarUnderTest(home="user01", name="calendar")
+ invites = yield calendar.sharingInvites()
+ self.assertEqual(len(invites), 0)
+ self.assertFalse(calendar.isShared())
+
+ shareeView = yield calendar.directShareWithUser("puser02")
+ invites = yield calendar.sharingInvites()
+ self.assertEqual(len(invites), 1)
+ self.assertEqual(invites[0].uid, shareeView.shareUID())
+ self.assertEqual(invites[0].ownerUID, "user01")
+ self.assertEqual(invites[0].shareeUID, "puser02")
+ self.assertEqual(invites[0].mode, _BIND_MODE_DIRECT)
+ self.assertEqual(invites[0].status, _BIND_STATUS_ACCEPTED)
+
+ sharedName = shareeView.name()
+
+ yield self.commit()
+
+ txn2 = self.newOtherTransaction()
+ shared = yield self.calendarUnderTest(txn=txn2, home="user02", name=sharedName)
+ self.assertTrue(shared is not None)
+
+ notifyHome = yield txn2.notificationsWithUID("user02")
+ notifications = yield notifyHome.listNotificationObjects()
+ self.assertEqual(len(notifications), 0)
+ yield self.otherCommit()
+
+ # Remove
+ txn2 = self.newOtherTransaction()
+ shared = yield self.calendarUnderTest(txn=txn2, home="puser02", name=sharedName)
+ yield shared.deleteShare()
+ yield self.otherCommit()
+
+ calendar = yield self.calendarUnderTest(home="user01", name="calendar")
+ invites = yield calendar.sharingInvites()
+ self.assertEqual(len(invites), 0)
+
+ notifyHome = yield self.transactionUnderTest().notificationsWithUID("user01")
+ notifications = yield notifyHome.listNotificationObjects()
+ self.assertEqual(len(notifications), 0)
+
+ test_direct_sharee.skip = True
+
+ @inlineCallbacks
+ def test_sharedNotifierID(self):
+ shared_name = yield self.createShare()
+
+ home = yield self.homeUnderTest(name="user01")
+ self.assertEquals(home.notifierID(), ("CalDAV", "user01",))
+ calendar = yield home.calendarWithName("calendar")
+ self.assertEquals(calendar.notifierID(), ("CalDAV", "user01/calendar",))
+ yield self.commit()
+
+ txn2 = self.newOtherTransaction()
+ home = yield self.homeUnderTest(txn=txn2, name="puser02")
+ self.assertEquals(home.notifierID(), ("CalDAV", "puser02",))
+ calendar = yield home.calendarWithName(shared_name)
+ self.assertEquals(calendar.notifierID(), ("CalDAV", "user01/calendar",))
+
+
+ @inlineCallbacks
+ def test_sharedWithTwo(self):
+ shared_name1 = yield self.createShare(shareeGUID="puser02")
+ shared_name2 = yield self.createShare(shareeGUID="puser03")
+
+ txn2 = self.newOtherTransaction()
+ otherCal = yield self.calendarUnderTest(txn=txn2, home="puser02", name=shared_name1)
+ self.assertTrue(otherCal is not None)
+ yield self.otherCommit()
+
+ txn2 = self.newOtherTransaction()
+ otherCal = yield self.calendarUnderTest(txn=txn2, home="puser03", name=shared_name2)
+ self.assertTrue(otherCal is not None)
+ yield self.otherCommit()
+
+
+
+class SharingRevisions(BaseSharingTests):
+ """
+ Test store-based sharing and interaction with revision table.
+ """
+
+ @inlineCallbacks
+ def test_shareWithRevision(self):
+ """
+ Verify that bindRevision on calendars and shared calendars has the correct value.
+ """
+ sharedName = yield self.createShare()
+
+ normalCal = yield self.calendarUnderTest(home="user01", name="calendar")
+ self.assertEqual(normalCal._bindRevision, 0)
+
+ txn2 = self.newOtherTransaction()
+ otherCal = yield self.calendarUnderTest(txn=txn2, home="puser02", name=sharedName)
+ self.assertNotEqual(otherCal._bindRevision, 0)
+
+
+ @inlineCallbacks
+ def test_updateShareRevision(self):
+ """
+ Verify that bindRevision on calendars and shared calendars has the correct value.
+ """
+ # Invite
+ calendar = yield self.calendarUnderTest(home="user01", name="calendar")
+ invites = yield calendar.sharingInvites()
+ self.assertEqual(len(invites), 0)
+
+ shareeView = yield calendar.inviteUserToShare("puser02", _BIND_MODE_READ, "summary")
+ newCalName = shareeView.shareUID()
+ yield self.commit()
+
+ normalCal = yield self.calendarUnderTest(home="user01", name="calendar")
+ self.assertEqual(normalCal._bindRevision, 0)
+ yield self.commit()
+
+ txn2 = self.newOtherTransaction()
+ otherHome = yield self.homeUnderTest(txn=txn2, name="puser02")
+ otherCal = yield otherHome.anyObjectWithShareUID(newCalName)
+ self.assertEqual(otherCal._bindRevision, 0)
+ yield self.otherCommit()
+
+ txn2 = self.newOtherTransaction()
+ shareeHome = yield self.homeUnderTest(txn=txn2, name="puser02")
+ shareeView = yield shareeHome.acceptShare(newCalName)
+ sharedName = shareeView.name()
+ yield self.otherCommit()
+
+ normalCal = yield self.calendarUnderTest(home="user01", name="calendar")
+ self.assertEqual(normalCal._bindRevision, 0)
+
+ txn2 = self.newOtherTransaction()
+ otherCal = yield self.calendarUnderTest(txn=txn2, home="puser02", name=sharedName)
+ self.assertNotEqual(otherCal._bindRevision, 0)
+
+
+ @inlineCallbacks
+ def test_sharedRevisions(self):
+ """
+ Verify that resourceNamesSinceRevision returns all resources after initial bind and sync.
+ """
+ sharedName = yield self.createShare()
+
+ normalCal = yield self.calendarUnderTest(home="user01", name="calendar")
+ self.assertEqual(normalCal._bindRevision, 0)
+
+ txn2 = self.newOtherTransaction()
+ otherHome = yield self.homeUnderTest(txn=txn2, name="puser02")
+ otherCal = yield self.calendarUnderTest(txn=txn2, home="puser02", name=sharedName)
+ self.assertNotEqual(otherCal._bindRevision, 0)
+
+ sync_token = yield otherCal.syncToken()
+ revision = otherCal.revisionFromToken(sync_token)
+
+ changed, deleted = yield otherCal.resourceNamesSinceRevision(revision - 1)
+ self.assertNotEqual(len(changed), 0)
+ self.assertEqual(len(deleted), 0)
+
+ changed, deleted = yield otherCal.resourceNamesSinceRevision(revision)
+ self.assertEqual(len(changed), 0)
+ self.assertEqual(len(deleted), 0)
+
+ sync_token = yield otherHome.syncToken()
+ revision = otherHome.revisionFromToken(sync_token)
+
+ for depth in ("1", "infinity",):
+ changed, deleted, invalid = yield otherHome.resourceNamesSinceRevision(revision - 1, depth)
+ self.assertEqual(len(changed), 0 if depth == "infinity" else 1)
+ self.assertEqual(len(deleted), 0)
+ self.assertEqual(len(invalid), 1 if depth == "infinity" else 0)
+
+ changed, deleted, invalid = yield otherHome.resourceNamesSinceRevision(revision, depth)
+ self.assertEqual(len(changed), 0)
+ self.assertEqual(len(deleted), 0)
+ self.assertEqual(len(invalid), 1 if depth == "infinity" else 0)
+
+ yield self.otherCommit()
+
+ yield self.removeShare()
+
+ txn2 = self.newOtherTransaction()
+ otherHome = yield self.homeUnderTest(txn=txn2, name="puser02")
+
+ for depth in ("1", "infinity",):
+ changed, deleted, invalid = yield otherHome.resourceNamesSinceRevision(revision, depth)
+ self.assertEqual(len(changed), 0)
+ self.assertEqual(len(deleted), 1)
+ self.assertEqual(len(invalid), 0)
Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/test/test_sql_sharing.py
===================================================================
--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/test/test_sql_sharing.py 2013-12-14 06:28:16 UTC (rev 12110)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/test/test_sql_sharing.py 2013-12-16 17:12:05 UTC (rev 12111)
@@ -563,10 +563,12 @@
self.assertEqual(len(deleted), 0)
for depth in ("1", "infinity",):
- changed, deleted = yield otherHome.resourceNamesSinceRevision(otherCal._bindRevision - 1, depth)
+ changed, deleted, invalid = yield otherHome.resourceNamesSinceRevision(otherCal._bindRevision - 1, depth)
self.assertNotEqual(len(changed), 0)
self.assertEqual(len(deleted), 0)
+ self.assertEqual(len(invalid), 0)
- changed, deleted = yield otherHome.resourceNamesSinceRevision(otherCal._bindRevision, depth)
+ changed, deleted, invalid = yield otherHome.resourceNamesSinceRevision(otherCal._bindRevision, depth)
self.assertEqual(len(changed), 0)
self.assertEqual(len(deleted), 0)
+ self.assertEqual(len(invalid), 0)
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-14 06:28:16 UTC (rev 12110)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/sql.py 2013-12-16 17:12:05 UTC (rev 12111)
@@ -1956,6 +1956,9 @@
@inlineCallbacks
def setComponent(self, component, inserting=False):
+ if isinstance(component, str) or isinstance(component, unicode):
+ component = self._componentClass.fromString(component)
+
self._componentChanged = False
# Handle all validation operations here.
Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/test/test_sql_sharing.py
===================================================================
--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/test/test_sql_sharing.py 2013-12-14 06:28:16 UTC (rev 12110)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/test/test_sql_sharing.py 2013-12-16 17:12:05 UTC (rev 12111)
@@ -1144,13 +1144,15 @@
otherHome = yield self.addressbookHomeUnderTest(name="user02")
for depth in ("1", "infinity",):
- changed, deleted = yield otherHome.resourceNamesSinceRevision(otherAB._bindRevision - 1, depth)
+ changed, deleted, invalid = yield otherHome.resourceNamesSinceRevision(otherAB._bindRevision - 1, depth)
self.assertNotEqual(len(changed), 0)
self.assertEqual(len(deleted), 0)
+ self.assertEqual(len(invalid), 0)
- changed, deleted = yield otherHome.resourceNamesSinceRevision(otherAB._bindRevision, depth)
+ changed, deleted, invalid = yield otherHome.resourceNamesSinceRevision(otherAB._bindRevision, depth)
self.assertEqual(len(changed), 0)
self.assertEqual(len(deleted), 0)
+ self.assertEqual(len(invalid), 0)
@inlineCallbacks
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-14 06:28:16 UTC (rev 12110)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/conduit.py 2013-12-16 17:12:05 UTC (rev 12111)
@@ -22,6 +22,8 @@
from txdav.common.idirectoryservice import DirectoryRecordNotFoundError
from txdav.common.icommondatastore import ExternalShareFailed
from twisted.python.reflect import namedClass
+from txdav.caldav.datastore.scheduling.freebusy import generateFreeBusyInfo
+from twistedcaldav.caldavxml import TimeRange
__all__ = [
@@ -372,6 +374,66 @@
})
+ @inlineCallbacks
+ def send_shareremove(self, txn, homeType, ownerUID, shareeUID, shareUID):
+ """
+ Send a sharing remove cross-pod message.
+
+ @param homeType: Type of home being shared.
+ @type homeType: C{int}
+ @param ownerUID: GUID of the sharer.
+ @type ownerUID: C{str}
+ @param shareeUID: GUID of the recipient
+ @type shareeUID: C{str}
+ @param shareUID: Resource/invite ID for recipient
+ @type shareUID: C{str}
+ """
+
+ _ignore_sender, recipient = self.validRequst(shareeUID, ownerUID)
+
+ action = {
+ "action": "shareremove",
+ "type": homeType,
+ "owner": ownerUID,
+ "sharee": shareeUID,
+ "share_id": shareUID,
+ }
+
+ result = yield self.sendRequest(txn, recipient, action)
+ returnValue(result)
+
+
+ @inlineCallbacks
+ def recv_shareremove(self, txn, message):
+ """
+ Process a sharing remove cross-pod message. Message arguments as per L{send_shareremove}.
+
+ @param message: message arguments
+ @type message: C{dict}
+ """
+
+ if message["action"] != "shareremove":
+ raise FailedCrossPodRequestError("Wrong action '{}' for recv_shareremove".format(message["action"]))
+
+ # Create a share
+ ownerHome = yield txn.homeWithUID(message["type"], message["owner"])
+ if ownerHome is None or ownerHome.external():
+ FailedCrossPodRequestError("Invalid owner UID specified")
+
+ try:
+ yield ownerHome.processExternalRemove(
+ message["owner"],
+ message["sharee"],
+ message["share_id"],
+ )
+ except ExternalShareFailed as e:
+ FailedCrossPodRequestError(str(e))
+
+ returnValue({
+ "result": "ok",
+ })
+
+
#
# Sharer data access related apis
#
@@ -415,24 +477,28 @@
if message["action"] != expected_action:
raise FailedCrossPodRequestError("Wrong action '{}' for recv_{}".format(message["action"], expected_action))
- # Create a share
- ownerHome = yield txn.homeWithUID(message["type"], message["owner"], create=True)
+ # Get a share
+ ownerHome = yield txn.homeWithUID(message["type"], message["owner"])
if ownerHome is None or ownerHome.external():
FailedCrossPodRequestError("Invalid owner UID specified")
- ownerHomeChild = yield ownerHome.childWithID(message["owner_id"])
- if ownerHomeChild is None:
- FailedCrossPodRequestError("Invalid owner shared resource specified")
+ shareeHome = yield txn.homeWithUID(message["type"], message["sharee"])
+ if shareeHome is None or not shareeHome.external():
+ FailedCrossPodRequestError("Invalid sharee UID specified")
+ shareeView = yield shareeHome.childWithID(message["owner_id"])
+ if shareeView is None:
+ FailedCrossPodRequestError("Invalid shared resource specified")
+
resourceID = message.get("resource_id", None)
if resourceID is not None:
- objectResource = yield ownerHomeChild.objectResourceWithID(resourceID)
+ objectResource = yield shareeView.objectResourceWithID(resourceID)
if objectResource is None:
FailedCrossPodRequestError("Invalid owner shared object resource specified")
else:
objectResource = None
- returnValue((ownerHome, ownerHomeChild, objectResource,))
+ returnValue((shareeView, objectResource,))
#
@@ -451,6 +517,8 @@
@type shareeView: L{CommonHomeChildExternal}
@param objectResource: the resource being operated on, or C{None} for classmethod.
@type objectResource: L{CommonObjectResourceExternal}
+ @param transform: a function used to convert the JSON result into return values.
+ @type transform: C{callable}
@param args: list of optional arguments.
@type args: C{list}
@param kwargs: optional keyword arguments.
@@ -485,41 +553,112 @@
@type transform: C{callable}
"""
- _ignore_ownerHome, ownerHomeChild, objectResource = yield self._recv(txn, message, actionName)
+ shareeView, 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", {}))
+ value = yield getattr(shareeView, 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", {}))
+ value = yield getattr(shareeView._objectResourceClass, method)(shareeView, *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,
+ "value": transform(value, shareeView, objectResource) if transform is not None else value,
})
+ @inlineCallbacks
+ def send_freebusy(
+ self,
+ calresource,
+ timerange,
+ matchtotal,
+ excludeuid,
+ organizer,
+ organizerPrincipal,
+ same_calendar_user,
+ servertoserver,
+ event_details,
+ ):
+ action, recipient = self._send("freebusy", calresource)
+ action["timerange"] = [timerange.start.getText(), timerange.end.getText()]
+ action["matchtotal"] = matchtotal
+ action["excludeuid"] = excludeuid
+ action["organizer"] = organizer
+ action["organizerPrincipal"] = organizerPrincipal
+ action["same_calendar_user"] = same_calendar_user
+ action["servertoserver"] = servertoserver
+ action["event_details"] = event_details
+ result = yield self.sendRequest(calresource._txn, recipient, action)
+ if result["result"] == "ok":
+ returnValue((result["fbresults"], result["matchtotal"],))
+ elif result["result"] == "exception":
+ raise namedClass(result["class"])(result["message"])
+
+
+ @inlineCallbacks
+ def recv_freebusy(self, txn, message):
+ """
+ Process a freebusy cross-pod message. Message arguments as per L{send_freebusy}.
+
+ @param message: message arguments
+ @type message: C{dict}
+ """
+
+ shareeView, _ignore_objectResource = yield self._recv(txn, message, "freebusy")
+ try:
+ # Operate on the L{CommonHomeChild}
+ fbinfo = [[], [], []]
+ matchtotal = yield generateFreeBusyInfo(
+ shareeView,
+ fbinfo,
+ TimeRange(start=message["timerange"][0], end=message["timerange"][1]),
+ message["matchtotal"],
+ message["excludeuid"],
+ message["organizer"],
+ message["organizerPrincipal"],
+ message["same_calendar_user"],
+ message["servertoserver"],
+ message["event_details"],
+ logItems=None
+ )
+ except Exception as e:
+ returnValue({
+ "result": "exception",
+ "class": ".".join((e.__class__.__module__, e.__class__.__name__,)),
+ "message": str(e),
+ })
+
+ for i in range(3):
+ for j in range(len(fbinfo[i])):
+ fbinfo[i][j] = fbinfo[i][j].getText()
+
+ returnValue({
+ "result": "ok",
+ "fbresults": fbinfo,
+ "matchtotal": matchtotal,
+ })
+
+
@staticmethod
- def _transform_string(value, ownerHomeChild, objectResource):
+ def _result_string(value, shareeView, objectResource):
return str(value)
@staticmethod
- def _transform_externalize(value, ownerHomeChild, objectResource):
- if isinstance(value, ownerHomeChild._objectResourceClass):
+ def _result_externalize(value, shareeView, objectResource):
+ if isinstance(value, shareeView._objectResourceClass):
value = value.externalize()
elif value is not None:
value = [v.externalize() for v in value]
@@ -528,14 +667,34 @@
@classmethod
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))
+ 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)
+ )
@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))
+ 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
@@ -547,10 +706,10 @@
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("loadallobjects", "loadAllObjects", transform_result=PoddingConduit._result_externalize)
+PoddingConduit._make_simple_object_action("loadallobjectswithnames", "loadAllObjectsWithNames", transform_result=PoddingConduit._result_externalize)
+PoddingConduit._make_simple_object_action("objectwith", "objectWith", transform_result=PoddingConduit._result_externalize)
+PoddingConduit._make_simple_object_action("create", "create", transform_result=PoddingConduit._result_externalize)
+PoddingConduit._make_simple_object_action("setcomponent", "setComponent")
+PoddingConduit._make_simple_object_action("component", "component", transform_result=PoddingConduit._result_string)
PoddingConduit._make_simple_object_action("remove", "remove")
Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/resource.py
===================================================================
--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/resource.py 2013-12-14 06:28:16 UTC (rev 12110)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/resource.py 2013-12-16 17:12:05 UTC (rev 12111)
@@ -139,6 +139,11 @@
self.log.error("Invalid JSON data in request: {ex}\n{body}", ex=e, body=body)
raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, "Invalid JSON data in request: {}\n{}".format(e, body)))
+ # Log extended item
+ if not hasattr(request, "extendedLogItems"):
+ request.extendedLogItems = {}
+ request.extendedLogItems["xpod"] = j["action"] if "action" in j else "unknown"
+
# Get the conduit to process the data
try:
result = yield self.store.conduit.processRequest(j)
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-14 06:28:16 UTC (rev 12110)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/test/test_conduit.py 2013-12-16 17:12:05 UTC (rev 12111)
@@ -32,6 +32,9 @@
from twistedcaldav.ical import Component, normalize_iCalStr
from txdav.common.icommondatastore import ObjectResourceNameAlreadyExistsError, \
ObjectResourceNameNotAllowedError
+from txdav.caldav.datastore.scheduling.freebusy import generateFreeBusyInfo
+from twistedcaldav.caldavxml import TimeRange
+from pycalendar.period import Period
class TestConduit (CommonCommonTests, twext.web2.dav.test.util.TestCase):
@@ -770,6 +773,7 @@
self.assertTrue(resource is not None)
self.assertEqual(resource.name(), "1.ics")
self.assertEqual(resource.uid(), "uid1")
+ self.assertFalse(resource._componentChanged)
yield self.otherCommit()
shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home="puser01", name="shared-calendar")
@@ -884,3 +888,42 @@
object1 = yield self.calendarObjectUnderTest(home="user01", calendar_name="calendar", name="1.ics")
self.assertTrue(object1 is None)
yield self.commit()
+
+
+ @inlineCallbacks
+ def test_freebusy(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()
+
+ fbstart = "{now:04d}0102T000000Z".format(**self.nowYear)
+ fbend = "{now:04d}0103T000000Z".format(**self.nowYear)
+
+ shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home="puser01", name="shared-calendar")
+
+ fbinfo = [[], [], []]
+ matchtotal = yield generateFreeBusyInfo(
+ shared,
+ fbinfo,
+ TimeRange(start=fbstart, end=fbend),
+ 0,
+ excludeuid=None,
+ organizer=None,
+ organizerPrincipal=None,
+ same_calendar_user=False,
+ servertoserver=False,
+ event_details=False,
+ logItems=None
+ )
+
+ self.assertEqual(matchtotal, 1)
+ self.assertEqual(fbinfo[0], [Period.parseText("{now:04d}0102T140000Z/PT1H".format(**self.nowYear)), ])
+ self.assertEqual(len(fbinfo[1]), 0)
+ self.assertEqual(len(fbinfo[2]), 0)
+ yield self.otherCommit()
Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/test/util.py
===================================================================
--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/test/util.py 2013-12-14 06:28:16 UTC (rev 12110)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/test/util.py 2013-12-16 17:12:05 UTC (rev 12111)
@@ -26,6 +26,8 @@
import twext.web2.dav.test.util
from txdav.common.datastore.sql_tables import _BIND_MODE_WRITE
+from twext.enterprise.ienterprise import AlreadyFinishedError
+import json
class FakeConduitRequest(object):
"""
@@ -52,7 +54,7 @@
def __init__(self, server, data):
self.server = server
- self.data = data
+ self.data = json.dumps(data)
@inlineCallbacks
@@ -61,6 +63,7 @@
# Generate an HTTP client request
try:
response = (yield self._processRequest())
+ response = json.loads(response)
except Exception as e:
raise ValueError("Failed cross-pod request: {}".format(e))
@@ -77,7 +80,8 @@
"""
store = self.storeMap[self.server.details()]
- result = yield store.conduit.processRequest(self.data)
+ result = yield store.conduit.processRequest(json.loads(self.data))
+ result = json.dumps(result)
returnValue(result)
@@ -121,10 +125,24 @@
def newOtherTransaction(self):
assert self.otherTransaction is None
store2 = self.otherStoreUnderTest()
- self.otherTransaction = store2.newTransaction()
+ txn = store2.newTransaction()
+ @inlineCallbacks
+ def maybeCommitThis():
+ try:
+ yield txn.commit()
+ except AlreadyFinishedError:
+ pass
+ self.addCleanup(maybeCommitThis)
+ self.otherTransaction = txn
return self.otherTransaction
+ def otherTransactionUnderTest(self):
+ if self.otherTransaction is None:
+ self.newOtherTransaction()
+ return self.otherTransaction
+
+
@inlineCallbacks
def otherCommit(self):
assert self.otherTransaction is not None
@@ -190,13 +208,24 @@
@inlineCallbacks
- def createShare(self, ownerGUID, shareeGUID, name="calendar"):
+ def createShare(self, ownerGUID="user01", shareeGUID="puser02", name="calendar"):
home = yield self.homeUnderTest(name=ownerGUID, create=True)
- calendar = yield home.calendarWithName("calendar")
+ calendar = yield home.calendarWithName(name)
yield calendar.inviteUserToShare(shareeGUID, _BIND_MODE_WRITE, "shared", shareName="shared-calendar")
yield self.commit()
home2 = yield self.homeUnderTest(txn=self.newOtherTransaction(), name=shareeGUID)
yield home2.acceptShare("shared-calendar")
yield self.otherCommit()
+
+ returnValue("shared-calendar")
+
+
+ @inlineCallbacks
+ def removeShare(self, ownerGUID="user01", shareeGUID="puser02", name="calendar"):
+
+ home = yield self.homeUnderTest(name=ownerGUID)
+ calendar = yield home.calendarWithName(name)
+ yield calendar.uninviteUserFromShare(shareeGUID)
+ 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-14 06:28:16 UTC (rev 12110)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/sql.py 2013-12-16 17:12:05 UTC (rev 12111)
@@ -85,6 +85,7 @@
from zope.interface import implements, directlyProvides
from collections import namedtuple
+import itertools
import json
import sys
import time
@@ -1471,7 +1472,7 @@
if shareeView is not None:
yield shareeView.declineShare()
- returnValue(shareeView)
+ returnValue(shareeView is not None)
#
@@ -1491,12 +1492,30 @@
# Try to find owner calendar via its external id
ownerView = yield ownerHome.childWithExternalID(ownerRID)
if ownerView is None:
- ownerView = yield ownerHome.createChildWithName(ownerName, externalID=ownerRID)
+ try:
+ ownerView = yield ownerHome.createChildWithName(ownerName, externalID=ownerRID)
+ except HomeChildNameAlreadyExistsError:
+ # This is odd - it means we possibly have a left over sharer collection which the sharer likely removed
+ # and re-created with the same name but now it has a different externalID and is not found by the initial
+ # query. What we do is check to see whether any shares still reference the old ID - if they do we are hosed.
+ # If not, we can remove the old item and create a new one.
+ oldOwnerView = yield ownerHome.childWithName(ownerName)
+ invites = yield oldOwnerView.sharingInvites()
+ if len(invites) != 0:
+ log.error("External invite collection name is present with a different externalID and still has shares")
+ raise
+ log.error("External invite collection name is present with a different externalID - trying to fix")
+ yield ownerHome.removeExternalChild(oldOwnerView)
+ ownerView = yield ownerHome.createChildWithName(ownerName, externalID=ownerRID)
+
if supported_components is not None and hasattr(ownerView, "setSupportedComponents"):
yield ownerView.setSupportedComponents(supported_components)
# Now carry out the share operation
- yield ownerView.inviteUserToShare(self.uid(), bindMode, summary, shareName=shareUID)
+ if bindMode == _BIND_MODE_DIRECT:
+ yield ownerView.directShareWithUser(self.uid(), shareName=shareUID)
+ else:
+ yield ownerView.inviteUserToShare(self.uid(), bindMode, summary, shareName=shareUID)
@inlineCallbacks
@@ -1518,7 +1537,12 @@
# Now carry out the share operation
yield ownerView.uninviteUserFromShare(self.uid())
+ # See if there are any references to the external share - if not remove it
+ invites = yield ownerView.sharingInvites()
+ if len(invites) == 0:
+ yield ownerHome.removeExternalChild(ownerView)
+
@inlineCallbacks
def processExternalReply(self, ownerUID, shareeUID, shareUID, bindStatus, summary=None):
"""
@@ -1544,7 +1568,29 @@
yield shareeHome.declineShare(shareUID)
+ @inlineCallbacks
+ def processExternalRemove(self, ownerUID, shareeUID, shareUID):
+ """
+ External invite received.
+ """
+ # Make sure the shareeUID and shareUID match
+
+ # Get the owner home - create external one if not present
+ shareeHome = yield self._txn.homeWithUID(self._homeType, shareeUID)
+ if shareeHome is None or not shareeHome.external():
+ raise ExternalShareFailed("Invalid sharee UID: {}".format(shareeUID))
+
+ # Try to find owner calendar via its external id
+ shareeView = yield shareeHome.anyObjectWithShareUID(shareUID)
+ if shareeView is None:
+ raise ExternalShareFailed("Invalid share UID: {}".format(shareUID))
+
+ # Now carry out the share operation
+ yield shareeView.deleteShare()
+
+
+
class CommonHome(SharingHomeMixIn):
log = Logger()
@@ -2158,6 +2204,10 @@
record a revision for the sharee home and sharee collection name with the "deleted" flag set. That way
the shared collection can be reported as removed.
+ For external shared collections we need to report them as invalid as we cannot aggregate the sync token
+ for this home with the sync token from the external share which is under the control of the other pod.
+ Reporting it as invalid means that clients should do requests directly on the share itself to sync it.
+
@param revision: the sync revision to compare to
@type revision: C{str}
@param depth: depth for determine what changed
@@ -2176,6 +2226,7 @@
changed = set()
deleted = set()
+ invalid = set()
deleted_collections = set()
changed_collections = set()
for path, name, wasdeleted in results:
@@ -2211,40 +2262,52 @@
shares = yield self.children()
for share in shares:
if not share.owned():
- sharerevision = 0 if revision < share._bindRevision else revision
- results = [
- (
- share.name(),
- name if name else "",
- wasdeleted
- )
- for name, wasdeleted in
- (yield Select([rev.RESOURCE_NAME, rev.DELETED],
- From=rev,
- Where=(rev.REVISION > sharerevision).And(
- rev.RESOURCE_ID == share._resourceID)).on(self._txn))
- if name
- ]
+ if share.external():
+ if depth == "1":
+ pass
+ else:
+ name = share.name() + "/"
+ invalid.add(name)
+ if name in changed:
+ changed.remove(name)
+ if name in deleted:
+ deleted.remove(name)
+ else:
+ sharerevision = 0 if revision < share._bindRevision else revision
+ results = [
+ (
+ share.name(),
+ name if name else "",
+ wasdeleted
+ )
+ for name, wasdeleted in
+ (yield Select([rev.RESOURCE_NAME, rev.DELETED],
+ From=rev,
+ Where=(rev.REVISION > sharerevision).And(
+ rev.RESOURCE_ID == share._resourceID)).on(self._txn))
+ if name
+ ]
- for path, name, wasdeleted in results:
- if wasdeleted:
- if sharerevision:
- if depth == "1":
- changed.add("%s/" % (path,))
- else:
- deleted.add("%s/%s" % (path, name,))
+ for path, name, wasdeleted in results:
+ if wasdeleted:
+ if sharerevision:
+ if depth == "1":
+ changed.add("%s/" % (path,))
+ else:
+ deleted.add("%s/%s" % (path, name,))
- for path, name, wasdeleted in results:
- # Always report collection as changed
- changed.add("%s/" % (path,))
- if name:
- # Resource changed - for depth "infinity" report resource as changed
- if depth != "1":
- changed.add("%s/%s" % (path, name,))
+ for path, name, wasdeleted in results:
+ # Always report collection as changed
+ changed.add("%s/" % (path,))
+ if name:
+ # Resource changed - for depth "infinity" report resource as changed
+ if depth != "1":
+ changed.add("%s/%s" % (path, name,))
changed = sorted(changed)
deleted = sorted(deleted)
- returnValue((changed, deleted))
+ invalid = sorted(invalid)
+ returnValue((changed, deleted, invalid,))
@inlineCallbacks
@@ -3084,6 +3147,11 @@
if shareeView is None:
shareeView = yield self.createShare(shareeUID=shareeUID, mode=_BIND_MODE_DIRECT, shareName=shareName)
yield shareeView.newShare()
+
+ # Check for external
+ if shareeView.viewerHome().external():
+ yield self._sendExternalInvite(shareeView)
+
returnValue(shareeView)
@@ -3154,6 +3222,8 @@
ownerView = yield self.ownerView()
if self.direct():
yield ownerView.removeShare(self)
+ if not ownerView.external():
+ yield self._removeExternalInvite(ownerView)
else:
yield self.declineShare()
@@ -3303,6 +3373,18 @@
)
+ @inlineCallbacks
+ def _removeExternalInvite(self):
+
+ yield self._txn.store().conduit.send_shareremove(
+ self._txn,
+ self.viewerHome()._homeType,
+ self.ownerHome().uid(),
+ self.viewerHome().uid(),
+ self.shareName(),
+ )
+
+
#
# Lower level API
#
@@ -4695,7 +4777,7 @@
@type revision: C{int}
"""
- if revision < self._bindRevision:
+ if revision < self._bindRevision and not self.external():
revision = 0
return super(CommonHomeChild, self).resourceNamesSinceRevision(revision)
@@ -5225,17 +5307,25 @@
)
+ @classmethod
+ def _otherSerializedAttributes(cls): #@NoSelf
+ return (
+ "_componentChanged",
+ )
+
+
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()])
+ return dict([(attr[1:], getattr(self, attr, None)) for attr in itertools.chain(self._rowAttributes(), self._otherSerializedAttributes())])
@classmethod
- def internalize(cls, mapping):
+ @inlineCallbacks
+ def internalize(cls, parent, 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}.
@@ -5243,7 +5333,10 @@
C{None} and ignore extra items.
"""
- return [mapping.get(row[1:]) for row in cls._rowAttributes()]
+ child = yield cls.makeClass(parent, [mapping.get(row[1:]) for row in cls._rowAttributes()])
+ for attr in cls._otherSerializedAttributes():
+ setattr(child, attr, mapping.get(attr[1:]))
+ returnValue(child)
@inlineCallbacks
@@ -5356,18 +5449,10 @@
self._locked = True
- def setComponent(self, component, inserting=False, options=None):
+ def setComponent(self, component, inserting=False):
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
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-14 06:28:16 UTC (rev 12110)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/sql_external.py 2013-12-16 17:12:05 UTC (rev 12111)
@@ -14,21 +14,21 @@
# See the License for the specific language governing permissions and
# limitations under the License.
##
-from txdav.common.icommondatastore import NonExistentExternalShare, \
- ExternalShareFailed
"""
SQL data store.
"""
-from twisted.internet.defer import inlineCallbacks, returnValue, succeed
-
from twext.internet.decorate import memoizedKey
from twext.python.log import Logger
+from twisted.internet.defer import inlineCallbacks, returnValue, succeed
+
from txdav.base.propertystore.sql import PropertyStore
from txdav.common.datastore.sql import CommonHome, CommonHomeChild, \
CommonObjectResource
from txdav.common.datastore.sql_tables import _HOME_STATUS_EXTERNAL
+from txdav.common.icommondatastore import NonExistentExternalShare, \
+ ExternalShareFailed
log = Logger()
@@ -116,6 +116,17 @@
raise AssertionError("CommonHomeExternal: not supported")
+ @inlineCallbacks
+ def removeExternalChild(self, child):
+ """
+ Remove an external child. Check that it is invalid or unused before calling this because if there
+ are valid references to it, removing will break things.
+ """
+ if child._externalID is None:
+ raise AssertionError("CommonHomeExternal: not supported")
+ yield super(CommonHomeExternal, self).removeChildWithName(child.name())
+
+
def syncToken(self):
"""
No children.
@@ -217,11 +228,16 @@
yield ownerView.removeShare(self)
- def remove(self, rid):
+ @inlineCallbacks
+ def remove(self):
"""
- External shares are never removed directly - instead they must be "uninvited".
+ External shares are never removed directly - instead they must be "uninvited". However,
+ the owner's external calendar can be removed.
"""
- raise AssertionError("CommonHomeChildExternal: not supported")
+ if self.owned():
+ yield super(CommonHomeChildExternal, self).remove()
+ else:
+ raise AssertionError("CommonHomeChildExternal: not supported")
@inlineCallbacks
@@ -291,14 +307,6 @@
@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
@@ -348,7 +356,7 @@
results = []
if mapping_list:
for mapping in mapping_list:
- child = yield cls.makeClass(parent, cls.internalize(mapping))
+ child = yield cls.internalize(parent, mapping)
results.append(child)
returnValue(results)
@@ -361,7 +369,7 @@
results = []
if mapping_list:
for mapping in mapping_list:
- child = yield cls.makeClass(parent, cls.internalize(mapping))
+ child = yield cls.internalize(parent, mapping)
results.append(child)
returnValue(results)
@@ -372,7 +380,7 @@
mapping = yield parent._txn.store().conduit.send_objectwith(parent, None, name, uid, resourceID)
if mapping:
- child = yield cls.makeClass(parent, cls.internalize(mapping))
+ child = yield cls.internalize(parent, mapping)
returnValue(child)
else:
returnValue(None)
@@ -381,20 +389,20 @@
@classmethod
@inlineCallbacks
def create(cls, parent, name, component, options=None):
- mapping = yield parent._txn.store().conduit.send_create(parent, None, name, component, options=options)
+ mapping = yield parent._txn.store().conduit.send_create(parent, None, name, str(component), options=options)
if mapping:
- child = yield cls.makeClass(parent, cls.internalize(mapping))
+ child = yield cls.internalize(parent, 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)
+ def setComponent(self, component, **kwargs):
+ self._componentChanged = yield self._txn.store().conduit.send_setcomponent(self.parentCollection(), self, str(component), **kwargs)
self._cachedComponent = None
- returnValue(changed)
+ returnValue(self._componentChanged)
@inlineCallbacks
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20140312/0021a37c/attachment.html>
More information about the calendarserver-changes
mailing list