/**
  * validate sync key
  * 
  * @param ActiveSync_Model_Device $_device
  * @param $_counter
  * @param $_class
  * @param $_collectionId
  * @return boolean
  */
 public function validateSyncKey(ActiveSync_Model_Device $_device, $_counter, $_class, $_collectionId = NULL)
 {
     $type = $_collectionId !== NULL ? $_class . '-' . $_collectionId : $_class;
     $syncState = new ActiveSync_Model_SyncState(array('device_id' => $_device->getId(), 'counter' => $_counter, 'type' => $type));
     try {
         $syncState = $this->_syncStateBackend->get($syncState);
     } catch (ActiveSync_Exception_SyncStateNotFound $asessnf) {
         return false;
     }
     if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) {
         Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' . print_r($syncState->toArray(), true));
     }
     // check if this was the latest syncKey
     try {
         $otherSyncState = clone $syncState;
         $otherSyncState->counter++;
         $otherSyncState = $this->_syncStateBackend->get($otherSyncState);
         if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) {
             Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' found more recent synckey');
         }
         if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) {
             Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' ' . print_r($otherSyncState->toArray(), true));
         }
         // undelete marked entries
         $contentStateFilter = new ActiveSync_Model_ContentStateFilter(array(array('field' => 'device_id', 'operator' => 'equals', 'value' => $_device->getId()), array('field' => 'class', 'operator' => 'equals', 'value' => $_class), array('field' => 'collectionid', 'operator' => 'equals', 'value' => $_collectionId), array('field' => 'is_deleted', 'operator' => 'equals', 'value' => true)));
         $stateIds = $this->_contentStateBackend->search($contentStateFilter, null, true);
         $this->_contentStateBackend->updateMultiple($stateIds, array('is_deleted' => 0));
         // remove entries added during latest sync
         $contentStateFilter = new ActiveSync_Model_ContentStateFilter(array(array('field' => 'device_id', 'operator' => 'equals', 'value' => $_device->getId()), array('field' => 'class', 'operator' => 'equals', 'value' => $_class), array('field' => 'collectionid', 'operator' => 'equals', 'value' => $_collectionId), array('field' => 'creation_time', 'operator' => 'after', 'value' => $syncState->lastsync)));
         $stateIds = $this->_contentStateBackend->search($contentStateFilter, null, true);
         $this->_contentStateBackend->delete($stateIds);
     } catch (ActiveSync_Exception_SyncStateNotFound $asessnf) {
         // finaly delete all entries marked for removal
         $contentStateFilter = new ActiveSync_Model_ContentStateFilter(array(array('field' => 'device_id', 'operator' => 'equals', 'value' => $_device->getId()), array('field' => 'class', 'operator' => 'equals', 'value' => $_class), array('field' => 'collectionid', 'operator' => 'equals', 'value' => $_collectionId), array('field' => 'is_deleted', 'operator' => 'equals', 'value' => true)));
         $stateIds = $this->_contentStateBackend->search($contentStateFilter, null, true);
         $this->_contentStateBackend->delete($stateIds);
     }
     // remove all other synckeys
     $this->_syncStateBackend->deleteOther($syncState);
     if (!empty($syncState->pendingdata)) {
         $syncState->pendingdata = Zend_Json::decode($syncState->pendingdata);
     }
     return $syncState;
 }
