[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