[CalendarServer-changes] [5922] CalendarServer/branches/new-store-no-caldavfile

source_changes at macosforge.org source_changes at macosforge.org
Mon Jul 19 19:56:49 PDT 2010


Revision: 5922
          http://trac.macosforge.org/projects/calendarserver/changeset/5922
Author:   cdaboo at apple.com
Date:     2010-07-19 19:56:47 -0700 (Mon, 19 Jul 2010)
Log Message:
-----------
Checkpoint - removed some of the ancillary CalDAVFile dependencies.

Modified Paths:
--------------
    CalendarServer/branches/new-store-no-caldavfile/calendarserver/tap/caldav.py
    CalendarServer/branches/new-store-no-caldavfile/calendarserver/tap/test/test_caldav.py
    CalendarServer/branches/new-store-no-caldavfile/calendarserver/tap/util.py
    CalendarServer/branches/new-store-no-caldavfile/twistedcaldav/extensions.py
    CalendarServer/branches/new-store-no-caldavfile/twistedcaldav/freebusyurl.py
    CalendarServer/branches/new-store-no-caldavfile/twistedcaldav/notifications.py
    CalendarServer/branches/new-store-no-caldavfile/twistedcaldav/resource.py
    CalendarServer/branches/new-store-no-caldavfile/twistedcaldav/schedule.py
    CalendarServer/branches/new-store-no-caldavfile/twistedcaldav/static.py
    CalendarServer/branches/new-store-no-caldavfile/twistedcaldav/storebridge.py
    CalendarServer/branches/new-store-no-caldavfile/twistedcaldav/timezoneservice.py
    CalendarServer/branches/new-store-no-caldavfile/txcaldav/calendarstore/file.py
    CalendarServer/branches/new-store-no-caldavfile/txcaldav/icalendarstore.py
    CalendarServer/branches/new-store-no-caldavfile/txcarddav/iaddressbookstore.py
    CalendarServer/branches/new-store-no-caldavfile/txdav/common/datastore/file.py
    CalendarServer/branches/new-store-no-caldavfile/txdav/datastore/file.py
    CalendarServer/branches/new-store-no-caldavfile/txdav/idav.py

Modified: CalendarServer/branches/new-store-no-caldavfile/calendarserver/tap/caldav.py
===================================================================
--- CalendarServer/branches/new-store-no-caldavfile/calendarserver/tap/caldav.py	2010-07-20 02:31:34 UTC (rev 5921)
+++ CalendarServer/branches/new-store-no-caldavfile/calendarserver/tap/caldav.py	2010-07-20 02:56:47 UTC (rev 5922)
@@ -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
 
@@ -373,18 +366,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/branches/new-store-no-caldavfile/calendarserver/tap/test/test_caldav.py
===================================================================
--- CalendarServer/branches/new-store-no-caldavfile/calendarserver/tap/test/test_caldav.py	2010-07-20 02:31:34 UTC (rev 5921)
+++ CalendarServer/branches/new-store-no-caldavfile/calendarserver/tap/test/test_caldav.py	2010-07-20 02:56:47 UTC (rev 5922)
@@ -55,6 +55,9 @@
     CalDAVOptions, CalDAVServiceMaker, CalDAVService, GroupOwnedUNIXServer,
     DelayedStartupProcessMonitor, DelayedStartupLineLogger, TwistdSlaveProcess
 )
+from calendarserver.provision.root import RootResource
+from twistedcaldav.directory.principal import DirectoryPrincipalProvisioningResource
+from twistedcaldav.static import CalendarHomeProvisioningFile
 
 
 # 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
+            CalendarHomeProvisioningFile
         ))
 
 

