示例#1
0
 /**
  * Invalidates all pingable flags for all folders.
  *
  * @access public
  * @return boolean
  */
 public static function InvalidatePingableFlags()
 {
     ZLog::Write(LOGLEVEL_DEBUG, "SyncCollections::InvalidatePingableFlags(): Invalidating now");
     try {
         $sc = new SyncCollections();
         $sc->LoadAllCollections();
         foreach ($sc as $folderid => $spa) {
             if ($spa->GetPingableFlag() == true) {
                 $spa->DelPingableFlag();
                 $sc->SaveCollection($spa);
             }
         }
         return true;
     } catch (ZPushException $e) {
     }
     return false;
 }
示例#2
0
 /**
  * Handles the Ping command
  *
  * @param int       $commandCode
  *
  * @access public
  * @return boolean
  */
 public function Handle($commandCode)
 {
     $interval = defined('PING_INTERVAL') && PING_INTERVAL > 0 ? PING_INTERVAL : 30;
     $pingstatus = false;
     $fakechanges = array();
     $foundchanges = false;
     // Contains all requested folders (containers)
     $sc = new SyncCollections();
     // Load all collections - do load states and check permissions
     try {
         $sc->LoadAllCollections(true, true, true);
     } catch (StateNotFoundException $snfex) {
         $pingstatus = SYNC_PINGSTATUS_FOLDERHIERSYNCREQUIRED;
         self::$topCollector->AnnounceInformation("StateNotFoundException: require HierarchySync", true);
     } catch (StateInvalidException $snfex) {
         // we do not have a ping status for this, but SyncCollections should have generated fake changes for the folders which are broken
         $fakechanges = $sc->GetChangedFolderIds();
         $foundchanges = true;
         self::$topCollector->AnnounceInformation("StateInvalidException: force sync", true);
     } catch (StatusException $stex) {
         $pingstatus = SYNC_PINGSTATUS_FOLDERHIERSYNCREQUIRED;
         self::$topCollector->AnnounceInformation("StatusException: require HierarchySync", true);
     }
     ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandlePing(): reference PolicyKey for PING: %s", $sc->GetReferencePolicyKey()));
     // receive PING initialization data
     if (self::$decoder->getElementStartTag(SYNC_PING_PING)) {
         self::$topCollector->AnnounceInformation("Processing PING data");
         ZLog::Write(LOGLEVEL_DEBUG, "HandlePing(): initialization data received");
         if (self::$decoder->getElementStartTag(SYNC_PING_LIFETIME)) {
             $sc->SetLifetime(self::$decoder->getElementContent());
             self::$decoder->getElementEndTag();
         }
         if (($el = self::$decoder->getElementStartTag(SYNC_PING_FOLDERS)) && $el[EN_FLAGS] & EN_FLAGS_CONTENT) {
             // remove PingableFlag from all collections
             foreach ($sc as $folderid => $spa) {
                 $spa->DelPingableFlag();
             }
             while (self::$decoder->getElementStartTag(SYNC_PING_FOLDER)) {
                 while (1) {
                     if (self::$decoder->getElementStartTag(SYNC_PING_SERVERENTRYID)) {
                         $folderid = self::$decoder->getElementContent();
                         self::$decoder->getElementEndTag();
                     }
                     if (self::$decoder->getElementStartTag(SYNC_PING_FOLDERTYPE)) {
                         $class = self::$decoder->getElementContent();
                         self::$decoder->getElementEndTag();
                     }
                     $e = self::$decoder->peek();
                     if ($e[EN_TYPE] == EN_TYPE_ENDTAG) {
                         self::$decoder->getElementEndTag();
                         break;
                     }
                 }
                 $spa = $sc->GetCollection($folderid);
                 if (!$spa) {
                     // The requested collection is not synchronized.
                     // check if the HierarchyCache is available, if not, trigger a HierarchySync
                     try {
                         self::$deviceManager->GetFolderClassFromCacheByID($folderid);
                     } catch (NoHierarchyCacheAvailableException $nhca) {
                         ZLog::Write(LOGLEVEL_INFO, sprintf("HandlePing(): unknown collection '%s', triggering HierarchySync", $folderid));
                         $pingstatus = SYNC_PINGSTATUS_FOLDERHIERSYNCREQUIRED;
                     }
                     // Trigger a Sync request because then the device will be forced to resync this folder.
                     $fakechanges[$folderid] = 1;
                     $foundchanges = true;
                 } else {
                     if ($class == $spa->GetContentClass()) {
                         $spa->SetPingableFlag(true);
                         ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandlePing(): using saved sync state for '%s' id '%s'", $spa->GetContentClass(), $folderid));
                     }
                 }
             }
             if (!self::$decoder->getElementEndTag()) {
                 return false;
             }
         }
         if (!self::$decoder->getElementEndTag()) {
             return false;
         }
         // save changed data
         foreach ($sc as $folderid => $spa) {
             $sc->SaveCollection($spa);
         }
     } else {
         // if no ping initialization data was sent, we check if we have pingable folders
         // if not, we indicate that there is nothing to do.
         if (!$sc->PingableFolders()) {
             $pingstatus = SYNC_PINGSTATUS_FAILINGPARAMS;
             ZLog::Write(LOGLEVEL_DEBUG, "HandlePing(): no pingable folders found and no initialization data sent. Returning SYNC_PINGSTATUS_FAILINGPARAMS.");
         }
     }
     // Check for changes on the default LifeTime, set interval and ONLY on pingable collections
     try {
         if (!$pingstatus && empty($fakechanges)) {
             $foundchanges = $sc->CheckForChanges($sc->GetLifetime(), $interval, true);
         }
     } catch (StatusException $ste) {
         switch ($ste->getCode()) {
             case SyncCollections::ERROR_NO_COLLECTIONS:
                 $pingstatus = SYNC_PINGSTATUS_FAILINGPARAMS;
                 break;
             case SyncCollections::ERROR_WRONG_HIERARCHY:
                 $pingstatus = SYNC_PINGSTATUS_FOLDERHIERSYNCREQUIRED;
                 self::$deviceManager->AnnounceProcessStatus(false, $pingstatus);
                 break;
             case SyncCollections::OBSOLETE_CONNECTION:
                 $foundchanges = false;
                 break;
         }
     }
     self::$encoder->StartWBXML();
     self::$encoder->startTag(SYNC_PING_PING);
     self::$encoder->startTag(SYNC_PING_STATUS);
     if (isset($pingstatus) && $pingstatus) {
         self::$encoder->content($pingstatus);
     } else {
         self::$encoder->content($foundchanges ? SYNC_PINGSTATUS_CHANGES : SYNC_PINGSTATUS_HBEXPIRED);
     }
     self::$encoder->endTag();
     if (!$pingstatus) {
         self::$encoder->startTag(SYNC_PING_FOLDERS);
         if (empty($fakechanges)) {
             $changes = $sc->GetChangedFolderIds();
         } else {
             $changes = $fakechanges;
         }
         foreach ($changes as $folderid => $changecount) {
             if ($changecount > 0) {
                 self::$encoder->startTag(SYNC_PING_FOLDER);
                 self::$encoder->content($folderid);
                 self::$encoder->endTag();
                 if (empty($fakechanges)) {
                     self::$topCollector->AnnounceInformation(sprintf("Found change in %s", $sc->GetCollection($folderid)->GetContentClass()), true);
                 }
             }
         }
         self::$encoder->endTag();
     }
     self::$encoder->endTag();
     return true;
 }
