Projects
Kolab:16:Testing
kolab-syncroton
Log In
Username
Password
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
Expand all
Collapse all
Changes of Revision 59
View file
kolab-syncroton.spec
Changed
@@ -43,7 +43,7 @@ %global upstream_version 2.4.2 Name: kolab-syncroton -Version: 2.4.2.31 +Version: 2.4.2.32 Release: 1%{?dist} Summary: ActiveSync for Kolab Groupware
View file
debian.changelog
Changed
@@ -1,4 +1,4 @@ -kolab-syncroton (2.4.2.31-0~kolab1) unstable; urgency=low +kolab-syncroton (2.4.2.32-0~kolab1) unstable; urgency=low * Release version 2.4.2
View file
kolab-syncroton-2.4.2.tar.gz/bin/analyzelogs.php
Added
@@ -0,0 +1,141 @@ +#!/usr/bin/env php +<?php +/* + +--------------------------------------------------------------------------+ + | Kolab Sync (ActiveSync for Kolab) | + | | + | Copyright (C) 2024, Apheleia IT AG <contact@apheleia-it.ch> | + | | + | This program is free software: you can redistribute it and/or modify | + | it under the terms of the GNU Affero General Public License as published | + | by the Free Software Foundation, either version 3 of the License, or | + | (at your option) any later version. | + | | + | This program is distributed in the hope that it will be useful, | + | but WITHOUT ANY WARRANTY; without even the implied warranty of | + | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | + | GNU Affero General Public License for more details. | + | | + | You should have received a copy of the GNU Affero General Public License | + | along with this program. If not, see <http://www.gnu.org/licenses/> | + +--------------------------------------------------------------------------+ + | Author: Christian Mollekopf <mollekopf@apheleia-it.ch> | + +--------------------------------------------------------------------------+ +*/ + + +define('RCUBE_INSTALL_PATH', realpath(dirname(__FILE__) . '/../') . '/'); + +// Define include path +$include_path = RCUBE_INSTALL_PATH . 'lib' . PATH_SEPARATOR; +$include_path .= RCUBE_INSTALL_PATH . 'lib/ext' . PATH_SEPARATOR; +$include_path .= ini_get('include_path'); +set_include_path($include_path); + +require_once "Syncroton/Command/ICommand.php"; +require_once "Syncroton/Command/Wbxml.php"; +require_once "Syncroton/Command/Sync.php"; +require_once "Syncroton/Command/Ping.php"; +require_once "Syncroton/Command/MoveItems.php"; + +$filename = $argv1; + +$content = file_get_contents($filename); + +// Split up the log files into chunks that hopefully match the commands +$parts = preg_split("/\.*\: " . preg_quote("DEBUG Syncroton_Server::handle::65 REQUEST METHOD: POST", '/') . "/", $content, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE); + +function getStatusConstants($classname) { + $reflect = new ReflectionClass($classname); + $result = $reflect->getConstants(); + $result = array_filter($result, function ($val) { + return str_starts_with($val, "STATUS_"); + }, ARRAY_FILTER_USE_KEY); + $result = array_flip($result); + return $result; +} + +function explainStatus($command, $status) { + switch ($command) { + case "Ping": + $result = getStatusConstants("Syncroton_Command_Ping"); + return $result$status ?? "Unknown"; + case "Sync": + $result = getStatusConstants("Syncroton_Command_Sync"); + return $result$status ?? "Unknown"; + case "MoveItems": + $result = getStatusConstants("Syncroton_Command_MoveItems"); + return $result$status ?? "Unknown"; + } + return "Unkonwn"; +} + +foreach ($parts as $part) { + preg_match('/\(.*)\: /', $part, $matches); + $timestamp = $matches1; + + preg_match('/\command\ => (.*)/', $part, $matches); + $command = $matches1; + + preg_match('/\<Status\>(.*)\<\/Status\>/', $part, $matches); + $status = $matches1 ?? null; + + $statusExplained = explainStatus($command, $status); + + print(" Command: ". str_pad($command, 10) . str_pad("\tStatus: $status ($statusExplained)", 45) . "\tTimestamp: $timestamp\n"); + if ($command == "Sync") { + // Find collections within this sync + // 25-Sep-2024 09:16:35.347730: INFO Syncroton_Command_Sync::handle::221 SyncKey is 7301 Class: Email CollectionId: 38b950ebd62cd9a66929c89615d0fc04 + if (preg_match_all('/SyncKey is (.*) Class: (.*) CollectionId: (.*)/', $part, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER)) { + foreach ($matches as $set) { + $offset = $set01; + $collectionId = $set30; + $class = $set20; + $synckey = $set10; + print(str_pad(" Collection: $collectionId ($class)", 58) . "\tSyncKey: $synckey\n"); + + //Find the offset for this collections messages + if (preg_match("/Processing $collectionId\.\.\./", $part, $match, PREG_OFFSET_CAPTURE, $offset)) { + // print_r($match); + $offset = $match01 ?? null; + //Find the actual changes + if ($offset && preg_match('/found \(added\/changed\/deleted\) (.*)\/(.*)\/(.*) entries for sync from server to client/', $part, $changesMatch, PREG_OFFSET_CAPTURE, $offset)) { + // If the offset is too large we are looking at the next collection. + if ($changesMatch01 - $offset < 200) { + print(" " . $changesMatch00 . "\n"); + } + } + } + //TODO We could figure out what the diff per collection was in terms of synckey to the last sync + //TODO We could figure out what we actually return in the response compared to the detected changeset + //TODO Warn if a collection is repeatedly synced with the same synckey, but changes are detected. It may be stuck in a sync loop. + } + } + + // Detect entries that are being added from the client + if (preg_match_all('/found (.*) entries to be added on server/', $part, $matches)) { + foreach ($matches0 ?? as $match) { + print(" " . $match. "\n"); + } + } + + if (preg_match_all('/found (.*) entries to be updated on server/', $part, $matches)) { + foreach ($matches0 ?? as $match) { + print(" " . $match. "\n"); + } + } + + if (preg_match_all('/found (.*) entries to be deleted on server/', $part, $matches)) { + foreach ($matches0 ?? as $match) { + print(" " . $match. "\n"); + } + } + } + //TODO on Sync: + //* number of Add/Change/Remove from client and from server + //* Synckey + //* list involved folders + //TODO on Sync: + //* Reason for interruption +} +
View file
kolab-syncroton-2.4.2.tar.gz/bin/inspect.php
Changed
@@ -41,6 +41,38 @@ // include global functions from Roundcube Framework require_once 'Roundcube/bootstrap.php'; + +function filterTypeToIMAPSearch($filter_type = 0) +{ + switch ($filter_type) { + case 1: + $mod = '-1 day'; + break; + case 2: + $mod = '-3 days'; + break; + case 3: + $mod = '-1 week'; + break; + case 4: + $mod = '-2 weeks'; + break; + case 5: + $mod = '-1 month'; + break; + } + + if (!empty($mod)) { + $dt = new DateTime('now', new DateTimeZone('UTC')); + $dt->modify($mod); + // RFC3501: IMAP SEARCH + return 'SINCE ' . $dt->format('d-M-Y'); + } + + return ""; +} + + $opts = rcube_utils::get_opt( 'e' => 'email', 'p' => 'adminpassword', @@ -163,17 +195,23 @@ $result$device_id'folders'$folder'id' = "counter" => $data'counter', "lastsync" => $data'lastsync', - "lastfiltertype" => $data'lastfiltertype' ?? null, "modseq" => $data'extra_data' ? json_decode($data'extra_data')->modseq : null, ; } $result$device_id'folders'$folder'id''name' = $folder'displayname'; + $result$device_id'folders'$folder'id''class' = $folder'class'; + $result$device_id'folders'$folder'id''lastfiltertype' = $folder'lastfiltertype' ?? null; $imap->select($folder'displayname'); $result$device_id'folders'$folder'id''imapModseq' = $imap->data'HIGHESTMODSEQ' ?? null; - $index = $imap->search($folder'displayname', 'ALL UNDELETED', false, 'COUNT'); + $index = $imap->search( + $folder'displayname', + 'ALL UNDELETED ' . filterTypeToIMAPSearch($folder'lastfiltertype'), + false, + 'COUNT' + ); if (!$index->is_error()) { $result$device_id'folders'$folder'id''imapMessagecount' = $index->count(); } @@ -200,21 +238,44 @@ } function filterType($value) { - if (!$value) { - return "No filter"; - } - switch($value) { - case 0: return "No filter"; - case 1: return "1 day"; - case 2: return "3 days"; - case 3: return "1 week"; - case 4: return "2 weeks"; - case 5: return "1 month"; - case 6: return "3 months"; - case 7: return "6 months"; - case 8: return "Filter by incomplete tasks"; - } - return "Unknown value: $value"; + if (!$value) { + return "No filter"; + } + switch($value) { + case 0: return "No filter"; + case 1: return "1 day"; + case 2: return "3 days"; + case 3: return "1 week"; + case 4: return "2 weeks"; + case 5: return "1 month"; + case 6: return "3 months (WARNING: not implemented)"; + case 7: return "6 months (WARNING: not implemented)"; + case 8: return "Filter by incomplete tasks"; + } + return "Unknown value: $value"; +} + +function getContentUids($db, $device_id, $folder_id) { + $contentSelect = $db->query( + "SELECT contentid FROM `syncroton_content`" + . " WHERE `device_id` = ? AND `folder_id` = ? AND `is_deleted` = 0", + $device_id, $folder_id + ); + + $contentUids = ; + while ($content = $db->fetch_assoc($contentSelect)) { + $contentUids = explode('::', $content'contentid')1; + } + return $contentUids; +} + +function getImapUids($imap, $folder, $lastfiltertype) { + $imap->select($folder); + $index = $imap->search($folder, 'ALL UNDELETED ' . filterTypeToIMAPSearch($lastfiltertype), true); + if (!$index->is_error()) { + return $index->get(); + } + return ; } println(""); @@ -239,6 +300,39 @@ println(" Last sync: " . ($folder'lastsync' ?? "None")); println(" Number of syncs: " . ($folder'counter' ?? "None")); println(" Filter type: " . filterType($folder'lastfiltertype' ?? null)); + + if (($folder'class' == "Email") && ($folder'counter' ?? false) && $messageCount != $totalCount && ($modseq == "none" || $modseq == $imapModseq)) { + if (($folder'lastfiltertype' ?? false) && $messageCount > $totalCount) { + // This doesn't have to indicate an issue, since the timewindow of the filter wanders, so some messages that have been synchronized may no longer match the window. + } else { + println(" Issue Detected: The sync state seems to be inconsistent. The device should be fully synced, but the sync counts differ."); + println(" There are $messageCount ContentParts (should match number of messages on the device), but $totalCount messages in IMAP matching the filter."); + + $contentUids = getContentUids($db, $deviceId, $folderId); + $imapUids = getImapUids($imap, $folder'name', $folder'lastfiltertype' ?? null); + + $entries = array_diff($imapUids, $contentUids); + if (!empty($entries)) { + println(" The following messages are on the server, but not the device:"); + foreach ($entries as $uid) { + println(" $uid"); + //TODO get details from imap? + } + } + + $entries = array_diff($contentUids, $imapUids); + if (!empty($entries)) { + println(" The following messages are on the device, but not the server:"); + foreach ($entries as $uid) { + println(" $uid"); + //TODO get details from the content part? + //TODO display creation_synckey? + } + } + println(""); + } + } + println(""); } }
View file
kolab-syncroton-2.4.2.tar.gz/lib/ext/Syncroton/Command/MoveItems.php
Changed
@@ -59,11 +59,11 @@ $response = $moves->appendChild($this->_outputDom->createElementNS('uri:Move', 'Response')); $response->appendChild($this->_outputDom->createElementNS('uri:Move', 'SrcMsgId', $move'srcMsgId')); - try { - if ($move'srcFldId' === $move'dstFldId') { - throw new Syncroton_Exception_Status_MoveItems(Syncroton_Exception_Status_MoveItems::SAME_FOLDER); - } + if ($this->_logger instanceof Zend_Log) { + $this->_logger->info(__METHOD__ . '::' . __LINE__ . " Moving from " . $move'srcFldId' . " to " . $move'dstFldId'); + } + try { try { $sourceFolder = $this->_folderBackend->getFolder($this->_device, $move'srcFldId'); } catch (Syncroton_Exception_NotFound $e) { @@ -76,19 +76,30 @@ throw new Syncroton_Exception_Status_MoveItems(Syncroton_Exception_Status_MoveItems::INVALID_DESTINATION); } + if ($move'srcFldId' === $move'dstFldId') { + throw new Syncroton_Exception_Status_MoveItems(Syncroton_Exception_Status_MoveItems::SAME_FOLDER); + } + $dataController = Syncroton_Data_Factory::factory($sourceFolder->class, $this->_device, $this->_syncTimeStamp); $newId = $dataController->moveItem($move'srcFldId', $move'srcMsgId', $move'dstFldId'); - if (!$newId) { + // We don't actually know what the reason was that this failed, but from the resolution description this error seems to make the most sense, + // and we rule out most other reasons before. throw new Syncroton_Exception_Status_MoveItems(Syncroton_Exception_Status_MoveItems::INVALID_SOURCE); } $response->appendChild($this->_outputDom->createElementNS('uri:Move', 'Status', Syncroton_Command_MoveItems::STATUS_SUCCESS)); $response->appendChild($this->_outputDom->createElementNS('uri:Move', 'DstMsgId', $newId)); - } catch (Syncroton_Exception_Status $e) { + } catch (Syncroton_Exception_Status_MoveItems $e) { + if ($this->_logger instanceof Zend_Log) { + $this->_logger->warn(__METHOD__ . '::' . __LINE__ . " Move failed: " . $e->getMessage()); + } $response->appendChild($this->_outputDom->createElementNS('uri:Move', 'Status', $e->getCode())); } catch (Exception $e) { - $response->appendChild($this->_outputDom->createElementNS('uri:Move', 'Status', Syncroton_Exception_Status::SERVER_ERROR)); + if ($this->_logger instanceof Zend_Log) { + $this->_logger->warn(__METHOD__ . '::' . __LINE__ . " Move failed: " . $e->getMessage()); + } + $response->appendChild($this->_outputDom->createElementNS('uri:Move', 'Status', Syncroton_Exception_Status_MoveItems::FOLDER_LOCKED)); } }
View file
kolab-syncroton-2.4.2.tar.gz/lib/ext/Syncroton/Command/Sync.php
Changed
@@ -467,6 +467,93 @@ } } + private function getServerModifications($dataController, $collectionData, $clientModifications) + { + $serverModifications = + 'added' => , + 'changed' => , + 'deleted' => , + ; + + // We first use hasChanges because it has a fast path for when there are no changes by fetching the count of messages only. + // However, in all other cases we will end up fetching the same entries as below, which is less than ideal. + // TODO: We should create a new method, which checks if there are no changes, and otherwise just let the code below figure out + // if there are any changes to process. + if (!$dataController->hasChanges($this->_contentStateBackend, $collectionData->folder, $collectionData->syncState)) { + return $serverModifications; + } + + // update _syncTimeStamp as $dataController->hasChanges might have spent some time + $this->_syncTimeStamp = new DateTime('now', new DateTimeZone('UTC')); + + // fetch entries added since last sync + $allClientEntries = $this->_contentStateBackend->getFolderState( + $this->_device, + $collectionData->folder, + $collectionData->syncState->counter + ); + + // fetch entries changed since last sync + $allChangedEntries = $dataController->getChangedEntries( + $collectionData->collectionId, + $collectionData->syncState, + $collectionData->options'filterType' + ); + + // fetch all entries + $allServerEntries = $dataController->getServerEntries( + $collectionData->collectionId, + $collectionData->options'filterType' + ); + + // add entries + $serverDiff = array_diff($allServerEntries, $allClientEntries); + // add entries which produced problems during delete from client + $serverModifications'added' = $clientModifications'forceAdd'; + // add entries not yet sent to client + $serverModifications'added' = array_unique(array_merge($serverModifications'added', $serverDiff)); + + // @todo still needed? + foreach($serverModifications'added' as $id => $serverId) { + // skip entries added by client during this sync session + if(isset($clientModifications'added'$serverId) && !isset($clientModifications'forceAdd'$serverId)) { + if ($this->_logger instanceof Zend_Log) { + $this->_logger->info(__METHOD__ . '::' . __LINE__ . " skipped added entry: " . $serverId); + } + unset($serverModifications'added'$id); + } + } + + // entries to be deleted + $serverModifications'deleted' = array_diff($allClientEntries, $allServerEntries); + + // entries changed since last sync + $serverModifications'changed' = array_merge($allChangedEntries, $clientModifications'forceChange'); + + foreach($serverModifications'changed' as $id => $serverId) { + // skip entry, if it got changed by client during current sync + if(isset($clientModifications'changed'$serverId) && !isset($clientModifications'forceChange'$serverId)) { + if ($this->_logger instanceof Zend_Log) { + $this->_logger->info(__METHOD__ . '::' . __LINE__ . " skipped changed entry: " . $serverId); + } + unset($serverModifications'changed'$id); + } + // skip entry, make sure we don't sent entries already added by client in this request + elseif (isset($clientModifications'added'$serverId) && !isset($clientModifications'forceAdd'$serverId)) { + if ($this->_logger instanceof Zend_Log) { + $this->_logger->info(__METHOD__ . '::' . __LINE__ . " skipped change for added entry: " . $serverId); + } + unset($serverModifications'changed'$id); + } + } + + // entries comeing in scope are already in $serverModifications'added' and do not need to + // be send with $serverCanges + $serverModifications'changed' = array_diff($serverModifications'changed', $serverModifications'added'); + + return $serverModifications; + } + /** * (non-PHPdoc) * @see Syncroton_Command_Wbxml::getResponse() @@ -663,18 +750,17 @@ ; $status = self::STATUS_SUCCESS; - $hasChanges = 0; if ($collectionData->getChanges === true) { // continue sync session? if(is_array($collectionData->syncState->pendingdata)) { $serverModifications = $collectionData->syncState->pendingdata; if ($this->_logger instanceof Zend_Log) { - $this->_logger->info(__METHOD__ . '::' . __LINE__ . " restored from sync state: (added/changed/deleted) " . count($serverModifications'added') . '/' . count($serverModifications'changed') . '/' . count($serverModifications'deleted') . ' entries for sync from server to client'); + $this->_logger->info(__METHOD__ . '::' . __LINE__ . " restored from sync state."); } } else { try { - $hasChanges = $dataController->hasChanges($this->_contentStateBackend, $collectionData->folder, $collectionData->syncState); + $serverModifications = $this->getServerModifications($dataController, $collectionData, $clientModifications); } catch (Syncroton_Exception_NotFound $e) { if ($this->_logger instanceof Zend_Log) { $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " Folder changes checking failed (not found): " . $e->getTraceAsString()); @@ -686,85 +772,6 @@ $this->_logger->crit(__METHOD__ . '::' . __LINE__ . " Folder changes checking failed: " . $e->getMessage()); } - // Prevent from removing client entries when getServerEntries() fails - // @todo: should we break the loop here? - $status = self::STATUS_SERVER_ERROR; - } - } - - if ($hasChanges) { - // update _syncTimeStamp as $dataController->hasChanges might have spent some time - $this->_syncTimeStamp = new DateTime('now', new DateTimeZone('UTC')); - - try { - // fetch entries added since last sync - $allClientEntries = $this->_contentStateBackend->getFolderState( - $this->_device, - $collectionData->folder, - $collectionData->syncState->counter - ); - - // fetch entries changed since last sync - $allChangedEntries = $dataController->getChangedEntries( - $collectionData->collectionId, - $collectionData->syncState, - $collectionData->options'filterType' - ); - - // fetch all entries - $allServerEntries = $dataController->getServerEntries( - $collectionData->collectionId, - $collectionData->options'filterType' - ); - - // add entries - $serverDiff = array_diff($allServerEntries, $allClientEntries); - // add entries which produced problems during delete from client - $serverModifications'added' = $clientModifications'forceAdd'; - // add entries not yet sent to client - $serverModifications'added' = array_unique(array_merge($serverModifications'added', $serverDiff)); - - // @todo still needed? - foreach($serverModifications'added' as $id => $serverId) { - // skip entries added by client during this sync session - if(isset($clientModifications'added'$serverId) && !isset($clientModifications'forceAdd'$serverId)) { - if ($this->_logger instanceof Zend_Log) { - $this->_logger->info(__METHOD__ . '::' . __LINE__ . " skipped added entry: " . $serverId); - } - unset($serverModifications'added'$id); - } - } - - // entries to be deleted - $serverModifications'deleted' = array_diff($allClientEntries, $allServerEntries); - - // entries changed since last sync - $serverModifications'changed' = array_merge($allChangedEntries, $clientModifications'forceChange'); - - foreach($serverModifications'changed' as $id => $serverId) { - // skip entry, if it got changed by client during current sync - if(isset($clientModifications'changed'$serverId) && !isset($clientModifications'forceChange'$serverId)) { - if ($this->_logger instanceof Zend_Log) { - $this->_logger->info(__METHOD__ . '::' . __LINE__ . " skipped changed entry: " . $serverId); - } - unset($serverModifications'changed'$id); - } - // skip entry, make sure we don't sent entries already added by client in this request - elseif (isset($clientModifications'added'$serverId) && !isset($clientModifications'forceAdd'$serverId)) { - if ($this->_logger instanceof Zend_Log) { - $this->_logger->info(__METHOD__ . '::' . __LINE__ . " skipped change for added entry: " . $serverId); - } - unset($serverModifications'changed'$id); - } - } - - // entries comeing in scope are already in $serverModifications'added' and do not need to - // be send with $serverCanges - $serverModifications'changed' = array_diff($serverModifications'changed', $serverModifications'added'); - } catch (Exception $e) { - if ($this->_logger instanceof Zend_Log) { - $this->_logger->crit(__METHOD__ . '::' . __LINE__ . " Folder state checking failed: " . $e->getMessage()); - } if ($this->_logger instanceof Zend_Log) { $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " Folder state checking failed: " . $e->getTraceAsString()); } @@ -773,10 +780,10 @@ // @todo: should we break the loop here? $status = self::STATUS_SERVER_ERROR; } + } - if ($this->_logger instanceof Zend_Log) { - $this->_logger->info(__METHOD__ . '::' . __LINE__ . " found (added/changed/deleted) " . count($serverModifications'added') . '/' . count($serverModifications'changed') . '/' . count($serverModifications'deleted') . ' entries for sync from server to client'); - } + if ($this->_logger instanceof Zend_Log) { + $this->_logger->info(__METHOD__ . '::' . __LINE__ . " found (added/changed/deleted) " . count($serverModifications'added') . '/' . count($serverModifications'changed') . '/' . count($serverModifications'deleted') . ' entries for sync from server to client'); } }
View file
kolab-syncroton-2.4.2.tar.gz/lib/kolab_sync_storage.php
Changed
@@ -1042,6 +1042,12 @@ // Use COPYUID feature (RFC2359) to get the new UID of the copied message if (empty($this->storage->conn->data'COPYUID')) { + // Check if the source item actually exists. Cyrus IMAP reports + // OK on a MOVE with an invalid UID, But COPYUID will be empty. + // This way we only incur the cost of the extra check once the move fails. + if (!$this->storage->get_message_headers($uid, $src_name)) { + throw new Syncroton_Exception_Status_MoveItems(Syncroton_Exception_Status_MoveItems::INVALID_SOURCE); + } throw new Syncroton_Exception_Status(Syncroton_Exception_Status::SERVER_ERROR); }
View file
kolab-syncroton-2.4.2.tar.gz/tests/Sync/MoveItemsTest.php
Changed
@@ -134,11 +134,74 @@ $this->assertSame('test sync', $xpath->query("ns:Commands/ns:Add/ns:ApplicationData/Email:Subject", $root)->item(0)->nodeValue); } + public function testInvalidMove() + { + $this->emptyTestFolder('INBOX', 'mail'); + $this->emptyTestFolder('Trash', 'mail'); + $uid = $this->appendMail('INBOX', 'mail.sync1'); + $this->registerDevice(); + $inbox = array_search('INBOX', $this->folders); + $trash = array_search('Trash', $this->folders); + + // Move item that doesn't exist + $request = <<<EOF + <?xml version="1.0" encoding="utf-8"?> + <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/"> + <MoveItems xmlns="uri:Move"> + <Move> + <SrcMsgId>foobar::99999</SrcMsgId> + <SrcFldId>foobar</SrcFldId> + <DstFldId>foobar</DstFldId> + </Move> + </MoveItems> + EOF; + + $response = $this->request($request, 'MoveItems'); + + $this->assertEquals(200, $response->getStatusCode()); + + $dom = $this->fromWbxml($response->getBody()); + $xpath = $this->xpath($dom); + $xpath->registerNamespace('Move', 'uri:Move'); + + $root = $xpath->query("//Move:MoveItems/Move:Response")->item(0); + $this->assertSame('1', $xpath->query("Move:Status", $root)->item(0)->nodeValue); + + // Move item that doesn't exist + $request = <<<EOF + <?xml version="1.0" encoding="utf-8"?> + <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/"> + <MoveItems xmlns="uri:Move"> + <Move> + <SrcMsgId>{$inbox}::99999</SrcMsgId> + <SrcFldId>{$inbox}</SrcFldId> + <DstFldId>{$trash}</DstFldId> + </Move> + </MoveItems> + EOF; + + $response = $this->request($request, 'MoveItems'); + + $this->assertEquals(200, $response->getStatusCode()); + + $dom = $this->fromWbxml($response->getBody()); + $xpath = $this->xpath($dom); + $xpath->registerNamespace('Move', 'uri:Move'); + + $root = $xpath->query("//Move:MoveItems/Move:Response")->item(0); + $this->assertSame('1', $xpath->query("Move:Status", $root)->item(0)->nodeValue); + } + /** * Test moving a contact */ public function testMoveContact() { + if ($this->isStorageDriver('kolab')) { + // The Contacts folder is not available, and consequently appendObject fails + $this->markTestSkipped('This test only works with the DAV backend.'); + } + // Test with multi-folder support enabled self::$deviceType = 'iphone'; @@ -150,6 +213,7 @@ $this->registerDevice(); $srcFolderId = array_search($davFolder, $this->folders); + $this->assertTrue(!empty($srcFolderId)); // Create a contacts folder $folderType = Syncroton_Command_FolderSync::FOLDERTYPE_CONTACT_USER_CREATED; @@ -196,6 +260,8 @@ $response = $this->request($request, 'Sync'); $this->assertEquals(200, $response->getStatusCode()); + $root = $xpath->query("//ns:Sync/ns:Collections/ns:Collection")->item(0); + $this->assertSame('1', $xpath->query("ns:Status", $root)->item(0)->nodeValue); $request = <<<EOF <?xml version="1.0" encoding="utf-8"?> @@ -242,6 +308,7 @@ $xpath = $this->xpath($dom); $root = $xpath->query("//ns:Sync/ns:Collections/ns:Collection")->item(0); + $this->assertSame('1', $xpath->query("ns:Status", $root)->item(0)->nodeValue); $this->assertSame($srcFolderId, $xpath->query("ns:CollectionId", $root)->item(0)->nodeValue); $this->assertSame(1, $xpath->query("ns:Commands/ns:Add", $root)->count()); $this->assertSame('Jane', $xpath->query("ns:Commands/ns:Add/ns:ApplicationData/Contacts:FirstName", $root)->item(0)->nodeValue);
View file
kolab-syncroton-2.4.2.tar.gz/tests/Sync/Sync/InconsistencyTest.php
Added
@@ -0,0 +1,146 @@ +<?php + +namespace Tests\Sync\Sync; + +class InconsistencyTest extends \Tests\SyncTestCase +{ + /** + * Test Sync command + */ + public function testSync() + { + $this->emptyTestFolder('INBOX', 'mail'); + $this->registerDevice(); + + // Append two mail messages + $uid1 = $this->appendMail('INBOX', 'mail.sync1'); + $this->appendMail('INBOX', 'mail.sync2'); + + // Initial sync + $folderId = '38b950ebd62cd9a66929c89615d0fc04'; + $syncKey = 0; + $request = <<<EOF + <?xml version="1.0" encoding="utf-8"?> + <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/"> + <Sync xmlns="uri:AirSync"> + <Collections> + <Collection> + <SyncKey>{$syncKey}</SyncKey> + <CollectionId>{$folderId}</CollectionId> + </Collection> + </Collections> + </Sync> + EOF; + + $response = $this->request($request, 'Sync'); + $this->assertEquals(200, $response->getStatusCode()); + $syncKey++; + + $request = <<<EOF + <?xml version="1.0" encoding="utf-8"?> + <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/"> + <Sync xmlns="uri:AirSync" xmlns:AirSyncBase="uri:AirSyncBase"> + <Collections> + <Collection> + <SyncKey>{$syncKey}</SyncKey> + <CollectionId>{$folderId}</CollectionId> + <DeletesAsMoves>1</DeletesAsMoves> + <GetChanges>1</GetChanges> + <WindowSize>2</WindowSize> + <Options> + <FilterType>0</FilterType> + <Conflict>1</Conflict> + <BodyPreference xmlns="uri:AirSyncBase"> + <Type>2</Type> + <TruncationSize>51200</TruncationSize> + <AllOrNone>0</AllOrNone> + </BodyPreference> + </Options> + </Collection> + </Collections> + </Sync> + EOF; + + $response = $this->request($request, 'Sync'); + + $this->assertEquals(200, $response->getStatusCode()); + + $dom = $this->fromWbxml($response->getBody()); + $xpath = $this->xpath($dom); + + $root = "//ns:Sync/ns:Collections/ns:Collection"; + $this->assertSame('1', $xpath->query("{$root}/ns:Status")->item(0)->nodeValue); + $this->assertSame(strval(++$syncKey), $xpath->query("{$root}/ns:SyncKey")->item(0)->nodeValue); + $this->assertSame($folderId, $xpath->query("{$root}/ns:CollectionId")->item(0)->nodeValue); + $this->assertSame(2, $xpath->query("{$root}/ns:Commands/ns:Add")->count()); + + // Initial sync is complete + // We now artifically create a sync inconsistency be deleting the content part of the first mail. + // This replicates a situation that we've seen, but don't know yet how it was created in the first place. + $rcube = \rcube::get_instance(); + $db = $rcube->get_dbh(); + $result = $db->query( + "DELETE FROM `syncroton_content`" + . " WHERE `contentid` = ?", + "$folderId::$uid1" + ); + $this->assertNull($db->is_error($result)); + + // Now sync again + $request = <<<EOF + <?xml version="1.0" encoding="utf-8"?> + <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/"> + <Sync xmlns="uri:AirSync" xmlns:AirSyncBase="uri:AirSyncBase"> + <Collections> + <Collection> + <SyncKey>{$syncKey}</SyncKey> + <CollectionId>{$folderId}</CollectionId> + <DeletesAsMoves>1</DeletesAsMoves> + <GetChanges>1</GetChanges> + <Options> + <FilterType>0</FilterType> + <Conflict>1</Conflict> + <BodyPreference xmlns="uri:AirSyncBase"> + <Type>2</Type> + <TruncationSize>51200</TruncationSize> + <AllOrNone>0</AllOrNone> + </BodyPreference> + </Options> + </Collection> + </Collections> + </Sync> + EOF; + + $response = $this->request($request, 'Sync'); + + $this->assertEquals(200, $response->getStatusCode()); + + $dom = $this->fromWbxml($response->getBody()); + $xpath = $this->xpath($dom); + + $root = "//ns:Sync/ns:Collections/ns:Collection"; + $this->assertSame('1', $xpath->query("{$root}/ns:Status")->item(0)->nodeValue); + $this->assertSame(strval(++$syncKey), $xpath->query("{$root}/ns:SyncKey")->item(0)->nodeValue); + $this->assertSame($folderId, $xpath->query("{$root}/ns:CollectionId")->item(0)->nodeValue); + $this->assertSame(1, $xpath->query("{$root}/ns:Commands/ns:Add")->count()); + + + //Assert that we have all content parts back + $sync = \kolab_sync::get_instance(); + $device = $sync->storage()->device_get(self::$deviceId); + + $result = $db->query( + "SELECT `contentid` FROM `syncroton_content`" + . " WHERE `device_id` = ?", + $device'ID' + ); + $data = ; + while ($state = $db->fetch_assoc($result)) { + $data = $state; + } + $this->assertSame(2, count($data)); + + return $syncKey; + } + +}
View file
kolab-syncroton-2.4.2.tar.gz/tests/SyncTestCase.php
Changed
@@ -175,6 +175,8 @@ if ($imap->folder_exists($foldername)) { // TODO exit("Not implemented for Kolab v3 storage driver"); + } else { + exit("Folder is missing"); } return;
View file
kolab-syncroton.dsc
Changed
@@ -2,7 +2,7 @@ Source: kolab-syncroton Binary: kolab-syncroton Architecture: all -Version: 1:2.4.2.31-1~kolab1 +Version: 1:2.4.2.32-1~kolab1 Maintainer: Jeroen van Meeuwen <vanmeeuwen@kolabsys.com> Uploaders: Jeroen van Meeuwen <vanmeeuwen@kolabsys.com> Homepage: http://www.kolab.org/
Locations
Projects
Search
Status Monitor
Help
Open Build Service
OBS Manuals
API Documentation
OBS Portal
Reporting a Bug
Contact
Mailing List
Forums
Chat (IRC)
Twitter
Open Build Service (OBS)
is an
openSUSE project
.