[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