[CalendarServer-changes] [6553] CalendarServer/trunk
source_changes at macosforge.org
source_changes at macosforge.org
Tue Nov 2 09:46:27 PDT 2010
Revision: 6553
http://trac.macosforge.org/projects/calendarserver/changeset/6553
Author: cdaboo at apple.com
Date: 2010-11-02 09:46:24 -0700 (Tue, 02 Nov 2010)
Log Message:
-----------
Convert a whole bunch of private resource properties into SQL columns on calendar objects so a single commit
can change the resource content and meta-data.
Modified Paths:
--------------
CalendarServer/trunk/twistedcaldav/datafilters/privateevents.py
CalendarServer/trunk/twistedcaldav/method/get.py
CalendarServer/trunk/twistedcaldav/method/put_common.py
CalendarServer/trunk/twistedcaldav/method/report_calendar_query.py
CalendarServer/trunk/twistedcaldav/method/report_common.py
CalendarServer/trunk/twistedcaldav/resource.py
CalendarServer/trunk/twistedcaldav/scheduling/implicit.py
CalendarServer/trunk/twistedcaldav/storebridge.py
CalendarServer/trunk/txdav/caldav/datastore/file.py
CalendarServer/trunk/txdav/caldav/datastore/sql.py
CalendarServer/trunk/txdav/caldav/datastore/test/common.py
CalendarServer/trunk/txdav/caldav/datastore/test/test_sql.py
CalendarServer/trunk/txdav/carddav/datastore/test/test_sql.py
CalendarServer/trunk/txdav/common/datastore/file.py
CalendarServer/trunk/txdav/common/datastore/sql.py
CalendarServer/trunk/txdav/common/datastore/sql_schema_v1.sql
CalendarServer/trunk/txdav/common/datastore/sql_tables.py
Modified: CalendarServer/trunk/twistedcaldav/datafilters/privateevents.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/datafilters/privateevents.py 2010-11-02 01:03:39 UTC (rev 6552)
+++ CalendarServer/trunk/twistedcaldav/datafilters/privateevents.py 2010-11-02 16:46:24 UTC (rev 6553)
@@ -53,7 +53,7 @@
@return: L{Component} for the filtered calendar data
"""
- if self.isowner or self.accessRestriction == Component.ACCESS_PUBLIC or self.accessRestriction is None:
+ if self.isowner or self.accessRestriction == Component.ACCESS_PUBLIC or not self.accessRestriction:
# No need to filter for the owner or public event
return ical
Modified: CalendarServer/trunk/twistedcaldav/method/get.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/get.py 2010-11-02 01:03:39 UTC (rev 6552)
+++ CalendarServer/trunk/twistedcaldav/method/get.py 2010-11-02 16:46:24 UTC (rev 6553)
@@ -31,8 +31,7 @@
from twext.web2.stream import MemoryStream
from twistedcaldav.caldavxml import ScheduleTag
-from twistedcaldav.customxml import TwistedCalendarAccessProperty,\
- calendarserver_namespace
+from twistedcaldav.customxml import calendarserver_namespace
from twistedcaldav.datafilters.privateevents import PrivateEventFilter
from twistedcaldav.resource import isPseudoCalendarCollectionResource,\
CalDAVResource
@@ -72,18 +71,13 @@
caldata = (yield self.iCalendarForUser(request))
- try:
- access = self.readDeadProperty(TwistedCalendarAccessProperty)
- except HTTPError:
- access = None
-
- if access:
+ if self.accessMode:
# Non DAV:owner's have limited access to the data
isowner = (yield self.isOwner(request, adminprincipals=True, readprincipals=True))
# Now "filter" the resource calendar data
- caldata = PrivateEventFilter(access, isowner).filter(caldata)
+ caldata = PrivateEventFilter(self.accessMode, isowner).filter(caldata)
response = Response()
response.stream = MemoryStream(str(caldata))
Modified: CalendarServer/trunk/twistedcaldav/method/put_common.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/put_common.py 2010-11-02 01:03:39 UTC (rev 6552)
+++ CalendarServer/trunk/twistedcaldav/method/put_common.py 2010-11-02 16:46:24 UTC (rev 6553)
@@ -28,6 +28,7 @@
from twisted.internet.defer import Deferred, inlineCallbacks, succeed
from twisted.internet.defer import returnValue
from twisted.python.failure import Failure
+from twisted.python import hashlib
from twext.web2.dav.util import joinURL, parentForURL
from twext.web2 import responsecode
@@ -50,10 +51,7 @@
from twistedcaldav.caldavxml import ScheduleTag, NoUIDConflict
from twistedcaldav.caldavxml import NumberOfRecurrencesWithinLimits
from twistedcaldav.caldavxml import caldav_namespace, MaxAttendeesPerInstance
-from twistedcaldav.customxml import calendarserver_namespace ,\
- TwistedCalendarHasPrivateCommentsProperty, TwistedSchedulingObjectResource,\
- TwistedScheduleMatchETags
-from twistedcaldav.customxml import TwistedCalendarAccessProperty
+from twistedcaldav.customxml import calendarserver_namespace
from twistedcaldav.datafilters.peruserdata import PerUserDataFilter
from twistedcaldav.ical import Component, Property
@@ -191,8 +189,9 @@
self.processing_organizer = processing_organizer
self.access = None
+ self.hasPrivateComments = False
+ self.isScheduleResource = False
-
@inlineCallbacks
def fullValidation(self):
"""
@@ -491,8 +490,8 @@
return d
else:
# Check whether an access property was present before and write that into the calendar data
- if not self.source and self.destination.exists() and self.destination.hasDeadProperty(TwistedCalendarAccessProperty):
- old_access = str(self.destination.readDeadProperty(TwistedCalendarAccessProperty))
+ if not self.source and self.destination.exists() and self.destination.accessMode:
+ old_access = self.destination.accessMode
self.calendar.addProperty(Property(name=Component.ACCESS_PROPERTY, value=old_access))
self.calendardata = str(self.calendar)
@@ -521,15 +520,15 @@
#
# NB Do this before implicit scheduling as we don't want old clients to trigger scheduling when
# the X- property is missing.
- new_has_private_comments = False
+ self.hasPrivateComments = False
if config.Scheduling.CalDAV.get("EnablePrivateComments", True) and self.calendar is not None:
- old_has_private_comments = self.destination.exists() and self.destinationcal and self.destination.hasDeadProperty(TwistedCalendarHasPrivateCommentsProperty)
- new_has_private_comments = self.calendar.hasPropertyInAnyComponent((
+ old_has_private_comments = self.destination.exists() and self.destinationcal and self.destination.hasPrivateComment
+ self.hasPrivateComments = self.calendar.hasPropertyInAnyComponent((
"X-CALENDARSERVER-PRIVATE-COMMENT",
"X-CALENDARSERVER-ATTENDEE-COMMENT",
))
- if old_has_private_comments and not new_has_private_comments:
+ if old_has_private_comments and not self.hasPrivateComments:
# Transfer old comments to new calendar
log.debug("Private Comments properties were entirely removed by the client. Restoring existing properties.")
old_calendar = (yield self.destination.iCalendarForUser(self.request))
@@ -538,8 +537,6 @@
"X-CALENDARSERVER-ATTENDEE-COMMENT",
))
self.calendardata = None
-
- returnValue(new_has_private_comments)
@inlineCallbacks
@@ -714,32 +711,91 @@
if implicit:
response = (yield self.doStorePut())
else:
- response = (yield self.destination.storeStream(MemoryStream(sourceText)))
+ response = (yield self.doStorePut(sourceText))
self.destination.newStoreProperties().update(sourceProperties)
else:
response = (yield self.doStorePut())
-
- # Update calendar-access property value on the resource
+
+ returnValue(response)
+
+
+ @inlineCallbacks
+ def doStorePut(self, data=None):
+
+ if data is None:
+ if self.calendardata is None:
+ self.calendardata = str(self.calendar)
+ data = self.calendardata
+
+ # Update calendar-access property value on the resource. We need to do this before the
+ # store as the store will "commit" the new value.
if self.access:
- self.destination.writeDeadProperty(TwistedCalendarAccessProperty(self.access))
+ self.destination.accessMode = self.access
# Do not remove the property if access was not specified and we are storing in a calendar.
# This ensure that clients that do not preserve the iCalendar property do not cause access
# restrictions to be lost.
elif not self.destinationcal:
- self.destination.removeDeadProperty(TwistedCalendarAccessProperty)
+ self.destination.accessMode = ""
- returnValue(IResponse(response))
+ # Check for existence of private comments and write property
+ if config.Scheduling.CalDAV.get("EnablePrivateComments", True):
+ self.destination.hasPrivateComment = self.hasPrivateComments
+ # Check for scheduling object resource and write property
+ self.destination.isScheduleObject = self.isScheduleResource
+ if self.isScheduleResource:
+ # Need to figure out when to change the schedule tag:
+ #
+ # 1. If this is not an internal request then the resource is being explicitly changed
+ # 2. If it is an internal request for the Organizer, schedule tag never changes
+ # 3. If it is an internal request for an Attendee and the message being processed came
+ # from the Organizer then the schedule tag changes.
- @inlineCallbacks
- def doStorePut(self):
+ change_scheduletag = True
+ if self.internal_request:
+ # Check what kind of processing is going on
+ if self.processing_organizer == True:
+ # All auto-processed updates for an Organizer leave the tag unchanged
+ change_scheduletag = False
+ elif self.processing_organizer == False:
+ # Auto-processed updates that are the result of an organizer "refresh' due
+ # to another Attendee's REPLY should leave the tag unchanged
+ change_scheduletag = not hasattr(self.request, "doing_attendee_refresh")
- if self.calendardata is None:
- self.calendardata = str(self.calendar)
- stream = MemoryStream(self.calendardata)
+ if change_scheduletag or self.scheduletag is None:
+ self.scheduletag = str(uuid.uuid4())
+
+
+ # Handle weak etag compatibility
+ if config.Scheduling.CalDAV.ScheduleTagCompatibility:
+ if change_scheduletag:
+ # Schedule-Tag change => weak ETag behavior must not happen
+ etags = ()
+ else:
+ # Schedule-Tag did not change => add current ETag to list of those that can
+ # be used in a weak pre-condition test
+ etags = self.destination.scheduleEtags
+ etags += (hashlib.md5(data).hexdigest(),)
+ self.destination.scheduleEtags = etags
+ else:
+ self.destination.scheduleEtags = ()
+ else:
+ self.destination.scheduleEtags = ()
+
+
+ stream = MemoryStream(data)
response = yield self.destination.storeStream(stream)
+ response = IResponse(response)
+ if self.isScheduleResource:
+ # Add a response header
+ response.headers.setHeader("Schedule-Tag", self.scheduletag)
+
+ self.destination.writeDeadProperty(ScheduleTag.fromString(self.scheduletag))
+ else:
+ self.destination.removeDeadProperty(ScheduleTag)
+
returnValue(response)
@inlineCallbacks
@@ -799,7 +855,7 @@
rruleChanged = self.truncateRecurrence()
# Preserve private comments
- new_has_private_comments = (yield self.preservePrivateComments())
+ yield self.preservePrivateComments()
# Do scheduling
implicit_result = (yield self.doImplicitScheduling())
@@ -829,7 +885,7 @@
log.err(msg)
raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "valid-calendar-data"), description=msg))
else:
- is_scheduling_resource, data_changed, did_implicit_action = implicit_result
+ self.isScheduleResource, data_changed, did_implicit_action = implicit_result
# Do the actual put or copy
response = (yield self.doStore(data_changed))
@@ -843,63 +899,6 @@
self.request.addResponseFilter(_removeEtag, atEnd=True)
- # Check for scheduling object resource and write property
- if is_scheduling_resource:
- self.destination.writeDeadProperty(TwistedSchedulingObjectResource.fromString("true"))
-
- # Need to figure out when to change the schedule tag:
- #
- # 1. If this is not an internal request then the resource is being explicitly changed
- # 2. If it is an internal request for the Organizer, schedule tag never changes
- # 3. If it is an internal request for an Attendee and the message being processed came
- # from the Organizer then the schedule tag changes.
-
- change_scheduletag = True
- if self.internal_request:
- # Check what kind of processing is going on
- if self.processing_organizer == True:
- # All auto-processed updates for an Organizer leave the tag unchanged
- change_scheduletag = False
- elif self.processing_organizer == False:
- # Auto-processed updates that are the result of an organizer "refresh' due
- # to another Attendee's REPLY should leave the tag unchanged
- change_scheduletag = not hasattr(self.request, "doing_attendee_refresh")
-
- if change_scheduletag or self.scheduletag is None:
- self.scheduletag = str(uuid.uuid4())
- self.destination.writeDeadProperty(ScheduleTag.fromString(self.scheduletag))
-
- # Add a response header
- response.headers.setHeader("Schedule-Tag", self.scheduletag)
-
- # Handle weak etag compatibility
- if config.Scheduling.CalDAV.ScheduleTagCompatibility:
- if change_scheduletag:
- # Schedule-Tag change => weak ETag behavior must not happen
- etags = ()
- else:
- # Schedule-Tag did not change => add current ETag to list of those that can
- # be used in a weak pre-condition test
- if self.destination.hasDeadProperty(TwistedScheduleMatchETags):
- etags = self.destination.readDeadProperty(TwistedScheduleMatchETags).children
- else:
- etags = ()
- etags += (davxml.GETETag.fromString(self.destination.etag().tag),)
- self.destination.writeDeadProperty(TwistedScheduleMatchETags(*etags))
- else:
- self.destination.removeDeadProperty(TwistedScheduleMatchETags)
- else:
- self.destination.writeDeadProperty(TwistedSchedulingObjectResource.fromString("false"))
- self.destination.removeDeadProperty(ScheduleTag)
- self.destination.removeDeadProperty(TwistedScheduleMatchETags)
-
- # Check for existence of private comments and write property
- if config.Scheduling.CalDAV.get("EnablePrivateComments", True):
- if new_has_private_comments:
- self.destination.writeDeadProperty(TwistedCalendarHasPrivateCommentsProperty())
- elif not self.destinationcal:
- self.destination.removeDeadProperty(TwistedCalendarHasPrivateCommentsProperty)
-
# Remember the resource's content-type.
if self.destinationcal:
content_type = self.request.headers.getHeader("content-type")
Modified: CalendarServer/trunk/twistedcaldav/method/report_calendar_query.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/report_calendar_query.py 2010-11-02 01:03:39 UTC (rev 6552)
+++ CalendarServer/trunk/twistedcaldav/method/report_calendar_query.py 2010-11-02 16:46:24 UTC (rev 6553)
@@ -38,7 +38,6 @@
from twistedcaldav.caldavxml import caldav_namespace,\
NumberOfRecurrencesWithinLimits
from twistedcaldav.config import config
-from twistedcaldav.customxml import TwistedCalendarAccessProperty
from txdav.common.icommondatastore import IndexedSearchException
from twistedcaldav.instance import TooManyInstancesError
from twistedcaldav.method import report_common
@@ -130,10 +129,7 @@
# Handle private events access restrictions
if not isowner:
- try:
- access = resource.readDeadProperty(TwistedCalendarAccessProperty)
- except HTTPError:
- access = None
+ access = resource.accessMode
else:
access = None
Modified: CalendarServer/trunk/twistedcaldav/method/report_common.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/report_common.py 2010-11-02 01:03:39 UTC (rev 6552)
+++ CalendarServer/trunk/twistedcaldav/method/report_common.py 2010-11-02 16:46:24 UTC (rev 6553)
@@ -57,7 +57,6 @@
from twistedcaldav import carddavxml
from twistedcaldav.caldavxml import caldav_namespace, CalendarData
from twistedcaldav.carddavxml import AddressData
-from twistedcaldav.customxml import TwistedCalendarAccessProperty
from twistedcaldav.datafilters.calendardata import CalendarDataFilter
from twistedcaldav.datafilters.privateevents import PrivateEventFilter
from twistedcaldav.datafilters.addressdata import AddressDataFilter
@@ -332,14 +331,9 @@
for property in props:
if isinstance(property, caldavxml.CalendarData):
# Handle private events access restrictions
- try:
- access = resource.readDeadProperty(TwistedCalendarAccessProperty)
- except HTTPError:
- access = None
-
if calendar is None:
calendar = (yield resource.iCalendarForUser(request))
- filtered = PrivateEventFilter(access, isowner).filter(calendar)
+ filtered = PrivateEventFilter(resource.accessMode, isowner).filter(calendar)
filtered = CalendarDataFilter(property, timezone).filter(filtered)
propvalue = CalendarData().fromCalendar(filtered)
properties_by_status[responsecode.OK].append(propvalue)
Modified: CalendarServer/trunk/twistedcaldav/resource.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/resource.py 2010-11-02 01:03:39 UTC (rev 6552)
+++ CalendarServer/trunk/twistedcaldav/resource.py 2010-11-02 16:46:24 UTC (rev 6553)
@@ -62,8 +62,6 @@
from twistedcaldav.caldavxml import caldav_namespace
from twistedcaldav.carddavxml import carddav_namespace
from twistedcaldav.config import config
-from twistedcaldav.customxml import TwistedCalendarAccessProperty,\
- TwistedScheduleMatchETags
from twistedcaldav.customxml import calendarserver_namespace
from twistedcaldav.datafilters.peruserdata import PerUserDataFilter
from twistedcaldav.datafilters.privateevents import PrivateEventFilter
@@ -750,6 +748,18 @@
# ACL
##
+ def _get_accessMode(self):
+ """
+ Needed as a stub because only calendar object resources use this but we need to do ACL
+ determination on the generic CalDAVResource for now.
+ """
+ return ""
+
+ def _set_accessMode(self, value):
+ raise NotImplementedError
+
+ accessMode = property(_get_accessMode, _set_accessMode)
+
# FIXME: Perhaps this is better done in authorize() instead.
@inlineCallbacks
def accessControlList(self, request, *args, **kwargs):
@@ -763,13 +773,12 @@
acls = (yield super(CalDAVResource, self).accessControlList(request, *args, **kwargs))
# Look for private events access classification
- if self.hasDeadProperty(TwistedCalendarAccessProperty):
- access = self.readDeadProperty(TwistedCalendarAccessProperty)
- if access.getValue() in (Component.ACCESS_PRIVATE, Component.ACCESS_CONFIDENTIAL, Component.ACCESS_RESTRICTED,):
+ if self.accessMode:
+ if self.accessMode in (Component.ACCESS_PRIVATE, Component.ACCESS_CONFIDENTIAL, Component.ACCESS_RESTRICTED,):
# Need to insert ACE to prevent non-owner principals from seeing this resource
owner = (yield self.owner(request))
newacls = []
- if access.getValue() == Component.ACCESS_PRIVATE:
+ if self.accessMode == Component.ACCESS_PRIVATE:
newacls.extend(config.AdminACEs)
newacls.extend(config.ReadACEs)
newacls.append(davxml.ACE(
@@ -1317,8 +1326,8 @@
if config.Scheduling.CalDAV.ScheduleTagCompatibility:
- if self.exists() and self.hasDeadProperty(TwistedScheduleMatchETags):
- etags = self.readDeadProperty(TwistedScheduleMatchETags).children
+ if self.exists() and hasattr(self, "scheduleEtags"):
+ etags = self.scheduleEtags
if len(etags) > 1:
# This is almost verbatim from twext.web2.static.checkPreconditions
if request.method not in ("GET", "HEAD"):
@@ -1415,13 +1424,9 @@
@inlineCallbacks
def iCalendarTextFiltered(self, isowner, accessUID=None):
- try:
- access = self.readDeadProperty(TwistedCalendarAccessProperty)
- except HTTPError:
- access = None
# Now "filter" the resource calendar data
- caldata = PrivateEventFilter(access, isowner).filter(
+ caldata = PrivateEventFilter(self.accessMode, isowner).filter(
(yield self.iCalendarText())
)
if accessUID:
Modified: CalendarServer/trunk/twistedcaldav/scheduling/implicit.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/scheduling/implicit.py 2010-11-02 01:03:39 UTC (rev 6552)
+++ CalendarServer/trunk/twistedcaldav/scheduling/implicit.py 2010-11-02 16:46:24 UTC (rev 6553)
@@ -25,7 +25,6 @@
from twistedcaldav import caldavxml
from twistedcaldav.caldavxml import caldav_namespace
-from twistedcaldav.customxml import TwistedSchedulingObjectResource
from twistedcaldav.directory.principal import DirectoryCalendarPrincipalResource
from twistedcaldav.ical import Property
from twistedcaldav.method import report_common
@@ -70,7 +69,7 @@
self.internal_request = internal_request
existing_resource = resource.exists()
- is_scheduling_object = (yield self.checkSchedulingObjectResource(resource))
+ is_scheduling_object = self.checkSchedulingObjectResource(resource)
existing_type = "schedule" if is_scheduling_object else "calendar"
new_type = "schedule" if (yield self.checkImplicitState()) else "calendar"
@@ -108,8 +107,8 @@
new_type = "schedule" if (yield self.checkImplicitState()) else "calendar"
dest_exists = destresource.exists()
- dest_is_implicit = (yield self.checkSchedulingObjectResource(destresource))
- src_is_implicit = (yield self.checkSchedulingObjectResource(srcresource)) or new_type == "schedule"
+ dest_is_implicit = self.checkSchedulingObjectResource(destresource)
+ src_is_implicit = self.checkSchedulingObjectResource(srcresource) or new_type == "schedule"
if srccal and destcal:
if src_is_implicit and dest_exists or dest_is_implicit:
@@ -138,8 +137,8 @@
new_type = "schedule" if (yield self.checkImplicitState()) else "calendar"
- dest_is_implicit = (yield self.checkSchedulingObjectResource(destresource))
- src_is_implicit = (yield self.checkSchedulingObjectResource(srcresource)) or new_type == "schedule"
+ dest_is_implicit = self.checkSchedulingObjectResource(destresource)
+ src_is_implicit = self.checkSchedulingObjectResource(srcresource) or new_type == "schedule"
if srccal and destcal:
if src_is_implicit or dest_is_implicit:
@@ -167,39 +166,15 @@
yield self.checkImplicitState()
- is_scheduling_object = (yield self.checkSchedulingObjectResource(resource))
+ is_scheduling_object = self.checkSchedulingObjectResource(resource)
resource_type = "schedule" if is_scheduling_object else "calendar"
self.action = "remove" if resource_type == "schedule" else "none"
returnValue((self.action != "none", False,))
- @inlineCallbacks
def checkSchedulingObjectResource(self, resource):
- if resource and resource.exists():
- try:
- implicit = resource.readDeadProperty(TwistedSchedulingObjectResource)
- except HTTPError:
- implicit = None
- if implicit is not None:
- returnValue(implicit != "false")
- else:
- calendar = (yield resource.iCalendarForUser(self.request))
- # Get the ORGANIZER and verify it is the same for all components
- try:
- organizer = calendar.validOrganizerForScheduling()
- except ValueError:
- # We have different ORGANIZERs in the same iCalendar object - this is an error
- returnValue(False)
- organizerPrincipal = resource.principalForCalendarUserAddress(organizer) if organizer else None
- resource.writeDeadProperty(TwistedSchedulingObjectResource("true" if organizerPrincipal != None else "false"))
- log.debug("Implicit - checked scheduling object resource state for UID: '%s', result: %s" % (
- calendar.resourceUID(),
- "true" if organizerPrincipal != None else "false",
- ))
- returnValue(organizerPrincipal != None)
-
- returnValue(False)
+ return resource.isScheduleObject if resource and resource.exists() else False
@inlineCallbacks
def checkImplicitState(self):
@@ -393,7 +368,7 @@
child = (yield self.request.locateResource(joinURL(collection_uri, rname)))
if child == check_resource:
returnValue(True)
- is_scheduling_object = (yield self.checkSchedulingObjectResource(child))
+ is_scheduling_object = self.checkSchedulingObjectResource(child)
matched_type = "schedule" if is_scheduling_object else "calendar"
if (
collection_uri != check_parent_uri and
@@ -581,7 +556,7 @@
newOrganizer = self.calendar.getOrganizer()
if oldOrganizer != newOrganizer:
log.error("Cannot change ORGANIZER: UID:%s" % (self.uid,))
- raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "valid-attendee-change")))
+ raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "valid-organizer-change")))
else:
# Special case of SCHEDULE-FORCE-SEND added to attendees and no other change
reinvites = set()
Modified: CalendarServer/trunk/twistedcaldav/storebridge.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/storebridge.py 2010-11-02 01:03:39 UTC (rev 6552)
+++ CalendarServer/trunk/twistedcaldav/storebridge.py 2010-11-02 16:46:24 UTC (rev 6553)
@@ -1039,8 +1039,41 @@
# FIXME: should be deleted, or raise an exception
+class _CalendarObjectMetaDataMixin(object):
-class CalendarObjectResource(_NewStoreFileMetaDataHelper, CalDAVResource, FancyEqMixin):
+ def _get_accessMode(self):
+ return self._newStoreObject.accessMode
+
+ def _set_accessMode(self, value):
+ self._newStoreObject.accessMode = value
+
+ accessMode = property(_get_accessMode, _set_accessMode)
+
+ def _get_isScheduleObject(self):
+ return self._newStoreObject.isScheduleObject
+
+ def _set_isScheduleObject(self, value):
+ self._newStoreObject.isScheduleObject = value
+
+ isScheduleObject = property(_get_isScheduleObject, _set_isScheduleObject)
+
+ def _get_scheduleEtags(self):
+ return self._newStoreObject.scheduleEtags
+
+ def _set_scheduleEtags(self, value):
+ self._newStoreObject.scheduleEtags = value
+
+ scheduleEtags = property(_get_scheduleEtags, _set_scheduleEtags)
+
+ def _get_hasPrivateComment(self):
+ return self._newStoreObject.hasPrivateComment
+
+ def _set_hasPrivateComment(self, value):
+ self._newStoreObject.hasPrivateComment = value
+
+ hasPrivateComment = property(_get_hasPrivateComment, _set_hasPrivateComment)
+
+class CalendarObjectResource(_NewStoreFileMetaDataHelper, _CalendarObjectMetaDataMixin, CalDAVResource, FancyEqMixin):
"""
A resource wrapping a calendar object.
"""
@@ -1254,13 +1287,20 @@
-class ProtoCalendarObjectResource(CalDAVResource, FancyEqMixin):
+class ProtoCalendarObjectResource(_CalendarObjectMetaDataMixin, CalDAVResource, FancyEqMixin):
compareAttributes = '_newStoreParentCalendar'.split()
def __init__(self, parentCalendar, name, *a, **kw):
+ """
+ We need to create an "empty" resource object here because resource meta-data does get
+ changed before the actual calendar data is written. So we need some kind of "container" for
+ that to ensure those meta-data values actually get pushed to the store when the resource is
+ created.
+ """
super(ProtoCalendarObjectResource, self).__init__(*a, **kw)
self._newStoreParentCalendar = parentCalendar
+ self._newStoreObject = self._newStoreParentCalendar.emptyObjectWithName(name)
self._name = name
@@ -1271,7 +1311,7 @@
(yield allDataFromStream(stream))
)
yield self._newStoreParentCalendar.createCalendarObjectWithName(
- self.name(), component
+ self.name(), component, objectResource=self._newStoreObject
)
CalendarObjectResource.transform(
self,
Modified: CalendarServer/trunk/txdav/caldav/datastore/file.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/file.py 2010-11-02 01:03:39 UTC (rev 6552)
+++ CalendarServer/trunk/txdav/caldav/datastore/file.py 2010-11-02 16:46:24 UTC (rev 6553)
@@ -14,7 +14,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
##
-from twisted.internet.defer import inlineCallbacks, returnValue
"""
File calendar store.
@@ -32,6 +31,7 @@
from errno import ENOENT
+from twisted.internet.defer import inlineCallbacks, returnValue
from twisted.internet.interfaces import ITransport
from twisted.python.failure import Failure
@@ -39,12 +39,16 @@
from twext.python.vcomponent import InvalidICalendarDataError
from twext.python.vcomponent import VComponent
+from twext.web2.dav import davxml
from twext.web2.dav.element.rfc2518 import ResourceType, GETContentType
from twext.web2.dav.resource import TwistedGETContentMD5
from twext.web2.http_headers import generateContentType
from twistedcaldav import caldavxml, customxml
from twistedcaldav.caldavxml import ScheduleCalendarTransp, Opaque
+from twistedcaldav.customxml import TwistedCalendarAccessProperty,\
+ TwistedCalendarHasPrivateCommentsProperty, TwistedSchedulingObjectResource,\
+ TwistedScheduleMatchETags
from twistedcaldav.sharing import InvitesDatabase
from txdav.caldav.icalendarstore import IAttachment
@@ -352,7 +356,49 @@
def organizer(self):
return self.component().getOrganizer()
+ def _get_accessMode(self):
+ return str(self.properties().get(PropertyName.fromElement(TwistedCalendarAccessProperty), ""))
+ def _set_accessMode(self, value):
+ self.properties()[PropertyName.fromElement(TwistedCalendarAccessProperty)] = TwistedCalendarAccessProperty(value)
+
+ accessMode = property(_get_accessMode, _set_accessMode)
+
+ def _get_isScheduleObject(self):
+ return str(self.properties().get(PropertyName.fromElement(TwistedSchedulingObjectResource), "false")) == "true"
+
+ def _set_isScheduleObject(self, value):
+ self.properties()[PropertyName.fromElement(TwistedSchedulingObjectResource)] = TwistedSchedulingObjectResource.fromString("true" if value else "false")
+
+ isScheduleObject = property(_get_isScheduleObject, _set_isScheduleObject)
+
+ def _get_scheduleEtags(self):
+ return tuple([str(etag) for etag in self.properties().get(PropertyName.fromElement(TwistedScheduleMatchETags), TwistedScheduleMatchETags()).children])
+
+ def _set_scheduleEtags(self, value):
+ if value:
+ etags = [davxml.GETETag.fromString(etag) for etag in value]
+ self.properties()[PropertyName.fromElement(TwistedScheduleMatchETags)] = TwistedScheduleMatchETags(*etags)
+ else:
+ try:
+ del self.properties()[PropertyName.fromElement(TwistedScheduleMatchETags)]
+ except KeyError:
+ pass
+
+ scheduleEtags = property(_get_scheduleEtags, _set_scheduleEtags)
+
+ def _get_hasPrivateComment(self):
+ return PropertyName.fromElement(TwistedCalendarHasPrivateCommentsProperty) in self.properties()
+
+ def _set_hasPrivateComment(self, value):
+ pname = PropertyName.fromElement(TwistedCalendarHasPrivateCommentsProperty)
+ if value:
+ self.properties()[pname] = TwistedCalendarHasPrivateCommentsProperty()
+ elif pname in self.properties():
+ del self.properties()[pname]
+
+ hasPrivateComment = property(_get_hasPrivateComment, _set_hasPrivateComment)
+
@inlineCallbacks
def createAttachmentWithName(self, name, contentType):
"""
Modified: CalendarServer/trunk/txdav/caldav/datastore/sql.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/sql.py 2010-11-02 01:03:39 UTC (rev 6552)
+++ CalendarServer/trunk/txdav/caldav/datastore/sql.py 2010-11-02 16:46:24 UTC (rev 6553)
@@ -38,14 +38,14 @@
from twistedcaldav import caldavxml, customxml
from twistedcaldav.caldavxml import ScheduleCalendarTransp, Opaque
from twistedcaldav.dateops import normalizeForIndex, datetimeMktime
-from txdav.common.icommondatastore import IndexedSearchException
+from twistedcaldav.ical import Component
from twistedcaldav.instance import InvalidOverriddenInstanceError
+from txdav.base.propertystore.base import PropertyName
from txdav.caldav.datastore.util import validateCalendarComponent,\
dropboxIDFromCalendarObject
from txdav.caldav.icalendarstore import ICalendarHome, ICalendar, ICalendarObject,\
IAttachment
-
from txdav.common.datastore.sql import CommonHome, CommonHomeChild,\
CommonObjectResource
from txdav.common.datastore.sql_legacy import \
@@ -54,7 +54,7 @@
from txdav.common.datastore.sql_tables import CALENDAR_TABLE,\
CALENDAR_BIND_TABLE, CALENDAR_OBJECT_REVISIONS_TABLE, CALENDAR_OBJECT_TABLE,\
_ATTACHMENTS_MODE_WRITE, CALENDAR_HOME_TABLE
-from txdav.base.propertystore.base import PropertyName
+from txdav.common.icommondatastore import IndexedSearchException
from vobject.icalendar import utc
@@ -220,6 +220,15 @@
4: 'T',
}
+accessMode_to_type = {
+ "" : 0,
+ Component.ACCESS_PUBLIC : 1,
+ Component.ACCESS_PRIVATE : 2,
+ Component.ACCESS_CONFIDENTIAL: 3,
+ Component.ACCESS_RESTRICTED : 4,
+}
+accesstype_to_accessMode = dict([(v, k) for k,v in accessMode_to_type.items()])
+
def _pathToName(path):
return path.rsplit(".", 1)[0]
@@ -232,8 +241,78 @@
super(CalendarObject, self).__init__(calendar, name, uid)
self._objectTable = CALENDAR_OBJECT_TABLE
+
+ self._access = accessMode_to_type[""]
+ self._schedule_object = False
+ self._schedule_etags = ""
+ self._private_comments = False
+ @inlineCallbacks
+ def initFromStore(self):
+ """
+ Initialise this object from the store. We read in and cache all the extra metadata
+ from the DB to avoid having to do DB queries for those individually later. Either the
+ name or uid is present, so we have to tweak the query accordingly.
+
+ @return: L{self} if object exists in the DB, else C{None}
+ """
+
+ if self._name:
+ rows = yield self._txn.execSQL("""
+ select
+ %(column_RESOURCE_ID)s,
+ %(column_RESOURCE_NAME)s,
+ %(column_UID)s,
+ %(column_MD5)s,
+ character_length(%(column_TEXT)s),
+ %(column_ACCESS)s,
+ %(column_SCHEDULE_OBJECT)s,
+ %(column_SCHEDULE_ETAGS)s,
+ %(column_PRIVATE_COMMENTS)s,
+ %(column_CREATED)s,
+ %(column_MODIFIED)s
+ from %(name)s
+ where %(column_RESOURCE_NAME)s = %%s and %(column_PARENT_RESOURCE_ID)s = %%s
+ """ % self._objectTable,
+ [self._name, self._parentCollection._resourceID]
+ )
+ else:
+ rows = yield self._txn.execSQL("""
+ select
+ %(column_RESOURCE_ID)s,
+ %(column_RESOURCE_NAME)s,
+ %(column_UID)s,
+ %(column_MD5)s,
+ character_length(%(column_TEXT)s),
+ %(column_ACCESS)s,
+ %(column_SCHEDULE_OBJECT)s,
+ %(column_SCHEDULE_ETAGS)s,
+ %(column_PRIVATE_COMMENTS)s,
+ %(column_CREATED)s,
+ %(column_MODIFIED)s
+ from %(name)s
+ where %(column_UID)s = %%s and %(column_PARENT_RESOURCE_ID)s = %%s
+ """ % self._objectTable,
+ [self._uid, self._parentCollection._resourceID]
+ )
+ if rows:
+ (self._resourceID,
+ self._name,
+ self._uid,
+ self._md5,
+ self._size,
+ self._access,
+ self._schedule_object,
+ self._schedule_etags,
+ self._private_comments,
+ self._created,
+ self._modified,) = tuple(rows[0])
+ yield self._loadPropertyStore()
+ returnValue(self)
+ else:
+ returnValue(None)
+
@property
def _calendar(self):
return self._parentCollection
@@ -312,9 +391,11 @@
yield self._txn.execSQL(
"""
insert into CALENDAR_OBJECT
- (CALENDAR_RESOURCE_ID, RESOURCE_NAME, ICALENDAR_TEXT, ICALENDAR_UID, ICALENDAR_TYPE, ATTACHMENTS_MODE, ORGANIZER, RECURRANCE_MAX, MD5)
+ (CALENDAR_RESOURCE_ID, RESOURCE_NAME, ICALENDAR_TEXT, ICALENDAR_UID, ICALENDAR_TYPE,
+ ATTACHMENTS_MODE, ORGANIZER, RECURRANCE_MAX, ACCESS, SCHEDULE_OBJECT, SCHEDULE_ETAGS,
+ PRIVATE_COMMENTS, MD5)
values
- (%s, %s, %s, %s, %s, %s, %s, %s, %s)
+ (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
returning
RESOURCE_ID,
CREATED,
@@ -331,6 +412,10 @@
_ATTACHMENTS_MODE_WRITE,
organizer,
normalizeForIndex(instances.limit) if instances.limit else None,
+ self._access,
+ self._schedule_object,
+ self._schedule_etags,
+ self._private_comments,
self._md5,
]
))[0]
@@ -338,9 +423,11 @@
yield self._txn.execSQL(
"""
update CALENDAR_OBJECT set
- (ICALENDAR_TEXT, ICALENDAR_UID, ICALENDAR_TYPE, ATTACHMENTS_MODE, ORGANIZER, RECURRANCE_MAX, MD5, MODIFIED)
+ (ICALENDAR_TEXT, ICALENDAR_UID, ICALENDAR_TYPE, ATTACHMENTS_MODE,
+ ORGANIZER, RECURRANCE_MAX, ACCESS, SCHEDULE_OBJECT, SCHEDULE_ETAGS,
+ PRIVATE_COMMENTS, MD5, MODIFIED)
=
- (%s, %s, %s, %s, %s, %s, %s, timezone('UTC', CURRENT_TIMESTAMP))
+ (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, timezone('UTC', CURRENT_TIMESTAMP))
where RESOURCE_ID = %s
returning MODIFIED
""",
@@ -354,6 +441,10 @@
_ATTACHMENTS_MODE_WRITE,
organizer,
normalizeForIndex(instances.limit) if instances.limit else None,
+ self._access,
+ self._schedule_object,
+ self._schedule_etags,
+ self._private_comments,
self._md5,
self._resourceID,
]
@@ -468,6 +559,38 @@
returnValue((yield self.component()).getOrganizer())
+ def _get_accessMode(self):
+ return accesstype_to_accessMode[self._access]
+
+ def _set_accessMode(self, value):
+ self._access = accessMode_to_type[value]
+
+ accessMode = property(_get_accessMode, _set_accessMode)
+
+ def _get_isScheduleObject(self):
+ return self._schedule_object
+
+ def _set_isScheduleObject(self, value):
+ self._schedule_object = value
+
+ isScheduleObject = property(_get_isScheduleObject, _set_isScheduleObject)
+
+ def _get_scheduleEtags(self):
+ return tuple(self._schedule_etags.split(",")) if self._schedule_etags else ()
+
+ def _set_scheduleEtags(self, value):
+ self._schedule_etags = ",".join(value) if value else ""
+
+ scheduleEtags = property(_get_scheduleEtags, _set_scheduleEtags)
+
+ def _get_hasPrivateComment(self):
+ return self._private_comments
+
+ def _set_hasPrivateComment(self, value):
+ self._private_comments = value
+
+ hasPrivateComment = property(_get_hasPrivateComment, _set_hasPrivateComment)
+
@inlineCallbacks
def createAttachmentWithName(self, name, contentType):
@@ -548,11 +671,8 @@
(
),
(
- PropertyName.fromElement(customxml.TwistedCalendarAccessProperty),
- PropertyName.fromElement(customxml.TwistedSchedulingObjectResource),
PropertyName.fromElement(caldavxml.ScheduleTag),
PropertyName.fromElement(customxml.TwistedScheduleMatchETags),
- PropertyName.fromElement(customxml.TwistedCalendarHasPrivateCommentsProperty),
PropertyName.fromElement(caldavxml.Originator),
PropertyName.fromElement(caldavxml.Recipient),
PropertyName.fromElement(customxml.ScheduleChanges),
Modified: CalendarServer/trunk/txdav/caldav/datastore/test/common.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/test/common.py 2010-11-02 01:03:39 UTC (rev 6552)
+++ CalendarServer/trunk/txdav/caldav/datastore/test/common.py 2010-11-02 16:46:24 UTC (rev 6553)
@@ -46,6 +46,7 @@
from twext.python.vcomponent import VComponent
from twistedcaldav.customxml import InviteNotification, InviteSummary
+from twistedcaldav.ical import Component
storePath = FilePath(__file__).parent().child("calendar_store")
@@ -661,10 +662,29 @@
calendar = yield self.calendarObjectUnderTest()
self.assertIsInstance(calendar.name(), basestring)
self.assertIsInstance(calendar.uid(), basestring)
+ self.assertIsInstance(calendar.accessMode, basestring)
+ self.assertIsInstance(calendar.isScheduleObject, bool)
+ self.assertIsInstance(calendar.scheduleEtags, tuple)
+ self.assertIsInstance(calendar.hasPrivateComment, bool)
self.assertIsInstance(calendar.md5(), basestring)
self.assertIsInstance(calendar.size(), int)
self.assertIsInstance(calendar.created(), int)
self.assertIsInstance(calendar.modified(), int)
+
+ self.assertEqual(calendar.accessMode, "")
+ self.assertEqual(calendar.isScheduleObject, False)
+ self.assertEqual(calendar.scheduleEtags, ())
+ self.assertEqual(calendar.hasPrivateComment, False)
+
+ calendar.accessMode = Component.ACCESS_PRIVATE
+ calendar.isScheduleObject = True
+ calendar.scheduleEtags = ("1234", "5678",)
+ calendar.hasPrivateComment = True
+
+ self.assertEqual(calendar.accessMode, Component.ACCESS_PRIVATE)
+ self.assertEqual(calendar.isScheduleObject, True)
+ self.assertEqual(calendar.scheduleEtags, ("1234", "5678",))
+ self.assertEqual(calendar.hasPrivateComment, True)
@inlineCallbacks
Modified: CalendarServer/trunk/txdav/caldav/datastore/test/test_sql.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/test/test_sql.py 2010-11-02 01:03:39 UTC (rev 6552)
+++ CalendarServer/trunk/txdav/caldav/datastore/test/test_sql.py 2010-11-02 16:46:24 UTC (rev 6553)
@@ -256,10 +256,12 @@
calendarStore = self._sqlCalendarStore
- # Provision the home now
+ # Provision the home and calendar now
txn = calendarStore.newTransaction()
home = yield txn.homeWithUID(ECALENDARTYPE, "uid1", create=True)
self.assertNotEqual(home, None)
+ cal = yield home.calendarWithName("calendar")
+ self.assertNotEqual(cal, None)
yield txn.commit()
txn1 = calendarStore.newTransaction()
@@ -268,12 +270,12 @@
home1 = yield txn1.homeWithUID(ECALENDARTYPE, "uid1", create=True)
home2 = yield txn2.homeWithUID(ECALENDARTYPE, "uid1", create=True)
- adbk1 = yield home1.calendarWithName("calendar")
- adbk2 = yield home2.calendarWithName("calendar")
+ cal1 = yield home1.calendarWithName("calendar")
+ cal2 = yield home2.calendarWithName("calendar")
@inlineCallbacks
def _defer1():
- yield adbk1.createObjectResourceWithName("1.ics", VComponent.fromString(
+ yield cal1.createObjectResourceWithName("1.ics", VComponent.fromString(
"BEGIN:VCALENDAR\r\n"
"VERSION:2.0\r\n"
"PRODID:-//Apple Inc.//iCal 4.0.1//EN\r\n"
@@ -318,7 +320,7 @@
@inlineCallbacks
def _defer2():
- yield adbk2.createObjectResourceWithName("2.ics", VComponent.fromString(
+ yield cal2.createObjectResourceWithName("2.ics", VComponent.fromString(
"BEGIN:VCALENDAR\r\n"
"VERSION:2.0\r\n"
"PRODID:-//Apple Inc.//iCal 4.0.1//EN\r\n"
@@ -363,6 +365,3 @@
yield d1
yield d2
-
-
-
Modified: CalendarServer/trunk/txdav/carddav/datastore/test/test_sql.py
===================================================================
--- CalendarServer/trunk/txdav/carddav/datastore/test/test_sql.py 2010-11-02 01:03:39 UTC (rev 6552)
+++ CalendarServer/trunk/txdav/carddav/datastore/test/test_sql.py 2010-11-02 16:46:24 UTC (rev 6553)
@@ -197,10 +197,12 @@
"""
addressbookStore = yield buildStore(self, self.notifierFactory)
- # Provision the home now
+ # Provision the home and addressbook now
txn = addressbookStore.newTransaction()
home = yield txn.homeWithUID(EADDRESSBOOKTYPE, "uid1", create=True)
self.assertNotEqual(home, None)
+ adbk = yield home.addressbookWithName("addressbook")
+ self.assertNotEqual(adbk, None)
yield txn.commit()
txn1 = addressbookStore.newTransaction()
Modified: CalendarServer/trunk/txdav/common/datastore/file.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/file.py 2010-11-02 01:03:39 UTC (rev 6552)
+++ CalendarServer/trunk/txdav/common/datastore/file.py 2010-11-02 16:46:24 UTC (rev 6553)
@@ -732,8 +732,20 @@
return obj
+ def emptyObjectWithName(self, name):
+ """
+ Sometimes we need an "empty" object that we can store meta-data on prior to actually
+ creating it with "real" data.
+ """
+ return self._objectResourceClass(name, self)
+
@writeOperation
- def createObjectResourceWithName(self, name, component):
+ def createObjectResourceWithName(self, name, component, objectResource=None):
+ """
+ As per L{emptyObjectWithName} we may get passed an already created object which we need
+ to use for the one being created in order to preserve any meta-data that might have been
+ set prior to the actual creation of the data.
+ """
if name.startswith("."):
raise ObjectResourceNameNotAllowedError(name)
@@ -741,7 +753,8 @@
if objectResourcePath.exists():
raise ObjectResourceNameAlreadyExistsError(name)
- objectResource = self._objectResourceClass(name, self)
+ if objectResource is None:
+ objectResource = self._objectResourceClass(name, self)
objectResource.setComponent(component, inserting=True)
self._cachedObjectResources[name] = objectResource
Modified: CalendarServer/trunk/txdav/common/datastore/sql.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/sql.py 2010-11-02 01:03:39 UTC (rev 6552)
+++ CalendarServer/trunk/txdav/common/datastore/sql.py 2010-11-02 16:46:24 UTC (rev 6553)
@@ -973,9 +973,20 @@
self._objects[name] = None
returnValue(None)
+ def emptyObjectWithName(self, name):
+ """
+ Sometimes we need an "empty" object that we can store meta-data on prior to actually
+ creating it with "real" data.
+ """
+ return self._objectResourceClass(self, name, None)
@inlineCallbacks
- def createObjectResourceWithName(self, name, component):
+ def createObjectResourceWithName(self, name, component, objectResource=None):
+ """
+ As per L{emptyObjectWithName} we may get passed an already created object which we need
+ to use for the one being created in order to preserve any meta-data that might have been
+ set prior to the actual creation of the data.
+ """
if name.startswith("."):
raise ObjectResourceNameNotAllowedError(name)
@@ -992,7 +1003,8 @@
if rows:
raise ObjectResourceNameAlreadyExistsError()
- objectResource = self._objectResourceClass(self, name, None)
+ if objectResource is None:
+ objectResource = self._objectResourceClass(self, name, None)
yield objectResource.setComponent(component, inserting=True)
self._objects[objectResource.name()] = objectResource
self._objects[objectResource.uid()] = objectResource
Modified: CalendarServer/trunk/txdav/common/datastore/sql_schema_v1.sql
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/sql_schema_v1.sql 2010-11-02 01:03:39 UTC (rev 6552)
+++ CalendarServer/trunk/txdav/common/datastore/sql_schema_v1.sql 2010-11-02 16:46:24 UTC (rev 6553)
@@ -128,9 +128,13 @@
ORGANIZER varchar(255),
ORGANIZER_OBJECT integer references CALENDAR_OBJECT,
RECURRANCE_MAX date, -- maximum date that recurrences have been expanded to.
- MD5 char(32) not null,
- CREATED timestamp default timezone('UTC', CURRENT_TIMESTAMP),
- MODIFIED timestamp default timezone('UTC', CURRENT_TIMESTAMP),
+ ACCESS integer default 0 not null,
+ SCHEDULE_OBJECT boolean default false not null,
+ SCHEDULE_ETAGS text default null,
+ PRIVATE_COMMENTS boolean default false not null,
+ MD5 char(32) not null,
+ CREATED timestamp default timezone('UTC', CURRENT_TIMESTAMP),
+ MODIFIED timestamp default timezone('UTC', CURRENT_TIMESTAMP),
unique(CALENDAR_RESOURCE_ID, RESOURCE_NAME)
@@ -152,6 +156,19 @@
insert into CALENDAR_OBJECT_ATTACHMENTS_MODE values (1, 'write');
+-- Enumeration of calendar access types
+
+create table CALENDAR_ACCESS_TYPE (
+ ID integer primary key,
+ DESCRIPTION varchar(32) not null unique
+);
+
+insert into CALENDAR_ACCESS_TYPE values (0, '' );
+insert into CALENDAR_ACCESS_TYPE values (1, 'public' );
+insert into CALENDAR_ACCESS_TYPE values (2, 'private' );
+insert into CALENDAR_ACCESS_TYPE values (3, 'confidential' );
+insert into CALENDAR_ACCESS_TYPE values (4, 'restricted' );
+
-----------------
-- Instance ID --
-----------------
Modified: CalendarServer/trunk/txdav/common/datastore/sql_tables.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/sql_tables.py 2010-11-02 01:03:39 UTC (rev 6552)
+++ CalendarServer/trunk/txdav/common/datastore/sql_tables.py 2010-11-02 16:46:24 UTC (rev 6553)
@@ -115,6 +115,10 @@
"column_RESOURCE_NAME" : "RESOURCE_NAME",
"column_TEXT" : "ICALENDAR_TEXT",
"column_UID" : "ICALENDAR_UID",
+ "column_ACCESS" : "ACCESS",
+ "column_SCHEDULE_OBJECT" : "SCHEDULE_OBJECT",
+ "column_SCHEDULE_ETAGS" : "SCHEDULE_ETAGS",
+ "column_PRIVATE_COMMENTS" : "PRIVATE_COMMENTS",
"column_MD5" : "MD5",
"column_CREATED" : "CREATED",
"column_MODIFIED" : "MODIFIED",
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20101102/151441a8/attachment-0001.html>
More information about the calendarserver-changes
mailing list