示例#3
0
 /**
  * Handles the Sync command
  * Performs the synchronization of messages
  *
  * @param int       $commandCode
  *
  * @access public
  * @return boolean
  */
 public function Handle($commandCode)
 {
     // Contains all requested folders (containers)
     $sc = new SyncCollections();
     $status = SYNC_STATUS_SUCCESS;
     $wbxmlproblem = false;
     $emptysync = false;
     // check if the hierarchySync was fully completed
     if (USE_PARTIAL_FOLDERSYNC) {
         if (self::$deviceManager->GetFolderSyncComplete() === false) {
             ZLog::Write(LOGLEVEL_INFO, "Request->HandleSync(): Sync request aborted, as exporting of folders has not yet completed");
             self::$topCollector->AnnounceInformation("Aborted due incomplete folder sync", true);
             $status = SYNC_STATUS_FOLDERHIERARCHYCHANGED;
         } else {
             ZLog::Write(LOGLEVEL_INFO, "Request->HandleSync(): FolderSync marked as complete");
         }
     }
     // Start Synchronize
     if (self::$decoder->getElementStartTag(SYNC_SYNCHRONIZE)) {
         // AS 1.0 sends version information in WBXML
         if (self::$decoder->getElementStartTag(SYNC_VERSION)) {
             $sync_version = self::$decoder->getElementContent();
             ZLog::Write(LOGLEVEL_DEBUG, sprintf("WBXML sync version: '%s'", $sync_version));
             if (!self::$decoder->getElementEndTag()) {
                 return false;
             }
         }
         // Synching specified folders
         // Android still sends heartbeat sync even if all syncfolders are disabled.
         // Check if Folders tag is empty (<Folders/>) and only sync if there are
         // some folders in the request. See ZP-172
         $startTag = self::$decoder->getElementStartTag(SYNC_FOLDERS);
         if (isset($startTag[EN_FLAGS]) && $startTag[EN_FLAGS]) {
             while (self::$decoder->getElementStartTag(SYNC_FOLDER)) {
                 $actiondata = array();
                 $actiondata["requested"] = true;
                 $actiondata["clientids"] = array();
                 $actiondata["modifyids"] = array();
                 $actiondata["removeids"] = array();
                 $actiondata["fetchids"] = array();
                 $actiondata["statusids"] = array();
                 // read class, synckey and folderid without SyncParameters Object for now
                 $class = $synckey = $folderid = false;
                 //for AS versions < 2.5
                 if (self::$decoder->getElementStartTag(SYNC_FOLDERTYPE)) {
                     $class = self::$decoder->getElementContent();
                     ZLog::Write(LOGLEVEL_DEBUG, sprintf("Sync folder: '%s'", $class));
                     if (!self::$decoder->getElementEndTag()) {
                         return false;
                     }
                 }
                 // SyncKey
                 if (self::$decoder->getElementStartTag(SYNC_SYNCKEY)) {
                     $synckey = "0";
                     if (($synckey = self::$decoder->getElementContent()) !== false) {
                         if (!self::$decoder->getElementEndTag()) {
                             return false;
                         }
                     }
                 } else {
                     return false;
                 }
                 // FolderId
                 if (self::$decoder->getElementStartTag(SYNC_FOLDERID)) {
                     $folderid = self::$decoder->getElementContent();
                     if (!self::$decoder->getElementEndTag()) {
                         return false;
                     }
                 }
                 // compatibility mode AS 1.0 - get folderid which was sent during GetHierarchy()
                 if (!$folderid && $class) {
                     $folderid = self::$deviceManager->GetFolderIdFromCacheByClass($class);
                 }
                 // folderid HAS TO BE known by now, so we retrieve the correct SyncParameters object for an update
                 try {
                     $spa = self::$deviceManager->GetStateManager()->GetSynchedFolderState($folderid);
                     // TODO remove resync of folders for < Z-Push 2 beta4 users
                     // this forces a resync of all states previous to Z-Push 2 beta4
                     if (!$spa instanceof SyncParameters) {
                         throw new StateInvalidException("Saved state are not of type SyncParameters");
                     }
                     // new/resync requested
                     if ($synckey == "0") {
                         $spa->RemoveSyncKey();
                     } else {
                         if ($synckey !== false) {
                             $spa->SetSyncKey($synckey);
                         }
                     }
                 } catch (StateInvalidException $stie) {
                     $spa = new SyncParameters();
                     $status = SYNC_STATUS_INVALIDSYNCKEY;
                     self::$topCollector->AnnounceInformation("State invalid - Resync folder", true);
                     self::$deviceManager->ForceFolderResync($folderid);
                 }
                 // update folderid.. this might be a new object
                 $spa->SetFolderId($folderid);
                 if ($class !== false) {
                     $spa->SetContentClass($class);
                 }
                 // Get class for as versions >= 12.0
                 if (!$spa->HasContentClass()) {
                     try {
                         $spa->SetContentClass(self::$deviceManager->GetFolderClassFromCacheByID($spa->GetFolderId()));
                         ZLog::Write(LOGLEVEL_DEBUG, sprintf("GetFolderClassFromCacheByID from Device Manager: '%s' for id:'%s'", $spa->GetContentClass(), $spa->GetFolderId()));
                     } catch (NoHierarchyCacheAvailableException $nhca) {
                         $status = SYNC_STATUS_FOLDERHIERARCHYCHANGED;
                         self::$deviceManager->ForceFullResync();
                     }
                 }
                 // done basic SPA initialization/loading -> add to SyncCollection
                 $sc->AddCollection($spa);
                 $sc->AddParameter($spa, "requested", true);
                 if ($spa->HasContentClass()) {
                     self::$topCollector->AnnounceInformation(sprintf("%s request", $spa->GetContentClass()), true);
                 } else {
                     ZLog::Write(LOGLEVEL_WARN, "Not possible to determine class of request. Request did not contain class and apparently there is an issue with the HierarchyCache.");
                 }
                 // SUPPORTED properties
                 if (($se = self::$decoder->getElementStartTag(SYNC_SUPPORTED)) !== false) {
                     // ZP-481: LG phones send an empty supported tag, so only read the contents if available here
                     // if <Supported/> is received, it's as no supported fields would have been sent at all.
                     // unsure if this is the correct approach, or if in this case some default list should be used
                     if ($se[EN_FLAGS] & EN_FLAGS_CONTENT) {
                         $supfields = array();
                         while (1) {
                             $el = self::$decoder->getElement();
                             if ($el[EN_TYPE] == EN_TYPE_ENDTAG) {
                                 break;
                             } else {
                                 $supfields[] = $el[EN_TAG];
                             }
                         }
                         self::$deviceManager->SetSupportedFields($spa->GetFolderId(), $supfields);
                     }
                 }
                 // Deletes as moves can be an empty tag as well as have value
                 if (self::$decoder->getElementStartTag(SYNC_DELETESASMOVES)) {
                     $spa->SetDeletesAsMoves(true);
                     if (($dam = self::$decoder->getElementContent()) !== false) {
                         $spa->SetDeletesAsMoves((bool) $dam);
                         if (!self::$decoder->getElementEndTag()) {
                             return false;
                         }
                     }
                 }
                 // Get changes can be an empty tag as well as have value
                 // code block partly contributed by dw2412
                 if (self::$decoder->getElementStartTag(SYNC_GETCHANGES)) {
                     $sc->AddParameter($spa, "getchanges", true);
                     if (($gc = self::$decoder->getElementContent()) !== false) {
                         $sc->AddParameter($spa, "getchanges", $gc);
                         if (!self::$decoder->getElementEndTag()) {
                             return false;
                         }
                     }
                 }
                 if (self::$decoder->getElementStartTag(SYNC_WINDOWSIZE)) {
                     $ws = self::$decoder->getElementContent();
                     // normalize windowsize - see ZP-477
                     if ($ws == 0 || $ws > 512) {
                         $ws = 512;
                     }
                     $spa->SetWindowSize($ws);
                     // also announce the currently requested window size to the DeviceManager
                     self::$deviceManager->SetWindowSize($spa->GetFolderId(), $spa->GetWindowSize());
                     if (!self::$decoder->getElementEndTag()) {
                         return false;
                     }
                 }
                 // conversation mode requested
                 if (self::$decoder->getElementStartTag(SYNC_CONVERSATIONMODE)) {
                     $spa->SetConversationMode(true);
                     if (($conversationmode = self::$decoder->getElementContent()) !== false) {
                         $spa->SetConversationMode((bool) $conversationmode);
                         if (!self::$decoder->getElementEndTag()) {
                             return false;
                         }
                     }
                 }
                 // Do not truncate by default
                 $spa->SetTruncation(SYNC_TRUNCATION_ALL);
                 // use default conflict handling if not specified by the mobile
                 $spa->SetConflict(SYNC_CONFLICT_DEFAULT);
                 while (self::$decoder->getElementStartTag(SYNC_OPTIONS)) {
                     $firstOption = true;
                     while (1) {
                         // foldertype definition
                         if (self::$decoder->getElementStartTag(SYNC_FOLDERTYPE)) {
                             $foldertype = self::$decoder->getElementContent();
                             ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): specified options block with foldertype '%s'", $foldertype));
                             // switch the foldertype for the next options
                             $spa->UseCPO($foldertype);
                             // set to synchronize all changes. The mobile could overwrite this value
                             $spa->SetFilterType(SYNC_FILTERTYPE_ALL);
                             if (!self::$decoder->getElementEndTag()) {
                                 return false;
                             }
                         } else {
                             if ($firstOption) {
                                 $spa->UseCPO();
                                 // set to synchronize all changes. The mobile could overwrite this value
                                 $spa->SetFilterType(SYNC_FILTERTYPE_ALL);
                             }
                         }
                         $firstOption = false;
                         if (self::$decoder->getElementStartTag(SYNC_FILTERTYPE)) {
                             $spa->SetFilterType(self::$decoder->getElementContent());
                             if (!self::$decoder->getElementEndTag()) {
                                 return false;
                             }
                         }
                         if (self::$decoder->getElementStartTag(SYNC_TRUNCATION)) {
                             $spa->SetTruncation(self::$decoder->getElementContent());
                             if (!self::$decoder->getElementEndTag()) {
                                 return false;
                             }
                         }
                         if (self::$decoder->getElementStartTag(SYNC_RTFTRUNCATION)) {
                             $spa->SetRTFTruncation(self::$decoder->getElementContent());
                             if (!self::$decoder->getElementEndTag()) {
                                 return false;
                             }
                         }
                         if (self::$decoder->getElementStartTag(SYNC_MIMESUPPORT)) {
                             $spa->SetMimeSupport(self::$decoder->getElementContent());
                             if (!self::$decoder->getElementEndTag()) {
                                 return false;
                             }
                         }
                         if (self::$decoder->getElementStartTag(SYNC_MIMETRUNCATION)) {
                             $spa->SetMimeTruncation(self::$decoder->getElementContent());
                             if (!self::$decoder->getElementEndTag()) {
                                 return false;
                             }
                         }
                         if (self::$decoder->getElementStartTag(SYNC_CONFLICT)) {
                             $spa->SetConflict(self::$decoder->getElementContent());
                             if (!self::$decoder->getElementEndTag()) {
                                 return false;
                             }
                         }
                         while (self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_BODYPREFERENCE)) {
                             if (self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_TYPE)) {
                                 $bptype = self::$decoder->getElementContent();
                                 $spa->BodyPreference($bptype);
                                 if (!self::$decoder->getElementEndTag()) {
                                     return false;
                                 }
                             }
                             if (self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_TRUNCATIONSIZE)) {
                                 $spa->BodyPreference($bptype)->SetTruncationSize(self::$decoder->getElementContent());
                                 if (!self::$decoder->getElementEndTag()) {
                                     return false;
                                 }
                             }
                             if (self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_ALLORNONE)) {
                                 $spa->BodyPreference($bptype)->SetAllOrNone(self::$decoder->getElementContent());
                                 if (!self::$decoder->getElementEndTag()) {
                                     return false;
                                 }
                             }
                             if (self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_PREVIEW)) {
                                 $spa->BodyPreference($bptype)->SetPreview(self::$decoder->getElementContent());
                                 if (!self::$decoder->getElementEndTag()) {
                                     return false;
                                 }
                             }
                             if (!self::$decoder->getElementEndTag()) {
                                 return false;
                             }
                         }
                         $e = self::$decoder->peek();
                         if ($e[EN_TYPE] == EN_TYPE_ENDTAG) {
                             self::$decoder->getElementEndTag();
                             break;
                         }
                     }
                 }
                 // limit items to be synchronized to the mobiles if configured
                 if (defined('SYNC_FILTERTIME_MAX') && SYNC_FILTERTIME_MAX > SYNC_FILTERTYPE_ALL && (!$spa->HasFilterType() || $spa->GetFilterType() == SYNC_FILTERTYPE_ALL || $spa->GetFilterType() > SYNC_FILTERTIME_MAX)) {
                     ZLog::Write(LOGLEVEL_DEBUG, sprintf("SYNC_FILTERTIME_MAX defined. Filter set to value: %s", SYNC_FILTERTIME_MAX));
                     $spa->SetFilterType(SYNC_FILTERTIME_MAX);
                 }
                 // Check if the hierarchycache is available. If not, trigger a HierarchySync
                 if (self::$deviceManager->IsHierarchySyncRequired()) {
                     $status = SYNC_STATUS_FOLDERHIERARCHYCHANGED;
                     ZLog::Write(LOGLEVEL_DEBUG, "HierarchyCache is also not available. Triggering HierarchySync to device");
                 }
                 if (($el = self::$decoder->getElementStartTag(SYNC_PERFORM)) && $el[EN_FLAGS] & EN_FLAGS_CONTENT) {
                     // We can not proceed here as the content class is unknown
                     if ($status != SYNC_STATUS_SUCCESS) {
                         ZLog::Write(LOGLEVEL_WARN, "Ignoring all incoming actions as global status indicates problem.");
                         $wbxmlproblem = true;
                         break;
                     }
                     $performaction = true;
                     // unset the importer
                     $this->importer = false;
                     $nchanges = 0;
                     while (1) {
                         // ADD, MODIFY, REMOVE or FETCH
                         $element = self::$decoder->getElement();
                         if ($element[EN_TYPE] != EN_TYPE_STARTTAG) {
                             self::$decoder->ungetElement($element);
                             break;
                         }
                         if ($status == SYNC_STATUS_SUCCESS) {
                             $nchanges++;
                         }
                         // Foldertype sent when synching SMS
                         if (self::$decoder->getElementStartTag(SYNC_FOLDERTYPE)) {
                             $foldertype = self::$decoder->getElementContent();
                             ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): incoming data with foldertype '%s'", $foldertype));
                             if (!self::$decoder->getElementEndTag()) {
                                 return false;
                             }
                         } else {
                             $foldertype = false;
                         }
                         $serverid = false;
                         if (self::$decoder->getElementStartTag(SYNC_SERVERENTRYID)) {
                             if (($serverid = self::$decoder->getElementContent()) !== false) {
                                 if (!self::$decoder->getElementEndTag()) {
                                     // end serverid
                                     return false;
                                 }
                             }
                         }
                         if (self::$decoder->getElementStartTag(SYNC_CLIENTENTRYID)) {
                             $clientid = self::$decoder->getElementContent();
                             if (!self::$decoder->getElementEndTag()) {
                                 // end clientid
                                 return false;
                             }
                         } else {
                             $clientid = false;
                         }
                         // Get the SyncMessage if sent
                         if (self::$decoder->getElementStartTag(SYNC_DATA)) {
                             $message = ZPush::getSyncObjectFromFolderClass($spa->GetContentClass());
                             $message->Decode(self::$decoder);
                             // set Ghosted fields
                             $message->emptySupported(self::$deviceManager->GetSupportedFields($spa->GetFolderId()));
                             if (!self::$decoder->getElementEndTag()) {
                                 // end applicationdata
                                 return false;
                             }
                         } else {
                             $message = false;
                         }
                         switch ($element[EN_TAG]) {
                             case SYNC_FETCH:
                                 array_push($actiondata["fetchids"], $serverid);
                                 break;
                             default:
                                 // get the importer
                                 if ($this->importer == false) {
                                     $status = $this->getImporter($sc, $spa, $actiondata);
                                 }
                                 if ($status == SYNC_STATUS_SUCCESS) {
                                     $this->importMessage($spa, $actiondata, $element[EN_TAG], $message, $clientid, $serverid, $foldertype, $nchanges);
                                 } else {
                                     ZLog::Write(LOGLEVEL_WARN, "Ignored incoming change, global status indicates problem.");
                                 }
                                 break;
                         }
                         if ($actiondata["fetchids"]) {
                             self::$topCollector->AnnounceInformation(sprintf("Fetching %d", $nchanges));
                         } else {
                             self::$topCollector->AnnounceInformation(sprintf("Incoming %d", $nchanges));
                         }
                         if (!self::$decoder->getElementEndTag()) {
                             // end add/change/delete/move
                             return false;
                         }
                     }
                     if ($status == SYNC_STATUS_SUCCESS && $this->importer !== false) {
                         ZLog::Write(LOGLEVEL_INFO, sprintf("Sync->Handle(): Processed %d incoming changes", $nchanges));
                         if (!$actiondata["fetchids"]) {
                             self::$topCollector->AnnounceInformation(sprintf("%d incoming", $nchanges), true);
                         }
                         try {
                             // Save the updated state, which is used for the exporter later
                             $sc->AddParameter($spa, "state", $this->importer->GetState());
                         } catch (StatusException $stex) {
                             $status = $stex->getCode();
                         }
                     }
                     if (!self::$decoder->getElementEndTag()) {
                         // end PERFORM
                         return false;
                     }
                 }
                 // save the failsave state
                 if (!empty($actiondata["statusids"])) {
                     unset($actiondata["failstate"]);
                     $actiondata["failedsyncstate"] = $sc->GetParameter($spa, "state");
                     self::$deviceManager->GetStateManager()->SetSyncFailState($actiondata);
                 }
                 // save actiondata
                 $sc->AddParameter($spa, "actiondata", $actiondata);
                 if (!self::$decoder->getElementEndTag()) {
                     // end collection
                     return false;
                 }
                 // AS14 does not send GetChanges anymore. We should do it if there were no incoming changes
                 if (!isset($performaction) && !$sc->GetParameter($spa, "getchanges") && $spa->HasSyncKey()) {
                     $sc->AddParameter($spa, "getchanges", true);
                 }
             }
             // END FOLDER
             if (!$wbxmlproblem && !self::$decoder->getElementEndTag()) {
                 // end collections
                 return false;
             }
         }
         // end FOLDERS
         if (self::$decoder->getElementStartTag(SYNC_HEARTBEATINTERVAL)) {
             $hbinterval = self::$decoder->getElementContent();
             if (!self::$decoder->getElementEndTag()) {
                 // SYNC_HEARTBEATINTERVAL
                 return false;
             }
         }
         if (self::$decoder->getElementStartTag(SYNC_WAIT)) {
             $wait = self::$decoder->getElementContent();
             if (!self::$decoder->getElementEndTag()) {
                 // SYNC_WAIT
                 return false;
             }
             // internally the heartbeat interval and the wait time are the same
             // heartbeat is in seconds, wait in minutes
             $hbinterval = $wait * 60;
         }
         if (self::$decoder->getElementStartTag(SYNC_WINDOWSIZE)) {
             $sc->SetGlobalWindowSize(self::$decoder->getElementContent());
             if (!self::$decoder->getElementEndTag()) {
                 // SYNC_WINDOWSIZE
                 return false;
             }
         }
         if (self::$decoder->getElementStartTag(SYNC_PARTIAL)) {
             $partial = true;
         } else {
             $partial = false;
         }
         if (!$wbxmlproblem && !self::$decoder->getElementEndTag()) {
             // end sync
             return false;
         }
     } else {
         $emptysync = true;
     }
     // END SYNCHRONIZE
     // check heartbeat/wait time
     if (isset($hbinterval)) {
         if ($hbinterval < 60 || $hbinterval > 3540) {
             $status = SYNC_STATUS_INVALIDWAITORHBVALUE;
             ZLog::Write(LOGLEVEL_WARN, sprintf("HandleSync(): Invalid heartbeat or wait value '%s'", $hbinterval));
         }
     }
     // Partial & Empty Syncs need saved data to proceed with synchronization
     if ($status == SYNC_STATUS_SUCCESS && ($emptysync === true || $partial === true)) {
         ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): Partial or Empty sync requested. Retrieving data of synchronized folders."));
         // Load all collections - do not overwrite existing (received!), load states and check permissions
         try {
             $sc->LoadAllCollections(false, true, true);
         } catch (StateNotFoundException $snfex) {
             $status = SYNC_STATUS_INVALIDSYNCKEY;
             self::$topCollector->AnnounceInformation("StateNotFoundException", true);
         } catch (StatusException $stex) {
             $status = SYNC_STATUS_FOLDERHIERARCHYCHANGED;
             self::$topCollector->AnnounceInformation(sprintf("StatusException code: %d", $status), true);
         }
         // update a few values
         foreach ($sc as $folderid => $spa) {
             // manually set getchanges parameter for this collection
             $sc->AddParameter($spa, "getchanges", true);
             // set new global windowsize without marking the SPA as changed
             if ($sc->GetGlobalWindowSize()) {
                 $spa->SetWindowSize($sc->GetGlobalWindowSize(), false);
             }
             // announce WindowSize to DeviceManager
             self::$deviceManager->SetWindowSize($folderid, $spa->GetWindowSize());
         }
         if (!$sc->HasCollections()) {
             $status = SYNC_STATUS_SYNCREQUESTINCOMPLETE;
         }
     }
     // HEARTBEAT & Empty sync
     if ($status == SYNC_STATUS_SUCCESS && (isset($hbinterval) || $emptysync == true)) {
         $interval = defined('PING_INTERVAL') && PING_INTERVAL > 0 ? PING_INTERVAL : 30;
         if (isset($hbinterval)) {
             $sc->SetLifetime($hbinterval);
         }
         // states are lazy loaded - we have to make sure that they are there!
         $loadstatus = SYNC_STATUS_SUCCESS;
         foreach ($sc as $folderid => $spa) {
             // some androids do heartbeat on the OUTBOX folder, with weird results - ZP-362
             // we do not load the state so we will never get relevant changes on the OUTBOX folder
             if (self::$deviceManager->GetFolderTypeFromCacheById($folderid) == SYNC_FOLDER_TYPE_OUTBOX) {
                 ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): Heartbeat on Outbox folder not allowed"));
                 continue;
             }
             $fad = array();
             // if loading the states fails, we do not enter heartbeat, but we keep $status on SYNC_STATUS_SUCCESS
             // so when the changes are exported the correct folder gets an SYNC_STATUS_INVALIDSYNCKEY
             if ($loadstatus == SYNC_STATUS_SUCCESS) {
                 $loadstatus = $this->loadStates($sc, $spa, $fad);
             }
         }
         if ($loadstatus == SYNC_STATUS_SUCCESS) {
             $foundchanges = false;
             try {
                 // always check for changes
                 ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): Entering Heartbeat mode"));
                 $foundchanges = $sc->CheckForChanges($sc->GetLifetime(), $interval);
             } catch (StatusException $stex) {
                 if ($stex->getCode() == SyncCollections::OBSOLETE_CONNECTION) {
                     $status = SYNC_COMMONSTATUS_SYNCSTATEVERSIONINVALID;
                 } else {
                     $status = SYNC_STATUS_FOLDERHIERARCHYCHANGED;
                     self::$topCollector->AnnounceInformation(sprintf("StatusException code: %d", $status), true);
                 }
             }
             // in case there are no changes and no other request has synchronized while we waited, we can reply with an empty response
             if (!$foundchanges && $status == SYNC_STATUS_SUCCESS) {
                 // if there were changes to the SPA or CPOs we need to save this before we terminate
                 // only save if the state was not modified by some other request, if so, return state invalid status
                 foreach ($sc as $folderid => $spa) {
                     if (self::$deviceManager->CheckHearbeatStateIntegrity($spa->GetFolderId(), $spa->GetUuid(), $spa->GetUuidCounter())) {
                         $status = SYNC_COMMONSTATUS_SYNCSTATEVERSIONINVALID;
                     } else {
                         $sc->SaveCollection($spa);
                     }
                 }
                 if ($status == SYNC_STATUS_SUCCESS) {
                     ZLog::Write(LOGLEVEL_DEBUG, "No changes found and no other process changed states. Replying with empty response and closing connection.");
                     self::$specialHeaders = array();
                     self::$specialHeaders[] = "Connection: close";
                     return true;
                 }
             }
             if ($foundchanges) {
                 foreach ($sc->GetChangedFolderIds() as $folderid => $changecount) {
                     // check if there were other sync requests for a folder during the heartbeat
                     $spa = $sc->GetCollection($folderid);
                     if ($changecount > 0 && $sc->WaitedForChanges() && self::$deviceManager->CheckHearbeatStateIntegrity($spa->GetFolderId(), $spa->GetUuid(), $spa->GetUuidCounter())) {
                         ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): heartbeat: found %d changes in '%s' which was already synchronized. Heartbeat aborted!", $changecount, $folderid));
                         $status = SYNC_COMMONSTATUS_SYNCSTATEVERSIONINVALID;
                     } else {
                         ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): heartbeat: found %d changes in '%s'", $changecount, $folderid));
                     }
                 }
             }
         }
     }
     ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): Start Output"));
     // Start the output
     self::$encoder->startWBXML();
     self::$encoder->startTag(SYNC_SYNCHRONIZE);
     // global status
     // SYNC_COMMONSTATUS_* start with values from 101
     if ($status != SYNC_COMMONSTATUS_SUCCESS && $status > 100) {
         self::$encoder->startTag(SYNC_STATUS);
         self::$encoder->content($status);
         self::$encoder->endTag();
     } else {
         self::$encoder->startTag(SYNC_FOLDERS);
         foreach ($sc as $folderid => $spa) {
             // get actiondata
             $actiondata = $sc->GetParameter($spa, "actiondata");
             if ($status == SYNC_STATUS_SUCCESS && (!$spa->GetContentClass() || !$spa->GetFolderId())) {
                 ZLog::Write(LOGLEVEL_ERROR, sprintf("HandleSync(): no content class or folderid found for collection."));
                 continue;
             }
             if (!$sc->GetParameter($spa, "requested")) {
                 ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): partial sync for folder class '%s' with id '%s'", $spa->GetContentClass(), $spa->GetFolderId()));
             }
             // initialize exporter to get changecount
             $changecount = false;
             if (isset($exporter)) {
                 unset($exporter);
             }
             // TODO we could check against $sc->GetChangedFolderIds() on heartbeat so we do not need to configure all exporter again
             if ($status == SYNC_STATUS_SUCCESS && ($sc->GetParameter($spa, "getchanges") || !$spa->HasSyncKey())) {
                 //make sure the states are loaded
                 $status = $this->loadStates($sc, $spa, $actiondata);
                 if ($status == SYNC_STATUS_SUCCESS) {
                     try {
                         // if this is an additional folder the backend has to be setup correctly
                         if (!self::$backend->Setup(ZPush::GetAdditionalSyncFolderStore($spa->GetFolderId()))) {
                             throw new StatusException(sprintf("HandleSync() could not Setup() the backend for folder id '%s'", $spa->GetFolderId()), SYNC_STATUS_FOLDERHIERARCHYCHANGED);
                         }
                         // Use the state from the importer, as changes may have already happened
                         $exporter = self::$backend->GetExporter($spa->GetFolderId());
                         if ($exporter === false) {
                             throw new StatusException(sprintf("HandleSync() could not get an exporter for folder id '%s'", $spa->GetFolderId()), SYNC_STATUS_FOLDERHIERARCHYCHANGED);
                         }
                     } catch (StatusException $stex) {
                         $status = $stex->getCode();
                     }
                     try {
                         // Stream the messages directly to the PDA
                         $streamimporter = new ImportChangesStream(self::$encoder, ZPush::getSyncObjectFromFolderClass($spa->GetContentClass()));
                         if ($exporter !== false) {
                             $exporter->Config($sc->GetParameter($spa, "state"));
                             $exporter->ConfigContentParameters($spa->GetCPO());
                             $exporter->InitializeExporter($streamimporter);
                             $changecount = $exporter->GetChangeCount();
                         }
                     } catch (StatusException $stex) {
                         if ($stex->getCode() === SYNC_FSSTATUS_CODEUNKNOWN && $spa->HasSyncKey()) {
                             $status = SYNC_STATUS_INVALIDSYNCKEY;
                         } else {
                             $status = $stex->getCode();
                         }
                     }
                     if (!$spa->HasSyncKey()) {
                         self::$topCollector->AnnounceInformation(sprintf("Exporter registered. %d objects queued.", $changecount), true);
                         // update folder status as initialized
                         $spa->SetFolderSyncTotal($changecount);
                         $spa->SetFolderSyncRemaining($changecount);
                         if ($changecount > 0) {
                             self::$deviceManager->SetFolderSyncStatus($folderid, DeviceManager::FLD_SYNC_INITIALIZED);
                         }
                     } else {
                         if ($status != SYNC_STATUS_SUCCESS) {
                             self::$topCollector->AnnounceInformation(sprintf("StatusException code: %d", $status), true);
                         }
                     }
                 }
             }
             if (isset($hbinterval) && $changecount == 0 && $status == SYNC_STATUS_SUCCESS) {
                 ZLog::Write(LOGLEVEL_DEBUG, "No changes found for heartbeat folder. Omitting empty output.");
                 continue;
             }
             // Get a new sync key to output to the client if any changes have been send or will are available
             if (!empty($actiondata["modifyids"]) || !empty($actiondata["clientids"]) || !empty($actiondata["removeids"]) || $changecount > 0 || !$spa->HasSyncKey() && $status == SYNC_STATUS_SUCCESS) {
                 $spa->SetNewSyncKey(self::$deviceManager->GetStateManager()->GetNewSyncKey($spa->GetSyncKey()));
             }
             self::$encoder->startTag(SYNC_FOLDER);
             if ($spa->HasContentClass()) {
                 ZLog::Write(LOGLEVEL_DEBUG, sprintf("Folder type: %s", $spa->GetContentClass()));
                 // AS 12.0 devices require content class
                 if (Request::GetProtocolVersion() < 12.1) {
                     self::$encoder->startTag(SYNC_FOLDERTYPE);
                     self::$encoder->content($spa->GetContentClass());
                     self::$encoder->endTag();
                 }
             }
             self::$encoder->startTag(SYNC_SYNCKEY);
             if ($status == SYNC_STATUS_SUCCESS && $spa->HasNewSyncKey()) {
                 self::$encoder->content($spa->GetNewSyncKey());
             } else {
                 self::$encoder->content($spa->GetSyncKey());
             }
             self::$encoder->endTag();
             self::$encoder->startTag(SYNC_FOLDERID);
             self::$encoder->content($spa->GetFolderId());
             self::$encoder->endTag();
             self::$encoder->startTag(SYNC_STATUS);
             self::$encoder->content($status);
             self::$encoder->endTag();
             // announce failing status to the process loop detection
             if ($status !== SYNC_STATUS_SUCCESS) {
                 self::$deviceManager->AnnounceProcessStatus($spa->GetFolderId(), $status);
             }
             // Output IDs and status for incoming items & requests
             if ($status == SYNC_STATUS_SUCCESS && (!empty($actiondata["clientids"]) || !empty($actiondata["modifyids"]) || !empty($actiondata["removeids"]) || !empty($actiondata["fetchids"]))) {
                 self::$encoder->startTag(SYNC_REPLIES);
                 // output result of all new incoming items
                 foreach ($actiondata["clientids"] as $clientid => $serverid) {
                     self::$encoder->startTag(SYNC_ADD);
                     self::$encoder->startTag(SYNC_CLIENTENTRYID);
                     self::$encoder->content($clientid);
                     self::$encoder->endTag();
                     if ($serverid) {
                         self::$encoder->startTag(SYNC_SERVERENTRYID);
                         self::$encoder->content($serverid);
                         self::$encoder->endTag();
                     }
                     self::$encoder->startTag(SYNC_STATUS);
                     self::$encoder->content(isset($actiondata["statusids"][$clientid]) ? $actiondata["statusids"][$clientid] : SYNC_STATUS_CLIENTSERVERCONVERSATIONERROR);
                     self::$encoder->endTag();
                     self::$encoder->endTag();
                 }
                 // loop through modify operations which were not a success, send status
                 foreach ($actiondata["modifyids"] as $serverid) {
                     if (isset($actiondata["statusids"][$serverid]) && $actiondata["statusids"][$serverid] !== SYNC_STATUS_SUCCESS) {
                         self::$encoder->startTag(SYNC_MODIFY);
                         self::$encoder->startTag(SYNC_SERVERENTRYID);
                         self::$encoder->content($serverid);
                         self::$encoder->endTag();
                         self::$encoder->startTag(SYNC_STATUS);
                         self::$encoder->content($actiondata["statusids"][$serverid]);
                         self::$encoder->endTag();
                         self::$encoder->endTag();
                     }
                 }
                 // loop through remove operations which were not a success, send status
                 foreach ($actiondata["removeids"] as $serverid) {
                     if (isset($actiondata["statusids"][$serverid]) && $actiondata["statusids"][$serverid] !== SYNC_STATUS_SUCCESS) {
                         self::$encoder->startTag(SYNC_REMOVE);
                         self::$encoder->startTag(SYNC_SERVERENTRYID);
                         self::$encoder->content($serverid);
                         self::$encoder->endTag();
                         self::$encoder->startTag(SYNC_STATUS);
                         self::$encoder->content($actiondata["statusids"][$serverid]);
                         self::$encoder->endTag();
                         self::$encoder->endTag();
                     }
                 }
                 if (!empty($actiondata["fetchids"])) {
                     self::$topCollector->AnnounceInformation(sprintf("Fetching %d objects ", count($actiondata["fetchids"])), true);
                 }
                 foreach ($actiondata["fetchids"] as $id) {
                     $data = false;
                     try {
                         $fetchstatus = SYNC_STATUS_SUCCESS;
                         // if this is an additional folder the backend has to be setup correctly
                         if (!self::$backend->Setup(ZPush::GetAdditionalSyncFolderStore($spa->GetFolderId()))) {
                             throw new StatusException(sprintf("HandleSync(): could not Setup() the backend to fetch in folder id '%s'", $spa->GetFolderId()), SYNC_STATUS_OBJECTNOTFOUND);
                         }
                         $data = self::$backend->Fetch($spa->GetFolderId(), $id, $spa->GetCPO());
                         // check if the message is broken
                         if (ZPush::GetDeviceManager(false) && ZPush::GetDeviceManager()->DoNotStreamMessage($id, $data)) {
                             ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): message not to be streamed as requested by DeviceManager, id = %s", $id));
                             $fetchstatus = SYNC_STATUS_CLIENTSERVERCONVERSATIONERROR;
                         }
                     } catch (StatusException $stex) {
                         $fetchstatus = $stex->getCode();
                     }
                     self::$encoder->startTag(SYNC_FETCH);
                     self::$encoder->startTag(SYNC_SERVERENTRYID);
                     self::$encoder->content($id);
                     self::$encoder->endTag();
                     self::$encoder->startTag(SYNC_STATUS);
                     self::$encoder->content($fetchstatus);
                     self::$encoder->endTag();
                     if ($data !== false && $status == SYNC_STATUS_SUCCESS) {
                         self::$encoder->startTag(SYNC_DATA);
                         $data->Encode(self::$encoder);
                         self::$encoder->endTag();
                     } else {
                         ZLog::Write(LOGLEVEL_WARN, sprintf("Unable to Fetch '%s'", $id));
                     }
                     self::$encoder->endTag();
                 }
                 self::$encoder->endTag();
             }
             if ($sc->GetParameter($spa, "getchanges") && $spa->HasFolderId() && $spa->HasContentClass() && $spa->HasSyncKey()) {
                 $windowSize = self::$deviceManager->GetWindowSize($spa->GetFolderId(), $spa->GetContentClass(), $spa->GetUuid(), $spa->GetUuidCounter(), $changecount);
                 if ($changecount > $windowSize) {
                     self::$encoder->startTag(SYNC_MOREAVAILABLE, false, true);
                 }
             }
             // Stream outgoing changes
             if ($status == SYNC_STATUS_SUCCESS && $sc->GetParameter($spa, "getchanges") == true && $windowSize > 0) {
                 self::$topCollector->AnnounceInformation(sprintf("Streaming data of %d objects", $changecount > $windowSize ? $windowSize : $changecount));
                 // Output message changes per folder
                 self::$encoder->startTag(SYNC_PERFORM);
                 $n = 0;
                 while (1) {
                     try {
                         $progress = $exporter->Synchronize();
                         if (!is_array($progress)) {
                             break;
                         }
                         $n++;
                     } catch (SyncObjectBrokenException $mbe) {
                         $brokenSO = $mbe->GetSyncObject();
                         if (!$brokenSO) {
                             ZLog::Write(LOGLEVEL_ERROR, sprintf("HandleSync(): Catched SyncObjectBrokenException but broken SyncObject not available. This should be fixed in the backend."));
                         } else {
                             if (!isset($brokenSO->id)) {
                                 $brokenSO->id = "Unknown ID";
                                 ZLog::Write(LOGLEVEL_ERROR, sprintf("HandleSync(): Catched SyncObjectBrokenException but no ID of object set. This should be fixed in the backend."));
                             }
                             self::$deviceManager->AnnounceIgnoredMessage($spa->GetFolderId(), $brokenSO->id, $brokenSO);
                         }
                     } catch (StatusException $stex) {
                         $status = $stex->getCode();
                         // during export we found out that the states should be thrown away (ZP-623)
                         if ($status == SYNC_STATUS_INVALIDSYNCKEY) {
                             self::$deviceManager->ForceFolderResync($spa->GetFolderId());
                             break;
                         }
                     }
                     if ($n >= $windowSize) {
                         ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): Exported maxItems of messages: %d / %d", $n, $changecount));
                         break;
                     }
                 }
                 // $progress is not an array when exporting the last message
                 // so we get the number to display from the streamimporter
                 if (isset($streamimporter)) {
                     $n = $streamimporter->GetImportedMessages();
                 }
                 self::$encoder->endTag();
                 self::$topCollector->AnnounceInformation(sprintf("Outgoing %d objects%s", $n, $n >= $windowSize ? " of " . $changecount : ""), true);
                 // update folder status, if there is something set
                 if ($spa->GetFolderSyncRemaining() && $changecount > 0) {
                     $spa->SetFolderSyncRemaining($changecount);
                 }
                 // changecount is initialized with 'false', so 0 means no changes!
                 if ($changecount === 0 || $changecount !== false && $changecount <= $windowSize) {
                     self::$deviceManager->SetFolderSyncStatus($folderid, DeviceManager::FLD_SYNC_COMPLETED);
                 } else {
                     self::$deviceManager->SetFolderSyncStatus($folderid, DeviceManager::FLD_SYNC_INPROGRESS);
                 }
             }
             self::$encoder->endTag();
             // Save the sync state for the next time
             if ($spa->HasNewSyncKey()) {
                 self::$topCollector->AnnounceInformation("Saving state");
                 try {
                     if (isset($exporter) && $exporter) {
                         $state = $exporter->GetState();
                     } else {
                         if ($sc->GetParameter($spa, "state") !== null) {
                             $state = $sc->GetParameter($spa, "state");
                         } else {
                             if (!$spa->HasSyncKey()) {
                                 $state = "";
                             }
                         }
                     }
                 } catch (StatusException $stex) {
                     $status = $stex->getCode();
                 }
                 if (isset($state) && $status == SYNC_STATUS_SUCCESS) {
                     self::$deviceManager->GetStateManager()->SetSyncState($spa->GetNewSyncKey(), $state, $spa->GetFolderId());
                 } else {
                     ZLog::Write(LOGLEVEL_ERROR, sprintf("HandleSync(): error saving '%s' - no state information available", $spa->GetNewSyncKey()));
                 }
             }
             // save SyncParameters
             if ($status == SYNC_STATUS_SUCCESS && empty($actiondata["fetchids"])) {
                 $sc->SaveCollection($spa);
             }
             // reset status for the next folder
             $status = SYNC_STATUS_SUCCESS;
         }
         // END foreach collection
         self::$encoder->endTag();
         //SYNC_FOLDERS
     }
     self::$encoder->endTag();
     //SYNC_SYNCHRONIZE
     return true;
 }
 /**
  * Returns the timestamp of the last synchronization of a device.
  *
  * @param $device       an ASDevice
  *
  * @access public
  * @return int                  timestamp
  */
 public static function GetLastSyncTimeOfDevice(&$device)
 {
     // we need a StateManager for this operation
     $stateManager = new StateManager();
     $stateManager->SetDevice($device);
     $sc = new SyncCollections();
     $sc->SetStateManager($stateManager);
     // load all collections of device without loading states or checking permissions
     $sc->LoadAllCollections(true, false, false);
     return $sc->GetLastSyncTime();
 }
