Skip to content
Permalink
Browse files

Properly handle delete's of calendar collections (or nested calendar …

…collections) wrt implicit scheduling.

git-svn-id: https://svn.calendarserver.org/repository/calendarserver/CalendarServer/trunk@3380 e27351fd-9f3e-4f54-a53b-843176b1656c
  • Loading branch information...
cyrusdaboo committed Nov 13, 2008
2 parents c7b8edc + 365c415 commit 65d74afa4c40c013ec65e48d185f44bb282fee2f
Showing with 257 additions and 71 deletions.
  1. +6 −71 twistedcaldav/method/delete.py
  2. +251 −0 twistedcaldav/method/delete_common.py
@@ -1,5 +1,5 @@
##
# Copyright (c) 2006-2007 Apple Inc. All rights reserved.
# Copyright (c) 2006-2008 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.
@@ -23,13 +23,10 @@
from twisted.internet.defer import inlineCallbacks, returnValue
from twisted.web2 import responsecode
from twisted.web2.dav import davxml
from twisted.web2.dav.fileop import delete
from twisted.web2.dav.util import parentForURL
from twisted.web2.http import HTTPError, StatusResponse
from twisted.web2.http import HTTPError

from twistedcaldav.memcachelock import MemcacheLock, MemcacheLockTimeoutError
from twistedcaldav.resource import isCalendarCollectionResource
from twistedcaldav.scheduling.implicit import ImplicitScheduler
from twistedcaldav.method.delete_common import DeleteResource
from twistedcaldav.log import Logger

log = Logger()
@@ -41,9 +38,6 @@ def http_DELETE(self, request):
# index file has the entry for the deleted calendar component removed.
#

# TODO: need to use transaction based delete on live scheduling object resources
# as the iTIP operation may fail and may need to prevent the delete from happening.

if not self.fp.exists():
log.err("File not found: %s" % (self.fp.path,))
raise HTTPError(responsecode.NOT_FOUND)
@@ -58,67 +52,8 @@ def http_DELETE(self, request):

yield parent.authorize(request, (davxml.Unbind(),))

# Do quota checks before we start deleting things
myquota = (yield self.quota(request))
if myquota is not None:
old_size = (yield self.quotaSize(request))
else:
old_size = 0

scheduler = None
isCalendarCollection = False
isCalendarResource = False
lock = None

if self.exists():
if isCalendarCollectionResource(parent):
isCalendarResource = True
calendar = self.iCalendar()
scheduler = ImplicitScheduler()
do_implicit_action, _ignore = (yield scheduler.testImplicitSchedulingDELETE(request, self, calendar))
if do_implicit_action:
lock = MemcacheLock("ImplicitUIDLock", calendar.resourceUID(), timeout=60.0)
else:
scheduler = None

elif isCalendarCollectionResource(self):
isCalendarCollection = True

try:
if lock:
yield lock.acquire()

# Do delete
response = (yield delete(request.uri, self.fp, depth))


# Adjust quota
if myquota is not None:
yield self.quotaSizeAdjust(request, -old_size)

if response == responsecode.NO_CONTENT:
if isCalendarResource:

index = parent.index()
index.deleteResource(self.fp.basename())

# Change CTag on the parent calendar collection
yield parent.updateCTag()

# Do scheduling
if scheduler:
yield scheduler.doImplicitScheduling()

elif isCalendarCollection:

# Do some clean up
yield self.deletedCalendar(request)

except MemcacheLockTimeoutError:
raise HTTPError(StatusResponse(responsecode.CONFLICT, "Resource: %s currently in use on the server." % (self.uri,)))

finally:
if lock:
yield lock.clean()
# Do smart delete taking into account the need to do implicit CANCELs etc
deleter = DeleteResource(request, self, parent, depth)
response = (yield deleter.run())

returnValue(response)
@@ -0,0 +1,251 @@
##
# Copyright (c) 2006-2008 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 twistedcaldav.method.report_common import applyToCalendarCollections


"""
CalDAV DELETE behaviors.
"""

__all__ = ["DeleteResource"]

from twisted.internet.defer import inlineCallbacks, returnValue
from twisted.web2 import responsecode
from twisted.web2.dav.fileop import delete
from twisted.web2.dav.http import ResponseQueue, MultiStatusResponse
from twisted.web2.dav.util import joinURL
from twisted.web2.http import HTTPError, StatusResponse

from twistedcaldav.log import Logger
from twistedcaldav.memcachelock import MemcacheLock, MemcacheLockTimeoutError
from twistedcaldav.resource import isCalendarCollectionResource,\
isPseudoCalendarCollectionResource
from twistedcaldav.scheduling.implicit import ImplicitScheduler

log = Logger()

class DeleteResource(object):

def __init__(self, request, resource, parent, depth):

self.request = request
self.resource = resource
self.parent = parent
self.depth = depth

@inlineCallbacks
def deleteResource(self, delresource, deluri, parent):
"""
Delete a plain resource which may be a collection - but only one not containing
calendar resources.
@param delresource:
@type delresource:
@param deluri:
@type deluri:
@param parent:
@type parent:
"""

# Do quota checks before we start deleting things
myquota = (yield delresource.quota(self.request))
if myquota is not None:
old_size = (yield delresource.quotaSize(self.request))
else:
old_size = 0