Modified: CalendarServer/branches/new-store-no-caldavfile/calendarserver/tap/util.py
===================================================================
--- CalendarServer/branches/new-store-no-caldavfile/calendarserver/tap/util.py	2010-07-20 02:31:34 UTC (rev 5921)
+++ CalendarServer/branches/new-store-no-caldavfile/calendarserver/tap/util.py	2010-07-20 02:56:47 UTC (rev 5922)
@@ -14,7 +14,6 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 ##
-
 __all__ = [
     "getRootResource",
     "FakeRequest",
@@ -47,12 +46,12 @@
 from twistedcaldav.directory.wiki import WikiDirectoryService
 from twistedcaldav.notify import installNotificationClient
 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
 
@@ -89,8 +88,8 @@
     rootResourceClass            = RootResource
     principalResourceClass       = DirectoryPrincipalProvisioningResource
     calendarResourceClass        = CalendarHomeProvisioningFile
-    iScheduleResourceClass       = IScheduleInboxFile
-    timezoneServiceResourceClass = TimezoneServiceFile
+    iScheduleResourceClass       = IScheduleInboxResource
+    timezoneServiceResourceClass = TimezoneServiceResource
     webCalendarResourceClass     = WebCalendarResource
     webAdminResourceClass        = WebAdminResource
     addressBookResourceClass     = AddressBookHomeProvisioningFile
@@ -181,7 +180,7 @@
         raise
 
     #
-    # Setup the PoxyDB Service
+    # Setup the ProxyDB Service
     #
     proxydbClass = namedClass(config.ProxyDBService.type)
 
@@ -369,7 +368,6 @@
                       % (timezoneServiceResourceClass,))
 
         timezoneService = timezoneServiceResourceClass(
-            NotFilePath(isfile=True),
             root,
         )
         root.putChild("timezones", timezoneService)

Modified: CalendarServer/branches/new-store-no-caldavfile/twistedcaldav/extensions.py
===================================================================
--- CalendarServer/branches/new-store-no-caldavfile/twistedcaldav/extensions.py	2010-07-20 02:31:34 UTC (rev 5921)
+++ CalendarServer/branches/new-store-no-caldavfile/twistedcaldav/extensions.py	2010-07-20 02:56:47 UTC (rev 5922)
@@ -686,6 +686,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

Modified: CalendarServer/branches/new-store-no-caldavfile/twistedcaldav/freebusyurl.py
===================================================================
--- CalendarServer/branches/new-store-no-caldavfile/twistedcaldav/freebusyurl.py	2010-07-20 02:31:34 UTC (rev 5921)
+++ CalendarServer/branches/new-store-no-caldavfile/twistedcaldav/freebusyurl.py	2010-07-20 02:56:47 UTC (rev 5922)
@@ -32,6 +32,8 @@
 from twext.web2 import responsecode
 from twext.web2.dav import davxml
 from twext.web2.dav.http import ErrorResponse
+from twext.web2.dav.method.propfind import http_PROPFIND
+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 +47,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.resource import deliverSchedulePrivilegeSet
 from twistedcaldav.scheduling.caldav import ScheduleViaCalDAV
 from twistedcaldav.scheduling.cuaddress import LocalCalendarUser
 from twistedcaldav.scheduling.scheduler import Scheduler
@@ -53,7 +56,7 @@
 log = Logger()
 
 
-class FreeBusyURLResource (CalDAVResource):
+class FreeBusyURLResource (ReadOnlyNoCopyResourceMixIn, CalDAVResource):
     """
     Free-busy URL resource.
 
@@ -70,6 +73,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()),
@@ -102,6 +108,9 @@
     def resourceType(self, request):
         return succeed(davxml.ResourceType.freebusyurl)
 
+    def contentType(self):
+        return MimeType("text", "calendar", charset="utf-8")
+
     def isCollection(self):
         return False
 
@@ -137,6 +146,8 @@
         """
         return self._processFBURL(request)
 
+    http_PROPFIND = http_PROPFIND
+
     @inlineCallbacks
     def _processFBURL(self, request):
         
@@ -250,3 +261,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/branches/new-store-no-caldavfile/twistedcaldav/notifications.py
===================================================================
--- CalendarServer/branches/new-store-no-caldavfile/twistedcaldav/notifications.py	2010-07-20 02:31:34 UTC (rev 5921)
+++ CalendarServer/branches/new-store-no-caldavfile/twistedcaldav/notifications.py	2010-07-20 02:56:47 UTC (rev 5922)
@@ -26,21 +26,25 @@
 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.method.propfind import http_PROPFIND
+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()
@@ -62,8 +66,12 @@
             yield self._parent.removedNotifictionMessage(request, self.resourceName())
         returnValue(response)
 
-class NotificationCollectionResource(DAVResource):
+    http_PROPFIND = http_PROPFIND
     
