[CalendarServer-changes] [6573] CalendarServer/trunk
source_changes at macosforge.org
source_changes at macosforge.org
Sat Nov 6 20:01:43 PDT 2010
Revision: 6573
http://trac.macosforge.org/projects/calendarserver/changeset/6573
Author: cdaboo at apple.com
Date: 2010-11-06 20:01:41 -0700 (Sat, 06 Nov 2010)
Log Message:
-----------
Refactor to get rid of proto class for calendar/addressbook collections. Also refactor storebridge
to pull out more "common" behavior between caldav/carddav. More refactoring along these lines to come.
Modified Paths:
--------------
CalendarServer/trunk/twistedcaldav/method/put_addressbook_common.py
CalendarServer/trunk/twistedcaldav/resource.py
CalendarServer/trunk/twistedcaldav/sharing.py
CalendarServer/trunk/twistedcaldav/storebridge.py
CalendarServer/trunk/twistedcaldav/test/test_wrapping.py
CalendarServer/trunk/txdav/caldav/datastore/file.py
CalendarServer/trunk/txdav/caldav/datastore/sql.py
CalendarServer/trunk/txdav/carddav/datastore/file.py
CalendarServer/trunk/txdav/carddav/datastore/sql.py
CalendarServer/trunk/txdav/common/datastore/file.py
CalendarServer/trunk/txdav/common/datastore/sql.py
Modified: CalendarServer/trunk/twistedcaldav/method/put_addressbook_common.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/put_addressbook_common.py 2010-11-04 23:05:26 UTC (rev 6572)
+++ CalendarServer/trunk/twistedcaldav/method/put_addressbook_common.py 2010-11-07 03:01:41 UTC (rev 6573)
@@ -353,7 +353,7 @@
@inlineCallbacks
def doSourceDelete(self):
# Delete the source resource
- yield self.source.storeRemove(self.request, self.source_uri)
+ yield self.source.storeRemove(self.request, False, self.source_uri)
log.debug("Source removed %s" % (self.source,))
returnValue(None)
Modified: CalendarServer/trunk/twistedcaldav/resource.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/resource.py 2010-11-04 23:05:26 UTC (rev 6572)
+++ CalendarServer/trunk/twistedcaldav/resource.py 2010-11-07 03:01:41 UTC (rev 6573)
@@ -192,6 +192,10 @@
##
def render(self, request):
+
+ if not self.exists():
+ return responsecode.NOT_FOUND
+
if config.EnableMonolithicCalendars:
#
# Send listing instead of iCalendar data to HTML agents
@@ -487,9 +491,8 @@
# FIXME: is there a better way to get back to the associated
# datastore object?
dataObject = None
- for attr in ("_newStoreCalendar", "_newStoreAddressBook"):
- if hasattr(self, attr):
- dataObject = getattr(self, attr)
+ if hasattr(self, "_newStoreObject"):
+ dataObject = getattr(self, "_newStoreObject")
if dataObject:
label = "collection" if isvirt else "default"
notifierID = dataObject.notifierID(label=label)
@@ -2322,21 +2325,11 @@
@inlineCallbacks
def makeRegularChild(self, name):
newCalendar = yield self._newStoreHome.calendarWithName(name)
- if newCalendar is None:
- # Local imports.due to circular dependency between modules.
- from twistedcaldav.storebridge import (
- ProtoCalendarCollectionResource)
- similar = ProtoCalendarCollectionResource(
- self._newStoreHome,
- name,
- principalCollections=self.principalCollections()
- )
- else:
- from twistedcaldav.storebridge import CalendarCollectionResource
- similar = CalendarCollectionResource(
- newCalendar, self._newStoreHome,
- principalCollections=self.principalCollections()
- )
+ from twistedcaldav.storebridge import CalendarCollectionResource
+ similar = CalendarCollectionResource(
+ newCalendar, self._newStoreHome, name=name,
+ principalCollections=self.principalCollections()
+ )
self.propagateTransaction(similar)
returnValue(similar)
@@ -2488,30 +2481,18 @@
# Check for public/global path
from twistedcaldav.storebridge import (
AddressBookCollectionResource,
- ProtoAddressBookCollectionResource,
GlobalAddressBookCollectionResource,
- ProtoGlobalAddressBookCollectionResource,
)
mainCls = AddressBookCollectionResource
- protoCls = ProtoAddressBookCollectionResource
if isinstance(self.record, InternalDirectoryRecord):
if "global" in self.record.shortNames:
mainCls = GlobalAddressBookCollectionResource
- protoCls = ProtoGlobalAddressBookCollectionResource
newAddressBook = yield self._newStoreHome.addressbookWithName(name)
- if newAddressBook is None:
- # Local imports.due to circular dependency between modules.
- similar = protoCls(
- self._newStoreHome,
- name,
- principalCollections=self.principalCollections()
- )
- else:
- similar = mainCls(
- newAddressBook, self._newStoreHome,
- principalCollections=self.principalCollections()
- )
+ similar = mainCls(
+ newAddressBook, self._newStoreHome, name,
+ principalCollections=self.principalCollections()
+ )
self.propagateTransaction(similar)
returnValue(similar)
Modified: CalendarServer/trunk/twistedcaldav/sharing.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/sharing.py 2010-11-04 23:05:26 UTC (rev 6572)
+++ CalendarServer/trunk/twistedcaldav/sharing.py 2010-11-07 03:01:41 UTC (rev 6573)
@@ -213,10 +213,8 @@
self._shareePrincipal = shareePrincipal
self._share = share
- if hasattr(self, "_newStoreCalendar"):
- self._newStoreCalendar.setSharingUID(self._shareePrincipal.principalUID())
- elif hasattr(self, "_newStoreAddressBook"):
- self._newStoreAddressBook.setSharingUID(self._shareePrincipal.principalUID())
+ if hasattr(self, "_newStoreObject"):
+ self._newStoreObject.setSharingUID(self._shareePrincipal.principalUID())
def isVirtualShare(self):
Modified: CalendarServer/trunk/twistedcaldav/storebridge.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/storebridge.py 2010-11-04 23:05:26 UTC (rev 6572)
+++ CalendarServer/trunk/twistedcaldav/storebridge.py 2010-11-07 03:01:41 UTC (rev 6573)
@@ -31,9 +31,11 @@
from twext.python import vcomponent
from twext.python.log import Logger
+from twext.web2 import responsecode
from twext.web2.dav import davxml
from twext.web2.dav.element.base import dav_namespace
from twext.web2.dav.http import ErrorResponse, ResponseQueue
+from twext.web2.dav.noneprops import NonePropertyStore
from twext.web2.dav.resource import TwistedACLInheritable, AccessDeniedError
from twext.web2.dav.util import parentForURL, allDataFromStream, joinURL, \
davXMLFromStream
@@ -184,33 +186,36 @@
-class _CalendarChildHelper(object):
+class _CommonHomeChildCollectionMixin(object):
"""
Methods for things which are like calendars.
"""
- def _initializeWithCalendar(self, calendar, home):
+ _childClass = None
+ _protoChildClass = None
+
+ def _initializeWithHomeChild(self, child, home):
"""
- Initialize with a calendar.
+ Initialize with a home child object.
- @param calendar: the wrapped calendar.
- @type calendar: L{txdav.caldav.icalendarstore.ICalendar}
+ @param child: the new store home child object.
+ @type calendar: L{txdav.common._.CommonHomeChild}
- @param home: the home through which the given calendar was accessed.
- @type home: L{txdav.caldav.icalendarstore.ICalendarHome}
+ @param home: the home through which the given home child was accessed.
+ @type home: L{txdav.common._.CommonHome}
"""
- self._newStoreCalendar = calendar
+ self._newStoreObject = child
self._newStoreParentHome = home
self._dead_properties = _NewStorePropertiesWrapper(
- self._newStoreCalendar.properties()
- )
+ self._newStoreObject.properties()
+ ) if self._newStoreObject else NonePropertyStore(self)
def index(self):
"""
Retrieve the new-style index wrapper.
"""
- return self._newStoreCalendar.retrieveOldIndex()
+ return self._newStoreObject.retrieveOldIndex()
def invitesDB(self):
@@ -218,33 +223,24 @@
Retrieve the new-style invites DB wrapper.
"""
if not hasattr(self, "_invitesDB"):
- self._invitesDB = self._newStoreCalendar.retrieveOldInvites()
+ self._invitesDB = self._newStoreObject.retrieveOldInvites()
return self._invitesDB
def exists(self):
# FIXME: tests
- return True
+ return self._newStoreObject is not None
@inlineCallbacks
def _indexWhatChanged(self, revision, depth):
# The newstore implementation supports this directly
returnValue(
- (yield self._newStoreCalendar.resourceNamesSinceToken(revision))
+ (yield self._newStoreObject.resourceNamesSinceToken(revision))
+ ([],)
)
- @classmethod
- def transform(cls, self, calendar, home):
- """
- Transform C{self} into a L{CalendarCollectionResource}.
- """
- self.__class__ = cls
- self._initializeWithCalendar(calendar, home)
-
-
@inlineCallbacks
def makeChild(self, name):
"""
@@ -252,38 +248,369 @@
based on a calendar object name.
"""
- cal = self._newStoreCalendar
- newStoreObject = yield cal.calendarObjectWithName(name)
-
- if newStoreObject is not None:
- similar = CalendarObjectResource(
- newStoreObject,
- principalCollections=self._principalCollections
- )
+ if self._newStoreObject:
+ newStoreObject = yield self._newStoreObject.objectResourceWithName(name)
+
+ if newStoreObject is not None:
+ similar = self._childClass(
+ newStoreObject,
+ principalCollections=self._principalCollections
+ )
+ else:
+ similar = self._protoChildClass(
+ self._newStoreObject, name,
+ principalCollections=self._principalCollections
+ )
+
+ self.propagateTransaction(similar)
+ returnValue(similar)
else:
- similar = ProtoCalendarObjectResource(
- cal, name,
- principalCollections=self._principalCollections
- )
+ returnValue(NoParent())
- self.propagateTransaction(similar)
- returnValue(similar)
-
-
@inlineCallbacks
def listChildren(self):
"""
@return: a sequence of the names of all known children of this resource.
"""
children = set(self.putChildren.keys())
- children.update((yield self._newStoreCalendar.listCalendarObjects()))
+ children.update((yield self._newStoreObject.listObjectResources()))
returnValue(sorted(children))
+ def name(self):
+ return self._name
-class StoreScheduleInboxResource(_CalendarChildHelper, ScheduleInboxResource):
+ def etag(self):
+ return ETag(self._newStoreObject.md5()) if self._newStoreObject else None
+
+ def lastModified(self):
+ return self._newStoreObject.modified() if self._newStoreObject else None
+
+
+ def creationDate(self):
+ return self._newStoreObject.created() if self._newStoreObject else None
+
+
+ def getSyncToken(self):
+ return self._newStoreObject.syncToken() if self._newStoreObject else None
+
+ @inlineCallbacks
+ def createCollection(self):
+ """
+ Override C{createCollection} to actually do the work.
+ """
+ self._newStoreObject = (yield self._newStoreParentHome.createChildWithName(self._name))
+
+ # Re-initialize to get stuff setup again now we have a "real" object
+ self._initializeWithHomeChild(self._newStoreObject, self._newStoreParentHome)
+
+ returnValue(CREATED)
+
+ @requiresPermissions(fromParent=[davxml.Unbind()])
+ @inlineCallbacks
+ def http_DELETE(self, request):
+ """
+ Override http_DELETE to validate 'depth' header.
+ """
+
+ if not self.exists():
+ log.err("Resource not found: %s" % (self,))
+ raise HTTPError(responsecode.NOT_FOUND)
+
+ depth = request.headers.getHeader("depth", "infinity")
+ if depth != "infinity":
+ msg = "illegal depth header for DELETE on collection: %s" % (
+ depth,
+ )
+ log.err(msg)
+ raise HTTPError(StatusResponse(BAD_REQUEST, msg))
+ response = (yield self.storeRemove(request, True, request.uri))
+ returnValue(response)
+
+ @inlineCallbacks
+ def storeRemove(self, request, viaRequest, where):
+ """
+ Delete this collection resource, first deleting each contained
+ object resource.
+
+ This has to emulate the behavior in fileop.delete in that any errors
+ need to be reported back in a multistatus response.
+
+ @param request: The request used to locate child resources. Note that
+ this is the request which I{triggered} the C{DELETE}, but which may
+ not actually be a C{DELETE} request itself.
+
+ @type request: L{twext.web2.iweb.IRequest}
+
+ @param viaRequest: Indicates if the delete was a direct result of an http_DELETE
+ which for calendars at least will require implicit cancels to be sent.
+
+ @type request: C{bool}
+
+ @param where: the URI at which the resource is being deleted.
+ @type where: C{str}
+
+ @return: an HTTP response suitable for sending to a client (or
+ including in a multi-status).
+
+ @rtype: something adaptable to L{twext.web2.iweb.IResponse}
+ """
+
+ # Check virtual share first
+ isVirtual = self.isVirtualShare()
+ if isVirtual:
+ log.debug("Removing shared collection %s" % (self,))
+ yield self.removeVirtualShare(request)
+ returnValue(NO_CONTENT)
+
+ log.debug("Deleting collection %s" % (self,))
+
+ # 'deluri' is this resource's URI; I should be able to synthesize it
+ # from 'self'.
+
+ errors = ResponseQueue(where, "DELETE", NO_CONTENT)
+
+ for childname in (yield self.listChildren()):
+
+ childurl = joinURL(where, childname)
+
+ # FIXME: use a more specific API; we should know what this child
+ # resource is, and not have to look it up. (Sharing information
+ # needs to move into the back-end first, though.)
+ child = (yield request.locateChildResource(self, childname))
+
+ try:
+ yield child.storeRemove(request, viaRequest, childurl)
+ except:
+ logDefaultException()
+ errors.add(childurl, BAD_REQUEST)
+
+ # Now do normal delete
+
+ # Handle sharing
+ wasShared = (yield self.isShared(request))
+ if wasShared:
+ yield self.downgradeFromShare(request)
+
+ # Actually delete it.
+ yield self._newStoreObject.remove()
+
+ # FIXME: handle exceptions, possibly like this:
+
+ # if isinstance(more_responses, MultiStatusResponse):
+ # # Merge errors
+ # errors.responses.update(more_responses.children)
+
+ response = errors.response()
+
+ returnValue(response)
+
+ def http_COPY(self, request):
+ """
+ Copying of calendar collections isn't allowed.
+ """
+ # FIXME: no direct tests
+ return FORBIDDEN
+
+
+ # FIXME: access control
+ @inlineCallbacks
+ def http_MOVE(self, request):
+ """
+ Moving a collection is allowed for the purposes of changing
+ that collections's name.
+ """
+ if not self.exists():
+ log.err("Resource not found: %s" % (self,))
+ raise HTTPError(responsecode.NOT_FOUND)
+
+ # Can not move outside of home or to existing collection
+ sourceURI = request.uri
+ destinationURI = urlsplit(request.headers.getHeader("destination"))[2]
+ if parentForURL(sourceURI) != parentForURL(destinationURI):
+ returnValue(FORBIDDEN)
+
+ destination = yield request.locateResource(destinationURI)
+ if destination.exists():
+ returnValue(FORBIDDEN)
+
+ # Forget the destination now as after the move we will need to re-init it with its
+ # new store object
+ request._forgetResource(destination, destinationURI)
+
+ # Move is valid so do it
+ basename = destinationURI.rstrip("/").split("/")[-1]
+ yield self._newStoreObject.rename(basename)
+ returnValue(NO_CONTENT)
+
+class CalendarCollectionResource(_CommonHomeChildCollectionMixin, CalDAVResource):
+ """
+ Wrapper around a L{txdav.caldav.icalendar.ICalendar}.
+ """
+
+
+ def __init__(self, calendar, home, name=None, *args, **kw):
+ """
+ Create a CalendarCollectionResource from a L{txdav.caldav.icalendar.ICalendar}
+ and the arguments required for L{CalDAVResource}.
+ """
+
+ self._childClass = CalendarObjectResource
+ self._protoChildClass = ProtoCalendarObjectResource
+ super(CalendarCollectionResource, self).__init__(*args, **kw)
+ self._initializeWithHomeChild(calendar, home)
+ self._name = calendar.name() if calendar else name
+
+
+ def __repr__(self):
+ return "<Calendar Collection Resource %r:%r %s>" % (
+ self._newStoreParentHome.uid(),
+ self._name,
+ "" if self._newStoreObject else "Non-existent"
+ )
+
+
+ def isCollection(self):
+ return True
+
+
+ def isCalendarCollection(self):
+ """
+ Yes, it is a calendar collection.
+ """
+ return True
+
+
+ @inlineCallbacks
+ def iCalendarRolledup(self, request):
+ # FIXME: uncached: implement cache in the storage layer
+
+ # Generate a monolithic calendar
+ calendar = vcomponent.VComponent("VCALENDAR")
+ calendar.addProperty(vcomponent.VProperty("VERSION", "2.0"))
+
+ # Do some optimisation of access control calculation by determining any
+ # inherited ACLs outside of the child resource loop and supply those to
+ # the checkPrivileges on each child.
+ filteredaces = (yield self.inheritedACEsforChildren(request))
+
+ tzids = set()
+ isowner = (yield self.isOwner(request, adminprincipals=True, readprincipals=True))
+ accessPrincipal = (yield self.resourceOwnerPrincipal(request))
+
+ for name, uid, type in (yield maybeDeferred(self.index().bruteForceSearch)): #@UnusedVariable
+ try:
+ child = yield request.locateChildResource(self, name)
+ except TypeError:
+ child = None
+
+ if child is not None:
+ # Check privileges of child - skip if access denied
+ try:
+ yield child.checkPrivileges(request, (davxml.Read(),), inherited_aces=filteredaces)
+ except AccessDeniedError:
+ continue
+
+ # Get the access filtered view of the data
+ caldata = yield child.iCalendarTextFiltered(isowner, accessPrincipal.principalUID() if accessPrincipal else "")
+ try:
+ subcalendar = vcomponent.VComponent.fromString(caldata)
+ except ValueError:
+ continue
+ assert subcalendar.name() == "VCALENDAR"
+
+ for component in subcalendar.subcomponents():
+
+ # Only insert VTIMEZONEs once
+ if component.name() == "VTIMEZONE":
+ tzid = component.propertyValue("TZID")
+ if tzid in tzids:
+ continue
+ tzids.add(tzid)
+
+ calendar.addComponent(component)
+
+ # Cache the data
+ data = str(calendar)
+ data = (yield self.getSyncToken()) + "\r\n" + data
+
+ returnValue(calendar)
+
+
+ createCalendarCollection = _CommonHomeChildCollectionMixin.createCollection
+
+
+ @inlineCallbacks
+ def storeRemove(self, request, implicitly, where):
+ """
+ Delete this calendar collection resource, first deleting each contained
+ calendar resource.
+
+ This has to emulate the behavior in fileop.delete in that any errors
+ need to be reported back in a multistatus response.
+
+ @param request: The request used to locate child resources. Note that
+ this is the request which I{triggered} the C{DELETE}, but which may
+ not actually be a C{DELETE} request itself.
+
+ @type request: L{twext.web2.iweb.IRequest}
+
+ @param implicitly: Should implicit scheduling operations be triggered
+ as a resut of this C{DELETE}?
+
+ @type implicitly: C{bool}
+
+ @param where: the URI at which the resource is being deleted.
+ @type where: C{str}
+
+ @return: an HTTP response suitable for sending to a client (or
+ including in a multi-status).
+
+ @rtype: something adaptable to L{twext.web2.iweb.IResponse}
+ """
+
+ # Not allowed to delete the default calendar
+ default = (yield self.isDefaultCalendar(request))
+ if default:
+ log.err("Cannot DELETE default calendar: %s" % (self,))
+ raise HTTPError(ErrorResponse(FORBIDDEN,
+ (caldav_namespace,
+ "default-calendar-delete-allowed",)))
+
+ response = (yield super(CalendarCollectionResource, self).storeRemove(request, implicitly, where))
+
+ if response == NO_CONTENT:
+ # Do some clean up
+ yield self.deletedCalendar(request)
+
+ returnValue(response)
+
+
+ # FIXME: access control
+ @inlineCallbacks
+ def http_MOVE(self, request):
+ """
+ Moving a calendar collection is allowed for the purposes of changing
+ that calendar's name.
+ """
+ defaultCalendar = (yield self.isDefaultCalendar(request))
+
+ result = (yield super(CalendarCollectionResource, self).http_MOVE(request))
+ if result == NO_CONTENT:
+ destinationURI = urlsplit(request.headers.getHeader("destination"))[2]
+ destination = yield request.locateResource(destinationURI)
+ yield self.movedCalendar(request, defaultCalendar,
+ destination, destinationURI)
+ returnValue(result)
+
+
+class StoreScheduleInboxResource(_CommonHomeChildCollectionMixin, ScheduleInboxResource):
+
def __init__(self, *a, **kw):
+
+ self._childClass = CalendarObjectResource
+ self._protoChildClass = ProtoCalendarObjectResource
super(StoreScheduleInboxResource, self).__init__(*a, **kw)
self.parent.propagateTransaction(self)
@@ -301,42 +628,30 @@
# this is a temporary workaround.
yield home.createCalendarWithName("inbox")
storage = yield home.calendarWithName("inbox")
- self._initializeWithCalendar(
+ self._initializeWithHomeChild(
storage,
self.parent._newStoreHome
)
+ self._name = storage.name()
returnValue(self)
- def name(self):
- return self._newStoreCalendar.name()
-
-
- def etag(self):
- return ETag(self._newStoreCalendar.md5())
-
-
- def lastModified(self):
- return self._newStoreCalendar.modified()
-
-
- def creationDate(self):
- return self._newStoreCalendar.created()
-
-
- def getSyncToken(self):
- return self._newStoreCalendar.syncToken()
-
-
def provisionFile(self):
pass
-
def provision(self):
pass
+ def http_DELETE(self, request):
+ return FORBIDDEN
+ def http_COPY(self, request):
+ return FORBIDDEN
+ def http_MOVE(self, request):
+ return FORBIDDEN
+
+
class _GetChildHelper(CalDAVResource):
def locateChild(self, request, segments):
@@ -702,263 +1017,9 @@
-class CalendarCollectionResource(_CalendarChildHelper, CalDAVResource):
- """
- Wrapper around a L{txdav.caldav.icalendar.ICalendar}.
- """
- def __init__(self, calendar, home, *args, **kw):
- """
- Create a CalendarCollectionResource from a L{txdav.caldav.icalendar.ICalendar}
- and the arguments required for L{CalDAVResource}.
- """
- super(CalendarCollectionResource, self).__init__(*args, **kw)
- self._initializeWithCalendar(calendar, home)
- def __repr__(self):
- return "<Calendar Collection Resource %r:%r>" % (
- self._newStoreCalendar.ownerCalendarHome().uid(),
- self._newStoreCalendar.name())
-
-
- def name(self):
- return self._newStoreCalendar.name()
-
-
- def etag(self):
- return ETag(self._newStoreCalendar.md5())
-
-
- def lastModified(self):
- return self._newStoreCalendar.modified()
-
-
- def creationDate(self):
- return self._newStoreCalendar.created()
-
-
- def getSyncToken(self):
- return self._newStoreCalendar.syncToken()
-
-
- def isCollection(self):
- return True
-
-
- def isCalendarCollection(self):
- """
- Yes, it is a calendar collection.
- """
- return True
-
-
- @inlineCallbacks
- def iCalendarRolledup(self, request):
- # FIXME: uncached: implement cache in the storage layer
-
- # Generate a monolithic calendar
- calendar = vcomponent.VComponent("VCALENDAR")
- calendar.addProperty(vcomponent.VProperty("VERSION", "2.0"))
-
- # Do some optimisation of access control calculation by determining any
- # inherited ACLs outside of the child resource loop and supply those to
- # the checkPrivileges on each child.
- filteredaces = (yield self.inheritedACEsforChildren(request))
-
- tzids = set()
- isowner = (yield self.isOwner(request, adminprincipals=True, readprincipals=True))
- accessPrincipal = (yield self.resourceOwnerPrincipal(request))
-
- for name, uid, type in (yield maybeDeferred(self.index().bruteForceSearch)): #@UnusedVariable
- try:
- child = yield request.locateChildResource(self, name)
- except TypeError:
- child = None
-
- if child is not None:
- # Check privileges of child - skip if access denied
- try:
- yield child.checkPrivileges(request, (davxml.Read(),), inherited_aces=filteredaces)
- except AccessDeniedError:
- continue
-
- # Get the access filtered view of the data
- caldata = yield child.iCalendarTextFiltered(isowner, accessPrincipal.principalUID() if accessPrincipal else "")
- try:
- subcalendar = vcomponent.VComponent.fromString(caldata)
- except ValueError:
- continue
- assert subcalendar.name() == "VCALENDAR"
-
- for component in subcalendar.subcomponents():
-
- # Only insert VTIMEZONEs once
- if component.name() == "VTIMEZONE":
- tzid = component.propertyValue("TZID")
- if tzid in tzids:
- continue
- tzids.add(tzid)
-
- calendar.addComponent(component)
-
- # Cache the data
- data = str(calendar)
- data = (yield self.getSyncToken()) + "\r\n" + data
-
- returnValue(calendar)
-
-
- @requiresPermissions(fromParent=[davxml.Unbind()])
- @inlineCallbacks
- def http_DELETE(self, request):
- """
- Override http_DELETE to validate 'depth' header.
- """
-
- depth = request.headers.getHeader("depth", "infinity")
- if depth != "infinity":
- msg = "illegal depth header for DELETE on collection: %s" % (
- depth,
- )
- log.err(msg)
- raise HTTPError(StatusResponse(BAD_REQUEST, msg))
- response = (yield self.storeRemove(request, True, request.uri))
- returnValue(response)
-
-
- @inlineCallbacks
- def storeRemove(self, request, implicitly, where):
- """
- Delete this calendar collection resource, first deleting each contained
- calendar resource.
-
- This has to emulate the behavior in fileop.delete in that any errors
- need to be reported back in a multistatus response.
-
- @param request: The request used to locate child resources. Note that
- this is the request which I{triggered} the C{DELETE}, but which may
- not actually be a C{DELETE} request itself.
-
- @type request: L{twext.web2.iweb.IRequest}
-
- @param implicitly: Should implicit scheduling operations be triggered
- as a resut of this C{DELETE}?
-
- @type implicitly: C{bool}
-
- @param where: the URI at which the resource is being deleted.
- @type where: C{str}
-
- @return: an HTTP response suitable for sending to a client (or
- including in a multi-status).
-
- @rtype: something adaptable to L{twext.web2.iweb.IResponse}
- """
-
- # Not allowed to delete the default calendar
- default = (yield self.isDefaultCalendar(request))
- if default:
- log.err("Cannot DELETE default calendar: %s" % (self,))
- raise HTTPError(ErrorResponse(FORBIDDEN,
- (caldav_namespace,
- "default-calendar-delete-allowed",)))
-
- # Is this a sharee's view of a shared calendar? If so, they can't do
- # scheduling onto it, so just delete it and move on.
- isVirtual = self.isVirtualShare()
- if isVirtual:
- log.debug("Removing shared calendar %s" % (self,))
- yield self.removeVirtualShare(request)
- returnValue(NO_CONTENT)
-
- log.debug("Deleting calendar %s" % (self,))
-
- # 'deluri' is this resource's URI; I should be able to synthesize it
- # from 'self'.
-
- errors = ResponseQueue(where, "DELETE", NO_CONTENT)
-
- for childname in (yield self.listChildren()):
-
- childurl = joinURL(where, childname)
-
- # FIXME: use a more specific API; we should know what this child
- # resource is, and not have to look it up. (Sharing information
- # needs to move into the back-end first, though.)
- child = (yield request.locateChildResource(self, childname))
-
- try:
- yield child.storeRemove(request, implicitly, childurl)
- except:
- logDefaultException()
- errors.add(childurl, BAD_REQUEST)
-
- # Now do normal delete
-
- # Handle sharing
- wasShared = (yield self.isShared(request))
- if wasShared:
- yield self.downgradeFromShare(request)
-
- # Actually delete it.
- yield self._newStoreParentHome.removeCalendarWithName(
- self._newStoreCalendar.name()
- )
- self.__class__ = ProtoCalendarCollectionResource
- del self._newStoreCalendar
-
- # FIXME: handle exceptions, possibly like this:
-
- # if isinstance(more_responses, MultiStatusResponse):
- # # Merge errors
- # errors.responses.update(more_responses.children)
-
- response = errors.response()
-
- if response == NO_CONTENT:
- # Do some clean up
- yield self.deletedCalendar(request)
-
- returnValue(response)
-
-
- def http_COPY(self, request):
- """
- Copying of calendar collections isn't allowed.
- """
- # FIXME: no direct tests
- return FORBIDDEN
-
-
- # FIXME: access control
- @inlineCallbacks
- def http_MOVE(self, request):
- """
- Moving a calendar collection is allowed for the purposes of changing
- that calendar's name.
- """
- defaultCalendar = (yield self.isDefaultCalendar(request))
- # FIXME: created to fix CDT test, no unit tests yet
- sourceURI = request.uri
- destinationURI = urlsplit(request.headers.getHeader("destination"))[2]
- if parentForURL(sourceURI) != parentForURL(destinationURI):
- returnValue(FORBIDDEN)
- destination = yield request.locateResource(destinationURI)
- # FIXME: should really use something other than 'fp' attribute.
- basename = destination.name()
- calendar = self._newStoreCalendar
- yield calendar.rename(basename)
- CalendarCollectionResource.transform(destination, calendar,
- self._newStoreParentHome)
- del self._newStoreCalendar
- self.__class__ = ProtoCalendarCollectionResource
- yield self.movedCalendar(request, defaultCalendar,
- destination, destinationURI)
- returnValue(NO_CONTENT)
-
-
-
class NoParent(CalDAVResource):
def http_MKCALENDAR(self, request):
return CONFLICT
@@ -970,75 +1031,6 @@
def isCollection(self):
return False
-class ProtoCalendarCollectionResource(CalDAVResource):
- """
- A resource representing a calendar collection which hasn't yet been created.
- """
-
- def __init__(self, home, name, *args, **kw):
- """
- A placeholder resource for a calendar collection which does not yet
- exist, but will become a L{CalendarCollectionResource}.
-
- @param home: The calendar home which will be this resource's parent,
- when it exists.
-
- @type home: L{txdav.caldav.icalendarstore.ICalendarHome}
- """
- super(ProtoCalendarCollectionResource, self).__init__(*args, **kw)
- self._newStoreParentHome = home
- self._name = name
-
-
- def isCollection(self):
- return True
-
- def makeChild(self, name):
- # FIXME: this is necessary for
- # twistedcaldav.test.test_mkcalendar.
- # MKCALENDAR.test_make_calendar_no_parent - there should be a more
- # structured way to refuse creation with a non-existent parent.
- return NoParent()
-
-
- def provisionFile(self):
- """
- Create a calendar collection.
- """
- # FIXME: there should be no need for this.
- return self.createCalendarCollection()
-
-
- @inlineCallbacks
- def createCalendarCollection(self):
- """
- Override C{createCalendarCollection} to actually do the work.
- """
- yield self._newStoreParentHome.createCalendarWithName(self._name)
- newStoreCalendar = yield self._newStoreParentHome.calendarWithName(
- self._name
- )
- CalendarCollectionResource.transform(
- self, newStoreCalendar, self._newStoreParentHome
- )
- returnValue(CREATED)
-
-
- def exists(self):
- # FIXME: tests
- return False
-
-
- def name(self):
- return self._name
-
- def provision(self):
- """
- This resource should do nothing if it's provisioned.
- """
- # FIXME: should be deleted, or raise an exception
-
-
class _CalendarObjectMetaDataMixin(object):
def _get_accessMode(self):
@@ -1341,144 +1333,32 @@
return succeed(0)
-
-class _AddressBookChildHelper(object):
+class AddressBookCollectionResource(_CommonHomeChildCollectionMixin, CalDAVResource):
"""
- Methods for things which are like addressbooks.
- """
-
- def _initializeWithAddressBook(self, addressbook, home):
- """
- Initialize with a addressbook.
-
- @param addressbook: the wrapped addressbook.
- @type addressbook: L{txdav.carddav.iaddressbookstore.IAddressBook}
-
- @param home: the home through which the given addressbook was accessed.
- @type home: L{txdav.carddav.iaddressbookstore.IAddressBookHome}
- """
- self._newStoreAddressBook = addressbook
- self._newStoreParentHome = home
- self._dead_properties = _NewStorePropertiesWrapper(
- self._newStoreAddressBook.properties()
- )
-
-
- def index(self):
- """
- Retrieve the new-style index wrapper.
- """
- return self._newStoreAddressBook.retrieveOldIndex()
-
-
- def invitesDB(self):
- """
- Retrieve the new-style invites DB wrapper.
- """
- if not hasattr(self, "_invitesDB"):
- self._invitesDB = self._newStoreAddressBook.retrieveOldInvites()
- return self._invitesDB
-
-
- def exists(self):
- # FIXME: tests
- return True
-
-
- @inlineCallbacks
- def _indexWhatChanged(self, revision, depth):
- # The newstore implementation supports this directly
- returnValue(
- (yield self._newStoreAddressBook.resourceNamesSinceToken(revision))
- + ([],)
- )
-
-
- @classmethod
- def transform(cls, self, addressbook, home):
- """
- Transform C{self} into a L{AddressBookCollectionResource}.
- """
- self.__class__ = cls
- self._initializeWithAddressBook(addressbook, home)
-
-
- @inlineCallbacks
- def makeChild(self, name):
- """
- Create a L{AddressBookObjectResource} or L{ProtoAddressBookObjectResource} based on a
- path object.
- """
- newStoreObject = yield self._newStoreAddressBook.addressbookObjectWithName(name)
-
- if newStoreObject is not None:
- similar = AddressBookObjectResource(
- newStoreObject,
- principalCollections=self._principalCollections
- )
- else:
- # FIXME: creation in http_PUT should talk to a specific resource
- # type; this is the domain of StoreAddressBookObjectResource.
- # similar = ProtoAddressBookObjectFile(self._newStoreAddressBook, path)
- similar = ProtoAddressBookObjectResource(
- self._newStoreAddressBook,
- name,
- principalCollections=self._principalCollections
- )
-
- # FIXME: tests should be failing without this line.
- # Specifically, http_PUT won't be committing its transaction properly.
- self.propagateTransaction(similar)
- returnValue(similar)
-
-
- @inlineCallbacks
- def listChildren(self):
- """
- @return: a sequence of the names of all known children of this resource.
- """
- children = set(self.putChildren.keys())
- children.update(
- (yield self._newStoreAddressBook.listAddressbookObjects())
- )
- returnValue(sorted(children))
-
-
-
-class AddressBookCollectionResource(_AddressBookChildHelper, CalDAVResource):
- """
Wrapper around a L{txdav.carddav.iaddressbook.IAddressBook}.
"""
- def __init__(self, addressbook, home, *args, **kw):
+ def __init__(self, addressbook, home, name=None, *args, **kw):
"""
Create a AddressBookCollectionResource from a L{txdav.carddav.iaddressbook.IAddressBook}
and the arguments required for L{CalDAVResource}.
"""
+
+ self._childClass = AddressBookObjectResource
+ self._protoChildClass = ProtoAddressBookObjectResource
super(AddressBookCollectionResource, self).__init__(*args, **kw)
- self._initializeWithAddressBook(addressbook, home)
+ self._initializeWithHomeChild(addressbook, home)
+ self._name = addressbook.name() if addressbook else name
- def name(self):
- return self._newStoreAddressBook.name()
+ def __repr__(self):
+ return "<AddressBook Collection Resource %r:%r %s>" % (
+ self._newStoreParentHome.uid(),
+ self._name,
+ "" if self._newStoreObject else "Non-existent"
+ )
- def etag(self):
- return ETag(self._newStoreAddressBook.md5())
-
-
- def lastModified(self):
- return self._newStoreAddressBook.modified()
-
-
- def creationDate(self):
- return self._newStoreAddressBook.created()
-
-
- def getSyncToken(self):
- return self._newStoreAddressBook.syncToken()
-
-
def isCollection(self):
return True
@@ -1490,218 +1370,15 @@
return True
- @requiresPermissions(fromParent=[davxml.Unbind()])
- @inlineCallbacks
- def http_DELETE(self, request):
- """
- Override http_DELETE to validate 'depth' header.
- """
- depth = request.headers.getHeader("depth", "infinity")
- if depth != "infinity":
- msg = "illegal depth header for DELETE on collection: %s" % (
- depth,
- )
- log.err(msg)
- raise HTTPError(StatusResponse(BAD_REQUEST, msg))
- response = (yield self.storeRemove(request, request.uri))
- returnValue(response)
+ createAddressBookCollection = _CommonHomeChildCollectionMixin.createCollection
- @inlineCallbacks
- def storeRemove(self, request, where):
- """
- Delete this addressbook collection resource, first deleting each contained
- addressbook resource.
-
- This has to emulate the behavior in fileop.delete in that any errors
- need to be reported back in a multistatus response.
-
- @param request: The request used to locate child resources. Note that
- this is the request which I{triggered} the C{DELETE}, but which may
- not actually be a C{DELETE} request itself.
-
- @type request: L{twext.web2.iweb.IRequest}
-
- @param where: the URI at which the resource is being deleted.
- @type where: C{str}
-
- @return: an HTTP response suitable for sending to a client (or
- including in a multi-status).
-
- @rtype: something adaptable to L{twext.web2.iweb.IResponse}
- """
-
- # Check virtual share first
- isVirtual = self.isVirtualShare()
- if isVirtual:
- log.debug("Removing shared calendar %s" % (self,))
- yield self.removeVirtualShare(request)
- returnValue(NO_CONTENT)
-
- log.debug("Deleting addressbook %s" % (self,))
-
- # 'deluri' is this resource's URI; I should be able to synthesize it
- # from 'self'.
-
- errors = ResponseQueue(where, "DELETE", NO_CONTENT)
-
- for childname in (yield self.listChildren()):
-
- childurl = joinURL(where, childname)
-
- # FIXME: use a more specific API; we should know what this child
- # resource is, and not have to look it up. (Sharing information
- # needs to move into the back-end first, though.)
- child = (yield request.locateChildResource(self, childname))
-
- try:
- yield child.storeRemove(request, childurl)
- except:
- logDefaultException()
- errors.add(childurl, BAD_REQUEST)
-
- # Now do normal delete
-
- # Handle sharing
- wasShared = (yield self.isShared(request))
- if wasShared:
- yield self.downgradeFromShare(request)
-
- # Actually delete it.
- yield self._newStoreParentHome.removeAddressBookWithName(
- self._newStoreAddressBook.name()
- )
- self.__class__ = ProtoAddressBookCollectionResource
- del self._newStoreAddressBook
-
- # FIXME: handle exceptions, possibly like this:
-
- # if isinstance(more_responses, MultiStatusResponse):
- # # Merge errors
- # errors.responses.update(more_responses.children)
-
- response = errors.response()
-
- returnValue(response)
-
-
- def http_COPY(self, request):
- """
- Copying of addressbook collections isn't allowed.
- """
- # FIXME: no direct tests
- return FORBIDDEN
-
-
- # FIXME: access control
- @inlineCallbacks
- def http_MOVE(self, request):
- """
- Moving a addressbook collection is allowed for the purposes of changing
- that addressbook's name.
- """
- # FIXME: created to fix CDT test, no unit tests yet
- sourceURI = request.uri
- destinationURI = urlsplit(request.headers.getHeader("destination"))[2]
- if parentForURL(sourceURI) != parentForURL(destinationURI):
- returnValue(FORBIDDEN)
- destination = yield request.locateResource(destinationURI)
- # FIXME: should really use something other than 'fp' attribute.
- basename = destination.name()
- addressbook = self._newStoreAddressBook
- yield addressbook.rename(basename)
- AddressBookCollectionResource.transform(destination, addressbook,
- self._newStoreParentHome)
- del self._newStoreAddressBook
- self.__class__ = ProtoAddressBookCollectionResource
- returnValue(NO_CONTENT)
-
-
-
-class ProtoAddressBookCollectionResource(CalDAVResource):
- """
- A resource representing an addressbook collection which hasn't yet been created.
- """
-
- def __init__(self, home, name, *args, **kw):
- """
- A placeholder resource for an addressbook collection which does not yet
- exist, but will become a L{AddressBookCollectionResource}.
-
- @param home: The addressbook home which will be this resource's parent,
- when it exists.
-
- @type home: L{txdav.carddav.iaddressbookstore.IAddressBookHome}
- """
- super(ProtoAddressBookCollectionResource, self).__init__(*args, **kw)
- self._newStoreParentHome = home
- self._name = name
-
-
- def isCollection(self):
- return True
-
-
- def makeChild(self, name):
- # FIXME: this is necessary for
- # twistedcaldav.test.test_mkcol.
- # MKCOL.test_make_addressbook_no_parent - there should be a more
- # structured way to refuse creation with a non-existent parent.
- return NoParent()
-
-
- def provisionFile(self):
- """
- Create an addressbook collection.
- """
- # FIXME: this should be done in the backend; provisionDefaultAddressBooks
- # should go away.
- return self.createAddressBookCollection()
-
-
- @inlineCallbacks
- def createAddressBookCollection(self):
- """
- Override C{createAddressBookCollection} to actually do the work.
- """
- yield self._newStoreParentHome.createAddressBookWithName(self._name)
- newStoreAddressBook = yield self._newStoreParentHome.addressbookWithName(
- self._name
- )
- AddressBookCollectionResource.transform(
- self, newStoreAddressBook, self._newStoreParentHome
- )
- returnValue(CREATED)
-
-
- def exists(self):
- # FIXME: tests
- return False
-
-
- def name(self):
- return self._name
-
- def provision(self):
- """
- This resource should do nothing if it's provisioned.
- """
- # FIXME: should be deleted, or raise an exception
-
-
class GlobalAddressBookCollectionResource(GlobalAddressBookResource, AddressBookCollectionResource):
"""
Wrapper around a L{txdav.carddav.iaddressbook.IAddressBook}.
"""
pass
-class ProtoGlobalAddressBookCollectionResource(GlobalAddressBookResource, ProtoAddressBookCollectionResource):
- """
- A resource representing an addressbook collection which hasn't yet been created.
- """
- pass
-
-
class AddressBookObjectResource(_NewStoreFileMetaDataHelper, CalDAVResource, FancyEqMixin):
"""
A resource wrapping a addressbook object.
@@ -1756,7 +1433,7 @@
"""
Override http_DELETE to validate 'depth' header.
"""
- return self.storeRemove(request, request.uri)
+ return self.storeRemove(request, True, request.uri)
@inlineCallbacks
@@ -1771,7 +1448,7 @@
@inlineCallbacks
- def storeRemove(self, request, where):
+ def storeRemove(self, request, viaRequest, where):
"""
Remove this addressbook object.
"""
Modified: CalendarServer/trunk/twistedcaldav/test/test_wrapping.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_wrapping.py 2010-11-04 23:05:26 UTC (rev 6572)
+++ CalendarServer/trunk/twistedcaldav/test/test_wrapping.py 2010-11-07 03:01:41 UTC (rev 6573)
@@ -32,8 +32,8 @@
from twistedcaldav.ical import Component as VComponent
from twistedcaldav.vcard import Component as VCComponent
-from twistedcaldav.storebridge import ProtoCalendarCollectionResource, \
- ProtoAddressBookCollectionResource, DropboxCollection
+from twistedcaldav.storebridge import DropboxCollection,\
+ CalendarCollectionResource, AddressBookCollectionResource
from twistedcaldav.test.util import TestCase
@@ -297,8 +297,10 @@
backend will be initialized to match.
"""
calDavFile = yield self.getResource("calendars/users/wsanchez/frobozz")
- self.assertIsInstance(calDavFile, ProtoCalendarCollectionResource)
+ self.assertIsInstance(calDavFile, CalendarCollectionResource)
+ self.assertFalse(calDavFile.exists())
yield calDavFile.createCalendarCollection()
+ self.assertTrue(calDavFile.exists())
yield self.commit()
@@ -315,7 +317,7 @@
"calendars/users/wsanchez/%s" % (specialName,)
)
self.assertIdentical(
- getattr(calDavFile, "_newStoreCalendar", None), None
+ getattr(calDavFile, "_newStoreObject", None), None
)
yield self.commit()
@@ -420,8 +422,10 @@
initialized to match.
"""
calDavFile = yield self.getResource("addressbooks/users/wsanchez/frobozz")
- self.assertIsInstance(calDavFile, ProtoAddressBookCollectionResource)
+ self.assertIsInstance(calDavFile, AddressBookCollectionResource)
+ self.assertFalse(calDavFile.exists())
yield calDavFile.createAddressBookCollection()
+ self.assertTrue(calDavFile.exists())
yield self.commit()
self.assertEquals(calDavFile._principalCollections,
frozenset([self.principalsResource]))
Modified: CalendarServer/trunk/txdav/caldav/datastore/file.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/file.py 2010-11-04 23:05:26 UTC (rev 6572)
+++ CalendarServer/trunk/txdav/caldav/datastore/file.py 2010-11-07 03:01:41 UTC (rev 6573)
@@ -85,40 +85,48 @@
class CalendarHome(CommonHome):
implements(ICalendarHome)
+ _topPath = "calendars"
+ _notifierPrefix = "CalDAV"
+
def __init__(self, uid, path, calendarStore, transaction, notifier):
super(CalendarHome, self).__init__(uid, path, calendarStore, transaction, notifier)
self._childClass = Calendar
- def calendarWithName(self, name):
+ createCalendarWithName = CommonHome.createChildWithName
+ removeCalendarWithName = CommonHome.removeChildWithName
+
+
+ def childWithName(self, name):
if name in IGNORE_NAMES:
# "dropbox" is a file storage area, not a calendar.
return None
else:
- return self.childWithName(name)
+ return super(CalendarHome, self).childWithName(name)
+ calendarWithName = childWithName
- createCalendarWithName = CommonHome.createChildWithName
- removeCalendarWithName = CommonHome.removeChildWithName
- def calendars(self):
+ def children(self):
"""
Return a generator of the child resource objects.
"""
for child in self.listCalendars():
yield self.calendarWithName(child)
+ calendars = children
- def listCalendars(self):
+ def listChildren(self):
"""
Return a generator of the child resource names.
"""
- for name in self.listChildren():
+ for name in super(CalendarHome, self).listChildren():
if name in IGNORE_NAMES:
continue
yield name
+ listCalendars = listChildren
@inlineCallbacks
@@ -138,8 +146,7 @@
def createdHome(self):
- self.createCalendarWithName("calendar")
- defaultCal = self.calendarWithName("calendar")
+ defaultCal = self.createCalendarWithName("calendar")
props = defaultCal.properties()
props[PropertyName(*ScheduleCalendarTransp.qname())] = ScheduleCalendarTransp(
Opaque())
@@ -153,7 +160,7 @@
"""
implements(ICalendar)
- def __init__(self, name, calendarHome, notifier, realName=None):
+ def __init__(self, name, calendarHome, realName=None):
"""
Initialize a calendar pointing at a path on disk.
@@ -168,8 +175,7 @@
will eventually have on disk.
@type realName: C{str}
"""
- super(Calendar, self).__init__(name, calendarHome, notifier,
- realName=realName)
+ super(Calendar, self).__init__(name, calendarHome, realName=realName)
self._index = Index(self)
self._invites = Invites(self)
Modified: CalendarServer/trunk/txdav/caldav/datastore/sql.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/sql.py 2010-11-04 23:05:26 UTC (rev 6572)
+++ CalendarServer/trunk/txdav/caldav/datastore/sql.py 2010-11-07 03:01:41 UTC (rev 6573)
@@ -66,15 +66,16 @@
implements(ICalendarHome)
+ _homeTable = CALENDAR_HOME_TABLE
+ _homeMetaDataTable = CALENDAR_HOME_METADATA_TABLE
+ _childTable = CALENDAR_TABLE
+ _bindTable = CALENDAR_BIND_TABLE
+ _notifierPrefix = "CalDAV"
+ _revisionsTable = CALENDAR_OBJECT_REVISIONS_TABLE
+
def __init__(self, transaction, ownerUID, notifier):
- self._homeTable = CALENDAR_HOME_TABLE
- self._homeMetaDataTable = CALENDAR_HOME_METADATA_TABLE
self._childClass = Calendar
- self._childTable = CALENDAR_TABLE
- self._bindTable = CALENDAR_BIND_TABLE
- self._revisionsTable = CALENDAR_OBJECT_REVISIONS_TABLE
-
super(CalendarHome, self).__init__(transaction, ownerUID, notifier)
self._shares = SQLLegacyCalendarShares(self)
@@ -99,8 +100,7 @@
@inlineCallbacks
def createdHome(self):
- yield self.createCalendarWithName("calendar")
- defaultCal = yield self.calendarWithName("calendar")
+ defaultCal = yield self.createCalendarWithName("calendar")
props = defaultCal.properties()
props[PropertyName(*ScheduleCalendarTransp.qname())] = ScheduleCalendarTransp(
Opaque())
@@ -114,7 +114,12 @@
"""
implements(ICalendar)
- def __init__(self, home, name, resourceID, notifier):
+ _bindTable = CALENDAR_BIND_TABLE
+ _homeChildTable = CALENDAR_TABLE
+ _revisionsTable = CALENDAR_OBJECT_REVISIONS_TABLE
+ _objectTable = CALENDAR_OBJECT_TABLE
+
+ def __init__(self, home, name, resourceID):
"""
Initialize a calendar pointing at a record in a database.
@@ -124,7 +129,7 @@
@param home: the home containing this calendar.
@type home: L{CalendarHome}
"""
- super(Calendar, self).__init__(home, name, resourceID, notifier)
+ super(Calendar, self).__init__(home, name, resourceID)
if name == 'inbox':
self._index = PostgresLegacyInboxIndexEmulator(self)
@@ -132,10 +137,6 @@
self._index = PostgresLegacyIndexEmulator(self)
self._invites = SQLLegacyCalendarInvites(self)
self._objectResourceClass = CalendarObject
- self._bindTable = CALENDAR_BIND_TABLE
- self._homeChildTable = CALENDAR_TABLE
- self._revisionsTable = CALENDAR_OBJECT_REVISIONS_TABLE
- self._objectTable = CALENDAR_OBJECT_TABLE
@property
Modified: CalendarServer/trunk/txdav/carddav/datastore/file.py
===================================================================
--- CalendarServer/trunk/txdav/carddav/datastore/file.py 2010-11-04 23:05:26 UTC (rev 6572)
+++ CalendarServer/trunk/txdav/carddav/datastore/file.py 2010-11-07 03:01:41 UTC (rev 6573)
@@ -64,6 +64,9 @@
implements(IAddressBookHome)
+ _topPath = "addressbooks"
+ _notifierPrefix = "CardDAV"
+
def __init__(self, uid, path, addressbookStore, transaction, notifier):
super(AddressBookHome, self).__init__(uid, path, addressbookStore, transaction, notifier)
@@ -88,7 +91,7 @@
"""
implements(IAddressBook)
- def __init__(self, name, addressbookHome, notifier, realName=None):
+ def __init__(self, name, addressbookHome, realName=None):
"""
Initialize an addressbook pointing at a path on disk.
@@ -104,8 +107,7 @@
@type realName: C{str}
"""
- super(AddressBook, self).__init__(name, addressbookHome, notifier,
- realName=realName)
+ super(AddressBook, self).__init__(name, addressbookHome, realName=realName)
self._index = Index(self)
self._invites = Invites(self)
Modified: CalendarServer/trunk/txdav/carddav/datastore/sql.py
===================================================================
--- CalendarServer/trunk/txdav/carddav/datastore/sql.py 2010-11-04 23:05:26 UTC (rev 6572)
+++ CalendarServer/trunk/txdav/carddav/datastore/sql.py 2010-11-07 03:01:41 UTC (rev 6573)
@@ -58,15 +58,16 @@
implements(IAddressBookHome)
+ _homeTable = ADDRESSBOOK_HOME_TABLE
+ _homeMetaDataTable = ADDRESSBOOK_HOME_METADATA_TABLE
+ _childTable = ADDRESSBOOK_TABLE
+ _bindTable = ADDRESSBOOK_BIND_TABLE
+ _notifierPrefix = "CardDAV"
+ _revisionsTable = ADDRESSBOOK_OBJECT_REVISIONS_TABLE
+
def __init__(self, transaction, ownerUID, notifier):
- self._homeTable = ADDRESSBOOK_HOME_TABLE
- self._homeMetaDataTable = ADDRESSBOOK_HOME_METADATA_TABLE
self._childClass = AddressBook
- self._childTable = ADDRESSBOOK_TABLE
- self._bindTable = ADDRESSBOOK_BIND_TABLE
- self._revisionsTable = ADDRESSBOOK_OBJECT_REVISIONS_TABLE
-
super(AddressBookHome, self).__init__(transaction, ownerUID, notifier)
self._shares = SQLLegacyAddressBookShares(self)
@@ -89,7 +90,12 @@
"""
implements(IAddressBook)
- def __init__(self, home, name, resourceID, notifier):
+ _bindTable = ADDRESSBOOK_BIND_TABLE
+ _homeChildTable = ADDRESSBOOK_TABLE
+ _revisionsTable = ADDRESSBOOK_OBJECT_REVISIONS_TABLE
+ _objectTable = ADDRESSBOOK_OBJECT_TABLE
+
+ def __init__(self, home, name, resourceID):
"""
Initialize an addressbook pointing at a path on disk.
@@ -105,15 +111,11 @@
@type realName: C{str}
"""
- super(AddressBook, self).__init__(home, name, resourceID, notifier)
+ super(AddressBook, self).__init__(home, name, resourceID)
self._index = PostgresLegacyABIndexEmulator(self)
self._invites = SQLLegacyAddressBookInvites(self)
self._objectResourceClass = AddressBookObject
- self._bindTable = ADDRESSBOOK_BIND_TABLE
- self._homeChildTable = ADDRESSBOOK_TABLE
- self._revisionsTable = ADDRESSBOOK_OBJECT_REVISIONS_TABLE
- self._objectTable = ADDRESSBOOK_OBJECT_TABLE
@property
Modified: CalendarServer/trunk/txdav/common/datastore/file.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/file.py 2010-11-04 23:05:26 UTC (rev 6572)
+++ CalendarServer/trunk/txdav/common/datastore/file.py 2010-11-07 03:01:41 UTC (rev 6573)
@@ -19,6 +19,7 @@
Common utility functions for a file based datastore.
"""
+from twext.internet.decorate import memoizedKey
from twext.python.log import LoggingMixIn
from twext.web2.dav.element.rfc2518 import ResourceType, GETContentType, HRef
from twext.web2.dav.element.rfc5842 import ResourceID
@@ -171,10 +172,9 @@
from txdav.carddav.datastore.file import AddressBookHome
super(CommonStoreTransaction, self).__init__(dataStore, name)
- self._homes = {}
- self._homes[ECALENDARTYPE] = {}
- self._homes[EADDRESSBOOKTYPE] = {}
- self._notifications = {}
+ self._calendarHomes = {}
+ self._addressbookHomes = {}
+ self._notificationHomes = {}
self._notifierFactory = notifierFactory
self._migrating = migrating
@@ -192,23 +192,71 @@
CommonStoreTransaction._homeClass[EADDRESSBOOKTYPE] = AddressBookHome
+ @memoizedKey('uid', '_calendarHomes')
def calendarHomeWithUID(self, uid, create=False):
return self.homeWithUID(ECALENDARTYPE, uid, create=create)
+ @memoizedKey("uid", "_addressbookHomes")
def addressbookHomeWithUID(self, uid, create=False):
return self.homeWithUID(EADDRESSBOOKTYPE, uid, create=create)
def homeWithUID(self, storeType, uid, create=False):
- if (uid, self) in self._homes[storeType]:
- return self._homes[storeType][(uid, self)]
-
if uid.startswith("."):
return None
+ if storeType not in (ECALENDARTYPE, EADDRESSBOOKTYPE):
+ raise RuntimeError("Unknown home type.")
+
+ return self._homeClass[storeType].homeWithUID(self, uid, create, storeType == ECALENDARTYPE)
+
+ @memoizedKey("uid", "_notificationHomes")
+ def notificationsWithUID(self, uid, home=None):
+
+ if home is None:
+ home = self.homeWithUID(self._notificationHomeType, uid, create=True)
+ return NotificationCollection.notificationsFromHome(self, home)
+
+
+class StubResource(object):
+ """
+ Just enough resource to keep the shared sql DB classes going.
+ """
+ def __init__(self, commonHome):
+ self._commonHome = commonHome
+
+
+ @property
+ def fp(self):
+ return self._commonHome._path
+
+
+
+class CommonHome(FileMetaDataMixin, LoggingMixIn):
+
+ # All these need to be initialized by derived classes for each store type
+ _childClass = None
+ _topPath = None
+ _notifierPrefix = None
+
+ def __init__(self, uid, path, dataStore, transaction, notifier):
+ self._dataStore = dataStore
+ self._uid = uid
+ self._path = path
+ self._transaction = transaction
+ self._notifier = notifier
+ self._shares = SharedCollectionsDatabase(StubResource(self))
+ self._newChildren = {}
+ self._removedChildren = set()
+ self._cachedChildren = {}
+
+
+ @classmethod
+ def homeWithUID(cls, txn, uid, create=False, withNotifications=False):
+
assert len(uid) >= 4
childPathSegments = []
- childPathSegments.append(self._dataStore._path.child(TOPPATHS[storeType]))
+ childPathSegments.append(txn._dataStore._path.child(cls._topPath))
childPathSegments.append(childPathSegments[-1].child(UIDPATH))
childPathSegments.append(childPathSegments[-1].child(uid[0:2]))
childPathSegments.append(childPathSegments[-1].child(uid[2:4]))
@@ -241,111 +289,27 @@
# do this _after_ all other file operations
home._path = childPath
return lambda : None
- self.addOperation(do, "create home UID %r" % (uid,))
+ txn.addOperation(do, "create home UID %r" % (uid,))
elif not childPath.isdir():
return None
else:
homePath = childPath
- if self._notifierFactory:
- notifier = self._notifierFactory.newNotifier(id=uid,
- prefix=NotifierPrefixes[storeType])
+ if txn._notifierFactory:
+ notifier = txn._notifierFactory.newNotifier(id=uid,
+ prefix=cls._notifierPrefix)
else:
notifier = None
- home = self._homeClass[storeType](uid, homePath, self._dataStore, self,
- notifier)
- self._homes[storeType][(uid, self)] = home
+ home = cls(uid, homePath, txn._dataStore, txn, notifier)
if creating:
home.createdHome()
+ if withNotifications:
+ txn.notificationsWithUID(uid, home)
- # Create notification collection
- if storeType == ECALENDARTYPE:
- self.notificationsWithUID(uid)
return home
- def notificationsWithUID(self, uid):
-
- if (uid, self) in self._notifications:
- return self._notifications[(uid, self)]
-
- home = self.homeWithUID(self._notificationHomeType, uid, create=True)
- if (uid, self) in self._notifications:
- return self._notifications[(uid, self)]
-
- notificationCollectionName = "notification"
- if not home._path.child(notificationCollectionName).isdir():
- notifications = self._createNotificationCollection(home, notificationCollectionName)
- else:
- notifications = NotificationCollection(notificationCollectionName, home)
- self._notifications[(uid, self)] = notifications
- return notifications
-
-
- def _createNotificationCollection(self, home, collectionName):
- # FIXME: this is a near-copy of CommonHome.createChildWithName.
- temporary = hidden(home._path.child(collectionName).temporarySibling())
- temporary.createDirectory()
- temporaryName = temporary.basename()
-
- c = NotificationCollection(temporary.basename(), home)
-
- def do():
- childPath = home._path.child(collectionName)
- temporary = childPath.sibling(temporaryName)
- try:
- props = c.properties()
- temporary.moveTo(childPath)
- c._name = collectionName
- # FIXME: _lots_ of duplication of work here.
- props.flush()
- except (IOError, OSError), e:
- if e.errno == EEXIST and childPath.isdir():
- raise HomeChildNameAlreadyExistsError(collectionName)
- raise
- # FIXME: direct tests, undo for index creation
- # Return undo
- return lambda: home._path.child(collectionName).remove()
-
- self.addOperation(do, "create notification child %r" %
- (collectionName,))
- props = c.properties()
- props[PropertyName(*ResourceType.qname())] = c.resourceType()
- return c
-
-
-
-class StubResource(object):
- """
- Just enough resource to keep the shared sql DB classes going.
- """
- def __init__(self, commonHome):
- self._commonHome = commonHome
-
-
- @property
- def fp(self):
- return self._commonHome._path
-
-
-
-class CommonHome(FileMetaDataMixin, LoggingMixIn):
-
- _childClass = None
-
- def __init__(self, uid, path, dataStore, transaction, notifier):
- self._dataStore = dataStore
- self._uid = uid
- self._path = path
- self._transaction = transaction
- self._notifier = notifier
- self._shares = SharedCollectionsDatabase(StubResource(self))
- self._newChildren = {}
- self._removedChildren = set()
- self._cachedChildren = {}
-
-
def __repr__(self):
return "<%s: %s>" % (self.__class__.__name__, self._path)
@@ -400,18 +364,10 @@
if name.startswith("."):
return None
- childPath = self._path.child(name)
- if childPath.isdir():
- if self._notifier:
- childID = "%s/%s" % (self.uid(), name)
- notifier = self._notifier.clone(label="collection", id=childID)
- else:
- notifier = None
- existingChild = self._childClass(name, self, notifier)
- self._cachedChildren[name] = existingChild
- return existingChild
- else:
- return None
+ child = self._childClass.objectWithName(self, name)
+ if child is not None:
+ self._cachedChildren[name] = child
+ return child
@writeOperation
@@ -432,11 +388,7 @@
# FIXME: some way to roll this back.
- if self._notifier:
- notifier = self._notifier.clone(label="collection", id=name)
- else:
- notifier = None
- c = self._newChildren[name] = self._childClass(temporary.basename(), self, notifier, realName=name)
+ c = self._newChildren[name] = self._childClass(temporary.basename(), self, realName=name)
c.retrieveOldIndex().create()
def do():
childPath = self._path.child(name)
@@ -461,8 +413,8 @@
self.createdChild(c)
self.notifyChanged()
+ return c
-
def createdChild(self, child):
pass
@@ -473,46 +425,14 @@
raise NoSuchHomeChildError(name)
child = self.childWithName(name)
- if not child:
+ if child is None:
raise NoSuchHomeChildError()
- self._removedChildren.add(name)
+ try:
+ child.remove()
+ finally:
+ self._removedChildren.add(name)
- def do(transaction=self._transaction):
- childPath = self._path.child(name)
- for i in xrange(1000):
- trash = childPath.sibling("._del_%s_%d" % (childPath.basename(), i))
- if not trash.exists():
- break
- else:
- raise InternalDataStoreError("Unable to create trash target for child at %s" % (childPath,))
-
- try:
- childPath.moveTo(trash)
- except (IOError, OSError), e:
- if e.errno == ENOENT:
- raise NoSuchHomeChildError(name)
- raise
-
- def cleanup():
- try:
- trash.remove()
- except Exception, e:
- self.log_error("Unable to delete trashed child at %s: %s" % (trash.fp, e))
-
- transaction.addOperation(cleanup, "remove child backup %r" % (name,))
- def undo():
- trash.moveTo(childPath)
-
- return undo
-
- # FIXME: direct tests
- self._transaction.addOperation(
- do, "prepare child remove %r" % (name,)
- )
-
- child.notifyChanged()
-
@inlineCallbacks
def syncToken(self):
@@ -587,7 +507,7 @@
_objectResourceClass = None
- def __init__(self, name, home, notifier, realName=None):
+ def __init__(self, name, home, realName=None):
"""
Initialize an home child pointing at a path on disk.
@@ -604,7 +524,6 @@
"""
self._name = name
self._home = home
- self._notifier = notifier
self._transaction = home._transaction
self._newObjectResources = {}
self._cachedObjectResources = {}
@@ -613,7 +532,18 @@
self._invites = None # Derived classes need to set this
self._renamedName = realName
+ if home._notifier:
+ childID = "%s/%s" % (home.uid(), name)
+ notifier = home._notifier.clone(label="collection", id=childID)
+ else:
+ notifier = None
+ self._notifier = notifier
+
+ @classmethod
+ def objectWithName(cls, home, name):
+ return cls(name, home) if home._path.child(name).isdir() else None
+
@property
def _path(self):
return self._home._path.child(self._name)
@@ -663,6 +593,44 @@
self.notifyChanged()
+ @writeOperation
+ def remove(self):
+
+ def do(transaction=self._transaction):
+ childPath = self._path
+ for i in xrange(1000):
+ trash = childPath.sibling("._del_%s_%d" % (childPath.basename(), i))
+ if not trash.exists():
+ break
+ else:
+ raise InternalDataStoreError("Unable to create trash target for child at %s" % (childPath,))
+
+ try:
+ childPath.moveTo(trash)
+ except (IOError, OSError), e:
+ if e.errno == ENOENT:
+ raise NoSuchHomeChildError(self._name)
+ raise
+
+ def cleanup():
+ try:
+ trash.remove()
+ except Exception, e:
+ self.log_error("Unable to delete trashed child at %s: %s" % (trash.fp, e))
+
+ self._transaction.addOperation(cleanup, "remove child backup %r" % (self._name,))
+ def undo():
+ trash.moveTo(childPath)
+
+ return undo
+
+ # FIXME: direct tests
+ self._transaction.addOperation(
+ do, "prepare child remove %r" % (self._name,)
+ )
+
+ self.notifyChanged()
+
def ownerHome(self):
return self._home
@@ -954,6 +922,49 @@
self._invites = None
self._objectResourceClass = NotificationObject
+ @classmethod
+ def notificationsFromHome(cls, txn, home):
+
+ notificationCollectionName = "notification"
+ if not home._path.child(notificationCollectionName).isdir():
+ notifications = cls._create(txn, home, notificationCollectionName)
+ else:
+ notifications = cls(notificationCollectionName, home)
+ return notifications
+
+
+ @classmethod
+ def _create(cls, txn, home, collectionName):
+ # FIXME: this is a near-copy of CommonHome.createChildWithName.
+ temporary = hidden(home._path.child(collectionName).temporarySibling())
+ temporary.createDirectory()
+ temporaryName = temporary.basename()
+
+ c = cls(temporary.basename(), home)
+
+ def do():
+ childPath = home._path.child(collectionName)
+ temporary = childPath.sibling(temporaryName)
+ try:
+ props = c.properties()
+ temporary.moveTo(childPath)
+ c._name = collectionName
+ # FIXME: _lots_ of duplication of work here.
+ props.flush()
+ except (IOError, OSError), e:
+ if e.errno == EEXIST and childPath.isdir():
+ raise HomeChildNameAlreadyExistsError(collectionName)
+ raise
+ # FIXME: direct tests, undo for index creation
+ # Return undo
+ return lambda: home._path.child(collectionName).remove()
+
+ txn.addOperation(do, "create notification child %r" %
+ (collectionName,))
+ props = c.properties()
+ props[PropertyName(*ResourceType.qname())] = c.resourceType()
+ return c
+
def resourceType(self):
return ResourceType.notification #@UndefinedVariable
Modified: CalendarServer/trunk/txdav/common/datastore/sql.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/sql.py 2010-11-04 23:05:26 UTC (rev 6572)
+++ CalendarServer/trunk/txdav/common/datastore/sql.py 2010-11-07 03:01:41 UTC (rev 6573)
@@ -48,10 +48,8 @@
from txdav.carddav.iaddressbookstore import IAddressBookTransaction
-from txdav.common.datastore.sql_tables import CALENDAR_HOME_TABLE, \
- ADDRESSBOOK_HOME_TABLE, NOTIFICATION_HOME_TABLE, _BIND_MODE_OWN, \
- _BIND_STATUS_ACCEPTED, NOTIFICATION_OBJECT_REVISIONS_TABLE,\
- CALENDAR_HOME_METADATA_TABLE, ADDRESSBOOK_HOME_METADATA_TABLE
+from txdav.common.datastore.sql_tables import NOTIFICATION_HOME_TABLE, _BIND_MODE_OWN, \
+ _BIND_STATUS_ACCEPTED, NOTIFICATION_OBJECT_REVISIONS_TABLE
from txdav.common.icommondatastore import HomeChildNameNotAllowedError, \
HomeChildNameAlreadyExistsError, NoSuchHomeChildError, \
ObjectResourceNameNotAllowedError, ObjectResourceNameAlreadyExistsError, \
@@ -132,8 +130,6 @@
Transaction implementation for SQL database.
"""
_homeClass = {}
- _homeTable = {}
- _homeMetaDataTable = {}
id = 0
@@ -163,10 +159,6 @@
from txdav.carddav.datastore.sql import AddressBookHome
CommonStoreTransaction._homeClass[ECALENDARTYPE] = CalendarHome
CommonStoreTransaction._homeClass[EADDRESSBOOKTYPE] = AddressBookHome
- CommonStoreTransaction._homeTable[ECALENDARTYPE] = CALENDAR_HOME_TABLE
- CommonStoreTransaction._homeTable[EADDRESSBOOKTYPE] = ADDRESSBOOK_HOME_TABLE
- CommonStoreTransaction._homeMetaDataTable[ECALENDARTYPE] = CALENDAR_HOME_METADATA_TABLE
- CommonStoreTransaction._homeMetaDataTable[EADDRESSBOOKTYPE] = ADDRESSBOOK_HOME_METADATA_TABLE
self._sqlTxn = sqlTxn
@@ -188,105 +180,18 @@
return self.homeWithUID(EADDRESSBOOKTYPE, uid, create=create)
- @inlineCallbacks
def homeWithUID(self, storeType, uid, create=False):
if storeType not in (ECALENDARTYPE, EADDRESSBOOKTYPE):
raise RuntimeError("Unknown home type.")
- if self._notifierFactory:
- notifier = self._notifierFactory.newNotifier(
- id=uid, prefix=NotifierPrefixes[storeType]
- )
- else:
- notifier = None
- homeObject = self._homeClass[storeType](self, uid, notifier)
- homeObject = (yield homeObject.initFromStore())
- if homeObject is not None:
- returnValue(homeObject)
- else:
- if not create:
- returnValue(None)
- # Need to lock to prevent race condition
- # FIXME: this is an entire table lock - ideally we want a row lock
- # but the row does not exist yet. However, the "exclusive" mode
- # does allow concurrent reads so the only thing we block is other
- # attempts to provision a home, which is not too bad
- yield self.execSQL(
- "lock %(name)s in exclusive mode" % CommonStoreTransaction._homeTable[storeType],
- )
- # Now test again
- exists = yield self.execSQL(
- "select %(column_RESOURCE_ID)s from %(name)s"
- " where %(column_OWNER_UID)s = %%s" % CommonStoreTransaction._homeTable[storeType],
- [uid]
- )
- if not exists:
- resourceid = (yield self.execSQL("""
- insert into %(name)s (%(column_OWNER_UID)s) values (%%s)
- returning %(column_RESOURCE_ID)s
- """ % CommonStoreTransaction._homeTable[storeType],
- [uid]
- ))[0][0]
- yield self.execSQL(
- "insert into %(name)s (%(column_RESOURCE_ID)s) values (%%s)" % CommonStoreTransaction._homeMetaDataTable[storeType],
- [resourceid]
- )
- home = yield self.homeWithUID(storeType, uid)
- if not exists:
- yield home.createdHome()
- returnValue(home)
+ return self._homeClass[storeType].homeWithUID(self, uid, create)
- def createHomeWithUIDLocked(self, storeType, uid):
- # Need to lock to prevent race condition
- # FIXME: this is an entire table lock - ideally we want a row lock
- # but the row does not exist yet. However, the "exclusive" mode
- # does allow concurrent reads so the only thing we block is other
- # attempts to provision a home, which is not too bad
-
- if storeType not in (ECALENDARTYPE, EADDRESSBOOKTYPE):
- raise RuntimeError("Unknown home type.")
-
- yield self.execSQL(
- "lock %(name)s in exclusive mode" % CommonStoreTransaction._homeTable[storeType],
- )
- # Now test again
- exists = yield self.execSQL(
- "select %(column_RESOURCE_ID)s from %(name)s"
- " where %(column_OWNER_UID)s = %%s" % CommonStoreTransaction._homeTable[storeType],
- [uid]
- )
- if not exists:
- yield self.execSQL(
- "insert into %(name)s (%(column_OWNER_UID)s) values (%%s)" % CommonStoreTransaction._homeTable[storeType],
- [uid]
- )
-
@memoizedKey("uid", "_notificationHomes")
- @inlineCallbacks
def notificationsWithUID(self, uid):
"""
Implement notificationsWithUID.
"""
- rows = yield self.execSQL(
- """
- select %(column_RESOURCE_ID)s from %(name)s where
- %(column_OWNER_UID)s = %%s
- """ % NOTIFICATION_HOME_TABLE, [uid]
- )
- if rows:
- resourceID = rows[0][0]
- created = False
- else:
- resourceID = str((yield self.execSQL(
- "insert into %(name)s (%(column_OWNER_UID)s) values (%%s) returning %(column_RESOURCE_ID)s" % NOTIFICATION_HOME_TABLE,
- [uid]
- ))[0][0])
- created = True
- collection = NotificationCollection(self, uid, resourceID)
- yield collection._loadPropertyStore()
- if created:
- yield collection._initSyncToken()
- returnValue(collection)
+ return NotificationCollection.notificationsWithUID(self, uid)
def postCommit(self, operation):
@@ -324,11 +229,13 @@
class CommonHome(LoggingMixIn):
+ # All these need to be initialized by derived classes for each store type
_homeTable = None
_homeMetaDataTable = None
_childClass = None
_childTable = None
_bindTable = None
+ _notifierPrefix = None
_revisionsTable = None
_notificationRevisionsTable = NOTIFICATION_OBJECT_REVISIONS_TABLE
@@ -351,7 +258,7 @@
@inlineCallbacks
def initFromStore(self):
"""
- Initialise this object from the store. We read in and cache all the extra metadata
+ Initialize this object from the store. We read in and cache all the extra meta-data
from the DB to avoid having to do DB queries for those individually later.
"""
@@ -367,6 +274,53 @@
else:
returnValue(None)
+ @classmethod
+ @inlineCallbacks
+ def homeWithUID(cls, txn, uid, create=False):
+
+ if txn._notifierFactory:
+ notifier = txn._notifierFactory.newNotifier(
+ id=uid, prefix=cls._notifierPrefix
+ )
+ else:
+ notifier = None
+ homeObject = cls(txn, uid, notifier)
+ homeObject = (yield homeObject.initFromStore())
+ if homeObject is not None:
+ returnValue(homeObject)
+ else:
+ if not create:
+ returnValue(None)
+ # Need to lock to prevent race condition
+ # FIXME: this is an entire table lock - ideally we want a row lock
+ # but the row does not exist yet. However, the "exclusive" mode
+ # does allow concurrent reads so the only thing we block is other
+ # attempts to provision a home, which is not too bad
+ yield txn.execSQL(
+ "lock %(name)s in exclusive mode" % cls._homeTable,
+ )
+ # Now test again
+ exists = yield txn.execSQL(
+ "select %(column_RESOURCE_ID)s from %(name)s"
+ " where %(column_OWNER_UID)s = %%s" % cls._homeTable,
+ [uid]
+ )
+ if not exists:
+ resourceid = (yield txn.execSQL("""
+ insert into %(name)s (%(column_OWNER_UID)s) values (%%s)
+ returning %(column_RESOURCE_ID)s
+ """ % cls._homeTable,
+ [uid]
+ ))[0][0]
+ yield txn.execSQL(
+ "insert into %(name)s (%(column_RESOURCE_ID)s) values (%%s)" % cls._homeMetaDataTable,
+ [resourceid]
+ )
+ home = yield cls.homeWithUID(txn, uid)
+ if not exists:
+ yield home.createdHome()
+ returnValue(home)
+
def __repr__(self):
return "<%s: %s>" % (self.__class__.__name__, self._resourceID)
@@ -412,7 +366,7 @@
@return: an iterable of C{str}s.
"""
- return self._listChildren(owned=True)
+ return self._childClass.listObjects(self, owned=True)
def listSharedChildren(self):
@@ -421,42 +375,9 @@
@return: an iterable of C{str}s.
"""
- return self._listChildren(owned=False)
+ return self._childClass.listObjects(self, owned=False)
- @inlineCallbacks
- def _listChildren(self, owned):
- """
- Retrieve the names of the children in this home.
-
- @return: an iterable of C{str}s.
- """
- # FIXME: not specified on the interface or exercised by the tests, but
- # required by clients of the implementation!
- if owned:
- rows = yield self._txn.execSQL("""
- select %(column_RESOURCE_NAME)s from %(name)s
- where
- %(column_HOME_RESOURCE_ID)s = %%s and
- %(column_BIND_MODE)s = %%s
- """ % self._bindTable,
- [self._resourceID, _BIND_MODE_OWN]
- )
- else:
- rows = yield self._txn.execSQL("""
- select %(column_RESOURCE_NAME)s from %(name)s
- where
- %(column_HOME_RESOURCE_ID)s = %%s and
- %(column_BIND_MODE)s != %%s and
- %(column_RESOURCE_NAME)s is not null
- """ % self._bindTable,
- [self._resourceID, _BIND_MODE_OWN]
- )
-
- names = [row[0] for row in rows]
- returnValue(names)
-
-
@memoizedKey("name", "_children")
def childWithName(self, name):
"""
@@ -466,7 +387,7 @@
@param name: a string.
@return: an L{ICalendar} or C{None} if no such child exists.
"""
- return self._childWithName(name, owned=True)
+ return self._childClass.objectWithName(self, name, owned=True)
@memoizedKey("name", "_sharedChildren")
@@ -485,104 +406,18 @@
@return: an L{ICalendar} or C{None} if no such child
exists.
"""
- return self._childWithName(name, owned=False)
+ return self._childClass.objectWithName(self, name, owned=False)
@inlineCallbacks
- def _childWithName(self, name, owned):
- """
- Retrieve the child with the given C{name} contained in this
- home.
-
- @param name: a string.
- @return: an L{ICalendar} or C{None} if no such child
- exists.
- """
-
- if owned:
- data = yield self._txn.execSQL("""
- select %(column_RESOURCE_ID)s from %(name)s
- where
- %(column_RESOURCE_NAME)s = %%s and
- %(column_HOME_RESOURCE_ID)s = %%s and
- %(column_BIND_MODE)s = %%s
- """ % self._bindTable,
- [
- name,
- self._resourceID,
- _BIND_MODE_OWN
- ]
- )
- else:
- data = yield self._txn.execSQL("""
- select %(column_RESOURCE_ID)s from %(name)s
- where
- %(column_RESOURCE_NAME)s = %%s and
- %(column_HOME_RESOURCE_ID)s = %%s and
- %(column_BIND_MODE)s != %%s
- """ % self._bindTable,
- [
- name,
- self._resourceID,
- _BIND_MODE_OWN
- ]
- )
-
- if not data:
- returnValue(None)
- resourceID = data[0][0]
- if self._notifier:
- childID = "%s/%s" % (self.uid(), name)
- notifier = self._notifier.clone(label="collection", id=childID)
- else:
- notifier = None
- child = self._childClass(self, name, resourceID, notifier)
- yield child.initFromStore()
- returnValue(child)
-
-
- @inlineCallbacks
def createChildWithName(self, name):
if name.startswith("."):
raise HomeChildNameNotAllowedError(name)
- rows = yield self._txn.execSQL(
- "select %(column_RESOURCE_NAME)s from %(name)s where "
- "%(column_RESOURCE_NAME)s = %%s AND "
- "%(column_HOME_RESOURCE_ID)s = %%s" % self._bindTable,
- [name, self._resourceID]
- )
- if rows:
- raise HomeChildNameAlreadyExistsError(name)
+ yield self._childClass.create(self, name)
+ child = (yield self.childWithName(name))
+ returnValue(child)
- rows = yield self._txn.execSQL("select nextval('RESOURCE_ID_SEQ')")
- resourceID = rows[0][0]
- yield self._txn.execSQL(
- "insert into %(name)s (%(column_RESOURCE_ID)s) values "
- "(%%s)" % self._childTable,
- [resourceID])
-
- yield self._txn.execSQL("""
- insert into %(name)s (
- %(column_HOME_RESOURCE_ID)s,
- %(column_RESOURCE_ID)s, %(column_RESOURCE_NAME)s, %(column_BIND_MODE)s,
- %(column_SEEN_BY_OWNER)s, %(column_SEEN_BY_SHAREE)s, %(column_BIND_STATUS)s) values (
- %%s, %%s, %%s, %%s, %%s, %%s, %%s)
- """ % self._bindTable,
- [self._resourceID, resourceID, name, _BIND_MODE_OWN, True, True,
- _BIND_STATUS_ACCEPTED]
- )
-
- newChild = yield self.childWithName(name)
- newChild.properties()[
- PropertyName.fromElement(ResourceType)
- ] = newChild.resourceType()
- yield newChild._initSyncToken()
- self.createdChild(newChild)
-
- self.notifyChanged()
-
-
def createdChild(self, child):
pass
@@ -590,22 +425,15 @@
@inlineCallbacks
def removeChildWithName(self, name):
child = yield self.childWithName(name)
- if not child:
+ if child is None:
raise NoSuchHomeChildError()
- yield child._deletedSyncToken()
try:
- yield self._txn.execSQL(
- "delete from %(name)s where %(column_RESOURCE_ID)s = %%s" % self._childTable,
- [child._resourceID],
- raiseOnZeroRowCount=NoSuchHomeChildError
- )
+ yield child.remove()
finally:
self._children.pop(name, None)
- child.notifyChanged()
-
@inlineCallbacks
def syncToken(self):
revision = (yield self._txn.execSQL(
@@ -822,20 +650,159 @@
_revisionsTable = None
_objectTable = None
- def __init__(self, home, name, resourceID, notifier):
+ def __init__(self, home, name, resourceID):
self._home = home
self._name = name
self._resourceID = resourceID
self._created = None
self._modified = None
self._objects = {}
+
+ if home._notifier:
+ childID = "%s/%s" % (home.uid(), name)
+ notifier = home._notifier.clone(label="collection", id=childID)
+ else:
+ notifier = None
self._notifier = notifier
self._index = None # Derived classes need to set this
self._invites = None # Derived classes need to set this
+ @classmethod
+ @inlineCallbacks
+ def listObjects(cls, home, owned):
+ """
+ Retrieve the names of the children that exist in this home.
+ @return: an iterable of C{str}s.
+ """
+ # FIXME: not specified on the interface or exercised by the tests, but
+ # required by clients of the implementation!
+ if owned:
+ rows = yield home._txn.execSQL("""
+ select %(column_RESOURCE_NAME)s from %(name)s
+ where
+ %(column_HOME_RESOURCE_ID)s = %%s and
+ %(column_BIND_MODE)s = %%s
+ """ % cls._bindTable,
+ [home._resourceID, _BIND_MODE_OWN]
+ )
+ else:
+ rows = yield home._txn.execSQL("""
+ select %(column_RESOURCE_NAME)s from %(name)s
+ where
+ %(column_HOME_RESOURCE_ID)s = %%s and
+ %(column_BIND_MODE)s != %%s and
+ %(column_RESOURCE_NAME)s is not null
+ """ % cls._bindTable,
+ [home._resourceID, _BIND_MODE_OWN]
+ )
+
+ names = [row[0] for row in rows]
+ returnValue(names)
+
+ @classmethod
@inlineCallbacks
+ def objectWithName(cls, home, name, owned):
+ """
+ Retrieve the child with the given C{name} contained in this
+ C{home}.
+
+ @param home: a L{CommonHome}.
+ @param name: a string.
+ @param owned: a boolean - whether or not to get a shared child
+ @param mustExist: a boolean - if False return and empty object
+ @return: an L{CommonHomChild} or C{None} if no such child
+ exists.
+ """
+
+ if owned:
+ data = yield home._txn.execSQL("""
+ select %(column_RESOURCE_ID)s from %(name)s
+ where
+ %(column_RESOURCE_NAME)s = %%s and
+ %(column_HOME_RESOURCE_ID)s = %%s and
+ %(column_BIND_MODE)s = %%s
+ """ % cls._bindTable,
+ [
+ name,
+ home._resourceID,
+ _BIND_MODE_OWN
+ ]
+ )
+ else:
+ data = yield home._txn.execSQL("""
+ select %(column_RESOURCE_ID)s from %(name)s
+ where
+ %(column_RESOURCE_NAME)s = %%s and
+ %(column_HOME_RESOURCE_ID)s = %%s and
+ %(column_BIND_MODE)s != %%s
+ """ % cls._bindTable,
+ [
+ name,
+ home._resourceID,
+ _BIND_MODE_OWN
+ ]
+ )
+
+ if not data:
+ returnValue(None)
+ resourceID = data[0][0]
+ child = cls(home, name, resourceID)
+ yield child.initFromStore()
+ returnValue(child)
+
+ @classmethod
+ @inlineCallbacks
+ def create(cls, home, name):
+
+ child = (yield cls.objectWithName(home, name, owned=True))
+ if child:
+ raise HomeChildNameAlreadyExistsError(name)
+
+ if name.startswith("."):
+ raise HomeChildNameNotAllowedError(name)
+
+ # Create and initialize (in a similar manner to initFromStore) this object
+ rows = yield home._txn.execSQL("select nextval('RESOURCE_ID_SEQ')")
+ resourceID = rows[0][0]
+ _created, _modified = (yield home._txn.execSQL("""
+ insert into %(name)s (%(column_RESOURCE_ID)s)
+ values (%%s)
+ returning %(column_CREATED)s, %(column_MODIFIED)s
+ """ % cls._homeChildTable,
+ [resourceID]
+ ))[0]
+
+ # Bind table needs entry
+ yield home._txn.execSQL("""
+ insert into %(name)s (
+ %(column_HOME_RESOURCE_ID)s,
+ %(column_RESOURCE_ID)s, %(column_RESOURCE_NAME)s, %(column_BIND_MODE)s,
+ %(column_SEEN_BY_OWNER)s, %(column_SEEN_BY_SHAREE)s, %(column_BIND_STATUS)s) values (
+ %%s, %%s, %%s, %%s, %%s, %%s, %%s)
+ """ % cls._bindTable,
+ [home._resourceID, resourceID, name, _BIND_MODE_OWN, True, True,
+ _BIND_STATUS_ACCEPTED]
+ )
+
+ # Initialize other state
+ child = cls(home, name, resourceID)
+ child._created = _created
+ child._modified = _modified
+ yield child._loadPropertyStore()
+
+ child.properties()[
+ PropertyName.fromElement(ResourceType)
+ ] = child.resourceType()
+ yield child._initSyncToken()
+ home.createdChild(child)
+
+ # Change notification for a create is on the home collection
+ home.notifyChanged()
+ returnValue(child)
+
+ @inlineCallbacks
def initFromStore(self):
"""
Initialise this object from the store. We read in and cache all the extra metadata
@@ -871,6 +838,12 @@
return "<%s: %s>" % (self.__class__.__name__, self._resourceID)
+ def exists(self):
+ """
+ An empty resource-id means this object does not yet exist in the DB.
+ """
+ return self._resourceID is not None
+
def name(self):
return self._name
@@ -893,6 +866,25 @@
self.notifyChanged()
+ @inlineCallbacks
+ def remove(self):
+
+ yield self._deletedSyncToken()
+
+ yield self._txn.execSQL(
+ "delete from %(name)s where %(column_RESOURCE_ID)s = %%s" % self._homeChildTable,
+ [self._resourceID],
+ raiseOnZeroRowCount=NoSuchHomeChildError
+ )
+
+ # Set to non-existent state
+ self._resourceID = None
+ self._created = None
+ self._modified = None
+ self._objects = {}
+
+ self.notifyChanged()
+
def ownerHome(self):
return self._home
@@ -1288,13 +1280,11 @@
def created(self):
- utc = datetime.datetime.strptime(self._created, "%Y-%m-%d %H:%M:%S.%f")
- return datetimeMktime(utc)
+ return datetimeMktime(datetime.datetime.strptime(self._created, "%Y-%m-%d %H:%M:%S.%f")) if self._created else None
def modified(self):
- utc = datetime.datetime.strptime(self._modified, "%Y-%m-%d %H:%M:%S.%f")
- return datetimeMktime(utc)
+ return datetimeMktime(datetime.datetime.strptime(self._modified, "%Y-%m-%d %H:%M:%S.%f")) if self._modified else None
def notifierID(self, label="default"):
@@ -1498,6 +1488,32 @@
self._resourceID = resourceID
self._notifications = {}
+ @classmethod
+ @inlineCallbacks
+ def notificationsWithUID(cls, txn, uid):
+ """
+ Implement notificationsWithUID.
+ """
+ rows = yield txn.execSQL(
+ """
+ select %(column_RESOURCE_ID)s from %(name)s where
+ %(column_OWNER_UID)s = %%s
+ """ % NOTIFICATION_HOME_TABLE, [uid]
+ )
+ if rows:
+ resourceID = rows[0][0]
+ created = False
+ else:
+ resourceID = str((yield txn.execSQL(
+ "insert into %(name)s (%(column_OWNER_UID)s) values (%%s) returning %(column_RESOURCE_ID)s" % NOTIFICATION_HOME_TABLE,
+ [uid]
+ ))[0][0])
+ created = True
+ collection = cls(txn, uid, resourceID)
+ yield collection._loadPropertyStore()
+ if created:
+ yield collection._initSyncToken()
+ returnValue(collection)
@inlineCallbacks
def _loadPropertyStore(self):
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20101106/afd598e7/attachment-0001.html>
More information about the calendarserver-changes
mailing list