Beispiel #2
0
 /**
  * return number of chnaged entries
  * 
  * @param $_dataController
  * @param array $_collectionData
  * @param $_lastSyncTimeStamp
  * @return int number of changed entries
  */
 private function _getItemEstimate($_dataController, $_collectionData, $_lastSyncTimeStamp)
 {
     // hack to have the same variable names all over the place
     $_collectionData['class'] = $_collectionData['folderType'];
     $_collectionData['collectionId'] = $_collectionData['serverEntryId'];
     $contentStateBackend = new ActiveSync_Backend_ContentState();
     $folderStateBackend = new ActiveSync_Backend_FolderState();
     // get current filterType
     $filter = new ActiveSync_Model_FolderStateFilter(array(array('field' => 'device_id', 'operator' => 'equals', 'value' => $this->_device->getId()), array('field' => 'class', 'operator' => 'equals', 'value' => $_collectionData['class']), array('field' => 'folderid', 'operator' => 'equals', 'value' => $_collectionData['collectionId'])));
     $folderState = $folderStateBackend->search($filter)->getFirstRecord();
     if ($folderState instanceof ActiveSync_Model_FolderState) {
         $filterType = $folderState->lastfiltertype;
     } else {
         $filterType = 0;
     }
     $allClientEntries = $contentStateBackend->getClientState($this->_device, $_collectionData['class'], $_collectionData['collectionId']);
     $_dataController->updateCache($_collectionData['collectionId']);
     $allServerEntries = $_dataController->getServerEntries($_collectionData['collectionId'], $filterType);
     $addedEntries = array_diff($allServerEntries, $allClientEntries);
     $deletedEntries = array_diff($allClientEntries, $allServerEntries);
     $changedEntries = $_dataController->getChanged($_collectionData['collectionId'], $_lastSyncTimeStamp);
     return count($addedEntries) + count($deletedEntries) + count($changedEntries);
 }
