[CalendarServer-changes] [1862]
CalendarServer/branches/users/cdaboo/server2server-1842
source_changes at macosforge.org
source_changes at macosforge.org
Mon Sep 10 20:20:43 PDT 2007
Revision: 1862
http://trac.macosforge.org/projects/calendarserver/changeset/1862
Author: cdaboo at apple.com
Date: 2007-09-10 20:20:43 -0700 (Mon, 10 Sep 2007)
Log Message:
-----------
Free-busy URL support. This adds a freebusy resource into each user's calendar home. A GET or POST on that resource
with the appropriate parameters will return a VFREEBUSY component with free-busy information for that user. Currently
following the prototype spec being worked on in Calconnect.
Modified Paths:
--------------
CalendarServer/branches/users/cdaboo/server2server-1842/conf/caldavd-test.plist
CalendarServer/branches/users/cdaboo/server2server-1842/conf/caldavd.plist
CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/config.py
CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/customxml.py
CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/directory/calendar.py
CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/schedule_common.py
CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/static.py
Added Paths:
-----------
CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/freebusyurl.py
Modified: CalendarServer/branches/users/cdaboo/server2server-1842/conf/caldavd-test.plist
===================================================================
--- CalendarServer/branches/users/cdaboo/server2server-1842/conf/caldavd-test.plist 2007-09-10 23:40:49 UTC (rev 1861)
+++ CalendarServer/branches/users/cdaboo/server2server-1842/conf/caldavd-test.plist 2007-09-11 03:20:43 UTC (rev 1862)
@@ -343,6 +343,14 @@
<string>conf/servertoserver-test.xml</string>
</dict>
+ <!-- Free-busy URL protocol -->
+ <key>FreeBusyURL</key>
+ <dict>
+ <key>Enabled</key>
+ <true/>
+ <key>Time Period</key>
+ <integer>14</integer>
+ </dict>
<!--
Twisted
Modified: CalendarServer/branches/users/cdaboo/server2server-1842/conf/caldavd.plist
===================================================================
--- CalendarServer/branches/users/cdaboo/server2server-1842/conf/caldavd.plist 2007-09-10 23:40:49 UTC (rev 1861)
+++ CalendarServer/branches/users/cdaboo/server2server-1842/conf/caldavd.plist 2007-09-11 03:20:43 UTC (rev 1862)
@@ -277,5 +277,14 @@
<string>/etc/caldavd/servertoserver.xml</string>
</dict>
+ <!-- Free-busy URL protocol -->
+ <key>FreeBusyURL</key>
+ <dict>
+ <key>Enabled</key>
+ <true/>
+ <key>Time Period</key>
+ <integer>14</integer>
+ </dict>
+
</dict>
</plist>
Modified: CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/config.py
===================================================================
--- CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/config.py 2007-09-10 23:40:49 UTC (rev 1861)
+++ CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/config.py 2007-09-11 03:20:43 UTC (rev 1862)
@@ -154,6 +154,11 @@
"HTTP Domain" : "", # Domain for http calendar user addresses on this server
},
+ "FreeBusyURL": {
+ "Enabled" : False, # Per-user free-busy-url protocol
+ "Time Period" : 14, # Number of days into the future to generate f-b data if no explicit time-range is specified
+ },
+
#
# Implementation details
#
Modified: CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/customxml.py
===================================================================
--- CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/customxml.py 2007-09-10 23:40:49 UTC (rev 1861)
+++ CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/customxml.py 2007-09-11 03:20:43 UTC (rev 1862)
@@ -256,12 +256,20 @@
class ServerToServerInbox (davxml.WebDAVEmptyElement):
"""
- Denotes the resourcetype of a server-to_server Inbox.
+ Denotes the resourcetype of a server-to-server Inbox.
(CalDAV-s2s-xx, section x.x.x)
"""
namespace = calendarserver_namespace
name = "server-to-server-inbox"
+class FreeBusyURL (davxml.WebDAVEmptyElement):
+ """
+ Denotes the resourcetype of a free-busy URL resource.
+ (CalDAV-s2s-xx, section x.x.x)
+ """
+ namespace = calendarserver_namespace
+ name = "free-busy-url"
+
##
# Extensions to davxml.ResourceType
##
@@ -272,3 +280,4 @@
davxml.ResourceType.calendarproxyread = davxml.ResourceType(davxml.Principal(), davxml.Collection(), CalendarProxyRead())
davxml.ResourceType.calendarproxywrite = davxml.ResourceType(davxml.Principal(), davxml.Collection(), CalendarProxyWrite())
davxml.ResourceType.servertoserverinbox = davxml.ResourceType(ServerToServerInbox())
+davxml.ResourceType.freebusyurl = davxml.ResourceType(FreeBusyURL())
Modified: CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/directory/calendar.py
===================================================================
--- CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/directory/calendar.py 2007-09-10 23:40:49 UTC (rev 1861)
+++ CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/directory/calendar.py 2007-09-11 03:20:43 UTC (rev 1862)
@@ -26,16 +26,15 @@
"DirectoryCalendarHomeResource",
]
-from twisted.web2 import responsecode
from twisted.web2.dav import davxml
from twisted.web2.dav.util import joinURL
from twisted.web2.dav.resource import TwistedACLInheritable, TwistedQuotaRootProperty
-from twisted.web2.http import HTTPError
from twistedcaldav import caldavxml
from twistedcaldav.config import config
from twistedcaldav.dropbox import DropBoxHomeResource
from twistedcaldav.extensions import ReadOnlyResourceMixIn, DAVResource
+from twistedcaldav.freebusyurl import FreeBusyURLResource
from twistedcaldav.notifications import NotificationsCollectionResource
from twistedcaldav.resource import CalDAVResource
from twistedcaldav.schedule import ScheduleInboxResource, ScheduleOutboxResource
@@ -201,6 +200,10 @@
childlist += (
("notifications", NotificationsCollectionResource),
)
+ if config.FreeBusyURL["Enabled"]:
+ childlist += (
+ ("freebusy", FreeBusyURLResource),
+ )
for name, cls in childlist:
child = self.provisionChild(name)
assert isinstance(child, cls), "Child %r is not a %s: %r" % (name, cls.__name__, child)
Added: CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/freebusyurl.py
===================================================================
--- CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/freebusyurl.py (rev 0)
+++ CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/freebusyurl.py 2007-09-11 03:20:43 UTC (rev 1862)
@@ -0,0 +1,237 @@
+##
+# Copyright (c) 2005-2007 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.
+#
+# DRI: Cyrus Daboo, cdaboo at apple.com
+##
+
+"""
+Free-busy-URL resources.
+"""
+
+__all__ = [
+ "FreeBusyURLResource",
+]
+
+from twisted.internet.defer import deferredGenerator
+from twisted.internet.defer import waitForDeferred
+from twisted.python import log
+from twisted.web2 import responsecode
+from twisted.web2.dav import davxml
+from twisted.web2.dav.http import ErrorResponse
+from twisted.web2.http import HTTPError
+from twisted.web2.http import Response
+from twisted.web2.http import StatusResponse
+from twisted.web2.http_headers import MimeType
+from twisted.web2.stream import MemoryStream
+
+from twistedcaldav import caldavxml
+from twistedcaldav.caldavxml import TimeRange
+from twistedcaldav.config import config
+from twistedcaldav.customxml import calendarserver_namespace
+from twistedcaldav.ical import Property
+from twistedcaldav.ical import parse_datetime
+from twistedcaldav.ical import parse_duration
+from twistedcaldav.resource import CalDAVResource
+from twistedcaldav.schedule_common import Scheduler
+
+from vobject.icalendar import utc
+
+import datetime
+
+class FreeBusyURLResource (CalDAVResource):
+ """
+ Free-busy URL resource.
+
+ Extends L{DAVResource} to provide free-busy URL functionality.
+ """
+
+ def __init__(self, parent):
+ """
+ @param parent: the parent resource of this one.
+ """
+ assert parent is not None
+
+ CalDAVResource.__init__(self, principalCollections=parent.principalCollections())
+
+ self.parent = parent
+
+ def defaultAccessControlList(self):
+ return davxml.ACL(
+ # DAV:Read, CalDAV:schedule for all principals (does not include anonymous)
+ davxml.ACE(
+ davxml.Principal(davxml.Authenticated()),
+ davxml.Grant(
+ davxml.Privilege(davxml.Read()),
+ davxml.Privilege(caldavxml.Schedule()),
+ ),
+ davxml.Protected(),
+ ),
+ )
+
+ def resourceType(self):
+ return davxml.ResourceType.freebusyurl
+
+ def isCollection(self):
+ return False
+
+ def isCalendarCollection(self):
+ return False
+
+ def isPseudoCalendarCollection(self):
+ return False
+
+ def render(self, request):
+ output = """<html>
+<head>
+<title>Free-Busy URL Resource</title>
+</head>
+<body>
+<h1>Free-busy URL Resource.</h1>
+</body
+</html>"""
+
+ response = Response(200, {}, output)
+ response.headers.setHeader("content-type", MimeType("text", "html"))
+ return response
+
+ def http_GET(self, request):
+ """
+ The free-busy URL POST method.
+ """
+ return self._processFBURL(request)
+
+ def http_POST(self, request):
+ """
+ The free-busy URL POST method.
+ """
+ return self._processFBURL(request)
+
+ @deferredGenerator
+ def _processFBURL(self, request):
+
+ #
+ # Check authentication and access controls
+ #
+ x = waitForDeferred(self.authorize(request, (davxml.Read(),)))
+ yield x
+ x.getResult()
+
+ # Extract query parameters from the URL
+ args = ('start', 'end', 'duration', 'token', 'format', 'user',)
+ for arg in args:
+ setattr(self, arg, request.args.get(arg, [None])[0])
+
+ # Some things we do not handle
+ if self.token or self.user:
+ raise HTTPError(ErrorResponse(responsecode.NOT_ACCEPTABLE, (calendarserver_namespace, "supported-query-parameter")))
+
+ # Check format
+ if self.format:
+ self.format = self.format.split(";")[0]
+ if self.format not in ("text/calendar", "text/plain"):
+ raise HTTPError(ErrorResponse(responsecode.NOT_ACCEPTABLE, (calendarserver_namespace, "supported-format")))
+ else:
+ self.format = "text/calendar"
+
+ # Start/end/duration must be valid iCalendar DATE-TIME UTC or DURATION values
+ try:
+ if self.start:
+ self.start = parse_datetime(self.start)
+ if self.start.tzinfo != utc:
+ raise ValueError()
+ if self.end:
+ self.end = parse_datetime(self.end)
+ if self.end.tzinfo != utc:
+ raise ValueError()
+ if self.duration:
+ self.duration = parse_duration(self.duration)
+ except ValueError:
+ raise HTTPError(ErrorResponse(responsecode.BAD_REQUEST, (calendarserver_namespace, "valid-query-parameters")))
+
+ # Sanity check start/end/duration
+
+ # End and duration cannot both be present
+ if self.end and self.duration:
+ raise HTTPError(ErrorResponse(responsecode.NOT_ACCEPTABLE, (calendarserver_namespace, "valid-query-parameters")))
+
+ # Duration must be positive
+ if self.duration and self.duration.days < 0:
+ raise HTTPError(ErrorResponse(responsecode.BAD_REQUEST, (calendarserver_namespace, "valid-query-parameters")))
+
+ # Now fill in the missing pieces
+ if self.start is None:
+ now = datetime.datetime.now()
+ self.start = now.replace(hour=0, minute=0, second=0, tzinfo=utc)
+ if self.duration:
+ self.end = self.start + self.duration
+ if self.end is None:
+ self.end = self.start + datetime.timedelta(days=config.FreeBusyURL["Time Period"])
+
+ # End > start
+ if self.end <= self.start:
+ raise HTTPError(ErrorResponse(responsecode.BAD_REQUEST, (calendarserver_namespace, "valid-query-parameters")))
+
+ # TODO: We should probably verify that the actual time-range is within sensible bounds (e.g. not too far in the past or future and not too long)
+
+ # Now lookup the principal details for the targetted user
+ principal = self.parent.principalForRecord()
+
+ # Pick the first mailto cu address or the first other type
+ cuaddr = None
+ for item in principal.calendarUserAddresses():
+ if cuaddr is None:
+ cuaddr = item
+ if item.startswith("mailto"):
+ cuaddr = item
+ break
+
+ # Get inbox details
+ inboxURL = principal.scheduleInboxURL()
+ if inboxURL is None:
+ raise HTTPError(StatusResponse(responsecode.INTERNAL_SERVER_ERROR, "No schedule inbox URL for principal: %s" % (principal,)))
+ try:
+ inbox = waitForDeferred(request.locateResource(inboxURL))
+ yield inbox
+ inbox = inbox.getResult()
+ except:
+ log.err("No schedule inbox for principal: %s" % (principal,))
+ inbox = None
+ if inbox is None:
+ raise HTTPError(StatusResponse(responsecode.INTERNAL_SERVER_ERROR, "No schedule inbox for principal: %s" % (principal,)))
+
+ scheduler = Scheduler(request, self)
+ scheduler.timerange = TimeRange(start="20000101T000000Z", end="20070102T000000Z")
+ scheduler.timerange.start = self.start
+ scheduler.timerange.end = self.end
+
+ scheduler.organizer = Scheduler.LocalCalendarUser(cuaddr, principal, inbox, inboxURL)
+
+ attendeeProp = Property("ATTENDEE", scheduler.organizer.cuaddr)
+
+ d = waitForDeferred(scheduler.generateAttendeeFreeBusyResponse(
+ scheduler.organizer,
+ None,
+ None,
+ attendeeProp,
+ False,
+ ))
+ yield d
+ fbresult = d.getResult()
+
+ response = Response()
+ response.stream = MemoryStream(str(fbresult))
+ response.headers.setHeader("content-type", MimeType.fromString("%s; charset=utf-8" % (self.format,)))
+
+ yield response
Modified: CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/schedule_common.py
===================================================================
--- CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/schedule_common.py 2007-09-10 23:40:49 UTC (rev 1861)
+++ CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/schedule_common.py 2007-09-11 03:20:43 UTC (rev 1862)
@@ -21,6 +21,7 @@
"""
__all__ = [
+ "Scheduler",
"CalDAVScheduler",
"ServerToServerScheduler",
]
@@ -92,6 +93,8 @@
self.recipients = None
self.calendar = None
self.organizer = None
+ self.timerange = None
+ self.excludeuid = None
@deferredGenerator
def doSchedulingViaPOST(self):
@@ -443,60 +446,17 @@
remote = isinstance(self.organizer, Scheduler.RemoteCalendarUser)
- # Find the current recipients calendar-free-busy-set
- fbset = waitForDeferred(recipient.principal.calendarFreeBusyURIs(self.request))
- yield fbset
- fbset = fbset.getResult()
-
- # First list is BUSY, second BUSY-TENTATIVE, third BUSY-UNAVAILABLE
- fbinfo = ([], [], [])
-
try:
- # Process the availability property from the Inbox.
- has_prop = waitForDeferred(recipient.inbox.hasProperty((calendarserver_namespace, "calendar-availability"), self.request))
- yield has_prop
- has_prop = has_prop.getResult()
- if has_prop:
- availability = waitForDeferred(recipient.inbox.readProperty((calendarserver_namespace, "calendar-availability"), self.request))
- yield availability
- availability = availability.getResult()
- availability = availability.calendar()
- report_common.processAvailabilityFreeBusy(availability, fbinfo, self.timerange)
+ d = waitForDeferred(self.generateAttendeeFreeBusyResponse(
+ recipient,
+ organizerProp,
+ uid,
+ attendeeProp,
+ remote,
+ ))
+ yield d
+ fbresult = d.getResult()
- # Check to see if the recipient is the same calendar user as the organizer.
- # Needed for masked UID stuff.
- if isinstance(self.organizer, Scheduler.LocalCalendarUser):
- same_calendar_user = self.organizer.principal.principalURL() == recipient.principal.principalURL()
- else:
- same_calendar_user = False
-
- # Now process free-busy set calendars
- matchtotal = 0
- for calURL in fbset:
- cal = waitForDeferred(self.request.locateResource(calURL))
- yield cal
- cal = cal.getResult()
- if cal is None or not cal.exists() or not isCalendarCollectionResource(cal):
- # We will ignore missing calendars. If the recipient has failed to
- # properly manage the free busy set that should not prevent us from working.
- continue
-
- matchtotal = waitForDeferred(report_common.generateFreeBusyInfo(
- self.request,
- cal,
- fbinfo,
- self.timerange,
- matchtotal,
- excludeuid=self.excludeuid,
- organizer=self.organizer.cuaddr,
- same_calendar_user=same_calendar_user,
- servertoserver=remote))
- yield matchtotal
- matchtotal = matchtotal.getResult()
-
- # Build VFREEBUSY iTIP reply for this recipient
- fbresult = report_common.buildFreeBusyResult(fbinfo, self.timerange, organizer=organizerProp, attendee=attendeeProp, uid=uid)
-
responses.add(recipient.cuaddr, responsecode.OK, reqstatus="2.0;Success", calendar=fbresult)
yield True
@@ -507,6 +467,64 @@
yield False
+ @deferredGenerator
+ def generateAttendeeFreeBusyResponse(self, recipient, organizerProp, uid, attendeeProp, remote):
+
+ # Find the current recipients calendar-free-busy-set
+ fbset = waitForDeferred(recipient.principal.calendarFreeBusyURIs(self.request))
+ yield fbset
+ fbset = fbset.getResult()
+
+ # First list is BUSY, second BUSY-TENTATIVE, third BUSY-UNAVAILABLE
+ fbinfo = ([], [], [])
+
+ # Process the availability property from the Inbox.
+ has_prop = waitForDeferred(recipient.inbox.hasProperty((calendarserver_namespace, "calendar-availability"), self.request))
+ yield has_prop
+ has_prop = has_prop.getResult()
+ if has_prop:
+ availability = waitForDeferred(recipient.inbox.readProperty((calendarserver_namespace, "calendar-availability"), self.request))
+ yield availability
+ availability = availability.getResult()
+ availability = availability.calendar()
+ report_common.processAvailabilityFreeBusy(availability, fbinfo, self.timerange)
+
+ # Check to see if the recipient is the same calendar user as the organizer.
+ # Needed for masked UID stuff.
+ if isinstance(self.organizer, Scheduler.LocalCalendarUser):
+ same_calendar_user = self.organizer.principal.principalURL() == recipient.principal.principalURL()
+ else:
+ same_calendar_user = False
+
+ # Now process free-busy set calendars
+ matchtotal = 0
+ for calURL in fbset:
+ cal = waitForDeferred(self.request.locateResource(calURL))
+ yield cal
+ cal = cal.getResult()
+ if cal is None or not cal.exists() or not isCalendarCollectionResource(cal):
+ # We will ignore missing calendars. If the recipient has failed to
+ # properly manage the free busy set that should not prevent us from working.
+ continue
+
+ matchtotal = waitForDeferred(report_common.generateFreeBusyInfo(
+ self.request,
+ cal,
+ fbinfo,
+ self.timerange,
+ matchtotal,
+ excludeuid=self.excludeuid,
+ organizer=self.organizer.cuaddr,
+ same_calendar_user=same_calendar_user,
+ servertoserver=remote))
+ yield matchtotal
+ matchtotal = matchtotal.getResult()
+
+ # Build VFREEBUSY iTIP reply for this recipient
+ fbresult = report_common.buildFreeBusyResult(fbinfo, self.timerange, organizer=organizerProp, attendee=attendeeProp, uid=uid)
+
+ yield fbresult
+
def generateRemoteResponse(self):
raise NotImplementedError
Modified: CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/static.py
===================================================================
--- CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/static.py 2007-09-10 23:40:49 UTC (rev 1861)
+++ CalendarServer/branches/users/cdaboo/server2server-1842/twistedcaldav/static.py 2007-09-11 03:20:43 UTC (rev 1862)
@@ -56,8 +56,8 @@
from twistedcaldav import customxml
from twistedcaldav.caldavxml import caldav_namespace
from twistedcaldav.config import config
-from twistedcaldav.directory.directory import DirectoryService
from twistedcaldav.extensions import DAVFile
+from twistedcaldav.freebusyurl import FreeBusyURLResource
from twistedcaldav.ical import Component as iComponent
from twistedcaldav.ical import Property as iProperty
from twistedcaldav.index import Index, IndexSchedule
@@ -499,12 +499,17 @@
NotificationsCollectionFileClass = NotificationsCollectionFile
else:
NotificationsCollectionFileClass = None
+ if config.FreeBusyURL["Enabled"]:
+ FreeBusyURLFileClass = FreeBusyURLFile
+ else:
+ FreeBusyURLFileClass = None
cls = {
"inbox" : ScheduleInboxFile,
"outbox" : ScheduleOutboxFile,
"dropbox" : DropBoxHomeFileClass,
"notifications": NotificationsCollectionFileClass,
+ "freebusy" : FreeBusyURLFileClass,
}.get(name, None)
if cls is not None:
@@ -607,7 +612,7 @@
Server-to-server scheduling inbox resource.
"""
def __init__(self, path, parent):
- CalDAVFile.__init__(self, path, parent)
+ CalDAVFile.__init__(self, path, principalCollections=parent.principalCollections())
ScheduleServerToServerResource.__init__(self, parent)
self.fp.open("w").close()
@@ -623,7 +628,7 @@
if path == self.fp.path:
return self
else:
- return CalDAVFile(path, principalCollections=self.principalCollections())
+ return responsecode.NOT_FOUND
def http_PUT (self, request): return responsecode.FORBIDDEN
def http_COPY (self, request): return responsecode.FORBIDDEN
@@ -637,6 +642,38 @@
(caldav_namespace, "calendar-collection-location-ok")
)
+class FreeBusyURLFile (AutoProvisioningFileMixIn, FreeBusyURLResource, CalDAVFile):
+ """
+ Free-busy URL resource.
+ """
+ def __init__(self, path, parent):
+ CalDAVFile.__init__(self, path, principalCollections=parent.principalCollections())
+ FreeBusyURLResource.__init__(self, parent)
+
+ def __repr__(self):
+ return "<%s (free-busy URL resource): %s>" % (self.__class__.__name__, self.fp.path)
+
+ def isCollection(self):
+ return False
+
+ def createSimilarFile(self, path):
+ if path == self.fp.path:
+ return self
+ else:
+ return responsecode.NOT_FOUND
+
+ def http_PUT (self, request): return responsecode.FORBIDDEN
+ def http_COPY (self, request): return responsecode.FORBIDDEN
+ def http_MOVE (self, request): return responsecode.FORBIDDEN
+ def http_DELETE (self, request): return responsecode.FORBIDDEN
+ def http_MKCOL (self, request): return responsecode.FORBIDDEN
+
+ def http_MKCALENDAR(self, request):
+ return ErrorResponse(
+ responsecode.FORBIDDEN,
+ (caldav_namespace, "calendar-collection-location-ok")
+ )
+
##
# ACL
##
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20070910/30139fe5/attachment.html
More information about the calendarserver-changes
mailing list