示例#5
0
 /**
  * Synchronizes a folder to the output stream. Changes for this folders are expected.
  *
  * @param SyncCollections       $sc
  * @param SyncParameters        $spa
  * @param IExportChanges        $exporter             Fully configured exporter for this folder
  * @param int                   $changecount          Amount of changes expected
  * @param ImportChangesStream   $streamimporter       Output stream
  * @param int                   $status               current status of the folder processing
  * @param string                $newFolderStat        the new folder stat to be set if everything was exported
  *
  * @throws StatusException
  * @return int  sync status code
  */
 private function syncFolder($sc, $spa, $exporter, $changecount, $streamimporter, $status, $newFolderStat)
 {
     $actiondata = $sc->GetParameter($spa, "actiondata");
     // send the WBXML start tags (if not happened already)
     $this->sendFolderStartTag();
     self::$encoder->startTag(SYNC_FOLDER);
     if ($spa->HasContentClass()) {
         ZLog::Write(LOGLEVEL_DEBUG, sprintf("Folder type: %s", $spa->GetContentClass()));
         // AS 12.0 devices require content class
         if (Request::GetProtocolVersion() < 12.1) {
             self::$encoder->startTag(SYNC_FOLDERTYPE);
             self::$encoder->content($spa->GetContentClass());
             self::$encoder->endTag();
         }
     }
     self::$encoder->startTag(SYNC_SYNCKEY);
     if ($status == SYNC_STATUS_SUCCESS && $spa->HasNewSyncKey()) {
         self::$encoder->content($spa->GetNewSyncKey());
     } else {
         self::$encoder->content($spa->GetSyncKey());
     }
     self::$encoder->endTag();
     self::$encoder->startTag(SYNC_FOLDERID);
     self::$encoder->content($spa->GetFolderId());
     self::$encoder->endTag();
     self::$encoder->startTag(SYNC_STATUS);
     self::$encoder->content($status);
     self::$encoder->endTag();
     // announce failing status to the process loop detection
     if ($status !== SYNC_STATUS_SUCCESS) {
         self::$deviceManager->AnnounceProcessStatus($spa->GetFolderId(), $status);
     }
     // Output IDs and status for incoming items & requests
     if ($status == SYNC_STATUS_SUCCESS && (!empty($actiondata["clientids"]) || !empty($actiondata["modifyids"]) || !empty($actiondata["removeids"]) || !empty($actiondata["fetchids"]))) {
         self::$encoder->startTag(SYNC_REPLIES);
         // output result of all new incoming items
         foreach ($actiondata["clientids"] as $clientid => $serverid) {
             self::$encoder->startTag(SYNC_ADD);
             self::$encoder->startTag(SYNC_CLIENTENTRYID);
             self::$encoder->content($clientid);
             self::$encoder->endTag();
             if ($serverid) {
                 self::$encoder->startTag(SYNC_SERVERENTRYID);
                 self::$encoder->content($serverid);
                 self::$encoder->endTag();
             }
             self::$encoder->startTag(SYNC_STATUS);
             self::$encoder->content(isset($actiondata["statusids"][$clientid]) ? $actiondata["statusids"][$clientid] : SYNC_STATUS_CLIENTSERVERCONVERSATIONERROR);
             self::$encoder->endTag();
             self::$encoder->endTag();
         }
         // loop through modify operations which were not a success, send status
         foreach ($actiondata["modifyids"] as $serverid) {
             if (isset($actiondata["statusids"][$serverid]) && $actiondata["statusids"][$serverid] !== SYNC_STATUS_SUCCESS) {
                 self::$encoder->startTag(SYNC_MODIFY);
                 self::$encoder->startTag(SYNC_SERVERENTRYID);
                 self::$encoder->content($serverid);
                 self::$encoder->endTag();
                 self::$encoder->startTag(SYNC_STATUS);
                 self::$encoder->content($actiondata["statusids"][$serverid]);
                 self::$encoder->endTag();
                 self::$encoder->endTag();
             }
         }
         // loop through remove operations which were not a success, send status
         foreach ($actiondata["removeids"] as $serverid) {
             if (isset($actiondata["statusids"][$serverid]) && $actiondata["statusids"][$serverid] !== SYNC_STATUS_SUCCESS) {
                 self::$encoder->startTag(SYNC_REMOVE);
                 self::$encoder->startTag(SYNC_SERVERENTRYID);
                 self::$encoder->content($serverid);
                 self::$encoder->endTag();
                 self::$encoder->startTag(SYNC_STATUS);
                 self::$encoder->content($actiondata["statusids"][$serverid]);
                 self::$encoder->endTag();
                 self::$encoder->endTag();
             }
         }
         if (!empty($actiondata["fetchids"])) {
             self::$topCollector->AnnounceInformation(sprintf("Fetching %d objects ", count($actiondata["fetchids"])), $this->singleFolder);
             $this->saveMultiFolderInfo("fetching", count($actiondata["fetchids"]));
         }
         foreach ($actiondata["fetchids"] as $id) {
             $data = false;
             try {
                 $fetchstatus = SYNC_STATUS_SUCCESS;
                 // if this is an additional folder the backend has to be setup correctly
                 if (!self::$backend->Setup(ZPush::GetAdditionalSyncFolderStore($spa->GetBackendFolderId()))) {
                     throw new StatusException(sprintf("HandleSync(): could not Setup() the backend to fetch in folder id %s/%s", $spa->GetFolderId(), $spa->GetBackendFolderId()), SYNC_STATUS_OBJECTNOTFOUND);
                 }
                 $data = self::$backend->Fetch($spa->GetBackendFolderId(), $id, $spa->GetCPO());
                 // check if the message is broken
                 if (ZPush::GetDeviceManager(false) && ZPush::GetDeviceManager()->DoNotStreamMessage($id, $data)) {
                     ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): message not to be streamed as requested by DeviceManager, id = %s", $id));
                     $fetchstatus = SYNC_STATUS_CLIENTSERVERCONVERSATIONERROR;
                 }
             } catch (StatusException $stex) {
                 $fetchstatus = $stex->getCode();
             }
             self::$encoder->startTag(SYNC_FETCH);
             self::$encoder->startTag(SYNC_SERVERENTRYID);
             self::$encoder->content($id);
             self::$encoder->endTag();
             self::$encoder->startTag(SYNC_STATUS);
             self::$encoder->content($fetchstatus);
             self::$encoder->endTag();
             if ($data !== false && $status == SYNC_STATUS_SUCCESS) {
                 self::$encoder->startTag(SYNC_DATA);
                 $data->Encode(self::$encoder);
                 self::$encoder->endTag();
             } else {
                 ZLog::Write(LOGLEVEL_WARN, sprintf("Unable to Fetch '%s'", $id));
             }
             self::$encoder->endTag();
         }
         self::$encoder->endTag();
     }
     if ($sc->GetParameter($spa, "getchanges") && $spa->HasFolderId() && $spa->HasContentClass() && $spa->HasSyncKey()) {
         $moreAvailableSent = false;
         $windowSize = self::$deviceManager->GetWindowSize($spa->GetFolderId(), $spa->GetUuid(), $spa->GetUuidCounter(), $changecount);
         // limit windowSize to the max available limit of the global window size left
         $globallyAvailable = $sc->GetGlobalWindowSize() - $this->globallyExportedItems;
         if ($changecount > $globallyAvailable && $windowSize > $globallyAvailable) {
             ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): Limit window size to %d as the global window size limit will be reached", $globallyAvailable));
             $windowSize = $globallyAvailable;
         }
         // send <MoreAvailable/> if there are more changes than fit in the folder windowsize
         // or there is a move state (another sync should be done afterwards)
         if ($changecount > $windowSize || $spa->GetMoveState() !== false) {
             self::$encoder->startTag(SYNC_MOREAVAILABLE, false, true);
             $moreAvailableSent = true;
             $spa->DelFolderStat();
         }
     }
     // Stream outgoing changes
     if ($status == SYNC_STATUS_SUCCESS && $sc->GetParameter($spa, "getchanges") == true && $windowSize > 0 && !!$exporter) {
         self::$topCollector->AnnounceInformation(sprintf("Streaming data of %d objects", $changecount > $windowSize ? $windowSize : $changecount));
         // Output message changes per folder
         self::$encoder->startTag(SYNC_PERFORM);
         $n = 0;
         WBXMLDecoder::ResetInWhile("syncSynchronize");
         while (WBXMLDecoder::InWhile("syncSynchronize")) {
             try {
                 $progress = $exporter->Synchronize();
                 if (!is_array($progress)) {
                     break;
                 }
                 $n++;
                 if ($n % 10 == 0) {
                     self::$topCollector->AnnounceInformation(sprintf("Streamed data of %d objects out of %d", $n, $changecount > $windowSize ? $windowSize : $changecount));
                 }
             } catch (SyncObjectBrokenException $mbe) {
                 $brokenSO = $mbe->GetSyncObject();
                 if (!$brokenSO) {
                     ZLog::Write(LOGLEVEL_ERROR, sprintf("HandleSync(): Catched SyncObjectBrokenException but broken SyncObject not available. This should be fixed in the backend."));
                 } else {
                     if (!isset($brokenSO->id)) {
                         $brokenSO->id = "Unknown ID";
                         ZLog::Write(LOGLEVEL_ERROR, sprintf("HandleSync(): Catched SyncObjectBrokenException but no ID of object set. This should be fixed in the backend."));
                     }
                     self::$deviceManager->AnnounceIgnoredMessage($spa->GetFolderId(), $brokenSO->id, $brokenSO);
                 }
             } catch (StatusException $stex) {
                 $status = $stex->getCode();
                 // during export we found out that the states should be thrown away (ZP-623)
                 if ($status == SYNC_STATUS_INVALIDSYNCKEY) {
                     self::$deviceManager->ForceFolderResync($spa->GetFolderId());
                     break;
                 }
             }
             if ($n >= $windowSize || Request::IsRequestTimeoutReached()) {
                 ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): Exported maxItems of messages: %d / %d", $n, $changecount));
                 break;
             }
         }
         // $progress is not an array when exporting the last message
         // so we get the number to display from the streamimporter if it's available
         if (!!$streamimporter) {
             $n = $streamimporter->GetImportedMessages();
         }
         self::$encoder->endTag();
         // log the request timeout
         if (Request::IsRequestTimeoutReached()) {
             ZLog::Write(LOGLEVEL_DEBUG, "HandleSync(): Stopping export as maximum request timeout is almost reached!");
             // Send a <MoreAvailable/> tag if we reached the request timeout, there are more changes and a moreavailable was not already send
             if (!$moreAvailableSent && $n > $windowSize) {
                 self::$encoder->startTag(SYNC_MOREAVAILABLE, false, true);
                 $spa->DelFolderStat();
                 $moreAvailableSent = true;
             }
         }
         self::$topCollector->AnnounceInformation(sprintf("Outgoing %d objects%s", $n, $n >= $windowSize ? " of " . $changecount : ""), $this->singleFolder);
         $this->saveMultiFolderInfo("outgoing", $n);
         $this->saveMultiFolderInfo("queued", $changecount);
         $this->globallyExportedItems += $n;
         // update folder status, if there is something set
         if ($spa->GetFolderSyncRemaining() && $changecount > 0) {
             $spa->SetFolderSyncRemaining($changecount);
         }
         // changecount is initialized with 'false', so 0 means no changes!
         if ($changecount === 0 || $changecount !== false && $changecount <= $windowSize) {
             self::$deviceManager->SetFolderSyncStatus($spa->GetFolderId(), DeviceManager::FLD_SYNC_COMPLETED);
             // we should update the folderstat, but we recheck to see if it changed. If so, it's not updated to force another sync
             $newFolderStatAfterExport = self::$backend->GetFolderStat(ZPush::GetAdditionalSyncFolderStore($spa->GetBackendFolderId()), $spa->GetBackendFolderId());
             if ($newFolderStat === $newFolderStatAfterExport) {
                 $this->setFolderStat($spa, $newFolderStat);
             } else {
                 ZLog::Write(LOGLEVEL_DEBUG, "Sync() Folderstat differs after export, force another exporter run.");
             }
         } else {
             self::$deviceManager->SetFolderSyncStatus($spa->GetFolderId(), DeviceManager::FLD_SYNC_INPROGRESS);
         }
     }
     self::$encoder->endTag();
     // Save the sync state for the next time
     if ($spa->HasNewSyncKey()) {
         self::$topCollector->AnnounceInformation("Saving state");
         try {
             if (isset($exporter) && $exporter) {
                 $state = $exporter->GetState();
                 // update the move state (it should be gone now)
                 list($moveState, ) = $exporter->GetMoveStates();
                 $spa->SetMoveState($moveState);
             } else {
                 if ($sc->GetParameter($spa, "state") !== null) {
                     $state = $sc->GetParameter($spa, "state");
                 } else {
                     if (!$spa->HasSyncKey()) {
                         $state = "";
                     }
                 }
             }
         } catch (StatusException $stex) {
             $status = $stex->getCode();
         }
         if (isset($state) && $status == SYNC_STATUS_SUCCESS) {
             self::$deviceManager->GetStateManager()->SetSyncState($spa->GetNewSyncKey(), $state, $spa->GetFolderId());
         } else {
             ZLog::Write(LOGLEVEL_ERROR, sprintf("HandleSync(): error saving '%s' - no state information available", $spa->GetNewSyncKey()));
         }
     }
     // save SyncParameters
     if ($status == SYNC_STATUS_SUCCESS && empty($actiondata["fetchids"])) {
         $sc->SaveCollection($spa);
     }
     return $status;
 }
 /**
  * Handles the GetItemEstimate command
  * Returns an estimation of how many items will be synchronized at the next sync
  * This is mostly used to show something in the progress bar
  *
  * @param int       $commandCode
  *
  * @access public
  * @return boolean
  */
 public function Handle($commandCode)
 {
     $sc = new SyncCollections();
     if (!self::$decoder->getElementStartTag(SYNC_GETITEMESTIMATE_GETITEMESTIMATE)) {
         return false;
     }
     if (!self::$decoder->getElementStartTag(SYNC_GETITEMESTIMATE_FOLDERS)) {
         return false;
     }
     while (self::$decoder->getElementStartTag(SYNC_GETITEMESTIMATE_FOLDER)) {
         $spa = new SyncParameters();
         $spastatus = false;
         if (Request::GetProtocolVersion() >= 14.0) {
             if (self::$decoder->getElementStartTag(SYNC_SYNCKEY)) {
                 try {
                     $spa->SetSyncKey(self::$decoder->getElementContent());
                 } catch (StateInvalidException $siex) {
                     $spastatus = SYNC_GETITEMESTSTATUS_SYNCSTATENOTPRIMED;
                 }
                 if (!self::$decoder->getElementEndTag()) {
                     return false;
                 }
             }
             if (self::$decoder->getElementStartTag(SYNC_GETITEMESTIMATE_FOLDERID)) {
                 $spa->SetFolderId(self::$decoder->getElementContent());
                 if (!self::$decoder->getElementEndTag()) {
                     return false;
                 }
             }
             // conversation mode requested
             if (self::$decoder->getElementStartTag(SYNC_CONVERSATIONMODE)) {
                 $spa->SetConversationMode(true);
                 if (($conversationmode = self::$decoder->getElementContent()) !== false) {
                     $spa->SetConversationMode((bool) $conversationmode);
                     if (!self::$decoder->getElementEndTag()) {
                         return false;
                     }
                 }
             }
             if (self::$decoder->getElementStartTag(SYNC_OPTIONS)) {
                 while (1) {
                     if (self::$decoder->getElementStartTag(SYNC_FILTERTYPE)) {
                         $spa->SetFilterType(self::$decoder->getElementContent());
                         if (!self::$decoder->getElementEndTag()) {
                             return false;
                         }
                     }
                     if (self::$decoder->getElementStartTag(SYNC_FOLDERTYPE)) {
                         $spa->SetContentClass(self::$decoder->getElementContent());
                         if (!self::$decoder->getElementEndTag()) {
                             return false;
                         }
                     }
                     if (self::$decoder->getElementStartTag(SYNC_MAXITEMS)) {
                         $spa->SetWindowSize($maxitems = self::$decoder->getElementContent());
                         if (!self::$decoder->getElementEndTag()) {
                             return false;
                         }
                     }
                     $e = self::$decoder->peek();
                     if ($e[EN_TYPE] == EN_TYPE_ENDTAG) {
                         self::$decoder->getElementEndTag();
                         break;
                     }
                 }
             }
         } else {
             //get items estimate does not necessarily send the folder type
             if (self::$decoder->getElementStartTag(SYNC_GETITEMESTIMATE_FOLDERTYPE)) {
                 $spa->SetContentClass(self::$decoder->getElementContent());
                 if (!self::$decoder->getElementEndTag()) {
                     return false;
                 }
             }
             if (self::$decoder->getElementStartTag(SYNC_GETITEMESTIMATE_FOLDERID)) {
                 $spa->SetFolderId(self::$decoder->getElementContent());
                 if (!self::$decoder->getElementEndTag()) {
                     return false;
                 }
             }
             if (!self::$decoder->getElementStartTag(SYNC_FILTERTYPE)) {
                 return false;
             }
             $spa->SetFilterType(self::$decoder->getElementContent());
             if (!self::$decoder->getElementEndTag()) {
                 return false;
             }
             if (!self::$decoder->getElementStartTag(SYNC_SYNCKEY)) {
                 return false;
             }
             try {
                 $spa->SetSyncKey(self::$decoder->getElementContent());
             } catch (StateInvalidException $siex) {
                 $spastatus = SYNC_GETITEMESTSTATUS_SYNCSTATENOTPRIMED;
             }
             if (!self::$decoder->getElementEndTag()) {
                 return false;
             }
         }
         if (!self::$decoder->getElementEndTag()) {
             return false;
         }
         //SYNC_GETITEMESTIMATE_FOLDER
         // Process folder data
         //In AS 14 request only collectionid is sent, without class
         if (!$spa->HasContentClass() && $spa->HasFolderId()) {
             try {
                 $spa->SetContentClass(self::$deviceManager->GetFolderClassFromCacheByID($spa->GetFolderId()));
             } catch (NoHierarchyCacheAvailableException $nhca) {
                 $spastatus = SYNC_GETITEMESTSTATUS_COLLECTIONINVALID;
             }
         }
         // compatibility mode AS 1.0 - get folderid which was sent during GetHierarchy()
         if (!$spa->HasFolderId() && $spa->HasContentClass()) {
             $spa->SetFolderId(self::$deviceManager->GetFolderIdFromCacheByClass($spa->GetContentClass()));
         }
         // Add collection to SC and load state
         $sc->AddCollection($spa);
         if ($spastatus) {
             // the CPO has a folder id now, so we can set the status
             $sc->AddParameter($spa, "status", $spastatus);
         } else {
             try {
                 $sc->AddParameter($spa, "state", self::$deviceManager->GetStateManager()->GetSyncState($spa->GetSyncKey()));
                 // if this is an additional folder the backend has to be setup correctly
                 if (!self::$backend->Setup(ZPush::GetAdditionalSyncFolderStore($spa->GetFolderId()))) {
                     throw new StatusException(sprintf("HandleGetItemEstimate() could not Setup() the backend for folder id '%s'", $spa->GetFolderId()), SYNC_GETITEMESTSTATUS_COLLECTIONINVALID);
                 }
             } catch (StateNotFoundException $snfex) {
                 // ok, the key is invalid. Question is, if the hierarchycache is still ok
                 //if not, we have to issue SYNC_GETITEMESTSTATUS_COLLECTIONINVALID which triggers a FolderSync
                 try {
                     self::$deviceManager->GetFolderClassFromCacheByID($spa->GetFolderId());
                     // we got here, so the HierarchyCache is ok
                     $sc->AddParameter($spa, "status", SYNC_GETITEMESTSTATUS_SYNCKKEYINVALID);
                 } catch (NoHierarchyCacheAvailableException $nhca) {
                     $sc->AddParameter($spa, "status", SYNC_GETITEMESTSTATUS_COLLECTIONINVALID);
                 }
                 self::$topCollector->AnnounceInformation("StateNotFoundException " . $sc->GetParameter($spa, "status"), true);
             } catch (StatusException $stex) {
                 if ($stex->getCode() == SYNC_GETITEMESTSTATUS_COLLECTIONINVALID) {
                     $sc->AddParameter($spa, "status", SYNC_GETITEMESTSTATUS_COLLECTIONINVALID);
                 } else {
                     $sc->AddParameter($spa, "status", SYNC_GETITEMESTSTATUS_SYNCSTATENOTPRIMED);
                 }
                 self::$topCollector->AnnounceInformation("StatusException " . $sc->GetParameter($spa, "status"), true);
             }
         }
     }
     if (!self::$decoder->getElementEndTag()) {
         return false;
     }
     //SYNC_GETITEMESTIMATE_FOLDERS
     if (!self::$decoder->getElementEndTag()) {
         return false;
     }
     //SYNC_GETITEMESTIMATE_GETITEMESTIMATE
     self::$encoder->startWBXML();
     self::$encoder->startTag(SYNC_GETITEMESTIMATE_GETITEMESTIMATE);
     $status = SYNC_GETITEMESTSTATUS_SUCCESS;
     // look for changes in all collections
     try {
         $sc->CountChanges();
     } catch (StatusException $ste) {
         $status = SYNC_GETITEMESTSTATUS_COLLECTIONINVALID;
     }
     $changes = $sc->GetChangedFolderIds();
     foreach ($sc as $folderid => $spa) {
         self::$encoder->startTag(SYNC_GETITEMESTIMATE_RESPONSE);
         if ($sc->GetParameter($spa, "status")) {
             $status = $sc->GetParameter($spa, "status");
         }
         self::$encoder->startTag(SYNC_GETITEMESTIMATE_STATUS);
         self::$encoder->content($status);
         self::$encoder->endTag();
         self::$encoder->startTag(SYNC_GETITEMESTIMATE_FOLDER);
         self::$encoder->startTag(SYNC_GETITEMESTIMATE_FOLDERTYPE);
         self::$encoder->content($spa->GetContentClass());
         self::$encoder->endTag();
         self::$encoder->startTag(SYNC_GETITEMESTIMATE_FOLDERID);
         self::$encoder->content($spa->GetFolderId());
         self::$encoder->endTag();
         if (isset($changes[$folderid]) && $changes[$folderid] !== false) {
             self::$encoder->startTag(SYNC_GETITEMESTIMATE_ESTIMATE);
             self::$encoder->content($changes[$folderid]);
             self::$encoder->endTag();
             if ($changes[$folderid] > 0) {
                 self::$topCollector->AnnounceInformation(sprintf("%s %d changes", $spa->GetContentClass(), $changes[$folderid]), true);
             }
         }
         self::$encoder->endTag();
         self::$encoder->endTag();
     }
     if (array_sum($changes) == 0) {
         self::$topCollector->AnnounceInformation("No changes found", true);
     }
     self::$encoder->endTag();
     return true;
 }
 /**
  * Returns details of a device like synctimes,
  * policy and wipe status, synched folders etc
  *
  * @param string    $devid      device id
  * @param string    $user       user to be looked up
  *
  * @return ASDevice object
  * @access public
  */
 public static function GetDeviceDetails($devid, $user)
 {
     try {
         $device = new ASDevice($devid, ASDevice::UNDEFINED, $user, ASDevice::UNDEFINED);
         $device->SetData(ZPush::GetStateMachine()->GetState($devid, IStateMachine::DEVICEDATA), false);
         $device->StripData();
         try {
             $lastsync = SyncCollections::GetLastSyncTimeOfDevice($device);
             if ($lastsync) {
                 $device->SetLastSyncTime($lastsync);
             }
         } catch (StateInvalidException $sive) {
             ZLog::Write(LOGLEVEL_WARN, sprintf("ZPushAdmin::GetDeviceDetails(): device '%s' of user '%s' has invalid states. Please sync to solve this issue.", $devid, $user));
             $device->SetDeviceError("Invalid states. Please force synchronization!");
         }
         return $device;
     } catch (StateNotFoundException $e) {
         ZLog::Write(LOGLEVEL_ERROR, sprintf("ZPushAdmin::GetDeviceDetails(): device '%s' of user '%s' can not be found", $devid, $user));
         return false;
     }
 }
