. * * Consult LICENSE file for details ************************************************/ class FolderSync extends RequestProcessor { /** * 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; // 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 if present if(self::$decoder->getElementStartTag(SYNC_FOLDERHIERARCHY_COUNT)) { self::$decoder->getElementContent(); if(!self::$decoder->getElementEndTag()) return false; } // Process the changes (either , , or ) $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; } // no incoming changes 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; } } ?>