Exemple #1
0
 /**
  * Handles creates, updates or deletes of a folder
  * issued by the commands FolderCreate, FolderUpdate and FolderDelete
  *
  * @param int       $commandCode
  *
  * @access public
  * @return boolean
  */
 public function Handle($commandCode)
 {
     $el = self::$decoder->getElement();
     if ($el[EN_TYPE] != EN_TYPE_STARTTAG) {
         return false;
     }
     $create = $update = $delete = false;
     if ($el[EN_TAG] == SYNC_FOLDERHIERARCHY_FOLDERCREATE) {
         $create = true;
     } else {
         if ($el[EN_TAG] == SYNC_FOLDERHIERARCHY_FOLDERUPDATE) {
             $update = true;
         } else {
             if ($el[EN_TAG] == SYNC_FOLDERHIERARCHY_FOLDERDELETE) {
                 $delete = true;
             }
         }
     }
     if (!$create && !$update && !$delete) {
         return false;
     }
     // SyncKey
     if (!self::$decoder->getElementStartTag(SYNC_FOLDERHIERARCHY_SYNCKEY)) {
         return false;
     }
     $synckey = self::$decoder->getElementContent();
     if (!self::$decoder->getElementEndTag()) {
         return false;
     }
     // ServerID
     $serverid = false;
     $backendid = false;
     if (self::$decoder->getElementStartTag(SYNC_FOLDERHIERARCHY_SERVERENTRYID)) {
         $serverid = self::$decoder->getElementContent();
         $backendid = self::$deviceManager->GetBackendIdForFolderId($serverid);
         if (!self::$decoder->getElementEndTag()) {
             return false;
         }
     }
     // Parent
     $parentid = false;
     $parentBackendId = false;
     // when creating or updating more information is necessary
     if (!$delete) {
         if (self::$decoder->getElementStartTag(SYNC_FOLDERHIERARCHY_PARENTID)) {
             $parentid = self::$decoder->getElementContent();
             $parentBackendId = self::$deviceManager->GetBackendIdForFolderId($parentid);
             if (!self::$decoder->getElementEndTag()) {
                 return false;
             }
         }
         // Displayname
         if (!self::$decoder->getElementStartTag(SYNC_FOLDERHIERARCHY_DISPLAYNAME)) {
             return false;
         }
         $displayname = self::$decoder->getElementContent();
         if (!self::$decoder->getElementEndTag()) {
             return false;
         }
         // Type
         $type = false;
         if (self::$decoder->getElementStartTag(SYNC_FOLDERHIERARCHY_TYPE)) {
             $type = self::$decoder->getElementContent();
             if (!self::$decoder->getElementEndTag()) {
                 return false;
             }
         }
     }
     // endtag foldercreate, folderupdate, folderdelete
     if (!self::$decoder->getElementEndTag()) {
         return false;
     }
     $status = SYNC_FSSTATUS_SUCCESS;
     // Get state of hierarchy
     try {
         $syncstate = self::$deviceManager->GetStateManager()->GetSyncState($synckey);
         $newsynckey = self::$deviceManager->GetStateManager()->GetNewSyncKey($synckey);
         // there are no SyncParameters for the hierarchy, but we use it to save the latest synckeys
         $spa = self::$deviceManager->GetStateManager()->GetSynchedFolderState(false);
         // Over the ChangesWrapper the HierarchyCache is notified about all changes
         $changesMem = self::$deviceManager->GetHierarchyChangesWrapper();
         // the hierarchyCache should now fully be initialized - check for changes in the additional folders
         $changesMem->Config(ZPush::GetAdditionalSyncFolders(false));
         // reset to default store in backend
         self::$backend->Setup(false);
         // there are unprocessed changes in the hierarchy, trigger resync
         if ($changesMem->GetChangeCount() > 0) {
             throw new StatusException("HandleFolderChange() can not proceed as there are unprocessed hierarchy changes", SYNC_FSSTATUS_SERVERERROR);
         }
         // any additional folders can not be modified!
         if ($serverid !== false && ZPush::GetAdditionalSyncFolderStore($backendid)) {
             throw new StatusException("HandleFolderChange() can not change additional folders which are configured", SYNC_FSSTATUS_SYSTEMFOLDER);
         }
         // switch user store if this this happens inside an additional folder
         // if this is an additional folder the backend has to be setup correctly
         if (!self::$backend->Setup(ZPush::GetAdditionalSyncFolderStore($parentBackendId != false ? $parentBackendId : $backendid))) {
             throw new StatusException(sprintf("HandleFolderChange() could not Setup() the backend for folder id '%s'", $parentBackendId != false ? $parentBackendId : $backendid), SYNC_FSSTATUS_SERVERERROR);
         }
     } catch (StateNotFoundException $snfex) {
         $status = SYNC_FSSTATUS_SYNCKEYERROR;
     } catch (StatusException $stex) {
         $status = $stex->getCode();
     }
     // set $newsynckey in case of an error
     if (!isset($newsynckey)) {
         $newsynckey = $synckey;
     }
     if ($status == SYNC_FSSTATUS_SUCCESS) {
         try {
             // Configure importer with last state
             $importer = self::$backend->GetImporter();
             $importer->Config($syncstate);
             // the messages from the PIM will be forwarded to the real importer
             $changesMem->SetDestinationImporter($importer);
             // Create SyncFolder object
             $folder = new SyncFolder();
             $folder->serverid = $serverid;
             $folder->parentid = $parentBackendId;
             if (isset($displayname)) {
                 $folder->displayname = $displayname;
             }
             if (isset($type)) {
                 $folder->type = $type;
             }
             // add the backendId to the SyncFolder object
             $folder->BackendId = $backendid;
             // process incoming change
             if (!$delete) {
                 // when creating, $folder->serverid is false, and the returned id is already mapped by the backend
                 $folder = $changesMem->ImportFolderChange($folder);
             } else {
                 // delete folder
                 $changesMem->ImportFolderDeletion($folder);
             }
         } catch (StatusException $stex) {
             $status = $stex->getCode();
         }
     }
     self::$encoder->startWBXML();
     if ($create) {
         self::$encoder->startTag(SYNC_FOLDERHIERARCHY_FOLDERCREATE);
         self::$encoder->startTag(SYNC_FOLDERHIERARCHY_STATUS);
         self::$encoder->content($status);
         self::$encoder->endTag();
         self::$encoder->startTag(SYNC_FOLDERHIERARCHY_SYNCKEY);
         self::$encoder->content($newsynckey);
         self::$encoder->endTag();
         self::$encoder->startTag(SYNC_FOLDERHIERARCHY_SERVERENTRYID);
         self::$encoder->content($folder->serverid);
         self::$encoder->endTag();
         self::$encoder->endTag();
     } elseif ($update) {
         self::$encoder->startTag(SYNC_FOLDERHIERARCHY_FOLDERUPDATE);
         self::$encoder->startTag(SYNC_FOLDERHIERARCHY_STATUS);
         self::$encoder->content($status);
         self::$encoder->endTag();
         self::$encoder->startTag(SYNC_FOLDERHIERARCHY_SYNCKEY);
         self::$encoder->content($newsynckey);
         self::$encoder->endTag();
         self::$encoder->endTag();
     } elseif ($delete) {
         self::$encoder->startTag(SYNC_FOLDERHIERARCHY_FOLDERDELETE);
         self::$encoder->startTag(SYNC_FOLDERHIERARCHY_STATUS);
         self::$encoder->content($status);
         self::$encoder->endTag();
         self::$encoder->startTag(SYNC_FOLDERHIERARCHY_SYNCKEY);
         self::$encoder->content($newsynckey);
         self::$encoder->endTag();
         self::$encoder->endTag();
     }
     self::$topCollector->AnnounceInformation(sprintf("Operation status %d", $status), true);
     // Save the sync state for the next time
     if (isset($importer)) {
         self::$deviceManager->GetStateManager()->SetSyncState($newsynckey, $importer->GetState());
         // update SPA & save it
         $spa->SetSyncKey($newsynckey);
         $spa->SetFolderId(false);
         self::$deviceManager->GetStateManager()->SetSynchedFolderState($spa);
         // invalidate all pingable flags
         SyncCollections::InvalidatePingableFlags();
     }
     return true;
 }