示例#8
0
 /**
  * Returns details of a device like synctimes,
  * policy and wipe status, synched folders etc
  *
  * @param string    $devid      device id
  * @param string    $user       user to be looked up
  *
  * @return ASDevice object
  * @access public
  */
 public static function GetDeviceDetails($devid, $user)
 {
     try {
         $device = new ASDevice($devid, ASDevice::UNDEFINED, $user, ASDevice::UNDEFINED);
         $device->SetData(ZPush::GetStateMachine()->GetState($devid, IStateMachine::DEVICEDATA), false);
         $device->StripData();
         try {
             // we need a StateManager for this operation
             $stateManager = new StateManager();
             $stateManager->SetDevice($device);
             $sc = new SyncCollections();
             $sc->SetStateManager($stateManager);
             // load all collections of device without loading states or checking permissions
             $sc->LoadAllCollections(true, false, false);
             if ($sc->GetLastSyncTime()) {
                 $device->SetLastSyncTime($sc->GetLastSyncTime());
             }
             // get information about the folder synchronization status from SyncCollections
             $folders = $device->GetAllFolderIds();
             foreach ($folders as $folderid) {
                 $fstatus = $device->GetFolderSyncStatus($folderid);
                 if ($fstatus !== false && isset($fstatus[ASDevice::FOLDERSYNCSTATUS])) {
                     $spa = $sc->GetCollection($folderid);
                     $total = $spa->GetFolderSyncTotal();
                     $todo = $spa->GetFolderSyncRemaining();
                     $fstatus['status'] = $fstatus[ASDevice::FOLDERSYNCSTATUS] == 1 ? 'Initialized' : 'Synchronizing';
                     $fstatus['total'] = $total;
                     $fstatus['done'] = $total - $todo;
                     $fstatus['todo'] = $todo;
                     $device->SetFolderSyncStatus($folderid, $fstatus);
                 }
             }
         } catch (StateInvalidException $sive) {
             ZLog::Write(LOGLEVEL_WARN, sprintf("ZPushAdmin::GetDeviceDetails(): device '%s' of user '%s' has invalid states. Please sync to solve this issue.", $devid, $user));
             $device->SetDeviceError("Invalid states. Please force synchronization!");
         }
         return $device;
     } catch (StateNotFoundException $e) {
         ZLog::Write(LOGLEVEL_ERROR, sprintf("ZPushAdmin::GetDeviceDetails(): device '%s' of user '%s' can not be found", $devid, $user));
         return false;
     }
 }
