[CalendarServer-changes] [11070] CalendarServer/branches/users/cdaboo/store-scheduling

source_changes at macosforge.org source_changes at macosforge.org
Fri Apr 19 13:52:47 PDT 2013


Revision: 11070
          http://trac.calendarserver.org//changeset/11070
Author:   cdaboo at apple.com
Date:     2013-04-19 13:52:47 -0700 (Fri, 19 Apr 2013)
Log Message:
-----------
Checkpoint: initial CDT implicit tests passing.

Modified Paths:
--------------
    CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/storebridge.py
    CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/scheduling/caldav/delivery.py
    CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/scheduling/caldav/test/test_delivery.py
    CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/scheduling/imip/delivery.py
    CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/scheduling/imip/test/test_outbound.py
    CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/scheduling/implicit.py
    CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/scheduling/ischedule/scheduler.py
    CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/scheduling/ischedule/test/test_delivery.py
    CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/scheduling/ischedule/test/test_utils.py
    CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/scheduling/itip.py
    CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/scheduling/processing.py
    CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/scheduling/scheduler.py
    CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/scheduling/test/test_implicit.py
    CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/scheduling/test/test_utils.py
    CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/sql.py
    CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/test/test_sql.py
    CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/icalendarstore.py
    CalendarServer/branches/users/cdaboo/store-scheduling/txdav/common/datastore/sql.py

Modified: CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/storebridge.py
===================================================================
--- CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/storebridge.py	2013-04-19 18:52:27 UTC (rev 11069)
+++ CalendarServer/branches/users/cdaboo/store-scheduling/twistedcaldav/storebridge.py	2013-04-19 20:52:47 UTC (rev 11070)
@@ -2320,11 +2320,11 @@
 
 
     @inlineCallbacks
-    def storeComponent(self, component):
+    def storeComponent(self, component, **kwargs):
 
         try:
             if self._newStoreObject:
