Commit 66b5a3ae authored by Sebastian Kummer's avatar Sebastian Kummer

Merging in latest from upstream (ZP/z-push:refs/heads/develop)

* commit '0df33f3f':
  ZP-848 SetSyncKey in SPO always if it is set (as before), but invalidate FolderStat if it does not change.
  ZP-741 Remove folder type from the loop detection data.
  ZP-772 Change default value of USE_FULLEMAIL_FOR_LOGIN to true.
  ZP-622 ZPushAdmin and Sync-without-heartbeat should not load the hierarchy folderdata states.
  ZP-778 Remove unused code.
  ZP-847 Remove fseek from encoder as not all stream types support it. Released under the Affero GNU General Public License (AGPL) version 3.
  ZP-622 Devices performing heartbeat weren't handling changes properly.
  ZP-622 Update year.
  ZP-778 Make sure the hierarchy cache is loaded when verifying hierarchy changes in SyncCollections.
  ZP-777 Verify potential hierarchy changes against a hierarchy hash coming from all foldernames + parent ids.
  ZP-622 Update year.
  ZP-771 Indicate changed additional folderdata in DeviceManager->IsHierarchySyncRequired().
  ZP-778 Verify received hierarchy notifications for validity.
  ZP-777 Catch and filter hierarchy events.
  ZP-622 added HIERARCHYNOTIFICATION constant to the IBackend.