# Do delete
response = (yield delete(deluri, delresource.fp, self.depth))

# Adjust quota
if myquota is not None:
yield delresource.quotaSizeAdjust(self.request, -old_size)

if response == responsecode.NO_CONTENT:
if isPseudoCalendarCollectionResource(parent):
index = parent.index()
index.deleteResource(delresource.fp.basename())

# Change CTag on the parent calendar collection
yield parent.updateCTag()

returnValue(response)

@inlineCallbacks
def deleteCalendarResource(self, delresource, deluri, parent):
"""
Delete a single calendar resource and do implicit scheduling actions if required.
@param delresource:
@type delresource:
@param deluri:
@type deluri:
@param parent:
@type parent:
"""

# TODO: need to use transaction based delete on live scheduling object resources
# as the iTIP operation may fail and may need to prevent the delete from happening.

# Do quota checks before we start deleting things
myquota = (yield delresource.quota(self.request))
if myquota is not None:
old_size = (yield delresource.quotaSize(self.request))
else:
old_size = 0

# Get data we need for implicit scheduling
calendar = delresource.iCalendar()
scheduler = ImplicitScheduler()
do_implicit_action, _ignore = (yield scheduler.testImplicitSchedulingDELETE(self.request, delresource, calendar))
if do_implicit_action:
lock = MemcacheLock("ImplicitUIDLock", calendar.resourceUID(), timeout=60.0)
else:
scheduler = None
lock = None

try:
if lock:
yield lock.acquire()

# Do delete
response = (yield delete(deluri, delresource.fp, self.depth))

# Adjust quota
if myquota is not None:
yield delresource.quotaSizeAdjust(self.request, -old_size)

if response == responsecode.NO_CONTENT:
index = parent.index()
index.deleteResource(delresource.fp.basename())

# Change CTag on the parent calendar collection
yield parent.updateCTag()

# Do scheduling
if scheduler:
yield scheduler.doImplicitScheduling()

except MemcacheLockTimeoutError:
raise HTTPError(StatusResponse(responsecode.CONFLICT, "Resource: %s currently in use on the server." % (deluri,)))

finally:
if lock:
yield lock.clean()

returnValue(response)

@inlineCallbacks
def deleteCalendar(self, delresource, deluri, parent):
"""
Delete an entire calendar collection by deleting each child resource in turn to
ensure that proper implicit scheduling actions occur.
This has to emulate the behavior in fileop.delete in that any errors need to be
reported back in a multistatus response.
"""

if self.depth != "infinity":
msg = "Client sent illegal depth header value for DELETE: %s" % (self.depth,)
log.err(msg)
raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, msg))

log.debug("Deleting calendar %s" % (delresource.fp.path,))

errors = ResponseQueue(deluri, "DELETE", responsecode.NO_CONTENT)

for childname in delresource.listChildren():

childurl = joinURL(deluri, childname)
child = (yield self.request.locateChildResource(delresource, childname))

try:
yield self.deleteCalendarResource(child, childurl, delresource)
except:
errors.add(childurl, responsecode.BAD_REQUEST)

# Now do normal delete
more_responses = (yield self.deleteResource(delresource, deluri, parent))

if isinstance(more_responses, MultiStatusResponse):
# Merge errors
errors.responses.update(more_responses.children)

response = errors.response()

if response == responsecode.NO_CONTENT:
# Do some clean up
yield delresource.deletedCalendar(self.request)

returnValue(response)

@inlineCallbacks
def deleteCollection(self):
"""
Delete a regular collection with special processing for any calendar collections
contained within it.
"""
if self.depth != "infinity":
msg = "Client sent illegal depth header value for DELETE: %s" % (self.depth,)
log.err(msg)
raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, msg))

log.debug("Deleting collection %s" % (self.resource.fp.path,))

errors = ResponseQueue(self.request.uri, "DELETE", responsecode.NO_CONTENT)

@inlineCallbacks
def doDeleteCalendar(delresource, deluri):

delparent = (yield delresource.locateParent(self.request, deluri))

response = (yield self.deleteCalendar(delresource, deluri, delparent))

if isinstance(response, MultiStatusResponse):
# Merge errors
errors.responses.update(response.children)

returnValue(True)

yield applyToCalendarCollections(self.resource, self.request, self.request.uri, self.depth, doDeleteCalendar, None)

# Now do normal delete
more_responses = (yield self.deleteResource(self.resource, self.request.uri, self.parent))

if isinstance(more_responses, MultiStatusResponse):
# Merge errors
errors.responses.update(more_responses.children)

response = errors.response()

returnValue(response)

@inlineCallbacks
def run(self):

if isCalendarCollectionResource(self.parent):
response = (yield self.deleteCalendarResource(self.resource, self.request.uri, self.parent))

elif isCalendarCollectionResource(self.resource):
response = (yield self.deleteCalendar(self.resource, self.request.uri, self.parent))

elif self.resource.isCollection():
response = (yield self.deleteCollection())

else:
response = (yield self.deleteResource(self.resource, self.request.uri, self.parent))

returnValue(response)

0 comments on commit 65d74af

Please sign in to comment.
You can’t perform that action at this time.