-                yield self._newStoreObject.setComponent(component)
+                yield self._newStoreObject.setComponent(component, **kwargs)
                 returnValue(NO_CONTENT)
             else:
                 self._newStoreObject = (yield self._newStoreParent.createObjectResourceWithName(
@@ -2631,7 +2631,7 @@
                     self._parentResource._newStoreObject._txn._authz_uid = authz.record.guid
 
             try:
-                response = (yield self.storeComponent(component))
+                response = (yield self.storeComponent(component, smart_merge=schedule_tag_match))
             except ResourceDeletedError:
                 # This is OK - it just means the server deleted the resource during the PUT. We make it look
                 # like the PUT succeeded.
@@ -2675,6 +2675,21 @@
                 raise
 
 
+    @requiresPermissions(fromParent=[davxml.Unbind()])
+    def http_DELETE(self, request):
+        """
+        Override http_DELETE to do schedule tag behavior.
+        """
+        if not self.exists():
+            log.debug("Resource not found: %s" % (self,))
+            raise HTTPError(NOT_FOUND)
+
+        # Do schedule tag check
+        self.validIfScheduleMatch(request)
+
+        return self.storeRemove(request)
+
+
     @requiresPermissions(davxml.WriteContent())
     @inlineCallbacks
     def POST_handler_attachment(self, request, action):

Modified: CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/scheduling/caldav/delivery.py
===================================================================
--- CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/scheduling/caldav/delivery.py	2013-04-19 18:52:27 UTC (rev 11069)
+++ CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/scheduling/caldav/delivery.py	2013-04-19 20:52:47 UTC (rev 11070)
@@ -150,7 +150,7 @@
         if store_inbox:
             # Copy calendar to inbox
             try:
-                yield recipient.inbox._createCalendarObjectWithNameInternal(name, self.scheduler.calendar, ComponentUpdateState.INBOX)
+                child = yield recipient.inbox._createCalendarObjectWithNameInternal(name, self.scheduler.calendar, ComponentUpdateState.INBOX)
             except Exception as e:
                 # FIXME: Bare except
                 log.err("Could not store data in Inbox : %s %s" % (recipient.inbox, e,))
@@ -165,9 +165,9 @@
                 returnValue(False)
             else:
                 # Store CS:schedule-changes property if present
-                if changes:
-                    props = recipient.inbox.properties()
-                    props[PropertyName(*changes.qname())] = changes
+                if changes is not None:
+                    props = child.properties()
+                    props[PropertyName.fromElement(changes)] = changes
 
         responses.add(recipient.cuaddr, responsecode.OK, reqstatus=iTIPRequestStatus.MESSAGE_DELIVERED)
         if autoprocessed:

Modified: CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/scheduling/caldav/test/test_delivery.py
===================================================================
--- CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/scheduling/caldav/test/test_delivery.py	2013-04-19 18:52:27 UTC (rev 11069)
+++ CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/scheduling/caldav/test/test_delivery.py	2013-04-19 20:52:47 UTC (rev 11070)
@@ -24,7 +24,7 @@
 
 class CalDAV (twistedcaldav.test.util.TestCase):
     """
-    twistedcaldav.scheduling.caldav tests
+    txdav.caldav.datastore.scheduling.caldav tests
     """
 
     @inlineCallbacks

Modified: CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/scheduling/imip/delivery.py
===================================================================
--- CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/scheduling/imip/delivery.py	2013-04-19 18:52:27 UTC (rev 11069)
+++ CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/scheduling/imip/delivery.py	2013-04-19 20:52:47 UTC (rev 11070)
@@ -1,4 +1,4 @@
-# -*- test-case-name: twistedcaldav.scheduling.test.test_imip -*-
+# -*- test-case-name: txdav.caldav.datastore.scheduling.test.test_imip -*-
 ##
 # Copyright (c) 2005-2013 Apple Inc. All rights reserved.
 #

Modified: CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/scheduling/imip/test/test_outbound.py
===================================================================
--- CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/scheduling/imip/test/test_outbound.py	2013-04-19 18:52:27 UTC (rev 11069)
+++ CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/scheduling/imip/test/test_outbound.py	2013-04-19 20:52:47 UTC (rev 11070)
@@ -26,9 +26,9 @@
 from twistedcaldav.directory import augment
 from twistedcaldav.directory.xmlfile import XMLDirectoryService
 from twistedcaldav.ical import Component
-from twistedcaldav.scheduling.imip.outbound import IMIPInvitationWork
-from twistedcaldav.scheduling.imip.outbound import MailSender
-from twistedcaldav.scheduling.imip.outbound import StringFormatTemplateLoader
+from txdav.caldav.datastore.scheduling.imip.outbound import IMIPInvitationWork
+from txdav.caldav.datastore.scheduling.imip.outbound import MailSender
+from txdav.caldav.datastore.scheduling.imip.outbound import StringFormatTemplateLoader
 from twistedcaldav.test.util import TestCase, xmlFile, augmentsFile
 from txdav.common.datastore.test.util import buildStore
 

Modified: CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/scheduling/implicit.py
===================================================================
--- CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/scheduling/implicit.py	2013-04-19 18:52:27 UTC (rev 11069)
+++ CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/scheduling/implicit.py	2013-04-19 20:52:47 UTC (rev 11070)
@@ -61,6 +61,7 @@
         self.return_status = ImplicitScheduler.STATUS_OK
         self.logItems = {}
         self.allowed_to_schedule = True
+        self.suppress_refresh = False
 
     NotAllowedExceptionDetails = collections.namedtuple("NotAllowedExceptionDetails", ("type", "args", "kwargs",))
 
@@ -326,19 +327,19 @@
 
 
     @inlineCallbacks
-    def refreshAllAttendeesExceptSome(self, request, resource, except_attendees=(), only_attendees=None):
+    def refreshAllAttendeesExceptSome(self, txn, resource, except_attendees=(), only_attendees=None):
         """
         Refresh the iCalendar data for all attendees except the one specified in attendees.
         """
 
         self.txn = resource._txn
-        self.request = request
         self.resource = resource
-        self.calendar = (yield self.resource.iCalendarForUser(self.request))
+        self.calendar_home = self.resource.parentCollection().ownerHome()
+        self.calendar_owner = self.calendar_home.uid()
+        self.calendar = (yield self.resource.componentForUser())
         self.state = "organizer"
         self.action = "modify"
 
-        self.calendar_owner = None
         self.internal_request = True
         self.except_attendees = except_attendees
         self.only_refresh_attendees = only_attendees
@@ -347,7 +348,7 @@
 
         # Get some useful information from the calendar
         yield self.extractCalendarData()
-        self.organizerPrincipal = self.home.directoryService().recordWithCalendarUserAddress(self.organizer)
+        self.organizerPrincipal = self.calendar_home.directoryService().recordWithCalendarUserAddress(self.organizer)
         self.organizerAddress = (yield addressmapping.mapper.getCalendarUser(self.organizer, self.organizerPrincipal))
 
         # Originator is the organizer in this case
@@ -355,38 +356,38 @@
         self.originator = self.organizer
 
         # We want to suppress chatty iMIP messages when other attendees reply
-        self.request.suppressRefresh = False
+        self.suppress_refresh = False
 
         for attendee in self.calendar.getAllAttendeeProperties():
             if attendee.parameterValue("PARTSTAT", "NEEDS-ACTION").upper() == "NEEDS-ACTION":
-                self.request.suppressRefresh = True
+                self.suppress_refresh = True
 
-        if hasattr(self.request, "doing_attendee_refresh"):
-            self.request.doing_attendee_refresh += 1
+        if hasattr(self.txn, "doing_attendee_refresh"):
+            self.txn.doing_attendee_refresh += 1
         else:
-            self.request.doing_attendee_refresh = 1
+            self.txn.doing_attendee_refresh = 1
         try:
             refreshCount = (yield self.processRequests())
         finally:
-            self.request.doing_attendee_refresh -= 1
-            if self.request.doing_attendee_refresh == 0:
-                delattr(self.request, "doing_attendee_refresh")
+            self.txn.doing_attendee_refresh -= 1
+            if self.txn.doing_attendee_refresh == 0:
+                delattr(self.txn, "doing_attendee_refresh")
 
         if refreshCount:
             self.logItems["itip.refreshes"] = refreshCount
 
 
     @inlineCallbacks
-    def sendAttendeeReply(self, request, resource, calendar, attendee):
+    def sendAttendeeReply(self, txn, resource, calendar, attendee):
 
-        self.txn = resource._txn
-        self.request = request
+        self.txn = txn
         self.resource = resource
+        self.calendar_home = self.resource.parentCollection().ownerHome()
+        self.calendar_owner = self.calendar_home.uid()
         self.calendar = calendar
         self.action = "modify"
         self.state = "attendee"
 
-        self.calendar_owner = None
         self.internal_request = True
         self.changed_rids = None
 
@@ -940,7 +941,7 @@
 
                 # Do the PUT processing
                 log.info("Implicit CANCEL - organizer: '%s' to attendee: '%s', UID: '%s', RIDs: '%s'" % (self.organizer, attendee, self.uid, rids))
-                response = (yield scheduler.doSchedulingViaPUT(self.originator, (attendee,), itipmsg, internal_request=True))
+                response = (yield scheduler.doSchedulingViaPUT(self.originator, (attendee,), itipmsg, internal_request=True, suppress_refresh=self.suppress_refresh))
                 self.handleSchedulingResponse(response, True)
 
                 count += 1
@@ -983,7 +984,7 @@
 
                 # Do the PUT processing
                 log.info("Implicit REQUEST - organizer: '%s' to attendee: '%s', UID: '%s'" % (self.organizer, attendee, self.uid,))
-                response = (yield scheduler.doSchedulingViaPUT(self.originator, (attendee,), itipmsg, internal_request=True))
+                response = (yield scheduler.doSchedulingViaPUT(self.originator, (attendee,), itipmsg, internal_request=True, suppress_refresh=self.suppress_refresh))
                 self.handleSchedulingResponse(response, True)
 
                 count += 1

Modified: CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/scheduling/ischedule/scheduler.py
===================================================================
--- CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/scheduling/ischedule/scheduler.py	2013-04-19 18:52:27 UTC (rev 11069)
+++ CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/scheduling/ischedule/scheduler.py	2013-04-19 20:52:47 UTC (rev 11070)
@@ -37,10 +37,10 @@
 from txdav.caldav.datastore.scheduling.ischedule.dkim import DKIMVerifier, \
     DKIMVerificationError, DKIMMissingError
 from twext.web2.http_headers import MimeType
-from twistedcaldav.scheduling.ischedule.xml import ischedule_namespace
+from txdav.caldav.datastore.scheduling.ischedule.xml import ischedule_namespace
 from txdav.xml.base import WebDAVUnknownElement
-from twistedcaldav.scheduling.ischedule.utils import getIPsFromHost
-from twistedcaldav.scheduling.ischedule import xml
+from txdav.caldav.datastore.scheduling.ischedule.utils import getIPsFromHost
+from txdav.caldav.datastore.scheduling.ischedule import xml
 from twistedcaldav.ical import normalizeCUAddress
 
 """

Modified: CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/scheduling/ischedule/test/test_delivery.py
===================================================================
--- CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/scheduling/ischedule/test/test_delivery.py	2013-04-19 18:52:27 UTC (rev 11069)
+++ CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/scheduling/ischedule/test/test_delivery.py	2013-04-19 20:52:47 UTC (rev 11070)
@@ -18,13 +18,13 @@
 from twisted.internet.defer import inlineCallbacks
 from twisted.python.modules import getModule
 from twistedcaldav.config import config
-from twistedcaldav.scheduling.ischedule import utils
+from txdav.caldav.datastore.scheduling.ischedule import utils
 from twisted.names import client
 from txdav.caldav.datastore.scheduling.ischedule.delivery import ScheduleViaISchedule
 
 class CalDAV (twistedcaldav.test.util.TestCase):
     """
-    twistedcaldav.scheduling.caldav tests
+    txdav.caldav.datastore.scheduling.caldav tests
     """
 
     def tearDown(self):

Modified: CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/scheduling/ischedule/test/test_utils.py
===================================================================
--- CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/scheduling/ischedule/test/test_utils.py	2013-04-19 18:52:27 UTC (rev 11069)
+++ CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/scheduling/ischedule/test/test_utils.py	2013-04-19 20:52:47 UTC (rev 11070)
@@ -16,7 +16,7 @@
 
 from twisted.internet.defer import inlineCallbacks
 from twistedcaldav.config import config
-from twistedcaldav.scheduling.ischedule import utils
+from txdav.caldav.datastore.scheduling.ischedule import utils
 from twistedcaldav.test.util import TestCase
 from twisted.python.modules import getModule
 from twisted.names.authority import BindAuthority

Modified: CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/scheduling/itip.py
===================================================================
--- CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/scheduling/itip.py	2013-04-19 18:52:27 UTC (rev 11069)
+++ CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/scheduling/itip.py	2013-04-19 20:52:47 UTC (rev 11070)
@@ -103,7 +103,7 @@
             return None, None
 
         # Merge Organizer data with Attendee's own changes (VALARMs, Comment only for now).
-        from twistedcaldav.scheduling.icaldiff import iCalDiff
+        from txdav.caldav.datastore.scheduling.icaldiff import iCalDiff
         rids = iCalDiff(calendar, itip_message, False).whatIsDifferent()
 
         # Different behavior depending on whether a master component is present or not

Modified: CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/scheduling/processing.py
===================================================================
--- CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/scheduling/processing.py	2013-04-19 18:52:27 UTC (rev 11069)
+++ CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/scheduling/processing.py	2013-04-19 20:52:47 UTC (rev 11070)
@@ -44,6 +44,7 @@
 import uuid
 from txdav.caldav.icalendarstore import ComponentUpdateState, \
     ComponentRemoveState
+from twext.enterprise.locking import NamedLock
 
 """
 CalDAV implicit processing.
@@ -199,6 +200,8 @@
             # Update the organizer's copy of the event
             log.debug("ImplicitProcessing - originator '%s' to recipient '%s' processing METHOD:REPLY, UID: '%s' - updating event" % (self.originator.cuaddr, self.recipient.cuaddr, self.uid))
             self.organizer_calendar_resource = (yield self.writeCalendarResource(None, self.recipient_calendar_resource, self.recipient_calendar))
+            self.organizer_uid = self.organizer_calendar_resource.parentCollection().ownerHome().uid()
+            self.organizer_calendar_resource_id = self.organizer_calendar_resource.id()
 
             organizer = self.recipient_calendar.getOrganizer()
 
@@ -323,10 +326,10 @@
         @type only_attendees: C{tuple}
         """
         log.debug("ImplicitProcessing - refreshing UID: '%s', Attendees: %s" % (self.uid, ", ".join(only_attendees) if only_attendees else "all"))
-        from twistedcaldav.scheduling.implicit import ImplicitScheduler
+        from txdav.caldav.datastore.scheduling.implicit import ImplicitScheduler
         scheduler = ImplicitScheduler()
         yield scheduler.refreshAllAttendeesExceptSome(
-            self.request,
+            self.txn,
             organizer_resource,
             exclude_attendees,
             only_attendees=only_attendees,
@@ -343,44 +346,30 @@
         @type attendeesToProcess: C{list}
         """
 
-        # We need to get the UID lock for implicit processing whilst we send the auto-reply
-        # as the Organizer processing will attempt to write out data to other attendees to
-        # refresh them. To prevent a race we need a lock.
-        uidlock = MemcacheLock(
-            "ImplicitUIDLock",
-            self.uid,
-            timeout=config.Scheduling.Options.UIDLockTimeoutSeconds,
-            expire_time=config.Scheduling.Options.UIDLockExpirySeconds,
-        )
+        # The original transaction is still around but likely committed at this point, so we need a brand new
+        # transaction to do this work.
+        txn = yield self.txn.store().newTransaction("Delayed attendee refresh for UID: %s" % (self.uid,))
 
         try:
-            yield uidlock.acquire()
-        except MemcacheLockTimeoutError:
-            # Just try again to get the lock
-            reactor.callLater(2.0, self._doDelayedRefresh, attendeesToProcess)
-        else:
+            # We need to get the UID lock for implicit processing whilst we send the auto-reply
+            # as the Organizer processing will attempt to write out data to other attendees to
+            # refresh them. To prevent a race we need a lock.
+            yield NamedLock.acquire(txn, "ImplicitUIDLock:%s" % (hashlib.md5(self.uid).hexdigest(),))
 
-            # inNewTransaction wipes out the remembered resource<-> URL mappings in the
-            # request object but we need to be able to map the actual reply resource to its
-            # URL when doing auto-processing, so we have to sneak that mapping back in here.
-            txn = yield self.organizer_calendar_resource.inNewTransaction(self.request, label="Delayed attendee refresh")
-
-            try:
-                organizer_resource = (yield self.request.locateResource(self.organizer_calendar_resource._url))
-                if organizer_resource.exists():
-                    yield self._doRefresh(organizer_resource, only_attendees=attendeesToProcess)
-                else:
-                    log.debug("ImplicitProcessing - skipping refresh of missing UID: '%s'" % (self.uid,))
-            except Exception, e:
-                log.debug("ImplicitProcessing - refresh exception UID: '%s', %s" % (self.uid, str(e)))
-                yield txn.abort()
-            except:
-                log.debug("ImplicitProcessing - refresh bare exception UID: '%s'" % (self.uid,))
-                yield txn.abort()
+            organizer_home = (yield txn.calendarHomeForUID(self.organizer_uid))
+            organizer_resource = (yield organizer_home.objectResourceWithID(self.organizer_calendar_resource_id))
+            if organizer_resource is not None:
+                yield self._doRefresh(organizer_resource, only_attendees=attendeesToProcess)
             else:
-                yield txn.commit()
-        finally:
-            yield uidlock.clean()
+                log.debug("ImplicitProcessing - skipping refresh of missing UID: '%s'" % (self.uid,))
+        except Exception, e:
+            log.debug("ImplicitProcessing - refresh exception UID: '%s', %s" % (self.uid, str(e)))
+            yield txn.abort()
+        except:
+            log.debug("ImplicitProcessing - refresh bare exception UID: '%s'" % (self.uid,))
+            yield txn.abort()
+        else:
+            yield txn.commit()
 
 
     def _enqueueBatchRefresh(self):
@@ -712,9 +701,9 @@
                 self.request._rememberResource(resource, resource._url)
                 # Send out a reply
                 log.debug("ImplicitProcessing - recipient '%s' processing UID: '%s' - auto-reply: %s" % (self.recipient.cuaddr, self.uid, partstat))
-                from twistedcaldav.scheduling.implicit import ImplicitScheduler
+                from txdav.caldav.datastore.scheduling.implicit import ImplicitScheduler
                 scheduler = ImplicitScheduler()
-                yield scheduler.sendAttendeeReply(self.request, resource, calendar, self.recipient)
+                yield scheduler.sendAttendeeReply(txn, resource, calendar, self.recipient)
             except Exception, e:
                 log.debug("ImplicitProcessing - auto-reply exception UID: '%s', %s" % (self.uid, str(e)))
                 yield txn.abort()
@@ -947,10 +936,15 @@
         Write out the calendar resource (iTIP) message to the specified calendar, either over-writing the named
         resource or by creating a new one.
 
-        @param collection: the L{Calendar} for the calendar collection to store the resource in.
-        @param resource: the L{CalendarObject} for the resource name to write into, or {None} to write a new resource.
-        @param calendar: the L{Component} calendar to write.
-        @return: L{Deferred} -> L{CalDAVResource}
+        @param collection: the calendar collection to store the resource in.
+        @type: L{Calendar}
+        @param resource: the resource object to write to, or C{None} to write a new resource.
+        @type: L{CalendarObject}
+        @param calendar: the calendar data to write.
+        @type: L{Component}
+
+        @return: the object resource written to (either the one passed in or a new one)
+        @rtype: L{CalendarObject}
         """
 
         # Create a new name if one was not provided
@@ -960,7 +954,7 @@
             newchild = (yield collection._createCalendarObjectWithNameInternal(name, calendar, internal_state))
         else:
             yield resource._setComponentInternal(calendar, internal_state=internal_state)
-            newchild = None
+            newchild = resource
 
         returnValue(newchild)
 
@@ -1036,16 +1030,16 @@
             raise ImplicitProcessorException("5.1;Service unavailable")
 
         # Locate the originator's copy of the event
-        calendar_resource, _ignore_name, _ignore_collection, _ignore_uri = (yield getCalendarObjectForRecord(self.request, self.originator.principal, self.uid))
+        calendar_resource, _ignore_name, _ignore_collection, _ignore_uri = (yield getCalendarObjectForRecord(self.txn, self.originator.principal, self.uid))
         if not calendar_resource:
             raise ImplicitProcessorException("5.1;Service unavailable")
-        originator_calendar = (yield calendar_resource.iCalendarForUser(self.request))
+        originator_calendar = (yield calendar_resource.componentForUser(self.originator.principal.uid))
 
         # Get attendee's view of that
         originator_calendar.attendeesView((self.recipient.cuaddr,))
 
         # Locate the attendee's copy of the event if it exists.
-        recipient_resource, recipient_resource_name, recipient_collection, recipient_collection_uri = (yield getCalendarObjectForRecord(self.request, self.recipient.principal, self.uid))
+        recipient_resource, recipient_resource_name, recipient_collection, recipient_collection_uri = (yield getCalendarObjectForRecord(self.txn, self.recipient.principal, self.uid))
 
         # We only need to fix data that already exists
         if recipient_resource:

Modified: CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/scheduling/scheduler.py
===================================================================
--- CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/scheduling/scheduler.py	2013-04-19 18:52:27 UTC (rev 11069)
+++ CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/scheduling/scheduler.py	2013-04-19 20:52:47 UTC (rev 11070)
@@ -278,75 +278,6 @@
             self.recipients = list(attendees)
 
 
-    def loadFromRequestHeaders(self):
-        """
-        Load Originator and Recipient from request headers.
-        """
-        self.loadOriginatorFromRequestHeaders()
-        self.loadRecipientsFromRequestHeaders()
-
-
-    def loadOriginatorFromRequestHeaders(self):
-        # Must have Originator header
-        originator = self.request.headers.getRawHeaders("originator")
-        if originator is None or (len(originator) != 1):
-            log.err("%s request must have Originator header" % (self.method,))
-            raise HTTPError(self.errorResponse(
-                responsecode.FORBIDDEN,
-                self.errorElements["originator-missing"],
-                "Missing originator",
-            ))
-        else:
-            self.originator = originator[0]
-
-
-    def loadRecipientsFromRequestHeaders(self):
-        # Get list of Recipient headers
-        rawRecipients = self.request.headers.getRawHeaders("recipient")
-        if rawRecipients is None or (len(rawRecipients) == 0):
-            log.err("%s request must have at least one Recipient header" % (self.method,))
-            raise HTTPError(self.errorResponse(
-                responsecode.FORBIDDEN,
-                self.errorElements["recipient-missing"],
-                "No recipients",
-            ))
-
-        # Recipient header may be comma separated list
-        self.recipients = []
-        for rawRecipient in rawRecipients:
-            for r in rawRecipient.split(","):
-                r = r.strip()
-                if len(r):
-                    self.recipients.append(r)
-
-
-    @inlineCallbacks
-    def loadCalendarFromRequest(self):
-        # Must be content-type text/calendar
-        contentType = self.request.headers.getHeader("content-type")
-        if contentType is not None and (contentType.mediaType, contentType.mediaSubtype) != ("text", "calendar"):
-            log.err("MIME type %s not allowed in calendar collection" % (contentType,))
-            raise HTTPError(self.errorResponse(
-                responsecode.FORBIDDEN,
-                self.errorElements["invalid-calendar-data-type"],
-                "Data is not calendar data",
-            ))
-
-        # Parse the calendar object from the HTTP request stream
-        try:
-            self.calendar = (yield Component.fromIStream(self.request.stream))
-
-            self.preProcessCalendarData()
-        except:
-            # FIXME: Bare except
-            log.err("Error while handling %s: %s" % (self.method, Failure(),))
-            raise HTTPError(self.errorResponse(
-                responsecode.FORBIDDEN,
-                self.errorElements["invalid-calendar-data"],
-                description="Can't parse calendar data"
-            ))
-
-
     def preProcessCalendarData(self):
         """
         After loading calendar data from the request, do some optional processing of it. This method will be
@@ -596,11 +527,11 @@
 
         # Now process partitioned recipients
         if partitioned_recipients:
-            yield self.generateRemoteSchedulingResponses(partitioned_recipients, responses, freebusy, getattr(self.request, 'doing_attendee_refresh', False))
+            yield self.generateRemoteSchedulingResponses(partitioned_recipients, responses, freebusy, getattr(self.txn, 'doing_attendee_refresh', False))
 
         # Now process other server recipients
         if otherserver_recipients:
-            yield self.generateRemoteSchedulingResponses(otherserver_recipients, responses, freebusy, getattr(self.request, 'doing_attendee_refresh', False))
+            yield self.generateRemoteSchedulingResponses(otherserver_recipients, responses, freebusy, getattr(self.txn, 'doing_attendee_refresh', False))
 
         # To reduce chatter, we suppress certain messages
         if not self.suppress_refresh:

Modified: CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/scheduling/test/test_implicit.py
===================================================================
--- CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/scheduling/test/test_implicit.py	2013-04-19 18:52:27 UTC (rev 11069)
+++ CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/scheduling/test/test_implicit.py	2013-04-19 20:52:47 UTC (rev 11070)
@@ -21,7 +21,7 @@
 from twext.web2 import responsecode
 from twext.web2.http import HTTPError
 
-from twisted.internet.defer import succeed, inlineCallbacks
+from twisted.internet.defer import succeed, inlineCallbacks, returnValue
 from twisted.trial.unittest import TestCase
 
 from twistedcaldav.ical import Component
@@ -36,6 +36,7 @@
 
 import hashlib
 import sys
+from twistedcaldav.config import config
 
 class FakeScheduler(object):
     """
@@ -46,7 +47,7 @@
         self.recipients = recipients
 
 
-    def doSchedulingViaPUT(self, originator, recipients, calendar, internal_request=False):
+    def doSchedulingViaPUT(self, originator, recipients, calendar, internal_request=False, suppress_refresh=False):
         self.recipients.extend(recipients)
         return succeed(ScheduleResponseQueue("FAKE", responsecode.OK))
 
@@ -904,6 +905,12 @@
             "inbox": {
             },
         },
+        "user03": {
+            "calendar_1": {
+            },
+            "inbox": {
+            },
+        },
     }
 
 
@@ -915,6 +922,44 @@
 
 
     @inlineCallbacks
+    def _createCalendarObject(self, data, user, name):
+        calendar_collection = (yield self.calendarUnderTest(home=user))
+        yield calendar_collection.createCalendarObjectWithName("test.ics", Component.fromString(data))
+        yield self.commit()
+
+
+    @inlineCallbacks
+    def _listCalendarObjects(self, user, collection_name="calendar_1"):
+        collection = (yield self.calendarUnderTest(name=collection_name, home=user))
+        items = (yield collection.listCalendarObjects())
+        yield self.commit()
+        returnValue(items)
+
+
+    @inlineCallbacks
+    def _getCalendarData(self, user, name=None):
+        if name is None:
+            items = (yield self._listCalendarObjects(user))
+            name = items[0]
+
+        calendar_resource = (yield self.calendarObjectUnderTest(name=name, home=user))
+        calendar = (yield calendar_resource.component())
+        yield self.commit()
+        returnValue(str(calendar).replace("\r\n ", ""))
+
+
+    @inlineCallbacks
+    def _setCalendarData(self, data, user, name=None):
+        if name is None:
+            items = (yield self._listCalendarObjects(user))
+            name = items[0]
+
+        calendar_resource = (yield self.calendarObjectUnderTest(name=name, home=user))
+        yield calendar_resource.setComponent(Component.fromString(data))
+        yield self.commit()
+
+
+    @inlineCallbacks
     def test_testImplicitSchedulingPUT_ScheduleState(self):
         """
         Test that checkImplicitState() always returns True for any organizer, valid or not.
@@ -1102,21 +1147,17 @@
 END:VEVENT
 END:VCALENDAR
 """
-        calendar_collection = (yield self.calendarUnderTest(home="user01"))
-        calendar = Component.fromString(data)
-        yield calendar_collection.createCalendarObjectWithName("test.ics", calendar)
-        yield self.commit()
+        yield self._createCalendarObject(data, "user01", "test.ics")
 
-        calendar_collection2 = (yield self.calendarUnderTest(home="user02"))
-        items = (yield calendar_collection2.listCalendarObjects())
-        self.assertEqual(len(items), 1)
-        self.assertTrue(items[0].startswith(hashlib.md5("12345-67890").hexdigest()))
-        inbox2 = (yield self.calendarUnderTest(name="inbox", home="user02"))
-        items = (yield inbox2.listCalendarObjects())
-        self.assertEqual(len(items), 1)
-        self.assertTrue(items[0].startswith(hashlib.md5("12345-67890").hexdigest()))
+        list2 = (yield self._listCalendarObjects("user02"))
+        self.assertEqual(len(list2), 1)
+        self.assertTrue(list2[0].startswith(hashlib.md5("12345-67890").hexdigest()))
 
+        list2 = (yield self._listCalendarObjects("user02", "inbox"))
+        self.assertEqual(len(list2), 1)
+        self.assertTrue(list2[0].startswith(hashlib.md5("12345-67890").hexdigest()))
 
+
     @inlineCallbacks
     def test_doImplicitScheduling_UpdateOrganizerEvent(self):
         """
@@ -1151,27 +1192,20 @@
 END:VEVENT
 END:VCALENDAR
 """
-        calendar_collection = (yield self.calendarUnderTest(home="user01"))
-        calendar = Component.fromString(data1)
-        yield calendar_collection.createCalendarObjectWithName("test.ics", calendar)
-        yield self.commit()
+        yield self._createCalendarObject(data1, "user01", "test.ics")
 
-        calendar_resource = (yield self.calendarObjectUnderTest(name="test.ics", home="user01"))
-        calendar = Component.fromString(data2)
-        yield calendar_resource.setComponent(calendar)
-        yield self.commit()
+        yield self._setCalendarData(data2, "user01", "test.ics")
 
-        calendar_collection2 = (yield self.calendarUnderTest(home="user02"))
-        items = (yield calendar_collection2.listCalendarObjects())
-        self.assertEqual(len(items), 1)
-        self.assertTrue(items[0].startswith(hashlib.md5("12345-67890").hexdigest()))
-        inbox2 = (yield self.calendarUnderTest(name="inbox", home="user02"))
-        items = (yield inbox2.listCalendarObjects())
-        self.assertEqual(len(items), 2)
-        self.assertTrue(items[0].startswith(hashlib.md5("12345-67890").hexdigest()))
-        self.assertTrue(items[1].startswith(hashlib.md5("12345-67890").hexdigest()))
+        list2 = (yield self._listCalendarObjects("user02"))
+        self.assertEqual(len(list2), 1)
+        self.assertTrue(list2[0].startswith(hashlib.md5("12345-67890").hexdigest()))
 
+        list2 = (yield self._listCalendarObjects("user02", "inbox"))
+        self.assertEqual(len(list2), 2)
+        self.assertTrue(list2[0].startswith(hashlib.md5("12345-67890").hexdigest()))
+        self.assertTrue(list2[1].startswith(hashlib.md5("12345-67890").hexdigest()))
 
+
     @inlineCallbacks
     def test_doImplicitScheduling_DeleteOrganizerEvent(self):
         """
@@ -1192,41 +1226,22 @@
 END:VEVENT
 END:VCALENDAR
 """
-        data2 = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTAMP:20080601T120000Z
-DTSTART:20080601T130000Z
-DTEND:20080601T140000Z
-ORGANIZER;CN="User 01":mailto:user01 at example.com
-ATTENDEE:mailto:user01 at example.com
-ATTENDEE:mailto:user02 at example.com
-END:VEVENT
-END:VCALENDAR
-"""
-        calendar_collection = (yield self.calendarUnderTest(home="user01"))
-        calendar = Component.fromString(data1)
-        yield calendar_collection.createCalendarObjectWithName("test.ics", calendar)
-        yield self.commit()
+        yield self._createCalendarObject(data1, "user01", "test.ics")
 
         calendar_resource = (yield self.calendarObjectUnderTest(name="test.ics", home="user01"))
-        calendar = Component.fromString(data2)
         yield calendar_resource.remove()
         yield self.commit()
 
-        calendar_collection2 = (yield self.calendarUnderTest(home="user02"))
-        items = (yield calendar_collection2.listCalendarObjects())
-        self.assertEqual(len(items), 1)
-        self.assertTrue(items[0].startswith(hashlib.md5("12345-67890").hexdigest()))
-        inbox2 = (yield self.calendarUnderTest(name="inbox", home="user02"))
-        items = (yield inbox2.listCalendarObjects())
-        self.assertEqual(len(items), 2)
-        self.assertTrue(items[0].startswith(hashlib.md5("12345-67890").hexdigest()))
-        self.assertTrue(items[1].startswith(hashlib.md5("12345-67890").hexdigest()))
+        list2 = (yield self._listCalendarObjects("user02"))
+        self.assertEqual(len(list2), 1)
+        self.assertTrue(list2[0].startswith(hashlib.md5("12345-67890").hexdigest()))
 
+        list2 = (yield self._listCalendarObjects("user02", "inbox"))
+        self.assertEqual(len(list2), 2)
+        self.assertTrue(list2[0].startswith(hashlib.md5("12345-67890").hexdigest()))
+        self.assertTrue(list2[1].startswith(hashlib.md5("12345-67890").hexdigest()))
 
+
     @inlineCallbacks
     def test_doImplicitScheduling_AttendeeEventNoOrganizerEvent(self):
         """
@@ -1247,26 +1262,19 @@
 END:VEVENT
 END:VCALENDAR
 """
-        calendar_collection = (yield self.calendarUnderTest(home="user02"))
-        calendar = Component.fromString(data)
         try:
-            yield calendar_collection.createCalendarObjectWithName("test.ics", calendar)
+            yield self._createCalendarObject(data, "user02", "test.ics")
         except AttendeeAllowedError:
             pass
         except:
             self.fail("Wrong exception raised: %s" % (sys.exc_info()[0].__name__,))
         else:
             self.fail("Exception not raised")
-        yield self.commit()
 
-        calendar_collection = (yield self.calendarUnderTest(home="user02"))
-        calendar = Component.fromString(data)
+        list1 = (yield self._listCalendarObjects("user01", "inbox"))
+        self.assertEqual(len(list1), 0)
 
-        inbox1 = (yield self.calendarUnderTest(name="inbox", home="user01"))
-        items = (yield inbox1.listCalendarObjects())
-        self.assertEqual(len(items), 0)
 
-
     @inlineCallbacks
     def test_doImplicitScheduling_AttendeeReply(self):
         """
@@ -1301,32 +1309,101 @@
 END:VEVENT
 END:VCALENDAR
 """
-        calendar_collection = (yield self.calendarUnderTest(home="user01"))
-        calendar1 = Component.fromString(data1)
-        yield calendar_collection.createCalendarObjectWithName("test.ics", calendar1)
-        yield self.commit()
+        yield self._createCalendarObject(data1, "user01", "test.ics")
 
-        calendar_resource1 = (yield self.calendarObjectUnderTest(name="test.ics", home="user01"))
-        calendar1 = (yield calendar_resource1.component())
-        self.assertTrue("SCHEDULE-STATUS=1.2" in str(calendar1).replace("\r\n ", ""))
+        calendar1 = (yield self._getCalendarData("user01", "test.ics"))
+        self.assertTrue("SCHEDULE-STATUS=1.2" in calendar1)
 
-        inbox2 = (yield self.calendarUnderTest(name="inbox", home="user02"))
-        items = (yield inbox2.listCalendarObjects())
-        self.assertEqual(len(items), 1)
-        yield self.commit()
+        list2 = (yield self._listCalendarObjects("user02", "inbox"))
+        self.assertEqual(len(list2), 1)
 
-        calendar_collection2 = (yield self.calendarUnderTest(home="user02"))
-        items = (yield calendar_collection2.listCalendarObjects())
-        calendar_resource2 = (yield self.calendarObjectUnderTest(name=items[0], home="user02",))
-        calendar2 = Component.fromString(data2)
-        yield calendar_resource2.setComponent(calendar2)
-        yield self.commit()
+        yield self._setCalendarData(data2, "user02")
 
-        inbox1 = (yield self.calendarUnderTest(name="inbox", home="user01"))
-        items = (yield inbox1.listCalendarObjects())
-        self.assertEqual(len(items), 1)
+        list1 = (yield self._listCalendarObjects("user01", "inbox"))
+        self.assertEqual(len(list1), 1)
 
-        calendar_resource1 = (yield self.calendarObjectUnderTest(name="test.ics", home="user01"))
-        calendar1 = (yield calendar_resource1.component())
-        self.assertTrue("SCHEDULE-STATUS=2.0" in str(calendar1).replace("\r\n ", ""))
-        self.assertTrue("PARTSTAT=ACCEPTED" in str(calendar1).replace("\r\n ", ""))
+        calendar1 = (yield self._getCalendarData("user01", "test.ics"))
+        self.assertTrue("SCHEDULE-STATUS=2.0" in calendar1)
+        self.assertTrue("PARTSTAT=ACCEPTED" in calendar1)
+
+
+    @inlineCallbacks
+    def test_doImplicitScheduling_refreshAllAttendeesExceptSome(self):
+        """
+        Test that doImplicitScheduling delivers scheduling messages to attendees who can then reply.
+        """
+
+        data1 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-attendee-reply
+DTSTAMP:20080601T120000Z
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user01 at example.com
+ATTENDEE:mailto:user01 at example.com
+ATTENDEE:mailto:user02 at example.com
+ATTENDEE:mailto:user03 at example.com
+END:VEVENT
+END:VCALENDAR
+"""
+        data2 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-attendee-reply
+DTSTAMP:20080601T120000Z
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user01 at example.com
+ATTENDEE:mailto:user01 at example.com
+ATTENDEE;PARTSTAT=ACCEPTED:mailto:user02 at example.com
+ATTENDEE:mailto:user03 at example.com
+END:VEVENT
+END:VCALENDAR
+"""
+
+        # Need refreshes to occur immediately, not via reactor.callLater
+        self.patch(config.Scheduling.Options, "AttendeeRefreshBatch", False)
+
+        yield self._createCalendarObject(data1, "user01", "test.ics")
+
+        list1 = (yield self._listCalendarObjects("user01", "inbox"))
+        self.assertEqual(len(list1), 0)
+
+        calendar1 = (yield self._getCalendarData("user01", "test.ics"))
+        self.assertTrue("SCHEDULE-STATUS=1.2" in calendar1)
+
+        list2 = (yield self._listCalendarObjects("user02", "inbox"))
+        self.assertEqual(len(list2), 1)
+
+        calendar2 = (yield self._getCalendarData("user02"))
+        self.assertTrue("PARTSTAT=ACCEPTED" not in calendar2)
+
+        list3 = (yield self._listCalendarObjects("user03", "inbox"))
+        self.assertEqual(len(list3), 1)
+
+        calendar3 = (yield self._getCalendarData("user03"))
+        self.assertTrue("PARTSTAT=ACCEPTED" not in calendar3)
+
+        yield self._setCalendarData(data2, "user02")
+
+        list1 = (yield self._listCalendarObjects("user01", "inbox"))
+        self.assertEqual(len(list1), 1)
+
+        calendar1 = (yield self._getCalendarData("user01", "test.ics"))
+        self.assertTrue("SCHEDULE-STATUS=2.0" in calendar1)
+        self.assertTrue("PARTSTAT=ACCEPTED" in calendar1)
+
+        list2 = (yield self._listCalendarObjects("user02", "inbox"))
+        self.assertEqual(len(list2), 1)
+
+        calendar2 = (yield self._getCalendarData("user02"))
+        self.assertTrue("PARTSTAT=ACCEPTED" in calendar2)
+
+        list3 = (yield self._listCalendarObjects("user03", "inbox"))
+        self.assertEqual(len(list3), 1)
+
+        calendar3 = (yield self._getCalendarData("user03"))
+        self.assertTrue("PARTSTAT=ACCEPTED" in calendar3)

Modified: CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/scheduling/test/test_utils.py
===================================================================
--- CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/scheduling/test/test_utils.py	2013-04-19 18:52:27 UTC (rev 11069)
+++ CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/scheduling/test/test_utils.py	2013-04-19 20:52:47 UTC (rev 11070)
@@ -146,7 +146,7 @@
     @inlineCallbacks
     def test_getCalendarObjectForRecord(self):
         """
-        Test that L{twistedcaldav.scheduling.utils.getCalendarObjectForRecord} detects and removes
+        Test that L{txdav.caldav.datastore.scheduling.utils.getCalendarObjectForRecord} detects and removes
         resources with duplicate UIDs in the same calendar home.
         """
 

Modified: CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/sql.py
===================================================================
--- CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/sql.py	2013-04-19 18:52:27 UTC (rev 11069)
+++ CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/sql.py	2013-04-19 20:52:47 UTC (rev 11070)
@@ -1451,8 +1451,6 @@
         """
 
         # Basic validation
-        #TODO: figure out what to do about etag/schedule-tag
-        self.validIfScheduleMatch(False, False, internal_state)
 
         # Do validation on external requests
         if internal_state == ComponentUpdateState.NORMAL:
@@ -1492,30 +1490,6 @@
             self.validAccess(component, inserting, internal_state)
 
 
-    def validIfScheduleMatch(self, etag_match, schedule_tag, internal_state):
-        """
-        Check for If-ScheduleTag-Match header behavior.
-        """
-        # Only when a direct request
-        self.schedule_tag_match = False
-        if not self.calendar().isInbox() and internal_state == ComponentUpdateState.NORMAL:
-            if schedule_tag:
-                self._validIfScheduleMatch(self.request)
-                self.schedule_tag_match = True
-            elif config.Scheduling.CalDAV.ScheduleTagCompatibility:
-                # Compatibility with old clients. Policy:
-                #
-                # 1. If If-Match header is not present, never do smart merge.
-                # 2. If If-Match is present and the specified ETag is
-                #    considered a "weak" match to the current Schedule-Tag,
-                #    then do smart merge, else reject with a 412.
-                #
-                # Actually by the time we get here the precondition will
-                # already have been tested and found to be OK, so we can just
-                # always do smart merge now if If-Match is present.
-                self.schedule_tag_match = etag_match is not None
-
-
     def validCalendarDataCheck(self, component, inserting):
         """
         Check that the calendar data is valid iCalendar.
@@ -1915,9 +1889,9 @@
             #    from the Organizer then the schedule tag changes.
 
             # Check what kind of processing is going on
-            change_scheduletag = internal_state not in (
-                ComponentUpdateState.ORGANIZER_ITIP_UPDATE,
-                ComponentUpdateState.ATTENDEE_ITIP_REFRESH,
+            change_scheduletag = not (
+                (internal_state == ComponentUpdateState.ORGANIZER_ITIP_UPDATE) or
+                (internal_state == ComponentUpdateState.ATTENDEE_ITIP_UPDATE) and not hasattr(self._txn, "doing_attendee_refresh")
             )
 
             if change_scheduletag or not self.scheduleTag:
@@ -1969,17 +1943,17 @@
                     raise UIDExistsError("UID already exists.")
 
 
-    def setComponent(self, component, inserting=False):
+    def setComponent(self, component, inserting=False, smart_merge=False):
         """
         Public api for storing a component. This will do full data validation checks on the specified component.
         Scheduling will be done automatically.
         """
 
-        return self._setComponentInternal(component, inserting, ComponentUpdateState.NORMAL)
+        return self._setComponentInternal(component, inserting, ComponentUpdateState.NORMAL, smart_merge)
 
 
     @inlineCallbacks
-    def _setComponentInternal(self, component, inserting=False, internal_state=ComponentUpdateState.NORMAL):
+    def _setComponentInternal(self, component, inserting=False, internal_state=ComponentUpdateState.NORMAL, smart_merge=False):
         """
         Setting the component internally to the store itself. This will bypass a whole bunch of data consistency checks
         on the assumption that those have been done prior to the component data being provided, provided the flag is set.
@@ -1987,6 +1961,7 @@
         """
 
         self._componentChanged = False
+        self.schedule_tag_match = not self.calendar().isInbox() and internal_state == ComponentUpdateState.NORMAL and smart_merge
 
         if internal_state != ComponentUpdateState.RAW:
             # Handle all validation operations here.
@@ -2466,14 +2441,6 @@
 
         isinbox = self._calendar.isInbox()
 
-        # Do If-Schedule-Tag-Match behavior first
-        # Important: this should only ever be done when storeRemove is called
-        # directly as a result of an HTTP DELETE to ensure the proper If-
-        # header is used in this test.
-        if not isinbox and internal_state == ComponentRemoveState.NORMAL:
-            #TODO: figure out what to do about etag/schedule-tag
-            self.validIfScheduleMatch(False, False, internal_state)
-
         # Pre-flight scheduling operation
         scheduler = None
         if not isinbox and internal_state == ComponentRemoveState.NORMAL:

Modified: CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/test/test_sql.py
===================================================================
--- CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/test/test_sql.py	2013-04-19 18:52:27 UTC (rev 11069)
+++ CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/datastore/test/test_sql.py	2013-04-19 20:52:47 UTC (rev 11070)
@@ -1149,7 +1149,7 @@
         home = yield self.transactionUnderTest().calendarHomeWithUID("home_defaults")
         self.assertEqual(home._default_events, default_events._resourceID)
         self.assertEqual(home._default_tasks, default_tasks._resourceID)
-        yield home.removeCalendarWithName("todos")
+        yield home.removeCalendarWithName("calendar_1-vtodo")
         yield self.commit()
 
         home = yield self.transactionUnderTest().calendarHomeWithUID("home_defaults")

Modified: CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/icalendarstore.py
===================================================================
--- CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/icalendarstore.py	2013-04-19 18:52:27 UTC (rev 11069)
+++ CalendarServer/branches/users/cdaboo/store-scheduling/txdav/caldav/icalendarstore.py	2013-04-19 20:52:47 UTC (rev 11070)
@@ -74,10 +74,6 @@
                             iTIP message. Some validation and implicit scheduling is not done. Schedule-Tag
                             is changed.
 
-    ATTENDEE_ITIP_REFRESH - the store is an update to an attendee's data caused by processing an incoming
-                            iTIP message. Some validation and implicit scheduling is not done. Schedule-Tag
-                            is changed.
-
     RAW                   - store the supplied data as-is without any processing or validation. This is used
                             for unit testing purposes only.
     """
@@ -86,14 +82,12 @@
     INBOX = NamedConstant()
     ORGANIZER_ITIP_UPDATE = NamedConstant()
     ATTENDEE_ITIP_UPDATE = NamedConstant()
-    ATTENDEE_ITIP_REFRESH = NamedConstant()
     RAW = NamedConstant()
 
     NORMAL.description = "normal"
     INBOX.description = "inbox"
     ORGANIZER_ITIP_UPDATE.description = "organizer-update"
     ATTENDEE_ITIP_UPDATE.description = "attendee-update"
-    ATTENDEE_ITIP_REFRESH.description = "attendee-refresh"
     RAW.description = "raw"
 
 

Modified: CalendarServer/branches/users/cdaboo/store-scheduling/txdav/common/datastore/sql.py
===================================================================
--- CalendarServer/branches/users/cdaboo/store-scheduling/txdav/common/datastore/sql.py	2013-04-19 18:52:27 UTC (rev 11069)
+++ CalendarServer/branches/users/cdaboo/store-scheduling/txdav/common/datastore/sql.py	2013-04-19 20:52:47 UTC (rev 11070)
@@ -1630,6 +1630,16 @@
         return "<%s: %s>" % (self.__class__.__name__, self._resourceID)
 
 
+    def id(self):
+        """
+        Retrieve the store identifier for this home.
+
+        @return: store identifier.
+        @rtype: C{int}
+        """
+        return self._resourceID
+
+
     def uid(self):
         """
         Retrieve the unique identifier for this home.
@@ -3380,6 +3390,16 @@
         yield self._loadPropertyStore()
 
 
+    def id(self):
+        """
+        Retrieve the store identifier for this collection.
+
+        @return: store identifier.
+        @rtype: C{int}
+        """
+        return self._resourceID
+
+
     @property
     def _txn(self):
         return self._home._txn
@@ -4301,6 +4321,16 @@
         return "<%s: %s>" % (self.__class__.__name__, self._resourceID)
 
 
+    def id(self):
+        """
+        Retrieve the store identifier for this object resource.
+
+        @return: store identifier.
+        @rtype: C{int}
+        """
+        return self._resourceID
+
+
     @property
     def _txn(self):
         return self._parentCollection._txn
@@ -4314,6 +4344,10 @@
         return self._txn.store().directoryService()
 
 
+    def parentCollection(self):
+        return self._parentCollection
+
+
     @classmethod
     def _selectForUpdateQuery(cls, nowait): #@NoSelf
         """
@@ -4578,6 +4612,16 @@
         return "<%s: %s>" % (self.__class__.__name__, self._resourceID)
 
 
+    def id(self):
+        """
+        Retrieve the store identifier for this collection.
+
+        @return: store identifier.
+        @rtype: C{int}
+        """
+        return self._resourceID
+
+
     def name(self):
         return "notification"
 
@@ -4930,6 +4974,16 @@
         return self._propertyStore
 
 
+    def id(self):
+        """
+        Retrieve the store identifier for this object.
+
+        @return: store identifier.
+        @rtype: C{int}
+        """
+        return self._resourceID
+
+
     @property
     def _txn(self):
         return self._home._txn
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20130419/5d052ba1/attachment-0001.html>


More information about the calendarserver-changes mailing list