[CalendarServer-changes] [5604] CalendarServer/trunk
source_changes at macosforge.org
source_changes at macosforge.org
Fri May 14 13:19:37 PDT 2010
Revision: 5604
http://trac.macosforge.org/projects/calendarserver/changeset/5604
Author: cdaboo at apple.com
Date: 2010-05-14 13:19:35 -0700 (Fri, 14 May 2010)
Log Message:
-----------
Shared address books, direct sharing merged from branch.
Modified Paths:
--------------
CalendarServer/trunk/twistedcaldav/customxml.py
CalendarServer/trunk/twistedcaldav/directory/addressbook.py
CalendarServer/trunk/twistedcaldav/method/delete_common.py
CalendarServer/trunk/twistedcaldav/method/get.py
CalendarServer/trunk/twistedcaldav/resource.py
CalendarServer/trunk/twistedcaldav/sharing.py
CalendarServer/trunk/twistedcaldav/static.py
CalendarServer/trunk/twistedcaldav/stdconfig.py
Added Paths:
-----------
CalendarServer/trunk/twistedcaldav/sharedcollection.py
Removed Paths:
-------------
CalendarServer/trunk/twistedcaldav/sharedcalendar.py
Property Changed:
----------------
CalendarServer/trunk/
Property changes on: CalendarServer/trunk
___________________________________________________________________
Modified: svn:mergeinfo
- /CalendarServer/branches/config-separation:4379-4443
/CalendarServer/branches/egg-info-351:4589-4625
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627:3628-3644
/CalendarServer/branches/users/cdaboo/partition-4464:4465-4957
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187:5188-5440
/CalendarServer/branches/users/glyph/contacts-server-merge:4971-5080
/CalendarServer/branches/users/glyph/sendfdport:5388-5424
/CalendarServer/branches/users/glyph/use-system-twisted:5084-5149
/CalendarServer/branches/users/sagen/locations-resources:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2:5052-5061
/CalendarServer/branches/users/sagen/resource-delegates-4038:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066:4068-4075
/CalendarServer/branches/users/sagen/resources-2:5084-5093
+ /CalendarServer/branches/config-separation:4379-4443
/CalendarServer/branches/egg-info-351:4589-4625
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464:4465-4957
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187:5188-5440
/CalendarServer/branches/users/glyph/contacts-server-merge:4971-5080
/CalendarServer/branches/users/glyph/sendfdport:5388-5424
/CalendarServer/branches/users/glyph/use-system-twisted:5084-5149
/CalendarServer/branches/users/sagen/locations-resources:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2:5052-5061
/CalendarServer/branches/users/sagen/resource-delegates-4038:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066:4068-4075
/CalendarServer/branches/users/sagen/resources-2:5084-5093
Modified: CalendarServer/trunk/twistedcaldav/customxml.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/customxml.py 2010-05-14 20:18:02 UTC (rev 5603)
+++ CalendarServer/trunk/twistedcaldav/customxml.py 2010-05-14 20:19:35 UTC (rev 5604)
@@ -688,12 +688,12 @@
namespace = calendarserver_namespace
name = "shared-url"
-class SharedCalendar (davxml.WebDAVElement):
+class SharedAs (davxml.WebDAVElement):
"""
The url for a shared calendar.
"""
namespace = calendarserver_namespace
- name = "shared-calendar"
+ name = "shared-as"
allowed_children = {
(dav_namespace, "href") : (1, 1),
Modified: CalendarServer/trunk/twistedcaldav/directory/addressbook.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/addressbook.py 2010-05-14 20:18:02 UTC (rev 5603)
+++ CalendarServer/trunk/twistedcaldav/directory/addressbook.py 2010-05-14 20:19:35 UTC (rev 5604)
@@ -263,7 +263,7 @@
self.parent = parent
childlist = ()
- if config.Sharing.Enabled and config.Sharing.Calendars.Enabled:
+ if config.Sharing.Enabled and config.Sharing.AddressBooks.Enabled and not config.Sharing.Calendars.Enabled:
childlist += (
("notification", NotificationCollectionResource),
)
Modified: CalendarServer/trunk/twistedcaldav/method/delete_common.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/delete_common.py 2010-05-14 20:18:02 UTC (rev 5603)
+++ CalendarServer/trunk/twistedcaldav/method/delete_common.py 2010-05-14 20:19:35 UTC (rev 5604)
@@ -343,6 +343,13 @@
log.err(msg)
raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, msg))
+ # Check virtual share first
+ isVirtual = yield delresource.isVirtualShare(self.request)
+ if isVirtual:
+ log.debug("Removing shared address book %s" % (delresource,))
+ yield delresource.removeVirtualShare(self.request)
+ returnValue(responsecode.NO_CONTENT)
+
log.debug("Deleting addressbook %s" % (delresource.fp.path,))
errors = ResponseQueue(deluri, "DELETE", responsecode.NO_CONTENT)
Modified: CalendarServer/trunk/twistedcaldav/method/get.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/get.py 2010-05-14 20:18:02 UTC (rev 5603)
+++ CalendarServer/trunk/twistedcaldav/method/get.py 2010-05-14 20:19:35 UTC (rev 5604)
@@ -21,7 +21,9 @@
__all__ = ["http_GET"]
from twisted.internet.defer import inlineCallbacks, returnValue
+from twext.web2 import responsecode
from twext.web2.dav import davxml
+from twext.web2.dav.http import ErrorResponse
from twext.web2.dav.util import parentForURL
from twext.web2.http import HTTPError
from twext.web2.http import Response
@@ -29,48 +31,70 @@
from twext.web2.stream import MemoryStream
from twistedcaldav.caldavxml import ScheduleTag
-from twistedcaldav.customxml import TwistedCalendarAccessProperty
+from twistedcaldav.customxml import TwistedCalendarAccessProperty,\
+ calendarserver_namespace
from twistedcaldav.datafilters.privateevents import PrivateEventFilter
from twistedcaldav.resource import isPseudoCalendarCollectionResource
@inlineCallbacks
def http_GET(self, request):
- # Look for calendar access restriction on existing resource.
if self.exists():
- parentURL = parentForURL(request.uri)
- parent = (yield request.locateResource(parentURL))
- if isPseudoCalendarCollectionResource(parent):
-
- # Check authorization first
- yield self.authorize(request, (davxml.Read(),))
-
- caldata = (yield self.iCalendarForUser(request))
-
- try:
- access = self.readDeadProperty(TwistedCalendarAccessProperty)
- except HTTPError:
- access = None
+ # Special sharing request on a calendar or address book
+ if self.isCalendarCollection() or self.isAddressBookCollection():
+
+ # Check for action=share
+ if request.args:
+ action = request.args.get("action", ("",))
+ if len(action) != 1:
+ raise HTTPError(ErrorResponse(responsecode.BAD_REQUEST, (calendarserver_namespace, "valid-action")))
+ action = action[0]
+
+ dispatch = {
+ "share" : self.directShare,
+ }.get(action, None)
- if access:
+ if dispatch is None:
+ raise HTTPError(ErrorResponse(responsecode.BAD_REQUEST, (calendarserver_namespace, "supported-action")))
- # Non DAV:owner's have limited access to the data
- isowner = (yield self.isOwner(request, adminprincipals=True, readprincipals=True))
-
- # Now "filter" the resource calendar data
- caldata = PrivateEventFilter(access, isowner).filter(caldata)
+ response = (yield dispatch(request))
+ returnValue(response)
+
+ else:
+ # Look for calendar access restriction on existing resource.
+ parentURL = parentForURL(request.uri)
+ parent = (yield request.locateResource(parentURL))
+ if isPseudoCalendarCollectionResource(parent):
+
+ # Check authorization first
+ yield self.authorize(request, (davxml.Read(),))
- response = Response()
- response.stream = MemoryStream(str(caldata))
- response.headers.setHeader("content-type", MimeType.fromString("text/calendar; charset=utf-8"))
+ caldata = (yield self.iCalendarForUser(request))
- # Add Schedule-Tag header if property is present
- if self.hasDeadProperty(ScheduleTag):
- scheduletag = self.readDeadProperty(ScheduleTag)
- if scheduletag:
- response.headers.setHeader("Schedule-Tag", str(scheduletag))
+ try:
+ access = self.readDeadProperty(TwistedCalendarAccessProperty)
+ except HTTPError:
+ access = None
+
+ if access:
+
+ # Non DAV:owner's have limited access to the data
+ isowner = (yield self.isOwner(request, adminprincipals=True, readprincipals=True))
+
+ # Now "filter" the resource calendar data
+ caldata = PrivateEventFilter(access, isowner).filter(caldata)
- returnValue(response)
+ response = Response()
+ response.stream = MemoryStream(str(caldata))
+ response.headers.setHeader("content-type", MimeType.fromString("text/calendar; charset=utf-8"))
+
+ # Add Schedule-Tag header if property is present
+ if self.hasDeadProperty(ScheduleTag):
+ scheduletag = self.readDeadProperty(ScheduleTag)
+ if scheduletag:
+ response.headers.setHeader("Schedule-Tag", str(scheduletag))
+
+ returnValue(response)
# Do normal GET behavior
response = (yield super(CalDAVFile, self).http_GET(request))
Modified: CalendarServer/trunk/twistedcaldav/resource.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/resource.py 2010-05-14 20:18:02 UTC (rev 5603)
+++ CalendarServer/trunk/twistedcaldav/resource.py 2010-05-14 20:19:35 UTC (rev 5604)
@@ -198,7 +198,10 @@
)
elif config.Sharing.AddressBooks.Enabled and self.isAddressBookCollection():
- baseProperties += (customxml.Invite.qname(),)
+ baseProperties += (
+ customxml.Invite.qname(),
+ customxml.AllowedSharingModes.qname(),
+ )
return super(CalDAVResource, self).liveProperties() + baseProperties
@@ -214,6 +217,7 @@
return qname in (
caldavxml.CalendarDescription.qname(),
caldavxml.CalendarTimeZone.qname(),
+ carddavxml.AddressBookDescription.qname(),
)
def isGlobalProperty(self, qname):
@@ -398,13 +402,18 @@
))
elif qname == customxml.Invite.qname():
- if config.Sharing.Enabled and config.Sharing.Calendars.Enabled and self.isCalendarCollection():
+ if config.Sharing.Enabled and (
+ config.Sharing.Calendars.Enabled and self.isCalendarCollection() or
+ config.Sharing.AddressBooks.Enabled and self.isAddressBookCollection()
+ ):
result = (yield self.inviteProperty(request))
returnValue(result)
elif qname == customxml.AllowedSharingModes.qname():
if config.Sharing.Enabled and config.Sharing.Calendars.Enabled and self.isCalendarCollection():
returnValue(customxml.AllowedSharingModes(customxml.CanBeShared()))
+ elif config.Sharing.Enabled and config.Sharing.AddressBooks.Enabled and self.isAddressBookCollection():
+ returnValue(customxml.AllowedSharingModes(customxml.CanBeShared()))
result = (yield super(CalDAVResource, self).readProperty(property, request))
returnValue(result)
@@ -481,13 +490,19 @@
yield self._preProcessWriteProperty(property, request)
if property.qname() == davxml.ResourceType.qname():
- if self.isCalendarCollection():
+ if self.isCalendarCollection() or self.isAddressBookCollection():
sawShare = [child for child in property.children if child.qname() == (calendarserver_namespace, "shared-owner")]
- if not (config.Sharing.Enabled and config.Sharing.Calendars.Enabled):
- raise HTTPError(StatusResponse(
- responsecode.FORBIDDEN,
- "Cannot create shared calendars on this server.",
- ))
+ if sawShare:
+ if self.isCalendarCollection() and not (config.Sharing.Enabled and config.Sharing.Calendars.Enabled):
+ raise HTTPError(StatusResponse(
+ responsecode.FORBIDDEN,
+ "Cannot create shared calendars on this server.",
+ ))
+ elif self.isAddressBookCollection() and not (config.Sharing.Enabled and config.Sharing.AddressBooks.Enabled):
+ raise HTTPError(StatusResponse(
+ responsecode.FORBIDDEN,
+ "Cannot create shared address books on this server.",
+ ))
# Check if adding or removing share
shared = (yield self.isShared(request))
@@ -500,7 +515,8 @@
"Protected property %s may not be set." % (property.sname(),)
))
for child in property.children:
- if child.qname() == caldavxml.Calendar.qname():
+ if self.isCalendarCollection and child.qname() == caldavxml.Calendar.qname() or \
+ self.isAddressBookCollection and child.qname() == carddavxml.AddressBook.qname():
break
else:
raise HTTPError(StatusResponse(
@@ -532,10 +548,12 @@
@inlineCallbacks
def accessControlList(self, request, *args, **kwargs):
+ acls = None
isvirt = (yield self.isVirtualShare(request))
if isvirt:
acls = self.shareeAccessControlList()
- else:
+
+ if acls is None:
acls = (yield super(CalDAVResource, self).accessControlList(request, *args, **kwargs))
# Look for private events access classification
Deleted: CalendarServer/trunk/twistedcaldav/sharedcalendar.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/sharedcalendar.py 2010-05-14 20:18:02 UTC (rev 5603)
+++ CalendarServer/trunk/twistedcaldav/sharedcalendar.py 2010-05-14 20:19:35 UTC (rev 5604)
@@ -1,45 +0,0 @@
-##
-# Copyright (c) 2010 Apple Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-##
-
-from twisted.internet.defer import inlineCallbacks, returnValue
-
-from twistedcaldav.linkresource import LinkResource
-
-__all__ = [
- "SharedCalendarResource",
-]
-
-"""
-Sharing behavior
-"""
-
-class SharedCalendarResource(LinkResource):
- """
- This is similar to a WrapperResource except that we locate our shared calendar resource dynamically.
- """
-
- def __init__(self, parent, share):
- self.share = share
- super(SharedCalendarResource, self).__init__(parent, None)
-
- @inlineCallbacks
- def linkedResource(self, request):
-
- if not hasattr(self, "_linkedResource"):
- self._linkedResource = (yield request.locateResource(self.share.hosturl))
- ownerPrincipal = (yield self.parent.ownerPrincipal(request))
- self._linkedResource.setVirtualShare(ownerPrincipal, self.share)
- returnValue(self._linkedResource)
Copied: CalendarServer/trunk/twistedcaldav/sharedcollection.py (from rev 5601, CalendarServer/branches/users/cdaboo/more-sharing-5591/twistedcaldav/sharedcollection.py)
===================================================================
--- CalendarServer/trunk/twistedcaldav/sharedcollection.py (rev 0)
+++ CalendarServer/trunk/twistedcaldav/sharedcollection.py 2010-05-14 20:19:35 UTC (rev 5604)
@@ -0,0 +1,45 @@
+##
+# Copyright (c) 2010 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from twisted.internet.defer import inlineCallbacks, returnValue
+
+from twistedcaldav.linkresource import LinkResource
+
+__all__ = [
+ "SharedCollectionResource",
+]
+
+"""
+Sharing behavior
+"""
+
+class SharedCollectionResource(LinkResource):
+ """
+ This is similar to a WrapperResource except that we locate our shared collection resource dynamically.
+ """
+
+ def __init__(self, parent, share):
+ self.share = share
+ super(SharedCollectionResource, self).__init__(parent, None)
+
+ @inlineCallbacks
+ def linkedResource(self, request):
+
+ if not hasattr(self, "_linkedResource"):
+ self._linkedResource = (yield request.locateResource(self.share.hosturl))
+ ownerPrincipal = (yield self.parent.ownerPrincipal(request))
+ self._linkedResource.setVirtualShare(ownerPrincipal, self.share)
+ returnValue(self._linkedResource)
Modified: CalendarServer/trunk/twistedcaldav/sharing.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/sharing.py 2010-05-14 20:18:02 UTC (rev 5603)
+++ CalendarServer/trunk/twistedcaldav/sharing.py 2010-05-14 20:19:35 UTC (rev 5604)
@@ -29,7 +29,7 @@
returnValue
from twistedcaldav import customxml, caldavxml
from twistedcaldav.config import config
-from twistedcaldav.customxml import SharedCalendar
+from twistedcaldav.customxml import calendarserver_namespace
from twistedcaldav.sql import AbstractSQLDatabase, db_prefix
from uuid import uuid4
from vobject.icalendar import dateTimeToString, utc
@@ -41,6 +41,10 @@
Sharing behavior
"""
+# Types of sharing mode
+SHARETYPE_INVITE = "I" # Invite based sharing
+SHARETYPE_DIRECT = "D" # Direct linking based sharing
+
class SharedCollectionMixin(object):
def invitesDB(self):
@@ -132,6 +136,73 @@
record.summary = summary
self.invitesDB().addOrUpdateRecord(record)
+ @inlineCallbacks
+ def directShare(self, request):
+ """
+ Directly bind an accessible calendar/address book collection into the current
+ principal's calendar/addressbook home.
+
+ @param request: the request triggering this action
+ @type request: L{IRequest}
+ """
+
+ # Need to have at least DAV:read to do this
+ yield self.authorize(request, (davxml.Read(),))
+
+ # Find current principal
+ authz_principal = self.currentPrincipal(request).children[0]
+ if not isinstance(authz_principal, davxml.HRef):
+ raise HTTPError(ErrorResponse(
+ responsecode.FORBIDDEN,
+ (calendarserver_namespace, "valid-principal"),
+ "Current user principal not a DAV:href",
+ ))
+ principalURL = str(authz_principal)
+ if not principalURL:
+ raise HTTPError(ErrorResponse(
+ responsecode.FORBIDDEN,
+ (calendarserver_namespace, "valid-principal"),
+ "Current user principal not specified",
+ ))
+ principal = (yield request.locateResource(principalURL))
+
+ # Check enabled for service
+ from twistedcaldav.directory.principal import DirectoryCalendarPrincipalResource
+ if not isinstance(principal, DirectoryCalendarPrincipalResource):
+ raise HTTPError(ErrorResponse(
+ responsecode.FORBIDDEN,
+ (calendarserver_namespace, "invalid-principal"),
+ "Current user principal is not a calendar/addressbook enabled principal",
+ ))
+
+ # Get the home collection
+ if self.isCalendarCollection():
+ home = principal.calendarHome()
+ elif self.isAddressBookCollection():
+ home = principal.addressBookHome()
+ else:
+ raise HTTPError(ErrorResponse(
+ responsecode.FORBIDDEN,
+ (calendarserver_namespace, "invalid-principal"),
+ "No calendar/addressbook home for principal",
+ ))
+
+ # TODO: Make sure principal is not sharing back to themselves
+ compareURL = (yield self.canonicalURL(request))
+ homeURL = home.url()
+ if compareURL.startswith(homeURL):
+ raise HTTPError(ErrorResponse(
+ responsecode.FORBIDDEN,
+ (calendarserver_namespace, "invalid-share"),
+ "Can't share your own calendar or addressbook",
+ ))
+
+ # Accept it
+ response = (yield home.acceptDirectShare(request, request.path, self.resourceID(), self.displayName()))
+
+ # Return the URL of the shared calendar
+ returnValue(response)
+
def isShared(self, request):
""" Return True if this is an owner shared calendar collection """
return succeed(self.isSpecialCollection(customxml.SharedOwner))
@@ -148,8 +219,11 @@
def removeVirtualShare(self, request):
""" Return True if this is a shared calendar collection """
- # Remove from sharee's calendar home
- shareeHome = self._shareePrincipal.calendarHome()
+ # Remove from sharee's calendar/address book home
+ if self.isCalendarCollection():
+ shareeHome = self._shareePrincipal.calendarHome()
+ elif self.isAddressBookCollection():
+ shareeHome = self._shareePrincipal.addressBookHome()
return shareeHome.removeShare(request, self._share)
@inlineCallbacks
@@ -182,8 +256,14 @@
assert self._isVirtualShare, "Only call this for a virtual share"
+ # Direct shares use underlying privileges of shared collection
+ if self._share.sharetype == SHARETYPE_DIRECT:
+ return None
+
+ # Invite shares use access mode from the invite
+
# Get the invite for this sharee
- invite = self.invitesDB().recordForInviteUID(self._share.inviteuid)
+ invite = self.invitesDB().recordForInviteUID(self._share.shareuid)
if invite is None:
return davxml.ACL()
@@ -208,13 +288,17 @@
davxml.Protected(),
TwistedACLInheritable(),
),
- # Inheritable CALDAV:read-free-busy access for authenticated users.
- davxml.ACE(
- davxml.Principal(davxml.Authenticated()),
- davxml.Grant(davxml.Privilege(caldavxml.ReadFreeBusy())),
- TwistedACLInheritable(),
- ),
)
+
+ if self.isCalendarCollection():
+ aces += (
+ # Inheritable CALDAV:read-free-busy access for authenticated users.
+ davxml.ACE(
+ davxml.Principal(davxml.Authenticated()),
+ davxml.Grant(davxml.Privilege(caldavxml.ReadFreeBusy())),
+ TwistedACLInheritable(),
+ ),
+ )
# Give read access to config.ReadPrincipals
aces += config.ReadACEs
@@ -305,13 +389,6 @@
record.state = "INVALID"
self.invitesDB().addOrUpdateRecord(record)
- def getInviteUsers(self, request):
- return succeed(True)
-
- def sendNotificationOnChange(self, icalendarComponent, request, state="added"):
- """ Possibly send a push and or email notification on a change to a resource in a shared collection """
- return succeed(True)
-
def inviteUserToShare(self, userid, cn, ace, summary, request):
""" Send out in invite first, and then add this user to the share list
@param userid:
@@ -388,10 +465,13 @@
# Cancel invites
record = self.invitesDB().recordForUserID(userid)
- # Remove any shared calendar
+ # Remove any shared calendar or address book
sharee = self.principalForCalendarUserAddress(record.userid)
if sharee:
- shareeHome = sharee.calendarHome()
+ if self.isCalendarCollection():
+ shareeHome = sharee.calendarHome()
+ elif self.isAddressBookCollection():
+ shareeHome = sharee.addressBookHome()
yield shareeHome.removeShareByUID(request, record.inviteuid)
# If current user state is accepted then we send an invite with the new state, otherwise
@@ -810,82 +890,102 @@
class SharedHomeMixin(object):
"""
A mix-in for calendar/addressbook homes that defines the operations for manipulating a sharee's
- set of shared calendfars.
+ set of shared calendars.
"""
def sharesDB(self):
if not hasattr(self, "_sharesDB"):
- self._sharesDB = SharedCalendarsDatabase(self)
+ self._sharesDB = SharedCollectionsDatabase(self)
return self._sharesDB
def provisionShares(self):
if not hasattr(self, "_provisionedShares"):
- from twistedcaldav.sharedcalendar import SharedCalendarResource
+ from twistedcaldav.sharedcollection import SharedCollectionResource
for share in self.sharesDB().allRecords():
- child = SharedCalendarResource(self, share)
+ child = SharedCollectionResource(self, share)
self.putChild(share.localname, child)
self._provisionedShares = True
@inlineCallbacks
- def acceptShare(self, request, hostUrl, inviteUID, displayname=None):
+ def acceptInviteShare(self, request, hostUrl, inviteUID, displayname=None):
- # Do this first to make sure we have a valid share
+ # Send the invite reply then add the link
yield self._changeShare(request, "ACCEPTED", hostUrl, inviteUID, displayname)
+ response = (yield self._acceptShare(request, SHARETYPE_INVITE, hostUrl, inviteUID, displayname))
+ returnValue(response)
+
+ def acceptDirectShare(self, request, hostUrl, resourceUID, displayname=None):
+
+ # Just add the link
+ return self._acceptShare(request, SHARETYPE_DIRECT, hostUrl, resourceUID, displayname)
+
+ @inlineCallbacks
+ def _acceptShare(self, request, sharetype, hostUrl, shareUID, displayname=None):
+
# Add or update in DB
- oldShare = self.sharesDB().recordForInviteUID(inviteUID)
+ oldShare = self.sharesDB().recordForShareUID(shareUID)
if not oldShare:
- oldShare = share = SharedCalendarRecord(inviteUID, hostUrl, str(uuid4()), displayname)
+ oldShare = share = SharedCollectionRecord(shareUID, sharetype, hostUrl, str(uuid4()), displayname)
self.sharesDB().addOrUpdateRecord(share)
# Set per-user displayname to whatever was given
if displayname:
- sharedCalendar = (yield request.locateResource(hostUrl))
+ sharedCollection = (yield request.locateResource(hostUrl))
ownerPrincipal = (yield self.ownerPrincipal(request))
- sharedCalendar.setVirtualShare(ownerPrincipal, oldShare)
- yield sharedCalendar.writeProperty(davxml.DisplayName.fromString(displayname), request)
+ sharedCollection.setVirtualShare(ownerPrincipal, oldShare)
+ yield sharedCollection.writeProperty(davxml.DisplayName.fromString(displayname), request)
- # Return the URL of the shared calendar
+ # Return the URL of the shared collection
returnValue(XMLResponse(
code = responsecode.OK,
- element = SharedCalendar(
+ element = customxml.SharedAs(
davxml.HRef.fromString(joinURL(self.url(), oldShare.localname))
)
))
- def wouldAcceptShare(self, hostUrl, request):
- return succeed(True)
-
def removeShare(self, request, share):
- """ Remove a shared calendar named in resourceName and send a decline """
- return self.declineShare(request, share.hosturl, share.inviteuid)
+ """ Remove a shared collection named in resourceName """
+ # Send a decline when an invite share is removed only
+ if share.sharetype == SHARETYPE_INVITE:
+ return self.declineShare(request, share.hosturl, share.shareuid)
+ else:
+ return self.removeDirectShare(request, share)
+
@inlineCallbacks
- def removeShareByUID(self, request, inviteuid):
- """ Remove a shared calendar but do not send a decline back """
+ def removeShareByUID(self, request, shareUID):
+ """ Remove a shared collection but do not send a decline back """
- record = self.sharesDB().recordForInviteUID(inviteuid)
- if record:
- shareURL = joinURL(self.url(), record.localname)
-
+ share = self.sharesDB().recordForShareUID(shareUID)
+ if share:
+ yield self.removeDirectShare(request, share)
+
+ returnValue(True)
+
+ @inlineCallbacks
+ def removeDirectShare(self, request, share):
+ """ Remove a shared collection but do not send a decline back """
+
+ shareURL = joinURL(self.url(), share.localname)
+
+ if self.isCalendarCollection():
# For backwards compatibility we need to sync this up with the calendar-free-busy-set on the inbox
principal = (yield self.resourceOwnerPrincipal(request))
inboxURL = principal.scheduleInboxURL()
if inboxURL:
inbox = (yield request.locateResource(inboxURL))
inbox.processFreeBusyCalendar(shareURL, False)
-
- self.sharesDB().removeRecordForInviteUID(inviteuid)
- returnValue(True)
+ self.sharesDB().removeRecordForShareUID(share.shareuid)
@inlineCallbacks
def declineShare(self, request, hostUrl, inviteUID):
# Remove it if its in the DB
- self.sharesDB().removeRecordForInviteUID(inviteUID)
+ yield self.removeShareByUID(request, inviteUID)
yield self._changeShare(request, "DECLINED", hostUrl, inviteUID)
@@ -893,31 +993,31 @@
@inlineCallbacks
def _changeShare(self, request, state, hostUrl, replytoUID, displayname=None):
- """ Accept an invite to a shared calendar """
+ """ Accept an invite to a shared collection """
# Change state in sharer invite
owner = (yield self.ownerPrincipal(request))
owner = owner.principalURL()
- sharedCalendar = (yield request.locateResource(hostUrl))
- if sharedCalendar is None:
- # Original shared calendar is gone - nothing we can do except ignore it
+ sharedCollection = (yield request.locateResource(hostUrl))
+ if sharedCollection is None:
+ # Original shared collection is gone - nothing we can do except ignore it
raise HTTPError(ErrorResponse(
responsecode.FORBIDDEN,
(customxml.calendarserver_namespace, "valid-request"),
- "invalid shared calendar",
+ "invalid shared collection",
))
# Change the record
- yield sharedCalendar.changeUserInviteState(request, replytoUID, owner, state, displayname)
+ yield sharedCollection.changeUserInviteState(request, replytoUID, owner, state, displayname)
- yield self.sendReply(request, owner, sharedCalendar, state, hostUrl, replytoUID, displayname)
+ yield self.sendReply(request, owner, sharedCollection, state, hostUrl, replytoUID, displayname)
@inlineCallbacks
- def sendReply(self, request, sharee, sharedCalendar, state, hostUrl, replytoUID, displayname=None):
+ def sendReply(self, request, sharee, sharedCollection, state, hostUrl, replytoUID, displayname=None):
# Locate notifications collection for sharer
- sharer = (yield sharedCalendar.ownerPrincipal(request))
+ sharer = (yield sharedCollection.ownerPrincipal(request))
notifications = (yield request.locateResource(sharer.notificationURL()))
# Generate invite XML
@@ -976,7 +1076,7 @@
"missing required XML elements",
))
if accepted:
- return self.acceptShare(request, hostUrl, replytoUID, displayname=summary)
+ return self.acceptInviteShare(request, hostUrl, replytoUID, displayname=summary)
else:
return self.declineShare(request, hostUrl, replytoUID)
@@ -999,28 +1099,29 @@
return allDataFromStream(request.stream).addCallback(_getData)
-class SharedCalendarRecord(object):
+class SharedCollectionRecord(object):
- def __init__(self, inviteuid, hosturl, localname, summary):
- self.inviteuid = inviteuid
+ def __init__(self, shareuid, sharetype, hosturl, localname, summary):
+ self.shareuid = shareuid
+ self.sharetype = sharetype
self.hosturl = hosturl
self.localname = localname
self.summary = summary
-class SharedCalendarsDatabase(AbstractSQLDatabase, LoggingMixIn):
+class SharedCollectionsDatabase(AbstractSQLDatabase, LoggingMixIn):
db_basename = db_prefix + "shares"
schema_version = "1"
db_type = "shares"
- def __init__(self, resource):
+ def __init__(self, resource):
"""
@param resource: the L{twistedcaldav.static.CalDAVFile} resource for
the shared collection. C{resource} must be a calendar/addressbook home collection.)
"""
self.resource = resource
- db_filename = os.path.join(self.resource.fp.path, SharedCalendarsDatabase.db_basename)
- super(SharedCalendarsDatabase, self).__init__(db_filename, True, autocommit=True)
+ db_filename = os.path.join(self.resource.fp.path, SharedCollectionsDatabase.db_basename)
+ super(SharedCollectionsDatabase, self).__init__(db_filename, True, autocommit=True)
def create(self):
"""
@@ -1038,25 +1139,25 @@
row = self._db_execute("select * from SHARES where LOCALNAME = :1", localname)
return self._makeRecord(row[0]) if row else None
- def recordForInviteUID(self, inviteUID):
+ def recordForShareUID(self, shareUID):
- row = self._db_execute("select * from SHARES where INVITEUID = :1", inviteUID)
+ row = self._db_execute("select * from SHARES where SHAREUID = :1", shareUID)
return self._makeRecord(row[0]) if row else None
def addOrUpdateRecord(self, record):
- self._db_execute("""insert or replace into SHARES (INVITEUID, HOSTURL, LOCALNAME, SUMMARY)
- values (:1, :2, :3, :4)
- """, record.inviteuid, record.hosturl, record.localname, record.summary,
+ self._db_execute("""insert or replace into SHARES (SHAREUID, SHARETYPE, HOSTURL, LOCALNAME, SUMMARY)
+ values (:1, :2, :3, :4, :5)
+ """, record.shareuid, record.sharetype, record.hosturl, record.localname, record.summary,
)
def removeRecordForLocalName(self, localname):
self._db_execute("delete from SHARES where LOCALNAME = :1", localname)
- def removeRecordForInviteUID(self, inviteUID):
+ def removeRecordForShareUID(self, shareUID):
- self._db_execute("delete from SHARES where INVITEUID = :1", inviteUID)
+ self._db_execute("delete from SHARES where SHAREUID = :1", shareUID)
def remove(self):
@@ -1067,13 +1168,13 @@
"""
@return: the schema version assigned to this index.
"""
- return SharedCalendarsDatabase.schema_version
+ return SharedCollectionsDatabase.schema_version
def _db_type(self):
"""
@return: the collection type assigned to this index.
"""
- return SharedCalendarsDatabase.db_type
+ return SharedCollectionsDatabase.db_type
def _db_init_data_tables(self, q):
"""
@@ -1082,15 +1183,17 @@
"""
#
# SHARES table is the primary table
- # INVITEUID: UID for this invite
+ # SHAREUID: UID for this share
+ # SHARETYPE: type of share: "I" for invite, "D" for direct
# HOSTURL: URL for data source
# LOCALNAME: local path name
- # SUMMARY: Invite summary
+ # SUMMARY: Share summary
#
q.execute(
"""
create table SHARES (
- INVITEUID text unique,
+ SHAREUID text unique,
+ SHARETYPE text(1),
HOSTURL text,
LOCALNAME text,
SUMMARY text
@@ -1100,7 +1203,7 @@
q.execute(
"""
- create index INVITEUID on SHARES (INVITEUID)
+ create index SHAREUID on SHARES (SHAREUID)
"""
)
q.execute(
@@ -1124,4 +1227,4 @@
def _makeRecord(self, row):
- return SharedCalendarRecord(*[str(item) if type(item) == types.UnicodeType else item for item in row])
+ return SharedCollectionRecord(*[str(item) if type(item) == types.UnicodeType else item for item in row])
Modified: CalendarServer/trunk/twistedcaldav/static.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/static.py 2010-05-14 20:18:02 UTC (rev 5603)
+++ CalendarServer/trunk/twistedcaldav/static.py 2010-05-14 20:19:35 UTC (rev 5604)
@@ -974,7 +974,7 @@
def provision(self):
result = super(CalendarHomeFile, self).provision()
- if config.Sharing.Enabled:
+ if config.Sharing.Enabled and config.Sharing.Calendars.Enabled:
self.provisionShares()
return result
@@ -989,7 +989,7 @@
else:
FreeBusyURLFileClass = None
- if config.Sharing.Enabled:
+ if config.Sharing.Enabled and config.Sharing.Calendars.Enabled:
NotificationCollectionFileClass = NotificationCollectionFile
else:
NotificationCollectionFileClass = None
@@ -1514,7 +1514,7 @@
def createSimilarFile(self, path):
raise HTTPError(responsecode.NOT_FOUND)
-class AddressBookHomeFile (AutoProvisioningFileMixIn, DirectoryAddressBookHomeResource, CalDAVFile):
+class AddressBookHomeFile (AutoProvisioningFileMixIn, SharedHomeMixin, DirectoryAddressBookHomeResource, CalDAVFile):
"""
Address book home collection resource.
"""
@@ -1540,6 +1540,8 @@
def provision(self):
result = super(AddressBookHomeFile, self).provision()
+ if config.Sharing.Enabled and config.Sharing.AddressBooks.Enabled:
+ self.provisionShares()
self.provisionLinks()
return result
@@ -1555,7 +1557,7 @@
def provisionChild(self, name):
- if config.Sharing.Enabled:
+ if config.Sharing.Enabled and config.Sharing.AddressBooks.Enabled and not config.Sharing.Calendars.Enabled:
NotificationCollectionFileClass = NotificationCollectionFile
else:
NotificationCollectionFileClass = None
Modified: CalendarServer/trunk/twistedcaldav/stdconfig.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/stdconfig.py 2010-05-14 20:18:02 UTC (rev 5603)
+++ CalendarServer/trunk/twistedcaldav/stdconfig.py 2010-05-14 20:19:35 UTC (rev 5604)
@@ -337,7 +337,7 @@
"AllowScheduling" : False, # Scheduling in shared calendars
},
"AddressBooks" : {
- "Enabled" : False, # Address Books on/off switch
+ "Enabled" : True, # Address Books on/off switch
}
},
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20100514/cbf10faf/attachment-0001.html>
More information about the calendarserver-changes
mailing list