parents 097d1553 0df33f3f
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
* *
* Created : 01.10.2011 * Created : 01.10.2011
* *
* Copyright 2007 - 2015 Zarafa Deutschland GmbH * Copyright 2007 - 2016 Zarafa Deutschland GmbH
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3, * it under the terms of the GNU Affero General Public License, version 3,
...@@ -114,6 +114,7 @@ class BackendZarafa implements IBackend, ISearchProvider { ...@@ -114,6 +114,7 @@ class BackendZarafa implements IBackend, ISearchProvider {
$this->changesSink = false; $this->changesSink = false;
$this->changesSinkFolders = array(); $this->changesSinkFolders = array();
$this->changesSinkStores = array(); $this->changesSinkStores = array();
$this->changesSinkHierarchyHash = false;
$this->wastebasket = false; $this->wastebasket = false;
$this->session = false; $this->session = false;
$this->folderStatCache = array(); $this->folderStatCache = array();
...@@ -883,7 +884,8 @@ class BackendZarafa implements IBackend, ISearchProvider { ...@@ -883,7 +884,8 @@ class BackendZarafa implements IBackend, ISearchProvider {
return false; return false;
} }
ZLog::Write(LOGLEVEL_DEBUG, "ZarafaBackend->HasChangesSink(): created"); $this->changesSinkHierarchyHash = $this->getHierarchyHash();
ZLog::Write(LOGLEVEL_DEBUG, sprintf("ZarafaBackend->HasChangesSink(): created - HierarchyHash: %s", $this->changesSinkHierarchyHash));
// advise the main store and also to check if the connection supports it // advise the main store and also to check if the connection supports it
return $this->adviseStoreToSink($this->defaultstore); return $this->adviseStoreToSink($this->defaultstore);
...@@ -925,9 +927,26 @@ class BackendZarafa implements IBackend, ISearchProvider { ...@@ -925,9 +927,26 @@ class BackendZarafa implements IBackend, ISearchProvider {
* @return array * @return array
*/ */
public function ChangesSink($timeout = 30) { public function ChangesSink($timeout = 30) {
// clear the folder stats cache
unset($this->folderStatCache);
$notifications = array(); $notifications = array();
$hierarchyNotifications = array();
$sinkresult = @mapi_sink_timedwait($this->changesSink, $timeout * 1000); $sinkresult = @mapi_sink_timedwait($this->changesSink, $timeout * 1000);
// reverse array so that the changes on folders are before changes on messages and
// it's possible to filter such notifications
$sinkresult = array_reverse($sinkresult, true);
foreach ($sinkresult as $sinknotif) { foreach ($sinkresult as $sinknotif) {
// add a notification on a folder
if ($sinknotif['objtype'] == MAPI_FOLDER) {
$hierarchyNotifications[$sinknotif['entryid']] = IBackend::HIERARCHYNOTIFICATION;
}
// change on a message, remove hierarchy notification
if (isset($sinknotif['parentid']) && $sinknotif['objtype'] == MAPI_MESSAGE && isset($notifications[$sinknotif['parentid']])) {
unset($hierarchyNotifications[$sinknotif['parentid']]);
}
// TODO check if adding $sinknotif['objtype'] = MAPI_MESSAGE wouldn't break anything
// check if something in the monitored folders changed // check if something in the monitored folders changed
if (isset($sinknotif['parentid']) && array_key_exists($sinknotif['parentid'], $this->changesSinkFolders)) { if (isset($sinknotif['parentid']) && array_key_exists($sinknotif['parentid'], $this->changesSinkFolders)) {
$notifications[] = $this->changesSinkFolders[$sinknotif['parentid']]; $notifications[] = $this->changesSinkFolders[$sinknotif['parentid']];
...@@ -937,6 +956,15 @@ class BackendZarafa implements IBackend, ISearchProvider { ...@@ -937,6 +956,15 @@ class BackendZarafa implements IBackend, ISearchProvider {
$notifications[] = $this->changesSinkFolders[$sinknotif['oldparentid']]; $notifications[] = $this->changesSinkFolders[$sinknotif['oldparentid']];
} }
} }
// validate hierarchy notifications by comparing the hierarchy hashes (too many false positives otherwise)
if (!empty($hierarchyNotifications)) {
$hash = $this->getHierarchyHash();
if ($hash !== $this->changesSinkHierarchyHash) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendZarafa->ChangesSink() Hierarchy notification, pending validation. HierarchyHash: %s", $hash));
$notifications[] = IBackend::HIERARCHYNOTIFICATION;
}
}
return $notifications; return $notifications;
} }
...@@ -1396,6 +1424,20 @@ class BackendZarafa implements IBackend, ISearchProvider { ...@@ -1396,6 +1424,20 @@ class BackendZarafa implements IBackend, ISearchProvider {
* Private methods * Private methods
*/ */
/**
* Returns a hash representing changes in the hierarchy of the main user.
* It changes if a folder is added, renamed or deleted.
*
* @access private
* @return string
*/
private function getHierarchyHash() {
$rootfolder = mapi_msgstore_openentry($this->defaultstore);
$hierarchy = mapi_folder_gethierarchytable($rootfolder, CONVENIENT_DEPTH);
return md5(serialize(mapi_table_queryallrows($hierarchy, array(PR_DISPLAY_NAME, PR_PARENT_ENTRYID))));
}
/** /**
* Advises a store to the changes sink * Advises a store to the changes sink
* *
......
...@@ -65,10 +65,10 @@ ...@@ -65,10 +65,10 @@
* (e.g. user@company.com) or the username only (user). * (e.g. user@company.com) or the username only (user).
* This is required for Z-Push to work properly after autodiscover. * This is required for Z-Push to work properly after autodiscover.
* Possible values: * Possible values:
* false - use the username only (default). * false - use the username only.
* true - use the complete email address. * true - string the mobile sends as username, e.g. full email address (default).
*/ */
define('USE_FULLEMAIL_FOR_LOGIN', false); define('USE_FULLEMAIL_FOR_LOGIN', true);
/********************************************************************************** /**********************************************************************************
* Default FileStateMachine settings * Default FileStateMachine settings
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
* *
* Created : 11.04.2011 * Created : 11.04.2011
* *
* Copyright 2007 - 2015 Zarafa Deutschland GmbH * Copyright 2007 - 2016 Zarafa Deutschland GmbH
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3, * it under the terms of the GNU Affero General Public License, version 3,
...@@ -67,6 +67,7 @@ class DeviceManager { ...@@ -67,6 +67,7 @@ class DeviceManager {
private $loopdetection; private $loopdetection;
private $hierarchySyncRequired; private $hierarchySyncRequired;
private $additionalFoldersHash;
/** /**
* Constructor * Constructor
...@@ -97,6 +98,8 @@ class DeviceManager { ...@@ -97,6 +98,8 @@ class DeviceManager {
$this->stateManager = new StateManager(); $this->stateManager = new StateManager();
$this->stateManager->SetDevice($this->device); $this->stateManager->SetDevice($this->device);
$this->additionalFoldersHash = $this->getAdditionalFoldersHash();
} }
/** /**
...@@ -510,7 +513,7 @@ class DeviceManager { ...@@ -510,7 +513,7 @@ class DeviceManager {
* @access public * @access public
* @return int * @return int
*/ */
public function GetWindowSize($folderid, $type, $uuid, $statecounter, $queuedmessages) { public function GetWindowSize($folderid, $uuid, $statecounter, $queuedmessages) {
if (isset($this->windowSize[$folderid])) if (isset($this->windowSize[$folderid]))
$items = $this->windowSize[$folderid]; $items = $this->windowSize[$folderid];
else else
...@@ -519,7 +522,7 @@ class DeviceManager { ...@@ -519,7 +522,7 @@ class DeviceManager {
$this->setLatestFolder($folderid); $this->setLatestFolder($folderid);
// detect if this is a loop condition // detect if this is a loop condition
if ($this->loopdetection->Detect($folderid, $type, $uuid, $statecounter, $items, $queuedmessages)) if ($this->loopdetection->Detect($folderid, $uuid, $statecounter, $items, $queuedmessages))
$items = ($items == 0) ? 0: 1+($this->loopdetection->IgnoreNextMessage(false)?1:0) ; $items = ($items == 0) ? 0: 1+($this->loopdetection->IgnoreNextMessage(false)?1:0) ;
if ($items >= 0 && $items <= 2) if ($items >= 0 && $items <= 2)
...@@ -608,13 +611,20 @@ class DeviceManager { ...@@ -608,13 +611,20 @@ class DeviceManager {
} }
/** /**
* Indicates if the hierarchy should be resynchronized * Indicates if the hierarchy should be resynchronized based on the general folder state and
* e.g. during PING * if additional folders changed.
* *
* @access public * @access public
* @return boolean * @return boolean
*/ */
public function IsHierarchySyncRequired() { public function IsHierarchySyncRequired() {
$this->loadDeviceData();
// if the hash of the additional folders changed, we have to sync the hierarchy
if ($this->additionalFoldersHash != $this->getAdditionalFoldersHash()) {
$this->hierarchySyncRequired = true;
}
// check if a hierarchy sync might be necessary // check if a hierarchy sync might be necessary
if ($this->device->GetFolderUUID(false) === false) if ($this->device->GetFolderUUID(false) === false)
$this->hierarchySyncRequired = true; $this->hierarchySyncRequired = true;
...@@ -622,6 +632,10 @@ class DeviceManager { ...@@ -622,6 +632,10 @@ class DeviceManager {
return $this->hierarchySyncRequired; return $this->hierarchySyncRequired;
} }
private function getAdditionalFoldersHash() {
return md5(serialize($this->device->GetAdditionalFolders()));
}
/** /**
* Indicates if a full hierarchy resync should be triggered due to loops * Indicates if a full hierarchy resync should be triggered due to loops
* *
......
...@@ -616,7 +616,6 @@ class LoopDetection extends InterProcessData { ...@@ -616,7 +616,6 @@ class LoopDetection extends InterProcessData {
* 3.3.1) item identified, loopcount >= 3 -> ignore item, set ignoredata flag * 3.3.1) item identified, loopcount >= 3 -> ignore item, set ignoredata flag
* *
* @param string $folderid the current folder id to be worked on * @param string $folderid the current folder id to be worked on
* @param string $type the type of that folder (Email, Calendar, Contact, Task)
* @param string $uuid the synkkey * @param string $uuid the synkkey
* @param string $counter the synckey counter * @param string $counter the synckey counter
* @param string $maxItems the current amount of items to be sent to the mobile * @param string $maxItems the current amount of items to be sent to the mobile
...@@ -625,8 +624,8 @@ class LoopDetection extends InterProcessData { ...@@ -625,8 +624,8 @@ class LoopDetection extends InterProcessData {
* @access public * @access public
* @return boolean when returning true if a loop has been identified * @return boolean when returning true if a loop has been identified
*/ */
public function Detect($folderid, $type, $uuid, $counter, $maxItems, $queuedMessages) { public function Detect($folderid, $uuid, $counter, $maxItems, $queuedMessages) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("LoopDetection->Detect(): folderid:'%s' type:'%s' uuid:'%s' counter:'%s' max:'%s' queued:'%s'", $folderid, $type, $uuid, $counter, $maxItems, $queuedMessages)); ZLog::Write(LOGLEVEL_DEBUG, sprintf("LoopDetection->Detect(): folderid:'%s' uuid:'%s' counter:'%s' max:'%s' queued:'%s'", $folderid, $uuid, $counter, $maxItems, $queuedMessages));
$this->broken_message_uuid = $uuid; $this->broken_message_uuid = $uuid;
$this->broken_message_counter = $counter; $this->broken_message_counter = $counter;
...@@ -652,16 +651,10 @@ class LoopDetection extends InterProcessData { ...@@ -652,16 +651,10 @@ class LoopDetection extends InterProcessData {
// completely new/unknown UUID // completely new/unknown UUID
if (empty($current)) if (empty($current))
$current = array("type" => $type, "uuid" => $uuid, "count" => $counter-1, "queued" => $queuedMessages); $current = array("uuid" => $uuid, "count" => $counter-1, "queued" => $queuedMessages);
// if data was created by SetSyncStateUsage(), we need to set the type
// TODO ZP-741: type is not used anywhere and could be removed from the loop data
if (isset($current['uuid']) && $current['uuid'] == $uuid && !isset($current['type'])) {
$current['type'] = $type;
}
// old UUID in cache - the device requested a new state!! // old UUID in cache - the device requested a new state!!
if (isset($current['type']) && $current['type'] == $type && isset($current['uuid']) && $current['uuid'] != $uuid ) { if (isset($current['uuid']) && $current['uuid'] != $uuid ) {
ZLog::Write(LOGLEVEL_DEBUG, "LoopDetection->Detect(): UUID changed for folder"); ZLog::Write(LOGLEVEL_DEBUG, "LoopDetection->Detect(): UUID changed for folder");
// some devices (iPhones) may request new UUIDs after broken items were sent several times // some devices (iPhones) may request new UUIDs after broken items were sent several times
...@@ -687,7 +680,6 @@ class LoopDetection extends InterProcessData { ...@@ -687,7 +680,6 @@ class LoopDetection extends InterProcessData {
// see if there are values // see if there are values
if (isset($current['uuid']) && $current['uuid'] == $uuid && if (isset($current['uuid']) && $current['uuid'] == $uuid &&
isset($current['type']) && $current['type'] == $type &&
isset($current['count'])) { isset($current['count'])) {
// case 1 - standard, during loop-resolving & resolving // case 1 - standard, during loop-resolving & resolving
......
...@@ -21,7 +21,7 @@ ...@@ -21,7 +21,7 @@
* *
* Created : 26.12.2011 * Created : 26.12.2011
* *
* Copyright 2007 - 2013 Zarafa Deutschland GmbH * Copyright 2007 - 2016 Zarafa Deutschland GmbH
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3, * it under the terms of the GNU Affero General Public License, version 3,
...@@ -201,12 +201,13 @@ class StateManager { ...@@ -201,12 +201,13 @@ class StateManager {
* Gets the state for a specified synckey (uuid + counter) * Gets the state for a specified synckey (uuid + counter)
* *
* @param string $synckey * @param string $synckey
* @param boolean $forceHierarchyLoading, default: false
* *
* @access public * @access public
* @return string * @return string
* @throws StateInvalidException, StateNotFoundException * @throws StateInvalidException, StateNotFoundException
*/ */
public function GetSyncState($synckey) { public function GetSyncState($synckey, $forceHierarchyLoading = false) {
// No sync state for sync key '0' // No sync state for sync key '0'
if($synckey == "0") { if($synckey == "0") {
$this->oldStateCounter = 0; $this->oldStateCounter = 0;
...@@ -217,8 +218,8 @@ class StateManager { ...@@ -217,8 +218,8 @@ class StateManager {
list($this->uuid, $this->oldStateCounter) = self::ParseStateKey($synckey); list($this->uuid, $this->oldStateCounter) = self::ParseStateKey($synckey);
// make sure the hierarchy cache is in place // make sure the hierarchy cache is in place
if ($this->hierarchyOperation) if ($this->hierarchyOperation || $forceHierarchyLoading)
$this->loadHierarchyCache(); $this->loadHierarchyCache($forceHierarchyLoading);
// the state machine will discard any sync states before this one, as they are no longer required // the state machine will discard any sync states before this one, as they are no longer required
return $this->statemachine->GetState($this->device->GetDeviceId(), IStateMachine::DEFTYPE, $this->uuid, $this->oldStateCounter, $this->deleteOldStates); return $this->statemachine->GetState($this->device->GetDeviceId(), IStateMachine::DEFTYPE, $this->uuid, $this->oldStateCounter, $this->deleteOldStates);
...@@ -473,12 +474,14 @@ class StateManager { ...@@ -473,12 +474,14 @@ class StateManager {
* Loads the HierarchyCacheState and initializes the HierarchyChache * Loads the HierarchyCacheState and initializes the HierarchyChache
* if this is an hierarchy operation * if this is an hierarchy operation
* *
* @param boolean $forceLoading, default: false
*
* @access private * @access private
* @return boolean * @return boolean
* @throws StateNotFoundException * @throws StateNotFoundException
*/ */
private function loadHierarchyCache() { private function loadHierarchyCache($forceLoading = false) {
if (!$this->hierarchyOperation) if (!$this->hierarchyOperation && $forceLoading == false)
return false; return false;
ZLog::Write(LOGLEVEL_DEBUG, sprintf("StateManager->loadHierarchyCache(): '%s-%s-%s-%d'", $this->device->GetDeviceId(), $this->uuid, IStateMachine::HIERARCHY, $this->oldStateCounter)); ZLog::Write(LOGLEVEL_DEBUG, sprintf("StateManager->loadHierarchyCache(): '%s-%s-%s-%d'", $this->device->GetDeviceId(), $this->uuid, IStateMachine::HIERARCHY, $this->oldStateCounter));
......
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
* *
* Created : 06.01.2012 * Created : 06.01.2012
* *
* Copyright 2007 - 2013 Zarafa Deutschland GmbH * Copyright 2007 - 2016 Zarafa Deutschland GmbH
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3, * it under the terms of the GNU Affero General Public License, version 3,
...@@ -70,6 +70,7 @@ class SyncCollections implements Iterator { ...@@ -70,6 +70,7 @@ class SyncCollections implements Iterator {
private $lastSyncTime; private $lastSyncTime;
private $waitingTime = 0; private $waitingTime = 0;
private $hierarchyExporterChecked = false;
private $loggedGlobalWindowSizeOverwrite = false; private $loggedGlobalWindowSizeOverwrite = false;
...@@ -118,17 +119,18 @@ class SyncCollections implements Iterator { ...@@ -118,17 +119,18 @@ class SyncCollections implements Iterator {
* Loads all collections known for the current device * Loads all collections known for the current device
* *
* @param boolean $overwriteLoaded (opt) overwrites Collection with saved state if set to true * @param boolean $overwriteLoaded (opt) overwrites Collection with saved state if set to true
* @param boolean $loadState (opt) indicates if the collection sync state should be loaded, default true * @param boolean $loadState (opt) indicates if the collection sync state should be loaded, default false
* @param boolean $checkPermissions (opt) if set to true each folder will pass * @param boolean $checkPermissions (opt) if set to true each folder will pass
* through a backend->Setup() to check permissions. * through a backend->Setup() to check permissions.
* If this fails a StatusException will be thrown. * If this fails a StatusException will be thrown.
* @param boolean $loadHierarchy (opt) if the hierarchy sync states should be loaded, default false
* *
* @access public * @access public
* @throws StatusException with SyncCollections::ERROR_WRONG_HIERARCHY if permission check fails * @throws StatusException with SyncCollections::ERROR_WRONG_HIERARCHY if permission check fails
* @throws StateInvalidException if the sync state can not be found or relation between states is invalid ($loadState = true) * @throws StateInvalidException if the sync state can not be found or relation between states is invalid ($loadState = true)
* @return boolean * @return boolean
*/ */
public function LoadAllCollections($overwriteLoaded = false, $loadState = false, $checkPermissions = false) { public function LoadAllCollections($overwriteLoaded = false, $loadState = false, $checkPermissions = false, $loadHierarchy = false) {
$this->loadStateManager(); $this->loadStateManager();
// this operation should not remove old state counters // this operation should not remove old state counters
...@@ -144,6 +146,10 @@ class SyncCollections implements Iterator { ...@@ -144,6 +146,10 @@ class SyncCollections implements Iterator {
$invalidStates = true; $invalidStates = true;
} }
// load the hierarchy data - there are no permissions to verify so we just set it to false
if ($loadHierarchy && !$this->LoadCollection(false, $loadState, false))
throw new StatusException("Invalid states found while loading hierarchy data. Forcing hierarchy sync");
if ($invalidStates) if ($invalidStates)
throw new StateInvalidException("Invalid states found while loading collections. Forcing sync"); throw new StateInvalidException("Invalid states found while loading collections. Forcing sync");
...@@ -180,6 +186,10 @@ class SyncCollections implements Iterator { ...@@ -180,6 +186,10 @@ class SyncCollections implements Iterator {
// in case there is something wrong with the state, just stop here // in case there is something wrong with the state, just stop here
// later when trying to retrieve the SyncParameters nothing will be found // later when trying to retrieve the SyncParameters nothing will be found
if ($folderid === false) {
throw new StatusException(sprintf("SyncCollections->LoadCollection(): could not get FOLDERDATA state of the hierarchy uuid: %s", $spa->GetUuid()), self::ERROR_WRONG_HIERARCHY);
}
// we also generate a fake change, so a sync on this folder is triggered // we also generate a fake change, so a sync on this folder is triggered
$this->changes[$folderid] = 1; $this->changes[$folderid] = 1;
...@@ -196,7 +206,8 @@ class SyncCollections implements Iterator { ...@@ -196,7 +206,8 @@ class SyncCollections implements Iterator {
// load the latest known syncstate if requested // load the latest known syncstate if requested
if ($addStatus && $loadState === true) { if ($addStatus && $loadState === true) {
try { try {
$this->addparms[$folderid]["state"] = $this->stateManager->GetSyncState($spa->GetLatestSyncKey()); // make sure the hierarchy cache is loaded when we are loading hierarchy states
$this->addparms[$folderid]["state"] = $this->stateManager->GetSyncState($spa->GetLatestSyncKey(), ($folderid === false));
} }
catch (StateNotFoundException $snfe) { catch (StateNotFoundException $snfe) {
// if we can't find the state, first we should try a sync of that folder, so // if we can't find the state, first we should try a sync of that folder, so
...@@ -493,7 +504,7 @@ class SyncCollections implements Iterator { ...@@ -493,7 +504,7 @@ class SyncCollections implements Iterator {
if (!empty($classes)) { if (!empty($classes)) {
// initialize all possible folders // initialize all possible folders
foreach ($this->collections as $folderid => $spa) { foreach ($this->collections as $folderid => $spa) {
if ($onlyPingable && $spa->GetPingableFlag() !== true) if (($onlyPingable && $spa->GetPingableFlag() !== true) || ! $folderid)
continue; continue;
// get the user store if this is a additional folder // get the user store if this is a additional folder
...@@ -544,7 +555,7 @@ class SyncCollections implements Iterator { ...@@ -544,7 +555,7 @@ class SyncCollections implements Iterator {
throw new StatusException("SyncCollections->CheckForChanges(): PolicyKey changed. Provisioning required.", self::ERROR_WRONG_HIERARCHY); throw new StatusException("SyncCollections->CheckForChanges(): PolicyKey changed. Provisioning required.", self::ERROR_WRONG_HIERARCHY);
// Check if a hierarchy sync is necessary // Check if a hierarchy sync is necessary
if (ZPush::GetDeviceManager()->IsHierarchySyncRequired()) if ($this->countHierarchyChange())
throw new StatusException("SyncCollections->CheckForChanges(): HierarchySync required.", self::HIERARCHY_CHANGED); throw new StatusException("SyncCollections->CheckForChanges(): HierarchySync required.", self::HIERARCHY_CHANGED);
// Check if there are newer requests // Check if there are newer requests
...@@ -567,15 +578,21 @@ class SyncCollections implements Iterator { ...@@ -567,15 +578,21 @@ class SyncCollections implements Iterator {
$validNotifications = false; $validNotifications = false;
foreach ($notifications as $folderid) { foreach ($notifications as $folderid) {
// check if the notification on the folder is within our filter // Check hierarchy notifications
if ($this->CountChange($folderid)) { if ($folderid === IBackend::HIERARCHYNOTIFICATION) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("SyncCollections->CheckForChanges(): Notification received on folder '%s'", $folderid)); // check received hierarchy notifications by exporting
$validNotifications = true; if ($this->countHierarchyChange(true))
$this->waitingTime = time()-$started; throw new StatusException("SyncCollections->CheckForChanges(): HierarchySync required.", self::HIERARCHY_CHANGED);
} }
else { // check if the notification on the folder is within our filter
ZLog::Write(LOGLEVEL_DEBUG, sprintf("SyncCollections->CheckForChanges(): Notification received on folder '%s', but it is not relevant", $folderid)); else if ($this->CountChange($folderid)) {
} ZLog::Write(LOGLEVEL_DEBUG, sprintf("SyncCollections->CheckForChanges(): Notification received on folder '%s'", $folderid));
$validNotifications = true;
$this->waitingTime = time()-$started;
}
else {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("SyncCollections->CheckForChanges(): Notification received on folder '%s', but it is not relevant", $folderid));
}
} }
if ($validNotifications) if ($validNotifications)
return true; return true;
...@@ -678,6 +695,55 @@ class SyncCollections implements Iterator { ...@@ -678,6 +695,55 @@ class SyncCollections implements Iterator {
return ($changecount > 0); return ($changecount > 0);
} }
/**
* Checks the hierarchy for changes.
*
* @param boolean export changes, default: false
*
* @access private
* @return boolean indicating if changes were found or not
*/
private function countHierarchyChange($exportChanges = false) {
$folderid = false;
$spa = $this->GetCollection($folderid);
// Check with device manager if the hierarchy should be reloaded.
// New additional folders are loaded here.
if (ZPush::GetDeviceManager()->IsHierarchySyncRequired()) {
ZLog::Write(LOGLEVEL_DEBUG, "SyncCollections->countHierarchyChange(): DeviceManager says HierarchySync is required.");
return true;
}
$changecount = false;
if ($exportChanges || $this->hierarchyExporterChecked === false) {
try {
$changesMem = ZPush::GetDeviceManager()->GetHierarchyChangesWrapper();
// the hierarchyCache should now fully be initialized - check for changes in the additional folders
$changesMem->Config(ZPush::GetAdditionalSyncFolders());
$exporter = ZPush::GetBackend()->GetExporter();
if ($exporter !== false && isset($this->addparms[$folderid]["state"])) {
$exporter->Config($this->addparms[$folderid]["state"]);
$ret = $exporter->InitializeExporter($changesMem);
while(is_array($exporter->Synchronize()));
if ($ret !== false)
$changecount = $changesMem->GetChangeCount();
$this->hierarchyExporterChecked = true;
}
}
catch (StatusException $ste) {
throw new StatusException("SyncCollections->countHierarchyChange(): exporter can not be re-configured.", self::ERROR_WRONG_HIERARCHY, null, LOGLEVEL_WARN);
}
// start over if exporter can not be configured atm
if ($changecount === false )
ZLog::Write(LOGLEVEL_WARN, "SyncCollections->countHierarchyChange(): no changes received from Exporter.");
}
return ($changecount > 0);
}
/** /**
* Returns an array with all folderid and the amount of changes found * Returns an array with all folderid and the amount of changes found
* *
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
* *
* Created : 02.01.2012 * Created : 02.01.2012
* *
* Copyright 2007 - 2013 Zarafa Deutschland GmbH * Copyright 2007 - 2016 Zarafa Deutschland GmbH
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3, * it under the terms of the GNU Affero General Public License, version 3,
...@@ -46,6 +46,7 @@ ...@@ -46,6 +46,7 @@
************************************************/ ************************************************/
interface IBackend { interface IBackend {
const HIERARCHYNOTIFICATION = 'hierarchynotification';
/** /**
* Returns a IStateMachine implementation used to save states * Returns a IStateMachine implementation used to save states
* *
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
* *
* Created : 16.02.2012 * Created : 16.02.2012
* *
* Copyright 2007 - 2015 Zarafa Deutschland GmbH * Copyright 2007 - 2016 Zarafa Deutschland GmbH
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3, * it under the terms of the GNU Affero General Public License, version 3,
...@@ -246,6 +246,7 @@ class FolderChange extends RequestProcessor { ...@@ -246,6 +246,7 @@ class FolderChange extends RequestProcessor {
// update SPA & save it // update SPA & save it
$spa->SetSyncKey($newsynckey); $spa->SetSyncKey($newsynckey);
$spa->SetFolderId(false);
self::$deviceManager->GetStateManager()->SetSynchedFolderState($spa); self::$deviceManager->GetStateManager()->SetSynchedFolderState($spa);
// invalidate all pingable flags // invalidate all pingable flags
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
* *
* Created : 16.02.2012 * Created : 16.02.2012
* *
* Copyright 2007 - 2015 Zarafa Deutschland GmbH * Copyright 2007 - 2016 Zarafa Deutschland GmbH
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3, * it under the terms of the GNU Affero General Public License, version 3,
...@@ -258,6 +258,7 @@ class FolderSync extends RequestProcessor { ...@@ -258,6 +258,7 @@ class FolderSync extends RequestProcessor {
// update SPA & save it // update SPA & save it
$spa->SetSyncKey($newsynckey); $spa->SetSyncKey($newsynckey);
$spa->SetFolderId(false);
self::$deviceManager->GetStateManager()->SetSynchedFolderState($spa); self::$deviceManager->GetStateManager()->SetSynchedFolderState($spa);
// invalidate all pingable flags // invalidate all pingable flags
......
...@@ -65,7 +65,7 @@ class Ping extends RequestProcessor { ...@@ -65,7 +65,7 @@ class Ping extends RequestProcessor {
// Load all collections - do load states and check permissions // Load all collections - do load states and check permissions
try { try {
$sc->LoadAllCollections(true, true, true); $sc->LoadAllCollections(true, true, true, true);
} }
catch (StateInvalidException $siex) { catch (StateInvalidException $siex) {
// if no params are present, indicate to send params, else do hierarchy sync // if no params are present, indicate to send params, else do hierarchy sync
......
...@@ -164,9 +164,11 @@ class Sync extends RequestProcessor { ...@@ -164,9 +164,11 @@ class Sync extends RequestProcessor {
$spa->RemoveSyncKey(); $spa->RemoveSyncKey();
$spa->DelFolderStat(); $spa->DelFolderStat();
} }
else if ($synckey !== false && $synckey !== $spa->GetSyncKey()) { else if ($synckey !== false) {
ZLog::Write(LOGLEVEL_DEBUG, "HandleSync(): Synckey does not match latest saved for this folder, removing folderstat to force Exporter setup"); if ($synckey !== $spa->GetSyncKey()) {
$spa->DelFolderStat(); ZLog::Write(LOGLEVEL_DEBUG, "HandleSync(): Synckey does not match latest saved for this folder, removing folderstat to force Exporter setup");
$spa->DelFolderStat();
}
$spa->SetSyncKey($synckey); $spa->SetSyncKey($synckey);
} }
} }
...@@ -579,7 +581,7 @@ class Sync extends RequestProcessor { ...@@ -579,7 +581,7 @@ class Sync extends RequestProcessor {
// Load all collections - do not overwrite existing (received!), load states and check permissions // Load all collections - do not overwrite existing (received!), load states and check permissions
try { try {
$sc->LoadAllCollections(false, true, true); $sc->LoadAllCollections(false, true, true, true);
} }
catch (StateInvalidException $siex) { catch (StateInvalidException $siex) {
$status = SYNC_STATUS_INVALIDSYNCKEY; $status = SYNC_STATUS_INVALIDSYNCKEY;
...@@ -605,6 +607,14 @@ class Sync extends RequestProcessor { ...@@ -605,6 +607,14 @@ class Sync extends RequestProcessor {
if (!$sc->HasCollections()) if (!$sc->HasCollections())
$status = SYNC_STATUS_SYNCREQUESTINCOMPLETE; $status = SYNC_STATUS_SYNCREQUESTINCOMPLETE;
} }
else if (isset($hbinterval)) {
// load the hierarchy data - there are no permissions to verify so we just set it to false
if (!$sc->LoadCollection(false, true, false)) {
$status = SYNC_STATUS_FOLDERHIERARCHYCHANGED;
self::$topCollector->AnnounceInformation(sprintf("StatusException code: %d", $status), $this->singleFolder);
$this->saveMultiFolderInfo("exeption", "StatusException");
}
}
// HEARTBEAT & Empty sync // HEARTBEAT & Empty sync
if ($status == SYNC_STATUS_SUCCESS && (isset($hbinterval) || $emptysync == true)) { if ($status == SYNC_STATUS_SUCCESS && (isset($hbinterval) || $emptysync == true)) {
...@@ -1026,7 +1036,7 @@ class Sync extends RequestProcessor { ...@@ -1026,7 +1036,7 @@ class Sync extends RequestProcessor {
} }
if($sc->GetParameter($spa, "getchanges") && $spa->HasFolderId() && $spa->HasContentClass() && $spa->HasSyncKey()) { if($sc->GetParameter($spa, "getchanges") && $spa->HasFolderId() && $spa->HasContentClass() && $spa->HasSyncKey()) {
$windowSize = self::$deviceManager->GetWindowSize($spa->GetFolderId(), $spa->GetContentClass(), $spa->GetUuid(), $spa->GetUuidCounter(), $changecount); $windowSize = self::$deviceManager->GetWindowSize($spa->GetFolderId(), $spa->GetUuid(), $spa->GetUuidCounter(), $changecount);
// limit windowSize to the max available limit of the global window size left // limit windowSize to the max available limit of the global window size left
$globallyAvailable = $sc->GetGlobalWindowSize() - $this->globallyExportedItems; $globallyAvailable = $sc->GetGlobalWindowSize() - $this->globallyExportedItems;
......
...@@ -106,8 +106,8 @@ class ZPushAdmin { ...@@ -106,8 +106,8 @@ class ZPushAdmin {
$sc = new SyncCollections(); $sc = new SyncCollections();
$sc->SetStateManager($stateManager); $sc->SetStateManager($stateManager);
// load all collections of device without loading states or checking permissions // load all collections of device without loading states, checking permissions or loading the hierarchy
$sc->LoadAllCollections(true, false, false); $sc->LoadAllCollections(true, false, false, false);
if ($sc->GetLastSyncTime()) if ($sc->GetLastSyncTime())
$device->SetLastSyncTime($sc->GetLastSyncTime()); $device->SetLastSyncTime($sc->GetLastSyncTime());
......
...@@ -306,7 +306,6 @@ class WBXMLEncoder extends WBXMLDefs { ...@@ -306,7 +306,6 @@ class WBXMLEncoder extends WBXMLDefs {
private function _contentStream($stream, $asBase64) { private function _contentStream($stream, $asBase64) {
// write full stream, including the finalizing terminator to the output stream (stuff outTermStr() would do) // write full stream, including the finalizing terminator to the output stream (stuff outTermStr() would do)
$this->outByte(self::WBXML_STR_I); $this->outByte(self::WBXML_STR_I);
fseek($stream, 0, SEEK_SET);
if ($asBase64) { if ($asBase64) {
$out_filter = stream_filter_append($this->_out, 'convert.base64-encode'); $out_filter = stream_filter_append($this->_out, 'convert.base64-encode');
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment