[CalendarServer-changes] [5982] CalendarServer/trunk

source_changes at macosforge.org source_changes at macosforge.org
Thu Aug 5 10:54:52 PDT 2010


Revision: 5982
          http://trac.macosforge.org/projects/calendarserver/changeset/5982
Author:   cdaboo at apple.com
Date:     2010-08-05 10:54:48 -0700 (Thu, 05 Aug 2010)
Log Message:
-----------
Merge nocaldavfile-2 branch to trunk.

Modified Paths:
--------------
    CalendarServer/trunk/calendarserver/provision/root.py
    CalendarServer/trunk/calendarserver/sidecar/test/test_task.py
    CalendarServer/trunk/calendarserver/tap/caldav.py
    CalendarServer/trunk/calendarserver/tap/test/test_caldav.py
    CalendarServer/trunk/calendarserver/tap/util.py
    CalendarServer/trunk/calendarserver/tools/export.py
    CalendarServer/trunk/calendarserver/tools/util.py
    CalendarServer/trunk/calendarserver/tools/warmup.py
    CalendarServer/trunk/twext/web2/dav/method/acl.py
    CalendarServer/trunk/twext/web2/dav/method/copymove.py
    CalendarServer/trunk/twext/web2/dav/method/delete.py
    CalendarServer/trunk/twext/web2/dav/method/delete_common.py
    CalendarServer/trunk/twext/web2/dav/method/get.py
    CalendarServer/trunk/twext/web2/dav/method/mkcol.py
    CalendarServer/trunk/twext/web2/dav/method/propfind.py
    CalendarServer/trunk/twext/web2/dav/method/proppatch.py
    CalendarServer/trunk/twext/web2/dav/method/put.py
    CalendarServer/trunk/twext/web2/dav/method/report.py
    CalendarServer/trunk/twext/web2/dav/resource.py
    CalendarServer/trunk/twext/web2/dav/test/util.py
    CalendarServer/trunk/twext/web2/server.py
    CalendarServer/trunk/twistedcaldav/customxml.py
    CalendarServer/trunk/twistedcaldav/directory/addressbook.py
    CalendarServer/trunk/twistedcaldav/directory/aggregate.py
    CalendarServer/trunk/twistedcaldav/directory/calendar.py
    CalendarServer/trunk/twistedcaldav/directory/calendaruserproxy.py
    CalendarServer/trunk/twistedcaldav/directory/opendirectorybacker.py
    CalendarServer/trunk/twistedcaldav/directory/principal.py
    CalendarServer/trunk/twistedcaldav/directory/resource.py
    CalendarServer/trunk/twistedcaldav/directory/test/test_principal.py
    CalendarServer/trunk/twistedcaldav/directory/util.py
    CalendarServer/trunk/twistedcaldav/directorybackedaddressbook.py
    CalendarServer/trunk/twistedcaldav/dropbox.py
    CalendarServer/trunk/twistedcaldav/extensions.py
    CalendarServer/trunk/twistedcaldav/freebusyurl.py
    CalendarServer/trunk/twistedcaldav/index.py
    CalendarServer/trunk/twistedcaldav/linkresource.py
    CalendarServer/trunk/twistedcaldav/mail.py
    CalendarServer/trunk/twistedcaldav/method/__init__.py
    CalendarServer/trunk/twistedcaldav/method/acl.py
    CalendarServer/trunk/twistedcaldav/method/copymove.py
    CalendarServer/trunk/twistedcaldav/method/copymove_contact.py
    CalendarServer/trunk/twistedcaldav/method/get.py
    CalendarServer/trunk/twistedcaldav/method/mkcol.py
    CalendarServer/trunk/twistedcaldav/method/put.py
    CalendarServer/trunk/twistedcaldav/method/put_addressbook_common.py
    CalendarServer/trunk/twistedcaldav/method/put_common.py
    CalendarServer/trunk/twistedcaldav/method/report_addressbook_query.py
    CalendarServer/trunk/twistedcaldav/method/report_calendar_query.py
    CalendarServer/trunk/twistedcaldav/method/report_common.py
    CalendarServer/trunk/twistedcaldav/method/report_freebusy.py
    CalendarServer/trunk/twistedcaldav/method/report_sync_collection.py
    CalendarServer/trunk/twistedcaldav/notifications.py
    CalendarServer/trunk/twistedcaldav/notify.py
    CalendarServer/trunk/twistedcaldav/resource.py
    CalendarServer/trunk/twistedcaldav/schedule.py
    CalendarServer/trunk/twistedcaldav/scheduling/caldav.py
    CalendarServer/trunk/twistedcaldav/scheduling/processing.py
    CalendarServer/trunk/twistedcaldav/scheduling/utils.py
    CalendarServer/trunk/twistedcaldav/sharedcollection.py
    CalendarServer/trunk/twistedcaldav/sharing.py
    CalendarServer/trunk/twistedcaldav/simpleresource.py
    CalendarServer/trunk/twistedcaldav/storebridge.py
    CalendarServer/trunk/twistedcaldav/test/test_accounting.py
    CalendarServer/trunk/twistedcaldav/test/test_addressbookmultiget.py
    CalendarServer/trunk/twistedcaldav/test/test_collectioncontents.py
    CalendarServer/trunk/twistedcaldav/test/test_config.py
    CalendarServer/trunk/twistedcaldav/test/test_extensions.py
    CalendarServer/trunk/twistedcaldav/test/test_mkcalendar.py
    CalendarServer/trunk/twistedcaldav/test/test_multiget.py
    CalendarServer/trunk/twistedcaldav/test/test_options.py
    CalendarServer/trunk/twistedcaldav/test/test_props.py
    CalendarServer/trunk/twistedcaldav/test/test_schedule.py
    CalendarServer/trunk/twistedcaldav/test/test_sharing.py
    CalendarServer/trunk/twistedcaldav/test/test_sql.py
    CalendarServer/trunk/twistedcaldav/test/test_validation.py
    CalendarServer/trunk/twistedcaldav/test/test_vcardindex.py
    CalendarServer/trunk/twistedcaldav/test/test_wrapping.py
    CalendarServer/trunk/twistedcaldav/test/util.py
    CalendarServer/trunk/twistedcaldav/timezoneservice.py
    CalendarServer/trunk/twistedcaldav/vcardindex.py
    CalendarServer/trunk/txcaldav/calendarstore/file.py
    CalendarServer/trunk/txcaldav/icalendarstore.py
    CalendarServer/trunk/txcarddav/addressbookstore/file.py
    CalendarServer/trunk/txcarddav/iaddressbookstore.py
    CalendarServer/trunk/txdav/common/datastore/__init__.py
    CalendarServer/trunk/txdav/common/datastore/file.py
    CalendarServer/trunk/txdav/datastore/file.py
    CalendarServer/trunk/txdav/idav.py

Added Paths:
-----------
    CalendarServer/trunk/calendarserver/sidecar/test/__init__.py
    CalendarServer/trunk/twistedcaldav/bind.py

Removed Paths:
-------------
    CalendarServer/trunk/twistedcaldav/static.py
    CalendarServer/trunk/twistedcaldav/test/test_DAV.py

Property Changed:
----------------
    CalendarServer/trunk/


Property changes on: CalendarServer/trunk
___________________________________________________________________
Modified: svn:mergeinfo
   - /CalendarServer/branches/config-separation:4379-4443
/CalendarServer/branches/egg-info-351:4589-4625
/CalendarServer/branches/new-store:5594-5919
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464:4465-4957
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187:5188-5440
/CalendarServer/branches/users/glyph/contacts-server-merge:4971-5080
/CalendarServer/branches/users/glyph/sendfdport:5388-5424
/CalendarServer/branches/users/glyph/use-system-twisted:5084-5149
/CalendarServer/branches/users/sagen/locations-resources:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2:5052-5061
/CalendarServer/branches/users/sagen/resource-delegates-4038:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066:4068-4075
/CalendarServer/branches/users/sagen/resources-2:5084-5093
/CalendarServer/branches/users/wsanchez/transations:5515-5593
   + /CalendarServer/branches/config-separation:4379-4443
/CalendarServer/branches/egg-info-351:4589-4625
/CalendarServer/branches/new-store:5594-5934
/CalendarServer/branches/new-store-no-caldavfile:5911-5935
/CalendarServer/branches/new-store-no-caldavfile-2:5936-5981
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464:4465-4957
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187:5188-5440
/CalendarServer/branches/users/glyph/contacts-server-merge:4971-5080
/CalendarServer/branches/users/glyph/sendfdport:5388-5424
/CalendarServer/branches/users/glyph/use-system-twisted:5084-5149
/CalendarServer/branches/users/sagen/locations-resources:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2:5052-5061
/CalendarServer/branches/users/sagen/resource-delegates-4038:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066:4068-4075
/CalendarServer/branches/users/sagen/resources-2:5084-5093
/CalendarServer/branches/users/wsanchez/transations:5515-5593

Modified: CalendarServer/trunk/calendarserver/provision/root.py
===================================================================
--- CalendarServer/trunk/calendarserver/provision/root.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/calendarserver/provision/root.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -19,20 +19,20 @@
     "RootResource",
 ]
 
-from twisted.internet.defer import inlineCallbacks, returnValue
+from twext.python.log import Logger
 from twext.web2 import responsecode
+from twext.web2.auth.wrapper import UnauthorizedResponse
 from twext.web2.dav import davxml
 from twext.web2.http import HTTPError, StatusResponse
-from twext.web2.auth.wrapper import UnauthorizedResponse
+
+from twisted.internet.defer import inlineCallbacks, returnValue
 from twisted.web.xmlrpc import Proxy
 
-from twext.python.log import Logger
-
-from twistedcaldav.resource import CalDAVComplianceMixIn
+from twistedcaldav.config import config
 from twistedcaldav.extensions import DAVFile, CachingPropertyStore
 from twistedcaldav.extensions import DirectoryPrincipalPropertySearchMixIn
 from twistedcaldav.extensions import ReadOnlyResourceMixIn
-from twistedcaldav.config import config
+from twistedcaldav.resource import CalDAVComplianceMixIn
 
 log = Logger()
 
@@ -69,7 +69,6 @@
             self.contentFilters.append((addConnectionClose, True))
 
     def deadProperties(self):
-        # FIXME: Same as in static.py's CalDAVFile
         if not hasattr(self, "_dead_properties"):
             # Get the property store from super
             deadProperties = super(RootResource, self).deadProperties()

Copied: CalendarServer/trunk/calendarserver/sidecar/test/__init__.py (from rev 5981, CalendarServer/branches/new-store-no-caldavfile-2/calendarserver/sidecar/test/__init__.py)
===================================================================
--- CalendarServer/trunk/calendarserver/sidecar/test/__init__.py	                        (rev 0)
+++ CalendarServer/trunk/calendarserver/sidecar/test/__init__.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -0,0 +1,15 @@
+##
+# 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.
+##

Modified: CalendarServer/trunk/calendarserver/sidecar/test/test_task.py
===================================================================
--- CalendarServer/trunk/calendarserver/sidecar/test/test_task.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/calendarserver/sidecar/test/test_task.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -22,7 +22,7 @@
 from twisted.python.usage import Options
 from twistedcaldav.config import config, ConfigDict
 from twistedcaldav.stdconfig import DEFAULT_CONFIG
-from twistedcaldav.test.util import TestCase
+from twistedcaldav.test.util import TestCase, todo
 from twisted.internet.defer import inlineCallbacks
 
 # Points to top of source tree.
@@ -85,6 +85,7 @@
         self.options.parseOptions(["-f", self.configFile])
         return CalDAVTaskServiceMaker().makeService(self.options)
 
+    @todo("FIXME: fix after new store changes")
     @inlineCallbacks
     def test_taskService(self):
         service = self.makeService()

Modified: CalendarServer/trunk/calendarserver/tap/caldav.py
===================================================================
--- CalendarServer/trunk/calendarserver/tap/caldav.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/calendarserver/tap/caldav.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -28,7 +28,7 @@
 from time import time
 
 from subprocess import Popen, PIPE
-from pwd import getpwnam, getpwuid
+from pwd import getpwuid
 from grp import getgrnam
 from OpenSSL.SSL import Error as SSLError
 import OpenSSL
@@ -67,14 +67,10 @@
 
 from twistedcaldav.config import ConfigurationError
 from twistedcaldav.config import config
-from twistedcaldav.directory.principal import DirectoryPrincipalProvisioningResource
 from twistedcaldav.directory import calendaruserproxy
 from twistedcaldav.directory.calendaruserproxyloader import XMLCalendarUserProxyLoader
 from twistedcaldav.localization import processLocalizationFiles
 from twistedcaldav.mail import IMIPReplyInboxResource
-from twistedcaldav.static import CalendarHomeProvisioningFile
-from twistedcaldav.static import IScheduleInboxFile
-from twistedcaldav.static import TimezoneServiceFile
 from twistedcaldav.stdconfig import DEFAULT_CONFIG, DEFAULT_CONFIG_FILE
 from twistedcaldav.upgrade import upgradeData
 
@@ -89,9 +85,6 @@
 from calendarserver.accesslog import AMPCommonAccessLoggingObserver
 from calendarserver.accesslog import AMPLoggingFactory
 from calendarserver.accesslog import RotatingFileAccessLoggingObserver
-from calendarserver.provision.root import RootResource
-from calendarserver.webadmin.resource import WebAdminResource
-from calendarserver.webcal.resource import WebCalendarResource
 from calendarserver.tap.util import getRootResource, computeProcessCount
 from calendarserver.tools.util import checkDirectory
 
@@ -372,18 +365,6 @@
     options = CalDAVOptions
 
     #
-    # Default resource classes
-    #
-    rootResourceClass            = RootResource
-    principalResourceClass       = DirectoryPrincipalProvisioningResource
-    calendarResourceClass        = CalendarHomeProvisioningFile
-    iScheduleResourceClass       = IScheduleInboxFile
-    imipResourceClass            = IMIPReplyInboxResource
-    timezoneServiceResourceClass = TimezoneServiceFile
-    webCalendarResourceClass     = WebCalendarResource
-    webAdminResourceClass        = WebAdminResource
-    
-    #
     # Default tap names
     #
     mailGatewayTapName = "caldav_mailgateway"

Modified: CalendarServer/trunk/calendarserver/tap/test/test_caldav.py
===================================================================
--- CalendarServer/trunk/calendarserver/tap/test/test_caldav.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/calendarserver/tap/test/test_caldav.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -47,14 +47,17 @@
 from twistedcaldav.stdconfig import DEFAULT_CONFIG
 
 from twistedcaldav.directory.aggregate import AggregateDirectoryService
+from twistedcaldav.directory.calendar import DirectoryCalendarHomeProvisioningResource
+from twistedcaldav.directory.directory import UnknownRecordTypeError
+from twistedcaldav.directory.principal import DirectoryPrincipalProvisioningResource
 from twistedcaldav.directory.sudo import SudoDirectoryService
-from twistedcaldav.directory.directory import UnknownRecordTypeError
 from twistedcaldav.test.util import TestCase
 
 from calendarserver.tap.caldav import (
     CalDAVOptions, CalDAVServiceMaker, CalDAVService, GroupOwnedUNIXServer,
     DelayedStartupProcessMonitor, DelayedStartupLineLogger, TwistdSlaveProcess
 )
+from calendarserver.provision.root import RootResource
 
 
 # Points to top of source tree.
@@ -698,7 +701,7 @@
         site = self.getSite()
         root = site.resource.resource.resource
 
-        self.failUnless(isinstance(root, CalDAVServiceMaker.rootResourceClass))
+        self.failUnless(isinstance(root, RootResource))
 
     def test_principalResource(self):
         """
@@ -709,7 +712,7 @@
 
         self.failUnless(isinstance(
             root.getChild("principals"),
-            CalDAVServiceMaker.principalResourceClass
+            DirectoryPrincipalProvisioningResource
         ))
 
     def test_calendarResource(self):
@@ -721,7 +724,7 @@
 
         self.failUnless(isinstance(
             root.getChild("calendars"),
-            CalDAVServiceMaker.calendarResourceClass
+            DirectoryCalendarHomeProvisioningResource
         ))
 
 

Modified: CalendarServer/trunk/calendarserver/tap/util.py
===================================================================
--- CalendarServer/trunk/calendarserver/tap/util.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/calendarserver/tap/util.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -14,7 +14,6 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 ##
-
 __all__ = [
     "getRootResource",
     "FakeRequest",
@@ -24,37 +23,38 @@
 import os
 from time import sleep
 
-from twisted.python.reflect import namedClass
-from twisted.internet.reactor import addSystemEventTrigger
-from twisted.cred.portal import Portal
+from twext.python.filepath import CachingFilePath as FilePath
+from twext.python.log import Logger
+from twext.web2.auth.basic import BasicCredentialFactory
+from twext.web2.dav import auth
 from twext.web2.http_headers import Headers
-from twext.web2.dav import auth
-from twext.web2.auth.basic import BasicCredentialFactory
 from twext.web2.resource import RedirectResource
 from twext.web2.static import File as FileResource
-from twext.python.filepath import CachingFilePath as FilePath
 
-from twext.python.log import Logger
+from twisted.cred.portal import Portal
+from twisted.internet.defer import inlineCallbacks, returnValue
+from twisted.internet.reactor import addSystemEventTrigger
+from twisted.python.reflect import namedClass
 
 from twistedcaldav import memcachepool
+from twistedcaldav.bind import doBind
 from twistedcaldav.directory import augment, calendaruserproxy
+from twistedcaldav.directory.addressbook import DirectoryAddressBookHomeProvisioningResource
 from twistedcaldav.directory.aggregate import AggregateDirectoryService
+from twistedcaldav.directory.calendar import DirectoryCalendarHomeProvisioningResource
 from twistedcaldav.directory.digest import QopDigestCredentialFactory
 from twistedcaldav.directory.internal import InternalDirectoryService
 from twistedcaldav.directory.principal import DirectoryPrincipalProvisioningResource
 from twistedcaldav.directory.sudo import SudoDirectoryService
-from twistedcaldav.directory.util import NotFilePath
 from twistedcaldav.directory.wiki import WikiDirectoryService
 from twistedcaldav.notify import NotifierFactory
+from twistedcaldav.directorybackedaddressbook import DirectoryBackedAddressBookResource
 from twistedcaldav.resource import CalDAVResource, AuthenticationWrapper
+from twistedcaldav.schedule import IScheduleInboxResource
 from twistedcaldav.simpleresource import SimpleResource
-from twistedcaldav.static import CalendarHomeProvisioningFile
-from twistedcaldav.static import IScheduleInboxFile
-from twistedcaldav.static import TimezoneServiceFile
-from twistedcaldav.static import AddressBookHomeProvisioningFile, DirectoryBackedAddressBookFile
 from twistedcaldav.timezones import TimezoneCache
+from twistedcaldav.timezoneservice import TimezoneServiceResource
 from twistedcaldav.util import getMemorySize, getNCPU
-from twisted.internet.defer import inlineCallbacks, returnValue
 
 try:
     from twistedcaldav.authkerb import NegotiateCredentialFactory
@@ -82,19 +82,22 @@
     tuples containing: path, resource class, __init__ args list, and optional
     authentication scheme ("basic" or "digest").
     """
+    
+    # FIXME: this is only here to workaround circular imports
+    doBind()
 
     #
     # Default resource classes
     #
     rootResourceClass            = RootResource
     principalResourceClass       = DirectoryPrincipalProvisioningResource
-    calendarResourceClass        = CalendarHomeProvisioningFile
-    iScheduleResourceClass       = IScheduleInboxFile
-    timezoneServiceResourceClass = TimezoneServiceFile
+    calendarResourceClass        = DirectoryCalendarHomeProvisioningResource
+    iScheduleResourceClass       = IScheduleInboxResource
+    timezoneServiceResourceClass = TimezoneServiceResource
     webCalendarResourceClass     = WebCalendarResource
     webAdminResourceClass        = WebAdminResource
-    addressBookResourceClass     = AddressBookHomeProvisioningFile
-    directoryBackedAddressBookResourceClass = DirectoryBackedAddressBookFile
+    addressBookResourceClass     = DirectoryAddressBookHomeProvisioningResource
+    directoryBackedAddressBookResourceClass = DirectoryBackedAddressBookResource
 
     #
     # Setup the Directory
@@ -181,7 +184,7 @@
         raise
 
     #
-    # Setup the PoxyDB Service
+    # Setup the ProxyDB Service
     #
     proxydbClass = namedClass(config.ProxyDBService.type)
 
@@ -291,16 +294,16 @@
     if config.EnableCalDAV:
         log.info("Setting up calendar collection: %r" % (calendarResourceClass,))
         calendarCollection = calendarResourceClass(
-            os.path.join(config.DocumentRoot, "calendars"),
-            directory, "/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,
         )
 
@@ -309,7 +312,6 @@
             log.info("Setting up directory address book: %r" % (directoryBackedAddressBookResourceClass,))
 
             directoryBackedAddressBookCollection = directoryBackedAddressBookResourceClass(
-                directoryPath,
                 principalCollections=(principalCollection,)
             )
             addSystemEventTrigger("after", "startup", directoryBackedAddressBookCollection.provisionDirectory)
@@ -373,7 +375,6 @@
                       % (timezoneServiceResourceClass,))
 
         timezoneService = timezoneServiceResourceClass(
-            NotFilePath(isfile=True),
             root,
         )
         root.putChild("timezones", timezoneService)
@@ -384,7 +385,6 @@
                       % (iScheduleResourceClass,))
 
         ischedule = iScheduleResourceClass(
-            NotFilePath(isfile=True),
             root,
         )
         root.putChild("ischedule", ischedule)

Modified: CalendarServer/trunk/calendarserver/tools/export.py
===================================================================
--- CalendarServer/trunk/calendarserver/tools/export.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/calendarserver/tools/export.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -39,8 +39,9 @@
 from twistedcaldav.config import ConfigurationError
 from twistedcaldav.ical import Component as iComponent, Property as iProperty
 from twistedcaldav.ical import iCalendarProductID
-from twistedcaldav.resource import isCalendarCollectionResource
-from twistedcaldav.static import CalDAVFile, CalendarHomeFile
+from twistedcaldav.resource import isCalendarCollectionResource,\
+    CalendarHomeResource
+from twistedcaldav.static import CalDAVFile
 from twistedcaldav.directory.directory import DirectoryService
 
 from calendarserver.tools.util import UsageError
@@ -123,7 +124,7 @@
         elif opt in ("-H", "--home"):
             path = abspath(arg)
             parent = CalDAVFile(dirname(abspath(path)))
-            calendarHome = CalendarHomeFile(arg, parent, dummyDirectoryRecord)
+            calendarHome = CalendarHomeResource(arg, parent, dummyDirectoryRecord)
             checkExists(calendarHome)
             calendarHomes.add(calendarHome)
 

Modified: CalendarServer/trunk/calendarserver/tools/util.py
===================================================================
--- CalendarServer/trunk/calendarserver/tools/util.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/calendarserver/tools/util.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -22,7 +22,7 @@
     "booleanArgument",
 ]
 
-import os, sys
+import os
 from time import sleep
 import socket
 from pwd import getpwnam
@@ -38,9 +38,8 @@
 from twistedcaldav.config import config, ConfigurationError
 from twistedcaldav.directory import augment, calendaruserproxy
 from twistedcaldav.directory.aggregate import AggregateDirectoryService
-from twistedcaldav.directory.directory import DirectoryService, DirectoryRecord, DirectoryError
+from twistedcaldav.directory.directory import DirectoryService, DirectoryRecord
 from twistedcaldav.notify import NotifierFactory
-from twistedcaldav.static import CalendarHomeProvisioningFile
 from twistedcaldav.stdconfig import DEFAULT_CONFIG_FILE
 
 from txdav.common.datastore.file import CommonDataStore
@@ -77,13 +76,13 @@
                     notifierFactory, True, False)
 
                 #
-                # Instantiating a CalendarHomeProvisioningResource with a directory
+                # Instantiating a DirectoryCalendarHomeProvisioningResource with a directory
                 # will register it with the directory (still smells like a hack).
                 #
                 # 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/", _newStore)
+                from twistedcaldav.directory.calendar import DirectoryCalendarHomeProvisioningResource
+                DirectoryCalendarHomeProvisioningResource(self, "/calendars/", _newStore)
 
                 from twistedcaldav.directory.principal import DirectoryPrincipalProvisioningResource
                 self._principalCollection = DirectoryPrincipalProvisioningResource("/principals/", self)
@@ -152,8 +151,8 @@
     # Need a data store
     _newStore = CommonDataStore(FilePath(config.DocumentRoot), True, False)
 
-    calendarCollection = CalendarHomeProvisioningFile(
-        os.path.join(config.DocumentRoot, "calendars"),
+    from twistedcaldav.directory.calendar import DirectoryCalendarHomeProvisioningResource
+    calendarCollection = DirectoryCalendarHomeProvisioningResource(
         aggregate, "/calendars/",
         _newStore,
     )

Modified: CalendarServer/trunk/calendarserver/tools/warmup.py
===================================================================
--- CalendarServer/trunk/calendarserver/tools/warmup.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/calendarserver/tools/warmup.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -33,8 +33,9 @@
 from os.path import dirname, abspath
 
 from twistedcaldav.config import ConfigurationError
-from twistedcaldav.resource import isPseudoCalendarCollectionResource
-from twistedcaldav.static import CalDAVFile, CalendarHomeFile
+from twistedcaldav.resource import isPseudoCalendarCollectionResource,\
+    CalendarHomeResource
+from twistedcaldav.static import CalDAVFile
 from twistedcaldav.directory.directory import DirectoryService
 
 from calendarserver.tools.util import loadConfig, getDirectory, dummyDirectoryRecord
@@ -104,7 +105,7 @@
         elif opt in ("-H", "--home"):
             path = abspath(arg)
             parent = CalDAVFile(dirname(abspath(path)))
-            calendarHome = CalendarHomeFile(arg, parent, dummyDirectoryRecord)
+            calendarHome = CalendarHomeResource(arg, parent, dummyDirectoryRecord)
             checkExists(calendarHome)
             calendarHomes.add(calendarHome)
 

Modified: CalendarServer/trunk/twext/web2/dav/method/acl.py
===================================================================
--- CalendarServer/trunk/twext/web2/dav/method/acl.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/twext/web2/dav/method/acl.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -45,8 +45,8 @@
     """
     Respond to a ACL request. (RFC 3744, section 8.1)
     """
-    if not self.fp.exists():
-        log.err("File not found: %s" % (self.fp.path,))
+    if not self.exists():
+        log.err("File not found: %s" % (self,))
         yield responsecode.NOT_FOUND
         return
 

Modified: CalendarServer/trunk/twext/web2/dav/method/copymove.py
===================================================================
--- CalendarServer/trunk/twext/web2/dav/method/copymove.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/twext/web2/dav/method/copymove.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -152,7 +152,7 @@
     #
     # Let's play it safe for now and ignore broken clients.
     #
-    if self.fp.isdir() and depth != "infinity":
+    if self.isCollection() and depth != "infinity":
         msg = "Client sent illegal depth header value for MOVE: %s" % (depth,)
         log.err(msg)
         raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, msg))
@@ -192,7 +192,7 @@
     #
 
     if not self.exists():
-        log.err("File not found: %s" % (self.fp.path,))
+        log.err("File not found: %s" % (self,))
         raise HTTPError(StatusResponse(
             responsecode.NOT_FOUND,
             "Source resource %s not found." % (request.uri,)
@@ -250,7 +250,7 @@
 
     if destination.exists() and not overwrite:
         log.err("Attempt to %s onto existing file without overwrite flag enabled: %s"
-                % (request.method, destination.fp.path))
+                % (request.method, destination))
         raise HTTPError(StatusResponse(
             responsecode.PRECONDITION_FAILED,
             "Destination %s already exists." % (destination_uri,)
@@ -260,7 +260,7 @@
     # Make sure destination's parent exists
     #
 
-    if not destination.fp.parent().isdir():
+    if not destination.parent().isCollection():
         log.err("Attempt to %s to a resource with no parent: %s"
                 % (request.method, destination.fp.path))
         raise HTTPError(StatusResponse(responsecode.CONFLICT, "No parent collection."))

Modified: CalendarServer/trunk/twext/web2/dav/method/delete.py
===================================================================
--- CalendarServer/trunk/twext/web2/dav/method/delete.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/twext/web2/dav/method/delete.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -45,8 +45,8 @@
     """
     Respond to a DELETE request. (RFC 2518, section 8.6)
     """
-    if not self.fp.exists():
-        log.err("File not found: %s" % (self.fp.path,))
+    if not self.exists():
+        log.err("File not found: %s" % (self,))
         raise HTTPError(responsecode.NOT_FOUND)
 
     depth = request.headers.getHeader("depth", "infinity")

Modified: CalendarServer/trunk/twext/web2/dav/method/delete_common.py
===================================================================
--- CalendarServer/trunk/twext/web2/dav/method/delete_common.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/twext/web2/dav/method/delete_common.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -41,8 +41,8 @@
     """
     Handle a resource delete with proper quota etc updates
     """
-    if not resource.fp.exists():
-        log.err("File not found: %s" % (resource.fp.path,))
+    if not resource.exists():
+        log.err("File not found: %s" % (resource,))
         raise HTTPError(responsecode.NOT_FOUND)
 
     # Do quota checks before we start deleting things

Modified: CalendarServer/trunk/twext/web2/dav/method/get.py
===================================================================
--- CalendarServer/trunk/twext/web2/dav/method/get.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/twext/web2/dav/method/get.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -36,17 +36,17 @@
 
 def http_OPTIONS(self, request):
     d = authorize(self, request)
-    d.addCallback(lambda _: super(twext.web2.dav.static.DAVFile, self).http_OPTIONS(request))
+    d.addCallback(lambda _: super(twext.web2.dav.resource.DAVResource, self).http_OPTIONS(request))
     return d
 
 def http_HEAD(self, request):
     d = authorize(self, request)
-    d.addCallback(lambda _: super(twext.web2.dav.static.DAVFile, self).http_HEAD(request))
+    d.addCallback(lambda _: super(twext.web2.dav.resource.DAVResource, self).http_HEAD(request))
     return d
 
 def http_GET(self, request):
     d = authorize(self, request)
-    d.addCallback(lambda _: super(twext.web2.dav.static.DAVFile, self).http_GET(request))
+    d.addCallback(lambda _: super(twext.web2.dav.resource.DAVResource, self).http_GET(request))
     return d
 
 def authorize(self, request):

Modified: CalendarServer/trunk/twext/web2/dav/method/mkcol.py
===================================================================
--- CalendarServer/trunk/twext/web2/dav/method/mkcol.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/twext/web2/dav/method/mkcol.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -53,14 +53,14 @@
     yield x
     x.getResult()
 
-    if self.fp.exists():
+    if self.exists():
         log.err("Attempt to create collection where file exists: %s"
-                % (self.fp.path,))
+                % (self,))
         raise HTTPError(responsecode.NOT_ALLOWED)
 
     if not parent.isCollection():
         log.err("Attempt to create collection with non-collection parent: %s"
-                % (self.fp.path,))
+                % (self,))
         raise HTTPError(StatusResponse(
             responsecode.CONFLICT,
             "Parent resource is not a collection."

Modified: CalendarServer/trunk/twext/web2/dav/method/propfind.py
===================================================================
--- CalendarServer/trunk/twext/web2/dav/method/propfind.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/twext/web2/dav/method/propfind.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -51,7 +51,7 @@
     Respond to a PROPFIND request. (RFC 2518, section 8.1)
     """
     if not self.exists():
-        log.err("File not found: %s" % (self.fp.path,))
+        log.err("File not found: %s" % (self,))
         raise HTTPError(responsecode.NOT_FOUND)
 
     #

Modified: CalendarServer/trunk/twext/web2/dav/method/proppatch.py
===================================================================
--- CalendarServer/trunk/twext/web2/dav/method/proppatch.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/twext/web2/dav/method/proppatch.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -46,8 +46,8 @@
     """
     Respond to a PROPPATCH request. (RFC 2518, section 8.2)
     """
-    if not self.fp.exists():
-        log.err("File not found: %s" % (self.fp.path,))
+    if not self.exists():
+        log.err("File not found: %s" % (self.path,))
         raise HTTPError(responsecode.NOT_FOUND)
 
     x = waitForDeferred(self.authorize(request, (davxml.WriteProperties(),)))

Modified: CalendarServer/trunk/twext/web2/dav/method/put.py
===================================================================
--- CalendarServer/trunk/twext/web2/dav/method/put.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/twext/web2/dav/method/put.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -93,7 +93,7 @@
     """
     Respond to a PUT request. (RFC 2518, section 8.7)
     """
-    log.msg("Writing request stream to %s" % (self.fp.path,))
+    log.msg("Writing request stream to %s" % (self,))
 
     #
     # Don't pass in the request URI, since PUT isn't specified to be able

Modified: CalendarServer/trunk/twext/web2/dav/method/report.py
===================================================================
--- CalendarServer/trunk/twext/web2/dav/method/report.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/twext/web2/dav/method/report.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -64,8 +64,8 @@
     """
     Respond to a REPORT request. (RFC 3253, section 3.6)
     """
-    if not self.fp.exists():
-        log.err("File not found: %s" % (self.fp.path,))
+    if not self.exists():
+        log.err("File not found: %s" % (self,))
         raise HTTPError(responsecode.NOT_FOUND)
 
     #

Modified: CalendarServer/trunk/twext/web2/dav/resource.py
===================================================================
--- CalendarServer/trunk/twext/web2/dav/resource.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/twext/web2/dav/resource.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -1356,7 +1356,7 @@
         This implementation returns a supported privilege set
         containing only the DAV:all privilege.
         """
-        return succeed(allPrivilegeSet)
+        return succeed(davPrivilegeSet)
 
     def currentPrivileges(self, request):
         """

Modified: CalendarServer/trunk/twext/web2/dav/test/util.py
===================================================================
--- CalendarServer/trunk/twext/web2/dav/test/util.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/twext/web2/dav/test/util.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -93,6 +93,9 @@
 
         return self._dead_properties
 
+    def parent(self):
+        return TestFile(self.fp.parent())
+
 class TestCase (unittest.TestCase):
     resource_class = TestFile
 

Modified: CalendarServer/trunk/twext/web2/server.py
===================================================================
--- CalendarServer/trunk/twext/web2/server.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/twext/web2/server.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -445,6 +445,13 @@
         self._urlsByResource[resource] = url
         return resource
 
+    def _forgetResource(self, resource, url):
+        """
+        Remember the URL of a visited resource.
+        """
+        del self._resourcesByURL[url]
+        del self._urlsByResource[resource]
+
     def urlForResource(self, resource):
         """
         Looks up the URL of the given resource if this resource was found while

Copied: CalendarServer/trunk/twistedcaldav/bind.py (from rev 5981, CalendarServer/branches/new-store-no-caldavfile-2/twistedcaldav/bind.py)
===================================================================
--- CalendarServer/trunk/twistedcaldav/bind.py	                        (rev 0)
+++ CalendarServer/trunk/twistedcaldav/bind.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -0,0 +1,35 @@
+##
+# 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.
+##
+
+"""
+Bind methods.
+Have to have this in a separate module for now.
+"""
+
+from twext.web2.dav.util import bindMethods
+
+##
+# Attach methods
+##
+
+def doBind():
+    import twext.web2.dav.method
+    from twext.web2.dav.resource import DAVResource
+    bindMethods(twext.web2.dav.method, DAVResource)
+
+    import twistedcaldav.method
+    from twistedcaldav.resource import CalDAVResource
+    bindMethods(twistedcaldav.method, CalDAVResource)

Modified: CalendarServer/trunk/twistedcaldav/customxml.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/customxml.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/twistedcaldav/customxml.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -325,7 +325,7 @@
 
 class PubSubXMPPURIProperty (davxml.WebDAVTextElement):
     """
-    A calendarhomefile property to indicate the pubsub XMPP URI to subscribe to
+    A calendar home property to indicate the pubsub XMPP URI to subscribe to
     for notifications.
     """
     namespace = calendarserver_namespace
@@ -335,7 +335,7 @@
 
 class PubSubHeartbeatProperty (davxml.WebDAVElement):
     """
-    A calendarhomefile property to indicate the pubsub XMPP URI to subscribe to
+    A calendar home property to indicate the pubsub XMPP URI to subscribe to
     for server heartbeats.
     """
     namespace = calendarserver_namespace
@@ -361,7 +361,7 @@
 
 class PubSubXMPPServerProperty (davxml.WebDAVTextElement):
     """
-    A calendarhomefile property to indicate the pubsub XMPP hostname to
+    A calendar home property to indicate the pubsub XMPP hostname to
     contact for notifications.
     """
     namespace = calendarserver_namespace
@@ -973,6 +973,14 @@
         (calendarserver_namespace, "invite-reply")          : (0, None),
     }
 
+class Link (davxml.WebDAVEmptyElement):
+    """
+    Denotes a linked resource.
+    """
+    namespace = calendarserver_namespace
+    name = "link"
+
+
 ##
 # Extensions to davxml.ResourceType
 ##
@@ -988,3 +996,4 @@
 davxml.ResourceType.sharedownercalendar = davxml.ResourceType(davxml.Collection(), caldavxml.Calendar(), SharedOwner())
 davxml.ResourceType.sharedcalendar = davxml.ResourceType(davxml.Collection(), caldavxml.Calendar(), Shared())
 davxml.ResourceType.sharedaddressbook = davxml.ResourceType(davxml.Collection(), carddavxml.AddressBook(), Shared())
+davxml.ResourceType.link = davxml.ResourceType(Link())

Modified: CalendarServer/trunk/twistedcaldav/directory/addressbook.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/addressbook.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/twistedcaldav/directory/addressbook.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -20,51 +20,62 @@
 
 __all__ = [
     "uidsResourceName",
-   #"DirectoryAddressBookProvisioningResource",
     "DirectoryAddressBookHomeProvisioningResource",
     "DirectoryAddressBookHomeTypeProvisioningResource",
     "DirectoryAddressBookHomeUIDProvisioningResource",
     "DirectoryAddressBookHomeResource",
-    "GlobalAddressBookResource",
 ]
 
 from twext.python.log import Logger
 from twext.web2 import responsecode
-from twext.web2.dav import davxml
-from twext.web2.dav.resource import TwistedACLInheritable
 from twext.web2.dav.util import joinURL
 from twext.web2.http import HTTPError
+from twext.web2.http_headers import ETag, MimeType
 
-from twisted.internet.defer import succeed
-
 from twistedcaldav.config import config
 from twistedcaldav.directory.idirectory import IDirectoryService
-from twistedcaldav.directory.resource import AutoProvisioningResourceMixIn
-from twistedcaldav.extensions import ReadOnlyResourceMixIn, DAVResource
-from twistedcaldav.notifications import NotificationCollectionResource
-from twistedcaldav.resource import CalDAVResource, CalDAVComplianceMixIn
+from twistedcaldav.directory.resource import DirectoryReverseProxyResource
+from twistedcaldav.directory.util import transactionFromRequest
+from twistedcaldav.extensions import ReadOnlyResourceMixIn, DAVResource,\
+    DAVResourceWithChildrenMixin
+from twistedcaldav.resource import AddressBookHomeResource
 
+from uuid import uuid4
+
 log = Logger()
 
 # Use __underbars__ convention to avoid conflicts with directory resource types.
 uidsResourceName = "__uids__"
 
+# FIXME: copied from resource.py to avoid circular dependency
+class CalDAVComplianceMixIn(object):
+    def davComplianceClasses(self):
+        return (
+            tuple(super(CalDAVComplianceMixIn, self).davComplianceClasses())
+            + config.CalDAVComplianceClasses
+        )
 
 class DirectoryAddressBookProvisioningResource (
-    AutoProvisioningResourceMixIn,
     ReadOnlyResourceMixIn,
     CalDAVComplianceMixIn,
+    DAVResourceWithChildrenMixin,
     DAVResource,
 ):
     def defaultAccessControlList(self):
         return config.ProvisioningResourceACL
 
+    def etag(self):
+        return ETag(str(uuid4()))
 
+    def contentType(self):
+        return MimeType("httpd", "unix-directory")
+
+
 class DirectoryAddressBookHomeProvisioningResource (DirectoryAddressBookProvisioningResource):
     """
     Resource which provisions address book home collections as needed.    
     """
-    def __init__(self, directory, url):
+    def __init__(self, directory, url, store):
         """
         @param directory: an L{IDirectoryService} to provision address books from.
         @param url: the canonical URL for the resource.
@@ -72,10 +83,11 @@
         assert directory is not None
         assert url.endswith("/"), "Collection URL must end in '/'"
 
-        DAVResource.__init__(self)
+        super(DirectoryAddressBookHomeProvisioningResource, self).__init__()
 
         self.directory = IDirectoryService(directory)
         self._url = url
+        self._newStore = store
 
         # FIXME: Smells like a hack
         directory.addressBookHomesCollection = self
@@ -83,23 +95,14 @@
         #
         # Create children
         #
-        def provisionChild(name):
-            self.putChild(name, self.provisionChild(name))
-
         for recordType in self.directory.recordTypes():
-            provisionChild(recordType)
+            self.putChild(recordType, DirectoryAddressBookHomeTypeProvisioningResource(self, recordType))
 
-        provisionChild(uidsResourceName)
+        self.putChild(uidsResourceName, DirectoryAddressBookHomeUIDProvisioningResource(self))
 
-    def provisionChild(self, recordType):
-        raise NotImplementedError("Subclass must implement provisionChild()")
-
     def url(self):
         return self._url
 
-    def getChild(self, name):
-        return self.putChildren.get(name, None)
-
     def listChildren(self):
         return self.directory.recordTypes()
 
@@ -127,7 +130,10 @@
     def isCollection(self):
         return True
 
+    def displayName(self):
+        return "addressbooks"
 
+
 class DirectoryAddressBookHomeTypeProvisioningResource (DirectoryAddressBookProvisioningResource):
     """
     Resource which provisions address book home collections of a specific
@@ -141,7 +147,7 @@
         assert parent is not None
         assert recordType is not None
 
-        DAVResource.__init__(self)
+        super(DirectoryAddressBookHomeTypeProvisioningResource, self).__init__()
 
         self.directory = parent.directory
         self.recordType = recordType
@@ -151,7 +157,6 @@
         return joinURL(self._parent.url(), self.recordType)
 
     def locateChild(self, request, segments):
-        self.provision()
         name = segments[0]
         if name == "":
             return (self, segments[1:])
@@ -177,8 +182,8 @@
             # Not a listable collection
             raise HTTPError(responsecode.FORBIDDEN)
 
-    def createSimilarFile(self, path):
-        raise HTTPError(responsecode.NOT_FOUND)
+    def makeChild(self, name):
+        return None
 
     ##
     # DAV
@@ -187,6 +192,9 @@
     def isCollection(self):
         return True
 
+    def displayName(self):
+        return self.recordType
+
     ##
     # ACL
     ##
@@ -199,13 +207,14 @@
 
 
 class DirectoryAddressBookHomeUIDProvisioningResource (DirectoryAddressBookProvisioningResource):
+
     def __init__(self, parent):
         """
         @param parent: the parent of this resource
         """
         assert parent is not None
 
-        DAVResource.__init__(self)
+        super(DirectoryAddressBookHomeUIDProvisioningResource, self).__init__()
 
         self.directory = parent.directory
         self.parent = parent
@@ -213,6 +222,18 @@
     def url(self):
         return joinURL(self.parent.url(), uidsResourceName)
 
+    def locateChild(self, request, segments):
+
+        name = segments[0]
+        if name == "":
+            return (self, ())
+
+        record = self.directory.recordWithUID(name)
+        if record:
+            return (self.homeResourceForRecord(record, request), segments[1:])
+        else:
+            return (None, ())
+
     def getChild(self, name, record=None):
         raise NotImplementedError("DirectoryAddressBookHomeUIDProvisioningResource.getChild no longer exists.")
 
@@ -220,6 +241,29 @@
         # Not a listable collection
         raise HTTPError(responsecode.FORBIDDEN)
 
+    def homeResourceForRecord(self, record, request):
+
+        transaction = transactionFromRequest(request, self.parent._newStore)
+
+        name = record.uid
+
+        if record is None:
+            self.log_msg("No directory record with GUID %r" % (name,))
+            return None
+
+        if not record.enabledForAddressBooks:
+            self.log_msg("Directory record %r is not enabled for address books" % (record,))
+            return None
+
+        assert len(name) > 4, "Directory record has an invalid GUID: %r" % (name,)
+        
+        if record.locallyHosted():
+            child = DirectoryAddressBookHomeResource(self, record, transaction)
+        else:
+            child = DirectoryReverseProxyResource(self, record)
+
+        return child
+
     ##
     # DAV
     ##
@@ -227,6 +271,9 @@
     def isCollection(self):
         return True
 
+    def displayName(self):
+        return uidsResourceName
+
     ##
     # ACL
     ##
@@ -238,175 +285,20 @@
         return self.parent.principalForRecord(record)
 
 
-class DirectoryAddressBookHomeResource (AutoProvisioningResourceMixIn, CalDAVResource):
+class DirectoryAddressBookHomeResource (AddressBookHomeResource):
     """
     Address book home collection resource.
     """
-    def __init__(self, parent, record):
+    def __init__(self, parent, record, transaction):
         """
         @param path: the path to the file which will back the resource.
         """
         assert parent is not None
         assert record is not None
+        assert transaction is not None
 
-        CalDAVResource.__init__(self)
-
         self.record = record
-        self.parent = parent
+        super(DirectoryAddressBookHomeResource, self).__init__(parent, record.uid, transaction)
 
-        childlist = ()
-        if config.Sharing.Enabled and config.Sharing.AddressBooks.Enabled and not config.Sharing.Calendars.Enabled:
-            childlist += (
-                ("notification", NotificationCollectionResource),
-            )
-        for name, cls in childlist:
-            child = self.provisionChild(name)
-            assert isinstance(child, cls), "Child %r is not a %s: %r" % (name, cls.__name__, child)
-            self.putChild(name, child)
-
-    def provisionDefaultAddressBooks(self):
-
-        # Disable notifications during provisioning
-        if hasattr(self, "clientNotifier"):
-            self.clientNotifier.disableNotify()
-
-        try:
-            self.provision()
-    
-            childName = "addressbook"
-            child = self.provisionChild(childName)
-            assert isinstance(child, CalDAVResource), "Child %r is not a %s: %r" % (childName, CalDAVResource.__name__, child) #@UndefinedVariable
-
-            d = child.createAddressBookCollection()
-        except:
-            # We want to make sure to re-enable notifications, so do so
-            # if there is an immediate exception above, or via errback, below
-            if hasattr(self, "clientNotifier"):
-                self.clientNotifier.enableNotify(None)
-            raise
-
-        # Re-enable notifications
-        if hasattr(self, "clientNotifier"):
-            d.addCallback(self.clientNotifier.enableNotify)
-            d.addErrback(self.clientNotifier.enableNotify)
-
-        return d
-
-    def provisionChild(self, name):
-        raise NotImplementedError("Subclass must implement provisionChild()")
-
-    def url(self):
-        return joinURL(self.parent.url(), self.record.uid, "/")
-
-    def canonicalURL(self, request):
-        return succeed(self.url())
-    ##
-    # DAV
-    ##
-    
-    def isCollection(self):
-        return True
-
-    def http_COPY(self, request):
-        return responsecode.FORBIDDEN
-
-    ##
-    # ACL
-    ##
-
-    def owner(self, request):
-        return succeed(davxml.HRef(self.principalForRecord().principalURL()))
-
-    def ownerPrincipal(self, request):
-        return succeed(self.principalForRecord())
-
-    def resourceOwnerPrincipal(self, request):
-        return succeed(self.principalForRecord())
-
-    def defaultAccessControlList(self):
-        myPrincipal = self.principalForRecord()
-
-        aces = (
-            # Inheritable DAV:all access for the resource's associated principal.
-            davxml.ACE(
-                davxml.Principal(davxml.HRef(myPrincipal.principalURL())),
-                davxml.Grant(davxml.Privilege(davxml.All())),
-                davxml.Protected(),
-                TwistedACLInheritable(),
-            ),
-        )
-
-        # Give read access to config.ReadPrincipals
-        aces += config.ReadACEs
-
-        # Give all access to config.AdminPrincipals
-        aces += config.AdminACEs
-        
-        return davxml.ACL(*aces)
-
-    def accessControlList(self, request, inheritance=True, expanding=False, inherited_aces=None):
-        # Permissions here are fixed, and are not subject to inheritance rules, etc.
-        return succeed(self.defaultAccessControlList())
-
-    def principalCollections(self):
-        return self.parent.principalCollections()
-
     def principalForRecord(self):
         return self.parent.principalForRecord(self.record)
-
-    ##
-    # Quota
-    ##
-
-    def hasQuotaRoot(self, request):
-        """
-        @return: a C{True} if this resource has quota root, C{False} otherwise.
-        """
-        return config.UserQuota != 0
-    
-    def quotaRoot(self, request):
-        """
-        @return: a C{int} containing the maximum allowed bytes if this collection
-            is quota-controlled, or C{None} if not quota controlled.
-        """
-        return config.UserQuota if config.UserQuota != 0 else None
-
-class GlobalAddressBookResource (CalDAVResource):
-    """
-    Global address book. All we care about is making sure permissions are setup.
-    """
-
-    def resourceType(self, request):
-        return succeed(davxml.ResourceType.sharedaddressbook)
-
-    def defaultAccessControlList(self):
-
-        aces = (
-            davxml.ACE(
-                davxml.Principal(davxml.Authenticated()),
-                davxml.Grant(
-                    davxml.Privilege(davxml.Read()),
-                    davxml.Privilege(davxml.ReadCurrentUserPrivilegeSet()),
-                    davxml.Privilege(davxml.Write()),
-                ),
-                davxml.Protected(),
-                TwistedACLInheritable(),
-           ),
-        )
-        
-        if config.GlobalAddressBook.EnableAnonymousReadAccess:
-            aces += (
-                davxml.ACE(
-                    davxml.Principal(davxml.Unauthenticated()),
-                    davxml.Grant(
-                        davxml.Privilege(davxml.Read()),
-                    ),
-                    davxml.Protected(),
-                    TwistedACLInheritable(),
-               ),
-            )
-        return davxml.ACL(*aces)
-
-    def accessControlList(self, request, inheritance=True, expanding=False, inherited_aces=None):
-        # Permissions here are fixed, and are not subject to inheritance rules, etc.
-        return succeed(self.defaultAccessControlList())

Modified: CalendarServer/trunk/twistedcaldav/directory/aggregate.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/aggregate.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/twistedcaldav/directory/aggregate.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -83,7 +83,6 @@
 
     #
     # Define calendarHomesCollection as a property so we can set it on contained services
-    # See CalendarHomeProvisioningFile.__init__()
     #
     def _getCalendarHomesCollection(self):
         return self._calendarHomesCollection
@@ -97,7 +96,6 @@
 
     #
     # Define addressBookHomesCollection as a property so we can set it on contained services
-    # See AddressBookHomeProvisioningFile.__init__()
     #
     def _getAddressBookHomesCollection(self):
         return self._addressBookHomesCollection

Modified: CalendarServer/trunk/twistedcaldav/directory/calendar.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/calendar.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/twistedcaldav/directory/calendar.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -1,6 +1,6 @@
 # -*- test-case-name: twistedcaldav.directory.test.test_calendar -*-
 ##
-# Copyright (c) 2006-2009 Apple Inc. All rights reserved.
+# Copyright (c) 2006-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.
@@ -21,55 +21,64 @@
 
 __all__ = [
     "uidsResourceName",
-   #"DirectoryCalendarProvisioningResource",
     "DirectoryCalendarHomeProvisioningResource",
     "DirectoryCalendarHomeTypeProvisioningResource",
     "DirectoryCalendarHomeUIDProvisioningResource",
     "DirectoryCalendarHomeResource",
 ]
 
-from twisted.internet.defer import succeed
+from twext.python.log import Logger
 from twext.web2 import responsecode
-from twext.web2.dav import davxml
-from twext.web2.http import HTTPError
 from twext.web2.dav.util import joinURL
-from twext.web2.dav.resource import TwistedACLInheritable
+from twext.web2.http import HTTPError
+from twext.web2.http_headers import ETag, MimeType
 
-from twext.python.log import Logger
+from twisted.internet.defer import succeed
 
-from twistedcaldav import caldavxml
 from twistedcaldav.config import config
-from twistedcaldav.dropbox import DropBoxHomeResource
-from twistedcaldav.extensions import ReadOnlyResourceMixIn, DAVResource
-from twistedcaldav.freebusyurl import FreeBusyURLResource
-from twistedcaldav.resource import CalDAVResource, CalDAVComplianceMixIn
-from twistedcaldav.schedule import ScheduleInboxResource, ScheduleOutboxResource
 from twistedcaldav.directory.idirectory import IDirectoryService
+from twistedcaldav.directory.resource import DirectoryReverseProxyResource
+from twistedcaldav.directory.util import transactionFromRequest
 from twistedcaldav.directory.wiki import getWikiACL
-from twistedcaldav.directory.resource import AutoProvisioningResourceMixIn
-from twistedcaldav.notifications import NotificationCollectionResource
+from twistedcaldav.extensions import ReadOnlyResourceMixIn, DAVResource,\
+    DAVResourceWithChildrenMixin
+from twistedcaldav.resource import CalendarHomeResource
 
+from uuid import uuid4
+
 log = Logger()
 
 # Use __underbars__ convention to avoid conflicts with directory resource types.
 uidsResourceName = "__uids__"
 
+# FIXME: copied from resource.py to avoid circular dependency
+class CalDAVComplianceMixIn(object):
+    def davComplianceClasses(self):
+        return (
+            tuple(super(CalDAVComplianceMixIn, self).davComplianceClasses())
+            + config.CalDAVComplianceClasses
+        )
 
 class DirectoryCalendarProvisioningResource (
-    AutoProvisioningResourceMixIn,
     ReadOnlyResourceMixIn,
     CalDAVComplianceMixIn,
+    DAVResourceWithChildrenMixin,
     DAVResource,
 ):
     def defaultAccessControlList(self):
         return config.ProvisioningResourceACL
 
+    def etag(self):
+        return ETag(str(uuid4()))
 
+    def contentType(self):
+        return MimeType("httpd", "unix-directory")
+
 class DirectoryCalendarHomeProvisioningResource (DirectoryCalendarProvisioningResource):
     """
     Resource which provisions calendar home collections as needed.    
     """
-    def __init__(self, directory, url):
+    def __init__(self, directory, url, store):
         """
         @param directory: an L{IDirectoryService} to provision calendars from.
         @param url: the canonical URL for the resource.
@@ -77,10 +86,11 @@
         assert directory is not None
         assert url.endswith("/"), "Collection URL must end in '/'"
 
-        DAVResource.__init__(self)
+        super(DirectoryCalendarHomeProvisioningResource, self).__init__()
 
         self.directory = IDirectoryService(directory)
         self._url = url
+        self._newStore = store
 
         # FIXME: Smells like a hack
         directory.calendarHomesCollection = self
@@ -88,23 +98,14 @@
         #
         # Create children
         #
-        def provisionChild(name):
-            self.putChild(name, self.provisionChild(name))
-
         for recordType in self.directory.recordTypes():
-            provisionChild(recordType)
+            self.putChild(recordType, DirectoryCalendarHomeTypeProvisioningResource(self, recordType))
 
-        provisionChild(uidsResourceName)
+        self.putChild(uidsResourceName, DirectoryCalendarHomeUIDProvisioningResource(self))
 
-    def provisionChild(self, recordType):
-        raise NotImplementedError("Subclass must implement provisionChild()")
-
     def url(self):
         return self._url
 
-    def getChild(self, name):
-        return self.putChildren.get(name, None)
-
     def listChildren(self):
         return self.directory.recordTypes()
 
@@ -132,6 +133,8 @@
     def isCollection(self):
         return True
 
+    def displayName(self):
+        return "calendars"
 
 class DirectoryCalendarHomeTypeProvisioningResource (DirectoryCalendarProvisioningResource):
     """
@@ -146,7 +149,7 @@
         assert parent is not None
         assert recordType is not None
 
-        DAVResource.__init__(self)
+        super(DirectoryCalendarHomeTypeProvisioningResource, self).__init__()
 
         self.directory = parent.directory
         self.recordType = recordType
@@ -156,7 +159,6 @@
         return joinURL(self._parent.url(), self.recordType)
 
     def locateChild(self, request, segments):
-        self.provision()
         name = segments[0]
         if name == "":
             return (self, segments[1:])
@@ -182,8 +184,8 @@
             # Not a listable collection
             raise HTTPError(responsecode.FORBIDDEN)
 
-    def createSimilarFile(self, path):
-        raise HTTPError(responsecode.NOT_FOUND)
+    def makeChild(self, name):
+        return None
 
     ##
     # DAV
@@ -192,6 +194,9 @@
     def isCollection(self):
         return True
 
+    def displayName(self):
+        return self.recordType
+
     ##
     # ACL
     ##
@@ -202,15 +207,15 @@
     def principalForRecord(self, record):
         return self._parent.principalForRecord(record)
 
-
 class DirectoryCalendarHomeUIDProvisioningResource (DirectoryCalendarProvisioningResource):
+
     def __init__(self, parent):
         """
         @param parent: the parent of this resource
         """
         assert parent is not None
 
-        DAVResource.__init__(self)
+        super(DirectoryCalendarHomeUIDProvisioningResource, self).__init__()
 
         self.directory = parent.directory
         self.parent = parent
@@ -218,6 +223,18 @@
     def url(self):
         return joinURL(self.parent.url(), uidsResourceName)
 
+    def locateChild(self, request, segments):
+
+        name = segments[0]
+        if name == "":
+            return (self, ())
+
+        record = self.directory.recordWithUID(name)
+        if record:
+            return (self.homeResourceForRecord(record, request), segments[1:])
+        else:
+            return (None, ())
+
     def getChild(self, name, record=None):
         raise NotImplementedError("DirectoryCalendarProvisioningResource.getChild no longer exists.")
 
@@ -225,6 +242,28 @@
         # Not a listable collection
         raise HTTPError(responsecode.FORBIDDEN)
 
+    def homeResourceForRecord(self, record, request):
+
+        transaction = transactionFromRequest(request, self.parent._newStore)
+        name = record.uid
+
+        if record is None:
+            self.log_msg("No directory record with GUID %r" % (name,))
+            return None
+
+        if not record.enabledForCalendaring:
+            self.log_msg("Directory record %r is not enabled for calendaring" % (record,))
+            return None
+
+        assert len(name) > 4, "Directory record has an invalid GUID: %r" % (name,)
+        
+        if record.locallyHosted():
+            child = DirectoryCalendarHomeResource(self, record, transaction)
+        else:
+            child = DirectoryReverseProxyResource(self, record)
+
+        return child
+
     ##
     # DAV
     ##
@@ -232,6 +271,9 @@
     def isCollection(self):
         return True
 
+    def displayName(self):
+        return uidsResourceName
+
     ##
     # ACL
     ##
@@ -243,129 +285,22 @@
         return self.parent.principalForRecord(record)
 
 
-class DirectoryCalendarHomeResource (AutoProvisioningResourceMixIn, CalDAVResource):
+class DirectoryCalendarHomeResource (CalendarHomeResource):
     """
     Calendar home collection resource.
     """
-    def __init__(self, parent, record):
+    def __init__(self, parent, record, transaction):
         """
         @param path: the path to the file which will back the resource.
         """
         assert parent is not None
         assert record is not None
+        assert transaction is not None
 
-        CalDAVResource.__init__(self)
-
         self.record = record
-        self.parent = parent
+        super(DirectoryCalendarHomeResource, self).__init__(parent, record.uid, transaction)
 
-        # Cache children which must be of a specific type
-        childlist = (
-            ("inbox" , ScheduleInboxResource ),
-            ("outbox", ScheduleOutboxResource),
-        )
-        if config.EnableDropBox:
-            childlist += (
-                ("dropbox", DropBoxHomeResource),
-            )
-        if config.FreeBusyURL.Enabled:
-            childlist += (
-                ("freebusy", FreeBusyURLResource),
-            )
-        if config.Sharing.Enabled and config.Sharing.Calendars.Enabled:
-            childlist += (
-                ("notification", NotificationCollectionResource),
-            )
-        for name, cls in childlist:
-            child = self.provisionChild(name)
-            # assert isinstance(child, cls), "Child %r is not a %s: %r" % (name, cls.__name__, child)
-            self.putChild(name, child)
-
-
-    def provisionChild(self, name):
-        raise NotImplementedError("Subclass must implement provisionChild()")
-
-    def url(self):
-        return joinURL(self.parent.url(), self.record.uid, "/")
-
-    def canonicalURL(self, request):
-        return succeed(self.url())
-
-    ##
-    # DAV
-    ##
-    
-    def isCollection(self):
-        return True
-
-    def http_COPY(self, request):
-        return responsecode.FORBIDDEN
-
-    ##
-    # ACL
-    ##
-
-    def owner(self, request):
-        return succeed(davxml.HRef(self.principalForRecord().principalURL()))
-
-    def ownerPrincipal(self, request):
-        return succeed(self.principalForRecord())
-
-    def resourceOwnerPrincipal(self, request):
-        return succeed(self.principalForRecord())
-
-    def defaultAccessControlList(self):
-        myPrincipal = self.principalForRecord()
-
-        aces = (
-            # Inheritable DAV:all access for the resource's associated principal.
-            davxml.ACE(
-                davxml.Principal(davxml.HRef(myPrincipal.principalURL())),
-                davxml.Grant(davxml.Privilege(davxml.All())),
-                davxml.Protected(),
-                TwistedACLInheritable(),
-            ),
-            # Inheritable CALDAV:read-free-busy access for authenticated users.
-            davxml.ACE(
-                davxml.Principal(davxml.Authenticated()),
-                davxml.Grant(davxml.Privilege(caldavxml.ReadFreeBusy())),
-                TwistedACLInheritable(),
-            ),
-        )
-
-        # Give read access to config.ReadPrincipals
-        aces += config.ReadACEs
-
-        # Give all access to config.AdminPrincipals
-        aces += config.AdminACEs
-        
-        if config.EnableProxyPrincipals:
-            aces += (
-                # DAV:read/DAV:read-current-user-privilege-set access for this principal's calendar-proxy-read users.
-                davxml.ACE(
-                    davxml.Principal(davxml.HRef(joinURL(myPrincipal.principalURL(), "calendar-proxy-read/"))),
-                    davxml.Grant(
-                        davxml.Privilege(davxml.Read()),
-                        davxml.Privilege(davxml.ReadCurrentUserPrivilegeSet()),
-                    ),
-                    davxml.Protected(),
-                    TwistedACLInheritable(),
-                ),
-                # DAV:read/DAV:read-current-user-privilege-set/DAV:write access for this principal's calendar-proxy-write users.
-                davxml.ACE(
-                    davxml.Principal(davxml.HRef(joinURL(myPrincipal.principalURL(), "calendar-proxy-write/"))),
-                    davxml.Grant(
-                        davxml.Privilege(davxml.Read()),
-                        davxml.Privilege(davxml.ReadCurrentUserPrivilegeSet()),
-                        davxml.Privilege(davxml.Write()),
-                    ),
-                    davxml.Protected(),
-                    TwistedACLInheritable(),
-                ),
-            )
-
-        return davxml.ACL(*aces)
-
+    # Special ACLs for Wiki service
     def accessControlList(self, request, inheritance=True, expanding=False, inherited_aces=None):
         def gotACL(wikiACL):
             if wikiACL is not None:
@@ -381,29 +316,5 @@
         d.addCallback(gotACL)
         return d
 
-    def principalCollections(self):
-        return self.parent.principalCollections()
-
     def principalForRecord(self):
         return self.parent.principalForRecord(self.record)
-
-    ##
-    # Quota
-    ##
-
-    def hasQuotaRoot(self, request):
-        """
-        Always get quota root value from config.
-
-        @return: a C{True} if this resource has quota root, C{False} otherwise.
-        """
-        return config.UserQuota != 0
-    
-    def quotaRoot(self, request):
-        """
-        Always get quota root value from config.
-
-        @return: a C{int} containing the maximum allowed bytes if this collection
-            is quota-controlled, or C{None} if not quota controlled.
-        """
-        return config.UserQuota if config.UserQuota != 0 else None

Modified: CalendarServer/trunk/twistedcaldav/directory/calendaruserproxy.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/calendaruserproxy.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/twistedcaldav/directory/calendaruserproxy.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -42,11 +42,11 @@
 from twistedcaldav.config import config, fullServerPath
 from twistedcaldav.database import AbstractADBAPIDatabase, ADBAPISqliteMixin,\
     ADBAPIPostgreSQLMixin
-from twistedcaldav.extensions import DAVFile, DAVPrincipalResource
+from twistedcaldav.extensions import DAVPrincipalResource,\
+    DAVResourceWithChildrenMixin
 from twistedcaldav.extensions import ReadOnlyWritePropertiesResourceMixIn
 from twistedcaldav.memcacher import Memcacher
 from twistedcaldav.resource import CalDAVComplianceMixIn
-from twistedcaldav.directory.util import NotFilePath
 
 class PermissionsMixIn (ReadOnlyWritePropertiesResourceMixIn):
     def defaultAccessControlList(self):
@@ -80,7 +80,7 @@
         # Permissions here are fixed, and are not subject to inheritance rules, etc.
         return succeed(self.defaultAccessControlList())
 
-class CalendarUserProxyPrincipalResource (CalDAVComplianceMixIn, PermissionsMixIn, DAVPrincipalResource, DAVFile):
+class CalendarUserProxyPrincipalResource (CalDAVComplianceMixIn, PermissionsMixIn, DAVResourceWithChildrenMixin, DAVPrincipalResource):
     """
     Calendar user proxy principal resource.
     """
@@ -96,7 +96,8 @@
 
         url = joinURL(parent.principalURL(), proxyType) + slash
 
-        super(CalendarUserProxyPrincipalResource, self).__init__(NotFilePath(isdir=True), url)
+        super(CalendarUserProxyPrincipalResource, self).__init__()
+        DAVResourceWithChildrenMixin.__init__(self)
 
         self.parent      = parent
         self.proxyType   = proxyType
@@ -128,13 +129,13 @@
         """
         return ProxyDBService
 
-    def resourceType(self, request):
+    def resourceType(self):
         if self.proxyType == "calendar-proxy-read":
-            return succeed(davxml.ResourceType.calendarproxyread)
+            return davxml.ResourceType.calendarproxyread
         elif self.proxyType == "calendar-proxy-write":
-            return succeed(davxml.ResourceType.calendarproxywrite)
+            return davxml.ResourceType.calendarproxywrite
         else:
-            return super(CalendarUserProxyPrincipalResource, self).resourceType(request)
+            return super(CalendarUserProxyPrincipalResource, self).resourceType()
 
     def isProxyType(self, read_write):
         if (

Modified: CalendarServer/trunk/twistedcaldav/directory/opendirectorybacker.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/opendirectorybacker.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/twistedcaldav/directory/opendirectorybacker.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -61,7 +61,6 @@
 from twistedcaldav.memcachelock import MemcacheLock, MemcacheLockTimeoutError
 from twistedcaldav.method.put_addressbook_common import StoreAddressObjectResource
 from twistedcaldav.query import addressbookqueryfilter
-from twistedcaldav.static import CalDAVFile
 from twistedcaldav.vcard import Component, Property
 
 from xmlrpclib import datetime
@@ -398,64 +397,65 @@
                     yield updateLock.release()
                     updateLock = None
                     
-                    tmpDirLock = self._tmpDirAddressBookLock
-                    self.log_debug("blocking on lock of: \"%s\")" % self._tmpDirAddressBookLockPath)
-                    yield tmpDirLock.acquire()
-                    
-                    try:
-                        self.log_info("Filling directory address book")
-                        startTime = time.time()
-                        newAddressBook = CalDAVFile(makeTmpFilename())
-                        yield newAddressBook.createAddressBookCollection()
-                        for key, record in records.items():
-                            try:
-                                vcard = record.vCard()
-                                # make up a destination 
-
-                                fileName = unquote(record.uriName())
-                                destination = CalDAVFile(join(newAddressBook.fp.path, fileName))
-                                destination_uri =  record.hRef()
-
-                                self.log_debug("Adding \"%s\", uri=\"%s\"" % (fileName, destination_uri, ))
-                                self.log_debug("VCard text =\n%s" % (record.vCardText(), ))
-                                
-                                yield StoreAddressObjectResource( request = None,
-                                                            sourceadbk = False,
-                                                            destinationadbk = True,
-                                                            destination = destination,
-                                                            destination_uri = destination_uri,
-                                                            destinationparent = newAddressBook,
-                                                            vcard = vcard,
-                                                            indexdestination = False,
-                                                            ).run()
-                            except:
-                                self.log_info("Could not add record %s" % (record,))
-                                del records[key]
-                                newAddressBookCTag = customxml.GETCTag(str(hash(self.baseGUID + ":" + self.realmName + ":" + "".join(str(hash(records[key])) for key in records.keys()))))
-                        
-                        self.log_info("Indexing new directory address book")
-                        newAddressBook.index().recreate()
-                        elaspedTime = time.time()-startTime
-                        self.log_info("Timing: Fill address book: %.1f ms (%d vcards, %.2f vcards/sec)" % (elaspedTime*1000, len(records), len(records)/elaspedTime))
-                        
-                        updateLock = self.updateLock()
-                        self.log_debug("blocking on lock of: \"%s\")" % self._updateLockPath)
-                        yield updateLock.acquire()
-
-                        self.log_debug("Swapping in new directory address book")
-                        
-                        # move old address book out of the way
-                        if self.directoryBackedAddressBook.fp.exists():               
-                            os.rename(self.directoryBackedAddressBook.fp.path, makeTmpFilename())
-        
-                        #move new one into place
-                        os.rename(newAddressBook.fp.path, self.directoryBackedAddressBook.fp.path)
-                        self.directoryBackedAddressBook.fp.restat()
-                        
-                        self.directoryBackedAddressBook.writeDeadProperty(newAddressBookCTag)
-                    finally:
-                        self.log_debug("unlocking: \"%s\")" % self._tmpDirAddressBookLockPath)
-                        yield tmpDirLock.release()
+                    #FIXME: implement store based cache
+#                    tmpDirLock = self._tmpDirAddressBookLock
+#                    self.log_debug("blocking on lock of: \"%s\")" % self._tmpDirAddressBookLockPath)
+#                    yield tmpDirLock.acquire()
+#                    
+#                    try:
+#                        self.log_info("Filling directory address book")
+#                        startTime = time.time()
+#                        newAddressBook = CalDAVFile(makeTmpFilename())
+#                        yield newAddressBook.createAddressBookCollection()
+#                        for key, record in records.items():
+#                            try:
+#                                vcard = record.vCard()
+#                                # make up a destination 
+#
+#                                fileName = unquote(record.uriName())
+#                                destination = CalDAVFile(join(newAddressBook.fp.path, fileName))
+#                                destination_uri =  record.hRef()
+#
+#                                self.log_debug("Adding \"%s\", uri=\"%s\"" % (fileName, destination_uri, ))
+#                                self.log_debug("VCard text =\n%s" % (record.vCardText(), ))
+#                                
+#                                yield StoreAddressObjectResource( request = None,
+#                                                            sourceadbk = False,
+#                                                            destinationadbk = True,
+#                                                            destination = destination,
+#                                                            destination_uri = destination_uri,
+#                                                            destinationparent = newAddressBook,
+#                                                            vcard = vcard,
+#                                                            indexdestination = False,
+#                                                            ).run()
+#                            except:
+#                                self.log_info("Could not add record %s" % (record,))
+#                                del records[key]
+#                                newAddressBookCTag = customxml.GETCTag(str(hash(self.baseGUID + ":" + self.realmName + ":" + "".join(str(hash(records[key])) for key in records.keys()))))
+#                        
+#                        self.log_info("Indexing new directory address book")
+#                        newAddressBook.index().recreate()
+#                        elaspedTime = time.time()-startTime
+#                        self.log_info("Timing: Fill address book: %.1f ms (%d vcards, %.2f vcards/sec)" % (elaspedTime*1000, len(records), len(records)/elaspedTime))
+#                        
+#                        updateLock = self.updateLock()
+#                        self.log_debug("blocking on lock of: \"%s\")" % self._updateLockPath)
+#                        yield updateLock.acquire()
+#
+#                        self.log_debug("Swapping in new directory address book")
+#                        
+#                        # move old address book out of the way
+#                        if self.directoryBackedAddressBook.fp.exists():               
+#                            os.rename(self.directoryBackedAddressBook.fp.path, makeTmpFilename())
+#        
+#                        #move new one into place
+#                        os.rename(newAddressBook.fp.path, self.directoryBackedAddressBook.fp.path)
+#                        self.directoryBackedAddressBook.fp.restat()
+#                        
+#                        self.directoryBackedAddressBook.writeDeadProperty(newAddressBookCTag)
+#                    finally:
+#                        self.log_debug("unlocking: \"%s\")" % self._tmpDirAddressBookLockPath)
+#                        yield tmpDirLock.release()
     
                 if not keepLock:
                     self.log_debug("unlocking: \"%s\")" % self._updateLockPath)

Modified: CalendarServer/trunk/twistedcaldav/directory/principal.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/principal.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/twistedcaldav/directory/principal.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -56,8 +56,8 @@
 from twistedcaldav.directory import augment
 from twistedcaldav.directory.calendaruserproxy import CalendarUserProxyPrincipalResource
 from twistedcaldav.directory.directory import DirectoryService, DirectoryRecord
-from twistedcaldav.directory.util import NotFilePath
-from twistedcaldav.extensions import ReadOnlyResourceMixIn, DAVFile, DAVPrincipalResource
+from twistedcaldav.extensions import ReadOnlyResourceMixIn, DAVPrincipalResource,\
+    DAVResourceWithChildrenMixin
 from twistedcaldav.resource import CalendarPrincipalCollectionResource, CalendarPrincipalResource
 from twistedcaldav.directory.idirectory import IDirectoryService
 from twistedcaldav import caldavxml, customxml
@@ -70,10 +70,6 @@
 # Use __underbars__ convention to avoid conflicts with directory resource types.
 uidsResourceName = "__uids__"
 
-# FIXME: These should not be tied to DAVFile
-# The reason that they is that web2.dav only implements DAV methods on
-# DAVFile instead of DAVResource.  That should change.
-
 class PermissionsMixIn (ReadOnlyResourceMixIn):
     def defaultAccessControlList(self):
         return authReadACL
@@ -131,7 +127,6 @@
 class DirectoryProvisioningResource (
     PermissionsMixIn,
     CalendarPrincipalCollectionResource,
-    DAVFile,
 ):
     def __init__(self, url, directory):
         """
@@ -141,7 +136,7 @@
         assert url.endswith("/"), "Collection URL must end in '/'"
 
         CalendarPrincipalCollectionResource.__init__(self, url)
-        DAVFile.__init__(self, NotFilePath(isdir=True))
+        DAVResourceWithChildrenMixin.__init__(self)
 
         self.directory = IDirectoryService(directory)
 
@@ -507,7 +502,7 @@
     def principalCollections(self):
         return self.parent.principalCollections()
 
-class DirectoryPrincipalResource (PermissionsMixIn, DAVPrincipalResource, DAVFile):
+class DirectoryPrincipalResource (PermissionsMixIn, DAVPrincipalResource):
     """
     Directory principal resource.
     """
@@ -526,7 +521,7 @@
         @param parent: the parent of this resource.
         @param record: the L{IDirectoryRecord} that this resource represents.
         """
-        super(DirectoryPrincipalResource, self).__init__(NotFilePath(isdir=True))
+        super(DirectoryPrincipalResource, self).__init__()
 
         if self.isCollection():
             slash = "/"
@@ -936,7 +931,6 @@
 
     def calendarHome(self, request):
         # FIXME: self.record.service.calendarHomesCollection smells like a hack
-        # See CalendarHomeProvisioningFile.__init__()
         service = self.record.service
         if hasattr(service, "calendarHomesCollection"):
             return service.calendarHomesCollection.homeForDirectoryRecord(self.record, request)
@@ -960,7 +954,6 @@
 
     def addressBookHome(self, request):
         # FIXME: self.record.service.addressBookHomesCollection smells like a hack
-        # See AddressBookHomeProvisioningFile.__init__()
         service = self.record.service
         if hasattr(service, "addressBookHomesCollection"):
             return service.addressBookHomesCollection.homeForDirectoryRecord(self.record, request)

Modified: CalendarServer/trunk/twistedcaldav/directory/resource.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/resource.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/twistedcaldav/directory/resource.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -18,54 +18,21 @@
 Implements a directory-backed principal hierarchy.
 """
 
-__all__ = ["AutoProvisioningResourceMixIn"]
+from twext.web2.dav.util import joinURL
 
-from twisted.internet.defer import maybeDeferred, inlineCallbacks, returnValue
+from twistedcaldav.client.reverseproxy import ReverseProxyResource
 
-class AutoProvisioningResourceMixIn (object):
-    """
-    Adds auto-provisioning to a Resource implementation.
-    """
-    def provision(self):
-        """
-        Provision this resource by creating any required backing store, etc. that
-        must be set up before the resource can be accessed normally.  Specifically,
-        this must have been called before anything that involves I/O happens.
-        This method may be called multiple times; provisioning code should ensure that
-        it handles this properly, typically by returning immediately if the resource is
-        already provisioned (eg. the backing store exists).
-        @return: a deferred or None.
-        """
-        return None
+__all__ = ["DirectoryReverseProxyResource"]
 
-    def provisionChild(self, name):
-        """
-        Creates the child object with the given name.
-        This is basically akin to L{File.createSimilarFile}, but here we know we're
-        creating a child of this resource, and can take certain actions to ensure that
-        it's prepared appropriately and is of the correct class.
-        @param name: the name of the child resource.
-        @return: the newly created (optionally deferred) child, or None of no resource
-            is bound as a child of this resource with the given C{name}.
-        """
-        return None
+class DirectoryReverseProxyResource(ReverseProxyResource):
+    
+    def __init__(self, parent, record):
+        self.parent = parent
+        self.record = record
+        
+        super(DirectoryReverseProxyResource, self).__init__(self.record.hostedAt)
+    
+    def url(self):
+        return joinURL(self.parent.url(), self.record.uid)
 
-    @inlineCallbacks
-    def locateChild(self, request, segments):
-        """
-        This implementation calls L{provision}, then super's L{locateChild}, thereby
-        ensuring that looked-up resources are provisioned.
-        """
-        yield maybeDeferred(self.provision)
 
-        name = segments[0]
-        if name != "":
-            # If getChild() finds a child resource, return it
-            child = self.getChild(name)
-            if child is None:
-                child = self.provisionChild(name)
-            if child:
-                returnValue((child, segments[1:],))
-        
-        result = (yield super(AutoProvisioningResourceMixIn, self).locateChild(request, segments))
-        returnValue(result)

Modified: CalendarServer/trunk/twistedcaldav/directory/test/test_principal.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/test_principal.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/twistedcaldav/directory/test/test_principal.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -23,9 +23,9 @@
 from twext.web2.dav.resource import AccessDeniedError
 from twext.web2.test.test_server import SimpleRequest
 
-from twistedcaldav.static import CalendarHomeProvisioningFile
 from twistedcaldav.config import config
 from twistedcaldav.directory import augment, calendaruserproxy
+from twistedcaldav.directory.calendar import DirectoryCalendarHomeProvisioningResource
 from twistedcaldav.directory.directory import DirectoryService
 from twistedcaldav.directory.xmlfile import XMLDirectoryService
 from twistedcaldav.directory.test.test_xmlfile import xmlFile, augmentsFile
@@ -36,7 +36,6 @@
 
 import twistedcaldav.test.util
 from txdav.common.datastore.file import CommonDataStore
-from twisted.python.filepath import FilePath
 
 
 class ProvisionedPrincipals (twistedcaldav.test.util.TestCase):
@@ -358,21 +357,19 @@
         # 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:])
+            path = os.path.join(self.docroot, directory.__class__.__name__)
 
             if os.path.exists(path):
                 rmdir(path)
             os.mkdir(path)
 
-            provisioningResource = CalendarHomeProvisioningFile(
-                path,
+            # Need a data store
+            _newStore = CommonDataStore(path, True, False)
+
+            provisioningResource = DirectoryCalendarHomeProvisioningResource(
                 directory,
-                url,
+                "/calendars/",
                 _newStore
             )
 

Modified: CalendarServer/trunk/twistedcaldav/directory/util.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/util.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/twistedcaldav/directory/util.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -20,9 +20,10 @@
 
 __all__ = [
     "uuidFromName",
-    "NotFilePath",
 ]
 
+from txdav.idav import AlreadyFinishedError
+
 from uuid import UUID, uuid5
 
 def uuidFromName(namespace, name):
@@ -38,146 +39,40 @@
 
     return str(uuid5(UUID(namespace), name))
 
-import errno
-import time
-from twext.python.filepath import CachingFilePath as FilePath
-
-class NotFilePath(FilePath):
+def transactionFromRequest(request, newStore):
     """
-    Dummy placeholder for FilePath for when we don't actually want a file.
-    Pretends to be an empty file or directory.
-    """
-    def __init__(self, isfile=False, isdir=False, islink=False):
-        assert isfile or isdir or islink
+    Return the associated transaction from the given HTTP request, creating a
+    new one from the given data store if none has yet been associated.
 
-        self._isfile = isfile
-        self._isdir  = isdir
-        self._islink = islink
+    Also, if the request was not previously associated with a transaction, add
+    a failsafe transaction-abort response filter to abort any transaction which
+    has not been committed or aborted by the resource which responds to the
+    request.
 
-        self._time = time.time()
+    @param request: The request to inspect.
+    @type request: L{IRequest}
 
-    def __cmp__(self, other):
-        if not isinstance(other, self.__class__):
-            return NotImplemented
-        return cmp(
-            ( self.isdir(),  self.isfile(),  self.islink()),
-            (other.isdir(), other.isfile(), other.islink()),
-        )
+    @param newStore: The store to create a transaction from.
+    @type newStore: L{IDataStore}
 
-    def __repr__(self):
-        types = []
-        if self.isdir():
-            types.append("dir")
-        if self.isfile():
-            types.append("file")
-        if self.islink():
-            types.append("link")
-        if types:
-            return "<%s (%s)>" % (self.__class__.__name__, ",".join(types))
-        else:
-            return "<%s>" % (self.__class__.__name__,)
+    @return: a transaction that should be used to read and write data
+        associated with the request.
+    @rtype: L{ITransaction} (and possibly L{ICalendarTransaction} and
+        L{IAddressBookTransaction} as well.
+    """
+    TRANSACTION_KEY = '_newStoreTransaction'
+    transaction = getattr(request, TRANSACTION_KEY, None)
+    if transaction is None:
+        transaction = newStore.newTransaction(repr(request))
+        def abortIfUncommitted(request, response):
+            try:
+                transaction.abort()
+            except AlreadyFinishedError:
+                pass
+            return response
+        abortIfUncommitted.handleErrors = True
+        request.addResponseFilter(abortIfUncommitted)
+        setattr(request, TRANSACTION_KEY, transaction)
+    return transaction
 
-    def _unimplemented(self, *args):
-        try:
-            raise NotImplementedError("NotFilePath isn't really a FilePath: psych!")
-        except NotImplementedError:
-            from twisted.python.failure import Failure
-            Failure().printTraceback()
-            raise
 
-    child                  = _unimplemented
-    preauthChild           = _unimplemented
-    siblingExtensionSearch = _unimplemented
-    siblingExtension       = _unimplemented
-    open                   = _unimplemented
-    clonePath              = _unimplemented # Cuz I think it's dumb
-
-    def childSearchPreauth(self, *paths):
-        return ()
-
-    def splitext(self):
-        return ("", "")
-
-    def basename(self):
-        return ""
-
-    def dirname(self):
-        return ""
-
-    def changed(self):
-        pass
-
-    def restat(self, reraise=True):
-        pass
-
-    def getsize(self):
-        return 0
-
-    def _time(self):
-        return self._time
-
-    # FIXME: Maybe we should have separate ctime, mtime, atime. Meh.
-    getModificationTime = _time
-    getStatusChangeTime = _time
-    getAccessTime       = _time
-
-    def exists(self):
-        return True
-
-    def isdir(self):
-        return self._isdir
-
-    def isfile(self):
-        return self._isfile
-
-    def islink(self):
-        return self._islink
-
-    def isabs(self):
-        return True
-
-    def listdir(self):
-        return ()
-
-    def touch(self):
-        self._time = time.time()
-
-    def _notAllowed(self):
-        raise OSError(errno.EACCES, "Permission denied")
-
-    remove     = _notAllowed
-    setContent = _notAllowed
-
-    def globChildren(self, pattern):
-        return ()
-
-    def parent(self):
-        return self.__class__(isdir=True)
-
-    def createDirectory(self):
-        if self.isdir():
-            raise OSError(errno.EEXIST, "File exists")
-        else:
-            return self._notAllowed
-
-    makedirs = createDirectory
-
-    def create(self):
-        if self.isfile():
-            raise OSError(errno.EEXIST, "File exists")
-        else:
-            return self._notAllowed
-
-    def temporarySibling(self):
-        return self.__class__(isfile=True)
-
-    def copyTo(self, destination):
-        if self.isdir():
-            if not destination.isdir():
-                destination.createDirectory()
-        elif self.isfile():
-            destination.open("w").close()
-        else:
-            raise NotImplementedError()
-
-    moveTo = _notAllowed

Modified: CalendarServer/trunk/twistedcaldav/directorybackedaddressbook.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directorybackedaddressbook.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/twistedcaldav/directorybackedaddressbook.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -22,22 +22,18 @@
     "DirectoryBackedAddressBookResource",
 ]
 
-
-
 from twext.python.log import Logger
-from twisted.internet.defer import succeed, inlineCallbacks, maybeDeferred, returnValue
-from twisted.python.reflect import namedClass
 from twext.web2 import responsecode
 from twext.web2.dav import davxml
 from twext.web2.dav.resource import TwistedACLInheritable
 from twext.web2.http import HTTPError, StatusResponse
 
+from twisted.internet.defer import succeed, inlineCallbacks, maybeDeferred, returnValue
+from twisted.python.reflect import namedClass
+
 from twistedcaldav.config import config
 from twistedcaldav.resource import CalDAVResource
 
-
-
-
 log = Logger()
 
 
@@ -47,13 +43,36 @@
     Directory-backed address book
     """
 
-    def __init__(self):
+    def __init__(self, principalCollections):
 
-        CalDAVResource.__init__(self)
+        CalDAVResource.__init__(self, principalCollections=principalCollections)
 
         self.directory = None       # creates directory attribute
 
+        # create with permissions, similar to CardDAVOptions in tap.py
+        # FIXME:  /Directory does not need to be in file system unless debug-only caching options are used
+#        try:
+#            os.mkdir(path)
+#            os.chmod(path, 0750)
+#            if config.UserName and config.GroupName:
+#                import pwd
+#                import grp
+#                uid = pwd.getpwnam(config.UserName)[2]
+#                gid = grp.getgrnam(config.GroupName)[2]
+#                os.chown(path, uid, gid)
+# 
+#            log.msg("Created %s" % (path,))
+#            
+#        except (OSError,), e:
+#            # this is caused by multiprocessor race and is harmless
+#            if e.errno != errno.EEXIST:
+#                raise
+
         
+    def makeChild(self, name):
+        from twistedcaldav.simpleresource import SimpleCalDAVResource
+        return SimpleCalDAVResource(principalCollections=self.principalCollections())
+
     def provisionDirectory(self):
         if self.directory is None:
             directoryClass = namedClass(config.DirectoryAddressBook.type)
@@ -94,8 +113,8 @@
            ),
         )
 
-    def resourceType(self, request):
-        return succeed(davxml.ResourceType.addressbook)
+    def resourceType(self):
+        return davxml.ResourceType.addressbook
 
     def isDirectoryBackedAddressBookCollection(self):
         return True

Modified: CalendarServer/trunk/twistedcaldav/dropbox.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/dropbox.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/twistedcaldav/dropbox.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -41,8 +41,8 @@
     """
     Drop box collection resource.
     """
-    def resourceType(self, request):
-        return succeed(davxml.ResourceType.dropboxhome)
+    def resourceType(self):
+        return davxml.ResourceType.dropboxhome
 
     def isCollection(self):
         return True
@@ -83,8 +83,8 @@
     """
     Drop box resource.
     """
-    def resourceType(self, request):
-        return succeed(davxml.ResourceType.dropbox)
+    def resourceType(self):
+        return davxml.ResourceType.dropbox
 
     def isCollection(self):
         return True

Modified: CalendarServer/trunk/twistedcaldav/extensions.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/extensions.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/twistedcaldav/extensions.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -21,6 +21,7 @@
 
 __all__ = [
     "DAVResource",
+    "DAVResourceWithChildren",
     "DAVPrincipalResource",
     "DAVFile",
     "ReadOnlyWritePropertiesResourceMixIn",
@@ -60,7 +61,7 @@
 import twistedcaldav
 from twistedcaldav import customxml
 from twistedcaldav.customxml import calendarserver_namespace
-from twistedcaldav.util import Alternator, printTracebacks
+from twistedcaldav.util import Alternator
 from twistedcaldav.directory.sudo import SudoDirectoryService
 from twistedcaldav.directory.directory import DirectoryService
 from twistedcaldav.method.report import http_REPORT
@@ -515,7 +516,7 @@
         for name in sorted(self.listChildren()):
             child = self.getChild(name)
 
-            url, name, size, lastModified, contentType = (yield self.getChildDirectoryEntry(child, name, request))
+            url, name, size, lastModified, contentType = self.getChildDirectoryEntry(child, name, request)
 
             # FIXME: gray out resources that are not readable
             output.append(
@@ -624,7 +625,6 @@
         result = (yield gotProperties(qnames))
         returnValue(result)
 
-    @inlineCallbacks
     def getChildDirectoryEntry(self, child, name, request):
         def orNone(value, default="?", f=None):
             if value is None:
@@ -635,7 +635,7 @@
                 return value
             
         url = urllib.quote(name, '/')
-        if isinstance(child, SuperDAVFile) and child.isCollection():
+        if isinstance(child, DAVResource) and child.isCollection():
             url += "/"
             name += "/"
 
@@ -643,7 +643,7 @@
             size = child.contentLength()
             lastModified = child.lastModified()
             rtypes = []
-            fullrtype = (yield child.resourceType(request))
+            fullrtype = child.resourceType()
             for rtype in fullrtype.children:
                 rtypes.append(rtype.name)
             if rtypes:
@@ -663,8 +663,16 @@
             size = None
             lastModified = None
             contentType = None
+            if hasattr(child, "resourceType"):
+                rtypes = []
+                fullrtype = child.resourceType()
+                for rtype in fullrtype.children:
+                    rtypes.append(rtype.name)
+                if rtypes:
+                    contentType = "(%s)" % (", ".join(rtypes),)
+                
 
-        returnValue((
+        return ((
             url,
             name,
             orNone(size),
@@ -686,6 +694,9 @@
     """
     http_REPORT = http_REPORT
 
+    def davComplianceClasses(self):
+        return ("1", "access-control") # Add "2" when we have locking
+
     def render(self, request):
         if not self.exists():
             return responsecode.NOT_FOUND
@@ -694,15 +705,62 @@
             return self.renderDirectory(request)
         return super(DAVResource, self).render(request)
 
-
-    def resourceType(self, request):
+    def resourceType(self):
         # Allow live property to be overridden by dead property
         if self.deadProperties().contains((dav_namespace, "resourcetype")):
-            return succeed(self.deadProperties().get((dav_namespace, "resourcetype")))
-        return succeed(davxml.ResourceType())
+            return self.deadProperties().get((dav_namespace, "resourcetype"))
+        return davxml.ResourceType(davxml.Collection()) if self.isCollection() else davxml.ResourceType()
 
+    def contentType(self):
+        return MimeType("httpd", "unix-directory") if self.isCollection() else None
 
+class DAVResourceWithChildrenMixin (object):
+    """
+    Bits needed from twext.web2.static
+    """
 
+    def __init__(self, principalCollections=None):
+        self.putChildren = {}
+        super(DAVResourceWithChildrenMixin, self).__init__(principalCollections=principalCollections)
+
+    def putChild(self, name, child):
+        """
+        Register a child with the given name with this resource.
+        @param name: the name of the child (a URI path segment)
+        @param child: the child to register
+        """
+        self.putChildren[name] = child
+
+    def getChild(self, name):
+        """
+        Look up a child resource.
+        @return: the child of this resource with the given name.
+        """
+        if name == "":
+            return self
+
+        result = self.putChildren.get(name, None)
+        if not result:
+            result = self.makeChild(name)
+        return result
+
+    def makeChild(self, name):
+        # Subclasses with real children need to override this and return the appropriate object
+        return None
+
+    def listChildren(self):
+        """
+        @return: a sequence of the names of all known children of this resource.
+        """
+        return self.putChildren.keys()
+
+    def locateChild(self, req, segments):
+        """
+        See L{IResource}C{.locateChild}.
+        """
+        # If getChild() finds a child resource, return it
+        return (self.getChild(segments[0]), segments[1:])
+
 class DAVPrincipalResource (DirectoryPrincipalPropertySearchMixIn,
                             SuperDAVPrincipalResource, LoggingMixIn,
                             DirectoryRenderingMixIn):
@@ -719,6 +777,14 @@
 
     http_REPORT = http_REPORT
 
+    def render(self, request):
+        if not self.exists():
+            return responsecode.NOT_FOUND
+
+        if self.isCollection():
+            return self.renderDirectory(request)
+        return super(DAVResource, self).render(request)
+
     @inlineCallbacks
     def readProperty(self, property, request):
         if type(property) is tuple:
@@ -730,7 +796,7 @@
 
         if namespace == dav_namespace:
             if name == "resourcetype":
-                rtype = (yield self.resourceType(request))
+                rtype = self.resourceType()
                 returnValue(rtype)
 
         elif namespace == calendarserver_namespace:
@@ -772,14 +838,14 @@
     def expandedGroupMemberships(self):
         return succeed(())
 
-    def resourceType(self, request):
+    def resourceType(self):
         # Allow live property to be overridden by dead property
         if self.deadProperties().contains((dav_namespace, "resourcetype")):
-            return succeed(self.deadProperties().get((dav_namespace, "resourcetype")))
+            return self.deadProperties().get((dav_namespace, "resourcetype"))
         if self.isCollection():
-            return succeed(davxml.ResourceType(davxml.Principal(), davxml.Collection()))
+            return davxml.ResourceType(davxml.Principal(), davxml.Collection())
         else:
-            return succeed(davxml.ResourceType(davxml.Principal()))
+            return davxml.ResourceType(davxml.Principal())
 
 
 
@@ -788,24 +854,14 @@
     """
     Extended L{twext.web2.dav.static.DAVFile} implementation.
     """
-    def readProperty(self, property, request):
-        if type(property) is tuple:
-            qname = property
-        else:
-            qname = property.qname()
 
-        if qname == (dav_namespace, "resourcetype"):
-            return self.resourceType(request)
-
-        return super(DAVFile, self).readProperty(property, request)
-
-    def resourceType(self, request):
+    def resourceType(self):
         # Allow live property to be overridden by dead property
         if self.deadProperties().contains((dav_namespace, "resourcetype")):
-            return succeed(self.deadProperties().get((dav_namespace, "resourcetype")))
+            return self.deadProperties().get((dav_namespace, "resourcetype"))
         if self.isCollection():
-            return succeed(davxml.ResourceType.collection)
-        return succeed(davxml.ResourceType.empty)
+            return davxml.ResourceType.collection
+        return davxml.ResourceType.empty
 
     def render(self, request):
         if not self.fp.exists():

Modified: CalendarServer/trunk/twistedcaldav/freebusyurl.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/freebusyurl.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/twistedcaldav/freebusyurl.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -32,6 +32,7 @@
 from twext.web2 import responsecode
 from twext.web2.dav import davxml
 from twext.web2.dav.http import ErrorResponse
+from twext.web2.dav.util import joinURL
 from twext.web2.http import HTTPError
 from twext.web2.http import Response
 from twext.web2.http import StatusResponse
@@ -45,7 +46,8 @@
 from twistedcaldav.ical import Property
 from twistedcaldav.ical import parse_datetime
 from twistedcaldav.ical import parse_duration
-from twistedcaldav.resource import CalDAVResource
+from twistedcaldav.resource import CalDAVResource, ReadOnlyNoCopyResourceMixIn
+from twistedcaldav.schedule import deliverSchedulePrivilegeSet
 from twistedcaldav.scheduling.caldav import ScheduleViaCalDAV
 from twistedcaldav.scheduling.cuaddress import LocalCalendarUser
 from twistedcaldav.scheduling.scheduler import Scheduler
@@ -53,7 +55,7 @@
 log = Logger()
 
 
-class FreeBusyURLResource (CalDAVResource):
+class FreeBusyURLResource (ReadOnlyNoCopyResourceMixIn, CalDAVResource):
     """
     Free-busy URL resource.
 
@@ -70,6 +72,9 @@
 
         self.parent = parent
 
+    def __repr__(self):
+        return "<%s (free-busy URL resource): %s>" % (self.__class__.__name__, joinURL(self.parent.url(), "freebusy"))
+
     def defaultAccessControlList(self):
         privs = (
             davxml.Privilege(davxml.Read()),
@@ -99,9 +104,12 @@
             )
         return davxml.ACL(*aces)
 
-    def resourceType(self, request):
-        return succeed(davxml.ResourceType.freebusyurl)
+    def resourceType(self):
+        return davxml.ResourceType.freebusyurl
 
+    def contentType(self):
+        return MimeType("text", "calendar", charset="utf-8")
+
     def isCollection(self):
         return False
 
@@ -250,3 +258,10 @@
         response.headers.setHeader("content-type", MimeType.fromString("%s; charset=utf-8" % (self.format,)))
     
         returnValue(response)
+
+    ##
+    # ACL
+    ##
+
+    def supportedPrivileges(self, request):
+        return succeed(deliverSchedulePrivilegeSet)

Modified: CalendarServer/trunk/twistedcaldav/index.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/index.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/twistedcaldav/index.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -113,7 +113,7 @@
 
     def __init__(self, resource):
         """
-        @param resource: the L{twistedcaldav.static.CalDAVFile} resource to
+        @param resource: the L{CalDAVResource} resource to
             index. C{resource} must be a calendar collection (ie.
             C{resource.isPseudoCalendarCollection()} returns C{True}.)
         """
@@ -419,7 +419,7 @@
 
     def __init__(self, resource):
         """
-        @param resource: the L{twistedcaldav.static.CalDAVFile} resource to
+        @param resource: the L{CalDAVResource} resource to
             index.
         """
         super(CalendarIndex, self).__init__(resource)
@@ -920,7 +920,7 @@
 
     def __init__(self, resource):
         """
-        @param resource: the L{twistedcaldav.static.CalDAVFile} resource to
+        @param resource: the L{CalDAVResource} resource to
             index. C{resource} must be a calendar collection (i.e.
             C{resource.isPseudoCalendarCollection()} returns C{True}.)
         """

Modified: CalendarServer/trunk/twistedcaldav/linkresource.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/linkresource.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/twistedcaldav/linkresource.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -18,15 +18,24 @@
 
 from twisted.internet.defer import inlineCallbacks, returnValue, maybeDeferred
 
-from twistedcaldav.resource import CalDAVComplianceMixIn
 from twext.web2.http import HTTPError
 from twext.web2 import responsecode
 from twext.web2.resource import WrapperResource
+from twistedcaldav.config import config
+from twext.web2.dav import davxml
 
 __all__ = [
     "LinkResource",
 ]
 
+# FIXME: copied from resource.py to avoid circular dependency
+class CalDAVComplianceMixIn(object):
+    def davComplianceClasses(self):
+        return (
+            tuple(super(CalDAVComplianceMixIn, self).davComplianceClasses())
+            + config.CalDAVComplianceClasses
+        )
+
 """
 A resource that is a soft-link to another.
 """
@@ -55,11 +64,8 @@
     def isCollection(self):
         return True
 
-    @inlineCallbacks
-    def resourceType(self, request):
-        hosted = (yield self.linkedResource(request))
-        result = (yield hosted.resourceType(request))
-        returnValue(result)
+    def resourceType(self):
+        return self._linkedResource.resourceType() if hasattr(self, "_linkedResource") else davxml.ResourceType.link
         
     def locateChild(self, request, segments):
         

Modified: CalendarServer/trunk/twistedcaldav/mail.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/mail.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/twistedcaldav/mail.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -58,14 +58,14 @@
 from twistedcaldav import ical, caldavxml
 from twistedcaldav import memcachepool
 from twistedcaldav.config import config
-from twistedcaldav.directory.util import NotFilePath
 from twistedcaldav.ical import Property
 from twistedcaldav.localization import translationTo
+from twistedcaldav.resource import CalDAVResource
+from twistedcaldav.schedule import deliverSchedulePrivilegeSet
 from twistedcaldav.scheduling.cuaddress import normalizeCUAddr
 from twistedcaldav.scheduling.itip import iTIPRequestStatus
 from twistedcaldav.scheduling.scheduler import IMIPScheduler
 from twistedcaldav.sql import AbstractSQLDatabase
-from twistedcaldav.static import CalDAVFile, deliverSchedulePrivilegeSet
 from twistedcaldav.util import AuthorizedHTTPGetter
 from twistedcaldav.stdconfig import DEFAULT_CONFIG, DEFAULT_CONFIG_FILE
 
@@ -173,7 +173,7 @@
 
 
 
-class IMIPInboxResource(CalDAVFile):
+class IMIPInboxResource(CalDAVResource):
     """
     IMIP-delivery Inbox resource.
 
@@ -186,7 +186,7 @@
         """
         assert parent is not None
 
-        CalDAVFile.__init__(self, NotFilePath(isfile=True), principalCollections=parent.principalCollections())
+        CalDAVResource.__init__(self, principalCollections=parent.principalCollections())
 
         self.parent = parent
 
@@ -219,8 +219,8 @@
 
         return succeed(self.iMIPACL)
 
-    def resourceType(self, request):
-        return succeed(davxml.ResourceType.ischeduleinbox)
+    def resourceType(self):
+        return davxml.ResourceType.ischeduleinbox
 
     def isCollection(self):
         return False

Modified: CalendarServer/trunk/twistedcaldav/method/__init__.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/__init__.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/twistedcaldav/method/__init__.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -17,8 +17,8 @@
 """
 CalDAV methods.
 
-Modules in this package are imported by twistedcaldav.static in order to
-bind methods to CalDAVFile.
+Modules in this package are imported by twistedcaldav.resource in order to
+bind methods to CalDAVResource.
 """
 
 __all__ = [

Modified: CalendarServer/trunk/twistedcaldav/method/acl.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/acl.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/twistedcaldav/method/acl.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -29,8 +29,8 @@
 from twisted.internet.defer import inlineCallbacks, returnValue
 
 from twistedcaldav.resource import isAddressBookCollectionResource,\
-    isPseudoCalendarCollectionResource
-from twistedcaldav.static import AddressBookHomeFile, CalendarHomeFile
+    isPseudoCalendarCollectionResource,\
+    CalendarHomeResource, AddressBookHomeResource, CalDAVResource
 
 log = Logger()
 
@@ -42,7 +42,7 @@
     #
 
     if self.exists():
-        if isinstance(self, CalendarHomeFile) or isinstance(self, AddressBookHomeFile):
+        if isinstance(self, CalendarHomeResource) or isinstance(self, AddressBookHomeResource):
             raise HTTPError(responsecode.NOT_ALLOWED)
 
         parentURL = parentForURL(request.uri)
@@ -51,5 +51,5 @@
             raise HTTPError(responsecode.NOT_ALLOWED)
 
     # Do normal ACL behavior
-    response = (yield super(CalDAVFile, self).http_ACL(request))
+    response = (yield super(CalDAVResource, self).http_ACL(request))
     returnValue(response)

Modified: CalendarServer/trunk/twistedcaldav/method/copymove.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/copymove.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/twistedcaldav/method/copymove.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -39,14 +39,8 @@
 )
 
 from twistedcaldav.resource import isCalendarCollectionResource,\
-    isPseudoCalendarCollectionResource
+    isPseudoCalendarCollectionResource, CalDAVResource
 
-CalDAVFile = None               # Pacify PyFlakes; this *should* be fixed, but
-                                # it's not actually an undefined name, as the
-                                # bottom of twistedcaldav.static fixes it up
-                                # for us before any functions in this module
-                                # are invoked.
-
 log = Logger()
 
 @inlineCallbacks
@@ -67,7 +61,7 @@
         # Check with CardDAV first (XXX might want to check EnableCardDAV switch?)
         result = yield maybeCOPYContact(self, request)
         if result is KEEP_GOING:
-            result = yield super(CalDAVFile, self).http_COPY(request)
+            result = yield super(CalDAVResource, self).http_COPY(request)
         returnValue(result)
 
     #
@@ -140,7 +134,7 @@
                 returnValue(result)
 
         # Do default WebDAV action
-        result = (yield super(CalDAVFile, self).http_MOVE(request))
+        result = (yield super(CalDAVResource, self).http_MOVE(request))
         
         if is_calendar_collection:
             # Do some clean up
@@ -215,7 +209,7 @@
         sourcecal:        True if source is in a calendar collection, False otherwise
         sourceparent:     The parent resource for the source
         destination_uri:  The URI of the destination resource
-        destination:      CalDAVFile of destination if special proccesing required,
+        destination:      CalDAVResource of destination if special processing required,
         None otherwise
         destinationcal:   True if the destination is in a calendar collection,
             False otherwise

Modified: CalendarServer/trunk/twistedcaldav/method/copymove_contact.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/copymove_contact.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/twistedcaldav/method/copymove_contact.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -193,7 +193,7 @@
         sourceadbk:        True if source is in an addressbook collection, False otherwise
         sourceparent:     The parent resource for the source
         destination_uri:  The URI of the destination resource
-        destination:      CalDAVFile of destination if special processing required,
+        destination:      CalDAVResource of destination if special processing required,
         None otherwise
         destinationadbk:   True if the destination is in an addressbook collection,
             False otherwise

Modified: CalendarServer/trunk/twistedcaldav/method/get.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/get.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/twistedcaldav/method/get.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -34,7 +34,8 @@
 from twistedcaldav.customxml import TwistedCalendarAccessProperty,\
     calendarserver_namespace
 from twistedcaldav.datafilters.privateevents import PrivateEventFilter
-from twistedcaldav.resource import isPseudoCalendarCollectionResource
+from twistedcaldav.resource import isPseudoCalendarCollectionResource,\
+    CalDAVResource
 
 @inlineCallbacks
 def http_GET(self, request):
@@ -97,5 +98,5 @@
                 returnValue(response)
 
     # Do normal GET behavior
-    response = (yield super(CalDAVFile, self).http_GET(request))
+    response = (yield super(CalDAVResource, self).http_GET(request))
     returnValue(response)

Modified: CalendarServer/trunk/twistedcaldav/method/mkcol.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/mkcol.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/twistedcaldav/method/mkcol.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -35,9 +35,9 @@
 
 from twistedcaldav import caldavxml, carddavxml, mkcolxml
 from twistedcaldav.config import config
-from twistedcaldav.resource import isAddressBookCollectionResource
+from twistedcaldav.resource import isAddressBookCollectionResource,\
+    CalDAVResource
 from twistedcaldav.resource import isPseudoCalendarCollectionResource
-from twistedcaldav.static import CalDAVFile
 
 log = Logger()
 
@@ -182,6 +182,6 @@
     
     else:
         # No request body so it is a standard MKCOL
-        result = yield super(CalDAVFile, self).http_MKCOL(request)
+        result = yield super(CalDAVResource, self).http_MKCOL(request)
         returnValue(result)
 

Modified: CalendarServer/trunk/twistedcaldav/method/put.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/put.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/twistedcaldav/method/put.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -31,8 +31,8 @@
 from twistedcaldav.caldavxml import caldav_namespace
 
 from twistedcaldav.method.put_common import StoreCalendarObjectResource
-from twistedcaldav.resource import isPseudoCalendarCollectionResource
-from twistedcaldav.static import CalDAVFile
+from twistedcaldav.resource import isPseudoCalendarCollectionResource,\
+    CalDAVResource
 
 log = Logger()
 
@@ -118,7 +118,7 @@
             raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, str(e)))
 
     else:
-        result = (yield super(CalDAVFile, self).http_PUT(request))
+        result = (yield super(CalDAVResource, self).http_PUT(request))
 
         if not hasattr(request, "extendedLogItems"):
             request.extendedLogItems = {}

Modified: CalendarServer/trunk/twistedcaldav/method/put_addressbook_common.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/put_addressbook_common.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/twistedcaldav/method/put_addressbook_common.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -97,16 +97,16 @@
         Function that does common PUT/COPY/MOVE behavior.
         
         @param request:           the L{twext.web2.server.Request} for the current HTTP request.
-        @param source:            the L{CalDAVFile} for the source resource to copy from, or None if source data
+        @param source:            the L{CalDAVResource} for the source resource to copy from, or None if source data
             is to be read from the request.
         @param source_uri:        the URI for the source resource.
-        @param destination:       the L{CalDAVFile} for the destination resource to copy into.
+        @param destination:       the L{CalDAVResource} for the destination resource to copy into.
         @param destination_uri:   the URI for the destination resource.
         @param vcard:          the C{str} or L{Component} vcard data if there is no source, None otherwise.
         @param sourceadbk:         True if the source resource is in a vcard collection, False otherwise.
         @param destinationadbk:    True if the destination resource is in a vcard collection, False otherwise
-        @param sourceparent:      the L{CalDAVFile} for the source resource's parent collection, or None if source is None.
-        @param destinationparent: the L{CalDAVFile} for the destination resource's parent collection.
+        @param sourceparent:      the L{CalDAVResource} for the source resource's parent collection, or None if source is None.
+        @param destinationparent: the L{CalDAVResource} for the destination resource's parent collection.
         @param deletesource:      True if the source resource is to be deleted on successful completion, False otherwise.
         """
         

Modified: CalendarServer/trunk/twistedcaldav/method/put_common.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/put_common.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/twistedcaldav/method/put_common.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -135,16 +135,16 @@
         Function that does common PUT/COPY/MOVE behavior.
         
         @param request:           the L{twext.web2.server.Request} for the current HTTP request.
-        @param source:            the L{CalDAVFile} for the source resource to copy from, or None if source data
+        @param source:            the L{CalDAVResource} for the source resource to copy from, or None if source data
             is to be read from the request.
         @param source_uri:        the URI for the source resource.
-        @param destination:       the L{CalDAVFile} for the destination resource to copy into.
+        @param destination:       the L{CalDAVResource} for the destination resource to copy into.
         @param destination_uri:   the URI for the destination resource.
         @param calendar:          the C{str} or L{Component} calendar data if there is no source, None otherwise.
         @param sourcecal:         True if the source resource is in a calendar collection, False otherwise.
         @param destinationcal:    True if the destination resource is in a calendar collection, False otherwise
-        @param sourceparent:      the L{CalDAVFile} for the source resource's parent collection, or None if source is None.
-        @param destinationparent: the L{CalDAVFile} for the destination resource's parent collection.
+        @param sourceparent:      the L{CalDAVResource} for the source resource's parent collection, or None if source is None.
+        @param destinationparent: the L{CalDAVResource} for the destination resource's parent collection.
         @param deletesource:      True if the source resource is to be deleted on successful completion, False otherwise.
         @param isiTIP:                True if relaxed calendar data validation is to be done, False otherwise.
         @param allowImplicitSchedule: True if implicit scheduling should be attempted, False otherwise.
@@ -674,7 +674,7 @@
             if do_implicit_action and self.allowImplicitSchedule:
 
                 # Cannot do implicit in sharee's shared calendar
-                isvirt = (yield self.destinationparent.isVirtualShare(self.request))
+                isvirt = self.destinationparent.isVirtualShare()
                 if isvirt:
                     raise HTTPError(ErrorResponse(
                         responsecode.FORBIDDEN,

Modified: CalendarServer/trunk/twistedcaldav/method/report_addressbook_query.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/report_addressbook_query.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/twistedcaldav/method/report_addressbook_query.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -104,7 +104,7 @@
         """
         Run a query on the specified address book collection
         accumulating the query responses.
-        @param addrresource: the L{CalDAVFile} for an address book collection.
+        @param addrresource: the L{CalDAVResource} for an address book collection.
         @param uri: the uri for the address book collecton resource.
         """
         
@@ -117,7 +117,7 @@
         def queryAddressBookObjectResource(resource, uri, name, vcard, query_ok = False):
             """
             Run a query on the specified vcard.
-            @param resource: the L{CalDAVFile} for the vcard.
+            @param resource: the L{CalDAVResource} for the vcard.
             @param uri: the uri of the resource.
             @param name: the name of the resource.
             @param vcard: the L{Component} vcard read from the resource.

Modified: CalendarServer/trunk/twistedcaldav/method/report_calendar_query.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/report_calendar_query.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/twistedcaldav/method/report_calendar_query.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -13,7 +13,6 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 ##
-from twistedcaldav.config import config
 
 """
 CalDAV calendar-query report
@@ -37,6 +36,7 @@
 
 from twistedcaldav.caldavxml import caldav_namespace,\
     NumberOfRecurrencesWithinLimits
+from twistedcaldav.config import config
 from twistedcaldav.customxml import TwistedCalendarAccessProperty
 from twistedcaldav.index import IndexedSearchException
 from twistedcaldav.instance import TooManyInstancesError
@@ -114,14 +114,14 @@
         """
         Run a query on the specified calendar collection
         accumulating the query responses.
-        @param calresource: the L{CalDAVFile} for a calendar collection.
+        @param calresource: the L{CalDAVResource} for a calendar collection.
         @param uri: the uri for the calendar collecton resource.
         """
         
         def queryCalendarObjectResource(resource, uri, name, calendar, timezone, query_ok=False, isowner=True):
             """
             Run a query on the specified calendar.
-            @param resource: the L{CalDAVFile} for the calendar.
+            @param resource: the L{CalDAVResource} for the calendar.
             @param uri: the uri of the resource.
             @param name: the name of the resource.
             @param calendar: the L{Component} calendar read from the resource.
@@ -183,7 +183,7 @@
                     index_query_ok = False
 
                 if not names:
-                    return
+                    returnValue(True)
                   
                 # Now determine which valid resources are readable and which are not
                 ok_resources = []

Modified: CalendarServer/trunk/twistedcaldav/method/report_common.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/report_common.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/twistedcaldav/method/report_common.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -81,7 +81,7 @@
     down from the root. Return a MultiStatus element of all responses.
     
     @param request: the L{IRequest} for the current request.
-    @param resource: the L{CalDAVFile} representing the root to start scanning
+    @param resource: the L{CalDAVResource} representing the root to start scanning
         for calendar collections.
     @param depth: the depth to do the scan.
     @param apply: the function to apply to each calendar collection located
@@ -118,7 +118,7 @@
     down from the root. Return a MultiStatus element of all responses.
     
     @param request: the L{IRequest} for the current request.
-    @param resource: the L{CalDAVFile} representing the root to start scanning
+    @param resource: the L{CalDAVResource} representing the root to start scanning
         for address book collections.
     @param depth: the depth to do the scan.
     @param apply: the function to apply to each address book collection located
@@ -154,7 +154,7 @@
     @param request: the L{IRequest} for the current request.
     @param responses: the list of responses to append the result of this method to.
     @param href: the L{HRef} element of the resource being targeted.
-    @param resource: the L{CalDAVFile} for the targeted resource.
+    @param resource: the L{CalDAVResource} for the targeted resource.
     @param calendar: the L{Component} for the calendar for the resource. This may be None
         if the calendar has not already been read in, in which case the resource
         will be used to get the calendar if needed.
@@ -196,7 +196,7 @@
 
     @param request: the L{IRequest} for the current request.
     @param prop: the L{PropertyContainer} element for the properties of interest.
-    @param resource: the L{CalDAVFile} for the targeted resource.
+    @param resource: the L{CalDAVResource} for the targeted resource.
     @param calendar: the L{Component} for the calendar for the resource. This may be None
         if the calendar has not already been read in, in which case the resource
         will be used to get the calendar if needed.
@@ -221,7 +221,7 @@
     Return property names for all properties on the specified resource.
     @param request: the L{IRequest} for the current request.
     @param prop: the L{PropertyContainer} element for the properties of interest.
-    @param resource: the L{CalDAVFile} for the targeted resource.
+    @param resource: the L{CalDAVResource} for the targeted resource.
     @param calendar: the L{Component} for the calendar for the resource. This may be None
         if the calendar has not already been read in, in which case the resource
         will be used to get the calendar if needed.
@@ -246,7 +246,7 @@
     Return the specified properties on the specified resource.
     @param request: the L{IRequest} for the current request.
     @param prop: the L{PropertyContainer} element for the properties of interest.
-    @param resource: the L{CalDAVFile} for the targeted resource.
+    @param resource: the L{CalDAVResource} for the targeted resource.
     @param calendar: the L{Component} for the calendar for the resource. This may be None
         if the calendar has not already been read in, in which case the resource
         will be used to get the calendar if needed.
@@ -310,7 +310,7 @@
     Return the specified properties on the specified resource.
     @param request: the L{IRequest} for the current request.
     @param props: a list of property elements or qname tuples for the properties of interest.
-    @param resource: the L{CalDAVFile} for the targeted resource.
+    @param resource: the L{CalDAVResource} for the targeted resource.
     @param calendar: the L{Component} for the calendar for the resource. This may be None
         if the calendar has not already been read in, in which case the resource
         will be used to get the calendar if needed.
@@ -389,7 +389,7 @@
     Run a free busy report on the specified calendar collection
     accumulating the free busy info for later processing.
     @param request:     the L{IRequest} for the current request.
-    @param calresource: the L{CalDAVFile} for a calendar collection.
+    @param calresource: the L{CalDAVResource} for a calendar collection.
     @param fbinfo:      the array of busy periods to update.
     @param timerange:   the L{TimeRange} for the query.
     @param matchtotal:  the running total for the number of matches.

Modified: CalendarServer/trunk/twistedcaldav/method/report_freebusy.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/report_freebusy.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/twistedcaldav/method/report_freebusy.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -62,7 +62,7 @@
         """
         Run a free busy report on the specified calendar collection
         accumulating the free busy info for later processing.
-        @param calresource: the L{CalDAVFile} for a calendar collection.
+        @param calresource: the L{CalDAVResource} for a calendar collection.
         @param uri: the uri for the calendar collecton resource.
         """
         

Modified: CalendarServer/trunk/twistedcaldav/method/report_sync_collection.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/report_sync_collection.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/twistedcaldav/method/report_sync_collection.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -60,7 +60,7 @@
         Return the specified properties on the specified resource.
         @param request: the L{IRequest} for the current request.
         @param props: a list of property elements or qname tuples for the properties of interest.
-        @param resource: the L{DAVFile} for the targeted resource.
+        @param resource: the L{DAVResource} for the targeted resource.
         @return: a map of OK and NOT FOUND property values.
         """
         properties_by_status = {

Modified: CalendarServer/trunk/twistedcaldav/notifications.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/notifications.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/twistedcaldav/notifications.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -26,21 +26,24 @@
 from twext.python.log import Logger, LoggingMixIn
 from twext.web2 import responsecode
 from twext.web2.dav import davxml
-from twext.web2.dav.resource import DAVResource
+
 from twisted.internet.defer import succeed, inlineCallbacks, returnValue
+
+from twistedcaldav.resource import ReadOnlyNoCopyResourceMixIn, CalDAVResource
 from twistedcaldav.sql import AbstractSQLDatabase, db_prefix
+
 import os
 import types
 
 log = Logger()
 
-class NotificationResource(DAVResource):
+class NotificationResource(CalDAVResource):
     """
     An xml resource in a Notification collection.
     """
     def __init__(self, parent):
         self._parent = parent
-        DAVResource.__init__(self)
+        CalDAVResource.__init__(self)
 
     def principalCollections(self):
         return self._parent.principalCollections()
@@ -61,9 +64,9 @@
         if response == responsecode.NO_CONTENT:
             yield self._parent.removedNotifictionMessage(request, self.resourceName())
         returnValue(response)
+    
+class NotificationCollectionResource(ReadOnlyNoCopyResourceMixIn, CalDAVResource):
 
-class NotificationCollectionResource(DAVResource):
-    
     def notificationsDB(self):
         
         if not hasattr(self, "_notificationsDB"):
@@ -73,8 +76,8 @@
     def isCollection(self):
         return True
 
-    def resourceType(self, request):
-        return succeed(davxml.ResourceType.notification)
+    def resourceType(self):
+        return davxml.ResourceType.notification
 
     @inlineCallbacks
     def addNotification(self, request, uid, xmltype, xmldata):
@@ -86,9 +89,6 @@
         # Update database
         self.notificationsDB().addOrUpdateRecord(NotificationRecord(uid, rname, xmltype.name))
 
-    def _writeNotification(self, request, uid, rname, xmltype, xmldata):
-        raise NotImplementedError
-
     def getNotifictionMessages(self, request, componentType=None, returnLatestVersion=True):
         return succeed([])
 
@@ -101,19 +101,23 @@
         # See if it exists and delete the resource
         record = self.notificationsDB().recordForUID(uid)
         if record:
-            yield self._deleteNotification(request, record.name)
-            self.notificationsDB().removeRecordForUID(record.uid)
+            yield self.deleteNotification(request, record)
 
+    @inlineCallbacks
     def deleteNotifictionMessageByName(self, request, rname):
 
         # See if it exists and delete the resource
         record = self.notificationsDB().recordForName(rname)
         if record:
-            self._deleteNotification(request, record.name)
-            self.notificationsDB().removeRecordForUID(record.uid)
+            yield self.deleteNotification(request, record)
         
-        return succeed(None)
+        returnValue(None)
 
+    @inlineCallbacks
+    def deleteNotification(self, request, record):
+        yield self._deleteNotification(request, record.name)
+        self.notificationsDB().removeRecordForUID(record.uid)
+        
     def removedNotifictionMessage(self, request, rname):
         self.notificationsDB().removeRecordForName(rname)
         return succeed(None)
@@ -133,7 +137,7 @@
 
     def __init__(self, resource):
         """
-        @param resource: the L{twistedcaldav.static.CalDAVFile} resource for
+        @param resource: the L{CalDAVResource} resource for
             the notifications collection.)
         """
         self.resource = resource

Modified: CalendarServer/trunk/twistedcaldav/notify.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/notify.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/twistedcaldav/notify.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -596,7 +596,7 @@
 
     Uses pubsub XMPP requests to let subscribers know when there
     has been a change made to a DAV resource (currently just
-    CalendarHomeFiles).  Uses XMPP login info from the config file
+    CalendarHomeResources).  Uses XMPP login info from the config file
     to determine which pubsub service to connect to.  When it's
     time to send a notification, XMPPNotifier computes a node path
     corresponding to the DAV resource and emits a publish request

Modified: CalendarServer/trunk/twistedcaldav/resource.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/resource.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/twistedcaldav/resource.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -29,10 +29,13 @@
     "isAddressBookCollectionResource",
 ]
 
+from urlparse import urlsplit
+from uuid import uuid4
+import datetime
 import urllib
-from urlparse import urlsplit
 import uuid
 
+
 from zope.interface import implements
 
 from twext.python.log import LoggingMixIn
@@ -40,38 +43,50 @@
 from twext.web2.dav.http import ErrorResponse
 
 from twisted.internet import reactor
-from twisted.internet.defer import Deferred, succeed, maybeDeferred
+from twisted.internet.defer import Deferred, succeed, maybeDeferred, fail
 from twisted.internet.defer import inlineCallbacks, returnValue
-from twext.web2 import responsecode
+from twisted.python.failure import Failure
+
+from twext.web2 import responsecode, http, http_headers
 from twext.web2.dav import davxml
 from twext.web2.dav.auth import AuthenticationWrapper as SuperAuthenticationWrapper
 from twext.web2.dav.davxml import dav_namespace
 from twext.web2.dav.idav import IDAVPrincipalCollectionResource
-from twext.web2.dav.resource import AccessDeniedError, DAVPrincipalCollectionResource
+from twext.web2.dav.resource import AccessDeniedError, DAVPrincipalCollectionResource,\
+    davPrivilegeSet
 from twext.web2.dav.resource import TwistedACLInheritable
-from twext.web2.dav.util import joinURL, parentForURL, unimplemented, normalizeURL
+from twext.web2.dav.util import joinURL, parentForURL, normalizeURL
 from twext.web2.http import HTTPError, RedirectResponse, StatusResponse, Response
 from twext.web2.http_headers import MimeType
 from twext.web2.stream import MemoryStream
 
 from twistedcaldav import caldavxml, customxml
 from twistedcaldav import carddavxml
+from twistedcaldav.caldavxml import caldav_namespace
 from twistedcaldav.carddavxml import carddav_namespace
-from twistedcaldav.caldavxml import caldav_namespace
 from twistedcaldav.config import config
-from twistedcaldav.customxml import TwistedCalendarAccessProperty
+from twistedcaldav.customxml import TwistedCalendarAccessProperty,\
+    TwistedScheduleMatchETags
 from twistedcaldav.customxml import calendarserver_namespace
 from twistedcaldav.datafilters.peruserdata import PerUserDataFilter
+from twistedcaldav.datafilters.privateevents import PrivateEventFilter
+from twistedcaldav.directory.internal import InternalDirectoryRecord
 from twistedcaldav.extensions import DAVResource, DAVPrincipalResource,\
-    PropertyNotFoundError
+    PropertyNotFoundError, DAVResourceWithChildrenMixin
 from twistedcaldav.ical import Component
 from twistedcaldav.ical import Component as iComponent
+from twistedcaldav.ical import Property as iProperty
 from twistedcaldav.ical import allowedComponents
 from twistedcaldav.icaldav import ICalDAVResource, ICalendarPrincipalResource
-from twistedcaldav.notify import getPubSubConfiguration, getPubSubPath
-from twistedcaldav.notify import getNodeCacher, NodeCreationException
-from twistedcaldav.sharing import SharedCollectionMixin
+from twistedcaldav.index import SyncTokenValidException, Index
+from twistedcaldav.linkresource import LinkResource
+from twistedcaldav.memcachelock import MemcacheLock, MemcacheLockTimeoutError
+from twistedcaldav.notify import getNodeCacher
+from twistedcaldav.notify import getPubSubConfiguration, getPubSubPath,\
+    getPubSubXMPPURI, getPubSubHeartbeatURI
+from twistedcaldav.sharing import SharedCollectionMixin, SharedHomeMixin
 from twistedcaldav.vcard import Component as vComponent
+from twistedcaldav.vcardindex import AddressBookIndex
 
 from txdav.common.icommondatastore import InternalDataStoreError
 
@@ -102,9 +117,75 @@
             + config.CalDAVComplianceClasses
         )
 
+class ReadOnlyResourceMixIn (object):
+    """
+    Read only resource.
+    """
 
-class CalDAVResource (CalDAVComplianceMixIn, SharedCollectionMixin, DAVResource, LoggingMixIn):
+    def writeProperty(self, property, request):
+        raise HTTPError(self.readOnlyResponse)
+
+    def http_ACL(self, request):       return responsecode.FORBIDDEN
+    def http_DELETE(self, request):    return responsecode.FORBIDDEN
+    def http_MKCOL(self, request):     return responsecode.FORBIDDEN
+    def http_MOVE(self, request):      return responsecode.FORBIDDEN
+    def http_PROPPATCH(self, request): return responsecode.FORBIDDEN
+    def http_PUT(self, request):       return responsecode.FORBIDDEN
+
+    def http_MKCALENDAR(self, request):
+        return ErrorResponse(
+            responsecode.FORBIDDEN,
+            (caldav_namespace, "calendar-collection-location-ok")
+        )
+
+class ReadOnlyNoCopyResourceMixIn (ReadOnlyResourceMixIn):
     """
+    Read only resource that disallows COPY.
+    """
+
+    def http_COPY(self, request): return responsecode.FORBIDDEN
+
+def _calendarPrivilegeSet ():
+    edited = False
+
+    top_supported_privileges = []
+
+    for supported_privilege in davPrivilegeSet.childrenOfType(davxml.SupportedPrivilege):
+        all_privilege = supported_privilege.childOfType(davxml.Privilege)
+        if isinstance(all_privilege.children[0], davxml.All):
+            all_description = supported_privilege.childOfType(davxml.Description)
+            all_supported_privileges = []
+            for all_supported_privilege in supported_privilege.childrenOfType(davxml.SupportedPrivilege):
+                read_privilege = all_supported_privilege.childOfType(davxml.Privilege)
+                if isinstance(read_privilege.children[0], davxml.Read):
+                    read_description = all_supported_privilege.childOfType(davxml.Description)
+                    read_supported_privileges = list(all_supported_privilege.childrenOfType(davxml.SupportedPrivilege))
+                    read_supported_privileges.append(
+                        davxml.SupportedPrivilege(
+                            davxml.Privilege(caldavxml.ReadFreeBusy()),
+                            davxml.Description("allow free busy report query", **{"xml:lang": "en"}),
+                        )
+                    )
+                    all_supported_privileges.append(
+                        davxml.SupportedPrivilege(read_privilege, read_description, *read_supported_privileges)
+                    )
+                    edited = True
+                else:
+                    all_supported_privileges.append(all_supported_privilege)
+            top_supported_privileges.append(
+                davxml.SupportedPrivilege(all_privilege, all_description, *all_supported_privileges)
+            )
+        else:
+            top_supported_privileges.append(supported_privilege)
+
+    assert edited, "Structure of davPrivilegeSet changed in a way that I don't know how to extend for calendarPrivilegeSet"
+
+    return davxml.SupportedPrivilegeSet(*top_supported_privileges)
+
+calendarPrivilegeSet = _calendarPrivilegeSet()
+
+class CalDAVResource (CalDAVComplianceMixIn, SharedCollectionMixin, DAVResourceWithChildrenMixin, DAVResource, LoggingMixIn):
+    """
     CalDAV resource.
 
     Extends L{DAVResource} to provide CalDAV functionality.
@@ -357,7 +438,7 @@
         else:
             qname = property.qname()
 
-        isvirt = (yield self.isVirtualShare(request))
+        isvirt = self.isVirtualShare()
         if isvirt:
             if self.isShadowableProperty(qname):
                 ownerPrincipal = (yield self.resourceOwnerPrincipal(request))
@@ -396,7 +477,7 @@
         else:
             qname = property.qname()
 
-        isvirt = (yield self.isVirtualShare(request))
+        isvirt = self.isVirtualShare()
 
         if self.isCalendarCollection() or self.isAddressBookCollection():
 
@@ -418,8 +499,6 @@
                         pubSubConfiguration = getPubSubConfiguration(config)
                         nodeName = getPubSubPath(notifierID, pubSubConfiguration)
                         propVal = customxml.PubSubXMPPPushKeyProperty(nodeName)
-                        # nodeCacher = getNodeCacher()
-                        # (yield nodeCacher.createNode(self.clientNotifier, nodeName))
                         returnValue(propVal)
 
                 returnValue(customxml.PubSubXMPPPushKeyProperty())
@@ -449,6 +528,9 @@
             owner = (yield self.owner(request))
             returnValue(davxml.Owner(owner))
 
+        elif qname == davxml.ResourceType.qname():
+            returnValue(self.resourceType())
+
         elif qname == davxml.ResourceID.qname():
             returnValue(davxml.ResourceID(davxml.HRef.fromString(self.resourceID())))
 
@@ -527,7 +609,7 @@
                 returnValue(customxml.AllowedSharingModes(customxml.CanBeShared()))
 
         elif qname == customxml.SharedURL.qname():
-            isvirt = (yield self.isVirtualShare(request))
+            isvirt = self.isVirtualShare()
             
             if isvirt:
                 returnValue(customxml.SharedURL(davxml.HRef.fromString(self._share.hosturl)))
@@ -544,7 +626,7 @@
         )
         
         # Per-user Dav props currently only apply to a sharee's copy of a calendar
-        isvirt = (yield self.isVirtualShare(request))
+        isvirt = self.isVirtualShare()
         if isvirt and (self.isShadowableProperty(property.qname()) or (not self.isGlobalProperty(property.qname()))):
             yield self._preProcessWriteProperty(property, request)
             ownerPrincipal = (yield self.resourceOwnerPrincipal(request))
@@ -645,14 +727,14 @@
                 sawShare = [child for child in property.children if child.qname() == (calendarserver_namespace, "shared-owner")]
                 if not shared and sawShare:
                     # Owner is trying to share a collection
-                    yield self.upgradeToShare(request)
+                    self.upgradeToShare()
                 elif shared and not sawShare:
                     # Remove share
                     yield self.downgradeFromShare(request)
                 returnValue(None)
             else:
                 # resourcetype cannot be changed but we will allow it to be set to the same value
-                currentType = (yield self.resourceType(request))
+                currentType = self.resourceType()
                 if currentType == property:
                     returnValue(None)
 
@@ -668,7 +750,7 @@
     def accessControlList(self, request, *args, **kwargs):
 
         acls = None
-        isvirt = (yield self.isVirtualShare(request))
+        isvirt = self.isVirtualShare()
         if isvirt:
             acls = self.shareeAccessControlList()
 
@@ -725,7 +807,7 @@
         Return the DAV:owner property value (MUST be a DAV:href or None).
         """
         
-        isVirt = (yield self.isVirtualShare(request))
+        isVirt = self.isVirtualShare()
         if isVirt:
             parent = (yield self.locateParent(request, self._share.hosturl))
         else:
@@ -741,7 +823,7 @@
         """
         Return the DAV:owner property value (MUST be a DAV:href or None).
         """
-        isVirt = (yield self.isVirtualShare(request))
+        isVirt = self.isVirtualShare()
         if isVirt:
             parent = (yield self.locateParent(request, self._share.hosturl))
         else:
@@ -759,7 +841,7 @@
         collection it will be the sharee, otherwise it will be the regular the ownerPrincipal.
         """
 
-        isVirt = (yield self.isVirtualShare(request))
+        isVirt = self.isVirtualShare()
         if isVirt:
             returnValue(self._shareePrincipal)
         else:
@@ -813,8 +895,14 @@
             else:
                 return super(DAVResource, self).displayName()
         else:
-            return super(DAVResource, self).displayName()
+            result = super(DAVResource, self).displayName()
+            if not result:
+                result = self.name()
+            return result
 
+    def name(self):
+        return None
+
     def resourceID(self):
         if not self.hasDeadProperty(davxml.ResourceID.qname()):
             uuidval = uuid.uuid4()
@@ -847,7 +935,7 @@
         if not self.isCollection(): return False
 
         try:
-            resourcetype = self.readDeadProperty((dav_namespace, "resourcetype"))
+            resourcetype = self.resourceType()
         except HTTPError, e:
             assert e.response.code == responsecode.NOT_FOUND, (
                 "Unexpected response code: %s" % (e.response.code,)
@@ -912,7 +1000,7 @@
 
         if depth != "0" and self.isCollection():
             basepath = request.urlForResource(self)
-            children = self.listChildren()
+            children = list(self.listChildren())
             getChild()
         else:
             completionDeferred.callback(None)
@@ -1048,29 +1136,6 @@
 
         returnValue(PerUserDataFilter(accessUID).filter(caldata))
 
-    def iCalendarRolledup(self, request):
-        """
-        See L{ICalDAVResource.iCalendarRolledup}.
-
-        This implementation raises L{NotImplementedError}; a subclass must
-        override it.
-        """
-        unimplemented(self)
-
-    def iCalendarText(self, name=None):
-        """
-        See L{ICalDAVResource.iCalendarText}.
-
-        This implementation returns the string representation (according to
-        L{str}) of the object returned by L{iCalendar} when given the same
-        arguments.
-
-        Note that L{iCalendar} by default calls this method, which creates
-        an infinite loop.  A subclass must override one of both of these
-        methods.
-        """
-        return str(self.iCalendar(name))
-
     def iCalendarAddressDoNormalization(self, ical):
         """
         Normalize calendar user addresses in the supplied iCalendar object into their
@@ -1099,14 +1164,6 @@
                 return principal
         return None
 
-    def createAddressBook(self, request):
-        """
-        See L{ICalDAVResource.createAddressBook}.
-        This implementation raises L{NotImplementedError}; a subclass must
-        override it.
-        """
-        unimplemented(self)
-
     def vCard(self, name=None):
         """
         See L{ICalDAVResource.vCard}.
@@ -1130,37 +1187,6 @@
         except ValueError:
             return None
 
-    def vCardRolledup(self, request):
-        """
-        See L{ICalDAVResource.vCardRolledup}.
-
-        This implementation raises L{NotImplementedError}; a subclass must
-        override it.
-        """
-        unimplemented(self)
-
-    def vCardText(self, name=None):
-        """
-        See L{ICalDAVResource.vCardText}.
-
-        This implementation returns the string representation (according to
-        L{str}) of the object returned by L{vCard} when given the same
-        arguments.
-
-        Note that L{vCard} by default calls this method, which creates
-        an infinite loop.  A subclass must override one of both of these
-        methods.
-        """
-        return str(self.vCard(name))
-
-    def vCardXML(self, name=None):
-        """
-        See L{ICalDAVResource.vCardXML}.
-        This implementation returns an XML element constructed from the object
-        returned by L{vCard} when given the same arguments.
-        """
-        return carddavxml.AddressData.fromAddress(self.vCard(name))
-
     def supportedReports(self):
         result = super(CalDAVResource, self).supportedReports()
         result.append(davxml.Report(caldavxml.CalendarQuery(),))
@@ -1258,14 +1284,14 @@
         """
 
         sharedParent = None
-        isvirt = (yield self.isVirtualShare(request))
+        isvirt = self.isVirtualShare()
         if isvirt:
             # A virtual share's quota root is the resource owner's root
             sharedParent = (yield request.locateResource(parentForURL(self._share.hosturl)))
         else:
             parent = (yield self.locateParent(request, request.urlForResource(self)))
             if isCalendarCollectionResource(parent) or isAddressBookCollectionResource(parent):
-                isvirt = (yield parent.isVirtualShare(request))
+                isvirt = parent.isVirtualShare()
                 if isvirt:
                     # A virtual share's quota root is the resource owner's root
                     sharedParent = (yield request.locateResource(parentForURL(parent._share.hosturl)))
@@ -1277,6 +1303,568 @@
 
         returnValue(result)
 
+    # Collection sync stuff
+
+    def whatchanged(self, client_token):
+        
+        current_token = str(self.readDeadProperty(customxml.GETCTag))
+        current_uuid, current_revision = current_token.split("#", 1)
+        current_revision = int(current_revision)
+
+        if client_token:
+            try:
+                caluuid, revision = client_token.split("#", 1)
+                revision = int(revision)
+                
+                # Check client token validity
+                if caluuid != current_uuid:
+                    raise ValueError
+                if revision > current_revision:
+                    raise ValueError
+            except ValueError:
+                raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (dav_namespace, "valid-sync-token")))
+        else:
+            revision = 0
+
+        try:
+            changed, removed = self.index().whatchanged(revision)
+        except SyncTokenValidException:
+            raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (dav_namespace, "valid-sync-token")))
+
+        return changed, removed, current_token
+
+    @inlineCallbacks
+    def bumpSyncToken(self):
+        """
+        Increment the sync-token which is also the ctag.
+        
+        return: a deferred that returns the new revision number
+        """
+        assert self.isCollection()
+        
+        # Need to lock
+        lock = MemcacheLock("ResourceLock", self.resourceID(), timeout=60.0)
+        try:
+            try:
+                yield lock.acquire()
+            except MemcacheLockTimeoutError:
+                raise HTTPError(StatusResponse(responsecode.CONFLICT, "Resource: %s currently in use on the server." % (self.uri,)))
+
+            try:
+                token = str(self.readDeadProperty(customxml.GETCTag))
+                caluuid, revision = token.split("#", 1)
+                revision = int(revision) + 1
+                token = "%s#%d" % (caluuid, revision,)
+    
+            except (HTTPError, ValueError):
+                # Initialise it
+                caluuid = uuid4()
+                revision = 1
+                token = "%s#%d" % (caluuid, revision,)
+    
+            yield self.updateCTag(token)
+            returnValue(revision)
+        finally:
+            yield lock.clean()
+
+    def initSyncToken(self):
+        """
+        Create a new sync-token which is also the ctag.
+        """
+        # FIXME: new implementation is in txcaldav.file, this should be
+        # deleted.
+        assert self.isCollection()
+        # Initialise it
+        caluuid = uuid4()
+        revision = 1
+        token = "%s#%d" % (caluuid, revision,)
+        try:
+            self.writeDeadProperty(customxml.GETCTag(token))
+        except:
+            return fail(Failure())
+
+    def getSyncToken(self):
+        """
+        Return current sync-token value.
+        """
+        assert self.isCollection()
+        
+        return str(self.readDeadProperty(customxml.GETCTag))
+
+    def updateCTag(self, token=None):
+        assert self.isCollection()
+        
+        if not token:
+            token = str(datetime.datetime.now())
+        try:
+            self.writeDeadProperty(customxml.GETCTag(token))
+        except:
+            return fail(Failure())
+
+        if hasattr(self, 'clientNotifier'):
+            self.clientNotifier.notify(op="update")
+
+        return succeed(True)
+
+    #
+    # Stuff from CalDAVFile
+    #
+
+    def checkPreconditions(self, request):
+        """
+        We override the base class to handle the special implicit scheduling weak ETag behavior
+        for compatibility with old clients using If-Match.
+        """
+        
+        if config.Scheduling.CalDAV.ScheduleTagCompatibility:
+            
+            if self.exists() and self.hasDeadProperty(TwistedScheduleMatchETags):
+                etags = self.readDeadProperty(TwistedScheduleMatchETags).children
+                if len(etags) > 1:
+                    # This is almost verbatim from twext.web2.static.checkPreconditions
+                    if request.method not in ("GET", "HEAD"):
+                        
+                        # Loop over each tag and succeed if any one matches, else re-raise last exception
+                        exists = self.exists()
+                        last_modified = self.lastModified()
+                        last_exception = None
+                        for etag in etags:
+                            try:
+                                http.checkPreconditions(
+                                    request,
+                                    entityExists = exists,
+                                    etag = http_headers.ETag(etag),
+                                    lastModified = last_modified,
+                                )
+                            except HTTPError, e:
+                                last_exception = e
+                            else:
+                                break
+                        else:
+                            if last_exception:
+                                raise last_exception
+            
+                    # Check per-method preconditions
+                    method = getattr(self, "preconditions_" + request.method, None)
+                    if method:
+                        response = maybeDeferred(method, request)
+                        response.addCallback(lambda _: request)
+                        return response
+                    else:
+                        return None
+
+        return super(CalDAVResource, self).checkPreconditions(request)
+
+    def createCalendar(self, request):
+        """
+        External API for creating a calendar.  Verify that the parent is a
+        collection, exists, is I{not} a calendar collection; that this resource
+        does not yet exist, then create it.
+
+        @param request: the request used to look up parent resources to
+            validate.
+
+        @type request: L{twext.web2.iweb.IRequest}
+
+        @return: a deferred that fires when a calendar collection has been
+            created in this resource.
+        """
+        if self.exists():
+            self.log_error("Attempt to create collection where file exists: %s" % (self,))
+            raise HTTPError(StatusResponse(responsecode.NOT_ALLOWED, "File exists"))
+
+        # newStore guarantees that we always have a parent calendar home
+        #if not self.fp.parent().isdir():
+        #    log.err("Attempt to create collection with no parent: %s" % (self.fp.path,))
+        #    raise HTTPError(StatusResponse(responsecode.CONFLICT, "No parent collection"))
+
+        #
+        # Verify that no parent collection is a calendar also
+        #
+
+        def _defer(parent):
+            if parent is not None:
+                self.log_error("Cannot create a calendar collection within a calendar collection %s" % (parent,))
+                raise HTTPError(ErrorResponse(
+                    responsecode.FORBIDDEN,
+                    (caldavxml.caldav_namespace, "calendar-collection-location-ok")
+                ))
+
+            return self.createCalendarCollection()
+
+        parent = self._checkParents(request, isPseudoCalendarCollectionResource)
+        parent.addCallback(_defer)
+        return parent
+
+
+    def createCalendarCollection(self):
+        """
+        Internal API for creating a calendar collection.
+
+        @return: a L{Deferred} which fires when the underlying collection has
+            actually been created.
+        """
+        return fail(NotImplementedError())
+
+
+    def createSpecialCollection(self, resourceType=None):
+        #
+        # Create the collection once we know it is safe to do so
+        #
+        def onCollection(status):
+            if status != responsecode.CREATED:
+                raise HTTPError(status)
+
+            self.writeDeadProperty(resourceType)
+            return status
+
+        def onError(f):
+            try:
+                rmdir(self.fp)
+            except Exception, e:
+                log.err("Unable to clean up after failed MKCOL (special resource type: %s): %s" % (e, resourceType,))
+            return f
+
+        d = mkcollection(self.fp)
+        if resourceType is not None:
+            d.addCallback(onCollection)
+        d.addErrback(onError)
+        return d
+
+    @inlineCallbacks
+    def iCalendarRolledup(self, request):
+        if self.isPseudoCalendarCollection():
+
+
+# FIXME: move cache implementation!
+            # Determine the cache key
+#            isvirt = self.isVirtualShare()
+#            if isvirt:
+#                principal = (yield self.resourceOwnerPrincipal(request))
+#                if principal:
+#                    cacheKey = principal.principalUID()
+#                else:
+#                    cacheKey = "unknown"
+#            else:
+#                isowner = (yield self.isOwner(request, adminprincipals=True, readprincipals=True))
+#                cacheKey = "owner" if isowner else "notowner"
+                
+            # Now check for a cached .ics
+#            rolled = self.fp.child(".subscriptions")
+#            if not rolled.exists():
+#                try:
+#                    rolled.makedirs()
+#                except IOError, e:
+#                    self.log_error("Unable to create internet calendar subscription cache directory: %s because of: %s" % (rolled.path, e,))
+#                    raise HTTPError(ErrorResponse(responsecode.INTERNAL_SERVER_ERROR))
+#            cached = rolled.child(cacheKey)
+#            if cached.exists():
+#                try:
+#                    cachedData = cached.open().read()
+#                except IOError, e:
+#                    self.log_error("Unable to open or read internet calendar subscription cache file: %s because of: %s" % (cached.path, e,))
+#                else:
+#                    # Check the cache token
+#                    token, data = cachedData.split("\r\n", 1)
+#                    if token == self.getSyncToken():
+#                        returnValue(data)
+
+            # Generate a monolithic calendar
+            calendar = iComponent("VCALENDAR")
+            calendar.addProperty(iProperty("VERSION", "2.0"))
+
+            # Do some optimisation of access control calculation by determining any inherited ACLs outside of
+            # the child resource loop and supply those to the checkPrivileges on each child.
+            filteredaces = (yield self.inheritedACEsforChildren(request))
+
+            tzids = set()
+            isowner = (yield self.isOwner(request, adminprincipals=True, readprincipals=True))
+            accessPrincipal = (yield self.resourceOwnerPrincipal(request))
+
+            for name, uid, type in self.index().bruteForceSearch(): #@UnusedVariable
+                try:
+                    child = yield request.locateChildResource(self, name)
+                except TypeError:
+                    child = None
+
+                if child is not None:
+                    # Check privileges of child - skip if access denied
+                    try:
+                        yield child.checkPrivileges(request, (davxml.Read(),), inherited_aces=filteredaces)
+                    except AccessDeniedError:
+                        continue
+
+                    # Get the access filtered view of the data
+                    caldata = child.iCalendarTextFiltered(isowner, accessPrincipal.principalUID() if accessPrincipal else "")
+                    try:
+                        subcalendar = iComponent.fromString(caldata)
+                    except ValueError:
+                        continue
+                    assert subcalendar.name() == "VCALENDAR"
+
+                    for component in subcalendar.subcomponents():
+                        
+                        # Only insert VTIMEZONEs once
+                        if component.name() == "VTIMEZONE":
+                            tzid = component.propertyValue("TZID")
+                            if tzid in tzids:
+                                continue
+                            tzids.add(tzid)
+
+                        calendar.addComponent(component)
+
+            # Cache the data
+            data = str(calendar)
+            data = self.getSyncToken() + "\r\n" + data
+#            try:
+#                cached.open(mode='w').write(data)
+#            except IOError, e:
+#                self.log_error("Unable to open or write internet calendar subscription cache file: %s because of: %s" % (cached.path, e,))
+                
+            returnValue(calendar)
+
+        raise HTTPError(ErrorResponse(responsecode.BAD_REQUEST))
+
+    def iCalendarTextFiltered(self, isowner, accessUID=None):
+        try:
+            access = self.readDeadProperty(TwistedCalendarAccessProperty)
+        except HTTPError:
+            access = None
+
+        # Now "filter" the resource calendar data
+        caldata = PrivateEventFilter(access, isowner).filter(self.iCalendarText())
+        if accessUID:
+            caldata = PerUserDataFilter(accessUID).filter(caldata)
+        return str(caldata)
+
+    def iCalendarText(self, name=None):
+        if self.isPseudoCalendarCollection():
+            if name is None:
+                return str(self.iCalendar())
+
+            calendar_resource = self.getChild(name)
+            return calendar_resource.iCalendarText()
+
+        elif self.isCollection():
+            return None
+
+        else:
+            if name is not None:
+                raise AssertionError("name must be None for non-collection calendar resource")
+
+        # FIXME: StoreBridge handles this case
+        raise NotImplementedError
+
+    def createAddressBook(self, request):
+        """
+        External API for creating an addressbook.  Verify that the parent is a
+        collection, exists, is I{not} an addressbook collection; that this resource
+        does not yet exist, then create it.
+
+        @param request: the request used to look up parent resources to
+            validate.
+
+        @type request: L{twext.web2.iweb.IRequest}
+
+        @return: a deferred that fires when an addressbook collection has been
+            created in this resource.
+        """
+        #
+        # request object is required because we need to validate against parent
+        # resources, and we need the request in order to locate the parents.
+        #
+
+        if self.exists():
+            self.log_error("Attempt to create collection where file exists: %s" % (self,))
+            raise HTTPError(StatusResponse(responsecode.NOT_ALLOWED, "File exists"))
+
+        # newStore guarantees that we always have a parent calendar home
+        #if not os.path.isdir(os.path.dirname(self.fp.path)):
+        #    log.err("Attempt to create collection with no parent: %s" % (self.fp.path,))
+        #    raise HTTPError(StatusResponse(responsecode.CONFLICT, "No parent collection"))
+
+        #
+        # Verify that no parent collection is a calendar also
+        #
+
+        def _defer(parent):
+            if parent is not None:
+                self.log_error("Cannot create an address book collection within an address book collection %s" % (parent,))
+                raise HTTPError(ErrorResponse(
+                    responsecode.FORBIDDEN,
+                    (carddavxml.carddav_namespace, "addressbook-collection-location-ok")
+                ))
+
+            return self.createAddressBookCollection()
+
+        parent = self._checkParents(request, isAddressBookCollectionResource)
+        parent.addCallback(_defer)
+        return parent
+
+    def createAddressBookCollection(self):
+        """
+        Internal API for creating an addressbook collection.
+
+        @return: a L{Deferred} which fires when the underlying collection has
+            actually been created.
+        """
+        return fail(NotImplementedError())
+
+    @inlineCallbacks
+    def vCardRolledup(self, request):
+        # TODO: just catenate all the vCards together 
+        yield fail(HTTPError((ErrorResponse(responsecode.BAD_REQUEST))))
+
+    def vCardText(self, name=None):
+        if self.isAddressBookCollection():
+            if name is None:
+                return str(self.vCard())
+
+            vcard_resource = self.getChild(name)
+            return vcard_resource.vCardText()
+
+        elif self.isCollection():
+            return None
+
+        else:
+            if name is not None:
+                raise AssertionError("name must be None for non-collection vcard resource")
+
+        # FIXME: StoreBridge handles this case
+        raise NotImplementedError
+
+    def vCardXML(self, name=None):
+        return carddavxml.AddressData.fromAddressData(self.vCardText(name))
+
+    def supportedPrivileges(self, request):
+        # read-free-busy support on calendar collection and calendar object resources
+        if self.isCollection():
+            return succeed(calendarPrivilegeSet)
+        else:
+            def gotParent(parent):
+                if parent and isCalendarCollectionResource(parent):
+                    return succeed(calendarPrivilegeSet)
+                else:
+                    return super(CalDAVResource, self).supportedPrivileges(request)
+
+            d = self.locateParent(request, request.urlForResource(self))
+            d.addCallback(gotParent)
+            return d
+
+        return super(CalDAVResource, self).supportedPrivileges(request)
+
+    def index(self):
+        """
+        Obtains the index for a calendar collection resource.
+        @return: the index object for this resource.
+        @raise AssertionError: if this resource is not a calendar collection
+            resource.
+        """
+        if self.isAddressBookCollection():
+            return AddressBookIndex(self)
+        else:
+            return Index(self)
+
+    ##
+    # Quota
+    ##
+
+    def quotaSize(self, request):
+        """
+        Get the size of this resource.
+        TODO: Take into account size of dead-properties. Does stat include xattrs size?
+
+        @return: an L{Deferred} with a C{int} result containing the size of the resource.
+        """
+#        if self.isCollection():
+#            @inlineCallbacks
+#            def walktree(top):
+#                """
+#                Recursively descend the directory tree rooted at top,
+#                calling the callback function for each regular file
+#
+#                @param top: L{FilePath} for the directory to walk.
+#                """
+#
+#                total = 0
+#                for f in top.listdir():
+#
+#                    # Ignore the database
+#                    if f.startswith("."):
+#                        continue
+#
+#                    child = top.child(f)
+#                    if child.isdir():
+#                        # It's a directory, recurse into it
+#                        total += yield walktree(child)
+#                    elif child.isfile():
+#                        # It's a file, call the callback function
+#                        total += child.getsize()
+#                    else:
+#                        # Unknown file type, print a message
+#                        pass
+#
+#                returnValue(total)
+#
+#            return walktree(self.fp)
+#        else:
+#            return succeed(self.fp.getsize())
+        return succeed(0)
+
+    ##
+    # Utilities
+    ##
+
+    @staticmethod
+    def _isChildURI(request, uri, immediateChild=True):
+        """
+        Verify that the supplied URI represents a resource that is a child
+        of the request resource.
+        @param request: the request currently in progress
+        @param uri: the URI to test
+        @return: True if the supplied URI is a child resource
+                 False if not
+        """
+        if uri is None: return False
+
+        #
+        # Parse the URI
+        #
+
+        (scheme, host, path, query, fragment) = urlsplit(uri) #@UnusedVariable
+
+        # Request hostname and child uri hostname have to be the same.
+        if host and host != request.headers.getHeader("host"):
+            return False
+
+        # Child URI must start with request uri text.
+        parent = request.uri
+        if not parent.endswith("/"):
+            parent += "/"
+
+        return path.startswith(parent) and (len(path) > len(parent)) and (not immediateChild or (path.find("/", len(parent)) == -1))
+
+    @inlineCallbacks
+    def _checkParents(self, request, test):
+        """
+        @param request: the request being processed.
+        @param test: a callable
+        @return: the closest parent for this resource using the request URI from
+            the given request for which C{test(parent)} evaluates to a true
+            value, or C{None} if no parent matches.
+        """
+        parent = self
+        parent_uri = request.uri
+
+        while True:
+            parent_uri = parentForURL(parent_uri)
+            if not parent_uri: break
+
+            parent = yield request.locateResource(parent_uri)
+
+            if test(parent):
+                returnValue(parent)
+
 class CalendarPrincipalCollectionResource (DAVPrincipalCollectionResource, CalDAVResource):
     """
     CalDAV principal collection.
@@ -1332,7 +1920,7 @@
             ),
         )
 
-class CalendarPrincipalResource (CalDAVComplianceMixIn, DAVPrincipalResource):
+class CalendarPrincipalResource (CalDAVComplianceMixIn, DAVResourceWithChildrenMixin, DAVPrincipalResource):
     """
     CalDAV principal resource.
 
@@ -1555,7 +2143,526 @@
         """
         return None
 
+class CommonHomeResource(SharedHomeMixin, CalDAVResource):
+    """
+    Calendar home collection resource.
+    """
+    def __init__(self, parent, name, transaction):
+        """
+        """
 
+        self.parent = parent
+        self.name = name
+        self.associateWithTransaction(transaction)
+        self._provisionedChildren = {}
+        self._provisionedLinks = {}
+        self._setupProvisions()
+
+        self._newStoreHome, created = self.makeNewStore()
+        CalDAVResource.__init__(self)
+
+        from twistedcaldav.storebridge import _NewStorePropertiesWrapper
+        self._dead_properties = _NewStorePropertiesWrapper(
+            self._newStoreHome.properties()
+        )
+        if created:
+            self.postCreateHome()
+
+    def _setupProvisions(self):
+        pass
+
+    def makeNewStore(self):
+        raise NotImplementedError
+
+    def postCreateHome(self):
+        pass
+
+    def liveProperties(self):
+        
+        return super(CommonHomeResource, self).liveProperties() + (
+            (customxml.calendarserver_namespace, "push-transports"),
+            (customxml.calendarserver_namespace, "pushkey"),
+            (customxml.calendarserver_namespace, "xmpp-uri"),
+            (customxml.calendarserver_namespace, "xmpp-heartbeat-uri"),
+            (customxml.calendarserver_namespace, "xmpp-server"),
+        )
+
+    def sharesDB(self):
+        """
+        Retrieve the new-style shares DB wrapper.
+        """
+        if not hasattr(self, "_sharesDB"):
+            self._sharesDB = self._newStoreHome.retrieveOldShares()
+        return self._sharesDB
+
+
+    def url(self):
+        return joinURL(self.parent.url(), self.name, "/")
+
+    def canonicalURL(self, request):
+        return succeed(self.url())
+
+    def exists(self):
+        # FIXME: tests
+        return True
+
+    def isCollection(self):
+        return True
+
+    def quotaSize(self, request):
+        # FIXME: tests, workingness
+        return succeed(0)
+
+    def hasQuotaRoot(self, request):
+        """
+        Always get quota root value from config.
+
+        @return: a C{True} if this resource has quota root, C{False} otherwise.
+        """
+        return config.UserQuota != 0
+    
+    def quotaRoot(self, request):
+        """
+        Always get quota root value from config.
+
+        @return: a C{int} containing the maximum allowed bytes if this collection
+            is quota-controlled, or C{None} if not quota controlled.
+        """
+        return config.UserQuota if config.UserQuota != 0 else None
+
+    def canShare(self):
+        raise NotImplementedError
+
+    def makeChild(self, name):
+        
+        # Try built-in children first
+        if name in self._provisionedChildren:
+            cls = self._provisionedChildren[name]
+            from twistedcaldav.notifications import NotificationCollectionResource
+            if cls is NotificationCollectionResource:
+                return self.createNotificationsCollection()
+            child = self._provisionedChildren[name](self)
+            self.putChild(name, child)
+            return child
+        
+        # Try built-in links next
+        if name in self._provisionedLinks:
+            child = LinkResource(self, self._provisionedLinks[name])
+            self.putChild(name, child)
+            return child
+        
+        # Try shares next
+        if self.canShare():
+            child = self.provisionShare(name)
+            if child:
+                return child
+
+        # Do normal child types
+        return self.makeRegularChild(name)
+
+    def createNotificationsCollection(self):
+        
+        txn = self._newStoreHome._transaction
+        notifications = txn.notificationsWithUID(self._newStoreHome.uid())
+
+        from twistedcaldav.storebridge import StoreNotificationCollectionResource
+        similar = StoreNotificationCollectionResource(
+            notifications,
+            self._newStoreHome,
+            principalCollections = self.principalCollections(),
+        )
+        self.propagateTransaction(similar)
+        return similar
+
+    def makeRegularChild(self, name):
+        raise NotImplementedError
+
+    def listChildren(self):
+        """
+        @return: a sequence of the names of all known children of this resource.
+        """
+        children = set(self._provisionedChildren.keys())
+        children.update(self._provisionedLinks.keys())
+        children.update(self.allShareNames())
+        children.update(self._newStoreHome.listChildren())
+        return children
+
+    def readProperty(self, property, request):
+        if type(property) is tuple:
+            qname = property
+        else:
+            qname = property.qname()
+
+        if qname == (customxml.calendarserver_namespace, "push-transports"):
+            pubSubConfiguration = getPubSubConfiguration(config)
+            if (pubSubConfiguration['enabled'] and
+                getattr(self, "clientNotifier", None) is not None):
+                    id = self.clientNotifier.getID()
+                    nodeName = getPubSubPath(id, pubSubConfiguration)
+                    children = []
+                    if pubSubConfiguration['aps-bundle-id']:
+                        children.append(
+                            customxml.PubSubTransportProperty(
+                                customxml.PubSubSubscriptionProperty(
+                                    davxml.HRef(
+                                        pubSubConfiguration['subscription-url']
+                                    ),
+                                ),
+                                customxml.PubSubAPSBundleIDProperty(
+                                    pubSubConfiguration['aps-bundle-id']
+                                ),
+                                type="APSD",
+                            )
+                        )
+                    if pubSubConfiguration['xmpp-server']:
+                        children.append(
+                            customxml.PubSubTransportProperty(
+                                customxml.PubSubXMPPServerProperty(
+                                    pubSubConfiguration['xmpp-server']
+                                ),
+                                customxml.PubSubXMPPURIProperty(
+                                    getPubSubXMPPURI(id, pubSubConfiguration)
+                                ),
+                                type="XMPP",
+                            )
+                        )
+
+                    propVal = customxml.PubSubPushTransportsProperty(*children)
+                    nodeCacher = getNodeCacher()
+                    d = nodeCacher.waitForNode(self.clientNotifier, nodeName)
+                    # In either case we're going to return the value
+                    d.addBoth(lambda ignored: propVal)
+                    return d
+
+
+            else:
+                return succeed(customxml.PubSubPushTransportsProperty())
+
+        if qname == (customxml.calendarserver_namespace, "pushkey"):
+            pubSubConfiguration = getPubSubConfiguration(config)
+            if pubSubConfiguration['enabled']:
+                if getattr(self, "clientNotifier", None) is not None:
+                    id = self.clientNotifier.getID()
+                    nodeName = getPubSubPath(id, pubSubConfiguration)
+                    propVal = customxml.PubSubXMPPPushKeyProperty(nodeName)
+                    nodeCacher = getNodeCacher()
+                    d = nodeCacher.waitForNode(self.clientNotifier, nodeName)
+                    # In either case we're going to return the xmpp-uri value
+                    d.addBoth(lambda ignored: propVal)
+                    return d
+            else:
+                return succeed(customxml.PubSubXMPPPushKeyProperty())
+
+
+        if qname == (customxml.calendarserver_namespace, "xmpp-uri"):
+            pubSubConfiguration = getPubSubConfiguration(config)
+            if pubSubConfiguration['enabled']:
+                if getattr(self, "clientNotifier", None) is not None:
+                    id = self.clientNotifier.getID()
+                    nodeName = getPubSubPath(id, pubSubConfiguration)
+                    propVal = customxml.PubSubXMPPURIProperty(
+                        getPubSubXMPPURI(id, pubSubConfiguration))
+                    nodeCacher = getNodeCacher()
+                    d = nodeCacher.waitForNode(self.clientNotifier, nodeName)
+                    # In either case we're going to return the xmpp-uri value
+                    d.addBoth(lambda ignored: propVal)
+                    return d
+            else:
+                return succeed(customxml.PubSubXMPPURIProperty())
+
+        elif qname == (customxml.calendarserver_namespace, "xmpp-heartbeat-uri"):
+            pubSubConfiguration = getPubSubConfiguration(config)
+            if pubSubConfiguration['enabled']:
+                return succeed(
+                    customxml.PubSubHeartbeatProperty(
+                        customxml.PubSubHeartbeatURIProperty(
+                            getPubSubHeartbeatURI(pubSubConfiguration)
+                        ),
+                        customxml.PubSubHeartbeatMinutesProperty(
+                            str(pubSubConfiguration['heartrate'])
+                        )
+                    )
+                )
+            else:
+                return succeed(customxml.PubSubHeartbeatURIProperty())
+
+        elif qname == (customxml.calendarserver_namespace, "xmpp-server"):
+            pubSubConfiguration = getPubSubConfiguration(config)
+            if pubSubConfiguration['enabled']:
+                return succeed(customxml.PubSubXMPPServerProperty(
+                    pubSubConfiguration['xmpp-server']))
+            else:
+                return succeed(customxml.PubSubXMPPServerProperty())
+
+        return super(CommonHomeResource, self).readProperty(property, request)
+
+    ##
+    # ACL
+    ##
+
+    def owner(self, request):
+        return succeed(davxml.HRef(self.principalForRecord().principalURL()))
+
+    def ownerPrincipal(self, request):
+        return succeed(self.principalForRecord())
+
+    def resourceOwnerPrincipal(self, request):
+        return succeed(self.principalForRecord())
+
+    def defaultAccessControlList(self):
+        myPrincipal = self.principalForRecord()
+
+        aces = (
+            # Inheritable DAV:all access for the resource's associated principal.
+            davxml.ACE(
+                davxml.Principal(davxml.HRef(myPrincipal.principalURL())),
+                davxml.Grant(davxml.Privilege(davxml.All())),
+                davxml.Protected(),
+                TwistedACLInheritable(),
+            ),
+        )
+
+        # Give read access to config.ReadPrincipals
+        aces += config.ReadACEs
+
+        # Give all access to config.AdminPrincipals
+        aces += config.AdminACEs
+        
+        return davxml.ACL(*aces)
+
+    def accessControlList(self, request, inheritance=True, expanding=False, inherited_aces=None):
+        # Permissions here are fixed, and are not subject to inheritance rules, etc.
+        return succeed(self.defaultAccessControlList())
+
+    def principalCollections(self):
+        return self.parent.principalCollections()
+
+    def principalForRecord(self):
+        raise NotImplementedError("Subclass must implement principalForRecord()")
+
+    # Methods not supported
+    http_ACL = None
+    http_COPY = None
+    http_MOVE = None
+
+
+class CalendarHomeResource(CommonHomeResource):
+    """
+    Calendar home collection resource.
+    """
+
+    def _setupProvisions(self):
+
+        # Cache children which must be of a specific type
+        from twistedcaldav.storebridge import StoreScheduleInboxResource
+        self._provisionedChildren["inbox"] = StoreScheduleInboxResource
+
+        from twistedcaldav.schedule import ScheduleOutboxResource
+        self._provisionedChildren["outbox"] = ScheduleOutboxResource
+
+        if config.EnableDropBox:
+            from twistedcaldav.storebridge import DropboxCollection
+            self._provisionedChildren["dropbox"] = DropboxCollection
+
+        if config.FreeBusyURL.Enabled:
+            from twistedcaldav.freebusyurl import FreeBusyURLResource
+            self._provisionedChildren["freebusy"] = FreeBusyURLResource
+
+        if config.Sharing.Enabled and config.Sharing.Calendars.Enabled:
+            from twistedcaldav.notifications import NotificationCollectionResource
+            self._provisionedChildren["notification"] = NotificationCollectionResource
+
+    def makeNewStore(self):
+        storeHome = self._associatedTransaction.calendarHomeWithUID(self.name)
+        if storeHome is not None:
+            created = False
+        else:
+            storeHome = self._associatedTransaction.calendarHomeWithUID(
+                self.name, create=True
+            )
+            created = True
+
+        return storeHome, created
+
+    def postCreateHome(self):
+        # This is a bit of a hack.  Really we ought to be always generating
+        # this URL live from a back-end method that tells us what the
+        # default calendar is.
+        inbox = self.getChild("inbox")
+        childURL = joinURL(self.url(), "calendar")
+        inbox.processFreeBusyCalendar(childURL, True)
+
+    def canShare(self):
+        return config.Sharing.Enabled and config.Sharing.Calendars.Enabled and self.exists()
+
+    def makeRegularChild(self, name):
+
+        newCalendar = self._newStoreHome.calendarWithName(name)
+        if newCalendar is None:
+            # Local imports.due to circular dependency between modules.
+            from twistedcaldav.storebridge import (
+                 ProtoCalendarCollectionResource)
+            similar = ProtoCalendarCollectionResource(
+                self._newStoreHome,
+                name,
+                principalCollections=self.principalCollections()
+            )
+        else:
+            from twistedcaldav.storebridge import CalendarCollectionResource
+            similar = CalendarCollectionResource(
+                newCalendar, self._newStoreHome,
+                principalCollections=self.principalCollections()
+            )
+        self.propagateTransaction(similar)
+        return similar
+
+    def defaultAccessControlList(self):
+        myPrincipal = self.principalForRecord()
+
+        aces = (
+            # Inheritable DAV:all access for the resource's associated principal.
+            davxml.ACE(
+                davxml.Principal(davxml.HRef(myPrincipal.principalURL())),
+                davxml.Grant(davxml.Privilege(davxml.All())),
+                davxml.Protected(),
+                TwistedACLInheritable(),
+            ),
+            # Inheritable CALDAV:read-free-busy access for authenticated users.
+            davxml.ACE(
+                davxml.Principal(davxml.Authenticated()),
+                davxml.Grant(davxml.Privilege(caldavxml.ReadFreeBusy())),
+                TwistedACLInheritable(),
+            ),
+        )
+
+        # Give read access to config.ReadPrincipals
+        aces += config.ReadACEs
+
+        # Give all access to config.AdminPrincipals
+        aces += config.AdminACEs
+        
+        if config.EnableProxyPrincipals:
+            aces += (
+                # DAV:read/DAV:read-current-user-privilege-set access for this principal's calendar-proxy-read users.
+                davxml.ACE(
+                    davxml.Principal(davxml.HRef(joinURL(myPrincipal.principalURL(), "calendar-proxy-read/"))),
+                    davxml.Grant(
+                        davxml.Privilege(davxml.Read()),
+                        davxml.Privilege(davxml.ReadCurrentUserPrivilegeSet()),
+                    ),
+                    davxml.Protected(),
+                    TwistedACLInheritable(),
+                ),
+                # DAV:read/DAV:read-current-user-privilege-set/DAV:write access for this principal's calendar-proxy-write users.
+                davxml.ACE(
+                    davxml.Principal(davxml.HRef(joinURL(myPrincipal.principalURL(), "calendar-proxy-write/"))),
+                    davxml.Grant(
+                        davxml.Privilege(davxml.Read()),
+                        davxml.Privilege(davxml.ReadCurrentUserPrivilegeSet()),
+                        davxml.Privilege(davxml.Write()),
+                    ),
+                    davxml.Protected(),
+                    TwistedACLInheritable(),
+                ),
+            )
+
+        return davxml.ACL(*aces)
+
+class AddressBookHomeResource (CommonHomeResource):
+    """
+    Address book home collection resource.
+    """
+    
+    def _setupProvisions(self):
+
+        # Cache children which must be of a specific type
+        if config.Sharing.Enabled and config.Sharing.AddressBooks.Enabled and not config.Sharing.Calendars.Enabled:
+            from twistedcaldav.notifications import NotificationCollectionResource
+            self._provisionedChildren["notification"] = NotificationCollectionResource
+
+        if config.GlobalAddressBook.Enabled:
+            self._provisionedLinks[config.GlobalAddressBook.Name] = "/addressbooks/public/global/addressbook/"
+
+    def makeNewStore(self):
+        return self._associatedTransaction.addressbookHomeWithUID(self.name, create=True), False     # Don't care about created
+
+    def canShare(self):
+        return config.Sharing.Enabled and config.Sharing.AddressBooks.Enabled and self.exists()
+
+    def makeRegularChild(self, name):
+
+        # Check for public/global path
+        from twistedcaldav.storebridge import (
+            AddressBookCollectionResource,
+            ProtoAddressBookCollectionResource,
+            GlobalAddressBookCollectionResource,
+            ProtoGlobalAddressBookCollectionResource,
+        )
+        mainCls = AddressBookCollectionResource
+        protoCls = ProtoAddressBookCollectionResource
+        if isinstance(self.record, InternalDirectoryRecord):
+            if "global" in self.record.shortNames:
+                mainCls = GlobalAddressBookCollectionResource
+                protoCls = ProtoGlobalAddressBookCollectionResource
+
+        newAddressBook = self._newStoreHome.addressbookWithName(name)
+        if newAddressBook is None:
+            # Local imports.due to circular dependency between modules.
+            similar = protoCls(
+                self._newStoreHome,
+                name,
+                principalCollections=self.principalCollections()
+            )
+        else:
+            similar = mainCls(
+                newAddressBook, self._newStoreHome,
+                principalCollections=self.principalCollections()
+            )
+        self.propagateTransaction(similar)
+        return similar
+
+
+class GlobalAddressBookResource (ReadOnlyResourceMixIn, CalDAVResource):
+    """
+    Global address book. All we care about is making sure permissions are setup.
+    """
+
+    def resourceType(self):
+        return davxml.ResourceType.sharedaddressbook
+
+    def defaultAccessControlList(self):
+
+        aces = (
+            davxml.ACE(
+                davxml.Principal(davxml.Authenticated()),
+                davxml.Grant(
+                    davxml.Privilege(davxml.Read()),
+                    davxml.Privilege(davxml.ReadCurrentUserPrivilegeSet()),
+                    davxml.Privilege(davxml.Write()),
+                ),
+                davxml.Protected(),
+                TwistedACLInheritable(),
+           ),
+        )
+        
+        if config.GlobalAddressBook.EnableAnonymousReadAccess:
+            aces += (
+                davxml.ACE(
+                    davxml.Principal(davxml.Unauthenticated()),
+                    davxml.Grant(
+                        davxml.Privilege(davxml.Read()),
+                    ),
+                    davxml.Protected(),
+                    TwistedACLInheritable(),
+               ),
+            )
+        return davxml.ACL(*aces)
+
+    def accessControlList(self, request, inheritance=True, expanding=False, inherited_aces=None):
+        # Permissions here are fixed, and are not subject to inheritance rules, etc.
+        return succeed(self.defaultAccessControlList())
+
+
 class AuthenticationWrapper(SuperAuthenticationWrapper):
 
     """ AuthenticationWrapper implementation which allows overriding

Modified: CalendarServer/trunk/twistedcaldav/schedule.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/schedule.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/twistedcaldav/schedule.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -25,28 +25,70 @@
     "IScheduleInboxResource",
 ]
 
-from twext.web2.dav.http import ErrorResponse
 
-from twisted.internet.defer import inlineCallbacks, returnValue, succeed
 from twext.web2 import responsecode
 from twext.web2.dav import davxml
 from twext.web2.dav.element.extensions import SyncCollection
+from twext.web2.dav.element.rfc2518 import HRef
+from twext.web2.dav.http import ErrorResponse, MultiStatusResponse
+from twext.web2.dav.noneprops import NonePropertyStore
+from twext.web2.dav.resource import davPrivilegeSet
 from twext.web2.dav.util import joinURL, normalizeURL
 from twext.web2.http import HTTPError
 from twext.web2.http import Response
 from twext.web2.http_headers import MimeType
 
+from twisted.internet.defer import inlineCallbacks, returnValue, succeed
+
 from twistedcaldav import caldavxml
-from twext.web2.dav.element.rfc2518 import HRef
-from txdav.propertystore.base import PropertyName
 from twistedcaldav.caldavxml import caldav_namespace, Opaque,\
     CalendarFreeBusySet, ScheduleCalendarTransp
 from twistedcaldav.config import config
 from twistedcaldav.customxml import calendarserver_namespace
-from twistedcaldav.resource import CalDAVResource
+from twistedcaldav.extensions import DAVResource
+from twistedcaldav.resource import CalDAVResource, ReadOnlyNoCopyResourceMixIn
 from twistedcaldav.resource import isCalendarCollectionResource
 from twistedcaldav.scheduling.scheduler import CalDAVScheduler, IScheduleScheduler
 
+from txdav.propertystore.base import PropertyName
+
+def _schedulePrivilegeSet(deliver):
+    edited = False
+
+    top_supported_privileges = []
+
+    for supported_privilege in davPrivilegeSet.childrenOfType(davxml.SupportedPrivilege):
+        all_privilege = supported_privilege.childOfType(davxml.Privilege)
+        if isinstance(all_privilege.children[0], davxml.All):
+            all_description = supported_privilege.childOfType(davxml.Description)
+            all_supported_privileges = list(supported_privilege.childrenOfType(davxml.SupportedPrivilege))
+            all_supported_privileges.append(
+                davxml.SupportedPrivilege(
+                    davxml.Privilege(caldavxml.ScheduleDeliver() if deliver else caldavxml.ScheduleSend()),
+                    davxml.Description("schedule privileges for current principal", **{"xml:lang": "en"}),
+                ),
+            )
+            if config.Scheduling.CalDAV.OldDraftCompatibility:
+                all_supported_privileges.append(
+                    davxml.SupportedPrivilege(
+                        davxml.Privilege(caldavxml.Schedule()),
+                        davxml.Description("old-style schedule privileges for current principal", **{"xml:lang": "en"}),
+                    ),
+                )
+            top_supported_privileges.append(
+                davxml.SupportedPrivilege(all_privilege, all_description, *all_supported_privileges)
+            )
+            edited = True
+        else:
+            top_supported_privileges.append(supported_privilege)
+
+    assert edited, "Structure of davPrivilegeSet changed in a way that I don't know how to extend for schedulePrivilegeSet"
+
+    return davxml.SupportedPrivilegeSet(*top_supported_privileges)
+
+deliverSchedulePrivilegeSet = _schedulePrivilegeSet(True)
+sendSchedulePrivilegeSet = _schedulePrivilegeSet(False)
+
 class CalendarSchedulingCollectionResource (CalDAVResource):
     """
     CalDAV principal resource.
@@ -60,7 +102,7 @@
         """
         assert parent is not None
 
-        CalDAVResource.__init__(self, principalCollections=parent.principalCollections())
+        super(CalendarSchedulingCollectionResource, self).__init__(principalCollections=parent.principalCollections())
 
         self.parent = parent
 
@@ -97,25 +139,9 @@
             (caldav_namespace, "schedule-default-calendar-URL"),
         )
 
-    def resourceType(self, request):
-        return succeed(davxml.ResourceType.scheduleInbox)
+    def resourceType(self):
+        return davxml.ResourceType.scheduleInbox
 
-    def defaultAccessControlList(self):
-        
-        privs = (
-            davxml.Privilege(caldavxml.ScheduleDeliver()),
-        )
-        if config.Scheduling.CalDAV.OldDraftCompatibility:
-            privs += (davxml.Privilege(caldavxml.Schedule()),)
-
-        return davxml.ACL(
-            # CalDAV:schedule-deliver for any authenticated user
-            davxml.ACE(
-                davxml.Principal(davxml.Authenticated()),
-                davxml.Grant(*privs),
-            ),
-        )
-
     @inlineCallbacks
     def readProperty(self, property, request):
         if type(property) is tuple:
@@ -128,7 +154,7 @@
             if not self.hasDeadProperty(property):
                 top = self.parent.url()
                 values = []
-                for cal in self.parent._newStoreCalendarHome.calendars():
+                for cal in self.parent._newStoreHome.calendars():
                     prop = cal.properties().get(PropertyName.fromString(ScheduleCalendarTransp.sname())) 
                     if prop == ScheduleCalendarTransp(Opaque()):
                         values.append(HRef(joinURL(top, cal.name())))
@@ -235,7 +261,7 @@
         defaultCalendarURL = joinURL(calendarHomeURL, "calendar")
         defaultCalendar = (yield request.locateResource(defaultCalendarURL))
         if defaultCalendar is None or not defaultCalendar.exists():
-            getter = iter(self.parent._newStoreCalendarHome.calendars())
+            getter = iter(self.parent._newStoreHome.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()'
@@ -255,6 +281,29 @@
             davxml.HRef(defaultCalendarURL))
         )
 
+    ##
+    # ACL
+    ##
+
+    def supportedPrivileges(self, request):
+        return succeed(deliverSchedulePrivilegeSet)
+
+    def defaultAccessControlList(self):
+        
+        privs = (
+            davxml.Privilege(caldavxml.ScheduleDeliver()),
+        )
+        if config.Scheduling.CalDAV.OldDraftCompatibility:
+            privs += (davxml.Privilege(caldavxml.Schedule()),)
+
+        return davxml.ACL(
+            # CalDAV:schedule-deliver for any authenticated user
+            davxml.ACE(
+                davxml.Principal(davxml.Authenticated()),
+                davxml.Grant(*privs),
+            ),
+        )
+
 class ScheduleOutboxResource (CalendarSchedulingCollectionResource):
     """
     CalDAV schedule Outbox resource.
@@ -262,6 +311,37 @@
     Extends L{DAVResource} to provide CalDAV functionality.
     """
 
+    def resourceType(self):
+        return davxml.ResourceType.scheduleOutbox
+
+    @inlineCallbacks
+    def http_POST(self, request):
+        """
+        The CalDAV POST method.
+    
+        This uses a generator function yielding either L{waitForDeferred} objects or L{Response} objects.
+        This allows for code that follows a 'linear' execution pattern rather than having to use nested
+        L{Deferred} callbacks. The logic is easier to follow this way plus we don't run into deep nesting
+        issues which the other approach would have with large numbers of recipients.
+        """
+        # Check authentication and access controls
+        yield self.authorize(request, (caldavxml.ScheduleSend(),))
+
+        # This is a local CALDAV scheduling operation.
+        scheduler = CalDAVScheduler(request, self)
+
+        # Do the POST processing treating
+        result = (yield scheduler.doSchedulingViaPOST())
+        returnValue(result.response())
+
+
+    ##
+    # ACL
+    ##
+
+    def supportedPrivileges(self, request):
+        return succeed(sendSchedulePrivilegeSet)
+
     def defaultAccessControlList(self):
         if config.EnableProxyPrincipals:
             myPrincipal = self.parent.principalForRecord()
@@ -283,30 +363,14 @@
         else:
             return super(ScheduleOutboxResource, self).defaultAccessControlList()
 
-    def resourceType(self, request):
-        return succeed(davxml.ResourceType.scheduleOutbox)
+    def report_urn_ietf_params_xml_ns_caldav_calendar_query(self, request, calendar_query):
+        return succeed(MultiStatusResponse(()))
+        
+    def report_urn_ietf_params_xml_ns_caldav_calendar_multiget(self, request, multiget):
+        responses = [davxml.StatusResponse(href, davxml.Status.fromResponseCode(responsecode.NOT_FOUND)) for href in multiget.resources]
+        return succeed(MultiStatusResponse((responses)))
 
-    @inlineCallbacks
-    def http_POST(self, request):
-        """
-        The CalDAV POST method.
-    
-        This uses a generator function yielding either L{waitForDeferred} objects or L{Response} objects.
-        This allows for code that follows a 'linear' execution pattern rather than having to use nested
-        L{Deferred} callbacks. The logic is easier to follow this way plus we don't run into deep nesting
-        issues which the other approach would have with large numbers of recipients.
-        """
-        # Check authentication and access controls
-        yield self.authorize(request, (caldavxml.ScheduleSend(),))
-
-        # This is a local CALDAV scheduling operation.
-        scheduler = CalDAVScheduler(request, self)
-
-        # Do the POST processing treating
-        result = (yield scheduler.doSchedulingViaPOST())
-        returnValue(result.response())
-
-class IScheduleInboxResource (CalDAVResource):
+class IScheduleInboxResource (ReadOnlyNoCopyResourceMixIn, DAVResource):
     """
     iSchedule Inbox resource.
 
@@ -319,30 +383,24 @@
         """
         assert parent is not None
 
-        CalDAVResource.__init__(self, principalCollections=parent.principalCollections())
+        DAVResource.__init__(self, principalCollections=parent.principalCollections())
 
         self.parent = parent
 
-    def defaultAccessControlList(self):
-        privs = (
-            davxml.Privilege(davxml.Read()),
-            davxml.Privilege(caldavxml.ScheduleDeliver()),
-        )
-        if config.Scheduling.CalDAV.OldDraftCompatibility:
-            privs += (davxml.Privilege(caldavxml.Schedule()),)
+    def deadProperties(self):
+        if not hasattr(self, "_dead_properties"):
+            self._dead_properties = NonePropertyStore(self)
+        return self._dead_properties
 
-        return davxml.ACL(
-            # DAV:Read, CalDAV:schedule-deliver for all principals (includes anonymous)
-            davxml.ACE(
-                davxml.Principal(davxml.All()),
-                davxml.Grant(*privs),
-                davxml.Protected(),
-            ),
-        )
+    def etag(self):
+        return None
 
-    def resourceType(self, request):
-        return succeed(davxml.ResourceType.ischeduleinbox)
+    def checkPreconditions(self, request):
+        return None
 
+    def resourceType(self):
+        return davxml.ResourceType.ischeduleinbox
+
     def isCollection(self):
         return False
 
@@ -381,3 +439,27 @@
         # Do the POST processing treating this as a non-local schedule
         result = (yield scheduler.doSchedulingViaPOST(use_request_headers=True))
         returnValue(result.response())
+
+    ##
+    # ACL
+    ##
+
+    def supportedPrivileges(self, request):
+        return succeed(deliverSchedulePrivilegeSet)
+
+    def defaultAccessControlList(self):
+        privs = (
+            davxml.Privilege(davxml.Read()),
+            davxml.Privilege(caldavxml.ScheduleDeliver()),
+        )
+        if config.Scheduling.CalDAV.OldDraftCompatibility:
+            privs += (davxml.Privilege(caldavxml.Schedule()),)
+
+        return davxml.ACL(
+            # DAV:Read, CalDAV:schedule-deliver for all principals (includes anonymous)
+            davxml.ACE(
+                davxml.Principal(davxml.All()),
+                davxml.Grant(*privs),
+                davxml.Protected(),
+            ),
+        )

Modified: CalendarServer/trunk/twistedcaldav/scheduling/caldav.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/scheduling/caldav.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/twistedcaldav/scheduling/caldav.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -133,7 +133,7 @@
     @inlineCallbacks
     def generateResponse(self, recipient, responses):
         # Hash the iCalendar data for use as the last path element of the URI path
-        name = md5(self.scheduler.calendardata + str(time.time()) + recipient.inbox.fp.path).hexdigest() + ".ics"
+        name = md5(self.scheduler.calendardata + str(time.time()) + recipient.inboxURL).hexdigest() + ".ics"
     
         # Get a resource for the new item
         childURL = joinURL(recipient.inboxURL, name)

Modified: CalendarServer/trunk/twistedcaldav/scheduling/processing.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/scheduling/processing.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/twistedcaldav/scheduling/processing.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -288,7 +288,7 @@
             log.debug("ImplicitProcessing - originator '%s' to recipient '%s' processing METHOD:REQUEST, UID: '%s' - new processed" % (self.originator.cuaddr, self.recipient.cuaddr, self.uid))
             autoprocessed = self.recipient.principal.getAutoSchedule()
             new_calendar = iTipProcessing.processNewRequest(self.message, self.recipient.cuaddr, autoprocessing=autoprocessed)
-            name =  md5(str(new_calendar) + str(time.time()) + default.fp.path).hexdigest() + ".ics"
+            name =  md5(str(new_calendar) + str(time.time()) + defaultURL).hexdigest() + ".ics"
             
             # Handle auto-reply behavior
             if autoprocessed:
@@ -616,15 +616,15 @@
         resource or by creating a new one.
         
         @param collURL: the C{str} containing the URL of the calendar collection.
-        @param collection: the L{CalDAVFile} for the calendar collection to store the resource in.
+        @param collection: the L{CalDAVResource} for the calendar collection to store the resource in.
         @param name: the C{str} for the resource name to write into, or {None} to write a new resource.
         @param calendar: the L{Component} calendar to write.
-        @return: L{Deferred} -> L{CalDAVFile}
+        @return: L{Deferred} -> L{CalDAVResource}
         """
         
         # Create a new name if one was not provided
         if name is None:
-            name =  md5(str(calendar) + str(time.time()) + collection.fp.path).hexdigest() + ".ics"
+            name =  md5(str(calendar) + str(time.time()) + collURL).hexdigest() + ".ics"
     
         # Get a resource for the new item
         newchildURL = joinURL(collURL, name)
@@ -655,7 +655,7 @@
         @param collURL: the URL of the calendar collection.
         @type name: C{str}
         @param collection: the calendar collection to delete the resource from.
-        @type collection: L{CalDAVFile}
+        @type collection: L{CalDAVResource}
         @param name: the resource name to write into, or {None} to write a new resource.
         @type name: C{str}
         """

Modified: CalendarServer/trunk/twistedcaldav/scheduling/utils.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/scheduling/utils.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/twistedcaldav/scheduling/utils.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -14,7 +14,7 @@
 # limitations under the License.
 ##
 
-from twisted.internet.defer import inlineCallbacks, returnValue
+from twisted.internet.defer import inlineCallbacks, returnValue, succeed
 from twistedcaldav.method import report_common
 from twext.web2.dav.util import joinURL
 
@@ -40,12 +40,10 @@
         request._rememberResource(calendar_home, calendar_home.url())
 
         # Run a UID query against the UID
-        @inlineCallbacks
         def queryCalendarCollection(collection, uri):
             if not allow_shared:
-                isvirt = (yield collection.isVirtualShare(request))
-                if isvirt:
-                    returnValue(True)
+                if collection.isVirtualShare():
+                    return succeed(True)
 
             rname = collection.index().resourceNameForUID(uid)
             if rname:
@@ -56,9 +54,9 @@
                 result["resource_name"] = rname
                 result["calendar_collection"] = collection
                 result["calendar_collection_uri"] = uri
-                returnValue(False)
+                return succeed(False)
             else:
-                returnValue(True)
+                return succeed(True)
         
         # NB We are by-passing privilege checking here. That should be OK as the data found is not
         # exposed to the user.

Modified: CalendarServer/trunk/twistedcaldav/sharedcollection.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/sharedcollection.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/twistedcaldav/sharedcollection.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -14,13 +14,14 @@
 # limitations under the License.
 ##
 
+__all__ = [
+    "SharedCollectionResource",
+]
+
 from twisted.internet.defer import inlineCallbacks, returnValue
 
 from twistedcaldav.linkresource import LinkResource
 
-__all__ = [
-    "SharedCollectionResource",
-]
 
 """
 Sharing behavior
@@ -40,6 +41,12 @@
         
         if not hasattr(self, "_linkedResource"):
             self._linkedResource = (yield request.locateResource(self.share.hosturl))
+            
+            # FIXME: this is awkward - because we are "mutation" this object into a virtual share
+            # we must not cache the resource at this URL, otherwise an access of the owner's resource
+            # will return the same virtually shared one which would be wrong.
+            request._forgetResource(self._linkedResource, self.share.hosturl)
+
             ownerPrincipal = (yield self.parent.ownerPrincipal(request))
             self._linkedResource.setVirtualShare(ownerPrincipal, self.share)
         returnValue(self._linkedResource)

Modified: CalendarServer/trunk/twistedcaldav/sharing.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/sharing.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/twistedcaldav/sharing.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -1,3 +1,4 @@
+# -*- test-case-name: twistedcaldav.test.test_sharing -*-
 ##
 # Copyright (c) 2010 Apple Inc. All rights reserved.
 #
@@ -24,15 +25,20 @@
 from twext.web2.dav.http import ErrorResponse, MultiStatusResponse
 from twext.web2.dav.resource import TwistedACLInheritable
 from twext.web2.dav.util import allDataFromStream, joinURL
-from twext.web2.http import HTTPError, Response, StatusResponse, XMLResponse
+from twext.web2.http import HTTPError, Response, XMLResponse
+
 from twisted.internet.defer import succeed, inlineCallbacks, DeferredList,\
     returnValue
+
 from twistedcaldav import customxml, caldavxml
 from twistedcaldav.config import config
 from twistedcaldav.customxml import calendarserver_namespace
+from twistedcaldav.linkresource import LinkFollowerMixIn
 from twistedcaldav.sql import AbstractSQLDatabase, db_prefix
+
+from vobject.icalendar import dateTimeToString, utc
+
 from uuid import uuid4
-from vobject.icalendar import dateTimeToString, utc
 import datetime
 import os
 import types
@@ -66,26 +72,23 @@
                 return None
         return self.isShared(request).addCallback(sharedOK)
 
-    @inlineCallbacks
-    def upgradeToShare(self, request):
+    def upgradeToShare(self):
         """ Upgrade this collection to a shared state """
         
         # Change resourcetype
-        rtype = (yield self.resourceType(request))
+        rtype = self.resourceType()
         rtype = davxml.ResourceType(*(rtype.children + (customxml.SharedOwner(),)))
         self.writeDeadProperty(rtype)
         
         # Create invites database
         self.invitesDB().create()
-
-        returnValue(True)
     
     @inlineCallbacks
     def downgradeFromShare(self, request):
         
         # Change resource type (note this might be called after deleting a resource
         # so we have to cope with that)
-        rtype = (yield self.resourceType(request))
+        rtype = self.resourceType()
         rtype = davxml.ResourceType(*([child for child in rtype.children if child != customxml.SharedOwner()]))
         self.writeDeadProperty(rtype)
         
@@ -212,9 +215,9 @@
         elif hasattr(self, "_newStoreAddressBook"):
             self._newStoreAddressBook.setSharingUID(self._shareePrincipal.principalUID())
 
-    def isVirtualShare(self, request):
+    def isVirtualShare(self):
         """ Return True if this is a shared calendar collection """
-        return succeed(hasattr(self, "_isVirtualShare"))
+        return hasattr(self, "_isVirtualShare")
 
     def removeVirtualShare(self, request):
         """ Return True if this is a shared calendar collection """
@@ -226,17 +229,16 @@
             shareeHome = self._shareePrincipal.addressBookHome(request)
         return shareeHome.removeShare(request, self._share)
 
-    @inlineCallbacks
-    def resourceType(self, request):
+    def resourceType(self):
         superObject = super(SharedCollectionMixin, self)
         try:
             superMethod = superObject.resourceType
         except AttributeError:
             rtype = davxml.ResourceType()
         else:
-            rtype = (yield superMethod(request))
+            rtype = superMethod()
 
-        isVirt = (yield self.isVirtualShare(request))
+        isVirt = self.isVirtualShare()
         if isVirt:
             rtype = davxml.ResourceType(
                 *(
@@ -244,7 +246,7 @@
                     (customxml.Shared(),)
                 )
             )
-        returnValue(rtype)
+        return rtype
         
     def sharedResourceType(self):
         """
@@ -635,10 +637,7 @@
 
             def _autoShare(isShared, request):
                 if not isShared:
-                    return self.upgradeToShare(request)
-                else:
-                    return succeed(True)
-                raise HTTPError(StatusResponse(responsecode.FORBIDDEN, "Cannot upgrade to shared calendar"))
+                    self.upgradeToShare()
 
             @inlineCallbacks
             def _processInviteDoc(_, request):
@@ -798,7 +797,7 @@
 
     def __init__(self, resource):
         """
-        @param resource: the L{twistedcaldav.static.CalDAVFile} resource for
+        @param resource: the L{CalDAVResource} resource for
             the shared collection. C{resource} must be a calendar/addressbook collection.)
         """
         self.resource = resource
@@ -811,6 +810,17 @@
         """
         self._db()
 
+
+    @property
+    def dbpath(self):
+        return self.resource.fp.child(InvitesDatabase.db_basename).path
+
+
+    @dbpath.setter
+    def dbpath(self, newpath):
+        pass
+
+
     def allRecords(self):
         
         records = self._db_execute("select * from INVITE order by USERID")
@@ -924,7 +934,7 @@
         
         return Invite(*[str(item) if type(item) == types.UnicodeType else item for item in row])
 
-class SharedHomeMixin(object):
+class SharedHomeMixin(LinkFollowerMixIn):
     """
     A mix-in for calendar/addressbook homes that defines the operations for manipulating a sharee's
     set of shared calendars.
@@ -936,15 +946,25 @@
             self._sharesDB = SharedCollectionsDatabase(self)
         return self._sharesDB
 
-    def provisionShares(self):
+    def provisionShare(self, name):
         
-        if not hasattr(self, "_provisionedShares"):
+        # Try to find a matching share
+        child = None
+        shares = self.allShares()
+        if name in shares:
             from twistedcaldav.sharedcollection import SharedCollectionResource
-            for share in self.sharesDB().allRecords():
-                child = SharedCollectionResource(self, share)
-                self.putChild(share.localname, child)
-            self._provisionedShares = True
+            child = SharedCollectionResource(self, shares[name])
+            self.putChild(name, child)
+        return child
 
+    def allShares(self):
+        if not hasattr(self, "_allShares"):
+            self._allShares = dict([(share.localname, share) for share in self.sharesDB().allRecords()])
+        return self._allShares
+
+    def allShareNames(self):
+        return tuple(self.allShares().keys())
+
     @inlineCallbacks
     def acceptInviteShare(self, request, hostUrl, inviteUID, displayname=None):
         
@@ -1153,13 +1173,24 @@
 
     def __init__(self, resource):   
         """
-        @param resource: the L{twistedcaldav.static.CalDAVFile} resource for
+        @param resource: the L{CalDAVResource} resource for
             the shared collection. C{resource} must be a calendar/addressbook home collection.)
         """
         self.resource = resource
         db_filename = os.path.join(self.resource.fp.path, SharedCollectionsDatabase.db_basename)
         super(SharedCollectionsDatabase, self).__init__(db_filename, True, autocommit=True)
 
+
+    @property
+    def dbpath(self):
+        return self.resource.fp.child(SharedCollectionsDatabase.db_basename).path
+
+
+    @dbpath.setter
+    def dbpath(self, newpath):
+        pass
+
+
     def create(self):
         """
         Create the index and initialize it.

Modified: CalendarServer/trunk/twistedcaldav/simpleresource.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/simpleresource.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/twistedcaldav/simpleresource.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -14,13 +14,6 @@
 # limitations under the License.
 ##
 
-from twext.web2.dav import davxml
-from twext.web2.dav.noneprops import NonePropertyStore
-from twisted.internet.defer import succeed
-from twistedcaldav.directory.util import NotFilePath
-from twistedcaldav.extensions import DAVFile
-from twistedcaldav.resource import CalDAVResource
-from twistedcaldav.static import CalDAVFile
 
 """
 Implements a simple non-file resource.
@@ -31,61 +24,15 @@
     "SimpleCalDAVResource",
 ]
 
-class SimpleResource (
-    CalDAVResource,
-    DAVFile,
-):
+from twext.web2.dav import davxml
+from twext.web2.dav.noneprops import NonePropertyStore
 
-    allReadACL = davxml.ACL(
-        # Read access for all users.
-        davxml.ACE(
-            davxml.Principal(davxml.All()),
-            davxml.Grant(davxml.Privilege(davxml.Read())),
-            davxml.Protected(),
-        ),
-    )
-    authReadACL = davxml.ACL(
-        # Read access for authenticated users.
-        davxml.ACE(
-            davxml.Principal(davxml.Authenticated()),
-            davxml.Grant(davxml.Privilege(davxml.Read())),
-            davxml.Protected(),
-        ),
-    )
+from twisted.internet.defer import succeed
 
-    def __init__(self, principalCollections, isdir=False, defaultACL=authReadACL):
-        """
-        Make sure it is a collection.
-        """
-        CalDAVResource.__init__(self, principalCollections=principalCollections)
-        DAVFile.__init__(self, NotFilePath(isfile=not isdir,isdir=isdir), principalCollections=principalCollections)
-        self.defaultACL = defaultACL
+from twistedcaldav.resource import CalDAVResource
 
-    def locateChild(self, req, segments):
-        child = self.getChild(segments[0])
-        if child is not None:
-            return (child, segments[1:])
-        return (None, ())
-
-    def getChild(self, name):
-        if name == "":
-            return self
-        else:
-            return self.putChildren.get(name, None)
-
-    def deadProperties(self):
-        if not hasattr(self, "_dead_properties"):
-            self._dead_properties = NonePropertyStore(self)
-        return self._dead_properties
-
-    def etag(self):
-        return None
-
-    def accessControlList(self, request, inheritance=True, expanding=False, inherited_aces=None):
-        return succeed(self.defaultACL)
-
-class SimpleCalDAVResource (
-    CalDAVFile,
+class SimpleResource (
+    CalDAVResource,
 ):
 
     allReadACL = davxml.ACL(
@@ -110,21 +57,12 @@
         Make sure it is a collection.
         """
         CalDAVResource.__init__(self, principalCollections=principalCollections)
-        DAVFile.__init__(self, NotFilePath(isfile=not isdir,isdir=isdir), principalCollections=principalCollections)
+        self._isDir = isdir
         self.defaultACL = defaultACL
 
-    def locateChild(self, req, segments):
-        child = self.getChild(segments[0])
-        if child is not None:
-            return (child, segments[1:])
-        return (None, ())
+    def isCollection(self):
+        return self._isDir
 
-    def getChild(self, name):
-        if name == "":
-            return self
-        else:
-            return self.putChildren.get(name, None)
-
     def deadProperties(self):
         if not hasattr(self, "_dead_properties"):
             self._dead_properties = NonePropertyStore(self)
@@ -135,3 +73,5 @@
 
     def accessControlList(self, request, inheritance=True, expanding=False, inherited_aces=None):
         return succeed(self.defaultACL)
+
+SimpleCalDAVResource = SimpleResource

Deleted: CalendarServer/trunk/twistedcaldav/static.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/static.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/twistedcaldav/static.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -1,2111 +0,0 @@
-# -*- test-case-name: twistedcaldav.test -*-
-##
-# Copyright (c) 2005-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.
-##
-
-"""
-CalDAV-aware static resources.
-"""
-
-__all__ = [
-    "CalDAVFile",
-    "AutoProvisioningFileMixIn",
-    "CalendarHomeProvisioningFile",
-    "CalendarHomeUIDProvisioningFile",
-    "CalendarHomeFile",
-    "ScheduleFile",
-    "ScheduleInboxFile",
-    "ScheduleOutboxFile",
-    "IScheduleInboxFile",
-    "DropBoxHomeFile",
-    "DropBoxCollectionFile",
-    "DropBoxChildFile",
-    "TimezoneServiceFile",
-    "NotificationCollectionFile",
-    "NotificationFile",
-    "AddressBookHomeProvisioningFile",
-    "AddressBookHomeUIDProvisioningFile",
-    "AddressBookHomeFile",
-    "DirectoryBackedAddressBookFile",
-    "GlobalAddressBookFile",
-]
-
-import datetime
-import os
-import errno
-from urlparse import urlsplit
-from uuid import uuid4
-
-from twext.python.log import Logger
-
-from twisted.internet.defer import fail, succeed, inlineCallbacks, returnValue, maybeDeferred
-from twisted.python.failure import Failure
-from twext.python.filepath import CachingFilePath as FilePath
-from twext.web2 import responsecode, http, http_headers
-from twext.web2.http import HTTPError, StatusResponse
-from twext.web2.dav import davxml
-from twext.web2.dav.element.base import dav_namespace
-from twext.web2.dav.fileop import mkcollection, rmdir
-from twext.web2.dav.http import ErrorResponse, MultiStatusResponse
-from twext.web2.dav.idav import IDAVResource
-from twext.web2.dav.method import put_common, delete_common
-from twext.web2.dav.noneprops import NonePropertyStore
-from twext.web2.dav.resource import AccessDeniedError
-from twext.web2.dav.resource import davPrivilegeSet
-from twext.web2.dav.util import parentForURL, bindMethods, joinURL
-from twext.web2.http_headers import generateContentType, MimeType
-from txdav.idav import AlreadyFinishedError
-
-from twistedcaldav import caldavxml
-from twistedcaldav import carddavxml
-from twistedcaldav import customxml
-from twistedcaldav.caldavxml import caldav_namespace
-from twistedcaldav.client.reverseproxy import ReverseProxyResource
-from twistedcaldav.config import config
-from twistedcaldav.customxml import TwistedCalendarAccessProperty, TwistedScheduleMatchETags
-from twistedcaldav.datafilters.peruserdata import PerUserDataFilter
-from twistedcaldav.directory.internal import InternalDirectoryRecord
-from twistedcaldav.directory.util import NotFilePath
-from twistedcaldav.extensions import DAVFile, CachingPropertyStore
-from twistedcaldav.linkresource import LinkResource, LinkFollowerMixIn
-from twistedcaldav.memcachelock import MemcacheLock, MemcacheLockTimeoutError
-
-from twistedcaldav.freebusyurl import FreeBusyURLResource
-from twistedcaldav.ical import Component as iComponent
-from twistedcaldav.ical import Property as iProperty
-from twistedcaldav.index import Index, IndexSchedule, SyncTokenValidException
-from twistedcaldav.resource import CalDAVResource, isCalendarCollectionResource, isPseudoCalendarCollectionResource
-from twistedcaldav.resource import isAddressBookCollectionResource
-from twistedcaldav.schedule import ScheduleInboxResource, ScheduleOutboxResource, IScheduleInboxResource
-from twistedcaldav.datafilters.privateevents import PrivateEventFilter
-from twistedcaldav.dropbox import DropBoxHomeResource, DropBoxCollectionResource
-from twistedcaldav.directorybackedaddressbook import DirectoryBackedAddressBookResource
-from twistedcaldav.directory.addressbook import uidsResourceName as uidsResourceNameAddressBook,\
-    GlobalAddressBookResource
-from twistedcaldav.directory.addressbook import DirectoryAddressBookHomeProvisioningResource
-from twistedcaldav.directory.addressbook import DirectoryAddressBookHomeTypeProvisioningResource
-from twistedcaldav.directory.addressbook import DirectoryAddressBookHomeUIDProvisioningResource
-from twistedcaldav.directory.addressbook import DirectoryAddressBookHomeResource
-from twistedcaldav.directory.calendar import uidsResourceName
-from twistedcaldav.directory.calendar import DirectoryCalendarHomeProvisioningResource
-from twistedcaldav.directory.calendar import DirectoryCalendarHomeTypeProvisioningResource
-from twistedcaldav.directory.calendar import DirectoryCalendarHomeUIDProvisioningResource
-from twistedcaldav.directory.calendar import DirectoryCalendarHomeResource
-from twistedcaldav.directory.resource import AutoProvisioningResourceMixIn
-from twistedcaldav.sharing import SharedHomeMixin
-from twistedcaldav.timezoneservice import TimezoneServiceResource
-from twistedcaldav.vcardindex import AddressBookIndex
-from twistedcaldav.notify import getPubSubConfiguration, getPubSubXMPPURI
-from twistedcaldav.notify import getPubSubHeartbeatURI, getPubSubPath
-from twistedcaldav.notify import Notifier, getNodeCacher
-from twistedcaldav.notifications import NotificationCollectionResource,\
-    NotificationResource
-
-log = Logger()
-
-class ReadOnlyResourceMixIn(object):
-
-    def http_PUT        (self, request): return responsecode.FORBIDDEN
-    def http_COPY       (self, request): return responsecode.FORBIDDEN
-    def http_MOVE       (self, request): return responsecode.FORBIDDEN
-    def http_DELETE     (self, request): return responsecode.FORBIDDEN
-    def http_MKCOL      (self, request): return responsecode.FORBIDDEN
-
-    def http_MKCALENDAR(self, request):
-        return ErrorResponse(
-            responsecode.FORBIDDEN,
-            (caldav_namespace, "calendar-collection-location-ok")
-        )
-
-
-
-class CalDAVFile (LinkFollowerMixIn, CalDAVResource, DAVFile):
-    """
-    CalDAV-accessible L{DAVFile} resource.
-    """
-#    def __repr__(self):
-#        if self.isCalendarCollection():
-#            return "<%s (calendar collection): %s>" % (self.__class__.__name__, self.fp.path)
-#        else:
-#            return super(CalDAVFile, self).__repr__()
-
-    def __eq__(self, other):
-        if not isinstance(other, CalDAVFile):
-            return False
-        return self.fp.path == other.fp.path
-
-    def checkPreconditions(self, request):
-        """
-        We override the base class to handle the special implicit scheduling weak ETag behavior
-        for compatibility with old clients using If-Match.
-        """
-        
-        if config.Scheduling.CalDAV.ScheduleTagCompatibility:
-            
-            if self.exists() and self.hasDeadProperty(TwistedScheduleMatchETags):
-                etags = self.readDeadProperty(TwistedScheduleMatchETags).children
-                if len(etags) > 1:
-                    # This is almost verbatim from twext.web2.static.checkPreconditions
-                    if request.method not in ("GET", "HEAD"):
-                        
-                        # Loop over each tag and succeed if any one matches, else re-raise last exception
-                        exists = self.exists()
-                        last_modified = self.lastModified()
-                        last_exception = None
-                        for etag in etags:
-                            try:
-                                http.checkPreconditions(
-                                    request,
-                                    entityExists = exists,
-                                    etag = http_headers.ETag(etag),
-                                    lastModified = last_modified,
-                                )
-                            except HTTPError, e:
-                                last_exception = e
-                            else:
-                                break
-                        else:
-                            if last_exception:
-                                raise last_exception
-            
-                    # Check per-method preconditions
-                    method = getattr(self, "preconditions_" + request.method, None)
-                    if method:
-                        response = maybeDeferred(method, request)
-                        response.addCallback(lambda _: request)
-                        return response
-                    else:
-                        return None
-
-        return super(CalDAVFile, self).checkPreconditions(request)
-
-    def deadProperties(self, caching=True):
-        if not hasattr(self, "_dead_properties"):
-            # FIXME: this code should actually be dead, as the property store
-            # should be initialized as part of the traversal process.
- 
-            # Get the property store from super
-            deadProperties = super(CalDAVFile, self).deadProperties()
-
-            if caching:
-                # Wrap the property store in a memory store
-                deadProperties = CachingPropertyStore(deadProperties)
-
-            self._dead_properties = deadProperties
-
-        return self._dead_properties
-
-    ##
-    # CalDAV
-    ##
-
-    def createCalendar(self, request):
-        """
-        External API for creating a calendar.  Verify that the parent is a
-        collection, exists, is I{not} a calendar collection; that this resource
-        does not yet exist, then create it.
-
-        @param request: the request used to look up parent resources to
-            validate.
-
-        @type request: L{twext.web2.iweb.IRequest}
-
-        @return: a deferred that fires when a calendar collection has been
-            created in this resource.
-        """
-        if self.fp.exists():
-            log.err("Attempt to create collection where file exists: %s" % (self.fp.path,))
-            raise HTTPError(StatusResponse(responsecode.NOT_ALLOWED, "File exists"))
-
-        # newStore guarantees that we always have a parent calendar home
-        #if not self.fp.parent().isdir():
-        #    log.err("Attempt to create collection with no parent: %s" % (self.fp.path,))
-        #    raise HTTPError(StatusResponse(responsecode.CONFLICT, "No parent collection"))
-
-        #
-        # Verify that no parent collection is a calendar also
-        #
-        log.msg("Creating calendar collection %s" % (self,))
-
-        def _defer(parent):
-            if parent is not None:
-                log.err("Cannot create a calendar collection within a calendar collection %s" % (parent,))
-                raise HTTPError(ErrorResponse(
-                    responsecode.FORBIDDEN,
-                    (caldavxml.caldav_namespace, "calendar-collection-location-ok")
-                ))
-
-            return self.createCalendarCollection()
-
-        parent = self._checkParents(request, isPseudoCalendarCollectionResource)
-        parent.addCallback(_defer)
-        return parent
-
-
-    def createCalendarCollection(self):
-        """
-        Internal API for creating a calendar collection.
-
-        This will immediately create the collection without performing any
-        verification.  For the normal API, see L{CalDAVFile.createCalendar}.
-
-        The default behavior is to return a failing Deferred; for a working
-        implementation, see L{twistedcaldav.legacy}.
-
-        @return: a L{Deferred} which fires when the underlying collection has
-            actually been created.
-        """
-        return fail(NotImplementedError())
-
-
-    def createSpecialCollection(self, resourceType=None):
-        #
-        # Create the collection once we know it is safe to do so
-        #
-        def onCollection(status):
-            if status != responsecode.CREATED:
-                raise HTTPError(status)
-
-            self.writeDeadProperty(resourceType)
-            return status
-
-        def onError(f):
-            try:
-                rmdir(self.fp)
-            except Exception, e:
-                log.err("Unable to clean up after failed MKCOL (special resource type: %s): %s" % (e, resourceType,))
-            return f
-
-        d = mkcollection(self.fp)
-        if resourceType is not None:
-            d.addCallback(onCollection)
-        d.addErrback(onError)
-        return d
-
-    @inlineCallbacks
-    def iCalendarRolledup(self, request):
-        if self.isPseudoCalendarCollection():
-
-
-# FIXME: move cache implementation!
-            # Determine the cache key
-#            isvirt = (yield self.isVirtualShare(request))
-#            if isvirt:
-#                principal = (yield self.resourceOwnerPrincipal(request))
-#                if principal:
-#                    cacheKey = principal.principalUID()
-#                else:
-#                    cacheKey = "unknown"
-#            else:
-#                isowner = (yield self.isOwner(request, adminprincipals=True, readprincipals=True))
-#                cacheKey = "owner" if isowner else "notowner"
-                
-            # Now check for a cached .ics
-#            rolled = self.fp.child(".subscriptions")
-#            if not rolled.exists():
-#                try:
-#                    rolled.makedirs()
-#                except IOError, e:
-#                    log.err("Unable to create internet calendar subscription cache directory: %s because of: %s" % (rolled.path, e,))
-#                    raise HTTPError(ErrorResponse(responsecode.INTERNAL_SERVER_ERROR))
-#            cached = rolled.child(cacheKey)
-#            if cached.exists():
-#                try:
-#                    cachedData = cached.open().read()
-#                except IOError, e:
-#                    log.err("Unable to open or read internet calendar subscription cache file: %s because of: %s" % (cached.path, e,))
-#                else:
-#                    # Check the cache token
-#                    token, data = cachedData.split("\r\n", 1)
-#                    if token == self.getSyncToken():
-#                        returnValue(data)
-
-            # Generate a monolithic calendar
-            calendar = iComponent("VCALENDAR")
-            calendar.addProperty(iProperty("VERSION", "2.0"))
-
-            # Do some optimisation of access control calculation by determining any inherited ACLs outside of
-            # the child resource loop and supply those to the checkPrivileges on each child.
-            filteredaces = (yield self.inheritedACEsforChildren(request))
-
-            tzids = set()
-            isowner = (yield self.isOwner(request, adminprincipals=True, readprincipals=True))
-            accessPrincipal = (yield self.resourceOwnerPrincipal(request))
-
-            for name, uid, type in self.index().bruteForceSearch(): #@UnusedVariable
-                try:
-                    child = yield request.locateChildResource(self, name)
-                    child = IDAVResource(child)
-                except TypeError:
-                    child = None
-
-                if child is not None:
-                    # Check privileges of child - skip if access denied
-                    try:
-                        yield child.checkPrivileges(request, (davxml.Read(),), inherited_aces=filteredaces)
-                    except AccessDeniedError:
-                        continue
-
-                    # Get the access filtered view of the data
-                    caldata = child.iCalendarTextFiltered(isowner, accessPrincipal.principalUID() if accessPrincipal else "")
-                    try:
-                        subcalendar = iComponent.fromString(caldata)
-                    except ValueError:
-                        continue
-                    assert subcalendar.name() == "VCALENDAR"
-
-                    for component in subcalendar.subcomponents():
-                        
-                        # Only insert VTIMEZONEs once
-                        if component.name() == "VTIMEZONE":
-                            tzid = component.propertyValue("TZID")
-                            if tzid in tzids:
-                                continue
-                            tzids.add(tzid)
-
-                        calendar.addComponent(component)
-
-            # Cache the data
-            data = str(calendar)
-            data = self.getSyncToken() + "\r\n" + data
-#            try:
-#                cached.open(mode='w').write(data)
-#            except IOError, e:
-#                log.err("Unable to open or write internet calendar subscription cache file: %s because of: %s" % (cached.path, e,))
-                
-            returnValue(calendar)
-
-        raise HTTPError(ErrorResponse(responsecode.BAD_REQUEST))
-
-    def iCalendarTextFiltered(self, isowner, accessUID=None):
-        try:
-            access = self.readDeadProperty(TwistedCalendarAccessProperty)
-        except HTTPError:
-            access = None
-
-        # Now "filter" the resource calendar data
-        caldata = PrivateEventFilter(access, isowner).filter(self.iCalendarText())
-        if accessUID:
-            caldata = PerUserDataFilter(accessUID).filter(caldata)
-        return str(caldata)
-
-    def iCalendarText(self, name=None):
-        if self.isPseudoCalendarCollection():
-            if name is None:
-                return str(self.iCalendar())
-
-            try:
-                calendar_file = self.fp.child(name).open()
-            except IOError, e:
-                if e[0] == errno.ENOENT: return None
-                raise
-
-        elif self.isCollection():
-            return None
-
-        else:
-            if name is not None:
-                raise AssertionError("name must be None for non-collection calendar resource")
-
-            calendar_file = self.fp.open()
-
-        # FIXME: This is blocking I/O
-        try:
-            calendar_data = calendar_file.read()
-        finally:
-            calendar_file.close()
-
-        return calendar_data
-
-    def createAddressBook(self, request):
-        """
-        External API for creating an addressbook.  Verify that the parent is a
-        collection, exists, is I{not} an addressbook collection; that this resource
-        does not yet exist, then create it.
-
-        @param request: the request used to look up parent resources to
-            validate.
-
-        @type request: L{twext.web2.iweb.IRequest}
-
-        @return: a deferred that fires when an addressbook collection has been
-            created in this resource.
-        """
-        #
-        # request object is required because we need to validate against parent
-        # resources, and we need the request in order to locate the parents.
-        #
-
-        if self.fp.exists():
-            log.err("Attempt to create collection where file exists: %s" % (self.fp.path,))
-            raise HTTPError(StatusResponse(responsecode.NOT_ALLOWED, "File exists"))
-
-        # newStore guarantees that we always have a parent calendar home
-        #if not os.path.isdir(os.path.dirname(self.fp.path)):
-        #    log.err("Attempt to create collection with no parent: %s" % (self.fp.path,))
-        #    raise HTTPError(StatusResponse(responsecode.CONFLICT, "No parent collection"))
-
-        #
-        # Verify that no parent collection is a calendar also
-        #
-        log.msg("Creating address book collection %s" % (self,))
-
-        def _defer(parent):
-            if parent is not None:
-                log.err("Cannot create an address book collection within an address book collection %s" % (parent,))
-                raise HTTPError(ErrorResponse(
-                    responsecode.FORBIDDEN,
-                    (carddavxml.carddav_namespace, "addressbook-collection-location-ok")
-                ))
-
-            return self.createAddressBookCollection()
-
-        parent = self._checkParents(request, isAddressBookCollectionResource)
-        parent.addCallback(_defer)
-        return parent
-
-    def createAddressBookCollection(self):
-        """
-        Internal API for creating an addressbook collection.
-
-        This will immediately create the collection without performing any
-        verification.  For the normal API, see L{CalDAVFile.createAddressBook}.
-
-        The default behavior is to return a failing Deferred; for a working
-        implementation, see L{twistedcaldav.legacy}.
-
-        @return: a L{Deferred} which fires when the underlying collection has
-            actually been created.
-        """
-        return fail(NotImplementedError())
-
-    @inlineCallbacks
-    def vCardRolledup(self, request):
-        # TODO: just catenate all the vCards together 
-        yield fail(HTTPError((ErrorResponse(responsecode.BAD_REQUEST))))
-
-    def vCardText(self, name=None):
-        if self.isAddressBookCollection():
-            if name is None:
-                return str(self.vCard())
-
-            try:
-                vcard_file = self.fp.child(name).open()
-            except IOError, e:
-                if e[0] == errno.ENOENT: return None
-                raise
-
-        elif self.isCollection():
-            return None
-
-        else:
-            if name is not None:
-                raise AssertionError("name must be None for non-collection vcard resource")
-
-            vcard_file = self.fp.open()
-
-        # FIXME: This is blocking I/O
-        try:
-            vcard_data = vcard_file.read()
-        finally:
-            vcard_file.close()
-
-        return vcard_data
-
-    def vCardXML(self, name=None):
-        return carddavxml.AddressData.fromAddressData(self.vCardText(name))
-
-    def supportedPrivileges(self, request):
-        # read-free-busy support on calendar collection and calendar object resources
-        if self.isCollection():
-            return succeed(calendarPrivilegeSet)
-        else:
-            def gotParent(parent):
-                if parent and isCalendarCollectionResource(parent):
-                    return succeed(calendarPrivilegeSet)
-                else:
-                    return super(CalDAVFile, self).supportedPrivileges(request)
-
-            d = self.locateParent(request, request.urlForResource(self))
-            d.addCallback(gotParent)
-            return d
-
-        return super(CalDAVFile, self).supportedPrivileges(request)
-
-    ##
-    # Public additions
-    ##
-
-    def index(self):
-        """
-        Obtains the index for a calendar collection resource.
-        @return: the index object for this resource.
-        @raise AssertionError: if this resource is not a calendar collection
-            resource.
-        """
-        if self.isAddressBookCollection():
-            return AddressBookIndex(self)
-        else:
-            return Index(self)
-
-    def whatchanged(self, client_token):
-        
-        current_token = str(self.readDeadProperty(customxml.GETCTag))
-        current_uuid, current_revision = current_token.split("#", 1)
-        current_revision = int(current_revision)
-
-        if client_token:
-            try:
-                caluuid, revision = client_token.split("#", 1)
-                revision = int(revision)
-                
-                # Check client token validity
-                if caluuid != current_uuid:
-                    raise ValueError
-                if revision > current_revision:
-                    raise ValueError
-            except ValueError:
-                raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (dav_namespace, "valid-sync-token")))
-        else:
-            revision = 0
-
-        try:
-            changed, removed = self.index().whatchanged(revision)
-        except SyncTokenValidException:
-            raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (dav_namespace, "valid-sync-token")))
-
-        return changed, removed, current_token
-
-    @inlineCallbacks
-    def bumpSyncToken(self):
-        """
-        Increment the sync-token which is also the ctag.
-        
-        return: a deferred that returns the new revision number
-        """
-        assert self.isCollection()
-        
-        # Need to lock
-        lock = MemcacheLock("ResourceLock", self.fp.path, timeout=60.0)
-        try:
-            try:
-                yield lock.acquire()
-            except MemcacheLockTimeoutError:
-                raise HTTPError(StatusResponse(responsecode.CONFLICT, "Resource: %s currently in use on the server." % (self.uri,)))
-
-            try:
-                token = str(self.readDeadProperty(customxml.GETCTag))
-                caluuid, revision = token.split("#", 1)
-                revision = int(revision) + 1
-                token = "%s#%d" % (caluuid, revision,)
-    
-            except (HTTPError, ValueError):
-                # Initialise it
-                caluuid = uuid4()
-                revision = 1
-                token = "%s#%d" % (caluuid, revision,)
-    
-            yield self.updateCTag(token)
-            returnValue(revision)
-        finally:
-            yield lock.clean()
-
-    def initSyncToken(self):
-        """
-        Create a new sync-token which is also the ctag.
-        """
-        # FIXME: new implementation is in txcaldav.file, this should be
-        # deleted.
-        assert self.isCollection()
-        # Initialise it
-        caluuid = uuid4()
-        revision = 1
-        token = "%s#%d" % (caluuid, revision,)
-        try:
-            self.writeDeadProperty(customxml.GETCTag(token))
-        except:
-            return fail(Failure())
-
-    def getSyncToken(self):
-        """
-        Return current sync-token value.
-        """
-        assert self.isCollection()
-        
-        return str(self.readDeadProperty(customxml.GETCTag))
-
-    def updateCTag(self, token=None):
-        assert self.isCollection()
-        
-        if not token:
-            token = str(datetime.datetime.now())
-        try:
-            self.writeDeadProperty(customxml.GETCTag(token))
-        except:
-            return fail(Failure())
-
-        return succeed(True)
-
-    ##
-    # File
-    ##
-
-    def listChildren(self):
-        return [
-            child for child in super(CalDAVFile, self).listChildren()
-            if not child.startswith(".")
-        ]
-
-    def createSimilarFile(self, path):
-        if self.comparePath(path):
-            return self
-
-        similar = super(CalDAVFile, self).createSimilarFile(path)
-
-        if isCalendarCollectionResource(self):
-            raise RuntimeError("Calendar collection resources should really "
-                               "be represented by a different class.")
-
-        return similar
-
-    ##
-    # Quota
-    ##
-
-    def quotaSize(self, request):
-        """
-        Get the size of this resource.
-        TODO: Take into account size of dead-properties. Does stat include xattrs size?
-
-        @return: an L{Deferred} with a C{int} result containing the size of the resource.
-        """
-        if self.isCollection():
-            @inlineCallbacks
-            def walktree(top):
-                """
-                Recursively descend the directory tree rooted at top,
-                calling the callback function for each regular file
-
-                @param top: L{FilePath} for the directory to walk.
-                """
-
-                total = 0
-                for f in top.listdir():
-
-                    # Ignore the database
-                    if f.startswith("."):
-                        continue
-
-                    child = top.child(f)
-                    if child.isdir():
-                        # It's a directory, recurse into it
-                        total += yield walktree(child)
-                    elif child.isfile():
-                        # It's a file, call the callback function
-                        total += child.getsize()
-                    else:
-                        # Unknown file type, print a message
-                        pass
-
-                returnValue(total)
-
-            return walktree(self.fp)
-        else:
-            return succeed(self.fp.getsize())
-
-    ##
-    # Utilities
-    ##
-
-    @staticmethod
-    def _isChildURI(request, uri, immediateChild=True):
-        """
-        Verify that the supplied URI represents a resource that is a child
-        of the request resource.
-        @param request: the request currently in progress
-        @param uri: the URI to test
-        @return: True if the supplied URI is a child resource
-                 False if not
-        """
-        if uri is None: return False
-
-        #
-        # Parse the URI
-        #
-
-        (scheme, host, path, query, fragment) = urlsplit(uri) #@UnusedVariable
-
-        # Request hostname and child uri hostname have to be the same.
-        if host and host != request.headers.getHeader("host"):
-            return False
-
-        # Child URI must start with request uri text.
-        parent = request.uri
-        if not parent.endswith("/"):
-            parent += "/"
-
-        return path.startswith(parent) and (len(path) > len(parent)) and (not immediateChild or (path.find("/", len(parent)) == -1))
-
-    @inlineCallbacks
-    def _checkParents(self, request, test):
-        """
-        @param request: the request being processed.
-        @param test: a callable
-        @return: the closest parent for this resource using the request URI from
-            the given request for which C{test(parent)} evaluates to a true
-            value, or C{None} if no parent matches.
-        """
-        parent = self
-        parent_uri = request.uri
-
-        while True:
-            parent_uri = parentForURL(parent_uri)
-            if not parent_uri: break
-
-            parent = yield request.locateResource(parent_uri)
-
-            if test(parent):
-                returnValue(parent)
-
-class AutoProvisioningFileMixIn (LinkFollowerMixIn, AutoProvisioningResourceMixIn):
-    def provision(self):
-        self.provisionFile()
-        return super(AutoProvisioningFileMixIn, self).provision()
-
-
-    def provisionFile(self):
-        if hasattr(self, "_provisioned_file"):
-            return False
-        else:
-            self._provisioned_file = True
-
-        # If the file already exists we can just exit here - there is no need to go further
-        if self.fp.exists():
-            return False
-
-        # At this point the original FilePath did not indicate an existing file, but we should
-        # recheck it to see if some other request sneaked in and already created/provisioned it
-
-        fp = self.fp
-
-        fp.restat(False)
-        if fp.exists():
-            return False
-
-        log.msg("Provisioning file: %s" % (self,))
-
-        if hasattr(self, "parent"):
-            parent = self.parent
-            if not parent.exists() and isinstance(parent, AutoProvisioningFileMixIn):
-                parent.provision()
-
-            assert parent.exists(), "Parent %s of %s does not exist" % (parent, self)
-            assert parent.isCollection(), "Parent %s of %s is not a collection" % (parent, self)
-
-        if self.isCollection():
-            try:
-                fp.makedirs()
-            except OSError:
-                # It's possible someone else created the directory in the meantime...
-                # Check our status again, and re-raise if we're not a collection.
-                if not self.isCollection():
-                    raise
-            fp.changed()
-        else:
-            fp.open("w").close()
-            fp.changed()
-
-        return True
-
-    def _initTypeAndEncoding(self):
-
-        # Handle cases not covered by getTypeAndEncoding()
-        if self.isCollection():
-            self._type = "httpd/unix-directory"
-        else:
-            super(AutoProvisioningFileMixIn, self)._initTypeAndEncoding()
-
-
-class CalendarHomeProvisioningFile(AutoProvisioningFileMixIn, 
-                                   DirectoryCalendarHomeProvisioningResource,
-                                   DAVFile):
-    """
-    Resource which provisions calendar home collections as needed.
-    """
-
-    def __init__(self, path, directory, url, store):
-        """
-        Initialize this L{CalendarHomeProvisioningFile}.
-
-        @param path: the path to the filesystem directory which will back the
-            resource.
-
-        @type path: L{FilePath}
-
-        @param directory: an L{IDirectoryService} to provision calendars from.
-
-        @param url: the canonical URL for this L{CalendarHomeProvisioningFile} 
-            resource.
-        """
-        DAVFile.__init__(self, path)
-        DirectoryCalendarHomeProvisioningResource.__init__(self, directory, url)
-        self._newStore = store
-
-
-    def provisionChild(self, name):
-        if name == uidsResourceName:
-            return CalendarHomeUIDProvisioningFile(self.fp.child(name).path, self)
-
-        return CalendarHomeTypeProvisioningFile(self.fp.child(name).path, self, name)
-
-
-    def createSimilarFile(self, path):
-        raise HTTPError(responsecode.NOT_FOUND)
-
-
-
-class CalendarHomeTypeProvisioningFile (AutoProvisioningFileMixIn, DirectoryCalendarHomeTypeProvisioningResource, DAVFile):
-    def __init__(self, path, parent, recordType):
-        """
-        @param path: the path to the file which will back the resource.
-        @param parent: the parent of this resource
-        @param recordType: the directory record type to provision.
-        """
-        DAVFile.__init__(self, path)
-        DirectoryCalendarHomeTypeProvisioningResource.__init__(self, parent, recordType)
-
-
-
-def _transactionFromRequest(request, newStore):
-    """
-    Return the associated transaction from the given HTTP request, creating a
-    new one from the given data store if none has yet been associated.
-
-    Also, if the request was not previously associated with a transaction, add
-    a failsafe transaction-abort response filter to abort any transaction which
-    has not been committed or aborted by the resource which responds to the
-    request.
-
-    @param request: The request to inspect.
-    @type request: L{IRequest}
-
-    @param newStore: The store to create a transaction from.
-    @type newStore: L{IDataStore}
-
-    @return: a transaction that should be used to read and write data
-        associated with the request.
-    @rtype: L{ITransaction} (and possibly L{ICalendarTransaction} and
-        L{IAddressBookTransaction} as well.
-    """
-    TRANSACTION_KEY = '_newStoreTransaction'
-    transaction = getattr(request, TRANSACTION_KEY, None)
-    if transaction is None:
-        transaction = newStore.newTransaction(repr(request))
-        def abortIfUncommitted(request, response):
-            try:
-                transaction.abort()
-            except AlreadyFinishedError:
-                pass
-            return response
-        abortIfUncommitted.handleErrors = True
-        request.addResponseFilter(abortIfUncommitted)
-        setattr(request, TRANSACTION_KEY, transaction)
-    return transaction
-
-
-
-class CalendarHomeUIDProvisioningFile (AutoProvisioningFileMixIn, DirectoryCalendarHomeUIDProvisioningResource, DAVFile):
-    def __init__(self, path, parent, homeResourceClass=None):
-        """
-        @param path: the path to the file which will back the resource.
-        """
-        DAVFile.__init__(self, path)
-        DirectoryCalendarHomeUIDProvisioningResource.__init__(self, parent)
-        if homeResourceClass is None:
-            self.homeResourceClass = CalendarHomeFile
-        else:
-            self.homeResourceClass = homeResourceClass
-
-
-    def locateChild(self, request, segments):
-
-        name = segments[0]
-        if name == "":
-            return (self, ())
-
-        record = self.directory.recordWithUID(name)
-        if record:
-            return (self.homeResourceForRecord(record, request), segments[1:])
-        else:
-            return (None, ())
-
-    def homeResourceForRecord(self, record, request):
-        self.provision()
-        transaction = _transactionFromRequest(request, self.parent._newStore)
-
-        name = record.uid
-
-        if record is None:
-            log.msg("No directory record with GUID %r" % (name,))
-            return None
-
-        if not record.enabledForCalendaring:
-            log.msg("Directory record %r is not enabled for calendaring" % (record,))
-            return None
-
-        assert len(name) > 4, "Directory record has an invalid GUID: %r" % (name,)
-        
-        if record.locallyHosted():
-            childPath = self.fp.child(name[0:2]).child(name[2:4]).child(name)
-            child = self.homeResourceClass(childPath.path, self, record, transaction)
-    
-            if not child.exists():
-                self.provision()
-    
-                if not childPath.parent().isdir():
-                    childPath.parent().makedirs()
-    
-                for oldPath in (
-                    # Pre 2.0: All in one directory
-                    self.fp.child(name),
-                    # Pre 1.2: In types hierarchy instead of the GUID hierarchy
-                    self.parent.getChild(record.recordType).fp.child(record.shortNames[0]),
-                ):
-                    if oldPath.exists():
-                        # The child exists at an old location.  Move to new location.
-                        log.msg("Moving calendar home from old location %r to new location %r." % (oldPath, childPath))
-                        try:
-                            oldPath.moveTo(childPath)
-                        except (OSError, IOError), e:
-                            log.err("Error moving calendar home %r: %s" % (oldPath, e))
-                            raise HTTPError(StatusResponse(
-                                responsecode.INTERNAL_SERVER_ERROR,
-                                "Unable to move calendar home."
-                            ))
-                        child.fp.changed()
-                        break
-
-                assert child.exists()
-        
-        else:
-            childPath = self.fp.child(name[0:2]).child(name[2:4]).child(name)
-            child = CalendarHomeReverseProxyFile(childPath.path, self, record)
-
-        return child
-
-    def createSimilarFile(self, path):
-        raise HTTPError(responsecode.NOT_FOUND)
-
-class CalendarHomeReverseProxyFile(ReverseProxyResource):
-    
-    def __init__(self, path, parent, record):
-        self.path = path
-        self.parent = parent
-        self.record = record
-        
-        super(CalendarHomeReverseProxyFile, self).__init__(self.record.hostedAt)
-    
-    def url(self):
-        return joinURL(self.parent.url(), self.record.uid)
-
-class CalendarHomeFile(AutoProvisioningFileMixIn, SharedHomeMixin, 
-                       DirectoryCalendarHomeResource, CalDAVFile):
-    """
-    Calendar home collection resource.
-    """
-    def liveProperties(self):
-        
-        return super(CalendarHomeFile, self).liveProperties() + (
-            (customxml.calendarserver_namespace, "push-transports"),
-            (customxml.calendarserver_namespace, "pushkey"),
-            (customxml.calendarserver_namespace, "xmpp-uri"),
-            (customxml.calendarserver_namespace, "xmpp-heartbeat-uri"),
-            (customxml.calendarserver_namespace, "xmpp-server"),
-        )
-
-    def __init__(self, path, parent, record, transaction):
-        """
-        @param path: the path to the file which will back the resource.
-        """
-
-        self.associateWithTransaction(transaction)
-
-        storeHome = transaction.calendarHomeWithUID(record.uid)
-        if storeHome is not None:
-            created = False
-        else:
-            storeHome = transaction.calendarHomeWithUID(
-                record.uid, create=True
-            )
-            created = True
-        self._newStoreCalendarHome = storeHome
-        CalDAVFile.__init__(self, path)
-        DirectoryCalendarHomeResource.__init__(self, parent, record)
-        from twistedcaldav.storebridge import _NewStorePropertiesWrapper
-        self._dead_properties = _NewStorePropertiesWrapper(
-            self._newStoreCalendarHome.properties()
-        )
-        if created:
-            # This is a bit of a hack.  Really we ought to be always generating
-            # this URL live from a back-end method that tells us what the
-            # default calendar is.
-            inbox = self.getChild("inbox")
-            childURL = joinURL(self.url(), "calendar")
-            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
-    
-    
-    def quotaSize(self, request):
-        # FIXME: tests, workingness
-        return succeed(0)
-
-
-    def provision(self):
-        if config.Sharing.Enabled and config.Sharing.Calendars.Enabled and self.fp.exists():
-            self.provisionShares()
-        return
-
-    def provisionChild(self, name):
-        from twistedcaldav.storebridge import StoreScheduleInboxFile
-        from twistedcaldav.storebridge import DropboxCollection
-        if config.EnableDropBox:
-            DropBoxHomeFileClass = DropboxCollection
-        else:
-            DropBoxHomeFileClass = None
-
-        if config.FreeBusyURL.Enabled:
-            FreeBusyURLFileClass = FreeBusyURLFile
-        else:
-            FreeBusyURLFileClass = None
-            
-        if config.Sharing.Enabled and config.Sharing.Calendars.Enabled:
-            NotificationCollectionFileClass = NotificationCollectionFile
-        else:
-            NotificationCollectionFileClass = None
-
-
-        # For storebridge stuff we special case this
-        if name == "notification":
-            return self.createNotificationsFile(self.fp.child(name).path)
-
-        cls = {
-            "inbox"        : StoreScheduleInboxFile,
-            "outbox"       : ScheduleOutboxFile,
-            "dropbox"      : DropBoxHomeFileClass,
-            "freebusy"     : FreeBusyURLFileClass,
-            "notification" : NotificationCollectionFileClass,
-        }.get(name, None)
-
-        if cls is not None:
-            child = cls(self.fp.child(name).path, self)
-            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)
-        return similar
-
-    def createSimilarFile(self, path):
-
-        if self.comparePath(path):
-            return self
-        else:
-            if not isinstance(path, FilePath):
-                path = FilePath(path)
-            newCalendar = self._newStoreCalendarHome.calendarWithName(
-                path.basename()
-            )
-            if newCalendar is None:
-                # Local imports.due to circular dependency between modules.
-                from twistedcaldav.storebridge import (
-                     ProtoCalendarCollectionFile)
-                similar = ProtoCalendarCollectionFile(
-                    self._newStoreCalendarHome,
-                    path, principalCollections=self.principalCollections()
-                )
-            else:
-                from twistedcaldav.storebridge import CalendarCollectionFile
-                similar = CalendarCollectionFile(
-                    newCalendar, self._newStoreCalendarHome,
-                    path, principalCollections=self.principalCollections()
-                )
-            self.propagateTransaction(similar)
-            return similar
-
-    def getChild(self, name):
-        # This avoids finding case variants of put children on case-insensitive filesystems.
-        if name not in self.putChildren and name.lower() in (x.lower() for x in self.putChildren):
-            return None
-
-        return super(CalendarHomeFile, self).getChild(name)
-
-
-    def readProperty(self, property, request):
-        if type(property) is tuple:
-            qname = property
-        else:
-            qname = property.qname()
-
-        if qname == (customxml.calendarserver_namespace, "push-transports"):
-            pubSubConfiguration = getPubSubConfiguration(config)
-            if (pubSubConfiguration['enabled'] and
-                getattr(self, "clientNotifier", None) is not None):
-                    id = self.clientNotifier.getID()
-                    nodeName = getPubSubPath(id, pubSubConfiguration)
-                    children = []
-                    if pubSubConfiguration['aps-bundle-id']:
-                        children.append(
-                            customxml.PubSubTransportProperty(
-                                customxml.PubSubSubscriptionProperty(
-                                    davxml.HRef(
-                                        pubSubConfiguration['subscription-url']
-                                    ),
-                                ),
-                                customxml.PubSubAPSBundleIDProperty(
-                                    pubSubConfiguration['aps-bundle-id']
-                                ),
-                                type="APSD",
-                            )
-                        )
-                    if pubSubConfiguration['xmpp-server']:
-                        children.append(
-                            customxml.PubSubTransportProperty(
-                                customxml.PubSubXMPPServerProperty(
-                                    pubSubConfiguration['xmpp-server']
-                                ),
-                                customxml.PubSubXMPPURIProperty(
-                                    getPubSubXMPPURI(id, pubSubConfiguration)
-                                ),
-                                type="XMPP",
-                            )
-                        )
-
-                    propVal = customxml.PubSubPushTransportsProperty(*children)
-                    nodeCacher = getNodeCacher()
-                    d = nodeCacher.createNode(self.clientNotifier, nodeName)
-                    # In either case we're going to return the value
-                    d.addBoth(lambda ignored: propVal)
-                    return d
-
-
-            else:
-                return succeed(customxml.PubSubPushTransportsProperty())
-
-        if qname == (customxml.calendarserver_namespace, "pushkey"):
-            pubSubConfiguration = getPubSubConfiguration(config)
-            if pubSubConfiguration['enabled']:
-                if getattr(self, "clientNotifier", None) is not None:
-                    id = self.clientNotifier.getID()
-                    nodeName = getPubSubPath(id, pubSubConfiguration)
-                    propVal = customxml.PubSubXMPPPushKeyProperty(nodeName)
-                    nodeCacher = getNodeCacher()
-                    d = nodeCacher.createNode(self.clientNotifier, nodeName)
-                    # In either case we're going to return the xmpp-uri value
-                    d.addBoth(lambda ignored: propVal)
-                    return d
-            else:
-                return succeed(customxml.PubSubXMPPPushKeyProperty())
-
-
-        if qname == (customxml.calendarserver_namespace, "xmpp-uri"):
-            pubSubConfiguration = getPubSubConfiguration(config)
-            if pubSubConfiguration['enabled']:
-                if getattr(self, "clientNotifier", None) is not None:
-                    id = self.clientNotifier.getID()
-                    nodeName = getPubSubPath(id, pubSubConfiguration)
-                    propVal = customxml.PubSubXMPPURIProperty(
-                        getPubSubXMPPURI(id, pubSubConfiguration))
-                    nodeCacher = getNodeCacher()
-                    d = nodeCacher.createNode(self.clientNotifier, nodeName)
-                    # In either case we're going to return the xmpp-uri value
-                    d.addBoth(lambda ignored: propVal)
-                    return d
-            else:
-                return succeed(customxml.PubSubXMPPURIProperty())
-
-        elif qname == (customxml.calendarserver_namespace, "xmpp-heartbeat-uri"):
-            pubSubConfiguration = getPubSubConfiguration(config)
-            if pubSubConfiguration['enabled']:
-                return succeed(
-                    customxml.PubSubHeartbeatProperty(
-                        customxml.PubSubHeartbeatURIProperty(
-                            getPubSubHeartbeatURI(pubSubConfiguration)
-                        ),
-                        customxml.PubSubHeartbeatMinutesProperty(
-                            str(pubSubConfiguration['heartrate'])
-                        )
-                    )
-                )
-            else:
-                return succeed(customxml.PubSubHeartbeatURIProperty())
-
-        elif qname == (customxml.calendarserver_namespace, "xmpp-server"):
-            pubSubConfiguration = getPubSubConfiguration(config)
-            if pubSubConfiguration['enabled']:
-                return succeed(customxml.PubSubXMPPServerProperty(
-                    pubSubConfiguration['xmpp-server']))
-            else:
-                return succeed(customxml.PubSubXMPPServerProperty())
-
-        return super(CalendarHomeFile, self).readProperty(property, request)
-
-
-class ScheduleFile (ReadOnlyResourceMixIn, AutoProvisioningFileMixIn, CalDAVFile):
-    def __init__(self, path, parent):
-        super(ScheduleFile, self).__init__(path, principalCollections=parent.principalCollections())
-
-    def isCollection(self):
-        return True
-
-    def createSimilarFile(self, path):
-        if self.comparePath(path):
-            return self
-        else:
-            return CalDAVFile(path, principalCollections=self.principalCollections())
-
-    def index(self):
-        """
-        Obtains the index for an schedule collection resource.
-        @return: the index object for this resource.
-        @raise AssertionError: if this resource is not a calendar collection
-            resource.
-        """
-        return IndexSchedule(self)
-
-class ScheduleInboxFile (ScheduleInboxResource, ScheduleFile):
-    """
-    Calendar scheduling inbox collection resource.
-    """
-    def __init__(self, path, parent):
-        ScheduleFile.__init__(self, path, parent)
-        ScheduleInboxResource.__init__(self, parent)
-
-    def __repr__(self):
-        return "<%s (calendar inbox collection): %s>" % (self.__class__.__name__, self.fp.path)
-
-
-    ##
-    # ACL
-    ##
-
-    def supportedPrivileges(self, request):
-        return succeed(deliverSchedulePrivilegeSet)
-
-class ScheduleOutboxFile (ScheduleOutboxResource, ScheduleFile):
-    """
-    Calendar scheduling outbox collection resource.
-    """
-    def __init__(self, path, parent):
-        ScheduleFile.__init__(self, NotFilePath(isdir=True), parent)
-        ScheduleOutboxResource.__init__(self, parent)
-
-    def deadProperties(self):
-        if not hasattr(self, "_dead_properties"):
-            self._dead_properties = NonePropertyStore(self)
-        return self._dead_properties
-
-    def etag(self):
-        return None
-
-    def provision(self):
-        """
-        Schedule outboxes do not need to be provisioned; they shouldn't store
-        anything.
-        """
-
-    def __repr__(self):
-        return "<%s (calendar outbox collection): %s>" % (self.__class__.__name__, self.fp.path)
-
-
-    ##
-    # ACL
-    ##
-
-    def supportedPrivileges(self, request):
-        return succeed(sendSchedulePrivilegeSet)
-
-    def report_urn_ietf_params_xml_ns_caldav_calendar_query(self, request, calendar_query):
-        return succeed(MultiStatusResponse(()))
-        
-    def report_urn_ietf_params_xml_ns_caldav_calendar_multiget(self, request, multiget):
-        responses = [davxml.StatusResponse(href, davxml.Status.fromResponseCode(responsecode.NOT_FOUND)) for href in multiget.resources]
-        return succeed(MultiStatusResponse((responses)))
-
-class IScheduleInboxFile (ReadOnlyResourceMixIn, IScheduleInboxResource, CalDAVFile):
-    """
-    Server-to-server scheduling inbox resource.
-    """
-    def __init__(self, path, parent):
-        CalDAVFile.__init__(self, path, principalCollections=parent.principalCollections())
-        IScheduleInboxResource.__init__(self, parent)
-
-    def __repr__(self):
-        return "<%s (server-to-server inbox resource): %s>" % (self.__class__.__name__, self.fp.path)
-
-    def isCollection(self):
-        return False
-
-    def createSimilarFile(self, path):
-        if self.comparePath(path):
-            return self
-        else:
-            return responsecode.NOT_FOUND
-
-    def deadProperties(self):
-        if not hasattr(self, "_dead_properties"):
-            self._dead_properties = NonePropertyStore(self)
-        return self._dead_properties
-
-    def etag(self):
-        return None
-
-    def checkPreconditions(self, request):
-        return None
-
-    ##
-    # ACL
-    ##
-
-    def supportedPrivileges(self, request):
-        return succeed(deliverSchedulePrivilegeSet)
-
-
-
-class FreeBusyURLFile (ReadOnlyResourceMixIn, AutoProvisioningFileMixIn, FreeBusyURLResource, CalDAVFile):
-    """
-    Free-busy URL resource.
-    """
-    def __init__(self, path, parent):
-        CalDAVFile.__init__(self, path, principalCollections=parent.principalCollections())
-        FreeBusyURLResource.__init__(self, parent)
-
-    def __repr__(self):
-        return "<%s (free-busy URL resource): %s>" % (self.__class__.__name__, self.fp.path)
-
-    def isCollection(self):
-        return False
-
-    def createSimilarFile(self, path):
-        if self.comparePath(path):
-            return self
-        else:
-            return responsecode.NOT_FOUND
-
-    ##
-    # ACL
-    ##
-
-    def supportedPrivileges(self, request):
-        return succeed(deliverSchedulePrivilegeSet)
-
-class DropBoxHomeFile (AutoProvisioningFileMixIn, DropBoxHomeResource, CalDAVFile):
-    def __init__(self, path, parent):
-        DropBoxHomeResource.__init__(self)
-        CalDAVFile.__init__(self, path, principalCollections=parent.principalCollections())
-        self.parent = parent
-
-    def createSimilarFile(self, path):
-        if self.comparePath(path):
-            return self
-        else:
-            return DropBoxCollectionFile(path, self)
-
-    def __repr__(self):
-        return "<%s (dropbox home collection): %s>" % (self.__class__.__name__, self.fp.path)
-
-class DropBoxCollectionFile (DropBoxCollectionResource, CalDAVFile):
-    def __init__(self, path, parent):
-        DropBoxCollectionResource.__init__(self)
-        CalDAVFile.__init__(self, path, principalCollections=parent.principalCollections())
-
-    def createSimilarFile(self, path):
-        if self.comparePath(path):
-            return self
-        else:
-            return DropBoxChildFile(path, self)
-
-    def __repr__(self):
-        return "<%s (dropbox collection): %s>" % (self.__class__.__name__, self.fp.path)
-
-class DropBoxChildFile (CalDAVFile):
-    def __init__(self, path, parent):
-        CalDAVFile.__init__(self, path, principalCollections=parent.principalCollections())
-
-        assert self.fp.isfile() or not self.fp.exists()
-
-    def createSimilarFile(self, path):
-        if self.comparePath(path):
-            return self
-        else:
-            return responsecode.NOT_FOUND
-
-class TimezoneServiceFile (ReadOnlyResourceMixIn, TimezoneServiceResource, CalDAVFile):
-    def __init__(self, path, parent):
-        CalDAVFile.__init__(self, path, principalCollections=parent.principalCollections())
-        TimezoneServiceResource.__init__(self, parent)
-
-        assert self.fp.isfile() or not self.fp.exists()
-
-    def createSimilarFile(self, path):
-        if self.comparePath(path):
-            return self
-        else:
-            return responsecode.NOT_FOUND
-
-    def deadProperties(self):
-        if not hasattr(self, "_dead_properties"):
-            self._dead_properties = NonePropertyStore(self)
-        return self._dead_properties
-
-    def etag(self):
-        return None
-
-    def checkPreconditions(self, request):
-        return None
-
-    def checkPrivileges(self, request, privileges, recurse=False, principal=None, inherited_aces=None):
-        return succeed(None)
-
-class NotificationCollectionFile(ReadOnlyResourceMixIn, NotificationCollectionResource, CalDAVFile):
-    """
-    Notification collection resource.
-    """
-    def __init__(self, path, parent):
-        NotificationCollectionResource.__init__(self)
-        CalDAVFile.__init__(self, path, principalCollections=parent.principalCollections())
-        self.parent = parent
-
-    def createSimilarFile(self, path):
-        if self.comparePath(path):
-            return self
-        else:
-            return NotificationFile(path, self)
-
-    def __repr__(self):
-        return "<%s (notification collection): %s>" % (self.__class__.__name__, self.fp.path)
-
-    def _writeNotification(self, request, uid, rname, xmltype, xmldata):
-        
-        # TODO: use the generic StoreObject api so that quota, sync-token etc all get changed properly
-        child = self.createSimilarFile(self.fp.child(rname).path)
-        def _defer(_):
-            child.writeDeadProperty(davxml.GETContentType.fromString(generateContentType(MimeType("text", "xml", params={"charset":"utf-8"}))))
-            child.writeDeadProperty(customxml.NotificationType(xmltype))
-            return True
-
-        url = request.urlForResource(self)
-        url = joinURL(url, rname)
-        request._rememberResource(child, url)
-        d = put_common.storeResource(request, data=xmldata, destination=child, destination_uri=url)
-        d.addCallback(_defer)
-        return d
-
-
-    def _deleteNotification(self, request, rname):
-        child = self.createSimilarFile(self.fp.child(rname).path)
-        url = request.urlForResource(self)
-        url = joinURL(url, rname)
-        request._rememberResource(child, url)
-        return delete_common.deleteResource(request, child, url)
-
-class NotificationFile(NotificationResource, CalDAVFile):
-
-    def __init__(self, path, parent):
-        NotificationResource.__init__(self, parent)
-        CalDAVFile.__init__(self, path, principalCollections=parent.principalCollections())
-
-        assert self.fp.isfile() or not self.fp.exists()
-
-    def createSimilarFile(self, path):
-        if self.comparePath(path):
-            return self
-        else:
-            return responsecode.NOT_FOUND
-
-    def __repr__(self):
-        return "<%s (notification file): %s>" % (self.__class__.__name__, self.fp.path)
-        
-    def resourceName(self):
-        return self.fp.basename()
-
-class AddressBookHomeProvisioningFile (AutoProvisioningFileMixIn, DirectoryAddressBookHomeProvisioningResource, DAVFile):
-    """
-    Resource which provisions address book home collections as needed.
-    """
-    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.
-        @param url: the canonical URL for the resource.
-        """
-        DAVFile.__init__(self, path)
-        DirectoryAddressBookHomeProvisioningResource.__init__(self, directory, url)
-        self._newStore = store
-
-
-    def provisionChild(self, name):
-        if name == uidsResourceNameAddressBook:
-            return AddressBookHomeUIDProvisioningFile(self.fp.child(name).path, self)
-
-        return AddressBookHomeTypeProvisioningFile(self.fp.child(name).path, self, name)
-
-    def createSimilarFile(self, path):
-        raise HTTPError(responsecode.NOT_FOUND)
-
-class AddressBookHomeTypeProvisioningFile (AutoProvisioningFileMixIn, DirectoryAddressBookHomeTypeProvisioningResource, DAVFile):
-    def __init__(self, path, parent, recordType):
-        """
-        @param path: the path to the file which will back the resource.
-        @param parent: the parent of this resource
-        @param recordType: the directory record type to provision.
-        """
-        DAVFile.__init__(self, path)
-        DirectoryAddressBookHomeTypeProvisioningResource.__init__(self, parent, recordType)
-
-class AddressBookHomeUIDProvisioningFile (AutoProvisioningFileMixIn, DirectoryAddressBookHomeUIDProvisioningResource, DAVFile):
-    def __init__(self, path, parent, homeResourceClass=None):
-        """
-        @param path: the path to the file which will back the resource.
-        """
-        DAVFile.__init__(self, path)
-        DirectoryAddressBookHomeUIDProvisioningResource.__init__(self, parent)
-        if homeResourceClass is None:
-            self.homeResourceClass = AddressBookHomeFile
-        else:
-            self.homeResourceClass = homeResourceClass
-
-    def locateChild(self, request, segments):
-
-        name = segments[0]
-        if name == "":
-            return (self, ())
-
-        record = self.directory.recordWithUID(name)
-        if record:
-            return (self.homeResourceForRecord(record, request), segments[1:])
-        else:
-            return (None, ())
-
-    def homeResourceForRecord(self, record, request):
-        self.provision()
-        transaction = _transactionFromRequest(request, self.parent._newStore)
-
-        name = record.uid
-
-        if record is None:
-            log.msg("No directory record with GUID %r" % (name,))
-            return None
-
-        if not record.enabledForAddressBooks:
-            log.msg("Directory record %r is not enabled for address books" % (record,))
-            return None
-
-        assert len(name) > 4
-        
-        childPath = self.fp.child(name[0:2]).child(name[2:4]).child(name)
-        child = self.homeResourceClass(childPath.path, self, record, transaction)
-
-        if not child.exists():
-            self.provision()
-
-            if not childPath.parent().isdir():
-                childPath.parent().makedirs()
-
-            for oldPath in (
-                # Pre 2.0: All in one directory
-                self.fp.child(name),
-                # Pre 1.2: In types hierarchy instead of the GUID hierarchy
-                self.parent.getChild(record.recordType).fp.child(record.shortNames[0]),
-            ):
-                if oldPath.exists():
-                    # The child exists at an old location.  Move to new location.
-                    log.msg("Moving address book home from old location %r to new location %r." % (oldPath, childPath))
-                    try:
-                        oldPath.moveTo(childPath)
-                    except (OSError, IOError), e:
-                        log.err("Error moving address book home %r: %s" % (oldPath, e))
-                        raise HTTPError(StatusResponse(
-                            responsecode.INTERNAL_SERVER_ERROR,
-                            "Unable to move address book home."
-                        ))
-                    child.fp.restat(False)
-                    break
-            else:
-                #
-                # NOTE: provisionDefaultAddressBooks() returns a deferred, which we are ignoring.
-                # The result being that the default calendars will be present at some point
-                # in the future, not necessarily right now, and we don't have a way to wait
-                # on that to finish.
-                #
-                child.provisionDefaultAddressBooks()
-
-                #
-                # Try to work around the above a little by telling the client that something
-                # when wrong temporarily if the child isn't provisioned right away.
-                #
-                if not child.exists():
-                    raise HTTPError(StatusResponse(
-                        responsecode.SERVICE_UNAVAILABLE,
-                        "Provisioning address book home."
-                    ))
-
-            assert child.exists()
-
-        return child
-
-    def createSimilarFile(self, path):
-        raise HTTPError(responsecode.NOT_FOUND)
-
-class AddressBookHomeFile (AutoProvisioningFileMixIn, SharedHomeMixin, DirectoryAddressBookHomeResource, CalDAVFile):
-    """
-    Address book home collection resource.
-    """
-    
-    def liveProperties(self):
-        return super(AddressBookHomeFile, self).liveProperties() + (
-            (customxml.calendarserver_namespace, "push-transports"),
-            (customxml.calendarserver_namespace, "pushkey"),
-            (customxml.calendarserver_namespace, "xmpp-uri"),
-            (customxml.calendarserver_namespace, "xmpp-heartbeat-uri"),
-            (customxml.calendarserver_namespace, "xmpp-server"),
-        )
-
-    def __init__(self, path, parent, record, transaction):
-        """
-        @param path: the path to the file which will back the resource.
-        """
-
-        self.associateWithTransaction(transaction)
-
-        # TODO: when addressbook home gets a resourceID( ) method, remove
-        # the "id=record.uid" keyword from this call:
-        # self.clientNotifier = ClientNotifier(self, id=record.uid)
-        self._newStoreAddressBookHome = (
-            transaction.addressbookHomeWithUID(record.uid, create=True)
-        )
-        CalDAVFile.__init__(self, path)
-        DirectoryAddressBookHomeResource.__init__(self, parent, record)
-
-        from twistedcaldav.storebridge import _NewStorePropertiesWrapper
-        self._dead_properties = _NewStorePropertiesWrapper(
-            self._newStoreAddressBookHome.properties()
-        )
-
-
-    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
-    
-    
-    def quotaSize(self, request):
-        # FIXME: tests, workingness
-        return succeed(0)
-
-
-    def provision(self):
-        if config.Sharing.Enabled and config.Sharing.AddressBooks.Enabled:
-            self.provisionShares()
-        self.provisionLinks()
-
-    def provisionLinks(self):
-        
-        if not hasattr(self, "_provisionedLinks"):
-            if config.GlobalAddressBook.Enabled:
-                self.putChild(
-                    config.GlobalAddressBook.Name,
-                    LinkResource(self, "/addressbooks/public/global/addressbook/"),
-                )
-            self._provisionedLinks = True
-
-    def provisionChild(self, name):
- 
-        if config.Sharing.Enabled and config.Sharing.AddressBooks.Enabled and not config.Sharing.Calendars.Enabled:
-            NotificationCollectionFileClass = NotificationCollectionFile
-        else:
-            NotificationCollectionFileClass = None
-
-        cls = {
-            "notification" : NotificationCollectionFileClass,
-        }.get(name, None)
-
-        if cls is not None:
-            child = cls(self.fp.child(name).path, self)
-            return child
-        return self.createSimilarFile(self.fp.child(name).path)
-
-    def createSimilarFile(self, path):
-        if self.comparePath(path):
-            return self
-        else:
-            if not isinstance(path, FilePath):
-                path = FilePath(path)
-
-            # Check for public/global path
-            from twistedcaldav.storebridge import (
-                AddressBookCollectionFile,
-                ProtoAddressBookCollectionFile,
-                GlobalAddressBookCollectionFile,
-                ProtoGlobalAddressBookCollectionFile,
-            )
-            mainCls = AddressBookCollectionFile
-            protoCls = ProtoAddressBookCollectionFile
-            if isinstance(self.record, InternalDirectoryRecord):
-                if "global" in self.record.shortNames:
-                    mainCls = GlobalAddressBookCollectionFile
-                    protoCls = ProtoGlobalAddressBookCollectionFile
-
-            newAddressBook = self._newStoreAddressBookHome.addressbookWithName(
-                path.basename()
-            )
-            if newAddressBook is None:
-                # Local imports.due to circular dependency between modules.
-                similar = protoCls(
-                    self._newStoreAddressBookHome,
-                    path, principalCollections=self.principalCollections()
-                )
-            else:
-                similar = mainCls(
-                    newAddressBook, self._newStoreAddressBookHome,
-                    path, principalCollections=self.principalCollections()
-                )
-            self.propagateTransaction(similar)
-            return similar
-
-    def getChild(self, name):
-        # This avoids finding case variants of put children on case-insensitive filesystems.
-        if name not in self.putChildren and name.lower() in (x.lower() for x in self.putChildren):
-            return None
-
-        return super(AddressBookHomeFile, self).getChild(name)
-
-
-    def readProperty(self, property, request):
-        if type(property) is tuple:
-            qname = property
-        else:
-            qname = property.qname()
-
-        if qname == (customxml.calendarserver_namespace, "push-transports"):
-            pubSubConfiguration = getPubSubConfiguration(config)
-            if (pubSubConfiguration['enabled'] and
-                getattr(self, "clientNotifier", None) is not None):
-                    id = self.clientNotifier.getID()
-                    nodeName = getPubSubPath(id, pubSubConfiguration)
-                    children = []
-                    if pubSubConfiguration['aps-bundle-id']:
-                        children.append(
-                            customxml.PubSubTransportProperty(
-                                customxml.PubSubSubscriptionProperty(
-                                    davxml.HRef(
-                                        pubSubConfiguration['subscription-url']
-                                    ),
-                                ),
-                                customxml.PubSubAPSBundleIDProperty(
-                                    pubSubConfiguration['aps-bundle-id']
-                                ),
-                                type="APSD",
-                            )
-                        )
-                    if pubSubConfiguration['xmpp-server']:
-                        children.append(
-                            customxml.PubSubTransportProperty(
-                                customxml.PubSubXMPPServerProperty(
-                                    pubSubConfiguration['xmpp-server']
-                                ),
-                                customxml.PubSubXMPPURIProperty(
-                                    getPubSubXMPPURI(id, pubSubConfiguration)
-                                ),
-                                type="XMPP",
-                            )
-                        )
-
-                    propVal = customxml.PubSubPushTransportsProperty(*children)
-                    nodeCacher = getNodeCacher()
-                    d = nodeCacher.createNode(self.clientNotifier, nodeName)
-                    # In either case we're going to return the value
-                    d.addBoth(lambda ignored: propVal)
-                    return d
-
-
-            else:
-                return succeed(customxml.PubSubPushTransportsProperty())
-
-        if qname == (customxml.calendarserver_namespace, "pushkey"):
-            pubSubConfiguration = getPubSubConfiguration(config)
-            if pubSubConfiguration['enabled']:
-                if getattr(self, "clientNotifier", None) is not None:
-                    id = self.clientNotifier.getID()
-                    nodeName = getPubSubPath(id, pubSubConfiguration)
-                    propVal = customxml.PubSubXMPPPushKeyProperty(nodeName)
-                    nodeCacher = getNodeCacher()
-                    d = nodeCacher.createNode(self.clientNotifier, nodeName)
-                    # In either case we're going to return the xmpp-uri value
-                    d.addBoth(lambda ignored: propVal)
-                    return d
-            else:
-                return succeed(customxml.PubSubXMPPPushKeyProperty())
-
-
-        if qname == (customxml.calendarserver_namespace, "xmpp-uri"):
-            pubSubConfiguration = getPubSubConfiguration(config)
-            if pubSubConfiguration['enabled']:
-                if getattr(self, "clientNotifier", None) is not None:
-                    id = self.clientNotifier.getID()
-                    nodeName = getPubSubPath(id, pubSubConfiguration)
-                    propVal = customxml.PubSubXMPPURIProperty(
-                        getPubSubXMPPURI(id, pubSubConfiguration))
-                    nodeCacher = getNodeCacher()
-                    d = nodeCacher.createNode(self.clientNotifier, nodeName)
-                    # In either case we're going to return the xmpp-uri value
-                    d.addBoth(lambda ignored: propVal)
-                    return d
-            else:
-                return succeed(customxml.PubSubXMPPURIProperty())
-
-        elif qname == (customxml.calendarserver_namespace, "xmpp-heartbeat-uri"):
-            pubSubConfiguration = getPubSubConfiguration(config)
-            if pubSubConfiguration['enabled']:
-                return succeed(
-                    customxml.PubSubHeartbeatProperty(
-                        customxml.PubSubHeartbeatURIProperty(
-                            getPubSubHeartbeatURI(pubSubConfiguration)
-                        ),
-                        customxml.PubSubHeartbeatMinutesProperty(
-                            str(pubSubConfiguration['heartrate'])
-                        )
-                    )
-                )
-            else:
-                return succeed(customxml.PubSubHeartbeatURIProperty())
-
-        elif qname == (customxml.calendarserver_namespace, "xmpp-server"):
-            pubSubConfiguration = getPubSubConfiguration(config)
-            if pubSubConfiguration['enabled']:
-                return succeed(customxml.PubSubXMPPServerProperty(
-                    pubSubConfiguration['xmpp-server']))
-            else:
-                return succeed(customxml.PubSubXMPPServerProperty())
-
-        return super(AddressBookHomeFile, self).readProperty(property, request)
-
-
-class DirectoryBackedAddressBookFile (ReadOnlyResourceMixIn, DirectoryBackedAddressBookResource, CalDAVFile):
-    """
-    Directory-backed address book, supporting directory vcard search.
-    """
-    def __init__(self, path, principalCollections):
-        CalDAVFile.__init__(self, path, principalCollections=principalCollections)
-        DirectoryBackedAddressBookResource.__init__(self)
-
-        # create with permissions, similar to CardDAVOptions in tap.py
-        # FIXME:  /Directory does not need to be in file system unless debug-only caching options are used
-        try:
-            os.mkdir(path)
-            os.chmod(path, 0750)
-            if config.UserName and config.GroupName:
-                import pwd
-                import grp
-                uid = pwd.getpwnam(config.UserName)[2]
-                gid = grp.getgrnam(config.GroupName)[2]
-                os.chown(path, uid, gid)
- 
-            log.msg("Created %s" % (path,))
-            
-        except (OSError,), e:
-            # this is caused by multiprocessor race and is harmless
-            if e.errno != errno.EEXIST:
-                raise
-
-    
-    def getChild(self, name):
-        
-        if name is "":
-            return self
-        else:
-            from twistedcaldav.simpleresource import SimpleCalDAVResource
-            return SimpleCalDAVResource(principalCollections=self.principalCollections())
-       
-    def createSimilarFile(self, path):
-        if self.comparePath(path):
-            return self
-        else:
-            from twistedcaldav.simpleresource import SimpleCalDAVResource
-            return SimpleCalDAVResource(principalCollections=self.principalCollections())
- 
-class GlobalAddressBookFile (ReadOnlyResourceMixIn, GlobalAddressBookResource, CalDAVFile):
-    """
-    Directory-backed address book, supporting directory vcard search.
-    """
-    def __init__(self, path, principalCollections):
-        CalDAVFile.__init__(self, path, principalCollections=principalCollections)
-        # self.clientNotifier = ClientNotifier(self)
-
-    def createSimilarFile(self, path):
-        if self.comparePath(path):
-            return self
-        else:
-            similar = CalDAVFile(path, principalCollections=self.principalCollections())
-            return similar
-
-##
-# Utilities
-##
-
-def locateExistingChild(resource, request, segments):
-    """
-    This C{locateChild()} implementation fails to find children if C{getChild()}
-    doesn't return one.
-    """
-    # If getChild() finds a child resource, return it
-    child = resource.getChild(segments[0])
-    if child is not None:
-        return (child, segments[1:])
-
-    # Otherwise, there is no child
-    return (None, ())
-
-def _schedulePrivilegeSet(deliver):
-    edited = False
-
-    top_supported_privileges = []
-
-    for supported_privilege in davPrivilegeSet.childrenOfType(davxml.SupportedPrivilege):
-        all_privilege = supported_privilege.childOfType(davxml.Privilege)
-        if isinstance(all_privilege.children[0], davxml.All):
-            all_description = supported_privilege.childOfType(davxml.Description)
-            all_supported_privileges = list(supported_privilege.childrenOfType(davxml.SupportedPrivilege))
-            all_supported_privileges.append(
-                davxml.SupportedPrivilege(
-                    davxml.Privilege(caldavxml.ScheduleDeliver() if deliver else caldavxml.ScheduleSend()),
-                    davxml.Description("schedule privileges for current principal", **{"xml:lang": "en"}),
-                ),
-            )
-            if config.Scheduling.CalDAV.OldDraftCompatibility:
-                all_supported_privileges.append(
-                    davxml.SupportedPrivilege(
-                        davxml.Privilege(caldavxml.Schedule()),
-                        davxml.Description("old-style schedule privileges for current principal", **{"xml:lang": "en"}),
-                    ),
-                )
-            top_supported_privileges.append(
-                davxml.SupportedPrivilege(all_privilege, all_description, *all_supported_privileges)
-            )
-            edited = True
-        else:
-            top_supported_privileges.append(supported_privilege)
-
-    assert edited, "Structure of davPrivilegeSet changed in a way that I don't know how to extend for schedulePrivilegeSet"
-
-    return davxml.SupportedPrivilegeSet(*top_supported_privileges)
-
-deliverSchedulePrivilegeSet = _schedulePrivilegeSet(True)
-sendSchedulePrivilegeSet = _schedulePrivilegeSet(False)
-
-def _calendarPrivilegeSet ():
-    edited = False
-
-    top_supported_privileges = []
-
-    for supported_privilege in davPrivilegeSet.childrenOfType(davxml.SupportedPrivilege):
-        all_privilege = supported_privilege.childOfType(davxml.Privilege)
-        if isinstance(all_privilege.children[0], davxml.All):
-            all_description = supported_privilege.childOfType(davxml.Description)
-            all_supported_privileges = []
-            for all_supported_privilege in supported_privilege.childrenOfType(davxml.SupportedPrivilege):
-                read_privilege = all_supported_privilege.childOfType(davxml.Privilege)
-                if isinstance(read_privilege.children[0], davxml.Read):
-                    read_description = all_supported_privilege.childOfType(davxml.Description)
-                    read_supported_privileges = list(all_supported_privilege.childrenOfType(davxml.SupportedPrivilege))
-                    read_supported_privileges.append(
-                        davxml.SupportedPrivilege(
-                            davxml.Privilege(caldavxml.ReadFreeBusy()),
-                            davxml.Description("allow free busy report query", **{"xml:lang": "en"}),
-                        )
-                    )
-                    all_supported_privileges.append(
-                        davxml.SupportedPrivilege(read_privilege, read_description, *read_supported_privileges)
-                    )
-                    edited = True
-                else:
-                    all_supported_privileges.append(all_supported_privilege)
-            top_supported_privileges.append(
-                davxml.SupportedPrivilege(all_privilege, all_description, *all_supported_privileges)
-            )
-        else:
-            top_supported_privileges.append(supported_privilege)
-
-    assert edited, "Structure of davPrivilegeSet changed in a way that I don't know how to extend for calendarPrivilegeSet"
-
-    return davxml.SupportedPrivilegeSet(*top_supported_privileges)
-
-calendarPrivilegeSet = _calendarPrivilegeSet()
-
-##
-# Attach methods
-##
-
-import twistedcaldav.method
-
-bindMethods(twistedcaldav.method, CalDAVFile)
-
-# Some resources do not support some methods
-setattr(CalendarHomeFile, "http_ACL", None)
-setattr(AddressBookHomeFile, "http_ACL", None)
-
-setattr(DropBoxCollectionFile, "http_MKCALENDAR", None)
-setattr(DropBoxChildFile, "http_MKCOL", None)
-setattr(DropBoxChildFile, "http_MKCALENDAR", None)
-
-# FIXME: Little bit of a circular dependency here...
-twistedcaldav.method.acl.CalDAVFile      = CalDAVFile
-twistedcaldav.method.copymove.CalDAVFile = CalDAVFile
-twistedcaldav.method.delete.CalDAVFile   = CalDAVFile
-twistedcaldav.method.get.CalDAVFile      = CalDAVFile
-twistedcaldav.method.mkcol.CalDAVFile    = CalDAVFile
-twistedcaldav.method.propfind.CalDAVFile = CalDAVFile
-twistedcaldav.method.put.CalDAVFile      = CalDAVFile
-
-

Modified: CalendarServer/trunk/twistedcaldav/storebridge.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/storebridge.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/twistedcaldav/storebridge.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -26,41 +26,38 @@
 
 from twisted.internet.defer import succeed, inlineCallbacks, returnValue
 from twisted.internet.protocol import Protocol
+from twisted.python.log import err as logDefaultException
 from twisted.python.util import FancyEqMixin
 
 from twext.python import vcomponent
-from twext.python.filepath import CachingFilePath as FilePath
 from twext.python.log import Logger
 
-from twext.web2.http_headers import ETag, MimeType
+from twext.web2.dav import davxml
+from twext.web2.dav.element.base import dav_namespace
 from twext.web2.dav.http import ErrorResponse, ResponseQueue
-from twext.web2.dav.element.base import dav_namespace
-from twext.web2.responsecode import (
-    FORBIDDEN, NO_CONTENT, NOT_FOUND, CREATED, CONFLICT, PRECONDITION_FAILED,
-    BAD_REQUEST, OK, NOT_IMPLEMENTED, NOT_ALLOWED)
-from twext.web2.dav import davxml
-from twext.web2.dav.resource import TwistedGETContentMD5, TwistedACLInheritable
+from twext.web2.dav.resource import TwistedACLInheritable
 from twext.web2.dav.util import parentForURL, allDataFromStream, joinURL, \
     davXMLFromStream
 from twext.web2.http import HTTPError, StatusResponse, Response
-from twext.web2.stream import ProducerStream, readStream
+from twext.web2.http_headers import ETag, MimeType
+from twext.web2.responsecode import (
+    FORBIDDEN, NO_CONTENT, NOT_FOUND, CREATED, CONFLICT, PRECONDITION_FAILED,
+    BAD_REQUEST, OK, NOT_IMPLEMENTED, NOT_ALLOWED
+)
+from twext.web2.stream import ProducerStream, readStream, MemoryStream
 
-from twistedcaldav.static import CalDAVFile, ScheduleInboxFile, \
-    NotificationCollectionFile, NotificationFile, GlobalAddressBookFile
+from twistedcaldav.caldavxml import ScheduleTag, caldav_namespace
+from twistedcaldav.memcachelock import MemcacheLock, MemcacheLockTimeoutError
+from twistedcaldav.notifications import NotificationCollectionResource, \
+    NotificationResource
+from twistedcaldav.resource import CalDAVResource, GlobalAddressBookResource
+from twistedcaldav.schedule import ScheduleInboxResource
+from twistedcaldav.scheduling.implicit import ImplicitScheduler
 from twistedcaldav.vcard import Component as VCard
-from twistedcaldav.resource import CalDAVResource
 
-from txdav.common.icommondatastore import NoSuchObjectResourceError, \
-    InternalDataStoreError
+from txdav.common.icommondatastore import NoSuchObjectResourceError
 from txdav.propertystore.base import PropertyName
 
-from twistedcaldav.caldavxml import ScheduleTag, caldav_namespace
-from twistedcaldav.scheduling.implicit import ImplicitScheduler
-from twistedcaldav.memcachelock import MemcacheLock, MemcacheLockTimeoutError
-
-from twisted.python.log import err as logDefaultException
-from twistedcaldav.method.propfind import http_PROPFIND
-
 log = Logger()
 
 
@@ -158,6 +155,47 @@
 
 
 
+class _NewStoreFileMetaDataHelper(object):
+
+    def name(self):
+        return self._newStoreObject.name()
+
+    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 uncommitted transaction creating this calendar file)
+
+        # FIXME: direct tests
+        try:
+            md5 = self._newStoreObject.md5()
+            if md5:
+                return ETag(md5)
+            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 contentType(self):
+        return self._newStoreObject.contentType()
+
+    def contentLength(self):
+        return self._newStoreObject.size()
+
+    def lastModified(self):
+        return self._newStoreObject.modified()
+
+    def creationDate(self):
+        return self._newStoreObject.created()
+
+    def newStoreProperties(self):
+        return self._newStoreObject.properties()
+
 class _CalendarChildHelper(object):
     """
     Methods for things which are like calendars.
@@ -203,52 +241,61 @@
     @classmethod
     def transform(cls, self, calendar, home):
         """
-        Transform C{self} into a L{CalendarCollectionFile}.
+        Transform C{self} into a L{CalendarCollectionResource}.
         """
         self.__class__ = cls
         self._initializeWithCalendar(calendar, home)
 
 
-    def createSimilarFile(self, path):
+    def makeChild(self, name):
         """
-        Create a L{CalendarObjectFile} or L{ProtoCalendarObjectFile} based on a
+        Create a L{CalendarObjectResource} or L{ProtoCalendarObjectResource} based on a
         path object.
         """
-        if not isinstance(path, FilePath):
-            path = FilePath(path)
 
-        newStoreObject = self._newStoreCalendar.calendarObjectWithName(
-            path.basename()
-        )
+        newStoreObject = self._newStoreCalendar.calendarObjectWithName(name)
 
         if newStoreObject is not None:
-            similar = CalendarObjectFile(newStoreObject, path,
-                principalCollections=self._principalCollections)
+            similar = CalendarObjectResource(
+                newStoreObject,
+                principalCollections=self._principalCollections
+            )
         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 = ProtoCalendarObjectFile(self._newStoreCalendar, path,
-                principalCollections=self._principalCollections)
+            similar = ProtoCalendarObjectResource(
+                self._newStoreCalendar,
+                name,
+                principalCollections=self._principalCollections
+            )
 
         # FIXME: tests should be failing without this line.
         # Specifically, http_PUT won't be committing its transaction properly.
         self.propagateTransaction(similar)
         return similar
 
+    def listChildren(self):
+        """
+        @return: a sequence of the names of all known children of this resource.
+        """
+        children = set(self.putChildren.keys())
+        children.update(self._newStoreCalendar.listCalendarObjects())
+        return sorted(children)
 
+
     def quotaSize(self, request):
         # FIXME: tests, workingness
         return succeed(0)
 
 
 
-class StoreScheduleInboxFile(_CalendarChildHelper, ScheduleInboxFile):
+class StoreScheduleInboxResource(_CalendarChildHelper, ScheduleInboxResource):
 
     def __init__(self, *a, **kw):
-        super(StoreScheduleInboxFile, self).__init__(*a, **kw)
+        super(StoreScheduleInboxResource, self).__init__(*a, **kw)
         self.parent.propagateTransaction(self)
-        home = self.parent._newStoreCalendarHome
+        home = self.parent._newStoreHome
         storage = home.calendarWithName("inbox")
         if storage is None:
             # raise RuntimeError("backend should be handling this for us")
@@ -259,14 +306,10 @@
             storage = home.calendarWithName("inbox")
         self._initializeWithCalendar(
             storage,
-            self.parent._newStoreCalendarHome
+            self.parent._newStoreHome
         )
 
 
-    def isCollection(self):
-        return True
-
-
     def provisionFile(self):
         pass
 
@@ -295,7 +338,7 @@
             qname = property.qname()
 
         if qname == (dav_namespace, "resourcetype"):
-            return self.resourceType(request)
+            return succeed(self.resourceType())
         return super(_GetChildHelper, self).readProperty(property, request)
 
 
@@ -308,10 +351,7 @@
         return super(_GetChildHelper, self).http_GET(request)
 
 
-    http_PROPFIND = http_PROPFIND
 
-
-
 class DropboxCollection(_GetChildHelper):
     """
     A collection of all dropboxes (containers for attachments), presented as a
@@ -320,12 +360,10 @@
     """
     # FIXME: no direct tests for this class at all.
 
-    def __init__(self, path, parent, *a, **kw):
-        # FIXME: constructor signature takes a 'path' because CalendarHomeFile
-        # requires it, but we don't need it (and shouldn't have it) eventually.
+    def __init__(self, parent, *a, **kw):
         kw.update(principalCollections=parent.principalCollections())
         super(DropboxCollection, self).__init__(*a, **kw)
-        self._newStoreCalendarHome = parent._newStoreCalendarHome
+        self._newStoreHome = parent._newStoreHome
         parent.propagateTransaction(self)
 
 
@@ -337,7 +375,7 @@
 
 
     def getChild(self, name):
-        calendarObject = self._newStoreCalendarHome.calendarObjectWithDropboxID(name)
+        calendarObject = self._newStoreHome.calendarObjectWithDropboxID(name)
         if calendarObject is None:
             return NoDropboxHere()
         objectDropbox = CalendarObjectDropbox(
@@ -347,19 +385,17 @@
         return objectDropbox
 
 
-    def resourceType(self, request):
-        return succeed(davxml.ResourceType.dropboxhome)
+    def resourceType(self,):
+        return davxml.ResourceType.dropboxhome
 
 
     def listChildren(self):
         l = []
-        for everyCalendar in self._newStoreCalendarHome.calendars():
+        for everyCalendar in self._newStoreHome.calendars():
             for everyObject in everyCalendar.calendarObjects():
                 l.append(everyObject.dropboxID())
         return l
 
-
-
 class NoDropboxHere(_GetChildHelper):
 
     def isCollection(self):
@@ -394,8 +430,8 @@
         return True
 
 
-    def resourceType(self, request):
-        return succeed(davxml.ResourceType.dropbox)
+    def resourceType(self):
+        return davxml.ResourceType.dropbox
 
 
     def getChild(self, name):
@@ -538,14 +574,16 @@
             return CREATED
         return readStream(request.stream, t.write).addCallback(done)
 
+    http_MKCOL = None
+    http_MKCALENDAR = None
 
 
-class CalendarAttachment(_GetChildHelper):
+class CalendarAttachment(_NewStoreFileMetaDataHelper, _GetChildHelper):
 
     def __init__(self, calendarObject, attachment, **kw):
         super(CalendarAttachment, self).__init__(**kw)
         self._newStoreCalendarObject = calendarObject
-        self._newStoreAttachment = attachment
+        self._newStoreAttachment = self._newStoreObject = attachment
 
 
     def etag(self):
@@ -600,26 +638,40 @@
         self.__class__ = ProtoCalendarAttachment
         return NO_CONTENT
 
+    http_MKCOL = None
+    http_MKCALENDAR = None
 
     def isCollection(self):
         return False
 
 
 
-class CalendarCollectionFile(_CalendarChildHelper, CalDAVFile):
+class CalendarCollectionResource(_CalendarChildHelper, CalDAVResource):
     """
     Wrapper around a L{txcaldav.icalendar.ICalendar}.
     """
 
     def __init__(self, calendar, home, *args, **kw):
         """
-        Create a CalendarCollectionFile from a L{txcaldav.icalendar.ICalendar}
-        and the arguments required for L{CalDAVFile}.
+        Create a CalendarCollectionResource from a L{txcaldav.icalendar.ICalendar}
+        and the arguments required for L{CalDAVResource}.
         """
-        super(CalendarCollectionFile, self).__init__(*args, **kw)
+        super(CalendarCollectionResource, self).__init__(*args, **kw)
         self._initializeWithCalendar(calendar, home)
 
 
+    def name(self):
+        return self._newStoreCalendar.name()
+
+    def etag(self):
+        return ETag(self._newStoreCalendar.md5())
+
+    def lastModified(self):
+        return self._newStoreCalendar.modified()
+
+    def creationDate(self):
+        return self._newStoreCalendar.created()
+
     def isCollection(self):
         return True
 
@@ -688,7 +740,7 @@
 
         # Is this a sharee's view of a shared calendar?  If so, they can't do
         # scheduling onto it, so just delete it and move on.
-        isVirtual = yield self.isVirtualShare(request)
+        isVirtual = self.isVirtualShare()
         if isVirtual:
             log.debug("Removing shared calendar %s" % (self,))
             yield self.removeVirtualShare(request)
@@ -727,7 +779,7 @@
         self._newStoreParentHome.removeCalendarWithName(
             self._newStoreCalendar.name()
         )
-        self.__class__ = ProtoCalendarCollectionFile
+        self.__class__ = ProtoCalendarCollectionResource
         del self._newStoreCalendar
 
         # FIXME: handle exceptions, possibly like this:
@@ -768,20 +820,20 @@
             returnValue(FORBIDDEN)
         destination = yield request.locateResource(destinationURI)
         # FIXME: should really use something other than 'fp' attribute.
-        basename = destination.fp.basename()
+        basename = destination.name()
         calendar = self._newStoreCalendar
         calendar.rename(basename)
-        CalendarCollectionFile.transform(destination, calendar,
+        CalendarCollectionResource.transform(destination, calendar,
                                          self._newStoreParentHome)
         del self._newStoreCalendar
-        self.__class__ = ProtoCalendarCollectionFile
+        self.__class__ = ProtoCalendarCollectionResource
         self.movedCalendar(request, defaultCalendar,
                            destination, destinationURI)
         returnValue(NO_CONTENT)
 
 
 
-class NoParent(CalDAVFile):
+class NoParent(CalDAVResource):
     def http_MKCALENDAR(self, request):
         return CONFLICT
 
@@ -789,36 +841,38 @@
     def http_PUT(self, request):
         return CONFLICT
 
+    def isCollection(self):
+        return False
 
-
-class ProtoCalendarCollectionFile(CalDAVFile):
+class ProtoCalendarCollectionResource(CalDAVResource):
     """
     A resource representing a calendar collection which hasn't yet been created.
     """
 
-    def __init__(self, home, *args, **kw):
+    def __init__(self, home, name, *args, **kw):
         """
         A placeholder resource for a calendar collection which does not yet
-        exist, but will become a L{CalendarCollectionFile}.
+        exist, but will become a L{CalendarCollectionResource}.
 
         @param home: The calendar home which will be this resource's parent,
             when it exists.
 
         @type home: L{txcaldav.icalendarstore.ICalendarHome}
         """
+        super(ProtoCalendarCollectionResource, self).__init__(*args, **kw)
         self._newStoreParentHome = home
-        super(ProtoCalendarCollectionFile, self).__init__(*args, **kw)
+        self._name = name
 
 
     def isCollection(self):
         return True
 
-    def createSimilarFile(self, path):
+    def makeChild(self, name):
         # FIXME: this is necessary for 
         # twistedcaldav.test.test_mkcalendar.
         #     MKCALENDAR.test_make_calendar_no_parent - there should be a more
         # structured way to refuse creation with a non-existent parent.
-        return NoParent(path)
+        return NoParent()
 
 
     def provisionFile(self):
@@ -835,12 +889,11 @@
         """
         d = succeed(CREATED)
 
-        calendarName = self.fp.basename()
-        self._newStoreParentHome.createCalendarWithName(calendarName)
+        self._newStoreParentHome.createCalendarWithName(self._name)
         newStoreCalendar = self._newStoreParentHome.calendarWithName(
-            calendarName
+            self._name
         )
-        CalendarCollectionFile.transform(
+        CalendarCollectionResource.transform(
             self, newStoreCalendar, self._newStoreParentHome
         )
         return d
@@ -851,6 +904,9 @@
         return False
 
 
+    def name(self):
+        return self._name
+
     def provision(self):
         """
         This resource should do nothing if it's provisioned.
@@ -864,7 +920,7 @@
 
 
 
-class CalendarObjectFile(CalDAVFile, FancyEqMixin):
+class CalendarObjectResource(_NewStoreFileMetaDataHelper, CalDAVResource, FancyEqMixin):
     """
     A resource wrapping a calendar object.
     """
@@ -873,19 +929,15 @@
 
     def __init__(self, calendarObject, *args, **kw):
         """
-        Construct a L{CalendarObjectFile} from an L{ICalendarObject}.
+        Construct a L{CalendarObjectResource} from an L{ICalendarObject}.
 
         @param calendarObject: The storage for the calendar object.
         @type calendarObject: L{txcaldav.icalendarstore.ICalendarObject}
         """
-        super(CalendarObjectFile, self).__init__(*args, **kw)
+        super(CalendarObjectResource, self).__init__(*args, **kw)
         self._initializeWithObject(calendarObject)
 
 
-    def isCollection(self):
-        return False
-
-
     def inNewTransaction(self, request):
         """
         Implicit auto-replies need to span multiple transactions.  Clean out
@@ -912,40 +964,15 @@
         return txn
 
 
+    def isCollection(self):
+        return False
+
+
     def exists(self):
         # FIXME: Tests
         return True
 
 
-    def name(self):
-        return self._newStoreObject.name()
-
-
-    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.iCalendarText()).hexdigest(),
-                    weak=False
-                )
-        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.
-            return None
-
-
-    def newStoreProperties(self):
-        return self._newStoreObject.properties()
-
-
     def quotaSize(self, request):
         # FIXME: tests
         return succeed(len(self._newStoreObject.iCalendarText()))
@@ -956,6 +983,10 @@
         return self._newStoreObject.iCalendarText()
 
 
+    def text(self):
+        return self.iCalendarText()
+
+
     @requiresPermissions(fromParent=[davxml.Unbind()])
     def http_DELETE(self, request):
         """
@@ -1005,7 +1036,7 @@
         required.
 
         @param request: Unused by this implementation; present for signature
-            compatibility with L{CalendarCollectionFile.storeRemove}.
+            compatibility with L{CalendarCollectionResource.storeRemove}.
 
         @type request: L{twext.web2.iweb.IRequest}
 
@@ -1072,7 +1103,7 @@
             # FIXME: clean this up with a 'transform' method
             self._newStoreParentCalendar = storeCalendar
             del self._newStoreObject
-            self.__class__ = ProtoCalendarObjectFile
+            self.__class__ = ProtoCalendarObjectResource
 
             # Adjust quota
             if myquota is not None:
@@ -1109,13 +1140,14 @@
 
 
 
-class ProtoCalendarObjectFile(CalDAVFile, FancyEqMixin):
+class ProtoCalendarObjectResource(CalDAVResource, FancyEqMixin):
 
     compareAttributes = '_newStoreParentCalendar'.split()
 
-    def __init__(self, parentCalendar, *a, **kw):
-        super(ProtoCalendarObjectFile, self).__init__(*a, **kw)
+    def __init__(self, parentCalendar, name, *a, **kw):
+        super(ProtoCalendarObjectResource, self).__init__(*a, **kw)
         self._newStoreParentCalendar = parentCalendar
+        self._name = name
 
 
     @inlineCallbacks
@@ -1125,9 +1157,9 @@
             (yield allDataFromStream(stream))
         )
         self._newStoreParentCalendar.createCalendarObjectWithName(
-            self.fp.basename(), component
+            self.name(), component
         )
-        CalendarObjectFile.transform(self, self._newStoreParentCalendar.calendarObjectWithName(self.fp.basename()))
+        CalendarObjectResource.transform(self, self._newStoreParentCalendar.calendarObjectWithName(self.name()))
         returnValue(CREATED)
 
 
@@ -1144,7 +1176,7 @@
 
 
     def name(self):
-        return self.fp.basename()
+        return self._name
 
     def quotaSize(self, request):
         # FIXME: tests, workingness
@@ -1197,60 +1229,81 @@
     @classmethod
     def transform(cls, self, addressbook, home):
         """
-        Transform C{self} into a L{AddressBookCollectionFile}.
+        Transform C{self} into a L{AddressBookCollectionResource}.
         """
         self.__class__ = cls
         self._initializeWithAddressBook(addressbook, home)
 
 
-    def createSimilarFile(self, path):
+    def makeChild(self, name):
         """
-        Create a L{AddressBookObjectFile} or L{ProtoAddressBookObjectFile} based on a
+        Create a L{AddressBookObjectResource} or L{ProtoAddressBookObjectResource} based on a
         path object.
         """
-        if not isinstance(path, FilePath):
-            path = FilePath(path)
+        newStoreObject = self._newStoreAddressBook.addressbookObjectWithName(name)
 
-        newStoreObject = self._newStoreAddressBook.addressbookObjectWithName(
-            path.basename()
-        )
-
         if newStoreObject is not None:
-            similar = AddressBookObjectFile(newStoreObject, path,
-                principalCollections=self._principalCollections)
+            similar = AddressBookObjectResource(
+                newStoreObject,
+                principalCollections=self._principalCollections
+            )
         else:
             # FIXME: creation in http_PUT should talk to a specific resource
             # type; this is the domain of StoreAddressBookObjectResource.
             # similar = ProtoAddressBookObjectFile(self._newStoreAddressBook, path)
-            similar = ProtoAddressBookObjectFile(self._newStoreAddressBook, path,
-                principalCollections=self._principalCollections)
+            similar = ProtoAddressBookObjectResource(
+                self._newStoreAddressBook,
+                name,
+                principalCollections=self._principalCollections
+            )
 
         # FIXME: tests should be failing without this line.
         # Specifically, http_PUT won't be committing its transaction properly.
         self.propagateTransaction(similar)
         return similar
 
+    def listChildren(self):
+        """
+        @return: a sequence of the names of all known children of this resource.
+        """
+        children = set(self.putChildren.keys())
+        children.update(self._newStoreAddressBook.listAddressbookObjects())
+        return sorted(children)
 
+
+
     def quotaSize(self, request):
         # FIXME: tests, workingness
         return succeed(0)
 
 
 
-class AddressBookCollectionFile(_AddressBookChildHelper, CalDAVFile):
+class AddressBookCollectionResource(_AddressBookChildHelper, CalDAVResource):
     """
     Wrapper around a L{txcarddav.iaddressbook.IAddressBook}.
     """
 
     def __init__(self, addressbook, home, *args, **kw):
         """
-        Create a AddressBookCollectionFile from a L{txcarddav.iaddressbook.IAddressBook}
-        and the arguments required for L{CalDAVFile}.
+        Create a AddressBookCollectionResource from a L{txcarddav.iaddressbook.IAddressBook}
+        and the arguments required for L{CalDAVResource}.
         """
-        super(AddressBookCollectionFile, self).__init__(*args, **kw)
+        super(AddressBookCollectionResource, self).__init__(*args, **kw)
         self._initializeWithAddressBook(addressbook, home)
 
 
+    def name(self):
+        return self._newStoreAddressBook.name()
+
+    def etag(self):
+        return ETag(self._newStoreAddressBook.md5())
+
+    def lastModified(self):
+        return self._newStoreAddressBook.modified()
+
+    def creationDate(self):
+        return self._newStoreAddressBook.created()
+
     def isCollection(self):
         return True
 
@@ -1304,7 +1357,7 @@
         """
 
         # Check virtual share first
-        isVirtual = yield self.isVirtualShare(request)
+        isVirtual = self.isVirtualShare()
         if isVirtual:
             log.debug("Removing shared calendar %s" % (self,))
             yield self.removeVirtualShare(request)
@@ -1343,7 +1396,7 @@
         self._newStoreParentHome.removeAddressBookWithName(
             self._newStoreAddressBook.name()
         )
-        self.__class__ = ProtoAddressBookCollectionFile
+        self.__class__ = ProtoAddressBookCollectionResource
         del self._newStoreAddressBook
 
         # FIXME: handle exceptions, possibly like this:
@@ -1379,46 +1432,47 @@
             returnValue(FORBIDDEN)
         destination = yield request.locateResource(destinationURI)
         # FIXME: should really use something other than 'fp' attribute.
-        basename = destination.fp.basename()
+        basename = destination.name()
         addressbook = self._newStoreAddressBook
         addressbook.rename(basename)
-        AddressBookCollectionFile.transform(destination, addressbook,
+        AddressBookCollectionResource.transform(destination, addressbook,
                                          self._newStoreParentHome)
         del self._newStoreAddressBook
-        self.__class__ = ProtoAddressBookCollectionFile
+        self.__class__ = ProtoAddressBookCollectionResource
         returnValue(NO_CONTENT)
 
 
 
-class ProtoAddressBookCollectionFile(CalDAVFile):
+class ProtoAddressBookCollectionResource(CalDAVResource):
     """
     A resource representing an addressbook collection which hasn't yet been created.
     """
 
-    def __init__(self, home, *args, **kw):
+    def __init__(self, home, name, *args, **kw):
         """
         A placeholder resource for an addressbook collection which does not yet
-        exist, but will become a L{AddressBookCollectionFile}.
+        exist, but will become a L{AddressBookCollectionResource}.
 
         @param home: The addressbook home which will be this resource's parent,
             when it exists.
 
         @type home: L{txcarddav.iaddressbookstore.IAddressBookHome}
         """
+        super(ProtoAddressBookCollectionResource, self).__init__(*args, **kw)
         self._newStoreParentHome = home
-        super(ProtoAddressBookCollectionFile, self).__init__(*args, **kw)
+        self._name = name
 
 
     def isCollection(self):
         return True
 
 
-    def createSimilarFile(self, path):
+    def makeChild(self, name):
         # FIXME: this is necessary for 
         # twistedcaldav.test.test_mkcol.
         #     MKCOL.test_make_addressbook_no_parent - there should be a more
         # structured way to refuse creation with a non-existent parent.
-        return NoParent(path)
+        return NoParent()
 
 
     def provisionFile(self):
@@ -1436,12 +1490,11 @@
         """
         d = succeed(CREATED)
 
-        Name = self.fp.basename()
-        self._newStoreParentHome.createAddressBookWithName(Name)
+        self._newStoreParentHome.createAddressBookWithName(self._name)
         newStoreAddressBook = self._newStoreParentHome.addressbookWithName(
-            Name
+            self._name
         )
-        AddressBookCollectionFile.transform(
+        AddressBookCollectionResource.transform(
             self, newStoreAddressBook, self._newStoreParentHome
         )
         return d
@@ -1452,6 +1505,9 @@
         return False
 
 
+    def name(self):
+        return self._name
+
     def provision(self):
         """
         This resource should do nothing if it's provisioned.
@@ -1464,105 +1520,20 @@
         return succeed(0)
 
 
-class GlobalAddressBookCollectionFile(_AddressBookChildHelper, GlobalAddressBookFile):
+class GlobalAddressBookCollectionResource(GlobalAddressBookResource, AddressBookCollectionResource):
     """
     Wrapper around a L{txcarddav.iaddressbook.IAddressBook}.
     """
+    pass
 
-    def __init__(self, addressbook, home, *args, **kw):
-        """
-        Create a GlobalAddressBookCollectionFile from a L{txcarddav.iaddressbook.IAddressBook}
-        and the arguments required for L{CalDAVFile}.
-        """
-        super(GlobalAddressBookCollectionFile, self).__init__(*args, **kw)
-        self._initializeWithAddressBook(addressbook, home)
-
-
-    def isCollection(self):
-        return True
-
-    def isAddressBookCollection(self):
-        """
-        Yes, it is a calendar collection.
-        """
-        return True
-
-class ProtoGlobalAddressBookCollectionFile(GlobalAddressBookFile):
+class ProtoGlobalAddressBookCollectionResource(GlobalAddressBookResource, ProtoAddressBookCollectionResource):
     """
     A resource representing an addressbook collection which hasn't yet been created.
     """
+    pass
 
-    def __init__(self, home, *args, **kw):
-        """
-        A placeholder resource for an addressbook collection which does not yet
-        exist, but will become a L{GlobalAddressBookCollectionFile}.
 
-        @param home: The addressbook home which will be this resource's parent,
-            when it exists.
-
-        @type home: L{txcarddav.iaddressbookstore.IAddressBookHome}
-        """
-        self._newStoreParentHome = home
-        super(ProtoGlobalAddressBookCollectionFile, self).__init__(*args, **kw)
-
-
-    def isCollection(self):
-        return True
-
-
-    def createSimilarFile(self, path):
-        # FIXME: this is necessary for 
-        # twistedcaldav.test.test_mkcol.
-        #     MKCOL.test_make_addressbook_no_parent - there should be a more
-        # structured way to refuse creation with a non-existent parent.
-        return NoParent(path)
-
-
-    def provisionFile(self):
-        """
-        Create an addressbook collection.
-        """
-        # FIXME: this should be done in the backend; provisionDefaultAddressBooks
-        # should go away.
-        return self.createAddressBookCollection()
-
-
-    def createAddressBookCollection(self):
-        """
-        Override C{createAddressBookCollection} to actually do the work.
-        """
-        d = succeed(CREATED)
-
-        Name = self.fp.basename()
-        self._newStoreParentHome.createAddressBookWithName(Name)
-        newStoreAddressBook = self._newStoreParentHome.addressbookWithName(
-            Name
-        )
-        GlobalAddressBookCollectionFile.transform(
-            self, newStoreAddressBook, self._newStoreParentHome
-        )
-        return d
-
-
-    def exists(self):
-        # FIXME: tests
-        return False
-
-
-    def provision(self):
-        """
-        This resource should do nothing if it's provisioned.
-        """
-        # FIXME: should be deleted, or raise an exception
-
-
-    def quotaSize(self, request):
-        # FIXME: tests, workingness
-        return succeed(0)
-
-
-
-class AddressBookObjectFile(CalDAVFile, FancyEqMixin):
+class AddressBookObjectResource(_NewStoreFileMetaDataHelper, CalDAVResource, FancyEqMixin):
     """
     A resource wrapping a addressbook object.
     """
@@ -1571,12 +1542,12 @@
 
     def __init__(self, Object, *args, **kw):
         """
-        Construct a L{AddressBookObjectFile} from an L{IAddressBookObject}.
+        Construct a L{AddressBookObjectResource} from an L{IAddressBookObject}.
 
         @param Object: The storage for the addressbook object.
         @type Object: L{txcarddav.iaddressbookstore.IAddressBookObject}
         """
-        super(AddressBookObjectFile, self).__init__(*args, **kw)
+        super(AddressBookObjectResource, self).__init__(*args, **kw)
         self._initializeWithObject(Object)
 
 
@@ -1595,7 +1566,7 @@
         homeUID = self._newStoreObject._addressbook._addressbookHome.uid()
         store = self._newStoreObject._transaction.store()
         txn = store.newTransaction("new AB transaction for " + self._newStoreObject.name())
-        newObject = (txn.HomeWithUID(homeUID)
+        newObject = (txn.addressbookHomeWithUID(homeUID)
                         .addressbookWithName(Name)
                         .addressbookObjectWithName(objectName))
         request._newStoreTransaction = txn
@@ -1613,34 +1584,6 @@
         return True
 
 
-    def name(self):
-        return self._newStoreObject.name()
-
-    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 addressbook file)
-
-        # FIXME: direct tests
-        try:
-            if self.hasDeadProperty(TwistedGETContentMD5):
-                return ETag(str(self.readDeadProperty(TwistedGETContentMD5)))
-            else:
-                return ETag(
-                    hashlib.new("md5", self.vCardText()).hexdigest(),
-                    weak=False
-                )
-        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.
-            return None
-
-
-    def newStoreProperties(self):
-        return self._newStoreObject.properties()
-
-
     def quotaSize(self, request):
         # FIXME: tests
         return succeed(len(self._newStoreObject.vCardText()))
@@ -1651,6 +1594,17 @@
         return self._newStoreObject.vCardText()
 
 
+    def text(self):
+        return self.vCardText()
+
+
+    def render(self, request):
+        output = self.vCardText()
+
+        response = Response(200, {}, output)
+        response.headers.setHeader("content-type", self.contentType())
+        return response
+
     @requiresPermissions(fromParent=[davxml.Unbind()])
     def http_DELETE(self, request):
         """
@@ -1693,7 +1647,7 @@
             # FIXME: clean this up with a 'transform' method
             self._newStoreParentAddressBook = storeAddressBook
             del self._newStoreObject
-            self.__class__ = ProtoAddressBookObjectFile
+            self.__class__ = ProtoAddressBookObjectResource
 
             # Adjust quota
             if myquota is not None:
@@ -1719,15 +1673,15 @@
 
 
 
-class ProtoAddressBookObjectFile(CalDAVFile, FancyEqMixin):
+class ProtoAddressBookObjectResource(CalDAVResource, FancyEqMixin):
 
     compareAttributes = '_newStoreParentAddressBook'.split()
 
-    def __init__(self, parentAddressBook, *a, **kw):
-        super(ProtoAddressBookObjectFile, self).__init__(*a, **kw)
+    def __init__(self, parentAddressBook, name, *a, **kw):
+        super(ProtoAddressBookObjectResource, self).__init__(*a, **kw)
         self._newStoreParentAddressBook = parentAddressBook
+        self._name = name
 
-
     @inlineCallbacks
     def storeStream(self, stream):
         # FIXME: direct tests 
@@ -1735,9 +1689,9 @@
             (yield allDataFromStream(stream))
         )
         self._newStoreParentAddressBook.createAddressBookObjectWithName(
-            self.fp.basename(), component
+            self.name(), component
         )
-        AddressBookObjectFile.transform(self, self._newStoreParentAddressBook.addressbookObjectWithName(self.fp.basename()))
+        AddressBookObjectResource.transform(self, self._newStoreParentAddressBook.addressbookObjectWithName(self.name()))
         returnValue(CREATED)
 
 
@@ -1755,7 +1709,7 @@
 
 
     def name(self):
-        return self.fp.basename()
+        return self._name
 
     def quotaSize(self, request):
         # FIXME: tests, workingness
@@ -1786,6 +1740,12 @@
         )
 
 
+    def locateChild(self, request, segments):
+        if segments[0] == '':
+            return self, segments[1:]
+        return self.getChild(segments[0]), segments[1:]
+
+
     def notificationsDB(self):
         """
         Retrieve the new-style index wrapper.
@@ -1801,96 +1761,162 @@
     @classmethod
     def transform(cls, self, notifications, home):
         """
-        Transform C{self} into a L{NotificationCollectionFile}.
+        Transform C{self} into a L{NotificationCollectionResource}.
         """
         self.__class__ = cls
         self._initializeWithNotifications(notifications, home)
 
 
-    def createSimilarFile(self, path):
+    def makeChild(self, name):
         """
         Create a L{NotificationObjectFile} or L{ProtoNotificationObjectFile} based on a
         path object.
         """
-        if not isinstance(path, FilePath):
-            path = FilePath(path)
+        newStoreObject = self._newStoreNotifications.notificationObjectWithName(name)
 
-        newStoreObject = self._newStoreNotifications.notificationObjectWithName(
-            path.basename()
-        )
-
         if newStoreObject is not None:
-            similar = StoreNotificationObjectFile(newStoreObject, path, self)
+            similar = StoreNotificationObjectFile(newStoreObject, 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)
+            similar = ProtoStoreNotificationObjectFile(self._newStoreNotifications, 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 listChildren(self):
+        """
+        @return: a sequence of the names of all known children of this resource.
+        """
+        children = set(self.putChildren.keys())
+        children.update(self._newStoreNotifications.listNotificationObjects())
+        return children
 
+
+
     def quotaSize(self, request):
         # FIXME: tests, workingness
         return succeed(0)
 
 
 
-class StoreNotificationCollectionFile(_NotificationChildHelper,
-                                      NotificationCollectionFile):
+class StoreNotificationCollectionResource(_NotificationChildHelper,
+                                      NotificationCollectionResource):
     """
     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}.
+        Create a CalendarCollectionResource from a L{txcaldav.icalendar.ICalendar}
+        and the arguments required for L{CalDAVResource}.
         """
-        super(StoreNotificationCollectionFile, self).__init__(*args, **kw)
+        super(StoreNotificationCollectionResource, self).__init__(*args, **kw)
         self._initializeWithNotifications(notifications, home)
 
 
+    def listChildren(self):
+        l = []
+        for notification in self._newStoreNotifications.notificationObjects():
+            l.append(notification.name())
+        return l
+
     def isCollection(self):
         return True
 
+    def addNotification(self, request, uid, xmltype, xmldata):
 
-    @inlineCallbacks
-    def http_DELETE(self, request):
+        self._newStoreNotifications.writeNotificationObject(uid, xmltype, xmldata)
+        return succeed(None)
+
+    def deleteNotification(self, request, record):
+        self._newStoreNotifications.removeNotificationObjectWithName(record.name)
+        return succeed(None)
+
+class StoreProtoNotificationCollectionResource(NotificationCollectionResource):
+    """
+    A resource representing a notification collection which hasn't yet been created.
+    """
+
+    def __init__(self, home, *args, **kw):
         """
-        Override http_DELETE to reject. 
+        A placeholder resource for a notification collection which does not yet
+        exist, but will become a L{StoreNotificationCollectionResource}.
+
+        @param home: The calendar home which will be this resource's parent,
+            when it exists.
+
+        @type home: L{txcaldav.icalendarstore.ICalendarHome}
         """
-        raise HTTPError(StatusResponse(FORBIDDEN, "Cannot delete notification collections"))
+        self._newStoreParentHome = home
+        super(StoreProtoNotificationCollectionResource, self).__init__(*args, **kw)
 
 
-    def http_COPY(self, request):
+    def isCollection(self):
+        return True
+
+    def makeChild(self, name):
+        # FIXME: this is necessary for 
+        # twistedcaldav.test.test_mkcalendar.
+        #     MKCALENDAR.test_make_calendar_no_parent - there should be a more
+        # structured way to refuse creation with a non-existent parent.
+        return NoParent()
+
+
+    def provisionFile(self):
         """
-        Copying of calendar collections isn't allowed.
+        Create a calendar collection.
         """
-        raise HTTPError(StatusResponse(FORBIDDEN, "Cannot copy notification collections"))
+        # FIXME: there should be no need for this.
+        return self.createNotificationCollection()
 
 
-    @inlineCallbacks
-    def http_MOVE(self, request):
+    def createNotificationCollection(self):
         """
-        Moving a calendar collection is allowed for the purposes of changing
-        that calendar's name.
+        Override C{createCalendarCollection} to actually do the work.
         """
-        raise HTTPError(StatusResponse(FORBIDDEN, "Cannot move notification collections"))
+        d = succeed(CREATED)
 
+        notificationName = self.name()
+        self._newStoreParentHome.createChildWithName(notificationName)
+        newStoreNotification = self._newStoreParentHome.childWithName(
+            notificationName
+        )
+        StoreNotificationCollectionResource.transform(
+            self, newStoreNotification, self._newStoreParentHome
+        )
+        return d
 
 
-class StoreNotificationObjectFile(NotificationFile):
+    def exists(self):
+        # FIXME: tests
+        return False
+
+
+    def provision(self):
+        """
+        This resource should do nothing if it's provisioned.
+        """
+        # FIXME: should be deleted, or raise an exception
+
+
+    def quotaSize(self, request):
+        # FIXME: tests, workingness
+        return succeed(0)
+
+
+
+class StoreNotificationObjectFile(NotificationResource):
     """
     A resource wrapping a calendar object.
     """
 
     def __init__(self, notificationObject, *args, **kw):
         """
-        Construct a L{CalendarObjectFile} from an L{ICalendarObject}.
+        Construct a L{CalendarObjectResource} from an L{ICalendarObject}.
 
         @param calendarObject: The storage for the calendar object.
         @type calendarObject: L{txcaldav.icalendarstore.ICalendarObject}
@@ -1915,8 +1941,9 @@
 
         # FIXME: direct tests
         try:
-            if self.hasDeadProperty(TwistedGETContentMD5):
-                return ETag(str(self.readDeadProperty(TwistedGETContentMD5)))
+            md5 = self._newStoreObject.md5()
+            if md5:
+                return ETag(md5)
             else:
                 return ETag(
                     hashlib.new("md5", self.text()).hexdigest(),
@@ -1928,7 +1955,18 @@
             # call storeRemove.
             return None
 
+    def contentType(self):
+        return self._newStoreObject.contentType()
 
+    def contentLength(self):
+        return self._newStoreObject.size()
+
+    def lastModified(self):
+        return self._newStoreObject.modified()
+
+    def creationDate(self):
+        return self._newStoreObject.created()
+
     def newStoreProperties(self):
         return self._newStoreObject.properties()
 
@@ -1943,6 +1981,10 @@
         return self._newStoreObject.xmldata()
 
 
+    @requiresPermissions(davxml.Read())
+    def http_GET(self, request):
+        return Response(OK, {"content-type":self.contentType()}, MemoryStream(self.text()))
+
     @requiresPermissions(fromParent=[davxml.Unbind()])
     def http_DELETE(self, request):
         """
@@ -2001,7 +2043,7 @@
 
 
 
-class ProtoStoreNotificationObjectFile(NotificationFile):
+class ProtoStoreNotificationObjectFile(NotificationResource):
 
     def __init__(self, parentNotifications, *a, **kw):
         super(ProtoStoreNotificationObjectFile, self).__init__(*a, **kw)

Deleted: CalendarServer/trunk/twistedcaldav/test/test_DAV.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_DAV.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/twistedcaldav/test/test_DAV.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -1,51 +0,0 @@
-##
-# Copyright (c) 2006-2007 Apple Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-##
-
-from twistedcaldav.static import CalDAVFile as MyResource
-
-import twext.web2.dav.test.test_acl
-import twext.web2.dav.test.test_copy
-import twext.web2.dav.test.test_delete
-import twext.web2.dav.test.test_lock
-import twext.web2.dav.test.test_mkcol
-import twext.web2.dav.test.test_move
-import twext.web2.dav.test.test_options
-import twext.web2.dav.test.test_prop
-import twext.web2.dav.test.test_put
-import twext.web2.dav.test.test_report
-import twext.web2.dav.test.test_report_expand
-from twisted.trial.unittest import SkipTest
-
-def ignored(self):
-    raise SkipTest("method requires backing store objects, tested elsewhere")
-
-class ACL           (twext.web2.dav.test.test_acl.ACL                    ):
-    resource_class = MyResource
-    test_DELETE = ignored
-
-class COPY          (twext.web2.dav.test.test_copy.COPY                  ): resource_class = MyResource
-class DELETE        (twext.web2.dav.test.test_delete.DELETE              ):
-    resource_class = MyResource
-    test_DELETE = ignored
-
-class LOCK_UNLOCK   (twext.web2.dav.test.test_lock.LOCK_UNLOCK           ): resource_class = MyResource
-class MKCOL         (twext.web2.dav.test.test_mkcol.MKCOL                ): resource_class = MyResource
-class MOVE          (twext.web2.dav.test.test_move.MOVE                  ): resource_class = MyResource
-class OPTIONS       (twext.web2.dav.test.test_options.OPTIONS            ): resource_class = MyResource
-class PROP          (twext.web2.dav.test.test_prop.PROP                  ): resource_class = MyResource
-class PUT           (twext.web2.dav.test.test_put.PUT                    ): resource_class = MyResource
-class REPORT        (twext.web2.dav.test.test_report.REPORT              ): resource_class = MyResource
-class REPORT_expand (twext.web2.dav.test.test_report_expand.REPORT_expand): resource_class = MyResource

Modified: CalendarServer/trunk/twistedcaldav/test/test_accounting.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_accounting.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/twistedcaldav/test/test_accounting.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -30,7 +30,7 @@
         super(AccountingITIP, self).setUp()
         config.AccountingCategories.iTIP = True
         config.AccountingPrincipals = ["*",]
-        config.AccountingLogRoot = self.mkdtemp("accounting")[0]
+        os.mkdir(config.AccountingLogRoot)
 
     class _Principal(object):
         
@@ -59,7 +59,7 @@
         """
         
         # Make log root a file
-        config.AccountingLogRoot = os.path.abspath(self.mktemp())
+        config.AccountingLogRoot = "other"
         open(config.AccountingLogRoot, "w").close()
         emitAccounting("iTIP", self._Principal("1234-5678"), "bogus")
 
@@ -70,7 +70,6 @@
         super(AccountingHTTP, self).setUp()
         config.AccountingCategories.HTTP = True
         config.AccountingPrincipals = ["*",]
-        config.AccountingLogRoot = self.mkdtemp("accounting")[0]
 
     def test_channel_request(self):
         """

Modified: CalendarServer/trunk/twistedcaldav/test/test_addressbookmultiget.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_addressbookmultiget.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/twistedcaldav/test/test_addressbookmultiget.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -45,7 +45,7 @@
         okuids = [r[0] for r in (os.path.splitext(f) for f in os.listdir(self.vcards_dir)) if r[1] == ".vcf"]
         okuids[:] = okuids[1:5]
 
-        baduids = ["12345 at example.com", "67890 at example.com"]
+        baduids = ["12345%40example.com", "67890%40example.com"]
 
         return self.simple_vcard_multiget("/addressbook_multiget_vcards/", okuids, baduids)
 
@@ -55,7 +55,7 @@
         """
         okuids = [r[0] for r in (os.path.splitext(f) for f in os.listdir(self.vcards_dir)) if r[1] == ".vcf"]
 
-        baduids = ["12345 at example.com", "67890 at example.com"]
+        baduids = ["12345%40example.com", "67890%40example.com"]
 
         return self.simple_vcard_multiget("/addressbook_multiget_vcards/", okuids, baduids)
 
@@ -75,7 +75,7 @@
 
         okuids = [r[0] for r in (os.path.splitext(f) for f in os.listdir(self.vcards_dir)) if r[1] == ".vcf"]
 
-        baduids = ["12345 at example.com", "67890 at example.com"]
+        baduids = ["12345%40example.com", "67890%40example.com"]
 
         d = self.simple_vcard_multiget("/addressbook_multiget_vcards/", okuids, baduids)
         d.addCallbacks(_restoreValueOK, _restoreValueError)
@@ -97,7 +97,7 @@
 
         okuids = [r[0] for r in (os.path.splitext(f) for f in os.listdir(self.vcards_dir)) if r[1] == ".vcf"]
 
-        baduids = ["12345 at example.com", "67890 at example.com"]
+        baduids = ["12345%40example.com", "67890%40example.com"]
 
         return self.simple_vcard_multiget("/addressbook_multiget_vcards/", okuids, baduids, withData=False)
 

Modified: CalendarServer/trunk/twistedcaldav/test/test_collectioncontents.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_collectioncontents.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/twistedcaldav/test/test_collectioncontents.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -203,16 +203,6 @@
         request = SimpleRequest(self.site, "MKCALENDAR", calendar_uri)
         return self.send(request, mkcalendar_cb)
 
-    def test_ignore_dot_files(self):
-        """
-        Make sure database files are not listed as children.
-        """
-        colpath = self.site.resource.fp
-        colpath.child("._bogus").touch()
-        colpath.child("bogus").touch()
-        children = self.site.resource.listChildren()
-        self.assertTrue("bogus" in children)
-        self.assertFalse("._bogus" in children)
 
     def test_fail_dot_file_put_in_calendar(self):
         """

Modified: CalendarServer/trunk/twistedcaldav/test/test_config.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_config.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/twistedcaldav/test/test_config.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -18,7 +18,7 @@
 from twext.python.log import logLevelForNamespace
 
 from twistedcaldav.config import config, ConfigDict
-from twistedcaldav.static import CalDAVFile
+from twistedcaldav.resource import CalDAVResource
 from twistedcaldav.stdconfig import DEFAULT_CONFIG, PListConfigProvider,\
     RELATIVE_PATHS
 from twistedcaldav.test.util import TestCase
@@ -241,7 +241,7 @@
         self.assertNotIn("Foo", DEFAULT_CONFIG)
 
     def testComplianceClasses(self):
-        resource = CalDAVFile("/")
+        resource = CalDAVResource()
         
         config.EnableProxyPrincipals = True
         self.assertTrue("calendar-proxy" in resource.davComplianceClasses())

Modified: CalendarServer/trunk/twistedcaldav/test/test_extensions.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_extensions.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/twistedcaldav/test/test_extensions.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -164,7 +164,7 @@
         def addUnicodeChild(davFile):
             m = MetaDataMixin()
             m.contentType = lambda: MimeType.fromString('text/plain')
-            m.resourceType = lambda r: succeed(davxml.ResourceType())
+            m.resourceType = lambda: davxml.ResourceType()
             m.isCollection = lambda: False
             davFile.putChild(unicodeChildName, m)
         yield self.doDirectoryTest([nonASCIIFilename], addUnicodeChild,

Modified: CalendarServer/trunk/twistedcaldav/test/test_mkcalendar.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_mkcalendar.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/twistedcaldav/test/test_mkcalendar.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -26,7 +26,7 @@
 from twext.web2.test.test_server import SimpleRequest
 
 from twistedcaldav import caldavxml
-from twistedcaldav.test.util import HomeTestCase
+from twistedcaldav.test.util import HomeTestCase, todo
 
 class MKCALENDAR (HomeTestCase):
     """
@@ -150,6 +150,7 @@
         request.stream = MemoryStream(mk.toxml())
         return self.send(request, do_test)
 
+    @todo("Remove: Does not make sense with new store")
     def test_make_calendar_no_parent(self):
         """
         Make calendar with no parent
@@ -167,6 +168,7 @@
         request = SimpleRequest(self.site, "MKCALENDAR", uri)
         return self.send(request, do_test)
 
+    @todo("Remove: Does not make sense with new store")
     def test_make_calendar_on_resource(self):
         """
         Make calendar on existing resource

Modified: CalendarServer/trunk/twistedcaldav/test/test_multiget.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_multiget.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/twistedcaldav/test/test_multiget.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -29,7 +29,7 @@
 from twistedcaldav import caldavxml
 from twistedcaldav import ical
 from twistedcaldav.index import db_basename
-from twistedcaldav.test.util import HomeTestCase
+from twistedcaldav.test.util import HomeTestCase, todo
 from twistedcaldav.config import config
 
 class CalendarMultiget (HomeTestCase):
@@ -47,7 +47,7 @@
         okuids = [r[0] for r in (os.path.splitext(f) for f in os.listdir(self.holidays_dir)) if r[1] == ".ics"]
         okuids[:] = okuids[1:10]
 
-        baduids = ["12345 at example.com", "67890 at example.com"]
+        baduids = ["12345%40example.com", "67890%40example.com"]
 
         return self.simple_event_multiget("/calendar_multiget_events/", okuids, baduids)
 
@@ -58,7 +58,7 @@
         """
         okuids = [r[0] for r in (os.path.splitext(f) for f in os.listdir(self.holidays_dir)) if r[1] == ".ics"]
 
-        baduids = ["12345 at example.com", "67890 at example.com"]
+        baduids = ["12345%40example.com", "67890%40example.com"]
 
         return self.simple_event_multiget("/calendar_multiget_events/", okuids, baduids)
 
@@ -79,7 +79,7 @@
 
         okuids = [r[0] for r in (os.path.splitext(f) for f in os.listdir(self.holidays_dir)) if r[1] == ".ics"]
 
-        baduids = ["12345 at example.com", "67890 at example.com"]
+        baduids = ["12345%40example.com", "67890%40example.com"]
 
         d = self.simple_event_multiget("/calendar_multiget_events/", okuids, baduids)
         d.addCallbacks(_restoreValueOK, _restoreValueError)
@@ -102,10 +102,11 @@
 
         okuids = [r[0] for r in (os.path.splitext(f) for f in os.listdir(self.holidays_dir)) if r[1] == ".ics"]
 
-        baduids = ["12345 at example.com", "67890 at example.com"]
+        baduids = ["12345%40example.com", "67890%40example.com"]
 
         return self.simple_event_multiget("/calendar_multiget_events/", okuids, baduids, withData=False)
 
+    @todo("Remove: Does not work with new store")
     @inlineCallbacks
     def test_multiget_one_broken_event(self):
         """
@@ -155,7 +156,9 @@
 BEGIN:VEVENT
 UID:bad
 DTSTART;VALUE=DATE:20020214
-DTEND;VALUE=DATE:20020""".replace("\n", "\r\n"))
+DTEND;VALUE=DATE:20020
+END:VCALENDAR
+""".replace("\n", "\r\n"))
         f.close
 
         okuids = ["good", ]

Modified: CalendarServer/trunk/twistedcaldav/test/test_options.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_options.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/twistedcaldav/test/test_options.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -74,7 +74,7 @@
 
             allow = response.headers.getHeader("allow")
             if not allow: self.fail("no Allow header: %s" % (response.headers,))
-            self.assertIn("MKCALENDAR", allow, "no MKCALENDAR support")
+            self.assertNotIn("MKCALENDAR", allow, "no MKCALENDAR support")
 
         request = SimpleRequest(self.site, "OPTIONS", "/")
 

Modified: CalendarServer/trunk/twistedcaldav/test/test_props.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_props.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/twistedcaldav/test/test_props.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -128,7 +128,6 @@
 
             query = davxml.PropertyFind(
                         davxml.PropertyContainer(
-                            davxml.GETETag(),
                             caldavxml.SupportedCalendarData(),
                             caldavxml.SupportedCalendarComponentSet(),
                             davxml.SupportedReportSet(),

Modified: CalendarServer/trunk/twistedcaldav/test/test_schedule.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_schedule.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/twistedcaldav/test/test_schedule.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -14,8 +14,6 @@
 # limitations under the License.
 ##
 
-import os
-
 from twext.web2 import responsecode
 from twext.web2.iweb import IResponse
 from twext.web2.dav import davxml
@@ -24,7 +22,6 @@
 from twext.web2.test.test_server import SimpleRequest
 
 from twistedcaldav import caldavxml
-from twistedcaldav.static import ScheduleInboxFile
 
 from twistedcaldav.test.util import HomeTestCase
 
@@ -38,8 +35,6 @@
         """
 
         inbox_uri  = "/inbox/"
-        #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)
@@ -78,7 +73,6 @@
 
         query = davxml.PropertyFind(
                     davxml.PropertyContainer(
-                        davxml.GETETag(),
                         caldavxml.CalendarFreeBusySet(),
                     ),
                 )

Modified: CalendarServer/trunk/twistedcaldav/test/test_sharing.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_sharing.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/twistedcaldav/test/test_sharing.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -23,20 +23,26 @@
 from twisted.internet.defer import inlineCallbacks, returnValue, succeed
 from twistedcaldav import customxml
 from twistedcaldav.config import config
-from twistedcaldav.static import CalDAVFile
-from twistedcaldav.test.util import InMemoryPropertyStore
-from twistedcaldav.test.util import TestCase
-import os
+from twistedcaldav.test.util import HomeTestCase, norequest
+from twistedcaldav.resource import CalDAVResource
 
 
-class SharingTests(TestCase):
+class SharingTests(HomeTestCase):
 
     class FakePrincipal(object):
         
+        class FakeRecord(object):
+            
+            def __init__(self, name, cuaddr):
+                self.fullName = name
+                self.guid = name
+                self.calendarUserAddresses = set((cuaddr,))
+
         def __init__(self, cuaddr):
             self.path = "/principals/__uids__/%s" % (cuaddr[7:].split('@')[0],)
             self.homepath = "/calendars/__uids__/%s" % (cuaddr[7:].split('@')[0],)
             self.displayname = cuaddr[7:].split('@')[0].upper()
+            self.record = self.FakeRecord(cuaddr[7:].split('@')[0], cuaddr)
 
         def calendarHome(self, request):
             class FakeHome(object):
@@ -50,25 +56,26 @@
             return self.displayname
             
 
+    @inlineCallbacks
     def setUp(self):
-        super(SharingTests, self).setUp()
-        config.Sharing.Enabled = True
-        config.Sharing.Calendars.Enabled = True
+        yield super(SharingTests, self).setUp()
 
-        collection = self.mktemp()
-        os.mkdir(collection)
-        self.resource = CalDAVFile(collection, self.site.resource)
-        self.resource._dead_properties = InMemoryPropertyStore()
-        self.resource.writeDeadProperty(davxml.ResourceType.calendar)
+        self.patch(config.Sharing, "Enabled", True)
+        self.patch(config.Sharing.Calendars, "Enabled", True)
+
+        self.resource = (
+            yield self.site.resource.locateChild(norequest(), ["calendar"])
+        )[0]
         self.site.resource.putChild("calendar", self.resource)
-        
-        self.resource.validUserIDForShare = self._fakeValidUserID
-        self.resource.validUserIDWithCommonNameForShare = self._fakeValidUserID
-        self.resource.sendInvite = lambda record, request:succeed(True)
-        self.resource.removeInvite = lambda record, request:succeed(True)
-        
-        self.resource.principalForCalendarUserAddress = lambda cuaddr: SharingTests.FakePrincipal(cuaddr)
-        
+
+        CalDAVResource.validUserIDForShare = self._fakeValidUserID
+        CalDAVResource.validUserIDWithCommonNameForShare = self._fakeValidUserID
+        CalDAVResource.sendInvite = lambda self, record, request: succeed(True)
+        CalDAVResource.removeInvite = lambda self, record, request: succeed(True)
+
+        CalDAVResource.principalForCalendarUserAddress = lambda self, cuaddr: SharingTests.FakePrincipal(cuaddr)
+
+
     def _fakeValidUserID(self, userid, *args):
         if userid.startswith("/principals/"):
             return userid
@@ -103,42 +110,42 @@
     def test_upgradeToShareOnCreate(self):
         request = SimpleRequest(self.site, "MKCOL", "/calendar/")
 
-        rtype = (yield self.resource.resourceType(request))
+        rtype = self.resource.resourceType()
         self.assertEquals(rtype, davxml.ResourceType.calendar)
         propInvite = (yield self.resource.readProperty(customxml.Invite, request))
         self.assertEquals(propInvite, None)
 
-        yield self.resource.upgradeToShare(request)
+        self.resource.upgradeToShare()
 
-        rtype = (yield self.resource.resourceType(request))
+        rtype = self.resource.resourceType()
         self.assertEquals(rtype, davxml.ResourceType.sharedownercalendar)
         propInvite = (yield self.resource.readProperty(customxml.Invite, request))
         self.assertEquals(propInvite, customxml.Invite())
         
         isShared = (yield self.resource.isShared(request))
         self.assertTrue(isShared)
-        isVShared = (yield self.resource.isVirtualShare(request))
+        isVShared = self.resource.isVirtualShare()
         self.assertFalse(isVShared)
 
     @inlineCallbacks
     def test_upgradeToShareAfterCreate(self):
         request = SimpleRequest(self.site, "PROPPATCH", "/calendar/")
 
-        rtype = (yield self.resource.resourceType(request))
+        rtype = self.resource.resourceType()
         self.assertEquals(rtype, davxml.ResourceType.calendar)
         propInvite = (yield self.resource.readProperty(customxml.Invite, request))
         self.assertEquals(propInvite, None)
 
-        yield self.resource.upgradeToShare(request)
+        self.resource.upgradeToShare()
 
-        rtype = (yield self.resource.resourceType(request))
+        rtype = self.resource.resourceType()
         self.assertEquals(rtype, davxml.ResourceType.sharedownercalendar)
         propInvite = (yield self.resource.readProperty(customxml.Invite, request))
         self.assertEquals(propInvite, customxml.Invite())
         
         isShared = (yield self.resource.isShared(request))
         self.assertTrue(isShared)
-        isVShared = (yield self.resource.isVirtualShare(request))
+        isVShared = self.resource.isVirtualShare()
         self.assertFalse(isVShared)
 
     @inlineCallbacks
@@ -147,27 +154,27 @@
 
         self.resource.writeDeadProperty(davxml.ResourceType.sharedownercalendar)
         self.resource.writeDeadProperty(customxml.Invite())
-        rtype = (yield self.resource.resourceType(request))
+        rtype = self.resource.resourceType()
         self.assertEquals(rtype, davxml.ResourceType.sharedownercalendar)
         propInvite = (yield self.resource.readProperty(customxml.Invite, None))
         self.assertEquals(propInvite, customxml.Invite())
 
         yield self.resource.downgradeFromShare(None)
 
-        rtype = (yield self.resource.resourceType(request))
+        rtype = self.resource.resourceType()
         self.assertEquals(rtype, davxml.ResourceType.calendar)
         propInvite = (yield self.resource.readProperty(customxml.Invite, None))
         self.assertEquals(propInvite, None)
         
         isShared = (yield self.resource.isShared(None))
         self.assertFalse(isShared)
-        isVShared = (yield self.resource.isVirtualShare(None))
+        isVShared = self.resource.isVirtualShare()
         self.assertFalse(isVShared)
 
     @inlineCallbacks
     def test_POSTaddInviteeAlreadyShared(self):
         
-        yield self.resource.upgradeToShare(SimpleRequest(self.site, "MKCOL", "/calendar/"))
+        self.resource.upgradeToShare()
 
         yield self._doPOST("""<?xml version="1.0" encoding="utf-8" ?>
 <CS:share xmlns:D="DAV:" xmlns:CS="http://calendarserver.org/ns/">
@@ -192,7 +199,7 @@
         
         isShared = (yield self.resource.isShared(None))
         self.assertTrue(isShared)
-        isVShared = (yield self.resource.isVirtualShare(None))
+        isVShared = self.resource.isVirtualShare()
         self.assertFalse(isVShared)
 
     @inlineCallbacks
@@ -222,13 +229,13 @@
         
         isShared = (yield self.resource.isShared(None))
         self.assertTrue(isShared)
-        isVShared = (yield self.resource.isVirtualShare(None))
+        isVShared = (yield self.resource.isVirtualShare())
         self.assertFalse(isVShared)
 
     @inlineCallbacks
     def test_POSTupdateInvitee(self):
         
-        yield self.resource.upgradeToShare(SimpleRequest(self.site, "MKCOL", "/calendar/"))
+        self.resource.upgradeToShare()
 
         yield self._doPOST("""<?xml version="1.0" encoding="utf-8" ?>
 <CS:share xmlns:D="DAV:" xmlns:CS="http://calendarserver.org/ns/">
@@ -264,7 +271,7 @@
     @inlineCallbacks
     def test_POSTremoveInvitee(self):
         
-        yield self.resource.upgradeToShare(SimpleRequest(self.site, "MKCOL", "/calendar/"))
+        self.resource.upgradeToShare()
 
         yield self._doPOST("""<?xml version="1.0" encoding="utf-8" ?>
 <CS:share xmlns:D="DAV:" xmlns:CS="http://calendarserver.org/ns/">
@@ -290,7 +297,7 @@
     @inlineCallbacks
     def test_POSTaddMoreInvitees(self):
         
-        yield self.resource.upgradeToShare(SimpleRequest(self.site, "MKCOL", "/calendar/"))
+        self.resource.upgradeToShare()
 
         yield self._doPOST("""<?xml version="1.0" encoding="utf-8" ?>
 <CS:share xmlns:D="DAV:" xmlns:CS="http://calendarserver.org/ns/">
@@ -344,7 +351,7 @@
     @inlineCallbacks
     def test_POSTaddRemoveInvitees(self):
         
-        yield self.resource.upgradeToShare(SimpleRequest(self.site, "MKCOL", "/calendar/"))
+        self.resource.upgradeToShare()
 
         yield self._doPOST("""<?xml version="1.0" encoding="utf-8" ?>
 <CS:share xmlns:D="DAV:" xmlns:CS="http://calendarserver.org/ns/">
@@ -394,7 +401,7 @@
     @inlineCallbacks
     def test_POSTaddRemoveSameInvitee(self):
         
-        yield self.resource.upgradeToShare(SimpleRequest(self.site, "MKCOL", "/calendar/"))
+        self.resource.upgradeToShare()
 
         yield self._doPOST("""<?xml version="1.0" encoding="utf-8" ?>
 <CS:share xmlns:D="DAV:" xmlns:CS="http://calendarserver.org/ns/">
@@ -444,7 +451,7 @@
     @inlineCallbacks
     def test_POSTaddInvalidInvitee(self):
         
-        yield self.resource.upgradeToShare(SimpleRequest(self.site, "MKCOL", "/calendar/"))
+        self.resource.upgradeToShare()
 
         response = (yield self._doPOST("""<?xml version="1.0" encoding="utf-8" ?>
 <CS:share xmlns:D="DAV:" xmlns:CS="http://calendarserver.org/ns/">
@@ -475,7 +482,7 @@
     @inlineCallbacks
     def test_POSTremoveInvalidInvitee(self):
         
-        yield self.resource.upgradeToShare(SimpleRequest(self.site, "MKCOL", "/calendar/"))
+        self.resource.upgradeToShare()
 
         yield self._doPOST("""<?xml version="1.0" encoding="utf-8" ?>
 <CS:share xmlns:D="DAV:" xmlns:CS="http://calendarserver.org/ns/">

Modified: CalendarServer/trunk/twistedcaldav/test/test_sql.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_sql.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/twistedcaldav/test/test_sql.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -173,19 +173,6 @@
         self.assertFalse(hasattr(db, "_db_connection"))
         db._db_close()
         
-    def test_ignore_db_files(self):
-        """
-        Make sure database files are not listed as children.
-        """
-        colpath = self.site.resource.fp.path
-        fd = open(os.path.join(colpath, db_prefix + "sqlite"), "w")
-        fd.close()
-        fd = open(os.path.join(colpath, "test"), "w")
-        fd.close()
-        children = self.site.resource.listChildren()
-        self.assertTrue("test" in children)
-        self.assertFalse(db_prefix + "sqlite" in children)
-
     def test_duplicate_create(self):
         dbname = self.mktemp()
         

Modified: CalendarServer/trunk/twistedcaldav/test/test_validation.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_validation.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/twistedcaldav/test/test_validation.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -24,11 +24,11 @@
 from twext.web2.test.test_server import SimpleRequest
 from twext.web2.http import HTTPError
 
-from twistedcaldav.static import CalDAVFile
 from twistedcaldav.config import config
 from twistedcaldav.ical import Component, Property
 from twistedcaldav.method.put_common import StoreCalendarObjectResource
 from twistedcaldav.caldavxml import MaxAttendeesPerInstance
+from twistedcaldav.resource import CalDAVResource
 
 class TestCopyMoveValidation(TestCase):
     """
@@ -39,14 +39,11 @@
         """
         Set up some CalDAV stuff.
         """
-        
-        class CalDAVFileWithName(CalDAVFile):
-            
-            def name(self):
-                return self.fp.basename()
 
-        self.destination = CalDAVFileWithName(self.mktemp())
-        self.destinationParent = CalDAVFileWithName(self.mktemp())
+        self.destination = CalDAVResource()
+        self.destination.name = lambda : '1'
+        self.destinationParent = CalDAVResource()
+        self.destinationParent.name = lambda : '2'
         self.sampleCalendar = Component.fromString("""
 BEGIN:VCALENDAR
 VERSION:2.0

Modified: CalendarServer/trunk/twistedcaldav/test/test_vcardindex.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_vcardindex.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/twistedcaldav/test/test_vcardindex.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -24,6 +24,30 @@
 
 import os
 
+class MinimalResourceReplacement(object):
+    """
+    Provide the minimal set of attributes and methods from CalDAVFile required
+    by L{Index}.
+    """
+
+    def __init__(self, filePath):
+        self.fp = filePath
+
+
+    def isAddressBookCollection(self):
+        return True
+
+
+    def getChild(self, name):
+        # FIXME: this should really return something with a child method
+        return self.fp.child(name)
+
+
+    def initSyncToken(self):
+        pass
+
+
+
 class SQLIndexTests (twistedcaldav.test.util.TestCase):
     """
     Test abstract SQL DB class
@@ -32,7 +56,10 @@
     def setUp(self):
         super(SQLIndexTests, self).setUp()
         self.site.resource.isAddressBookCollection = lambda: True
-        self.db = AddressBookIndex(self.site.resource)
+        self.indexDirPath = self.site.resource.fp
+        # FIXME: since this resource lies about isCalendarCollection, it doesn't
+        # have all the associated backend machinery to actually get children.
+        self.db = AddressBookIndex(MinimalResourceReplacement(self.indexDirPath))
 
 
     def test_reserve_uid_ok(self):

Modified: CalendarServer/trunk/twistedcaldav/test/test_wrapping.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_wrapping.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/twistedcaldav/test/test_wrapping.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -18,8 +18,9 @@
 Tests for the interaction between model-level and protocol-level logic.
 """
 
+
 from twext.web2.server import Request
-from twext.web2.responsecode import OK, UNAUTHORIZED
+from twext.web2.responsecode import UNAUTHORIZED
 from twext.web2.http_headers import Headers
 from txdav.idav import AlreadyFinishedError
 
@@ -31,8 +32,8 @@
 from twistedcaldav.ical import Component as VComponent
 from twistedcaldav.vcard import Component as VCComponent
 
-from twistedcaldav.storebridge import ProtoCalendarCollectionFile, \
-    ProtoAddressBookCollectionFile, DropboxCollection
+from twistedcaldav.storebridge import ProtoCalendarCollectionResource, \
+    ProtoAddressBookCollectionResource, DropboxCollection
 
 from twistedcaldav.test.util import TestCase
 
@@ -219,25 +220,22 @@
 
     def test_createStore(self):
         """
-        Creating a CalendarHomeProvisioningFile will create a paired
+        Creating a DirectoryCalendarHomeProvisioningResource will create a paired
         CalendarStore.
         """
         self.assertIsInstance(self.calendarCollection._newStore, CalendarStore)
-        self.assertEquals(self.calendarCollection._newStore._path,
-                          self.site.resource.fp)
 
 
     @inlineCallbacks
     def test_lookupCalendarHome(self):
         """
         When a L{CalDAVFile} representing an existing calendar home is looked
-        up in a CalendarHomeFile, it will create a corresponding
+        up in a CalendarHomeResource, it will create a corresponding
         L{CalendarHome} via C{newTransaction().calendarHomeWithUID}.
         """
         calDavFile = yield self.getResource("calendars/users/wsanchez/")
         self.commit()
-        self.assertEquals(calDavFile.fp, calDavFile._newStoreCalendarHome._path)
-        self.assertIsInstance(calDavFile._newStoreCalendarHome, CalendarHome)
+        self.assertIsInstance(calDavFile._newStoreHome, CalendarHome)
 
 
     @inlineCallbacks
@@ -253,7 +251,7 @@
         )
         self.commit()
         self.assertIsInstance(dropBoxResource, DropboxCollection)
-        self.assertEquals((yield dropBoxResource.resourceType(None)),
+        self.assertEquals(dropBoxResource.resourceType(),
                           davxml.ResourceType.dropboxhome)
 
 
@@ -261,14 +259,13 @@
     def test_lookupExistingCalendar(self):
         """
         When a L{CalDAVFile} representing an existing calendar collection is
-        looked up in a L{CalendarHomeFile} representing a calendar home, it
+        looked up in a L{CalendarHomeResource} representing a calendar home, it
         will create a corresponding L{Calendar} via
         C{CalendarHome.calendarWithName}.
         """
         calDavFile = yield self.getResource("calendars/users/wsanchez/calendar")
         self.commit()
-        self.assertEquals(calDavFile.fp, calDavFile._newStoreCalendar._path)
-        self.assertEquals((yield calDavFile.resourceType(None)),
+        self.assertEquals(calDavFile.resourceType(),
                           davxml.ResourceType.calendar)
 
 
@@ -276,16 +273,15 @@
     def test_lookupNewCalendar(self):
         """
         When a L{CalDAVFile} which represents a not-yet-created calendar
-        collection is looked up in a L{CalendarHomeFile} representing a calendar
+        collection is looked up in a L{CalendarHomeResource} representing a calendar
         home, it will initially have a new storage backend set to C{None}, but
         when the calendar is created via a protocol action, the backend will be
         initialized to match.
         """
         calDavFile = yield self.getResource("calendars/users/wsanchez/frobozz")
-        self.assertIsInstance(calDavFile, ProtoCalendarCollectionFile)
+        self.assertIsInstance(calDavFile, ProtoCalendarCollectionResource)
         calDavFile.createCalendarCollection()
         self.commit()
-        self.assertEquals(calDavFile.fp, calDavFile._newStoreCalendar._path)
 
 
     @inlineCallbacks
@@ -293,7 +289,7 @@
         """
         When a L{CalDAVFile} I{not} representing a calendar collection - one of
         the special collections, like the dropbox or freebusy URLs - is looked
-        up in a L{CalendarHomeFile} representing a calendar home, it will I{not}
+        up in a L{CalendarHomeResource} representing a calendar home, it will I{not}
         create a corresponding L{Calendar} via C{CalendarHome.calendarWithName}.
         """
         for specialName in ['dropbox', 'freebusy', 'notifications']:
@@ -318,8 +314,6 @@
             "calendars/users/wsanchez/calendar/1.ics"
         )
         self.commit()
-        self.assertEquals(calDavFileCalendar._newStoreObject._path,
-                          calDavFileCalendar.fp)
         self.assertEquals(calDavFileCalendar._principalCollections,
                           frozenset([self.principalsResource]))
 
@@ -345,8 +339,6 @@
         AddressBookStore.
         """
         self.assertIsInstance(self.addressbookCollection._newStore, AddressBookStore)
-        self.assertEquals(self.addressbookCollection._newStore._path,
-                          self.site.resource.fp)
 
 
     @inlineCallbacks
@@ -358,8 +350,7 @@
         """
         calDavFile = yield self.getResource("addressbooks/users/wsanchez/")
         self.commit()
-        self.assertEquals(calDavFile.fp, calDavFile._newStoreAddressBookHome._path)
-        self.assertIsInstance(calDavFile._newStoreAddressBookHome, AddressBookHome)
+        self.assertIsInstance(calDavFile._newStoreHome, AddressBookHome)
 
 
     @inlineCallbacks
@@ -371,7 +362,8 @@
         """
         calDavFile = yield self.getResource("addressbooks/users/wsanchez/addressbook")
         self.commit()
-        self.assertEquals(calDavFile.fp, calDavFile._newStoreAddressBook._path)
+        self.assertEquals(calDavFile._principalCollections,
+                          frozenset([self.principalsResource]))
 
 
     @inlineCallbacks
@@ -384,10 +376,11 @@
         initialized to match.
         """
         calDavFile = yield self.getResource("addressbooks/users/wsanchez/frobozz")
-        self.assertIsInstance(calDavFile, ProtoAddressBookCollectionFile)
+        self.assertIsInstance(calDavFile, ProtoAddressBookCollectionResource)
         calDavFile.createAddressBookCollection()
         self.commit()
-        self.assertEquals(calDavFile.fp, calDavFile._newStoreAddressBook._path)
+        self.assertEquals(calDavFile._principalCollections,
+                          frozenset([self.principalsResource]))
 
 
     @inlineCallbacks
@@ -402,8 +395,6 @@
             "addressbooks/users/wsanchez/addressbook/1.vcf"
         )
         self.commit()
-        self.assertEquals(calDavFileAddressBook._newStoreObject._path,
-                          calDavFileAddressBook.fp)
         self.assertEquals(calDavFileAddressBook._principalCollections,
                           frozenset([self.principalsResource]))
 

Modified: CalendarServer/trunk/twistedcaldav/test/util.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/util.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/twistedcaldav/test/util.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -15,6 +15,7 @@
 ##
 
 from __future__ import with_statement
+from calendarserver.provision.root import RootResource
 
 __all__ = [
     "featureUnimplemented",
@@ -33,19 +34,20 @@
 from twisted.internet.protocol import ProcessProtocol
 
 from twext.python.memcacheclient import ClientFactory
-from twext.python.filepath import CachingFilePath as FilePath
+from twext.python.filepath import CachingFilePath as FilePath, CachingFilePath
 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.bind import doBind
 from twistedcaldav.config import config
-from twistedcaldav.static import CalDAVFile, CalendarHomeProvisioningFile,\
-    AddressBookHomeProvisioningFile
-from twistedcaldav.directory.xmlfile import XMLDirectoryService
 from twistedcaldav.directory import augment
+from twistedcaldav.directory.addressbook import DirectoryAddressBookHomeProvisioningResource
+from twistedcaldav.directory.calendar import DirectoryCalendarHomeProvisioningResource
 from twistedcaldav.directory.principal import (
     DirectoryPrincipalProvisioningResource)
+from twistedcaldav.directory.xmlfile import XMLDirectoryService
 
 from txdav.common.datastore.file import CommonDataStore
 
@@ -66,7 +68,7 @@
 proxiesFile = dirTest.child("proxies.xml")
 
 class TestCase(twext.web2.dav.test.util.TestCase):
-    resource_class = CalDAVFile
+    resource_class = RootResource
 
     def createStockDirectoryService(self):
         """
@@ -96,28 +98,21 @@
 
     def setupCalendars(self):
         """
-        Set up the resource at /calendars (a L{CalendarHomeProvisioningFile}),
+        Set up the resource at /calendars (a L{DirectoryCalendarHomeProvisioningResource}),
         and assign it as C{self.calendarCollection}.
         """
-        path = self.site.resource.fp.child("calendars")
-        path.createDirectory()
 
         # Need a data store
-        _newStore = CommonDataStore(self.site.resource.fp, None, True, False)
+        _newStore = CommonDataStore(CachingFilePath(config.DocumentRoot), None, True, False)
 
-        self.calendarCollection = CalendarHomeProvisioningFile(
-            path,
+        self.calendarCollection = DirectoryCalendarHomeProvisioningResource(
             self.directoryService,
             "/calendars/",
             _newStore
         )
         self.site.resource.putChild("calendars", self.calendarCollection)
 
-        path = self.site.resource.fp.child("addressbooks")
-        path.createDirectory()
-
-        self.addressbookCollection = AddressBookHomeProvisioningFile(
-            path,
+        self.addressbookCollection = DirectoryAddressBookHomeProvisioningResource(
             self.directoryService,
             "/addressbooks/",
             _newStore
@@ -128,11 +123,16 @@
     def setUp(self):
         super(TestCase, self).setUp()
 
+        # FIXME: this is only here to workaround circular imports
+        doBind()
+
         config.reset()
         serverroot = self.mktemp()
         os.mkdir(serverroot)
-        config.ServerRoot = serverroot
+        config.ServerRoot = os.path.abspath(serverroot)
         config.ConfigRoot = "config"
+        config.LogRoot = "logs"
+        config.RunRoot = "logs"
         
         if not os.path.exists(config.DataRoot):
             os.makedirs(config.DataRoot)
@@ -140,6 +140,8 @@
             os.makedirs(config.DocumentRoot)
         if not os.path.exists(config.ConfigRoot):
             os.makedirs(config.ConfigRoot)
+        if not os.path.exists(config.LogRoot):
+            os.makedirs(config.LogRoot)
 
         config.Memcached.Pools.Default.ClientEnabled = False
         config.Memcached.Pools.Default.ServerEnabled = False
@@ -296,30 +298,32 @@
     def setUp(self):
         """
         Replace self.site.resource with an appropriately provisioned
-        CalendarHomeFile, and replace self.docroot with a path pointing at that
+        CalendarHomeResource, and replace self.docroot with a path pointing at that
         file.
         """
         super(HomeTestCase, self).setUp()
 
         fp = FilePath(self.mktemp())
+        fp.createDirectory()
 
         self.createStockDirectoryService()
 
         # Need a data store
         _newStore = CommonDataStore(fp, None, True, False)
 
-        self.homeProvisioner = CalendarHomeProvisioningFile(
-            os.path.join(fp.path, "calendars"),
+        self.homeProvisioner = DirectoryCalendarHomeProvisioningResource(
             self.directoryService, "/calendars/",
             _newStore
         )
         
-        def _defer(_):
+        def _defer(user):
             # Commit the transaction
             self.site.resource._associatedTransaction.commit()
-            
+            self.docroot = user._newStoreHome._path.path
+
         return self._refreshRoot().addCallback(_defer)
 
+
     @inlineCallbacks
     def _refreshRoot(self):
         """
@@ -338,10 +342,8 @@
         # 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
+        returnValue(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):
@@ -368,21 +370,22 @@
         super(AddressBookHomeTestCase, self).setUp()
 
         fp = FilePath(self.mktemp())
+        fp.createDirectory()
 
         self.createStockDirectoryService()
 
         # Need a data store
         _newStore = CommonDataStore(fp, None, True, False)
 
-        self.homeProvisioner = AddressBookHomeProvisioningFile(
-            os.path.join(fp.path, "addressbooks"),
+        self.homeProvisioner = DirectoryAddressBookHomeProvisioningResource(
             self.directoryService, "/addressbooks/",
             _newStore
         )
         
-        def _defer(_):
+        def _defer(user):
             # Commit the transaction
             self.site.resource._associatedTransaction.commit()
+            self.docroot = user._newStoreHome._path.path
             
         return self._refreshRoot().addCallback(_defer)
 
@@ -404,10 +407,8 @@
         # 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
+        returnValue(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):

Modified: CalendarServer/trunk/twistedcaldav/timezoneservice.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/timezoneservice.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/twistedcaldav/timezoneservice.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -26,6 +26,8 @@
 
 from twext.web2 import responsecode
 from twext.web2.dav import davxml
+from twext.web2.dav.method.propfind import http_PROPFIND
+from twext.web2.dav.noneprops import NonePropertyStore
 from twext.web2.http import HTTPError
 from twext.web2.http import Response
 from twext.web2.http import XMLResponse
@@ -36,14 +38,15 @@
 
 from twistedcaldav import customxml
 from twistedcaldav.customxml import calendarserver_namespace
+from twistedcaldav.extensions import DAVResource
 from twistedcaldav.ical import parse_date_or_datetime
 from twistedcaldav.ical import tzexpand
-from twistedcaldav.resource import CalDAVResource
+from twistedcaldav.resource import ReadOnlyNoCopyResourceMixIn
 from twistedcaldav.timezones import TimezoneException
 from twistedcaldav.timezones import listTZs
 from twistedcaldav.timezones import readTZ
 
-class TimezoneServiceResource (CalDAVResource):
+class TimezoneServiceResource (ReadOnlyNoCopyResourceMixIn, DAVResource):
     """
     Timezone Service resource.
 
@@ -56,11 +59,25 @@
         """
         assert parent is not None
 
-        CalDAVResource.__init__(self, principalCollections=parent.principalCollections())
+        DAVResource.__init__(self, principalCollections=parent.principalCollections())
 
         self.parent = parent
         self.cache = {}
 
+    def deadProperties(self):
+        if not hasattr(self, "_dead_properties"):
+            self._dead_properties = NonePropertyStore(self)
+        return self._dead_properties
+
+    def etag(self):
+        return None
+
+    def checkPreconditions(self, request):
+        return None
+
+    def checkPrivileges(self, request, privileges, recurse=False, principal=None, inherited_aces=None):
+        return succeed(None)
+
     def defaultAccessControlList(self):
         return davxml.ACL(
             # DAV:Read for all principals (includes anonymous)
@@ -73,8 +90,8 @@
             ),
         )
 
-    def resourceType(self, request):
-        return succeed(davxml.ResourceType.timezones)
+    def resourceType(self):
+        return davxml.ResourceType.timezones
 
     def isCollection(self):
         return False
@@ -99,6 +116,8 @@
         response.headers.setHeader("content-type", MimeType("text", "html"))
         return response
 
+    http_PROPFIND = http_PROPFIND
+
     def http_GET(self, request):
         """
         The timezone service POST method.

Modified: CalendarServer/trunk/twistedcaldav/vcardindex.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/vcardindex.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/twistedcaldav/vcardindex.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -221,7 +221,7 @@
 
     def __init__(self, resource):
         """
-        @param resource: the L{twistedcaldav.static.CalDAVFile} resource to
+        @param resource: the L{CalDAVResource} resource to
             index. C{resource} must be an addressbook collection (ie.
             C{resource.isAddressBookCollection()} returns C{True}.)
         """

Modified: CalendarServer/trunk/txcaldav/calendarstore/file.py
===================================================================
--- CalendarServer/trunk/txcaldav/calendarstore/file.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/txcaldav/calendarstore/file.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -56,7 +56,7 @@
     CommonHome, CommonHomeChild, CommonObjectResource
 from txdav.common.icommondatastore import InvalidObjectResourceError, \
     NoSuchObjectResourceError, InternalDataStoreError
-from txdav.datastore.file import writeOperation, hidden
+from txdav.datastore.file import writeOperation, hidden, FileMetaDataMixin
 from txdav.propertystore.base import PropertyName
 
 from zope.interface import implements
@@ -75,7 +75,7 @@
 
 
     def calendarWithName(self, name):
-        if name == 'dropbox':
+        if name in ('dropbox', 'notifications', 'freebusy'):
             # "dropbox" is a file storage area, not a calendar.
             return None
         else:
@@ -86,12 +86,24 @@
     removeCalendarWithName = CommonHome.removeChildWithName
 
     def calendars(self):
+        """
+        Return a generator of the child resource objects.
+        """
         for child in self.children():
             if child.name() in ('dropbox', 'notification'):
                 continue
             yield child
 
+    def listCalendars(self):
+        """
+        Return a generator of the child resource names.
+        """
+        for name in self.listChildren():
+            if name in ('dropbox', 'notification'):
+                continue
+            yield name
 
+
     def calendarObjectWithDropboxID(self, dropboxID):
         """
         Implement lookup with brute-force scanning.
@@ -157,6 +169,7 @@
 
     ownerCalendarHome = CommonHomeChild.ownerHome
     calendarObjects = CommonHomeChild.objectResources
+    listCalendarObjects = CommonHomeChild.listObjectResources
     calendarObjectWithName = CommonHomeChild.objectResourceWithName
     calendarObjectWithUID = CommonHomeChild.objectResourceWithUID
     createCalendarObjectWithName = CommonHomeChild.createObjectResourceWithName
@@ -270,7 +283,7 @@
     def component(self):
         if self._component is not None:
             return self._component
-        text = self.iCalendarText()
+        text = self.text()
 
         try:
             component = VComponent.fromString(text)
@@ -282,7 +295,7 @@
         return component
 
 
-    def iCalendarText(self):
+    def text(self):
         if self._component is not None:
             return str(self._component)
         try:
@@ -308,6 +321,7 @@
             )
         return text
 
+    iCalendarText = text
 
     def uid(self):
         if not hasattr(self, "_uid"):
@@ -373,12 +387,12 @@
         if attachProperty is not None:
             # Make sure the value type is URI
             valueType = attachProperty.params().get("VALUE", ("TEXT",))
-            if valueType[0] == "URI": 
+            if valueType[0] == "URI":
                 # FIXME: more aggressive checking to see if this URI is really the
                 # 'right' URI.  Maybe needs to happen in the front end.
                 attachPath = attachProperty.value().split("/")[-2]
                 return attachPath
-        
+
         return self.uid() + ".dropbox"
 
 
@@ -429,7 +443,7 @@
         """
         self._attachment = attachment
         self._contentType = contentType
-        self._file = self._attachment._computePath().open("w")
+        self._file = self._attachment._path.open("w")
 
 
     def write(self, data):
@@ -441,14 +455,14 @@
         # FIXME: do anything
         self._file.close()
 
-        md5 = hashlib.md5(self._attachment._computePath().getContent()).hexdigest()
-        props = self._attachment._properties()
+        md5 = hashlib.md5(self._attachment._path.getContent()).hexdigest()
+        props = self._attachment.properties()
         props[contentTypeKey] = GETContentType(generateContentType(self._contentType))
         props[md5key] = TwistedGETContentMD5.fromString(md5)
         props.flush()
 
 
-class Attachment(object):
+class Attachment(FileMetaDataMixin):
     """
     An L{Attachment} is a container for the data associated with a I{locally-
     stored} calendar attachment.  That is to say, there will only be
@@ -469,7 +483,7 @@
         return self._name
 
 
-    def _properties(self):
+    def properties(self):
         """
         Create and return a private xattr L{PropertyStore} for storing some of
         the data about this L{Attachment}.  This is private because attachments
@@ -480,14 +494,10 @@
         return PropertyStore(
             self._calendarObject._parentCollection._home.peruser_uid(),
             self._calendarObject._parentCollection._home.uid(),
-            self._computePath
+            lambda :self._path
         )
 
 
-    def contentType(self):
-        return self._properties()[contentTypeKey].mimeType()
-
-
     def store(self, contentType):
         return AttachmentStorageTransport(self, contentType)
 
@@ -495,16 +505,12 @@
         # FIXME: makeConnection
         # FIXME: actually stream
         # FIMXE: connectionLost
-        protocol.dataReceived(self._computePath().getContent())
+        protocol.dataReceived(self._path.getContent())
         # FIXME: ConnectionDone, not NotImplementedError
         protocol.connectionLost(Failure(NotImplementedError()))
 
-
-    def md5(self):
-        return self._properties()[md5key]
-
-
-    def _computePath(self):
+    @property
+    def _path(self):
         dropboxPath = self._calendarObject._dropboxPath()
         return dropboxPath.child(self.name())
 
@@ -517,11 +523,17 @@
 
     def __init__(self, calendar):
         self.calendar = calendar
-        self.fp = self.calendar._path
 
+
+    @property
+    def fp(self):
+        return self.calendar._path
+
+
     def isCalendarCollection(self):
         return True
 
+
     def getChild(self, name):
         calendarObject = self.calendar.calendarObjectWithName(name)
         if calendarObject:
@@ -536,6 +548,7 @@
         else:
             return None
 
+
     def bumpSyncToken(self, reset=False):
         # FIXME: needs direct tests
         return self.calendar._updateSyncToken(reset)
@@ -545,6 +558,8 @@
         # FIXME: needs direct tests
         self.bumpSyncToken(True)
 
+
+
 class Index(object):
     #
     # OK, here's where we get ugly.

Modified: CalendarServer/trunk/txcaldav/icalendarstore.py
===================================================================
--- CalendarServer/trunk/txcaldav/icalendarstore.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/txcaldav/icalendarstore.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -14,12 +14,14 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 ##
-from txdav.common.icommondatastore import ICommonTransaction
 
 """
 Calendar store interfaces
 """
 
+from txdav.common.icommondatastore import ICommonTransaction
+from txdav.idav import IDataStoreResource
+
 from zope.interface import Interface
 from txdav.idav import INotifier
 
@@ -64,7 +66,7 @@
 # Interfaces
 #
 
-class ICalendarHome(INotifier):
+class ICalendarHome(INotifier, IDataStoreResource):
     """
     An L{ICalendarHome} is a collection of calendars which belongs to a
     specific principal and contains the calendars which that principal has
@@ -131,17 +133,12 @@
 
         @param name: a string.
         @raise NoSuchCalendarObjectError: if no such calendar exists.
-        """
 
-    def properties():
-        """
-        Retrieve the property store for this calendar home.
-
         @return: an L{IPropertyStore}.
         """
 
 
-class ICalendar(INotifier):
+class ICalendar(INotifier, IDataStoreResource):
     """
     Calendar
 
@@ -151,15 +148,6 @@
     read/write access.
     """
 
-    def name():
-        """
-        Identify this calendar uniquely, as with
-        L{ICalendarHome.calendarWithName}.
-
-        @return: the name of this calendar.
-        @rtype: C{str}
-        """
-
     def rename(name):
         """
         Change the name of this calendar.
@@ -268,15 +256,8 @@
             that have been removed, and the current sync token.
         """
 
-    def properties():
-        """
-        Retrieve the property store for this calendar.
 
-        @return: an L{IPropertyStore}.
-        """
-
-
-class ICalendarObject(Interface):
+class ICalendarObject(IDataStoreResource):
     """
     Calendar object
 
@@ -335,14 +316,6 @@
         @return: a URI string.
         """
 
-    def properties():
-        """
-        Retrieve the property store for this calendar object.
-
-        @return: an L{IPropertyStore}.
-        """
-
-
     def dropboxID():
         """
         An identifier, unique to the calendar home, that specifies a location
@@ -403,35 +376,11 @@
 
 
 
-class IAttachment(Interface):
+class IAttachment(IDataStoreResource):
     """
     Information associated with an attachment to a calendar object.
     """
 
-    def name():
-        """
-        A short name, unique to this attachment's L{ICalendarObject}.
-
-        @rtype: C{str}
-        """
-
-
-    def contentType():
-        """
-        A slash-separated content type of the body of this attachment.
-
-        @rtype: C{str}
-        """
-
-
-    def md5():
-        """
-        The MD5 hex digest of this attachment's contents.
-
-        @rtype: C{str}
-        """
-
-
     def store(contentType):
         """
         @param contentType: The content type of the data which will be stored.

Modified: CalendarServer/trunk/txcarddav/addressbookstore/file.py
===================================================================
--- CalendarServer/trunk/txcarddav/addressbookstore/file.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/txcarddav/addressbookstore/file.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -64,6 +64,7 @@
         self._childClass = AddressBook
 
     addressbooks = CommonHome.children
+    listAddressbooks = CommonHome.listChildren
     addressbookWithName = CommonHome.childWithName
     createAddressBookWithName = CommonHome.createChildWithName
     removeAddressBookWithName = CommonHome.removeChildWithName
@@ -113,6 +114,7 @@
 
     ownerAddressBookHome = CommonHomeChild.ownerHome
     addressbookObjects = CommonHomeChild.objectResources
+    listAddressbookObjects = CommonHomeChild.listObjectResources
     addressbookObjectWithName = CommonHomeChild.objectResourceWithName
     addressbookObjectWithUID = CommonHomeChild.objectResourceWithUID
     createAddressBookObjectWithName = CommonHomeChild.createObjectResourceWithName
@@ -213,7 +215,7 @@
     def component(self):
         if self._component is not None:
             return self._component
-        text = self.vCardText()
+        text = self.text()
 
         try:
             component = VComponent.fromString(text)
@@ -225,7 +227,7 @@
         return component
 
 
-    def vCardText(self):
+    def text(self):
         if self._component is not None:
             return str(self._component)
         try:
@@ -251,6 +253,7 @@
             )
         return text
 
+    vCardText = text
 
     def uid(self):
         if not hasattr(self, "_uid"):

Modified: CalendarServer/trunk/txcarddav/iaddressbookstore.py
===================================================================
--- CalendarServer/trunk/txcarddav/iaddressbookstore.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/txcarddav/iaddressbookstore.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -18,9 +18,9 @@
 Address book store interfaces
 """
 
-from zope.interface import Interface
 from txdav.common.icommondatastore import ICommonTransaction
 from txdav.idav import INotifier
+from txdav.idav import IDataStoreResource
 
 __all__ = [
     # Classes
@@ -52,7 +52,7 @@
 # Interfaces
 #
 
-class IAddressBookHome(INotifier):
+class IAddressBookHome(INotifier, IDataStoreResource):
     """
     AddressBook home
 
@@ -107,15 +107,8 @@
         @raise NoSuchAddressBookObjectError: if no such addressbook exists.
         """
 
-    def properties():
-        """
-        Retrieve the property store for this addressbook home.
 
-        @return: an L{IPropertyStore}.
-        """
-
-
-class IAddressBook(INotifier):
+class IAddressBook(INotifier, IDataStoreResource):
     """
     AddressBook
 
@@ -125,15 +118,6 @@
     read/write access.
     """
 
-    def name():
-        """
-        Identify this addressbook uniquely, as with
-        L{IAddressBookHome.addressbookWithName}.
-
-        @return: the name of this addressbook.
-        @rtype: C{str}
-        """
-
     def rename(name):
         """
         Change the name of this addressbook.
@@ -230,15 +214,8 @@
             that have been removed, and the current sync token.
         """
 
-    def properties():
-        """
-        Retrieve the property store for this addressbook.
 
-        @return: an L{IPropertyStore}.
-        """
-
-
-class IAddressBookObject(Interface):
+class IAddressBookObject(IDataStoreResource):
     """
     AddressBook object
 
@@ -277,10 +254,3 @@
 
         @return: a string containing a UID.
         """
-
-    def properties():
-        """
-        Retrieve the property store for this addressbook object.
-
-        @return: an L{IPropertyStore}.
-        """

Modified: CalendarServer/trunk/txdav/common/datastore/__init__.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/__init__.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/txdav/common/datastore/__init__.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -1,5 +1,5 @@
 ##
-# Copyright (c) 2005-2007 Apple Inc. All rights reserved.
+# 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.
@@ -14,3 +14,6 @@
 # limitations under the License.
 ##
 
+"""
+xxxDAV protocol data store for Twisted.
+"""

Modified: CalendarServer/trunk/txdav/common/datastore/file.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/file.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/txdav/common/datastore/file.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -38,7 +38,7 @@
 from txdav.common.inotifications import INotificationCollection, \
     INotificationObject
 from txdav.datastore.file import DataStoreTransaction, DataStore, writeOperation, \
-    hidden, isValidName, cached
+    hidden, isValidName, cached, FileMetaDataMixin
 from txdav.idav import IDataStore
 from txdav.propertystore.base import PropertyName
 from txdav.propertystore.xattr import PropertyStore
@@ -178,13 +178,9 @@
                 homePath = childPath.temporarySibling()
                 createDirectory(homePath)
                 def do():
-                    def lastly():
-                        homePath.moveTo(childPath)
-                        # home._path = homePath
-                        # do this _after_ all other file operations
-                        home._path = childPath
-                        return lambda : None
-                    self.addOperation(lastly, "create home finalize")
+                    homePath.moveTo(childPath)
+                    # do this _after_ all other file operations
+                    home._path = childPath
                     return lambda : None
                 self.addOperation(do, "create home UID %r" % (uid,))
 
@@ -215,58 +211,67 @@
             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)
+        if (uid, self) in self._notifications:
+            return self._notifications[(uid, self)]
 
-        notifications = NotificationCollection(notificationPath.basename(), home)
+        notificationCollectionName = "notification"
+        if not home._path.child(notificationCollectionName).isdir():
+            notifications = self._createNotificationCollection(home, notificationCollectionName)
+        else:
+            notifications = NotificationCollection(notificationCollectionName, home)
         self._notifications[(uid, self)] = notifications
         return notifications
 
-    def createNotifcationCollection(self, home, notificationPath):
 
-        if notificationPath.isdir():
-            return notificationPath
-
-        name = notificationPath.basename()
-
-        temporary = hidden(notificationPath.temporarySibling())
+    def _createNotificationCollection(self, home, collectionName):
+        # FIXME: this is a near-copy of CommonHome.createChildWithName.
+        temporary = hidden(home._path.child(collectionName).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_.
+        temporaryName = temporary.basename()
 
-        # FIXME: some way to roll this back.
+        c = NotificationCollection(temporary.basename(), home)
 
-        c = NotificationCollection(temporary.basename(), home)
         def do():
+            childPath = home._path.child(collectionName)
+            temporary = childPath.sibling(temporaryName)
             try:
                 props = c.properties()
-                temporary.moveTo(notificationPath)
-                c._name = name
+                temporary.moveTo(childPath)
+                c._name = collectionName
                 # FIXME: _lots_ of duplication of work here.
                 props.flush()
             except (IOError, OSError), e:
-                if e.errno == EEXIST and notificationPath.isdir():
-                    raise HomeChildNameAlreadyExistsError(name)
+                if e.errno == EEXIST and childPath.isdir():
+                    raise HomeChildNameAlreadyExistsError(collectionName)
                 raise
             # FIXME: direct tests, undo for index creation
             # Return undo
-            return lambda: notificationPath.remove()
+            return lambda: home._path.child(collectionName).remove()
 
-        self.addOperation(do, "create child %r" % (name,))
+        self.addOperation(do, "create notification child %r" %
+                          (collectionName,))
         props = c.properties()
         props[PropertyName(*ResourceType.qname())] = c.resourceType()
-        return temporary
+        return c
 
+
+
 class StubResource(object):
     """
     Just enough resource to keep the shared sql DB classes going.
     """
-    def __init__(self, stubit):
-        self.fp = stubit._path
+    def __init__(self, commonHome):
+        self._commonHome = commonHome
 
-class CommonHome(LoggingMixIn):
 
+    @property
+    def fp(self):
+        return self._commonHome._path
+
+
+
+class CommonHome(FileMetaDataMixin, LoggingMixIn):
+
     _childClass = None
 
     def __init__(self, uid, path, dataStore, transaction, notifier):
@@ -288,9 +293,11 @@
     def uid(self):
         return self._uid
 
+
     def peruser_uid(self):
         return self._peruser_uid
 
+
     def _updateSyncToken(self, reset=False):
         "Stub for updating sync token."
         # FIXME: actually update something
@@ -302,13 +309,31 @@
         """
         return self._shares
 
+
     def children(self):
+        """
+        Return a set of the child resource objects.
+        """
         return set(self._newChildren.itervalues()) | set(
             self.childWithName(name)
             for name in self._path.listdir()
             if not name.startswith(".")
         )
 
+
+    def listChildren(self):
+        """
+        Return a set of the names of the child resources.
+        """
+        return sorted(set(
+            [child.name() for child in self._newChildren.itervalues()]
+        ) | set(
+            name
+            for name in self._path.listdir()
+            if not name.startswith(".")
+        ))
+
+
     def childWithName(self, name):
         child = self._newChildren.get(name)
         if child is not None:
@@ -346,6 +371,7 @@
             raise HomeChildNameAlreadyExistsError(name)
 
         temporary = hidden(childPath.temporarySibling())
+        temporaryName = temporary.basename()
         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_.
@@ -359,6 +385,8 @@
         c = self._newChildren[name] = self._childClass(temporary.basename(), self, notifier, realName=name)
         c.retrieveOldIndex().create()
         def do():
+            childPath = self._path.child(name)
+            temporary = childPath.sibling(temporaryName)
             try:
                 props = c.properties()
                 temporary.moveTo(childPath)
@@ -371,7 +399,7 @@
                 raise
             # FIXME: direct tests, undo for index creation
             # Return undo
-            return lambda: childPath.remove()
+            return lambda: self._path.child(childPath.basename()).remove()
 
         self._transaction.addOperation(do, "create child %r" % (name,))
         if self._notifier:
@@ -380,9 +408,11 @@
         props[PropertyName(*ResourceType.qname())] = c.resourceType()
         self.createdChild(c)
 
+
     def createdChild(self, child):
         pass
 
+
     @writeOperation
     def removeChildWithName(self, name):
         if name.startswith(".") or name in self._removedChildren:
@@ -428,7 +458,6 @@
         if self._notifier:
             self._transaction.postCommit(self._notifier.notify)
 
-
     # @cached
     def properties(self):
         # FIXME: needs tests for actual functionality
@@ -445,7 +474,8 @@
         else:
             return None
 
-class CommonHomeChild(LoggingMixIn, FancyEqMixin):
+
+class CommonHomeChild(FileMetaDataMixin, LoggingMixIn, FancyEqMixin):
     """
     """
 
@@ -538,6 +568,9 @@
         self.properties().setPerUserUID(uid)
 
     def objectResources(self):
+        """
+        Return a list of object resource objects.
+        """
         return sorted((
             self.objectResourceWithName(name)
             for name in (
@@ -550,6 +583,21 @@
         )
 
 
+    def listObjectResources(self):
+        """
+        Return a list of object resource names.
+        """
+        return sorted((
+            name
+            for name in (
+                set(self._newObjectResources.iterkeys()) |
+                set(name for name in self._path.listdir()
+                    if not name.startswith(".")) -
+                set(self._removedObjectResources)
+            ))
+        )
+
+
     def objectResourceWithName(self, name):
         if name in self._removedObjectResources:
             return None
@@ -631,7 +679,7 @@
 
 
     def _updateSyncToken(self, reset=False):
-        # FIXME: add locking a-la CalDAVFile.bumpSyncToken
+        # FIXME: add locking a-la CalDAVResource.bumpSyncToken
         # FIXME: tests for desired concurrency properties
         ctag = PropertyName.fromString(GETCTag.sname())
         props = self.properties()
@@ -683,7 +731,7 @@
             return None
 
 
-class CommonObjectResource(LoggingMixIn, FancyEqMixin):
+class CommonObjectResource(FileMetaDataMixin, LoggingMixIn, FancyEqMixin):
     """
     @ivar _path: The path of the file on disk
 
@@ -708,10 +756,6 @@
         return "<%s: %s>" % (self.__class__.__name__, self._path.path)
 
 
-    def name(self):
-        return self._path.basename()
-
-
     @writeOperation
     def setComponent(self, component):
         raise NotImplementedError
@@ -784,6 +828,7 @@
         return ResourceType.notification
 
     notificationObjects = CommonHomeChild.objectResources
+    listNotificationObjects = CommonHomeChild.listObjectResources
     notificationObjectWithName = CommonHomeChild.objectResourceWithName
     removeNotificationObjectWithUID = CommonHomeChild.removeObjectResourceWithUID
     notificationObjectsSinceToken = CommonHomeChild.objectResourcesSinceToken

Modified: CalendarServer/trunk/txdav/datastore/file.py
===================================================================
--- CalendarServer/trunk/txdav/datastore/file.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/txdav/datastore/file.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -21,8 +21,17 @@
 """
 
 from twext.python.log import LoggingMixIn
+from txdav.idav import IDataStoreResource
 from txdav.idav import AlreadyFinishedError
+from txdav.propertystore.base import PropertyName
 
+from twext.web2.dav.element.rfc2518 import GETContentType
+from twext.web2.dav.resource import TwistedGETContentMD5
+
+
+
+from zope.interface.declarations import implements
+
 def isValidName(name):
     """
     Determine if the given string is a valid name.  i.e. does it conflict with
@@ -208,3 +217,72 @@
 
     def postCommit(self, operation):
         self._postCommitOperations.append(operation)
+
+class FileMetaDataMixin(object):
+    
+    implements(IDataStoreResource)
+    
+    def name(self):
+        """
+        Identify the name of the object
+
+        @return: the name of this object.
+        @rtype: C{str}
+        """
+
+        return self._path.basename()
+
+    def contentType(self):
+        """
+        The content type of the object's content.
+
+        @rtype: L{MimeType}
+        """
+        try:
+            return self.properties()[PropertyName.fromElement(GETContentType)].mimeType()
+        except KeyError:
+            return None
+
+    def md5(self):
+        """
+        The MD5 hex digest of this object's content.
+
+        @rtype: C{str}
+        """
+        try:
+            return str(self.properties()[PropertyName.fromElement(TwistedGETContentMD5)])
+        except KeyError:
+            return None
+
+    def size(self):
+        """
+        The octet-size of this object's content.
+
+        @rtype: C{int}
+        """
+        if self._path.exists():
+            return self._path.getsize()
+        else:
+            return 0
+
+    def created(self):
+        """
+        The creation date-time stamp of this object.
+
+        @rtype: C{int}
+        """
+        if self._path.exists():
+            return self._path.getmtime() # No creation time on POSIX
+        else:
+            return None
+
+    def modified(self):
+        """
+        The last modification date-time stamp of this object.
+
+        @rtype: C{int}
+        """
+        if self._path.exists():
+            return self._path.getmtime()
+        else:
+            return None

Modified: CalendarServer/trunk/txdav/idav.py
===================================================================
--- CalendarServer/trunk/txdav/idav.py	2010-08-05 16:11:14 UTC (rev 5981)
+++ CalendarServer/trunk/txdav/idav.py	2010-08-05 17:54:48 UTC (rev 5982)
@@ -25,6 +25,7 @@
     "IPropertyName",
     "IPropertyStore",
     "IDataStore",
+    "IDataStoreResource",
 ]
 
 from zope.interface import Attribute, Interface
@@ -119,7 +120,62 @@
         """
 
 
+class IDataStoreResource(Interface):
+    """
+    An L{IDataStoreResource} are the objects stored in an L{IDataStore}.
+    """
+    
+    def name():
+        """
+        Identify the name of the object
 
+        @return: the name of this object.
+        @rtype: C{str}
+        """
+
+    def contentType():
+        """
+        The content type of the object's content.
+
+        @rtype: L{MimeType}
+        """
+
+
+    def md5():
+        """
+        The MD5 hex digest of this object's content.
+
+        @rtype: C{str}
+        """
+
+    def size():
+        """
+        The octet-size of this object's content.
+
+        @rtype: C{int}
+        """
+
+    def created():
+        """
+        The creation date-time stamp of this object.
+
+        @rtype: C{int}
+        """
+
+    def modified():
+        """
+        The last modification date-time stamp of this object.
+
+        @rtype: C{int}
+        """
+
+    def properties():
+        """
+        Retrieve the property store for this object.
+
+        @return: an L{IPropertyStore}.
+        """
+    
 class ITransaction(Interface):
     """
     Transaction that can be aborted and either succeeds or fails in
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20100805/b0d6c2c7/attachment-0001.html>


More information about the calendarserver-changes mailing list