+class NotificationCollectionResource(ReadOnlyNoCopyResourceMixIn, CalDAVResource):
+
+    http_PROPFIND = http_PROPFIND
+    
     def notificationsDB(self):
         
         if not hasattr(self, "_notificationsDB"):
@@ -86,9 +94,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 +106,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)

Modified: CalendarServer/branches/new-store-no-caldavfile/twistedcaldav/resource.py
===================================================================
--- CalendarServer/branches/new-store-no-caldavfile/twistedcaldav/resource.py	2010-07-20 02:31:34 UTC (rev 5921)
+++ CalendarServer/branches/new-store-no-caldavfile/twistedcaldav/resource.py	2010-07-20 02:56:47 UTC (rev 5922)
@@ -47,9 +47,11 @@
 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, unimplemented, normalizeURL,\
+    bindMethods
 from twext.web2.http import HTTPError, RedirectResponse, StatusResponse, Response
 from twext.web2.http_headers import MimeType
 from twext.web2.stream import MemoryStream
@@ -102,7 +104,110 @@
             + config.CalDAVComplianceClasses
         )
 
+class ReadOnlyResourceMixIn (object):
+    """
+    Read only resource.
+    """
 
+    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 _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()
+
 class CalDAVResource (CalDAVComplianceMixIn, SharedCollectionMixin, DAVResource, LoggingMixIn):
     """
     CalDAV resource.
@@ -1587,4 +1692,3 @@
         return False
     else:
         return resource.isAddressBookCollection()
-

Modified: CalendarServer/branches/new-store-no-caldavfile/twistedcaldav/schedule.py
===================================================================
--- CalendarServer/branches/new-store-no-caldavfile/twistedcaldav/schedule.py	2010-07-20 02:31:34 UTC (rev 5921)
+++ CalendarServer/branches/new-store-no-caldavfile/twistedcaldav/schedule.py	2010-07-20 02:56:47 UTC (rev 5922)
@@ -31,22 +31,26 @@
 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.noneprops import NonePropertyStore
 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 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,\
+    deliverSchedulePrivilegeSet
 from twistedcaldav.resource import isCalendarCollectionResource
 from twistedcaldav.scheduling.scheduler import CalDAVScheduler, IScheduleScheduler
 
+from txdav.propertystore.base import PropertyName
+
 class CalendarSchedulingCollectionResource (CalDAVResource):
     """
     CalDAV principal resource.
@@ -306,7 +310,7 @@
         result = (yield scheduler.doSchedulingViaPOST())
         returnValue(result.response())
 
-class IScheduleInboxResource (CalDAVResource):
+class IScheduleInboxResource (ReadOnlyNoCopyResourceMixIn, DAVResource):
     """
     iSchedule Inbox resource.
 
@@ -319,27 +323,21 @@
         """
         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 checkPreconditions(self, request):
+        return None
+
     def resourceType(self, request):
         return succeed(davxml.ResourceType.ischeduleinbox)
 
@@ -381,3 +379,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/branches/new-store-no-caldavfile/twistedcaldav/static.py
===================================================================
--- CalendarServer/branches/new-store-no-caldavfile/twistedcaldav/static.py	2010-07-20 02:31:34 UTC (rev 5921)
+++ CalendarServer/branches/new-store-no-caldavfile/twistedcaldav/static.py	2010-07-20 02:56:47 UTC (rev 5922)
@@ -28,13 +28,9 @@
     "ScheduleFile",
     "ScheduleInboxFile",
     "ScheduleOutboxFile",
-    "IScheduleInboxFile",
     "DropBoxHomeFile",
     "DropBoxCollectionFile",
     "DropBoxChildFile",
-    "TimezoneServiceFile",
-    "NotificationCollectionFile",
-    "NotificationFile",
     "AddressBookHomeProvisioningFile",
     "AddressBookHomeUIDProvisioningFile",
     "AddressBookHomeFile",
@@ -60,12 +56,10 @@
 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 twistedcaldav import caldavxml
 from twistedcaldav import carddavxml
@@ -87,9 +81,8 @@
 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.schedule import ScheduleInboxResource, ScheduleOutboxResource
 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
@@ -104,13 +97,10 @@
 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 ClientNotifier, getNodeCacher
