[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