示例#9
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;
 }
示例#10
0
 /**
  * Handles the GetItemEstimate command
  * Returns an estimation of how many items will be synchronized at the next sync
  * This is mostly used to show something in the progress bar
  *
  * @param int       $commandCode
  *
  * @access public
  * @return boolean
  */
 public function Handle($commandCode)
 {
     $sc = new SyncCollections();
     if (!self::$decoder->getElementStartTag(SYNC_GETITEMESTIMATE_GETITEMESTIMATE)) {
         return false;
     }
     if (!self::$decoder->getElementStartTag(SYNC_GETITEMESTIMATE_FOLDERS)) {
         return false;
     }
     while (self::$decoder->getElementStartTag(SYNC_GETITEMESTIMATE_FOLDER)) {
         $spa = new SyncParameters();
         $spastatus = false;
         // read the folder properties
         WBXMLDecoder::ResetInWhile("getItemEstimateFolders");
         while (WBXMLDecoder::InWhile("getItemEstimateFolders")) {
             if (self::$decoder->getElementStartTag(SYNC_SYNCKEY)) {
                 try {
                     $spa->SetSyncKey(self::$decoder->getElementContent());
                 } catch (StateInvalidException $siex) {
                     $spastatus = SYNC_GETITEMESTSTATUS_SYNCSTATENOTPRIMED;
                 }
                 if (!self::$decoder->getElementEndTag()) {
                     return false;
                 }
             } elseif (self::$decoder->getElementStartTag(SYNC_GETITEMESTIMATE_FOLDERID)) {
                 $fid = self::$decoder->getElementContent();
                 $spa->SetFolderId($fid);
                 $spa->SetBackendFolderId(self::$deviceManager->GetBackendIdForFolderId($fid));
                 if (!self::$decoder->getElementEndTag()) {
                     return false;
                 }
             } elseif (self::$decoder->getElementStartTag(SYNC_CONVERSATIONMODE)) {
                 $spa->SetConversationMode(true);
                 if (($conversationmode = self::$decoder->getElementContent()) !== false) {
                     $spa->SetConversationMode((bool) $conversationmode);
                     if (!self::$decoder->getElementEndTag()) {
                         return false;
                     }
                 }
             } elseif (self::$decoder->getElementStartTag(SYNC_GETITEMESTIMATE_FOLDERTYPE)) {
                 $spa->SetContentClass(self::$decoder->getElementContent());
                 if (!self::$decoder->getElementEndTag()) {
                     return false;
                 }
             } elseif (self::$decoder->getElementStartTag(SYNC_FILTERTYPE)) {
                 $spa->SetFilterType(self::$decoder->getElementContent());
                 if (!self::$decoder->getElementEndTag()) {
                     return false;
                 }
             }
             while (self::$decoder->getElementStartTag(SYNC_OPTIONS)) {
                 WBXMLDecoder::ResetInWhile("getItemEstimateOptions");
                 while (WBXMLDecoder::InWhile("getItemEstimateOptions")) {
                     $firstOption = true;
                     // foldertype definition
                     if (self::$decoder->getElementStartTag(SYNC_FOLDERTYPE)) {
                         $foldertype = self::$decoder->getElementContent();
                         ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleGetItemEstimate(): specified options block with foldertype '%s'", $foldertype));
                         // switch the foldertype for the next options
                         $spa->UseCPO($foldertype);
                         // set to synchronize all changes. The mobile could overwrite this value
                         $spa->SetFilterType(SYNC_FILTERTYPE_ALL);
                         if (!self::$decoder->getElementEndTag()) {
                             return false;
                         }
                     } else {
                         if ($firstOption) {
                             $spa->UseCPO();
                             // set to synchronize all changes. The mobile could overwrite this value
                             $spa->SetFilterType(SYNC_FILTERTYPE_ALL);
                         }
                     }
                     $firstOption = false;
                     if (self::$decoder->getElementStartTag(SYNC_FILTERTYPE)) {
                         $spa->SetFilterType(self::$decoder->getElementContent());
                         if (!self::$decoder->getElementEndTag()) {
                             return false;
                         }
                     }
                     if (self::$decoder->getElementStartTag(SYNC_MAXITEMS)) {
                         $spa->SetWindowSize($maxitems = self::$decoder->getElementContent());
                         if (!self::$decoder->getElementEndTag()) {
                             return false;
                         }
                     }
                     $e = self::$decoder->peek();
                     if ($e[EN_TYPE] == EN_TYPE_ENDTAG) {
                         self::$decoder->getElementEndTag();
                         break;
                     }
                 }
             }
             $e = self::$decoder->peek();
             if ($e[EN_TYPE] == EN_TYPE_ENDTAG) {
                 self::$decoder->getElementEndTag();
                 //SYNC_GETITEMESTIMATE_FOLDER
                 break;
             }
         }
         // Process folder data
         //In AS 14 request only collectionid is sent, without class
         if (!$spa->HasContentClass() && $spa->HasFolderId()) {
             try {
                 $spa->SetContentClass(self::$deviceManager->GetFolderClassFromCacheByID($spa->GetFolderId()));
             } catch (NoHierarchyCacheAvailableException $nhca) {
                 $spastatus = SYNC_GETITEMESTSTATUS_COLLECTIONINVALID;
             }
         }
         // compatibility mode AS 1.0 - get folderid which was sent during GetHierarchy()
         if (!$spa->HasFolderId() && $spa->HasContentClass()) {
             $spa->SetFolderId(self::$deviceManager->GetFolderIdFromCacheByClass($spa->GetContentClass()));
         }
         // Add collection to SC and load state
         $sc->AddCollection($spa);
         if ($spastatus) {
             // the CPO has a folder id now, so we can set the status
             $sc->AddParameter($spa, "status", $spastatus);
         } else {
             try {
                 $sc->AddParameter($spa, "state", self::$deviceManager->GetStateManager()->GetSyncState($spa->GetSyncKey()));
                 // if this is an additional folder the backend has to be setup correctly
                 if (!self::$backend->Setup(ZPush::GetAdditionalSyncFolderStore($spa->GetBackendFolderId()))) {
                     throw new StatusException(sprintf("HandleGetItemEstimate() could not Setup() the backend for folder id %s/%s", $spa->GetFolderId(), $spa->GetBackendFolderId()), SYNC_GETITEMESTSTATUS_COLLECTIONINVALID);
                 }
             } catch (StateNotFoundException $snfex) {
                 // ok, the key is invalid. Question is, if the hierarchycache is still ok
                 //if not, we have to issue SYNC_GETITEMESTSTATUS_COLLECTIONINVALID which triggers a FolderSync
                 try {
                     self::$deviceManager->GetFolderClassFromCacheByID($spa->GetFolderId());
                     // we got here, so the HierarchyCache is ok
                     $sc->AddParameter($spa, "status", SYNC_GETITEMESTSTATUS_SYNCKKEYINVALID);
                 } catch (NoHierarchyCacheAvailableException $nhca) {
                     $sc->AddParameter($spa, "status", SYNC_GETITEMESTSTATUS_COLLECTIONINVALID);
                 }
                 self::$topCollector->AnnounceInformation("StateNotFoundException " . $sc->GetParameter($spa, "status"), true);
             } catch (StatusException $stex) {
                 if ($stex->getCode() == SYNC_GETITEMESTSTATUS_COLLECTIONINVALID) {
                     $sc->AddParameter($spa, "status", SYNC_GETITEMESTSTATUS_COLLECTIONINVALID);
                 } else {
                     $sc->AddParameter($spa, "status", SYNC_GETITEMESTSTATUS_SYNCSTATENOTPRIMED);
                 }
                 self::$topCollector->AnnounceInformation("StatusException " . $sc->GetParameter($spa, "status"), true);
             }
         }
     }
     if (!self::$decoder->getElementEndTag()) {
         return false;
     }
     //SYNC_GETITEMESTIMATE_FOLDERS
     if (!self::$decoder->getElementEndTag()) {
         return false;
     }
     //SYNC_GETITEMESTIMATE_GETITEMESTIMATE
     self::$encoder->startWBXML();
     self::$encoder->startTag(SYNC_GETITEMESTIMATE_GETITEMESTIMATE);
     $status = SYNC_GETITEMESTSTATUS_SUCCESS;
     // look for changes in all collections
     try {
         $sc->CountChanges();
     } catch (StatusException $ste) {
         $status = SYNC_GETITEMESTSTATUS_COLLECTIONINVALID;
     }
     $changes = $sc->GetChangedFolderIds();
     foreach ($sc as $folderid => $spa) {
         self::$encoder->startTag(SYNC_GETITEMESTIMATE_RESPONSE);
         if ($sc->GetParameter($spa, "status")) {
             $status = $sc->GetParameter($spa, "status");
         }
         self::$encoder->startTag(SYNC_GETITEMESTIMATE_STATUS);
         self::$encoder->content($status);
         self::$encoder->endTag();
         self::$encoder->startTag(SYNC_GETITEMESTIMATE_FOLDER);
         self::$encoder->startTag(SYNC_GETITEMESTIMATE_FOLDERTYPE);
         self::$encoder->content($spa->GetContentClass());
         self::$encoder->endTag();
         self::$encoder->startTag(SYNC_GETITEMESTIMATE_FOLDERID);
         self::$encoder->content($spa->GetFolderId());
         self::$encoder->endTag();
         if (isset($changes[$folderid]) && $changes[$folderid] !== false) {
             self::$encoder->startTag(SYNC_GETITEMESTIMATE_ESTIMATE);
             self::$encoder->content($changes[$folderid]);
             self::$encoder->endTag();
             if ($changes[$folderid] > 0) {
                 self::$topCollector->AnnounceInformation(sprintf("%s %d changes", $spa->GetContentClass(), $changes[$folderid]), true);
             }
             // update the device data to mark folders as complete when synching with WM
             if ($changes[$folderid] == 0) {
                 self::$deviceManager->SetFolderSyncStatus($folderid, DeviceManager::FLD_SYNC_COMPLETED);
             }
         }
         self::$encoder->endTag();
         self::$encoder->endTag();
     }
     if (array_sum($changes) == 0) {
         self::$topCollector->AnnounceInformation("No changes found", true);
     }
     self::$encoder->endTag();
     return true;
 }
示例#11
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;
 }