-from twistedcaldav.notifications import NotificationCollectionResource,\
-    NotificationResource
 
 log = Logger()
 
@@ -1070,30 +1060,26 @@
             DropBoxHomeFileClass = None
 
         if config.FreeBusyURL.Enabled:
-            FreeBusyURLFileClass = FreeBusyURLFile
+            FreeBusyURLResourceClass = FreeBusyURLResource
         else:
-            FreeBusyURLFileClass = None
+            FreeBusyURLResourceClass = 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":
+        if name == "notification" and config.Sharing.Enabled and config.Sharing.Calendars.Enabled:
             return self.createNotificationsFile(self.fp.child(name).path)
 
-        cls = {
-            "inbox"        : StoreScheduleInboxFile,
-            "outbox"       : ScheduleOutboxFile,
-            "dropbox"      : DropBoxHomeFileClass,
-            "freebusy"     : FreeBusyURLFileClass,
-            "notification" : NotificationCollectionFileClass,
-        }.get(name, None)
+        cls, isFileType = {
+            "inbox"        : (StoreScheduleInboxFile, True,),
+            "outbox"       : (ScheduleOutboxFile, True,),
+            "dropbox"      : (DropBoxHomeFileClass, True,),
+            "freebusy"     : (FreeBusyURLResourceClass, False,),
+        }.get(name, (None, True,))
 
         if cls is not None:
-            child = cls(self.fp.child(name).path, self)
+            if isFileType:
+                child = cls(self.fp.child(name).path, self)
+            else:
+                child = cls(self)
             child.clientNotifier = self.clientNotifier.clone(child,
                 label="collection")
             return child
@@ -1104,10 +1090,11 @@
         txn = self._newStoreCalendarHome._transaction
         notifications = txn.notificationsWithUID(self._newStoreCalendarHome.uid())
 
-        from twistedcaldav.storebridge import StoreNotificationCollectionFile
-        similar = StoreNotificationCollectionFile(
-            notifications, self._newStoreCalendarHome,
-            path, self,
+        from twistedcaldav.storebridge import NotificationCollectionFile
+        similar = NotificationCollectionFile(
+            notifications,
+            self._newStoreCalendarHome,
+            principalCollections = self.principalCollections(),
         )
         self.propagateTransaction(similar)
         similar.clientNotifier = self.clientNotifier.clone(similar,
@@ -1342,203 +1329,6 @@
         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.
@@ -1743,22 +1533,28 @@
 
     def provisionChild(self, name):
  
-        if config.Sharing.Enabled and config.Sharing.AddressBooks.Enabled and not config.Sharing.Calendars.Enabled:
-            NotificationCollectionFileClass = NotificationCollectionFile
-        else:
-            NotificationCollectionFileClass = None
+        # For storebridge stuff we special case this
+        if name == "notification" and config.Sharing.Enabled and config.Sharing.AddressBooks.Enabled and not config.Sharing.Calendars.Enabled:
+            return self.createNotificationsFile(self.fp.child(name).path)
 
-        cls = {
-            "notification" : NotificationCollectionFileClass,
-        }.get(name, None)
-
-        if cls is not None:
-            child = cls(self.fp.child(name).path, self)
-            child.clientNotifier = self.clientNotifier.clone(child,
-                label="collection")
-            return child
         return self.createSimilarFile(self.fp.child(name).path)
 
+    def createNotificationsFile(self, path):
+        
+        txn = self._newStoreAddressBookHome._transaction
+        notifications = txn.notificationsWithUID(self._newStoreAddressBookHome.uid())
+
+        from twistedcaldav.storebridge import NotificationCollectionFile
+        similar = NotificationCollectionFile(
+            notifications,
+            self._newStoreAddressBookHome,
+            principalCollections = self.principalCollections(),
+        )
+        self.propagateTransaction(similar)
+        similar.clientNotifier = self.clientNotifier.clone(similar,
+            label="collection")
+        return similar
+
     def createSimilarFile(self, path):
         if self.comparePath(path):
             return self
@@ -2082,10 +1878,6 @@
 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

Modified: CalendarServer/branches/new-store-no-caldavfile/twistedcaldav/storebridge.py
===================================================================
--- CalendarServer/branches/new-store-no-caldavfile/twistedcaldav/storebridge.py	2010-07-20 02:31:34 UTC (rev 5921)
+++ CalendarServer/branches/new-store-no-caldavfile/twistedcaldav/storebridge.py	2010-07-20 02:56:47 UTC (rev 5922)
@@ -39,14 +39,14 @@
     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.stream import ProducerStream, readStream, MemoryStream
 
 from twistedcaldav.static import CalDAVFile, ScheduleInboxFile, \
-    NotificationCollectionFile, NotificationFile, GlobalAddressBookFile
+    GlobalAddressBookFile
 from twistedcaldav.vcard import Component as VCard
 from twistedcaldav.resource import CalDAVResource
 
@@ -55,11 +55,13 @@
 from txdav.propertystore.base import PropertyName
 
 from twistedcaldav.caldavxml import ScheduleTag, caldav_namespace
+from twistedcaldav.method.propfind import http_PROPFIND
+from twistedcaldav.notifications import NotificationCollectionResource,\
+    NotificationResource
+from twistedcaldav.memcachelock import MemcacheLock, MemcacheLockTimeoutError
 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()
 
@@ -928,8 +930,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.iCalendarText()).hexdigest(),
@@ -1617,8 +1620,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.vCardText()).hexdigest(),
@@ -1775,6 +1779,15 @@
         )
 
 