Beispiel #3
0
 /**
  * (non-PHPdoc)
  * @see ActiveSync_Command_Wbxml::getResponse()
  */
 public function getResponse()
 {
     // add aditional namespaces for tasks and email
     $this->_outputDom->documentElement->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:Tasks', 'uri:Tasks');
     $this->_outputDom->documentElement->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:Email', 'uri:Email');
     $this->_outputDom->documentElement->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:AirSyncBase', 'uri:AirSyncBase');
     $sync = $this->_outputDom->documentElement;
     $collections = $sync->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Collections'));
     foreach ($this->_collections as $class => $classCollections) {
         foreach ($classCollections as $collectionId => $collectionData) {
             if ($class == 'collectionNotFound') {
                 $collection = $collections->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Collection'));
                 $collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Class', $collectionData['class']));
                 $collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'SyncKey', 0));
                 $collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'CollectionId', $collectionData['collectionId']));
                 $collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Status', self::STATUS_FOLDER_HIERARCHY_HAS_CHANGED));
             } elseif ($collectionData['syncKeyValid'] !== true) {
                 $collectionData['syncState']->counter = 0;
                 // set synckey to 0
                 $collection = $collections->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Collection'));
                 $collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Class', $collectionData['class']));
                 $collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'SyncKey', $collectionData['syncState']->counter));
                 $collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'CollectionId', $collectionData['collectionId']));
                 $collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Status', self::STATUS_INVALID_SYNC_KEY));
                 $this->_contentStateBackend->resetState($this->_device, $collectionData['class'], $collectionData['collectionId']);
                 $this->_controller->resetSyncState($collectionData['syncState']);
             } elseif ($collectionData['syncState']->counter === 0) {
                 $collectionData['syncState']->counter++;
                 // initial sync
                 // send back a new SyncKey only
                 $collection = $collections->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Collection'));
                 $collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Class', $collectionData['class']));
                 $collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'SyncKey', $collectionData['syncState']->counter));
                 $collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'CollectionId', $collectionData['collectionId']));
                 $collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Status', self::STATUS_SUCCESS));
                 $this->_contentStateBackend->resetState($this->_device, $collectionData['class'], $collectionData['collectionId']);
                 $this->_controller->resetSyncState($collectionData['syncState']);
             } else {
                 if (empty($collectionData['added']) && empty($collectionData['changed']) && empty($collectionData['deleted']) && $collectionData['getChanges'] === false) {
                     // keep synckey during fetch requests
                 } else {
                     $collectionData['syncState']->counter++;
                 }
                 // collection header
                 $collection = $collections->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Collection'));
                 $collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Class', $collectionData['class']));
                 $collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'SyncKey', $collectionData['syncState']->counter));
                 $collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'CollectionId', $collectionData['collectionId']));
                 $collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Status', self::STATUS_SUCCESS));
                 $responses = NULL;
                 // sent reponse for newly added entries
                 if (!empty($collectionData['added'])) {
                     if ($responses === NULL) {
                         $responses = $collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Responses'));
                     }
                     foreach ($collectionData['added'] as $clientId => $entryData) {
                         $add = $responses->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Add'));
                         $add->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'ClientId', $clientId));
                         if (isset($entryData['serverId'])) {
                             $add->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'ServerId', $entryData['serverId']));
                         }
                         $add->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Status', $entryData['status']));
                     }
                 }
                 // sent reponse for changed entries
                 // not really needed
                 if (!empty($collectionData['changed'])) {
                     if ($responses === NULL) {
                         $responses = $collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Responses'));
                     }
                     foreach ($collectionData['changed'] as $serverId => $status) {
                         $change = $responses->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Change'));
                         $change->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'ServerId', $serverId));
                         $change->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Status', $status));
                     }
                 }
                 $dataController = ActiveSync_Controller::dataFactory($collectionData['class'], $this->_device, $this->_syncTimeStamp);
                 // sent response for to be fetched entries
                 if (!empty($collectionData['toBeFetched'])) {
                     if ($responses === NULL) {
                         $responses = $collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Responses'));
                     }
                     foreach ($collectionData['toBeFetched'] as $serverId) {
                         $fetch = $responses->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Fetch'));
                         $fetch->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'ServerId', $serverId));
                         try {
                             $applicationData = $this->_outputDom->createElementNS('uri:AirSync', 'ApplicationData');
                             // don't limit size during fetch
                             $collectionFetchData = $collectionData;
                             unset($collectionFetchData['bodyPreferences'][1]['truncationSize']);
                             unset($collectionFetchData['bodyPreferences'][2]['truncationSize']);
                             unset($collectionFetchData['bodyPreferences'][4]['truncationSize']);
                             unset($collectionFetchData['mimeTruncation']);
                             $dataController->appendXML($applicationData, $collectionData['collectionId'], $serverId, $collectionData);
                             $fetch->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Status', self::STATUS_SUCCESS));
                             $fetch->appendChild($applicationData);
                         } catch (Exception $e) {
                             if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) {
                                 Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . " unable to convert entry to xml: " . $e->getMessage());
                             }
                             $fetch->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Status', self::STATUS_OBJECT_NOT_FOUND));
                         }
                     }
                 }
                 if ($collectionData['getChanges'] === true) {
                     if ($collectionData['syncState']->counter === 1) {
                         // all entries available
                         $serverAdds = $dataController->getServerEntries($collectionData['collectionId'], $collectionData['filterType']);
                         $serverChanges = array();
                         $serverDeletes = array();
                     } else {
                         // continue sync session
                         if (is_array($collectionData['syncState']->pendingdata)) {
                             if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) {
                                 Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . " restored from sync state ");
                             }
                             $serverAdds = $collectionData['syncState']->pendingdata['serverAdds'];
                             $serverChanges = $collectionData['syncState']->pendingdata['serverChanges'];
                             $serverDeletes = $collectionData['syncState']->pendingdata['serverDeletes'];
                         } else {
                             // fetch entries added since last sync
                             $allClientEntries = $this->_contentStateBackend->getClientState($this->_device, $collectionData['class'], $collectionData['collectionId']);
                             $allServerEntries = $dataController->getServerEntries($collectionData['collectionId'], $collectionData['filterType']);
                             // add entries
                             $serverDiff = array_diff($allServerEntries, $allClientEntries);
                             // add entries which produced problems during delete from client
                             $serverAdds = $this->_collections[$class][$collectionId]['forceAdd'];
                             // add entries not yet sent to client
                             $serverAdds = array_unique(array_merge($serverAdds, $serverDiff));
                             foreach ($serverAdds as $id => $serverId) {
                                 // skip entries added by client during this sync session
                                 // @todo $this->_collections[$class][$collectionId] should be equal to $collectionData ???
                                 if (isset($collectionData['added'][$serverId]) && !isset($this->_collections[$class][$collectionId]['forceAdd'][$serverId])) {
                                     if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) {
                                         Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . " skipped added entry: " . $serverId);
                                     }
                                     unset($serverAdds[$id]);
                                 }
                             }
                             // entries to be deleted
                             $serverDeletes = array_diff($allClientEntries, $allServerEntries);
                             // fetch entries changed since last sync
                             $serverChanges = $dataController->getChanged($collectionData['collectionId'], $collectionData['syncState']->lastsync, $this->_syncTimeStamp);
                             $serverChanges = array_merge($serverChanges, $this->_collections[$class][$collectionId]['forceChange']);
                             foreach ($serverChanges as $id => $serverId) {
                                 // skip entry, if it got changed by client during current sync
                                 // @todo $this->_collections[$class][$collectionId] should be equal to $collectionData ???
                                 if (isset($collectionData['changed'][$serverId]) && !isset($this->_collections[$class][$collectionId]['forceChange'][$serverId])) {
                                     if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) {
                                         Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . " skipped changed entry: " . $serverId);
                                     }
                                     unset($serverChanges[$id]);
                                 }
                             }
                             // entries comeing in scope are already in $serverAdds and do not need to
                             // be send with $serverCanges
                             $serverChanges = array_diff($serverChanges, $serverAdds);
                         }
                     }
                     if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) {
                         Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . " found (added/changed/deleted) " . count($serverAdds) . '/' . count($serverChanges) . '/' . count($serverDeletes) . ' entries for sync from server to client');
                     }
                     if (count($serverAdds) + count($serverChanges) + count($serverDeletes) > $collectionData['windowSize']) {
                         $this->_moreAvailable = true;
                         $collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'MoreAvailable'));
                     }
                     if (count($serverAdds) > 0 || count($serverChanges) > 0 || count($serverDeletes) > 0) {
                         $commands = $collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Commands'));
                     }
                     /**
                      * process added entries
                      */
                     // fetch estimated entries in one batch
                     $ids = array_slice($serverAdds, 0, abs($collectionData['windowSize'] - $this->_totalCount), TRUE);
                     $serverEntries = $dataController->getMultiple($ids);
                     foreach ($serverAdds as $id => $serverId) {
                         if ($this->_totalCount === $collectionData['windowSize']) {
                             break;
                         }
                         try {
                             #$add = $commands->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Add'));
                             $add = $this->_outputDom->createElementNS('uri:AirSync', 'Add');
                             $entriesIdx = $serverEntries->getIndexById($serverId);
                             $serverEntriy = $entriesIdx !== FALSE ? $serverEntries[$entriesIdx] : $serverId;
                             $add->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'ServerId', $serverId));
                             $applicationData = $add->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'ApplicationData'));
                             $dataController->appendXML($applicationData, $collectionData['collectionId'], $serverEntriy, $collectionData);
                             $commands->appendChild($add);
                             #$this->_addContentState($collectionData['class'], $collectionData['collectionId'], $serverId);
                             $this->_totalCount++;
                         } catch (Exception $e) {
                             if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) {
                                 Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . " unable to convert entry to xml: " . $e->getMessage());
                             }
                         }
                         // mark as send to the client, even the conversion to xml might have failed
                         $this->_addContentState($collectionData['class'], $collectionData['collectionId'], $serverId);
                         if ($serverEntriy instanceof Tinebase_Record_Abstract) {
                             $serverEntries->removeRecord($serverEntriy);
                         }
                         unset($serverAdds[$id]);
                     }
                     /**
                      * process changed entries
                      */
                     foreach ($serverChanges as $id => $serverId) {
                         if ($this->_totalCount === $collectionData['windowSize']) {
                             break;
                         }
                         try {
                             $change = $this->_outputDom->createElementNS('uri:AirSync', 'Change');
                             $entriesIdx = $serverEntries->getIndexById($serverId);
                             $serverEntriy = $entriesIdx !== FALSE ? $serverEntries[$entriesIdx] : $serverId;
                             $change->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'ServerId', $serverId));
                             $applicationData = $change->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'ApplicationData'));
                             $dataController->appendXML($applicationData, $collectionData['collectionId'], $serverEntriy, $collectionData);
                             $commands->appendChild($change);
                             $this->_totalCount++;
                         } catch (Exception $e) {
                             if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) {
                                 Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . " unable to convert entry to xml: " . $e->getMessage());
                             }
                         }
                         if ($serverEntriy instanceof Tinebase_Record_Abstract) {
                             $serverEntries->removeRecord($serverEntriy);
                         }
                         unset($serverChanges[$id]);
                     }
                     /**
                      * process deleted entries
                      */
                     foreach ($serverDeletes as $id => $serverId) {
                         if ($this->_totalCount === $collectionData['windowSize']) {
                             break;
                         }
                         try {
                             #$delete = $commands->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Delete'));
                             $delete = $this->_outputDom->createElementNS('uri:AirSync', 'Delete');
                             $delete->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'ServerId', $serverId));
                             $this->_markContentStateAsDeleted($collectionData['class'], $collectionData['collectionId'], $serverId);
                             $commands->appendChild($delete);
                             $this->_totalCount++;
                         } catch (Exception $e) {
                             if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) {
                                 Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . " unable to convert entry to xml: " . $e->getMessage());
                             }
                         }
                         unset($serverDeletes[$id]);
                     }
                 }
                 if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) {
                     Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . " new synckey is " . $collectionData['syncState']->counter);
                 }
             }
             if ($class != 'collectionNotFound') {
                 // save data to sync state if more data available
                 if ($this->_moreAvailable === true) {
                     $collectionData['syncState']->pendingdata = array('serverAdds' => (array) $serverAdds, 'serverChanges' => (array) $serverChanges, 'serverDeletes' => (array) $serverDeletes);
                 } else {
                     $collectionData['syncState']->pendingdata = null;
                 }
                 $keepPreviousSyncKey = true;
                 // increment sync timestamp by 1 second
                 $this->_syncTimeStamp->add('1', Tinebase_DateTime::MODIFIER_SECOND);
                 if (!empty($collectionData['added'])) {
                     if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) {
                         Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . " remove previous synckey as client added new entries");
                     }
                     $keepPreviousSyncKey = false;
                 }
                 $collectionData['syncState']->lastsync = $this->_syncTimeStamp;
                 $this->_controller->updateSyncState($collectionData['syncState'], $keepPreviousSyncKey);
                 // store current filter type
                 try {
                     $folderState = $this->getFolderState($this->_device, $collectionData['collectionId']);
                     $folderState->lastfiltertype = $collectionData['filterType'];
                     $this->_folderStateBackend->update($folderState);
                 } catch (Tinebase_Exception_NotFound $tenf) {
                     // failed to get folderstate => should not happen but is also no problem in this state
                     if (Tinebase_Core::isLogLevel(Zend_Log::CRIT)) {
                         Tinebase_Core::getLogger()->crit(__METHOD__ . '::' . __LINE__ . ' failed to get content state for: ' . $collectionData['collectionId']);
                     }
                 }
             }
         }
     }
     return $this->_outputDom;
 }
 /**
  * return number of chnaged entries
  * 
  * @param $_dataController
  * @param array $_collectionData
  * @param $_lastSyncTimeStamp
  * @return int number of changed entries
  */
 protected function _getItemEstimate($_dataController, $_collectionData)
 {
     $contentStateBackend = new ActiveSync_Backend_ContentState();
     $allClientEntries = $contentStateBackend->getClientState($this->_device, $_collectionData['class'], $_collectionData['collectionId']);
     $_dataController->updateCache($_collectionData['collectionId']);
     $allServerEntries = $_dataController->getServerEntries($_collectionData['collectionId'], $_collectionData['filterType']);
     $addedEntries = array_diff($allServerEntries, $allClientEntries);
     $deletedEntries = array_diff($allClientEntries, $allServerEntries);
     $changedEntries = $_dataController->getChanged($_collectionData['collectionId'], $_collectionData['syncState']->lastsync, $this->_syncTimeStamp);
     return count($addedEntries) + count($deletedEntries) + count($changedEntries);
 }