diff --git a/lib/ext/Syncroton/Command/Sync.php b/lib/ext/Syncroton/Command/Sync.php --- a/lib/ext/Syncroton/Command/Sync.php +++ b/lib/ext/Syncroton/Command/Sync.php @@ -467,6 +467,93 @@ } } + private function getServerModifications($dataController, $collectionData) + { + $serverModifications = [ + 'added' => [], + 'changed' => [], + 'deleted' => [], + ]; + + // We first use hasChanges because it has a fast path for when there are no changes by fetching the count of messages only. + // However, in all other cases we will end up fetching the same entries as below, which is less than ideal. + // TODO: We should create a new method, which checks if there are no changes, and otherwise just let the code below figure out + // if there are any changes to process. + if (!$dataController->hasChanges($this->_contentStateBackend, $collectionData->folder, $collectionData->syncState)) { + return $serverModifications; + } + + // update _syncTimeStamp as $dataController->hasChanges might have spent some time + $this->_syncTimeStamp = new DateTime('now', new DateTimeZone('UTC')); + + // fetch entries added since last sync + $allClientEntries = $this->_contentStateBackend->getFolderState( + $this->_device, + $collectionData->folder, + $collectionData->syncState->counter + ); + + // fetch entries changed since last sync + $allChangedEntries = $dataController->getChangedEntries( + $collectionData->collectionId, + $collectionData->syncState, + $collectionData->options['filterType'] + ); + + // fetch all entries + $allServerEntries = $dataController->getServerEntries( + $collectionData->collectionId, + $collectionData->options['filterType'] + ); + + // add entries + $serverDiff = array_diff($allServerEntries, $allClientEntries); + // add entries which produced problems during delete from client + $serverModifications['added'] = $clientModifications['forceAdd']; + // add entries not yet sent to client + $serverModifications['added'] = array_unique(array_merge($serverModifications['added'], $serverDiff)); + + // @todo still needed? + foreach($serverModifications['added'] as $id => $serverId) { + // skip entries added by client during this sync session + if(isset($clientModifications['added'][$serverId]) && !isset($clientModifications['forceAdd'][$serverId])) { + if ($this->_logger instanceof Zend_Log) { + $this->_logger->info(__METHOD__ . '::' . __LINE__ . " skipped added entry: " . $serverId); + } + unset($serverModifications['added'][$id]); + } + } + + // entries to be deleted + $serverModifications['deleted'] = array_diff($allClientEntries, $allServerEntries); + + // entries changed since last sync + $serverModifications['changed'] = array_merge($allChangedEntries, $clientModifications['forceChange']); + + foreach($serverModifications['changed'] as $id => $serverId) { + // skip entry, if it got changed by client during current sync + if(isset($clientModifications['changed'][$serverId]) && !isset($clientModifications['forceChange'][$serverId])) { + if ($this->_logger instanceof Zend_Log) { + $this->_logger->info(__METHOD__ . '::' . __LINE__ . " skipped changed entry: " . $serverId); + } + unset($serverModifications['changed'][$id]); + } + // skip entry, make sure we don't sent entries already added by client in this request + elseif (isset($clientModifications['added'][$serverId]) && !isset($clientModifications['forceAdd'][$serverId])) { + if ($this->_logger instanceof Zend_Log) { + $this->_logger->info(__METHOD__ . '::' . __LINE__ . " skipped change for added entry: " . $serverId); + } + unset($serverModifications['changed'][$id]); + } + } + + // entries comeing in scope are already in $serverModifications['added'] and do not need to + // be send with $serverCanges + $serverModifications['changed'] = array_diff($serverModifications['changed'], $serverModifications['added']); + + return $serverModifications; + } + /** * (non-PHPdoc) * @see Syncroton_Command_Wbxml::getResponse() @@ -663,18 +750,17 @@ ]; $status = self::STATUS_SUCCESS; - $hasChanges = 0; if ($collectionData->getChanges === true) { // continue sync session? if(is_array($collectionData->syncState->pendingdata)) { $serverModifications = $collectionData->syncState->pendingdata; if ($this->_logger instanceof Zend_Log) { - $this->_logger->info(__METHOD__ . '::' . __LINE__ . " restored from sync state: (added/changed/deleted) " . count($serverModifications['added']) . '/' . count($serverModifications['changed']) . '/' . count($serverModifications['deleted']) . ' entries for sync from server to client'); + $this->_logger->info(__METHOD__ . '::' . __LINE__ . " restored from sync state."); } } else { try { - $hasChanges = $dataController->hasChanges($this->_contentStateBackend, $collectionData->folder, $collectionData->syncState); + $serverModifications = $this->getServerModifications($dataController, $collectionData); } catch (Syncroton_Exception_NotFound $e) { if ($this->_logger instanceof Zend_Log) { $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " Folder changes checking failed (not found): " . $e->getTraceAsString()); @@ -686,85 +772,6 @@ $this->_logger->crit(__METHOD__ . '::' . __LINE__ . " Folder changes checking failed: " . $e->getMessage()); } - // Prevent from removing client entries when getServerEntries() fails - // @todo: should we break the loop here? - $status = self::STATUS_SERVER_ERROR; - } - } - - if ($hasChanges) { - // update _syncTimeStamp as $dataController->hasChanges might have spent some time - $this->_syncTimeStamp = new DateTime('now', new DateTimeZone('UTC')); - - try { - // fetch entries added since last sync - $allClientEntries = $this->_contentStateBackend->getFolderState( - $this->_device, - $collectionData->folder, - $collectionData->syncState->counter - ); - - // fetch entries changed since last sync - $allChangedEntries = $dataController->getChangedEntries( - $collectionData->collectionId, - $collectionData->syncState, - $collectionData->options['filterType'] - ); - - // fetch all entries - $allServerEntries = $dataController->getServerEntries( - $collectionData->collectionId, - $collectionData->options['filterType'] - ); - - // add entries - $serverDiff = array_diff($allServerEntries, $allClientEntries); - // add entries which produced problems during delete from client - $serverModifications['added'] = $clientModifications['forceAdd']; - // add entries not yet sent to client - $serverModifications['added'] = array_unique(array_merge($serverModifications['added'], $serverDiff)); - - // @todo still needed? - foreach($serverModifications['added'] as $id => $serverId) { - // skip entries added by client during this sync session - if(isset($clientModifications['added'][$serverId]) && !isset($clientModifications['forceAdd'][$serverId])) { - if ($this->_logger instanceof Zend_Log) { - $this->_logger->info(__METHOD__ . '::' . __LINE__ . " skipped added entry: " . $serverId); - } - unset($serverModifications['added'][$id]); - } - } - - // entries to be deleted - $serverModifications['deleted'] = array_diff($allClientEntries, $allServerEntries); - - // entries changed since last sync - $serverModifications['changed'] = array_merge($allChangedEntries, $clientModifications['forceChange']); - - foreach($serverModifications['changed'] as $id => $serverId) { - // skip entry, if it got changed by client during current sync - if(isset($clientModifications['changed'][$serverId]) && !isset($clientModifications['forceChange'][$serverId])) { - if ($this->_logger instanceof Zend_Log) { - $this->_logger->info(__METHOD__ . '::' . __LINE__ . " skipped changed entry: " . $serverId); - } - unset($serverModifications['changed'][$id]); - } - // skip entry, make sure we don't sent entries already added by client in this request - elseif (isset($clientModifications['added'][$serverId]) && !isset($clientModifications['forceAdd'][$serverId])) { - if ($this->_logger instanceof Zend_Log) { - $this->_logger->info(__METHOD__ . '::' . __LINE__ . " skipped change for added entry: " . $serverId); - } - unset($serverModifications['changed'][$id]); - } - } - - // entries comeing in scope are already in $serverModifications['added'] and do not need to - // be send with $serverCanges - $serverModifications['changed'] = array_diff($serverModifications['changed'], $serverModifications['added']); - } catch (Exception $e) { - if ($this->_logger instanceof Zend_Log) { - $this->_logger->crit(__METHOD__ . '::' . __LINE__ . " Folder state checking failed: " . $e->getMessage()); - } if ($this->_logger instanceof Zend_Log) { $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " Folder state checking failed: " . $e->getTraceAsString()); } @@ -773,10 +780,10 @@ // @todo: should we break the loop here? $status = self::STATUS_SERVER_ERROR; } + } - if ($this->_logger instanceof Zend_Log) { - $this->_logger->info(__METHOD__ . '::' . __LINE__ . " found (added/changed/deleted) " . count($serverModifications['added']) . '/' . count($serverModifications['changed']) . '/' . count($serverModifications['deleted']) . ' entries for sync from server to client'); - } + if ($this->_logger instanceof Zend_Log) { + $this->_logger->info(__METHOD__ . '::' . __LINE__ . " found (added/changed/deleted) " . count($serverModifications['added']) . '/' . count($serverModifications['changed']) . '/' . count($serverModifications['deleted']) . ' entries for sync from server to client'); } }