+    def locateChild(self, request, segments):
+        if segments[0] == '':
+            return self, segments[1:]
+        return self.getChild(segments[0]), segments[1:]
+
+
+    def getChild(self, name):
+        return None
+
     def notificationsDB(self):
         """
         Retrieve the new-style index wrapper.
@@ -1790,7 +1803,7 @@
     @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)
@@ -1809,12 +1822,12 @@
         )
 
         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.
@@ -1828,8 +1841,8 @@
 
 
 
-class StoreNotificationCollectionFile(_NotificationChildHelper,
-                                      NotificationCollectionFile):
+class NotificationCollectionFile(_NotificationChildHelper,
+                                      NotificationCollectionResource):
     """
     Wrapper around a L{txcaldav.icalendar.ICalendar}.
     """
@@ -1839,40 +1852,114 @@
         Create a CalendarCollectionFile from a L{txcaldav.icalendar.ICalendar}
         and the arguments required for L{CalDAVFile}.
         """
-        super(StoreNotificationCollectionFile, self).__init__(*args, **kw)
+        super(NotificationCollectionFile, self).__init__(*args, **kw)
         self._initializeWithNotifications(notifications, home)
 
 
+    def getChild(self, name):
+        notificationObject = self._newStoreNotifications.notificationObjectWithName(name)
+        if notificationObject is None:
+            return None
+        notification = StoreNotificationObjectFile(
+            notificationObject,
+            self,
+        )
+        self.propagateTransaction(notification)
+        return notification
+
+
+    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):
+        
+        self._newStoreNotifications.writeNotificationObject(uid, xmltype, xmldata)
+        return succeed(None)
 
-    @inlineCallbacks
-    def http_DELETE(self, request):
+    def deleteNotification(self, request, record):
+        self._newStoreNotifications.removeNotificationObjectWithName(record.name)
+        return succeed(None)
+        
+class ProtoNotificationCollectionFile(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{NotificationCollectionFile}.
+
+        @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(ProtoNotificationCollectionFile, self).__init__(*args, **kw)
 
 
-    def http_COPY(self, request):
+    def isCollection(self):
+        return True
+
+    def createSimilarFile(self, path):
+        # 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)
+
+
+    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.fp.basename()
+        self._newStoreParentHome.createChildWithName(notificationName)
+        newStoreNotification = self._newStoreParentHome.childWithName(
+            notificationName
+        )
+        NotificationCollectionFile.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.
     """
@@ -1904,8 +1991,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(),
@@ -1917,7 +2005,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()
 
@@ -1932,6 +2031,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):
         """
@@ -1990,7 +2093,7 @@
 
 
 
-class ProtoStoreNotificationObjectFile(NotificationFile):
+class ProtoStoreNotificationObjectFile(NotificationResource):
 
     def __init__(self, parentNotifications, *a, **kw):
         super(ProtoStoreNotificationObjectFile, self).__init__(*a, **kw)

Modified: CalendarServer/branches/new-store-no-caldavfile/twistedcaldav/timezoneservice.py
===================================================================
--- CalendarServer/branches/new-store-no-caldavfile/twistedcaldav/timezoneservice.py	2010-07-20 02:31:34 UTC (rev 5921)
+++ CalendarServer/branches/new-store-no-caldavfile/twistedcaldav/timezoneservice.py	2010-07-20 02:56:47 UTC (rev 5922)
@@ -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)
@@ -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/branches/new-store-no-caldavfile/txcaldav/calendarstore/file.py
===================================================================
--- CalendarServer/branches/new-store-no-caldavfile/txcaldav/calendarstore/file.py	2010-07-20 02:31:34 UTC (rev 5921)
+++ CalendarServer/branches/new-store-no-caldavfile/txcaldav/calendarstore/file.py	2010-07-20 02:56:47 UTC (rev 5922)
@@ -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
@@ -440,13 +440,13 @@
         self._file.close()
 
         md5 = hashlib.md5(self._attachment._computePath().getContent()).hexdigest()
