[CalendarServer-changes] [5816] CalendarServer/branches/new-store
source_changes at macosforge.org
source_changes at macosforge.org
Wed Jun 30 14:07:31 PDT 2010
Revision: 5816
http://trac.macosforge.org/projects/calendarserver/changeset/5816
Author: cdaboo at apple.com
Date: 2010-06-30 14:07:30 -0700 (Wed, 30 Jun 2010)
Log Message:
-----------
Support for sharing. Fixes to unit tests.
Modified Paths:
--------------
CalendarServer/branches/new-store/calendarserver/tap/util.py
CalendarServer/branches/new-store/calendarserver/tools/util.py
CalendarServer/branches/new-store/twistedcaldav/directory/test/test_principal.py
CalendarServer/branches/new-store/twistedcaldav/resource.py
CalendarServer/branches/new-store/twistedcaldav/schedule.py
CalendarServer/branches/new-store/twistedcaldav/sharing.py
CalendarServer/branches/new-store/twistedcaldav/static.py
CalendarServer/branches/new-store/twistedcaldav/storebridge.py
CalendarServer/branches/new-store/twistedcaldav/test/test_addressbookmultiget.py
CalendarServer/branches/new-store/twistedcaldav/test/test_addressbookquery.py
CalendarServer/branches/new-store/twistedcaldav/test/test_mkcalendar.py
CalendarServer/branches/new-store/twistedcaldav/test/test_multiget.py
CalendarServer/branches/new-store/twistedcaldav/test/test_schedule.py
CalendarServer/branches/new-store/twistedcaldav/test/test_wrapping.py
CalendarServer/branches/new-store/twistedcaldav/test/util.py
CalendarServer/branches/new-store/txcaldav/calendarstore/file.py
CalendarServer/branches/new-store/txcaldav/calendarstore/test/common.py
CalendarServer/branches/new-store/txcaldav/icalendarstore.py
CalendarServer/branches/new-store/txcarddav/addressbookstore/file.py
CalendarServer/branches/new-store/txcarddav/addressbookstore/test/common.py
CalendarServer/branches/new-store/txcarddav/iaddressbookstore.py
CalendarServer/branches/new-store/txdav/common/datastore/file.py
CalendarServer/branches/new-store/txdav/common/icommondatastore.py
CalendarServer/branches/new-store/txdav/propertystore/base.py
Added Paths:
-----------
CalendarServer/branches/new-store/txdav/common/inotifications.py
Modified: CalendarServer/branches/new-store/calendarserver/tap/util.py
===================================================================
--- CalendarServer/branches/new-store/calendarserver/tap/util.py 2010-06-30 21:02:35 UTC (rev 5815)
+++ CalendarServer/branches/new-store/calendarserver/tap/util.py 2010-06-30 21:07:30 UTC (rev 5816)
@@ -67,6 +67,8 @@
from calendarserver.webadmin.resource import WebAdminResource
from calendarserver.webcal.resource import WebCalendarResource
+from txdav.common.datastore.file import CommonDataStore
+
log = Logger()
@@ -272,18 +274,23 @@
principalCollection = principalResourceClass("/principals/", directory)
+ # Need a data store
+ _newStore = CommonDataStore(FilePath(config.DocumentRoot), config.EnableCalDAV, config.EnableCardDAV)
+
if config.EnableCalDAV:
log.info("Setting up calendar collection: %r" % (calendarResourceClass,))
calendarCollection = calendarResourceClass(
os.path.join(config.DocumentRoot, "calendars"),
directory, "/calendars/",
+ _newStore,
)
if config.EnableCardDAV:
log.info("Setting up address book collection: %r" % (addressBookResourceClass,))
addressBookCollection = addressBookResourceClass(
os.path.join(config.DocumentRoot, "addressbooks"),
- directory, "/addressbooks/"
+ directory, "/addressbooks/",
+ _newStore,
)
directoryPath = os.path.join(config.DocumentRoot, config.DirectoryAddressBook.name)
Modified: CalendarServer/branches/new-store/calendarserver/tools/util.py
===================================================================
--- CalendarServer/branches/new-store/calendarserver/tools/util.py 2010-06-30 21:02:35 UTC (rev 5815)
+++ CalendarServer/branches/new-store/calendarserver/tools/util.py 2010-06-30 21:07:30 UTC (rev 5816)
@@ -28,6 +28,7 @@
from pwd import getpwnam
from grp import getgrnam
+from twisted.python.filepath import FilePath
from twisted.python.reflect import namedClass
from twext.python.log import Logger
@@ -42,6 +43,8 @@
from twistedcaldav.static import CalendarHomeProvisioningFile
from twistedcaldav.stdconfig import DEFAULT_CONFIG_FILE
+from txdav.common.datastore.file import CommonDataStore
+
log = Logger()
def loadConfig(configFileName):
@@ -60,6 +63,10 @@
class MyDirectoryService (AggregateDirectoryService):
def getPrincipalCollection(self):
if not hasattr(self, "_principalCollection"):
+
+ # Need a data store
+ _newStore = CommonDataStore(FilePath(config.DocumentRoot), True, False)
+
#
# Instantiating a CalendarHomeProvisioningResource with a directory
# will register it with the directory (still smells like a hack).
@@ -67,7 +74,7 @@
# We need that in order to locate calendar homes via the directory.
#
from twistedcaldav.static import CalendarHomeProvisioningFile
- CalendarHomeProvisioningFile(os.path.join(config.DocumentRoot, "calendars"), self, "/calendars/")
+ CalendarHomeProvisioningFile(os.path.join(config.DocumentRoot, "calendars"), self, "/calendars/", _newStore)
from twistedcaldav.directory.principal import DirectoryPrincipalProvisioningResource
self._principalCollection = DirectoryPrincipalProvisioningResource("/principals/", self)
@@ -132,9 +139,14 @@
principalCollections=(principalCollection,),
)
root.putChild("principals", principalCollection)
+
+ # Need a data store
+ _newStore = CommonDataStore(FilePath(config.DocumentRoot), True, False)
+
calendarCollection = CalendarHomeProvisioningFile(
os.path.join(config.DocumentRoot, "calendars"),
aggregate, "/calendars/",
+ _newStore,
)
root.putChild("calendars", calendarCollection)
Modified: CalendarServer/branches/new-store/twistedcaldav/directory/test/test_principal.py
===================================================================
--- CalendarServer/branches/new-store/twistedcaldav/directory/test/test_principal.py 2010-06-30 21:02:35 UTC (rev 5815)
+++ CalendarServer/branches/new-store/twistedcaldav/directory/test/test_principal.py 2010-06-30 21:07:30 UTC (rev 5816)
@@ -35,6 +35,8 @@
from twistedcaldav.directory.principal import DirectoryCalendarPrincipalResource
import twistedcaldav.test.util
+from txdav.common.datastore.file import CommonDataStore
+from twisted.python.filepath import FilePath
class ProvisionedPrincipals (twistedcaldav.test.util.TestCase):
@@ -356,6 +358,9 @@
# Need to create a calendar home provisioner for each service.
calendarRootResources = {}
+ # Need a data store
+ _newStore = CommonDataStore(FilePath(self.docroot), True, False)
+
for directory in self.directoryServices:
url = "/homes_" + directory.__class__.__name__ + "/"
path = os.path.join(self.docroot, url[1:])
@@ -364,7 +369,12 @@
rmdir(path)
os.mkdir(path)
- provisioningResource = CalendarHomeProvisioningFile(path, directory, url)
+ provisioningResource = CalendarHomeProvisioningFile(
+ path,
+ directory,
+ url,
+ _newStore
+ )
calendarRootResources[directory.__class__.__name__] = provisioningResource
Modified: CalendarServer/branches/new-store/twistedcaldav/resource.py
===================================================================
--- CalendarServer/branches/new-store/twistedcaldav/resource.py 2010-06-30 21:02:35 UTC (rev 5815)
+++ CalendarServer/branches/new-store/twistedcaldav/resource.py 2010-06-30 21:07:30 UTC (rev 5816)
@@ -73,6 +73,7 @@
from twistedcaldav.sharing import SharedCollectionMixin
from twistedcaldav.vcard import Component as vComponent
+from txdav.common.icommondatastore import InternalDataStoreError
##
# Sharing Conts
@@ -995,7 +996,11 @@
an infinite loop. A subclass must override one of both of these
methods.
"""
- calendar_data = self.iCalendarText(name)
+
+ try:
+ calendar_data = self.iCalendarText(name)
+ except InternalDataStoreError:
+ return None
if calendar_data is None: return None
@@ -1087,7 +1092,10 @@
an infinite loop. A subclass must override one of both of these
methods.
"""
- vcard_data = self.vCardText(name)
+ try:
+ vcard_data = self.vCardText(name)
+ except InternalDataStoreError:
+ return None
if vcard_data is None: return None
Modified: CalendarServer/branches/new-store/twistedcaldav/schedule.py
===================================================================
--- CalendarServer/branches/new-store/twistedcaldav/schedule.py 2010-06-30 21:02:35 UTC (rev 5815)
+++ CalendarServer/branches/new-store/twistedcaldav/schedule.py 2010-06-30 21:07:30 UTC (rev 5816)
@@ -235,7 +235,7 @@
defaultCalendarURL = joinURL(calendarHomeURL, "calendar")
defaultCalendar = (yield request.locateResource(defaultCalendarURL))
if defaultCalendar is None or not defaultCalendar.exists():
- getter = iter(self.parent._newStoreCalendarHome)
+ getter = iter(self.parent._newStoreCalendarHome.calendars())
# FIXME: the back-end should re-provision a default calendar here.
# Really, the dead property shouldn't be necessary, and this should
# be entirely computed by a back-end method like 'defaultCalendar()'
Modified: CalendarServer/branches/new-store/twistedcaldav/sharing.py
===================================================================
--- CalendarServer/branches/new-store/twistedcaldav/sharing.py 2010-06-30 21:02:35 UTC (rev 5815)
+++ CalendarServer/branches/new-store/twistedcaldav/sharing.py 2010-06-30 21:07:30 UTC (rev 5816)
@@ -174,7 +174,7 @@
if self.isCalendarCollection():
home = principal.calendarHome(request)
elif self.isAddressBookCollection():
- home = principal.addressBookHome()
+ home = principal.addressBookHome(request)
else:
raise HTTPError(ErrorResponse(
responsecode.FORBIDDEN,
@@ -218,7 +218,7 @@
if self.isCalendarCollection():
shareeHome = self._shareePrincipal.calendarHome(request)
elif self.isAddressBookCollection():
- shareeHome = self._shareePrincipal.addressBookHome()
+ shareeHome = self._shareePrincipal.addressBookHome(request)
return shareeHome.removeShare(request, self._share)
@inlineCallbacks
@@ -481,7 +481,7 @@
if self.isCalendarCollection():
shareeHome = sharee.calendarHome(request)
elif self.isAddressBookCollection():
- shareeHome = sharee.addressBookHome()
+ shareeHome = sharee.addressBookHome(request)
yield shareeHome.removeShareByUID(request, record.inviteuid)
# If current user state is accepted then we send an invite with the new state, otherwise
@@ -513,11 +513,11 @@
# Locate notifications collection for user
sharee = self.principalForCalendarUserAddress(record.userid)
if sharee is None:
- raise ValueError("sharee is None but userid was valid before")
- notifications = (yield request.locateResource(sharee.notificationURL()))
+ raise ValueError("sharee is None but userid was valid before")
+ notifications = self._newStoreParentHome._transaction.notificationsWithUID(sharee.principalUID())
# Look for existing notification
- oldnotification = (yield notifications.getNotifictionMessageByUID(request, record.inviteuid))
+ oldnotification = notifications.notificationObjectWithUID(record.inviteuid)
if oldnotification:
# TODO: rollup changes?
pass
@@ -545,7 +545,7 @@
).toxml()
# Add to collections
- yield notifications.addNotification(request, record.inviteuid, xmltype, xmldata)
+ notifications.writeNotificationObject(record.inviteuid, xmltype, xmldata)
@inlineCallbacks
def removeInvite(self, record, request):
Modified: CalendarServer/branches/new-store/twistedcaldav/static.py
===================================================================
--- CalendarServer/branches/new-store/twistedcaldav/static.py 2010-06-30 21:02:35 UTC (rev 5815)
+++ CalendarServer/branches/new-store/twistedcaldav/static.py 2010-06-30 21:07:30 UTC (rev 5816)
@@ -111,9 +111,6 @@
from twistedcaldav.notifications import NotificationCollectionResource,\
NotificationResource
-from txcaldav.calendarstore.file import CalendarStore
-from txcarddav.addressbookstore.file import AddressBookStore
-
log = Logger()
class ReadOnlyResourceMixIn(object):
@@ -849,7 +846,7 @@
Resource which provisions calendar home collections as needed.
"""
- def __init__(self, path, directory, url):
+ def __init__(self, path, directory, url, store):
"""
Initialize this L{CalendarHomeProvisioningFile}.
@@ -865,8 +862,7 @@
"""
DAVFile.__init__(self, path)
DirectoryCalendarHomeProvisioningResource.__init__(self, directory, url)
- storagePath = self.fp.child(uidsResourceName)
- self._newStore = CalendarStore(storagePath)
+ self._newStore = store
def provisionChild(self, name):
@@ -1040,7 +1036,15 @@
inbox.processFreeBusyCalendar(childURL, True)
+ def sharesDB(self):
+ """
+ Retrieve the new-style shares DB wrapper.
+ """
+ if not hasattr(self, "_sharesDB"):
+ self._sharesDB = self._newStoreCalendarHome.retrieveOldShares()
+ return self._sharesDB
+
def exists(self):
# FIXME: tests
return True
@@ -1052,11 +1056,9 @@
def provision(self):
+ if config.Sharing.Enabled and config.Sharing.Calendars.Enabled and self.fp.exists():
+ self.provisionShares()
return
- result = super(CalendarHomeFile, self).provision()
- if config.Sharing.Enabled and config.Sharing.Calendars.Enabled:
- self.provisionShares()
- return result
def provisionChild(self, name):
if config.EnableDropBox:
@@ -1074,6 +1076,10 @@
else:
NotificationCollectionFileClass = None
+ # For storebridge stuff we special case this
+ if name == "notification":
+ return self.createNotificationsFile(self.fp.child(name).path)
+
from twistedcaldav.storebridge import StoreScheduleInboxFile
cls = {
"inbox" : StoreScheduleInboxFile,
@@ -1090,6 +1096,21 @@
return child
return self.createSimilarFile(self.fp.child(name).path)
+ def createNotificationsFile(self, path):
+
+ txn = self._newStoreCalendarHome._transaction
+ notifications = txn.notificationsWithUID(self._newStoreCalendarHome.uid())
+
+ from twistedcaldav.storebridge import StoreNotificationCollectionFile
+ similar = StoreNotificationCollectionFile(
+ notifications, self._newStoreCalendarHome,
+ path, self,
+ )
+ self.propagateTransaction(similar)
+ similar.clientNotifier = self.clientNotifier.clone(similar,
+ label="collection")
+ return similar
+
def createSimilarFile(self, path):
if self.comparePath(path):
@@ -1446,7 +1467,7 @@
def checkPrivileges(self, request, privileges, recurse=False, principal=None, inherited_aces=None):
return succeed(None)
-class NotificationCollectionFile(ReadOnlyResourceMixIn, AutoProvisioningFileMixIn, NotificationCollectionResource, CalDAVFile):
+class NotificationCollectionFile(ReadOnlyResourceMixIn, NotificationCollectionResource, CalDAVFile):
"""
Notification collection resource.
"""
@@ -1512,7 +1533,7 @@
"""
Resource which provisions address book home collections as needed.
"""
- def __init__(self, path, directory, url):
+ def __init__(self, path, directory, url, store):
"""
@param path: the path to the file which will back the resource.
@param directory: an L{IDirectoryService} to provision address books from.
@@ -1520,8 +1541,7 @@
"""
DAVFile.__init__(self, path)
DirectoryAddressBookHomeProvisioningResource.__init__(self, directory, url)
- storagePath = self.fp.child(uidsResourceName)
- self._newStore = AddressBookStore(storagePath)
+ self._newStore = store
def provisionChild(self, name):
@@ -1677,6 +1697,15 @@
)
+ def sharesDB(self):
+ """
+ Retrieve the new-style shares DB wrapper.
+ """
+ if not hasattr(self, "_sharesDB"):
+ self._sharesDB = self._newStoreAddressBookHome.retrieveOldShares()
+ return self._sharesDB
+
+
def exists(self):
# FIXME: tests
return True
@@ -1688,12 +1717,9 @@
def provision(self):
- return
- result = super(AddressBookHomeFile, self).provision()
if config.Sharing.Enabled and config.Sharing.AddressBooks.Enabled:
self.provisionShares()
self.provisionLinks()
- return result
def provisionLinks(self):
Modified: CalendarServer/branches/new-store/twistedcaldav/storebridge.py
===================================================================
--- CalendarServer/branches/new-store/twistedcaldav/storebridge.py 2010-06-30 21:02:35 UTC (rev 5815)
+++ CalendarServer/branches/new-store/twistedcaldav/storebridge.py 2010-06-30 21:07:30 UTC (rev 5816)
@@ -40,10 +40,12 @@
from twext.web2.dav.util import parentForURL, allDataFromStream, joinURL
from twext.web2.http import HTTPError, StatusResponse
-from twistedcaldav.static import CalDAVFile, ScheduleInboxFile
+from twistedcaldav.static import CalDAVFile, ScheduleInboxFile,\
+ NotificationCollectionFile, NotificationFile
from twistedcaldav.vcard import Component as VCard
-from txdav.common.icommondatastore import NoSuchObjectResourceError
+from txdav.common.icommondatastore import NoSuchObjectResourceError,\
+ InternalDataStoreError
from txdav.propertystore.base import PropertyName
from twistedcaldav.caldavxml import ScheduleTag, caldav_namespace
@@ -148,6 +150,14 @@
return self._newStoreCalendar.retrieveOldIndex()
+ def invitesDB(self):
+ """
+ Retrieve the new-style invites DB wrapper.
+ """
+ if not hasattr(self, "_invitesDB"):
+ self._invitesDB = self._newStoreCalendar.retrieveOldInvites()
+ return self._invitesDB
+
def exists(self):
# FIXME: tests
return True
@@ -555,7 +565,7 @@
hashlib.new("md5", self.iCalendarText()).hexdigest(),
weak=False
)
- except NoSuchObjectResourceError:
+ except (NoSuchObjectResourceError, InternalDataStoreError):
# FIXME: a workaround for the fact that DELETE still rudely vanishes
# the calendar object out from underneath the store, and doesn't
# call storeRemove.
@@ -800,6 +810,14 @@
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
@@ -1143,7 +1161,7 @@
hashlib.new("md5", self.vCardText()).hexdigest(),
weak=False
)
- except NoSuchObjectResourceError:
+ except (NoSuchObjectResourceError, InternalDataStoreError):
# FIXME: a workaround for the fact that DELETE still rudely vanishes
# the addressbook object out from underneath the store, and doesn't
# call storeRemove.
@@ -1273,3 +1291,265 @@
def quotaSize(self, request):
# FIXME: tests, workingness
return succeed(0)
+
+
+class _NotificationChildHelper(object):
+ """
+ Methods for things which are like notification objects.
+ """
+
+ def _initializeWithNotifications(self, notifications, home):
+ """
+ Initialize with a notification collection.
+
+ @param notifications: the wrapped http://daboo.name/groups/daboofamily/wiki/c4c42/6585_Sale.html.
+ @type notifications: L{txdav.common.inotification.INotificationCollection}
+
+ @param home: the home through which the given notification collection was accessed.
+ @type home: L{txdav.icommonstore.ICommonHome}
+ """
+ self._newStoreNotifications = notifications
+ self._newStoreParentHome = home
+ self._dead_properties = _NewStorePropertiesWrapper(
+ self._newStoreNotifications.properties()
+ )
+
+
+ def notificationsDB(self):
+ """
+ Retrieve the new-style index wrapper.
+ """
+ return self._newStoreNotifications.retrieveOldIndex()
+
+
+ def exists(self):
+ # FIXME: tests
+ return True
+
+
+ @classmethod
+ def transform(cls, self, notifications, home):
+ """
+ Transform C{self} into a L{NotificationCollectionFile}.
+ """
+ self.__class__ = cls
+ self._initializeWithNotifications(notifications, home)
+
+
+ def createSimilarFile(self, path):
+ """
+ Create a L{NotificationObjectFile} or L{ProtoNotificationObjectFile} based on a
+ path object.
+ """
+ if not isinstance(path, FilePath):
+ path = FilePath(path)
+
+ newStoreObject = self._newStoreNotifications.notificationObjectWithName(
+ path.basename()
+ )
+
+ if newStoreObject is not None:
+ similar = StoreNotificationObjectFile(newStoreObject, path, self)
+ else:
+ # FIXME: creation in http_PUT should talk to a specific resource
+ # type; this is the domain of StoreCalendarObjectResource.
+ # similar = ProtoCalendarObjectFile(self._newStoreCalendar, path)
+ similar = ProtoStoreNotificationObjectFile(self._newStoreNotifications, path, self)
+
+ # FIXME: tests should be failing without this line.
+ # Specifically, http_PUT won't be committing its transaction properly.
+ self.propagateTransaction(similar)
+ return similar
+
+
+ def quotaSize(self, request):
+ # FIXME: tests, workingness
+ return succeed(0)
+
+
+
+class StoreNotificationCollectionFile(_NotificationChildHelper, NotificationCollectionFile):
+ """
+ Wrapper around a L{txcaldav.icalendar.ICalendar}.
+ """
+
+ def __init__(self, notifications, home, *args, **kw):
+ """
+ Create a CalendarCollectionFile from a L{txcaldav.icalendar.ICalendar}
+ and the arguments required for L{CalDAVFile}.
+ """
+ super(StoreNotificationCollectionFile, self).__init__(*args, **kw)
+ self._initializeWithNotifications(notifications, home)
+
+
+ def isCollection(self):
+ return True
+
+ @inlineCallbacks
+ def http_DELETE(self, request):
+ """
+ Override http_DELETE to reject.
+ """
+
+ raise HTTPError(StatusResponse(FORBIDDEN, "Cannot delete notification collections"))
+
+
+ def http_COPY(self, request):
+ """
+ Copying of calendar collections isn't allowed.
+ """
+ raise HTTPError(StatusResponse(FORBIDDEN, "Cannot copy notification collections"))
+
+
+ @inlineCallbacks
+ def http_MOVE(self, request):
+ """
+ Moving a calendar collection is allowed for the purposes of changing
+ that calendar's name.
+ """
+ raise HTTPError(StatusResponse(FORBIDDEN, "Cannot move notification collections"))
+
+class StoreNotificationObjectFile(NotificationFile):
+ """
+ A resource wrapping a calendar object.
+ """
+
+ def __init__(self, notificationObject, *args, **kw):
+ """
+ Construct a L{CalendarObjectFile} from an L{ICalendarObject}.
+
+ @param calendarObject: The storage for the calendar object.
+ @type calendarObject: L{txcaldav.icalendarstore.ICalendarObject}
+ """
+ super(StoreNotificationObjectFile, self).__init__(*args, **kw)
+ self._initializeWithObject(notificationObject)
+
+
+ def isCollection(self):
+ return False
+
+
+ def exists(self):
+ # FIXME: Tests
+ return True
+
+
+ def etag(self):
+ # FIXME: far too slow to be used for real, but I needed something to
+ # placate the etag computation in the case where the file doesn't exist
+ # yet (an uncommited transaction creating this calendar file)
+
+ # FIXME: direct tests
+ try:
+ if self.hasDeadProperty(TwistedGETContentMD5):
+ return ETag(str(self.readDeadProperty(TwistedGETContentMD5)))
+ else:
+ return ETag(
+ hashlib.new("md5", self.text()).hexdigest(),
+ weak=False
+ )
+ except NoSuchObjectResourceError:
+ # FIXME: a workaround for the fact that DELETE still rudely vanishes
+ # the calendar object out from underneath the store, and doesn't
+ # call storeRemove.
+ return None
+
+
+ def newStoreProperties(self):
+ return self._newStoreObject.properties()
+
+
+ def quotaSize(self, request):
+ # FIXME: tests
+ return succeed(len(self._newStoreObject.xmldata()))
+
+
+ def text(self, ignored=None):
+ assert ignored is None, "This is a notification object, not a notification"
+ return self._newStoreObject.xmldata()
+
+
+ @inlineCallbacks
+ def http_DELETE(self, request):
+ """
+ Override http_DELETE to validate 'depth' header.
+ """
+
+ #
+ # Check authentication and access controls
+ #
+ parentURL = parentForURL(request.uri)
+ parent = (yield request.locateResource(parentURL))
+
+ yield parent.authorize(request, (davxml.Unbind(),))
+
+ response = (yield self.storeRemove(request, request.uri))
+ returnValue(response)
+
+ @inlineCallbacks
+ def storeRemove(self, request, where):
+ """
+ Remove this notification object.
+ """
+ # 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
+
+ try:
+
+ storeNotifications = self._newStoreObject._notificationCollection
+
+ # Do delete
+
+ # FIXME: public attribute please
+ storeNotifications.removeNotificationObjectWithName(self._newStoreObject.name())
+
+ # FIXME: clean this up with a 'transform' method
+ self._newStoreParentNotifications = storeNotifications
+ del self._newStoreObject
+ self.__class__ = ProtoStoreNotificationObjectFile
+
+ # Adjust quota
+ if myquota is not None:
+ yield self.quotaSizeAdjust(request, -old_size)
+
+ except MemcacheLockTimeoutError:
+ raise HTTPError(StatusResponse(CONFLICT, "Resource: %s currently in use on the server." % (where,)))
+
+ returnValue(NO_CONTENT)
+
+ def _initializeWithObject(self, notificationObject):
+ self._newStoreObject = notificationObject
+ self._dead_properties = _NewStorePropertiesWrapper(
+ self._newStoreObject.properties()
+ )
+
+
+ @classmethod
+ def transform(cls, self, notificationObject):
+ self.__class__ = cls
+ self._initializeWithObject(notificationObject)
+
+
+
+class ProtoStoreNotificationObjectFile(NotificationFile):
+
+ def __init__(self, parentNotifications, *a, **kw):
+ super(ProtoStoreNotificationObjectFile, self).__init__(*a, **kw)
+ self._newStoreParentNotifications = parentNotifications
+
+ def isCollection(self):
+ return False
+
+ def exists(self):
+ # FIXME: tests
+ return False
+
+
+ def quotaSize(self, request):
+ # FIXME: tests, workingness
+ return succeed(0)
+
Modified: CalendarServer/branches/new-store/twistedcaldav/test/test_addressbookmultiget.py
===================================================================
--- CalendarServer/branches/new-store/twistedcaldav/test/test_addressbookmultiget.py 2010-06-30 21:02:35 UTC (rev 5815)
+++ CalendarServer/branches/new-store/twistedcaldav/test/test_addressbookmultiget.py 2010-06-30 21:07:30 UTC (rev 5816)
@@ -25,13 +25,13 @@
from twext.web2.dav.util import davXMLFromStream
from twext.web2.test.test_server import SimpleRequest
-import twistedcaldav.test.util
from twistedcaldav import carddavxml
from twistedcaldav import vcard
from twistedcaldav.index import db_basename
from twistedcaldav.config import config
+from twistedcaldav.test.util import AddressBookHomeTestCase
-class AddressBookMultiget (twistedcaldav.test.util.TestCase):
+class AddressBookMultiget (AddressBookHomeTestCase):
"""
addressbook-multiget REPORT
"""
Modified: CalendarServer/branches/new-store/twistedcaldav/test/test_addressbookquery.py
===================================================================
--- CalendarServer/branches/new-store/twistedcaldav/test/test_addressbookquery.py 2010-06-30 21:02:35 UTC (rev 5815)
+++ CalendarServer/branches/new-store/twistedcaldav/test/test_addressbookquery.py 2010-06-30 21:07:30 UTC (rev 5816)
@@ -25,12 +25,12 @@
from twext.web2.dav.util import davXMLFromStream
from twext.web2.test.test_server import SimpleRequest
-import twistedcaldav.test.util
from twistedcaldav import carddavxml, vcard
from twistedcaldav.index import db_basename
from twistedcaldav.config import config
+from twistedcaldav.test.util import AddressBookHomeTestCase
-class AddressBookQuery (twistedcaldav.test.util.TestCase):
+class AddressBookQuery (AddressBookHomeTestCase):
"""
addressbook-query REPORT
"""
Modified: CalendarServer/branches/new-store/twistedcaldav/test/test_mkcalendar.py
===================================================================
--- CalendarServer/branches/new-store/twistedcaldav/test/test_mkcalendar.py 2010-06-30 21:02:35 UTC (rev 5815)
+++ CalendarServer/branches/new-store/twistedcaldav/test/test_mkcalendar.py 2010-06-30 21:07:30 UTC (rev 5816)
@@ -41,7 +41,7 @@
"""
Make calendar
"""
- uri = "/calendar_make"
+ uri = "/calendar_make/"
path = os.path.join(self.docroot, uri[1:])
if os.path.exists(path):
@@ -71,7 +71,7 @@
"""
Make calendar with properties (CalDAV-access-09, section 5.3.1.2)
"""
- uri = "/calendar_prop"
+ uri = "/calendar_prop/"
path = os.path.join(self.docroot, uri[1:])
if os.path.exists(path):
@@ -156,7 +156,7 @@
"""
Make calendar with no parent
"""
- uri = "/no/parent/for/calendar"
+ uri = "/no/parent/for/calendar/"
def do_test(response):
response = IResponse(response)
@@ -173,16 +173,37 @@
"""
Make calendar on existing resource
"""
- uri = "/calendar_on_resource"
+ uri = "/calendar_on_resource/"
path = os.path.join(self.docroot, uri[1:])
if not os.path.exists(path):
- f = open(path, "w")
+ f = open(path[:-1], 'w')
f.close()
def do_test(response):
response = IResponse(response)
+ if response.code != responsecode.NOT_ALLOWED:
+ self.fail("Incorrect response to MKCALENDAR on existing resource: %s" % (response.code,))
+
+ # FIXME: Check for DAV:resource-must-be-null element
+
+ request = SimpleRequest(self.site, "MKCALENDAR", uri)
+ return self.send(request, do_test)
+
+ def test_make_calendar_on_collection(self):
+ """
+ Make calendar on existing collection
+ """
+ uri = "/calendar_on_resource/"
+ path = os.path.join(self.docroot, uri[1:])
+
+ if not os.path.exists(path):
+ os.mkdir(path)
+
+ def do_test(response):
+ response = IResponse(response)
+
if response.code != responsecode.FORBIDDEN:
self.fail("Incorrect response to MKCALENDAR on existing resource: %s" % (response.code,))
@@ -195,7 +216,7 @@
"""
Make calendar in calendar
"""
- first_uri = "/calendar_in_calendar"
+ first_uri = "/calendar_in_calendar/"
first_path = os.path.join(self.docroot, first_uri[1:])
if os.path.exists(first_path): rmdir(first_path)
Modified: CalendarServer/branches/new-store/twistedcaldav/test/test_multiget.py
===================================================================
--- CalendarServer/branches/new-store/twistedcaldav/test/test_multiget.py 2010-06-30 21:02:35 UTC (rev 5815)
+++ CalendarServer/branches/new-store/twistedcaldav/test/test_multiget.py 2010-06-30 21:07:30 UTC (rev 5816)
@@ -127,7 +127,7 @@
SUMMARY:New Year's Day
END:VEVENT
END:VCALENDAR
-""",
+""".replace("\n", "\r\n"),
"bad":"""BEGIN:VCALENDAR
CALSCALE:GREGORIAN
PRODID:-//Apple Computer\, Inc//iCal 2.0//EN
@@ -140,7 +140,7 @@
SUMMARY:Valentine's Day
END:VEVENT
END:VCALENDAR
-"""
+""".replace("\n", "\r\n")
}
yield self.simple_event_multiget("/calendar_multiget_events/", okuids, baduids, data)
@@ -155,7 +155,7 @@
BEGIN:VEVENT
UID:bad
DTSTART;VALUE=DATE:20020214
-DTEND;VALUE=DATE:20020""")
+DTEND;VALUE=DATE:20020""".replace("\n", "\r\n"))
f.close
okuids = ["good", ]
Modified: CalendarServer/branches/new-store/twistedcaldav/test/test_schedule.py
===================================================================
--- CalendarServer/branches/new-store/twistedcaldav/test/test_schedule.py 2010-06-30 21:02:35 UTC (rev 5815)
+++ CalendarServer/branches/new-store/twistedcaldav/test/test_schedule.py 2010-06-30 21:07:30 UTC (rev 5816)
@@ -26,20 +26,20 @@
from twistedcaldav import caldavxml
from twistedcaldav.static import ScheduleInboxFile
-import twistedcaldav.test.util
+from twistedcaldav.test.util import HomeTestCase
-class Properties (twistedcaldav.test.util.TestCase):
+class Properties (HomeTestCase):
"""
CalDAV properties
"""
- def test_missing_free_busy_set_prop(self):
+ def test_free_busy_set_prop(self):
"""
Test for PROPFIND on Inbox with missing calendar-free-busy-set property.
"""
inbox_uri = "/inbox/"
- inbox_path = os.path.join(self.docroot, "inbox")
- self.site.resource.putChild("inbox", ScheduleInboxFile(inbox_path, self.site.resource))
+ #inbox_path = os.path.join(self.docroot, "inbox")
+ #self.site.resource.putChild("inbox", ScheduleInboxFile(inbox_path, self.site.resource))
def propfind_cb(response):
response = IResponse(response)
@@ -71,8 +71,8 @@
if not free_busy_set:
self.fail("Expected CalDAV:calendar-free-busy-set element; but got none.")
- if free_busy_set.children:
- self.fail("Expected empty CalDAV:calendar-free-busy-set element; but got %s." % (free_busy_set.children,))
+ if not free_busy_set.children:
+ self.fail("Expected non-empty CalDAV:calendar-free-busy-set element.")
return davXMLFromStream(response.stream).addCallback(got_xml)
Modified: CalendarServer/branches/new-store/twistedcaldav/test/test_wrapping.py
===================================================================
--- CalendarServer/branches/new-store/twistedcaldav/test/test_wrapping.py 2010-06-30 21:02:35 UTC (rev 5815)
+++ CalendarServer/branches/new-store/twistedcaldav/test/test_wrapping.py 2010-06-30 21:07:30 UTC (rev 5816)
@@ -20,16 +20,19 @@
from twisted.internet.defer import inlineCallbacks, returnValue
-from twistedcaldav.directory.calendar import uidsResourceName
from twistedcaldav.ical import Component as VComponent
+from twistedcaldav.vcard import Component as VCComponent
-from twistedcaldav.storebridge import ProtoCalendarCollectionFile
+from twistedcaldav.storebridge import ProtoCalendarCollectionFile,\
+ ProtoAddressBookCollectionFile
from twistedcaldav.test.util import TestCase
from txcaldav.calendarstore.file import CalendarStore, CalendarHome
from txcaldav.calendarstore.test.test_file import event4_text
+from txcarddav.addressbookstore.file import AddressBookStore, AddressBookHome
+from txcarddav.addressbookstore.test.test_file import vcard4_text
class WrappingTests(TestCase):
@@ -78,6 +81,33 @@
txn.commit()
+ def populateOneAddressBookObject(self, objectName, objectText):
+ """
+ Populate one addressbook object in the test user's addressbook.
+
+ @param objectName: The name of a addressbook object.
+ @type objectName: str
+ @param objectText: Some iVcard text to populate it with.
+ @type objectText: str
+ """
+ record = self.directoryService.recordWithShortName("users", "wsanchez")
+ uid = record.uid
+ # XXX there should be a more test-friendly way to ensure the directory
+ # actually exists
+ try:
+ self.addressbookCollection._newStore._path.createDirectory()
+ except:
+ pass
+ txn = self.addressbookCollection._newStore.newTransaction()
+ home = txn.addressbookHomeWithUID(uid, True)
+ adbk = home.addressbookWithName("addressbook")
+ if adbk is None:
+ home.createAddressBookWithName("addressbook")
+ adbk = home.addressbookWithName("addressbook")
+ adbk.createAddressBookObjectWithName(objectName, VCComponent.fromString(objectText))
+ txn.commit()
+
+
@inlineCallbacks
def getResource(self, path):
"""
@@ -111,7 +141,7 @@
"""
self.assertIsInstance(self.calendarCollection._newStore, CalendarStore)
self.assertEquals(self.calendarCollection._newStore._path,
- self.calendarCollection.fp.child(uidsResourceName))
+ self.site.resource.fp)
@inlineCallbacks
@@ -203,3 +233,86 @@
self.commit()
self.assertEquals(calDavFileCalendar._principalCollections,
frozenset([self.principalsResource]))
+
+ def test_createAddressBookStore(self):
+ """
+ Creating a AddressBookHomeProvisioningFile will create a paired
+ AddressBookStore.
+ """
+ self.assertIsInstance(self.addressbookCollection._newStore, AddressBookStore)
+ self.assertEquals(self.addressbookCollection._newStore._path,
+ self.site.resource.fp)
+
+
+ @inlineCallbacks
+ def test_lookupAddressBookHome(self):
+ """
+ When a L{CalDAVFile} representing an existing addressbook home is looked up
+ in a AddressBookHomeFile, it will create a corresponding L{AddressBookHome}
+ via C{newTransaction().addressbookHomeWithUID}.
+ """
+ calDavFile = yield self.getResource("addressbooks/users/wsanchez/")
+ self.commit()
+ self.assertEquals(calDavFile.fp, calDavFile._newStoreAddressBookHome._path)
+ self.assertIsInstance(calDavFile._newStoreAddressBookHome, AddressBookHome)
+
+
+ @inlineCallbacks
+ def test_lookupExistingAddressBook(self):
+ """
+ When a L{CalDAVFile} representing an existing addressbook collection is
+ looked up in a L{AddressBookHomeFile} representing a addressbook home, it will
+ create a corresponding L{AddressBook} via C{AddressBookHome.addressbookWithName}.
+ """
+ calDavFile = yield self.getResource("addressbooks/users/wsanchez/addressbook")
+ self.commit()
+ self.assertEquals(calDavFile.fp, calDavFile._newStoreAddressBook._path)
+
+
+ @inlineCallbacks
+ def test_lookupNewAddressBook(self):
+ """
+ When a L{CalDAVFile} which represents a not-yet-created addressbook
+ collection is looked up in a L{AddressBookHomeFile} representing a addressbook
+ home, it will initially have a new storage backend set to C{None}, but
+ when the addressbook is created via a protocol action, the backend will be
+ initialized to match.
+ """
+ calDavFile = yield self.getResource("addressbooks/users/wsanchez/frobozz")
+ self.assertIsInstance(calDavFile, ProtoAddressBookCollectionFile)
+ calDavFile.createAddressBookCollection()
+ self.commit()
+ self.assertEquals(calDavFile.fp, calDavFile._newStoreAddressBook._path)
+
+
+ @inlineCallbacks
+ def test_lookupAddressBookObject(self):
+ """
+ When a L{CalDAVFile} representing an existing addressbook object is looked
+ up on a L{CalDAVFile} representing a addressbook collection, a parallel
+ L{AddressBookObject} will be created (with a matching FilePath).
+ """
+ self.populateOneAddressBookObject("1.vcf", vcard4_text)
+ calDavFileAddressBook = yield self.getResource(
+ "addressbooks/users/wsanchez/addressbook/1.vcf"
+ )
+ self.commit()
+ self.assertEquals(calDavFileAddressBook._newStoreObject._path,
+ calDavFileAddressBook.fp)
+ self.assertEquals(calDavFileAddressBook._principalCollections,
+ frozenset([self.principalsResource]))
+
+
+ @inlineCallbacks
+ def test_lookupNewAddressBookObject(self):
+ """
+ When a L{CalDAVFile} representing a new addressbook object on a
+ L{CalDAVFile} representing an existing addressbook collection, the list of
+ principal collections will be propagated down to it.
+ """
+ calDavFileAddressBook = yield self.getResource(
+ "addressbooks/users/wsanchez/addressbook/xyzzy.ics"
+ )
+ self.commit()
+ self.assertEquals(calDavFileAddressBook._principalCollections,
+ frozenset([self.principalsResource]))
Modified: CalendarServer/branches/new-store/twistedcaldav/test/util.py
===================================================================
--- CalendarServer/branches/new-store/twistedcaldav/test/util.py 2010-06-30 21:02:35 UTC (rev 5815)
+++ CalendarServer/branches/new-store/twistedcaldav/test/util.py 2010-06-30 21:07:30 UTC (rev 5816)
@@ -15,7 +15,6 @@
##
from __future__ import with_statement
-from twext.web2.dav import davxml
__all__ = [
"featureUnimplemented",
@@ -29,23 +28,27 @@
from twisted.python.failure import Failure
from twisted.internet.base import DelayedCall
-from twisted.internet.defer import succeed, fail
+from twisted.internet.defer import succeed, fail, inlineCallbacks, returnValue
from twisted.internet.error import ProcessDone
from twisted.internet.protocol import ProcessProtocol
from twext.python.memcacheclient import ClientFactory
from twext.python.filepath import CachingFilePath as FilePath
import twext.web2.dav.test.util
+from twext.web2.dav import davxml
from twext.web2.http import HTTPError, StatusResponse
from twistedcaldav import memcacher
from twistedcaldav.config import config
-from twistedcaldav.static import CalDAVFile, CalendarHomeProvisioningFile
+from twistedcaldav.static import CalDAVFile, CalendarHomeProvisioningFile,\
+ AddressBookHomeProvisioningFile
from twistedcaldav.directory.xmlfile import XMLDirectoryService
from twistedcaldav.directory import augment
from twistedcaldav.directory.principal import (
DirectoryPrincipalProvisioningResource)
+from txdav.common.datastore.file import CommonDataStore
+
DelayedCall.debug = True
def _todo(f, why):
@@ -98,14 +101,30 @@
"""
path = self.site.resource.fp.child("calendars")
path.createDirectory()
+
+ # Need a data store
+ _newStore = CommonDataStore(self.site.resource.fp, True, False)
+
self.calendarCollection = CalendarHomeProvisioningFile(
path,
self.directoryService,
- "/calendars/"
+ "/calendars/",
+ _newStore
)
self.site.resource.putChild("calendars", self.calendarCollection)
+ path = self.site.resource.fp.child("addressbooks")
+ path.createDirectory()
+ self.addressbookCollection = AddressBookHomeProvisioningFile(
+ path,
+ self.directoryService,
+ "/addressbooks/",
+ _newStore
+ )
+ self.site.resource.putChild("addressbooks", self.addressbookCollection)
+
+
def setUp(self):
super(TestCase, self).setUp()
@@ -283,12 +302,89 @@
self.createStockDirectoryService()
+ # Need a data store
+ _newStore = CommonDataStore(fp, True, False)
+
self.homeProvisioner = CalendarHomeProvisioningFile(
- fp, self.directoryService, "/"
+ os.path.join(fp.path, "calendars"),
+ self.directoryService, "/calendars/",
+ _newStore
)
- self._refreshRoot()
+
+ def _defer(_):
+ # Commit the transaction
+ self.site.resource._associatedTransaction.commit()
+
+ return self._refreshRoot().addCallback(_defer)
+ @inlineCallbacks
+ def _refreshRoot(self):
+ """
+ Refresh the user resource positioned at the root of this site, to give
+ it a new transaction.
+ """
+ users = self.homeProvisioner.getChild("users")
+ class norequest(object): pass
+ user, ignored = (yield users.locateChild(norequest(), ["wsanchez"]))
+ # Force the request to succeed regardless of the implementation of
+ # accessControlList.
+ user.accessControlList = lambda request, *a, **k: succeed(
+ self.grantInherit(davxml.All())
+ )
+
+ # Fix the site to point directly at the user's calendar home so that we
+ # can focus on testing just that rather than hierarchy traversal..
+ self.site.resource = user
+
+ # Fix the docroot so that 'mkdtemp' will create directories in the right
+ # place (beneath the calendar).
+ self.docroot = user.fp.path
+
+ @inlineCallbacks
+ def send(self, request, callback):
+ """
+ Override C{send} in order to refresh the 'user' resource each time, to
+ get a new transaction to associate with the calendar home.
+ """
+ yield self._refreshRoot()
+ result = (yield super(HomeTestCase, self).send(request, callback))
+ returnValue(result)
+
+class AddressBookHomeTestCase(TestCase):
+ """
+ Utility class for tests which wish to interact with a addressbook home rather
+ than a top-level resource hierarchy.
+ """
+
+ def setUp(self):
+ """
+ Replace self.site.resource with an appropriately provisioned
+ AddressBookHomeFile, and replace self.docroot with a path pointing at that
+ file.
+ """
+ super(AddressBookHomeTestCase, self).setUp()
+
+ fp = FilePath(self.mktemp())
+
+ self.createStockDirectoryService()
+
+ # Need a data store
+ _newStore = CommonDataStore(fp, True, False)
+
+ self.homeProvisioner = AddressBookHomeProvisioningFile(
+ os.path.join(fp.path, "addressbooks"),
+ self.directoryService, "/addressbooks/",
+ _newStore
+ )
+
+ def _defer(_):
+ # Commit the transaction
+ self.site.resource._associatedTransaction.commit()
+
+ return self._refreshRoot().addCallback(_defer)
+
+ @inlineCallbacks
def _refreshRoot(self):
"""
Refresh the user resource positioned at the root of this site, to give
@@ -296,7 +392,7 @@
"""
users = self.homeProvisioner.getChild("users")
class norequest(object): pass
- user, ignored = users.locateChild(norequest(), ["wsanchez"])
+ user, ignored = (yield users.locateChild(norequest(), ["wsanchez"]))
# Force the request to succeed regardless of the implementation of
# accessControlList.
@@ -312,14 +408,15 @@
# place (beneath the calendar).
self.docroot = user.fp.path
-
+ @inlineCallbacks
def send(self, request, callback):
"""
Override C{send} in order to refresh the 'user' resource each time, to
get a new transaction to associate with the calendar home.
"""
- self._refreshRoot()
- return super(HomeTestCase, self).send(request, callback)
+ yield self._refreshRoot()
+ result = (yield super(AddressBookHomeTestCase, self).send(request, callback))
+ returnValue(result)
Modified: CalendarServer/branches/new-store/txcaldav/calendarstore/file.py
===================================================================
--- CalendarServer/branches/new-store/txcaldav/calendarstore/file.py 2010-06-30 21:02:35 UTC (rev 5815)
+++ CalendarServer/branches/new-store/txcaldav/calendarstore/file.py 2010-06-30 21:07:30 UTC (rev 5816)
@@ -21,6 +21,7 @@
__all__ = [
"CalendarStore",
+ "CalendarStoreTransaction",
"CalendarHome",
"Calendar",
"CalendarObject",
@@ -34,10 +35,10 @@
from twistedcaldav.caldavxml import ScheduleCalendarTransp, Opaque
from twistedcaldav.index import Index as OldIndex, IndexSchedule as OldInboxIndex
+from twistedcaldav.sharing import InvitesDatabase
from txcaldav.icalendarstore import ICalendar, ICalendarObject
-from txcaldav.icalendarstore import ICalendarStore, ICalendarHome
-from txcaldav.icalendarstore import ICalendarStoreTransaction
+from txcaldav.icalendarstore import ICalendarHome
from txdav.common.datastore.file import CommonDataStore, CommonStoreTransaction,\
CommonHome, CommonHomeChild, CommonObjectResource
@@ -48,64 +49,15 @@
from zope.interface import implements
-class CalendarStore(CommonDataStore):
- """
- An implementation of L{ICalendarObject} backed by a
- L{twext.python.filepath.CachingFilePath}.
+CalendarStore = CommonDataStore
- @ivar _path: A L{CachingFilePath} referencing a directory on disk that
- stores all calendar data for a group of uids.
- """
- implements(ICalendarStore)
+CalendarStoreTransaction = CommonStoreTransaction
- def __init__(self, path):
- """
- Create a calendar store.
-
- @param path: a L{FilePath} pointing at a directory on disk.
- """
- super(CalendarStore, self).__init__(path)
- self._transactionClass = CalendarStoreTransaction
-
-
-class CalendarStoreTransaction(CommonStoreTransaction):
- """
- In-memory implementation of
-
- Note that this provides basic 'undo' support, but not truly transactional
- operations.
- """
-
- implements(ICalendarStoreTransaction)
-
- def __init__(self, calendarStore):
- """
- Initialize a transaction; do not call this directly, instead call
- L{CalendarStore.newTransaction}.
-
- @param calendarStore: The store that created this transaction.
-
- @type calendarStore: L{CalendarStore}
- """
- super(CalendarStoreTransaction, self).__init__(calendarStore)
- self._homeClass = CalendarHome
-
- calendarHomeWithUID = CommonStoreTransaction.homeWithUID
-
- def creatingHome(self, home):
- home.createCalendarWithName("calendar")
- defaultCal = home.calendarWithName("calendar")
- props = defaultCal.properties()
- props[PropertyName(*ScheduleCalendarTransp.qname())] = ScheduleCalendarTransp(
- Opaque())
- home.createCalendarWithName("inbox")
-
-
class CalendarHome(CommonHome):
implements(ICalendarHome)
- def __init__(self, path, calendarStore, transaction):
- super(CalendarHome, self).__init__(path, calendarStore, transaction)
+ def __init__(self, uid, path, calendarStore, transaction):
+ super(CalendarHome, self).__init__(uid, path, calendarStore, transaction)
self._childClass = Calendar
@@ -118,6 +70,13 @@
def _calendarStore(self):
return self._dataStore
+ def created(self):
+ self.createCalendarWithName("calendar")
+ defaultCal = self.calendarWithName("calendar")
+ props = defaultCal.properties()
+ props[PropertyName(*ScheduleCalendarTransp.qname())] = ScheduleCalendarTransp(
+ Opaque())
+ self.createCalendarWithName("inbox")
class Calendar(CommonHomeChild):
"""
@@ -144,6 +103,7 @@
super(Calendar, self).__init__(name, calendarHome, realName)
self._index = Index(self)
+ self._invites = Invites(self)
self._objectResourceClass = CalendarObject
@property
@@ -308,49 +268,48 @@
return self.component().getOrganizer()
-class Index(object):
- #
- # OK, here's where we get ugly.
- # The index code needs to be rewritten also, but in the meantime...
- #
- class StubResource(object):
- """
- Just enough resource to keep the Index class going.
- """
- def __init__(self, calendar):
- self.calendar = calendar
- self.fp = self.calendar._path
+class CalendarStubResource(object):
+ """
+ Just enough resource to keep the calendar's sql DB classes going.
+ """
+ def __init__(self, calendar):
+ self.calendar = calendar
+ self.fp = self.calendar._path
- def isCalendarCollection(self):
- return True
+ def isCalendarCollection(self):
+ return True
- def getChild(self, name):
- calendarObject = self.calendar.calendarObjectWithName(name)
- if calendarObject:
- class ChildResource(object):
- def __init__(self, calendarObject):
- self.calendarObject = calendarObject
+ def getChild(self, name):
+ calendarObject = self.calendar.calendarObjectWithName(name)
+ if calendarObject:
+ class ChildResource(object):
+ def __init__(self, calendarObject):
+ self.calendarObject = calendarObject
- def iCalendar(self):
- return self.calendarObject.component()
+ def iCalendar(self):
+ return self.calendarObject.component()
- return ChildResource(calendarObject)
- else:
- return None
+ return ChildResource(calendarObject)
+ else:
+ return None
- def bumpSyncToken(self, reset=False):
- # FIXME: needs direct tests
- return self.calendar._updateSyncToken(reset)
+ def bumpSyncToken(self, reset=False):
+ # FIXME: needs direct tests
+ return self.calendar._updateSyncToken(reset)
- def initSyncToken(self):
- # FIXME: needs direct tests
- self.bumpSyncToken(True)
+ def initSyncToken(self):
+ # FIXME: needs direct tests
+ self.bumpSyncToken(True)
-
+class Index(object):
+ #
+ # OK, here's where we get ugly.
+ # The index code needs to be rewritten also, but in the meantime...
+ #
def __init__(self, calendar):
self.calendar = calendar
- stubResource = Index.StubResource(calendar)
+ stubResource = CalendarStubResource(calendar)
if self.calendar.name() == 'inbox':
indexClass = OldInboxIndex
else:
@@ -368,3 +327,14 @@
calendarObject._componentType = componentType
yield calendarObject
+
+
+class Invites(object):
+ #
+ # OK, here's where we get ugly.
+ # The index code needs to be rewritten also, but in the meantime...
+ #
+ def __init__(self, calendar):
+ self.calendar = calendar
+ stubResource = CalendarStubResource(calendar)
+ self._oldInvites = InvitesDatabase(stubResource)
Modified: CalendarServer/branches/new-store/txcaldav/calendarstore/test/common.py
===================================================================
--- CalendarServer/branches/new-store/txcaldav/calendarstore/test/common.py 2010-06-30 21:02:35 UTC (rev 5815)
+++ CalendarServer/branches/new-store/txcaldav/calendarstore/test/common.py 2010-06-30 21:07:30 UTC (rev 5816)
@@ -25,14 +25,15 @@
from txdav.idav import IPropertyStore
from txdav.propertystore.base import PropertyName
-from txdav.common.icommondatastore import HomeChildNameAlreadyExistsError
+from txdav.common.icommondatastore import HomeChildNameAlreadyExistsError,\
+ ICommonDataStore, ICommonStoreTransaction
from txdav.common.icommondatastore import InvalidObjectResourceError
from txdav.common.icommondatastore import NoSuchHomeChildError
from txdav.common.icommondatastore import NoSuchObjectResourceError
from txdav.common.icommondatastore import ObjectResourceNameAlreadyExistsError
from txcaldav.icalendarstore import (
- ICalendarStore, ICalendarStoreTransaction, ICalendarObject, ICalendarHome,
+ ICalendarObject, ICalendarHome,
ICalendar)
from twext.python.filepath import CachingFilePath as FilePath
@@ -158,7 +159,7 @@
def storeUnderTest(self):
"""
- Subclasses must override this to return an L{ICalendarStore} provider
+ Subclasses must override this to return an L{ICommonDataStore} provider
which adheres to the structure detailed by L{CommonTests.requirements}.
This attribute is a dict of dict of dicts; the outermost layer
representing UIDs mapping to calendar homes, then calendar names mapping
@@ -244,19 +245,19 @@
def test_calendarStoreProvides(self):
"""
- The calendar store provides L{ICalendarStore} and its required
+ The calendar store provides L{ICommonDataStore} and its required
attributes.
"""
calendarStore = self.storeUnderTest()
- self.assertProvides(ICalendarStore, calendarStore)
+ self.assertProvides(ICommonDataStore, calendarStore)
def test_transactionProvides(self):
"""
The transactions generated by the calendar store provide
- L{ICalendarStoreTransaction} and its required attributes.
+ L{ICommonStoreTransaction} and its required attributes.
"""
- self.assertProvides(ICalendarStoreTransaction,
+ self.assertProvides(ICommonStoreTransaction,
self.storeUnderTest().newTransaction())
@@ -299,7 +300,7 @@
def test_calendarHomeWithUID_absent(self):
"""
- L{ICalendarStoreTransaction.calendarHomeWithUID} should return C{None}
+ L{ICommonStoreTransaction.calendarHomeWithUID} should return C{None}
when asked for a non-existent calendar home.
"""
self.assertEquals(
@@ -704,7 +705,7 @@
def test_calendarHomeWithUID_create(self):
"""
- L{ICalendarStoreTransaction.calendarHomeWithUID} with C{create=True}
+ L{ICommonStoreTransaction.calendarHomeWithUID} with C{create=True}
will create a calendar home that doesn't exist yet.
"""
txn = self.transactionUnderTest()
Modified: CalendarServer/branches/new-store/txcaldav/icalendarstore.py
===================================================================
--- CalendarServer/branches/new-store/txcaldav/icalendarstore.py 2010-06-30 21:02:35 UTC (rev 5815)
+++ CalendarServer/branches/new-store/txcaldav/icalendarstore.py 2010-06-30 21:07:30 UTC (rev 5816)
@@ -20,12 +20,10 @@
"""
from zope.interface import Interface
-from txdav.idav import ITransaction
__all__ = [
# Classes
- "ICalendarStore",
"ICalendarHome",
"ICalendar",
"ICalendarObject",
@@ -46,33 +44,6 @@
# Interfaces
#
-class ICalendarStore(Interface):
- """
- Calendar store
- """
- def newTransaction():
- """
- Create a new transaction.
- """
-
-class ICalendarStoreTransaction(ITransaction):
- """
- Calendar store transaction
- """
-
- def calendarHomeWithUID(uid, create=False):
- """
- Retrieve the calendar home for the principal with the given C{uid}.
-
- If C{create} is C{True}, create the calendar home if it doesn't already
- exist.
-
- @return: an L{ICalendarHome} or C{None} if no such calendar
- home exists.
- """
-
-
-
class ICalendarHome(Interface):
"""
Calendar home
@@ -89,6 +60,11 @@
@return: a string.
"""
+ def created(self):
+ """
+ Calendar home was created. Do initialization
+ """
+
def calendars():
"""
Retrieve calendars contained in this calendar home.
Modified: CalendarServer/branches/new-store/txcarddav/addressbookstore/file.py
===================================================================
--- CalendarServer/branches/new-store/txcarddav/addressbookstore/file.py 2010-06-30 21:02:35 UTC (rev 5815)
+++ CalendarServer/branches/new-store/txcarddav/addressbookstore/file.py 2010-06-30 21:07:30 UTC (rev 5816)
@@ -21,6 +21,7 @@
__all__ = [
"AddressBookStore",
+ "AddressBookStoreTransaction",
"AddressBookHome",
"AddressBook",
"AddressBookObject",
@@ -30,74 +31,32 @@
from twext.web2.dav.element.rfc2518 import ResourceType
+from twistedcaldav.sharing import InvitesDatabase
from twistedcaldav.vcard import Component as VComponent, InvalidVCardDataError
from twistedcaldav.vcardindex import AddressBookIndex as OldIndex
from txcarddav.iaddressbookstore import IAddressBook, IAddressBookObject
-from txcarddav.iaddressbookstore import IAddressBookStore, IAddressBookHome
-from txcarddav.iaddressbookstore import IAddressBookStoreTransaction
+from txcarddav.iaddressbookstore import IAddressBookHome
from txdav.common.datastore.file import CommonDataStore, CommonHome,\
- CommonStoreTransaction, CommonHomeChild, CommonObjectResource
+ CommonStoreTransaction, CommonHomeChild, CommonObjectResource,\
+ CommonStubResource
from txdav.common.icommondatastore import InvalidObjectResourceError,\
NoSuchObjectResourceError, InternalDataStoreError
from txdav.datastore.file import hidden, writeOperation
from zope.interface import implements
-class AddressBookStore(CommonDataStore):
- """
- An implementation of L{IAddressBookObject} backed by a
- L{twext.python.filepath.CachingFilePath}.
+AddressBookStore = CommonDataStore
- @ivar _path: A L{CachingFilePath} referencing a directory on disk that
- stores all addressbook data for a group of uids.
- """
- implements(IAddressBookStore)
+AddressBookStoreTransaction = CommonStoreTransaction
- def __init__(self, path):
- """
- Create an addressbook store.
-
- @param path: a L{FilePath} pointing at a directory on disk.
- """
- super(AddressBookStore, self).__init__(path)
- self._transactionClass = AddressBookStoreTransaction
-
-
-class AddressBookStoreTransaction(CommonStoreTransaction):
- """
- In-memory implementation of
-
- Note that this provides basic 'undo' support, but not truly transactional
- operations.
- """
-
- implements(IAddressBookStoreTransaction)
-
- def __init__(self, addressbookStore):
- """
- Initialize a transaction; do not call this directly, instead call
- L{AddressBookStore.newTransaction}.
-
- @param addressbookStore: The store that created this transaction.
-
- @type addressbookStore: L{AddressBookStore}
- """
- super(AddressBookStoreTransaction, self).__init__(addressbookStore)
- self._homeClass = AddressBookHome
-
-
- addressbookHomeWithUID = CommonStoreTransaction.homeWithUID
-
- def creatingHome(self, home):
- home.createAddressBookWithName("addressbook")
-
class AddressBookHome(CommonHome):
+
implements(IAddressBookHome)
- def __init__(self, path, addressbookStore, transaction):
- super(AddressBookHome, self).__init__(path, addressbookStore, transaction)
+ def __init__(self, uid, path, addressbookStore, transaction):
+ super(AddressBookHome, self).__init__(uid, path, addressbookStore, transaction)
self._childClass = AddressBook
@@ -110,8 +69,9 @@
def _addressbookStore(self):
return self._dataStore
+ def created(self):
+ self.createAddressBookWithName("addressbook")
-
class AddressBook(CommonHomeChild):
"""
File-based implementation of L{IAddressBook}.
@@ -137,6 +97,7 @@
super(AddressBook, self).__init__(name, addressbookHome, realName)
self._index = Index(self)
+ self._invites = Invites(self)
self._objectResourceClass = AddressBookObject
@property
@@ -146,7 +107,6 @@
def resourceType(self):
return ResourceType.addressbook
- addressbooks = CommonHome.children
ownerAddressBookHome = CommonHomeChild.ownerHome
addressbookObjects = CommonHomeChild.objectResources
addressbookObjectWithName = CommonHomeChild.objectResourceWithName
@@ -285,49 +245,37 @@
return self._uid
-class Index(object):
- #
- # OK, here's where we get ugly.
- # The index code needs to be rewritten also, but in the meantime...
- #
- class StubResource(object):
- """
- Just enough resource to keep the Index class going.
- """
- def __init__(self, addressbook):
- self.addressbook = addressbook
- self.fp = self.addressbook._path
+class AddressBookStubResource(CommonStubResource):
+ """
+ Just enough resource to keep the addressbook's sql DB classes going.
+ """
- def isAddressBookCollection(self):
- return True
+ def isAddressBookCollection(self):
+ return True
- def getChild(self, name):
- addressbookObject = self.addressbook.addressbookObjectWithName(name)
- if addressbookObject:
- class ChildResource(object):
- def __init__(self, addressbookObject):
- self.addressbookObject = addressbookObject
+ def getChild(self, name):
+ addressbookObject = self.resource.addressbookObjectWithName(name)
+ if addressbookObject:
+ class ChildResource(object):
+ def __init__(self, addressbookObject):
+ self.addressbookObject = addressbookObject
- def iAddressBook(self):
- return self.addressbookObject.component()
+ def iAddressBook(self):
+ return self.addressbookObject.component()
- return ChildResource(addressbookObject)
- else:
- return None
+ return ChildResource(addressbookObject)
+ else:
+ return None
- def bumpSyncToken(self, reset=False):
- # FIXME: needs direct tests
- return self.addressbook._updateSyncToken(reset)
-
- def initSyncToken(self):
- # FIXME: needs direct tests
- self.bumpSyncToken(True)
-
-
+class Index(object):
+ #
+ # OK, here's where we get ugly.
+ # The index code needs to be rewritten also, but in the meantime...
+ #
def __init__(self, addressbook):
self.addressbook = addressbook
- stubResource = Index.StubResource(addressbook)
+ stubResource = AddressBookStubResource(addressbook)
self._oldIndex = OldIndex(stubResource)
@@ -341,3 +289,14 @@
addressbookObject._componentType = componentType
yield addressbookObject
+
+
+class Invites(object):
+ #
+ # OK, here's where we get ugly.
+ # The index code needs to be rewritten also, but in the meantime...
+ #
+ def __init__(self, addressbook):
+ self.addressbook = addressbook
+ stubResource = AddressBookStubResource(addressbook)
+ self._oldInvites = InvitesDatabase(stubResource)
Modified: CalendarServer/branches/new-store/txcarddav/addressbookstore/test/common.py
===================================================================
--- CalendarServer/branches/new-store/txcarddav/addressbookstore/test/common.py 2010-06-30 21:02:35 UTC (rev 5815)
+++ CalendarServer/branches/new-store/txcarddav/addressbookstore/test/common.py 2010-06-30 21:07:30 UTC (rev 5816)
@@ -32,7 +32,7 @@
from txdav.common.icommondatastore import ObjectResourceNameAlreadyExistsError
from txcarddav.iaddressbookstore import (
- IAddressBookStore, IAddressBookStoreTransaction, IAddressBookObject, IAddressBookHome,
+ IAddressBookObject, IAddressBookHome,
IAddressBook,
)
from twistedcaldav.vcard import Component as VComponent
Modified: CalendarServer/branches/new-store/txcarddav/iaddressbookstore.py
===================================================================
--- CalendarServer/branches/new-store/txcarddav/iaddressbookstore.py 2010-06-30 21:02:35 UTC (rev 5815)
+++ CalendarServer/branches/new-store/txcarddav/iaddressbookstore.py 2010-06-30 21:07:30 UTC (rev 5816)
@@ -22,11 +22,9 @@
]
from zope.interface import Interface
-from txdav.idav import ITransaction
__all__ = [
# Classes
- "IAddressBookStore",
"IAddressBookHome",
"IAddressBook",
"IAddressBookObject",
@@ -36,33 +34,6 @@
# Interfaces
#
-class IAddressBookStore(Interface):
- """
- AddressBook store
- """
- def newTransaction():
- """
- Create a new transaction.
- """
-
-class IAddressBookStoreTransaction(ITransaction):
- """
- AddressBook store transaction
- """
-
- def addressbookHomeWithUID(uid, create=False):
- """
- Retrieve the addressbook home for the principal with the given C{uid}.
-
- If C{create} is C{True}, create the addressbook home if it doesn't already
- exist.
-
- @return: an L{IAddressBookHome} or C{None} if no such addressbook
- home exists.
- """
-
-
-
class IAddressBookHome(Interface):
"""
AddressBook home
@@ -79,6 +50,11 @@
@return: a string.
"""
+ def created(self):
+ """
+ Addressbook home was created. Do initialization
+ """
+
def addressbooks():
"""
Retrieve addressbooks contained in this addressbook home.
Modified: CalendarServer/branches/new-store/txdav/common/datastore/file.py
===================================================================
--- CalendarServer/branches/new-store/txdav/common/datastore/file.py 2010-06-30 21:02:35 UTC (rev 5815)
+++ CalendarServer/branches/new-store/txdav/common/datastore/file.py 2010-06-30 21:07:30 UTC (rev 5816)
@@ -18,7 +18,7 @@
from errno import EEXIST, ENOENT
from twext.python.log import LoggingMixIn
-from twext.web2.dav.element.rfc2518 import ResourceType
+from twext.web2.dav.element.rfc2518 import ResourceType, GETContentType
from txdav.datastore.file import DataStoreTransaction, DataStore, writeOperation,\
hidden, isValidName, cached
@@ -27,21 +27,61 @@
from txdav.common.icommondatastore import HomeChildNameNotAllowedError,\
HomeChildNameAlreadyExistsError, NoSuchHomeChildError,\
InternalDataStoreError, ObjectResourceNameNotAllowedError,\
- ObjectResourceNameAlreadyExistsError, NoSuchObjectResourceError
+ ObjectResourceNameAlreadyExistsError, NoSuchObjectResourceError,\
+ ICommonDataStore, ICommonStoreTransaction
from twisted.python.util import FancyEqMixin
-from twistedcaldav.customxml import GETCTag
+from twistedcaldav.customxml import GETCTag, NotificationType
from uuid import uuid4
+from zope.interface.declarations import implements
+from txdav.common.inotifications import INotificationCollection,\
+ INotificationObject
+from twistedcaldav.notifications import NotificationRecord
+from twistedcaldav.notifications import NotificationsDatabase as OldNotificationIndex
+from twext.web2.http_headers import generateContentType, MimeType
+from twistedcaldav.sharing import SharedCollectionsDatabase
"""
Common utility functions for a file based datastore.
"""
+ECALENDARTYPE = 0
+EADDRESSBOOKTYPE = 1
+TOPPATHS = (
+ "calendars",
+ "addressbooks"
+)
+UIDPATH = "__uids__"
+
class CommonDataStore(DataStore):
"""
- Generic data store.
+ An implementation of data store.
+
+ @ivar _path: A L{CachingFilePath} referencing a directory on disk that
+ stores all addressbook data for a group of uids.
"""
- pass
+ implements(ICommonDataStore)
+ def __init__(self, path, enableCalendars, enableAddressBooks):
+ """
+ Create a store.
+
+ @param path: a L{FilePath} pointing at a directory on disk.
+ """
+ assert enableCalendars or enableAddressBooks
+
+ super(CommonDataStore, self).__init__(path)
+ self.enableCalendars = enableCalendars
+ self.enableAddressBooks = enableAddressBooks
+ self._transactionClass = CommonStoreTransaction
+
+ def newTransaction(self):
+ """
+ Create a new transaction.
+
+ @see Transaction
+ """
+ return self._transactionClass(self, self.enableCalendars, self.enableAddressBooks)
+
class CommonStoreTransaction(DataStoreTransaction):
"""
In-memory implementation of
@@ -50,9 +90,11 @@
operations.
"""
- _homeClass = None
+ implements(ICommonStoreTransaction)
- def __init__(self, dataStore):
+ _homeClass = {}
+
+ def __init__(self, dataStore, enableCalendars, enableAddressBooks):
"""
Initialize a transaction; do not call this directly, instead call
L{DataStore.newTransaction}.
@@ -61,22 +103,47 @@
@type dataStore: L{DataStore}
"""
+
+ assert enableCalendars or enableAddressBooks
+
super(CommonStoreTransaction, self).__init__(dataStore)
self._homes = {}
+ self._homes[ECALENDARTYPE] = {}
+ self._homes[EADDRESSBOOKTYPE] = {}
+ self._notifications = {}
+
+ if enableCalendars:
+ self._notificationHomeType = ECALENDARTYPE
+ else:
+ self._notificationHomeType = EADDRESSBOOKTYPE
+ from txcaldav.calendarstore.file import CalendarHome
+ from txcarddav.addressbookstore.file import AddressBookHome
+ CommonStoreTransaction._homeClass[ECALENDARTYPE] = CalendarHome
+ CommonStoreTransaction._homeClass[EADDRESSBOOKTYPE] = AddressBookHome
- def homeWithUID(self, uid, create=False):
- if (uid, self) in self._homes:
- return self._homes[(uid, self)]
+ def calendarHomeWithUID(self, uid, create=False):
+ return self.homeWithUID(ECALENDARTYPE, uid, create)
+ def addressbookHomeWithUID(self, uid, create=False):
+ return self.homeWithUID(EADDRESSBOOKTYPE, uid, 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
assert len(uid) >= 4
- childPath1 = self._dataStore._path.child(uid[0:2])
- childPath2 = childPath1.child(uid[2:4])
- childPath3 = childPath2.child(uid)
+ childPathSegments = []
+ childPathSegments.append(self._dataStore._path.child(TOPPATHS[storeType]))
+ childPathSegments.append(childPathSegments[-1].child(UIDPATH))
+ childPathSegments.append(childPathSegments[-1].child(uid[0:2]))
+ childPathSegments.append(childPathSegments[-1].child(uid[2:4]))
+ childPath = childPathSegments[-1].child(uid)
+
def createDirectory(path):
try:
path.createDirectory()
@@ -88,49 +155,110 @@
creating = False
if create:
- if not childPath2.isdir():
- if not childPath1.isdir():
- createDirectory(childPath1)
- createDirectory(childPath2)
- if childPath3.isdir():
- homePath = childPath3
+ # Create intermediate directories
+ for child in childPathSegments:
+ if not child.isdir():
+ createDirectory(child)
+
+ if childPath.isdir():
+ homePath = childPath
else:
creating = True
- homePath = childPath3.temporarySibling()
+ homePath = childPath.temporarySibling()
createDirectory(homePath)
def do():
def lastly():
- homePath.moveTo(childPath3)
+ homePath.moveTo(childPath)
# home._path = homePath
# do this _after_ all other file operations
- home._path = childPath3
+ home._path = childPath
return lambda : None
self.addOperation(lastly, "create home finalize")
return lambda : None
self.addOperation(do, "create home UID %r" % (uid,))
- elif not childPath3.isdir():
+ elif not childPath.isdir():
return None
else:
- homePath = childPath3
+ homePath = childPath
- home = self._homeClass(homePath, self._dataStore, self)
- self._homes[(uid, self)] = home
+ home = self._homeClass[storeType](uid, homePath, self._dataStore, self)
+ self._homes[storeType][(uid, self)] = home
if creating:
- self.creatingHome(home)
+ home.created()
+
+ # Create notification collection
+ if storeType == ECALENDARTYPE:
+ self.notificationsWithUID(uid)
return home
- def creatingHome(self, home):
- raise NotImplementedError
+ def notificationsWithUID(self, uid):
+ if (uid, self) in self._notifications:
+ return self._notifications[(uid, self)]
+
+ home = self.homeWithUID(self._notificationHomeType, uid, create=True)
+ notificationPath = home._path.child("notification")
+ if not notificationPath.isdir():
+ notificationPath = self.createNotifcationCollection(home, notificationPath)
+
+ notifications = NotificationCollection(notificationPath.basename(), home)
+ self._notifications[(uid, self)] = notifications
+ return notifications
+
+ def createNotifcationCollection(self, home, notificationPath):
+
+ if notificationPath.isdir():
+ return notificationPath
+
+ name = notificationPath.basename()
+
+ temporary = hidden(notificationPath.temporarySibling())
+ temporary.createDirectory()
+ # In order for the index to work (which is doing real file ops on disk
+ # via SQLite) we need to create a real directory _immediately_.
+
+ # FIXME: some way to roll this back.
+
+ c = NotificationCollection(temporary.basename(), home)
+ def do():
+ try:
+ props = c.properties()
+ temporary.moveTo(notificationPath)
+ c._name = name
+ # FIXME: _lots_ of duplication of work here.
+ props.path = notificationPath
+ props.flush()
+ except (IOError, OSError), e:
+ if e.errno == EEXIST and notificationPath.isdir():
+ raise HomeChildNameAlreadyExistsError(name)
+ raise
+ # FIXME: direct tests, undo for index creation
+ # Return undo
+ return lambda: notificationPath.remove()
+
+ self.addOperation(do, "create child %r" % (name,))
+ props = c.properties()
+ props[PropertyName(*ResourceType.qname())] = c.resourceType()
+ return temporary
+
+class StubResource(object):
+ """
+ Just enough resource to keep the shared sql DB classes going.
+ """
+ def __init__(self, stubit):
+ self.fp = stubit._path
+
class CommonHome(LoggingMixIn):
_childClass = None
- def __init__(self, path, dataStore, transaction):
+ def __init__(self, uid, path, dataStore, transaction):
self._dataStore = dataStore
+ self._uid = uid
self._path = path
self._transaction = transaction
+ self._shares = SharedCollectionsDatabase(StubResource(self))
self._newChildren = {}
self._removedChildren = set()
self._cachedChildren = {}
@@ -141,7 +269,7 @@
def uid(self):
- return self._path.basename()
+ return self._uid
def _updateSyncToken(self, reset=False):
@@ -149,6 +277,12 @@
# FIXME: actually update something
+ def retrieveOldShares(self):
+ """
+ Retrieve the old Index object.
+ """
+ return self._shares
+
def children(self):
return set(self._newChildren.itervalues()) | set(
self.childWithName(name)
@@ -303,6 +437,7 @@
self._cachedObjectResources = {}
self._removedObjectResources = set()
self._index = None # Derived classes need to set this
+ self._invites = None # Derived classes need to set this
self._renamedName = realName
@@ -320,7 +455,13 @@
"""
return self._index._oldIndex
+ def retrieveOldInvites(self):
+ """
+ Retrieve the old Invites DB object.
+ """
+ return self._invites._oldInvites
+
def __repr__(self):
return "<%s: %s>" % (self.__class__.__name__, self._path.path)
@@ -513,7 +654,7 @@
raise NotImplementedError
- def vCardText(self):
+ def text(self):
raise NotImplementedError
@@ -526,3 +667,186 @@
self._transaction.addOperation(props.flush, "object properties flush")
return props
+class CommonStubResource(object):
+ """
+ Just enough resource to keep the collection sql DB classes going.
+ """
+ def __init__(self, resource):
+ self.resource = resource
+ self.fp = self.resource._path
+
+ def bumpSyncToken(self, reset=False):
+ # FIXME: needs direct tests
+ return self.resource._updateSyncToken(reset)
+
+
+ def initSyncToken(self):
+ # FIXME: needs direct tests
+ self.bumpSyncToken(True)
+
+
+class NotificationCollection(CommonHomeChild):
+ """
+ File-based implementation of L{INotificationCollection}.
+ """
+ implements(INotificationCollection)
+
+ def __init__(self, name, parent, realName=None):
+ """
+ Initialize an notification collection pointing at a path on disk.
+
+ @param name: the subdirectory of parent where this notification collection
+ resides.
+ @type name: C{str}
+
+ @param parent: the home containing this notification collection.
+ @type parent: L{CommonHome}
+ """
+
+ super(NotificationCollection, self).__init__(name, parent, realName)
+
+ self._index = NotificationIndex(self)
+ self._invites = None
+ self._objectResourceClass = NotificationObject
+
+ def resourceType(self):
+ return ResourceType.notification
+
+ notificationObjects = CommonHomeChild.objectResources
+ notificationObjectWithName = CommonHomeChild.objectResourceWithName
+ removeNotificationObjectWithUID = CommonHomeChild.removeObjectResourceWithUID
+ notificationObjectsSinceToken = CommonHomeChild.objectResourcesSinceToken
+
+ def notificationObjectWithUID(self, uid):
+
+ record = self.retrieveOldIndex().recordForUID(uid)
+ return self.notificationObjectWithName(record.name) if record else None
+
+ def writeNotificationObject(self, uid, xmltype, xmldata):
+ name = uid + ".xml"
+ if name.startswith("."):
+ raise ObjectResourceNameNotAllowedError(name)
+
+ objectResource = NotificationObject(name, self)
+ objectResource.setData(uid, xmltype, xmldata)
+ self._cachedObjectResources[name] = objectResource
+
+ # Update database
+ self.retrieveOldIndex().addOrUpdateRecord(NotificationRecord(uid, name, xmltype.name))
+
+ @writeOperation
+ def removeNotificationObjectWithName(self, name):
+ if name.startswith("."):
+ raise NoSuchObjectResourceError(name)
+
+ self.retrieveOldIndex().removeRecordForName(name)
+
+ objectResourcePath = self._path.child(name)
+ if objectResourcePath.isfile():
+ self._removedObjectResources.add(name)
+ # FIXME: test for undo
+ def do():
+ objectResourcePath.remove()
+ return lambda: None
+ self._transaction.addOperation(do, "remove object resource object %r" %
+ (name,))
+ else:
+ raise NoSuchObjectResourceError(name)
+
+ def _doValidate(self, component):
+ # Nothing to do - notifications are always generated internally by the server
+ # so they better be valid all the time!
+ pass
+
+
+class NotificationObject(CommonObjectResource):
+ """
+ """
+ implements(INotificationObject)
+
+ def __init__(self, name, notifications):
+
+ super(NotificationObject, self).__init__(name, notifications)
+
+
+ @property
+ def _notificationCollection(self):
+ return self._parentCollection
+
+ @writeOperation
+ def setData(self, uid, xmltype, xmldata):
+
+ rname = uid + ".xml"
+ self._notificationCollection.retrieveOldIndex().addOrUpdateRecord(
+ NotificationRecord(uid, rname, xmltype.name)
+ )
+
+ def do():
+ backup = None
+ if self._path.exists():
+ backup = hidden(self._path.temporarySibling())
+ self._path.moveTo(backup)
+ fh = self._path.open("w")
+ try:
+ # FIXME: concurrency problem; if this write is interrupted
+ # halfway through, the underlying file will be corrupt.
+ fh.write(xmldata)
+ finally:
+ fh.close()
+ def undo():
+ if backup:
+ backup.moveTo(self._path)
+ else:
+ self._path.remove()
+ return undo
+ self._transaction.addOperation(do, "set notification data %r" % (self.name(),))
+
+ # Mark all properties as dirty, so they will be re-added to the
+ # temporary file when the main file is deleted. NOTE: if there were a
+ # temporary file and a rename() as there should be, this should really
+ # happen after the write but before the rename.
+ self.properties().update(self.properties())
+
+ props = self.properties()
+ props[PropertyName(*GETContentType.qname())] = GETContentType.fromString(generateContentType(MimeType("text", "xml", params={"charset":"utf-8"})))
+ props[PropertyName(*NotificationType.qname())] = NotificationType(xmltype)
+
+ # FIXME: the property store's flush() method may already have been
+ # added to the transaction, but we need to add it again to make sure it
+ # happens _after_ the new file has been written. we may end up doing
+ # the work multiple times, and external callers to property-
+ # manipulation methods won't work.
+ self._transaction.addOperation(self.properties().flush, "post-update property flush")
+
+ def xmldata(self):
+ try:
+ fh = self._path.open()
+ except IOError, e:
+ if e[0] == ENOENT:
+ raise NoSuchObjectResourceError(self)
+ else:
+ raise
+
+ try:
+ text = fh.read()
+ finally:
+ fh.close()
+
+ return text
+
+
+ def uid(self):
+ if not hasattr(self, "_uid"):
+ self._uid = self.xmldata
+ return self._uid
+
+class NotificationIndex(object):
+ #
+ # OK, here's where we get ugly.
+ # The index code needs to be rewritten also, but in the meantime...
+ #
+ def __init__(self, notificationCollection):
+ self.notificationCollection = notificationCollection
+ stubResource = CommonStubResource(notificationCollection)
+ self._oldIndex = OldNotificationIndex(stubResource)
+
Modified: CalendarServer/branches/new-store/txdav/common/icommondatastore.py
===================================================================
--- CalendarServer/branches/new-store/txdav/common/icommondatastore.py 2010-06-30 21:02:35 UTC (rev 5815)
+++ CalendarServer/branches/new-store/txdav/common/icommondatastore.py 2010-06-30 21:07:30 UTC (rev 5816)
@@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
##
+from zope.interface.interface import Interface
"""
Common store interfaces
@@ -103,3 +104,54 @@
"""
Uh, oh.
"""
+
+#
+# Interfaces
+#
+
+class ICommonDataStore(Interface):
+ """
+ Data store
+ """
+ def newTransaction():
+ """
+ Create a new transaction.
+ """
+
+class ICommonStoreTransaction(Interface):
+ """
+ AddressBook store transaction
+ """
+
+ def calendarHomeWithUID(uid, create=False):
+ """
+ Retrieve the addressbook home for the principal with the given C{uid}.
+
+ If C{create} is C{True}, create the addressbook home if it doesn't already
+ exist.
+
+ @return: an L{IAddressBookHome} or C{None} if no such addressbook
+ home exists.
+ """
+
+ def addressbookHomeWithUID(uid, create=False):
+ """
+ Retrieve the addressbook home for the principal with the given C{uid}.
+
+ If C{create} is C{True}, create the addressbook home if it doesn't already
+ exist.
+
+ @return: an L{IAddressBookHome} or C{None} if no such addressbook
+ home exists.
+ """
+
+ def notificationsWithUID(uid):
+ """
+ Retrieve the notification collection for the principal with the given C{uid}.
+
+ @return: an L{INotificationCollection} or C{None} if no such notification
+ collection exists.
+ """
+
+
+
Added: CalendarServer/branches/new-store/txdav/common/inotifications.py
===================================================================
--- CalendarServer/branches/new-store/txdav/common/inotifications.py (rev 0)
+++ CalendarServer/branches/new-store/txdav/common/inotifications.py 2010-06-30 21:07:30 UTC (rev 5816)
@@ -0,0 +1,171 @@
+##
+# Copyright (c) 2010 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+"""
+Common notification interfaces
+"""
+
+from zope.interface.interface import Interface
+
+
+__all__ = [
+ "INotificationCollection",
+ "INotification",
+]
+
+class INotificationCollection(Interface):
+ """
+ NotificationCollection
+
+ A notification collection is a container for notification objects.
+ A notification collection belongs to a specific principal.
+ """
+
+ def name():
+ """
+ Identify this notification collection.
+
+ @return: the name of this notification collection.
+ @rtype: C{str}
+ """
+
+ def notificationObjects():
+ """
+ Retrieve the notification objects contained in this notification
+ collection with the given C{componentType}.
+
+ @param componentType: a string.
+ @return: an iterable of L{INotificationObject}s.
+ """
+
+ def notificationObjectWithName(name):
+ """
+ Retrieve the notification object with the given C{name} contained
+ in this notification collection.
+
+ @param name: a string.
+ @return: an L{INotificationObject} or C{None} if no such notification
+ object exists.
+ """
+
+ def notificationObjectWithUID(uid):
+ """
+ Retrieve the notification object with the given C{uid} contained
+ in this notification collection.
+
+ @param uid: a string.
+ @return: an L{INotificationObject} or C{None} if no such notification
+ object exists.
+ """
+
+ def writeNotificationObject(uid, xmltype, xmldata):
+ """
+ Write a notification with the given C{uid} in this
+ notification collection from the given C{xmldata} with
+ given C{xmltype}. Create or overwrite are OK.
+
+ @param uid: a string.
+ @param xmltype: a string.
+ @param xmldata: a string.
+ @param component: a C{VCARD} L{Component}
+ """
+
+ def removeNotificationObjectWithName(name):
+ """
+ Remove the notification object with the given C{name} from this
+ notification collection. If C{deleteOnly} is C{True} then do not
+
+
+ @param name: a string.
+ @raise NoSuchObjectResourceError: if no such NoSuchObjectResourceError object
+ exists.
+ """
+
+ def removeNotificationObjectWithUID(uid):
+ """
+ Remove the notification object with the given C{uid} from this
+ notification collection.
+
+ @param uid: a string.
+ @raise NoSuchObjectResourceError: if the notification object does
+ not exist.
+ """
+
+ def syncToken():
+ """
+ Retrieve the current sync token for this notification.
+
+ @return: a string containing a sync token.
+ """
+
+ def notificationObjectsSinceToken(token):
+ """
+ Retrieve all notification objects in this notification collection that have
+ changed since the given C{token} was last valid.
+
+ @param token: a sync token.
+ @return: a 3-tuple containing an iterable of
+ L{INotificationObject}s that have changed, an iterable of uids
+ that have been removed, and the current sync token.
+ """
+
+ def properties():
+ """
+ Retrieve the property store for this notification.
+
+ @return: an L{IPropertyStore}.
+ """
+
+
+class INotificationObject(Interface):
+ """
+ Notification object
+
+ An notification object describes an XML notification.
+ """
+
+ def setData(uid, xmltype, xmldata):
+ """
+ Rewrite this notification object to match the given C{xmltype} and
+ C{xmldata}. C{xmldata} must have the same UID as this notification object.
+
+ @param xmltype: a string.
+ @param xmldata: a string.
+ @raise InvalidObjectResourceError: if the given
+ C{xmltype} or C{xmldata} is not a valid for
+ an notification object.
+ """
+
+ def xmldata():
+ """
+ Retrieve the notification data for this notification object.
+
+ @return: a string.
+ """
+
+ def uid():
+ """
+ Retrieve the UID for this notification object.
+
+ @return: a string containing a UID.
+ """
+
+ def properties():
+ """
+ Retrieve the property store for this notification object.
+
+ @return: an L{IPropertyStore}.
+ """
Modified: CalendarServer/branches/new-store/txdav/propertystore/base.py
===================================================================
--- CalendarServer/branches/new-store/txdav/propertystore/base.py 2010-06-30 21:02:35 UTC (rev 5815)
+++ CalendarServer/branches/new-store/txdav/propertystore/base.py 2010-06-30 21:07:30 UTC (rev 5816)
@@ -90,6 +90,16 @@
"""
implements(IPropertyStore)
+ def __init__(self, peruser):
+ """
+ Instantiate the property store for a user.
+
+ @param peruser: the user uid
+ @type peruser: C{str}
+ """
+
+ self._peruser = peruser
+
#
# Subclasses must override these
#
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20100630/0aa0f203/attachment-0001.html>
More information about the calendarserver-changes
mailing list