0010040: allow concurrent updates via ActiveSync
authorMichael Spahn <m.spahn@metaways.de>
Tue, 8 Jul 2014 14:17:21 +0000 (16:17 +0200)
committerPhilipp Schüle <p.schuele@metaways.de>
Fri, 11 Jul 2014 09:59:19 +0000 (11:59 +0200)
* adds current seq of base event to exceptions
* this disables concurrency check for updating events via ActiveSync

https://forge.tine20.org/mantisbt/view.php?id=10040

Change-Id: Ib5e462b956dd4bc9544ad47d9a3b208d7bb4095f
Reviewed-on: http://gerrit.tine20.com/customers/836
Tested-by: Jenkins CI (http://ci.tine20.com/)
Reviewed-by: Philipp Schüle <p.schuele@metaways.de>
tests/tine20/ActiveSync/Controller/CalendarTests.php
tine20/ActiveSync/Controller/Calendar.php

index 911b0f7..4f8d5c7 100644 (file)
@@ -303,7 +303,191 @@ Zeile 3</AirSyncBase:Data>
             <InstanceId>20121125T130000Z</InstanceId>
         </Request>
     </MeetingResponse>';
-    
+
+    protected $_testConcurrentUpdate = '<?xml version="1.0" encoding="utf-8"?>
+    <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
+    <Sync xmlns="uri:AirSync" xmlns:AirSyncBase="uri:AirSyncBase" xmlns:Calendar="uri:Calendar">
+      <Collections>
+        <Collection>
+          <SyncKey>26</SyncKey>
+          <CollectionId>calendar-root</CollectionId>
+          <GetChanges/>
+          <WindowSize>25</WindowSize>
+          <Options>
+            <FilterType>4</FilterType>
+            <BodyPreference xmlns="uri:AirSyncBase">
+              <Type>1</Type>
+              <TruncationSize>32768</TruncationSize>
+            </BodyPreference>
+          </Options>
+          <Commands>
+            <Change>
+              <ServerId>b2cb312482f641185b205591f40f78a47ea9f667</ServerId>
+              <ApplicationData>
+                <Timezone xmlns="uri:Calendar">xP///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoAAAAFAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAFAAMAAAAAAAAAxP///w==</Timezone>
+                <AllDayEvent xmlns="uri:Calendar">0</AllDayEvent>
+                <BusyStatus xmlns="uri:Calendar">2</BusyStatus>
+                <DtStamp xmlns="uri:Calendar">20131106T072501Z</DtStamp>
+                <EndTime xmlns="uri:Calendar">20140113T140000Z</EndTime>
+                <Sensitivity xmlns="uri:Calendar">0</Sensitivity>
+                <Subject xmlns="uri:Calendar">Mittagspause</Subject>
+                <StartTime xmlns="uri:Calendar">20140113T120000Z</StartTime>
+                <UID xmlns="uri:Calendar">1a5010a3d46316a2709cda40683911f95a856665</UID>
+                <MeetingStatus xmlns="uri:Calendar">1</MeetingStatus>
+                <Attendees xmlns="uri:Calendar">
+                  <Attendee>
+                    <Name>Unittest, Tine</Name>
+                    <Email>unittest@tine20.org</Email>
+                    <AttendeeType>1</AttendeeType>
+                  </Attendee>
+                </Attendees>
+                <Recurrence xmlns="uri:Calendar">
+                  <Type>1</Type>
+                  <Interval>1</Interval>
+                  <DayOfWeek>62</DayOfWeek>
+                  <FirstDayOfWeek>1</FirstDayOfWeek>
+                </Recurrence>
+                <Exceptions xmlns="uri:Calendar">
+                  <Exception>
+                    <Body xmlns="uri:AirSyncBase">
+                      <Type>1</Type>
+                      <Data/>
+                    </Body>
+                    <Deleted>0</Deleted>
+                    <ExceptionStartTime>20141203T120000Z</ExceptionStartTime>
+                    <StartTime>20141203T140000Z</StartTime>
+                    <EndTime>20141203T153000Z</EndTime>
+                    <Subject>Mittagspause</Subject>
+                    <BusyStatus>2</BusyStatus>
+                    <AllDayEvent>0</AllDayEvent>
+                    <Attendees>
+                      <Attendee>
+                        <Name>Unittest, Tine</Name>
+                        <Email>unittest@tine20.org</Email>
+                        <AttendeeType>1</AttendeeType>
+                      </Attendee>
+                    </Attendees>
+                  </Exception>
+                  <Exception>
+                    <Body xmlns="uri:AirSyncBase">
+                      <Type>1</Type>
+                      <Data/>
+                    </Body>
+                    <Deleted>0</Deleted>
+                    <ExceptionStartTime>20140625T110000Z</ExceptionStartTime>
+                    <StartTime>20140625T130000Z</StartTime>
+                    <EndTime>20140625T150000Z</EndTime>
+                    <Subject>Mittagspause</Subject>
+                    <BusyStatus>2</BusyStatus>
+                    <AllDayEvent>0</AllDayEvent>
+                    <Attendees>
+                      <Attendee>
+                        <Name>Unittest, Tine</Name>
+                        <Email>unittest@tine20.org</Email>
+                        <AttendeeType>1</AttendeeType>
+                      </Attendee>
+                    </Attendees>
+                  </Exception>
+                </Exceptions>
+              </ApplicationData>
+            </Change>
+          </Commands>
+        </Collection>
+      </Collections>
+    </Sync>';
+
+    protected $_testConcurrentUpdateUpdate = '<?xml version="1.0" encoding="utf-8"?>
+    <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
+    <Sync xmlns="uri:AirSync" xmlns:AirSyncBase="uri:AirSyncBase" xmlns:Calendar="uri:Calendar">
+      <Collections>
+        <Collection>
+          <SyncKey>26</SyncKey>
+          <CollectionId>calendar-root</CollectionId>
+          <GetChanges/>
+          <WindowSize>25</WindowSize>
+          <Options>
+            <FilterType>4</FilterType>
+            <BodyPreference xmlns="uri:AirSyncBase">
+              <Type>1</Type>
+              <TruncationSize>32768</TruncationSize>
+            </BodyPreference>
+          </Options>
+          <Commands>
+            <Change>
+              <ServerId>b2cb312482f641185b205591f40f78a47ea9f667</ServerId>
+              <ApplicationData>
+                <Timezone xmlns="uri:Calendar">xP///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoAAAAFAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAFAAMAAAAAAAAAxP///w==</Timezone>
+                <AllDayEvent xmlns="uri:Calendar">0</AllDayEvent>
+                <BusyStatus xmlns="uri:Calendar">2</BusyStatus>
+                <DtStamp xmlns="uri:Calendar">20131106T072501Z</DtStamp>
+                <EndTime xmlns="uri:Calendar">20140113T140000Z</EndTime>
+                <Sensitivity xmlns="uri:Calendar">0</Sensitivity>
+                <Subject xmlns="uri:Calendar">Mittagspause</Subject>
+                <StartTime xmlns="uri:Calendar">20140113T120000Z</StartTime>
+                <UID xmlns="uri:Calendar">1a5010a3d46316a2709cda40683911f95a856665</UID>
+                <MeetingStatus xmlns="uri:Calendar">1</MeetingStatus>
+                <Attendees xmlns="uri:Calendar">
+                  <Attendee>
+                    <Name>Unittest, Tine</Name>
+                    <Email>unittest@tine20.org</Email>
+                    <AttendeeType>1</AttendeeType>
+                  </Attendee>
+                </Attendees>
+                <Recurrence xmlns="uri:Calendar">
+                  <Type>1</Type>
+                  <Interval>1</Interval>
+                  <DayOfWeek>62</DayOfWeek>
+                  <FirstDayOfWeek>1</FirstDayOfWeek>
+                </Recurrence>
+                <Exceptions xmlns="uri:Calendar">
+                  <Exception>
+                    <Body xmlns="uri:AirSyncBase">
+                      <Type>1</Type>
+                      <Data/>
+                    </Body>
+                    <Deleted>0</Deleted>
+                    <ExceptionStartTime>20141203T120000Z</ExceptionStartTime>
+                    <StartTime>20141203T140000Z</StartTime>
+                    <EndTime>20141203T153000Z</EndTime>
+                    <Subject>Mittagspause</Subject>
+                    <BusyStatus>2</BusyStatus>
+                    <AllDayEvent>0</AllDayEvent>
+                    <Attendees>
+                      <Attendee>
+                        <Name>Unittest, Tine</Name>
+                        <Email>unittest@tine20.org</Email>
+                        <AttendeeType>1</AttendeeType>
+                      </Attendee>
+                    </Attendees>
+                  </Exception>
+                  <Exception>
+                    <Body xmlns="uri:AirSyncBase">
+                      <Type>1</Type>
+                      <Data/>
+                    </Body>
+                    <Deleted>0</Deleted>
+                    <ExceptionStartTime>20140625T110000Z</ExceptionStartTime>
+                    <StartTime>20140625T130000Z</StartTime>
+                    <EndTime>20140625T150000Z</EndTime>
+                    <Subject>Abendessen</Subject>
+                    <BusyStatus>2</BusyStatus>
+                    <AllDayEvent>0</AllDayEvent>
+                    <Attendees>
+                      <Attendee>
+                        <Name>Unittest, Tine</Name>
+                        <Email>unittest@tine20.org</Email>
+                        <AttendeeType>1</AttendeeType>
+                      </Attendee>
+                    </Attendees>
+                  </Exception>
+                </Exceptions>
+              </ApplicationData>
+            </Change>
+          </Commands>
+        </Collection>
+      </Collections>
+    </Sync>';
+
     /**
      * Runs the test methods of this class.
      *
@@ -474,6 +658,51 @@ Zeile 3</AirSyncBase:Data>
         return array($serverId, $syncrotonEvent);
     }
     
+    public function testConcurrentUpdate($syncrotonFolder = null)
+    {
+        if ($syncrotonFolder === null) {
+            $syncrotonFolder = $this->testCreateFolder();
+        }
+        
+        $controller = Syncroton_Data_Factory::factory($this->_class, $this->_getDevice(Syncroton_Model_Device::TYPE_IPHONE), Tinebase_DateTime::now());
+        
+        $xml = new SimpleXMLElement($this->_testConcurrentUpdate);
+        $syncrotonEvent = new Syncroton_Model_Event($xml->Collections->Collection->Commands->Change[0]->ApplicationData);
+        
+        $serverId = $controller->createEntry($syncrotonFolder->serverId, $syncrotonEvent);
+        
+        $event = Calendar_Controller_Event::getInstance()->get($serverId);
+        
+        // change exception #2
+        $exceptions = Calendar_Controller_Event::getInstance()->getRecurExceptions($event);
+        foreach ($exceptions as $exception) {
+            if ($exception->dtstart->toString() == '2014-06-25 13:00:00') {
+                $exception->summary = 'Fruehstueck';
+                Calendar_Controller_Event::getInstance()->update($exceptions[1]);
+            }
+        }
+        
+        $xmlUpdate = new SimpleXMLElement($this->_testConcurrentUpdateUpdate);
+        $syncrotonEvent = new Syncroton_Model_Event($xmlUpdate->Collections->Collection->Commands->Change[0]->ApplicationData);
+
+        $syncTimestamp = Calendar_Controller_Event::getInstance()->get($serverId)->creation_time;
+        $controller = Syncroton_Data_Factory::factory($this->_class, $this->_getDevice(Syncroton_Model_Device::TYPE_IPHONE), $syncTimestamp);
+        
+        // update exception - should not throw concurrency exception
+        $serverId = $controller->updateEntry($syncrotonFolder->serverId, $serverId, $syncrotonEvent);
+        
+        $event = Calendar_Controller_Event::getInstance()->get($serverId);
+        $exceptions = Calendar_Controller_Event::getInstance()->getRecurExceptions($event);
+        
+        foreach ($exceptions as $exception) {
+            if ($exception->dtstart->toString() == '2014-06-25 13:00:00') {
+                $this->assertEquals('Abendessen', $exception->summary, print_r($exceptions[1]->toArray(), true));
+            } else {
+                $this->assertEquals('Mittagspause', $exception->summary, print_r($exceptions[1]->toArray(), true));
+            }
+        }
+    }
+
     public function testCreateEntryOutlook13($syncrotonFolder = null)
     {
         if ($syncrotonFolder === null) {
index 2755511..38369a8 100644 (file)
@@ -520,6 +520,9 @@ class ActiveSync_Controller_Calendar extends ActiveSync_Controller_Abstract impl
             $data->copyFieldsFromParent();
         }
         
+        // Update seq to entries seq to prevent concurrent update
+        $event->seq = $entry['seq'];
+        
         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(
             __METHOD__ . '::' . __LINE__ . " Event before mapping: " . print_r($event->toArray(), true));
         
@@ -582,7 +585,7 @@ class ActiveSync_Controller_Calendar extends ActiveSync_Controller_Abstract impl
                     Calendar_Model_Attender::emailsToAttendee($event, $newAttendees);
                     
                     break;
-                    
+
                 case 'exdate':
                     // handle exceptions from recurrence
                     $exdates = new Tinebase_Record_RecordSet('Calendar_Model_Event');
@@ -590,7 +593,7 @@ class ActiveSync_Controller_Calendar extends ActiveSync_Controller_Abstract impl
                     foreach ($data->$syncrotonProperty as $exception) {
                         if ($exception->deleted == 0) {
                             $eventException = $this->toTineModel($exception);
-                            $eventException->last_modified_time = $this->_syncTimeStamp;
+                            $eventException->last_modified_time = new Tinebase_DateTime($this->_syncTimeStamp);
                             $eventException->recurid            = new Tinebase_DateTime($exception->exceptionStartTime);
                             $eventException->is_deleted         = false;
                         } else {
@@ -600,8 +603,10 @@ class ActiveSync_Controller_Calendar extends ActiveSync_Controller_Abstract impl
                             ));
                         }
                         
+                        $eventException->seq = $entry['seq'];
                         $exdates->addRecord($eventException);
                     }
+                    
                     $event->$tine20Property = $exdates;
                     
                     break;