-        props = self._attachment._properties()
+        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
@@ -467,7 +467,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
@@ -482,10 +482,6 @@
         )
 
 
-    def contentType(self):
-        return self._properties()[contentTypeKey].mimeType()
-
-
     def store(self, contentType):
         return AttachmentStorageTransport(self, contentType)
 
@@ -498,10 +494,6 @@
         protocol.connectionLost(Failure(NotImplementedError()))
 
 
-    def md5(self):
-        return self._properties()[md5key]
-
-
     def _computePath(self):
         dropboxPath = self._calendarObject._dropboxPath()
         return dropboxPath.child(self.name())

Modified: CalendarServer/branches/new-store-no-caldavfile/txcaldav/icalendarstore.py
===================================================================
--- CalendarServer/branches/new-store-no-caldavfile/txcaldav/icalendarstore.py	2010-07-20 02:31:34 UTC (rev 5921)
+++ CalendarServer/branches/new-store-no-caldavfile/txcaldav/icalendarstore.py	2010-07-20 02:56:47 UTC (rev 5922)
@@ -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
 
 
@@ -63,7 +65,7 @@
 # Interfaces
 #
 
-class ICalendarHome(Interface):
+class ICalendarHome(IDataStoreResource):
     """
     An L{ICalendarHome} is a collection of calendars which belongs to a
     specific principal and contains the calendars which that principal has
@@ -132,15 +134,8 @@
         @raise NoSuchCalendarObjectError: if no such calendar exists.
         """
 
-    def properties():
-        """
-        Retrieve the property store for this calendar home.
 
-        @return: an L{IPropertyStore}.
-        """
-
-
-class ICalendar(Interface):
+class ICalendar(IDataStoreResource):
     """
     Calendar
 
@@ -150,15 +145,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.
@@ -267,15 +253,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
 
@@ -334,14 +313,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
@@ -402,35 +373,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/branches/new-store-no-caldavfile/txcarddav/iaddressbookstore.py
===================================================================
--- CalendarServer/branches/new-store-no-caldavfile/txcarddav/iaddressbookstore.py	2010-07-20 02:31:34 UTC (rev 5921)
+++ CalendarServer/branches/new-store-no-caldavfile/txcarddav/iaddressbookstore.py	2010-07-20 02:56:47 UTC (rev 5922)
@@ -20,6 +20,7 @@
 
 from zope.interface import Interface
 from txdav.common.icommondatastore import ICommonTransaction
+from txdav.idav import IDataStoreResource
 
 __all__ = [
     # Classes
@@ -51,7 +52,7 @@
 # Interfaces
 #
 
-class IAddressBookHome(Interface):
+class IAddressBookHome(IDataStoreResource):
     """
     AddressBook home
 