Exemple #2
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);
         // there are no SyncParameters for the hierarchy, but we use it to save the latest synckeys
         $spa = self::$deviceManager->GetStateManager()->GetSynchedFolderState(false);
     } 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(false));
     // reset to default store in backend
     self::$backend->Setup(false);
     // 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;
         WBXMLDecoder::ResetInWhile("folderSyncIncomingChange");
         while (WBXMLDecoder::InWhile("folderSyncIncomingChange")) {
             $folder = new SyncFolder();
             if (!$folder->Decode(self::$decoder)) {
                 break;
             }
             // add the backendId to the SyncFolder object
             $folder->BackendId = self::$deviceManager->GetBackendIdForFolderId($folder->serverid);
             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
             $totalChanges = $exporter->GetChangeCount();
             $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 && Request::IsRequestTimeoutReached()) {
                     ZLog::Write(LOGLEVEL_WARN, sprintf("Request->HandleFolderSync(): Exporting folders is too slow. In %d seconds only %d from %d changes were processed.", time() - $_SERVER["REQUEST_TIME"], $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);
             // update SPA & save it
             $spa->SetSyncKey($newsynckey);
             $spa->SetFolderId(false);
             self::$deviceManager->GetStateManager()->SetSynchedFolderState($spa);
             // invalidate all pingable flags
             SyncCollections::InvalidatePingableFlags();
         }
     }
     self::$encoder->endTag();
     return true;
 }