function HandleFolderSync($backend, $protocolversion) { global $zpushdtd; global $input, $output; // Maps serverid -> clientid for items that are received from the PIM $map = array(); $decoder = new WBXMLDecoder($input, $zpushdtd); $encoder = new WBXMLEncoder($output, $zpushdtd); // Parse input if (!$decoder->getElementStartTag(SYNC_FOLDERHIERARCHY_FOLDERSYNC)) { return false; } if (!$decoder->getElementStartTag(SYNC_FOLDERHIERARCHY_SYNCKEY)) { return false; } $synckey = $decoder->getElementContent(); if (!$decoder->getElementEndTag()) { return false; } if ($decoder->getElementStartTag(SYNC_FOLDERHIERARCHY_CHANGES)) { // Ignore <Count> if present if ($decoder->getElementStartTag(SYNC_FOLDERHIERARCHY_COUNT)) { $decoder->getElementContent(); if (!$decoder->getElementEndTag()) { return false; } } // Process the changes (either <Add>, <Modify>, or <Remove>) $element = $decoder->getElement(); if ($element[EN_TYPE] != EN_TYPE_STARTTAG) { return false; } while (1) { $folder = new SyncFolder(); if (!$folder->decode($decoder)) { break; } switch ($element[EN_TAG]) { case SYNC_ADD: case SYNC_MODIFY: $serverid = $backend->folderimporter->ImportFolderChange($folder); break; case SYNC_REMOVE: $serverid = $backend->folderimporter->ImportFolderDeletion($folder); break; } if ($serverid) { $map[$serverid] = $folder->clientid; } } if (!$decoder->getElementEndTag()) { return false; } } if (!$decoder->getElementEndTag()) { return false; } // We have processed incoming foldersync requests, now send the PIM // our changes // First, get the syncstate that is associated with this synckey $statemachine = new StateMachine(); // The state machine will discard any sync states before this one, as they are no // longer required $syncstate = $statemachine->getSyncState($synckey); // We will be saving the sync state under 'newsynckey' $newsynckey = $statemachine->getNewSyncKey($synckey); // The MemImporter caches all imports in-memory, so we can send a change count // before sending the actual data. As the amount of data done in this operation // is rather low, this is not memory problem. Note that this is not done when // sync'ing messages - we let the exporter write directly to WBXML. $importer = new ImportHierarchyChangesMem($encoder); // Request changes from backend, they will be sent to the MemImporter passed as the first // argument, which stores them in $importer. Returns the new sync state for this exporter. $exporter = $backend->GetExporter(); $exporter->Config($importer, false, false, $syncstate, 0, 0); while (is_array($exporter->Synchronize())) { } // Output our WBXML reply now $encoder->StartWBXML(); $encoder->startTag(SYNC_FOLDERHIERARCHY_FOLDERSYNC); $encoder->startTag(SYNC_FOLDERHIERARCHY_ERROR); $encoder->content(1); $encoder->endTag(); $encoder->startTag(SYNC_FOLDERHIERARCHY_SYNCKEY); $encoder->content($newsynckey); $encoder->endTag(); $encoder->startTag(SYNC_FOLDERHIERARCHY_CHANGES); $encoder->startTag(SYNC_FOLDERHIERARCHY_COUNT); $encoder->content($importer->count); $encoder->endTag(); if (count($importer->changed) > 0) { foreach ($importer->changed as $folder) { $encoder->startTag(SYNC_FOLDERHIERARCHY_ADD); $folder->encode($encoder); $encoder->endTag(); } } if (count($importer->deleted) > 0) { foreach ($importer->deleted as $folder) { $encoder->startTag(SYNC_FOLDERHIERARCHY_REMOVE); $encoder->startTag(SYNC_FOLDERHIERARCHY_SERVERENTRYID); $encoder->content($folder); $encoder->endTag(); $encoder->endTag(); } } $encoder->endTag(); $encoder->endTag(); // Save the sync state for the next time $syncstate = $exporter->GetState(); $statemachine->setSyncState($newsynckey, $syncstate); return true; }
function HandleFolderSync($backend, $protocolversion) { global $zpushdtd; global $input, $output; // Maps serverid -> clientid for items that are received from the PIM $map = array(); $decoder = new WBXMLDecoder($input, $zpushdtd); $encoder = new WBXMLEncoder($output, $zpushdtd); // Parse input if (!$decoder->getElementStartTag(SYNC_FOLDERHIERARCHY_FOLDERSYNC)) { return false; } if (!$decoder->getElementStartTag(SYNC_FOLDERHIERARCHY_SYNCKEY)) { return false; } $synckey = $decoder->getElementContent(); if (!$decoder->getElementEndTag()) { return false; } // First, get the syncstate that is associated with this synckey $statemachine = new StateMachine(); // The state machine will discard any sync states before this one, as they are no // longer required $syncstate = $statemachine->getSyncState($synckey); // additional information about already seen folders $sfolderstate = $statemachine->getSyncState("s" . $synckey); if (!$sfolderstate) { $foldercache = array(); if ($sfolderstate === false) { debugLog("Error: FolderChacheState for state 's" . $synckey . "' not found. Reinitializing..."); } } else { $foldercache = unserialize($sfolderstate); // transform old seenfolder array if (array_key_exists("0", $foldercache)) { $tmp = array(); foreach ($foldercache as $s) { $tmp[$s] = new SyncFolder(); } $foldercache = $tmp; } } // We will be saving the sync state under 'newsynckey' $newsynckey = $statemachine->getNewSyncKey($synckey); $changes = false; if ($decoder->getElementStartTag(SYNC_FOLDERHIERARCHY_CHANGES)) { // Ignore <Count> if present if ($decoder->getElementStartTag(SYNC_FOLDERHIERARCHY_COUNT)) { $decoder->getElementContent(); if (!$decoder->getElementEndTag()) { return false; } } // Process the changes (either <Add>, <Modify>, or <Remove>) $element = $decoder->getElement(); if ($element[EN_TYPE] != EN_TYPE_STARTTAG) { return false; } while (1) { $folder = new SyncFolder(); if (!$folder->decode($decoder)) { break; } // Configure importer with last state $importer = $backend->GetHierarchyImporter(); $importer->Config($syncstate); switch ($element[EN_TAG]) { case SYNC_ADD: case SYNC_MODIFY: $serverid = $importer->ImportFolderChange($folder); // add folder to the serverflags $foldercache[$serverid] = $folder; $changes = true; break; case SYNC_REMOVE: $serverid = $importer->ImportFolderDeletion($folder); $changes = true; // remove folder from the folderchache if (array_key_exists($serverid, $foldercache)) { unset($foldercache[$serverid]); } break; } if ($serverid) { $map[$serverid] = $folder->clientid; } } if (!$decoder->getElementEndTag()) { return false; } } if (!$decoder->getElementEndTag()) { return false; } // We have processed incoming foldersync requests, now send the PIM // our changes // The MemImporter caches all imports in-memory, so we can send a change count // before sending the actual data. As the amount of data done in this operation // is rather low, this is not memory problem. Note that this is not done when // sync'ing messages - we let the exporter write directly to WBXML. $importer = new ImportHierarchyChangesMem($foldercache); // Request changes from backend, they will be sent to the MemImporter passed as the first // argument, which stores them in $importer. Returns the new sync state for this exporter. $exporter = $backend->GetExporter(); $exporter->Config($importer, false, false, $syncstate, 0, 0); while (is_array($exporter->Synchronize())) { } // Output our WBXML reply now $encoder->StartWBXML(); // Simple Public Folder Sync global $pubfolders; if (!empty($pubfolders)) { foreach ($pubfolders as $pf) { $publicf = new SyncFolder(); $publicf->type = $pf['type']; $publicf->displayname = $pf['name']; $publicf->parentid = "0"; $publicf->serverid = $pf['puid']; $publicf->nodebug = true; $importer->ImportFolderChange($publicf); } } $encoder->startTag(SYNC_FOLDERHIERARCHY_FOLDERSYNC); $encoder->startTag(SYNC_FOLDERHIERARCHY_STATUS); $encoder->content(1); $encoder->endTag(); $encoder->startTag(SYNC_FOLDERHIERARCHY_SYNCKEY); // only send new synckey if changes were processed or there are outgoing changes $encoder->content($changes || $importer->count > 0 ? $newsynckey : $synckey); $encoder->endTag(); $encoder->startTag(SYNC_FOLDERHIERARCHY_CHANGES); $encoder->startTag(SYNC_FOLDERHIERARCHY_COUNT); $encoder->content($importer->count); $encoder->endTag(); if (count($importer->changed) > 0) { foreach ($importer->changed as $folder) { // send a modify flag if the folder is already known on the device if (isset($folder->serverid) && array_key_exists($folder->serverid, $foldercache) !== false) { $encoder->startTag(SYNC_FOLDERHIERARCHY_UPDATE); } else { $encoder->startTag(SYNC_FOLDERHIERARCHY_ADD); } $foldercache[$folder->serverid] = $folder; $folder->encode($encoder); $encoder->endTag(); } } if (count($importer->deleted) > 0) { foreach ($importer->deleted as $folder) { $encoder->startTag(SYNC_FOLDERHIERARCHY_REMOVE); $encoder->startTag(SYNC_FOLDERHIERARCHY_SERVERENTRYID); $encoder->content($folder); $encoder->endTag(); $encoder->endTag(); // remove folder from the folderchache if (array_key_exists($folder, $foldercache)) { unset($foldercache[$folder]); } } } $encoder->endTag(); $encoder->endTag(); // Save the sync state for the next time $syncstate = $exporter->GetState(); $statemachine->setSyncState($newsynckey, $syncstate); $statemachine->setSyncState("s" . $newsynckey, serialize($foldercache)); return true; }
/** * Handles the FolderSync command * * @param int $commandCode * * @access public * @return boolean */ public function Handle($commandCode) { // Maps serverid -> clientid for items that are received from the PIM $map = array(); // Parse input if (!self::$decoder->getElementStartTag(SYNC_FOLDERHIERARCHY_FOLDERSYNC)) { return false; } if (!self::$decoder->getElementStartTag(SYNC_FOLDERHIERARCHY_SYNCKEY)) { return false; } $synckey = self::$decoder->getElementContent(); if (!self::$decoder->getElementEndTag()) { return false; } $status = SYNC_FSSTATUS_SUCCESS; $newsynckey = $synckey; try { $syncstate = self::$deviceManager->GetStateManager()->GetSyncState($synckey); // We will be saving the sync state under 'newsynckey' $newsynckey = self::$deviceManager->GetStateManager()->GetNewSyncKey($synckey); } catch (StateNotFoundException $snfex) { $status = SYNC_FSSTATUS_SYNCKEYERROR; } catch (StateInvalidException $sive) { $status = SYNC_FSSTATUS_SYNCKEYERROR; } // The ChangesWrapper caches all imports in-memory, so we can send a change count // before sending the actual data. // the HierarchyCache is notified and the changes from the PIM are transmitted to the actual backend $changesMem = self::$deviceManager->GetHierarchyChangesWrapper(); // the hierarchyCache should now fully be initialized - check for changes in the additional folders $changesMem->Config(ZPush::GetAdditionalSyncFolders()); // process incoming changes if (self::$decoder->getElementStartTag(SYNC_FOLDERHIERARCHY_CHANGES)) { // Ignore <Count> if present if (self::$decoder->getElementStartTag(SYNC_FOLDERHIERARCHY_COUNT)) { self::$decoder->getElementContent(); if (!self::$decoder->getElementEndTag()) { return false; } } // Process the changes (either <Add>, <Modify>, or <Remove>) $element = self::$decoder->getElement(); if ($element[EN_TYPE] != EN_TYPE_STARTTAG) { return false; } $importer = false; while (1) { $folder = new SyncFolder(); if (!$folder->Decode(self::$decoder)) { break; } try { if ($status == SYNC_FSSTATUS_SUCCESS && !$importer) { // Configure the backends importer with last state $importer = self::$backend->GetImporter(); $importer->Config($syncstate); // the messages from the PIM will be forwarded to the backend $changesMem->forwardImporter($importer); } if ($status == SYNC_FSSTATUS_SUCCESS) { switch ($element[EN_TAG]) { case SYNC_ADD: case SYNC_MODIFY: $serverid = $changesMem->ImportFolderChange($folder); break; case SYNC_REMOVE: $serverid = $changesMem->ImportFolderDeletion($folder); break; } // TODO what does $map?? if ($serverid) { $map[$serverid] = $folder->clientid; } } else { ZLog::Write(LOGLEVEL_WARN, sprintf("Request->HandleFolderSync(): ignoring incoming folderchange for folder '%s' as status indicates problem.", $folder->displayname)); self::$topCollector->AnnounceInformation("Incoming change ignored", true); } } catch (StatusException $stex) { $status = $stex->getCode(); } } if (!self::$decoder->getElementEndTag()) { return false; } } else { // check for a potential process loop like described in Issue ZP-5 if ($synckey != "0" && self::$deviceManager->IsHierarchyFullResyncRequired()) { $status = SYNC_FSSTATUS_SYNCKEYERROR; } self::$deviceManager->AnnounceProcessStatus(false, $status); } if (!self::$decoder->getElementEndTag()) { return false; } // We have processed incoming foldersync requests, now send the PIM // our changes // Output our WBXML reply now self::$encoder->StartWBXML(); self::$encoder->startTag(SYNC_FOLDERHIERARCHY_FOLDERSYNC); if ($status == SYNC_FSSTATUS_SUCCESS) { try { // do nothing if this is an invalid device id (like the 'validate' Androids internal client sends) if (!Request::IsValidDeviceID()) { throw new StatusException(sprintf("Request::IsValidDeviceID() indicated that '%s' is not a valid device id", Request::GetDeviceID()), SYNC_FSSTATUS_SERVERERROR); } // Changes from backend are sent to the MemImporter and processed for the HierarchyCache. // The state which is saved is from the backend, as the MemImporter is only a proxy. $exporter = self::$backend->GetExporter(); $exporter->Config($syncstate); $exporter->InitializeExporter($changesMem); // Stream all changes to the ImportExportChangesMem while (is_array($exporter->Synchronize())) { } // get the new state from the backend $newsyncstate = isset($exporter) ? $exporter->GetState() : ""; } catch (StatusException $stex) { if ($stex->getCode() == SYNC_FSSTATUS_CODEUNKNOWN) { $status = SYNC_FSSTATUS_SYNCKEYERROR; } else { $status = $stex->getCode(); } } } self::$encoder->startTag(SYNC_FOLDERHIERARCHY_STATUS); self::$encoder->content($status); self::$encoder->endTag(); if ($status == SYNC_FSSTATUS_SUCCESS) { self::$encoder->startTag(SYNC_FOLDERHIERARCHY_SYNCKEY); $synckey = $changesMem->IsStateChanged() ? $newsynckey : $synckey; self::$encoder->content($synckey); self::$encoder->endTag(); // Stream folders directly to the PDA $streamimporter = new ImportChangesStream(self::$encoder, false); $changesMem->InitializeExporter($streamimporter); $changeCount = $changesMem->GetChangeCount(); self::$encoder->startTag(SYNC_FOLDERHIERARCHY_CHANGES); self::$encoder->startTag(SYNC_FOLDERHIERARCHY_COUNT); self::$encoder->content($changeCount); self::$encoder->endTag(); while ($changesMem->Synchronize()) { } self::$encoder->endTag(); self::$topCollector->AnnounceInformation(sprintf("Outgoing %d folders", $changeCount), true); // everything fine, save the sync state for the next time if ($synckey == $newsynckey) { self::$deviceManager->GetStateManager()->SetSyncState($newsynckey, $newsyncstate); } } self::$encoder->endTag(); return true; }
function HandleFolderSync($backend, $devid, $protocolversion) { global $zpushdtd; global $input, $output; global $useragent; global $user; // Maps serverid -> clientid for items that are received from the PIM $map = array(); $decoder = new WBXMLDecoder($input, $zpushdtd); $encoder = new WBXMLEncoder($output, $zpushdtd); // Parse input if (!$decoder->getElementStartTag(SYNC_FOLDERHIERARCHY_FOLDERSYNC)) { return false; } if (!$decoder->getElementStartTag(SYNC_FOLDERHIERARCHY_SYNCKEY)) { return false; } $synckey = $decoder->getElementContent(); if (!$decoder->getElementEndTag()) { return false; } // First, get the syncstate that is associated with this synckey $statemachine = new StateMachine($devid, $user); // The state machine will discard any sync states before this one, as they are no // longer required $syncstate = $statemachine->getSyncState($synckey); // Get Foldercache $SyncCache = unserialize($statemachine->getSyncCache()); if (isset($SyncCache['folders']) && is_array($SyncCache['folders'])) { foreach ($SyncCache['folders'] as $key => $value) { if (!isset($value['class'])) { $statemachine->deleteSyncCache(); _ErrorHandleFolderSync("9"); return true; } $exporter = $backend->GetExporter($key); if (isset($exporter->exporter) && $exporter->exporter === false) { unset($SyncCache['folders'][$key]); } } } // additional information about already seen folders if ($synckey != "0") { $seenfolders = $statemachine->getSyncState("s" . $synckey); } // if we have any error with one of the requests bail out here! if ($synckey != "0" && is_numeric($seenfolders) && $seenfolders < 0 || is_numeric($syncstate) && $syncstate < 0) { // if we get a numeric syncstate back it means we have an error... debugLog("GetSyncState ERROR (Seenfolders: " . abs($seenfolders) . ", Syncstate: " . abs($syncstate) . ")"); if ($seenfolders < 0) { $status = abs($seenfolders); } if ($syncstate < 0) { $status = abs($syncstate); } // Output our WBXML reply now _ErrorHandleFolderSync(abs($status)); return true; } else { $foldercache = unserialize($statemachine->getSyncCache()); // Clear the foldercache in SyncCache in case the SyncKey = 0 if ($synckey == "0") { // $statemachine->deleteSyncCache(); unset($foldercache['folders']); debugLog("Clean the folders in foldercache"); } debugLog("GetSyncState OK"); } if ($synckey == "0" && (!isset($seenfolders) || is_numeric($seenfolders) && $seenfolders < 0)) { $seenfolders = false; } $seenfolders = unserialize($seenfolders); if (!$seenfolders) { $seenfolders = array(); } if (!$foldercache || !is_array($foldercache)) { $foldercache = array(); } // lets clean the old state files away... if ($synckey != "0") { $statemachine->cleanOldSyncState("s" . $synckey); if (($delstatus = $statemachine->cleanOldSyncState($synckey)) !== true) { _ErrorHandleFolderSync(abs($delstatus)); return true; } } // We will be saving the sync state under 'newsynckey' $newsynckey = $statemachine->getNewSyncKey($synckey); $changes = false; if ($decoder->getElementStartTag(SYNC_FOLDERHIERARCHY_CHANGES)) { // Ignore <Count> if present if ($decoder->getElementStartTag(SYNC_FOLDERHIERARCHY_COUNT)) { $decoder->getElementContent(); if (!$decoder->getElementEndTag()) { return false; } } // Process the changes (either <Add>, <Modify>, or <Remove>) $element = $decoder->getElement(); if ($element[EN_TYPE] != EN_TYPE_STARTTAG) { return false; } while (1) { $folder = new SyncFolder(); if (!$folder->decode($decoder)) { break; } // Configure importer with last state $importer = $backend->GetHierarchyImporter(); $importer->Config($syncstate); switch ($element[EN_TAG]) { case SYNC_ADD: case SYNC_MODIFY: $serverid = $importer->ImportFolderChange($folder); $statemachine->updateSyncCacheFolder($foldercache, $serverid, $folder->parentid, $folder->displayname, $folder->type); // add folder to the serverflags $seenfolders[] = $serverid; $changes = true; break; case SYNC_REMOVE: $serverid = $importer->ImportFolderDeletion($folder); // remove folder from the folderflags array $changes = true; if (($sid = array_search($serverid, $seenfolders)) !== false) { unset($seenfolders[$sid]); $seenfolders = array_values($seenfolders); } $statemachine->deleteSyncCacheFolder($foldercache, $serverid); break; } if ($serverid) { $map[$serverid] = $folder->clientid; } } if (!$decoder->getElementEndTag()) { return false; } } if (!$decoder->getElementEndTag()) { return false; } // We have processed incoming foldersync requests, now send the PIM // our changes // The MemImporter caches all imports in-memory, so we can send a change count // before sending the actual data. As the amount of data done in this operation // is rather low, this is not memory problem. Note that this is not done when // sync'ing messages - we let the exporter write directly to WBXML. $importer = new ImportHierarchyChangesMem($encoder); // Request changes from backend, they will be sent to the MemImporter passed as the first // argument, which stores them in $importer. Returns the new sync state for this exporter. $exporter = $backend->GetExporter(); $exporter->Config($importer, false, false, $syncstate, 0, 0, false, false); while (is_array($exporter->Synchronize())) { } // Output our WBXML reply now $encoder->StartWBXML(); $encoder->startTag(SYNC_FOLDERHIERARCHY_FOLDERSYNC); $encoder->startTag(SYNC_FOLDERHIERARCHY_STATUS); $encoder->content(1); $encoder->endTag(); $encoder->startTag(SYNC_FOLDERHIERARCHY_SYNCKEY); $encoder->content($changes || $importer->count > 0 ? $newsynckey : $synckey); $foldercache['hierarchy']['synckey'] = $changes || $importer->count > 0 ? $newsynckey : $synckey; $encoder->endTag(); $encoder->startTag(SYNC_FOLDERHIERARCHY_CHANGES); // remove unnecessary updates where details between cache and real folder are equal if (count($importer->changed) > 0) { foreach ($importer->changed as $key => $folder) { if (isset($folder->serverid) && in_array($folder->serverid, $seenfolders) && isset($foldercache['folders'][$folder->serverid]) && $foldercache['folders'][$folder->serverid]['parentid'] == $folder->parentid && $foldercache['folders'][$folder->serverid]['displayname'] == $folder->displayname && $foldercache['folders'][$folder->serverid]['type'] == $folder->type) { debugLog("Ignoring " . $folder->serverid . " from importer->changed because it is folder update requests!"); unset($importer->changed[$key]); $importer->count--; } } } // remove unnecessary deletes where folders never got sent to the device if (count($importer->deleted) > 0) { foreach ($importer->deleted as $key => $folder) { if (($sid = array_search($folder, $seenfolders)) === false) { debugLog("Removing {$folder} from importer->deleted because sid {$sid} (not in seenfolders)!"); unset($importer->deleted[$key]); $importer->count--; } } } $encoder->startTag(SYNC_FOLDERHIERARCHY_COUNT); $encoder->content($importer->count); $encoder->endTag(); if (count($importer->changed) > 0) { foreach ($importer->changed as $folder) { if (isset($folder->serverid) && in_array($folder->serverid, $seenfolders)) { $encoder->startTag(SYNC_FOLDERHIERARCHY_UPDATE); } else { $seenfolders[] = $folder->serverid; $encoder->startTag(SYNC_FOLDERHIERARCHY_ADD); } $statemachine->updateSyncCacheFolder($foldercache, $folder->serverid, $folder->parentid, $folder->displayname, $folder->type); $folder->encode($encoder); $encoder->endTag(); } } if (count($importer->deleted) > 0) { foreach ($importer->deleted as $folder) { if (($sid = array_search($folder, $seenfolders)) !== false) { $encoder->startTag(SYNC_FOLDERHIERARCHY_REMOVE); $encoder->startTag(SYNC_FOLDERHIERARCHY_SERVERENTRYID); $encoder->content($folder); $encoder->endTag(); $encoder->endTag(); // remove folder from the folderflags array unset($seenfolders[$sid]); $statemachine->deleteSyncCacheFolder($foldercache, $folder); $seenfolders = array_values($seenfolders); } else { debugLog("Don't send {$folder} because sid {$sid} (not in seenfolders)!"); } } } $encoder->endTag(); $encoder->endTag(); // Save the sync state for the next time $syncstate = $exporter->GetState(); $statemachine->setSyncState($newsynckey, $syncstate); $statemachine->setSyncState("s" . $newsynckey, serialize($seenfolders)); // Remove collections from foldercache for that no folder exists if (isset($foldercache['collections'])) { foreach ($foldercache['collections'] as $key => $value) { if (!isset($foldercache['folders'][$key])) { unset($foldercache['collections'][$key]); } } } $statemachine->setSyncCache(serialize($foldercache), true); return true; }
/** * Imports a list of folders which are to be deleted * * @param long $flags * @param mixed $sourcekeys array with sourcekeys * * @access public * @return */ function ImportFolderDeletion($flags, $sourcekeys) { foreach ($sourcekeys as $sourcekey) { $this->importer->ImportFolderDeletion(SyncFolder::GetObject(bin2hex($sourcekey))); } return 0; }
/** * Handles the FolderSync command * * @param int $commandCode * * @access public * @return boolean */ public function Handle($commandCode) { // Parse input if (!self::$decoder->getElementStartTag(SYNC_FOLDERHIERARCHY_FOLDERSYNC)) { return false; } if (!self::$decoder->getElementStartTag(SYNC_FOLDERHIERARCHY_SYNCKEY)) { return false; } $synckey = self::$decoder->getElementContent(); if (!self::$decoder->getElementEndTag()) { return false; } // every FolderSync with SyncKey 0 should return the supported AS version & command headers if ($synckey == "0") { self::$specialHeaders = array(); self::$specialHeaders[] = ZPush::GetSupportedProtocolVersions(); self::$specialHeaders[] = ZPush::GetSupportedCommands(); } $status = SYNC_FSSTATUS_SUCCESS; $newsynckey = $synckey; try { $syncstate = self::$deviceManager->GetStateManager()->GetSyncState($synckey); // We will be saving the sync state under 'newsynckey' $newsynckey = self::$deviceManager->GetStateManager()->GetNewSyncKey($synckey); } catch (StateNotFoundException $snfex) { $status = SYNC_FSSTATUS_SYNCKEYERROR; } catch (StateInvalidException $sive) { $status = SYNC_FSSTATUS_SYNCKEYERROR; } // The ChangesWrapper caches all imports in-memory, so we can send a change count // before sending the actual data. // the HierarchyCache is notified and the changes from the PIM are transmitted to the actual backend $changesMem = self::$deviceManager->GetHierarchyChangesWrapper(); // the hierarchyCache should now fully be initialized - check for changes in the additional folders $changesMem->Config(ZPush::GetAdditionalSyncFolders()); // process incoming changes if (self::$decoder->getElementStartTag(SYNC_FOLDERHIERARCHY_CHANGES)) { // Ignore <Count> if present if (self::$decoder->getElementStartTag(SYNC_FOLDERHIERARCHY_COUNT)) { self::$decoder->getElementContent(); if (!self::$decoder->getElementEndTag()) { return false; } } // Process the changes (either <Add>, <Modify>, or <Remove>) $element = self::$decoder->getElement(); if ($element[EN_TYPE] != EN_TYPE_STARTTAG) { return false; } $importer = false; while (1) { $folder = new SyncFolder(); if (!$folder->Decode(self::$decoder)) { break; } try { if ($status == SYNC_FSSTATUS_SUCCESS && !$importer) { // Configure the backends importer with last state $importer = self::$backend->GetImporter(); $importer->Config($syncstate); // the messages from the PIM will be forwarded to the backend $changesMem->forwardImporter($importer); } if ($status == SYNC_FSSTATUS_SUCCESS) { switch ($element[EN_TAG]) { case SYNC_ADD: case SYNC_MODIFY: $serverid = $changesMem->ImportFolderChange($folder); break; case SYNC_REMOVE: $serverid = $changesMem->ImportFolderDeletion($folder); break; } } else { ZLog::Write(LOGLEVEL_WARN, sprintf("Request->HandleFolderSync(): ignoring incoming folderchange for folder '%s' as status indicates problem.", $folder->displayname)); self::$topCollector->AnnounceInformation("Incoming change ignored", true); } } catch (StatusException $stex) { $status = $stex->getCode(); } } if (!self::$decoder->getElementEndTag()) { return false; } } else { // check for a potential process loop like described in Issue ZP-5 if ($synckey != "0" && self::$deviceManager->IsHierarchyFullResyncRequired()) { $status = SYNC_FSSTATUS_SYNCKEYERROR; } self::$deviceManager->AnnounceProcessStatus(false, $status); } if (!self::$decoder->getElementEndTag()) { return false; } // We have processed incoming foldersync requests, now send the PIM // our changes // Output our WBXML reply now self::$encoder->StartWBXML(); self::$encoder->startTag(SYNC_FOLDERHIERARCHY_FOLDERSYNC); if ($status == SYNC_FSSTATUS_SUCCESS) { try { // do nothing if this is an invalid device id (like the 'validate' Androids internal client sends) if (!Request::IsValidDeviceID()) { throw new StatusException(sprintf("Request::IsValidDeviceID() indicated that '%s' is not a valid device id", Request::GetDeviceID()), SYNC_FSSTATUS_SERVERERROR); } // Changes from backend are sent to the MemImporter and processed for the HierarchyCache. // The state which is saved is from the backend, as the MemImporter is only a proxy. $exporter = self::$backend->GetExporter(); $exporter->Config($syncstate); $exporter->InitializeExporter($changesMem); // Stream all changes to the ImportExportChangesMem $maxExporttime = Request::GetExpectedConnectionTimeout(); $totalChanges = $exporter->GetChangeCount(); $started = time(); $exported = 0; $partial = false; while (is_array($exporter->Synchronize())) { $exported++; if (time() % 4) { self::$topCollector->AnnounceInformation(sprintf("Exported %d from %d folders", $exported, $totalChanges)); } // if partial sync is allowed, stop if this takes too long if (USE_PARTIAL_FOLDERSYNC && time() - $started > $maxExporttime) { ZLog::Write(LOGLEVEL_WARN, sprintf("Request->HandleFolderSync(): Exporting folders is too slow. In %d seconds only %d from %d changes were processed.", time() - $started, $exported, $totalChanges)); self::$topCollector->AnnounceInformation(sprintf("Partial export of %d out of %d folders", $exported, $totalChanges), true); self::$deviceManager->SetFolderSyncComplete(false); $partial = true; break; } } // update the foldersync complete flag if (USE_PARTIAL_FOLDERSYNC && $partial == false && self::$deviceManager->GetFolderSyncComplete() === false) { // say that we are done with partial synching self::$deviceManager->SetFolderSyncComplete(true); // reset the loop data to prevent any loop detection to kick in now self::$deviceManager->ClearLoopDetectionData(Request::GetAuthUser(), Request::GetDeviceID()); ZLog::Write(LOGLEVEL_INFO, "Request->HandleFolderSync(): Chunked exporting of folders completed successfully"); } // get the new state from the backend $newsyncstate = isset($exporter) ? $exporter->GetState() : ""; } catch (StatusException $stex) { if ($stex->getCode() == SYNC_FSSTATUS_CODEUNKNOWN) { $status = SYNC_FSSTATUS_SYNCKEYERROR; } else { $status = $stex->getCode(); } } } self::$encoder->startTag(SYNC_FOLDERHIERARCHY_STATUS); self::$encoder->content($status); self::$encoder->endTag(); if ($status == SYNC_FSSTATUS_SUCCESS) { self::$encoder->startTag(SYNC_FOLDERHIERARCHY_SYNCKEY); $synckey = $changesMem->IsStateChanged() ? $newsynckey : $synckey; self::$encoder->content($synckey); self::$encoder->endTag(); // Stream folders directly to the PDA $streamimporter = new ImportChangesStream(self::$encoder, false); $changesMem->InitializeExporter($streamimporter); $changeCount = $changesMem->GetChangeCount(); self::$encoder->startTag(SYNC_FOLDERHIERARCHY_CHANGES); self::$encoder->startTag(SYNC_FOLDERHIERARCHY_COUNT); self::$encoder->content($changeCount); self::$encoder->endTag(); while ($changesMem->Synchronize()) { } self::$encoder->endTag(); self::$topCollector->AnnounceInformation(sprintf("Outgoing %d folders", $changeCount), true); // everything fine, save the sync state for the next time if ($synckey == $newsynckey) { self::$deviceManager->GetStateManager()->SetSyncState($newsynckey, $newsyncstate); } } self::$encoder->endTag(); return true; }
/** * Synchronizes a change * * @access public * @return array */ public function Synchronize() { $progress = array(); // Get one of our stored changes and send it to the importer, store the new state if // it succeeds if ($this->folderid == false) { if ($this->step < count($this->changes)) { $change = $this->changes[$this->step]; switch ($change["type"]) { case "change": $folder = $this->backend->GetFolder($change["id"]); $stat = $this->backend->StatFolder($change["id"]); if (!$folder) { return; } if ($this->flags & BACKEND_DISCARD_DATA || $this->importer->ImportFolderChange($folder)) { $this->updateState("change", $stat); } break; case "delete": if ($this->flags & BACKEND_DISCARD_DATA || $this->importer->ImportFolderDeletion(SyncFolder::GetObject($change["id"]))) { $this->updateState("delete", $change); } break; } $this->step++; $progress = array(); $progress["steps"] = count($this->changes); $progress["progress"] = $this->step; return $progress; } else { return false; } } else { if ($this->step < count($this->changes)) { $change = $this->changes[$this->step]; switch ($change["type"]) { case "change": // Note: because 'parseMessage' and 'statMessage' are two seperate // calls, we have a chance that the message has changed between both // calls. This may cause our algorithm to 'double see' changes. $stat = $this->backend->StatMessage($this->folderid, $change["id"]); $message = $this->backend->GetMessage($this->folderid, $change["id"], $this->contentparameters); // copy the flag to the message $message->flags = isset($change["flags"]) ? $change["flags"] : 0; if ($stat && $message) { if ($this->flags & BACKEND_DISCARD_DATA || $this->importer->ImportMessageChange($change["id"], $message) == true) { $this->updateState("change", $stat); } } break; case "delete": if ($this->flags & BACKEND_DISCARD_DATA || $this->importer->ImportMessageDeletion($change["id"]) == true) { $this->updateState("delete", $change); } break; case "flags": if ($this->flags & BACKEND_DISCARD_DATA || $this->importer->ImportMessageReadFlag($change["id"], $change["flags"]) == true) { $this->updateState("flags", $change); } break; case "move": if ($this->flags & BACKEND_DISCARD_DATA || $this->importer->ImportMessageMove($change["id"], $change["parent"]) == true) { $this->updateState("move", $change); } break; } $this->step++; $progress = array(); $progress["steps"] = count($this->changes); $progress["progress"] = $this->step; return $progress; } else { return false; } } }