@@ -106,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(Interface):
+class IAddressBook(IDataStoreResource):
     """
     AddressBook
 
@@ -124,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.
@@ -229,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
 
@@ -276,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/branches/new-store-no-caldavfile/txdav/common/datastore/file.py
===================================================================
--- CalendarServer/branches/new-store-no-caldavfile/txdav/common/datastore/file.py	2010-07-20 02:31:34 UTC (rev 5921)
+++ CalendarServer/branches/new-store-no-caldavfile/txdav/common/datastore/file.py	2010-07-20 02:56:47 UTC (rev 5922)
@@ -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
@@ -205,11 +205,14 @@
             return self._notifications[(uid, self)]
 
         home = self.homeWithUID(self._notificationHomeType, uid, create=True)
+        if (uid, self) in self._notifications:
+            return self._notifications[(uid, self)]
+
         notificationPath = home._path.child("notification")
         if not notificationPath.isdir():
-            notificationPath = self.createNotifcationCollection(home, notificationPath)
-
-        notifications = NotificationCollection(notificationPath.basename(), home)
+            notifications = self.createNotifcationCollection(home, notificationPath)
+        else:
+            notifications = NotificationCollection(notificationPath.basename(), home)
         self._notifications[(uid, self)] = notifications
         return notifications
 
@@ -241,12 +244,12 @@
                 raise
             # FIXME: direct tests, undo for index creation
             # Return undo
-            return lambda: notificationPath.remove()
+            return lambda: home._path.child(notificationPath.basename()).remove()
 
         self.addOperation(do, "create child %r" % (name,))
         props = c.properties()
         props[PropertyName(*ResourceType.qname())] = c.resourceType()
-        return temporary
+        return c
 
 class StubResource(object):
     """
@@ -255,7 +258,7 @@
     def __init__(self, stubit):
         self.fp = stubit._path
 
-class CommonHome(LoggingMixIn):
+class CommonHome(FileMetaDataMixin, LoggingMixIn):
 
     _childClass = None
 
@@ -351,7 +354,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,))
         props = c.properties()
@@ -415,7 +418,7 @@
         return props
 
 
-class CommonHomeChild(LoggingMixIn, FancyEqMixin):
+class CommonHomeChild(FileMetaDataMixin, LoggingMixIn, FancyEqMixin):
     """
     """
 
@@ -639,7 +642,7 @@
         raise NotImplementedError
 
 
-class CommonObjectResource(LoggingMixIn, FancyEqMixin):
+class CommonObjectResource(FileMetaDataMixin, LoggingMixIn, FancyEqMixin):
     """
     @ivar _path: The path of the file on disk
 
@@ -664,10 +667,6 @@
         return "<%s: %s>" % (self.__class__.__name__, self._path.path)
 
 
-    def name(self):
-        return self._path.basename()
-
-
     @writeOperation
     def setComponent(self, component):
         raise NotImplementedError

Modified: CalendarServer/branches/new-store-no-caldavfile/txdav/datastore/file.py
===================================================================
--- CalendarServer/branches/new-store-no-caldavfile/txdav/datastore/file.py	2010-07-20 02:31:34 UTC (rev 5921)
+++ CalendarServer/branches/new-store-no-caldavfile/txdav/datastore/file.py	2010-07-20 02:56:47 UTC (rev 5922)
@@ -21,7 +21,14 @@
 """
 
 from twext.python.log import LoggingMixIn
+from twext.web2.dav.element.rfc2518 import GETContentType
+from twext.web2.dav.resource import TwistedGETContentMD5
 
+from txdav.idav import IDataStoreResource
+from txdav.propertystore.base import PropertyName
+
+from zope.interface.declarations import implements
+
 def isValidName(name):
     """
     Determine if the given string is a valid name.  i.e. does it conflict with
@@ -201,3 +208,72 @@
                 raise
 
 
+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
+    
\ No newline at end of file

Modified: CalendarServer/branches/new-store-no-caldavfile/txdav/idav.py
===================================================================
--- CalendarServer/branches/new-store-no-caldavfile/txdav/idav.py	2010-07-20 02:31:34 UTC (rev 5921)
+++ CalendarServer/branches/new-store-no-caldavfile/txdav/idav.py	2010-07-20 02:56:47 UTC (rev 5922)
@@ -25,6 +25,7 @@
     "IPropertyName",
     "IPropertyStore",
     "IDataStore",
+    "IDataStoreResource",
 ]
 
 from zope.interface import Attribute, Interface
@@ -117,7 +118,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/20100719/4f08b8cd/attachment-0001.html>


More information about the calendarserver-changes mailing list