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