Changes of Revision 71

kolab-syncroton.spec Changed
x
 
1
@@ -43,7 +43,7 @@
2
 %global upstream_version 2.4.2
3
 
4
 Name:           kolab-syncroton
5
-Version: 2.4.2.31
6
+Version: 2.4.2.34
7
 Release:        1%{?dist}
8
 Summary:        ActiveSync for Kolab Groupware
9
 
10
debian.changelog Changed
7
 
1
@@ -1,4 +1,4 @@
2
-kolab-syncroton (2.4.2.31-0~kolab1) unstable; urgency=low
3
+kolab-syncroton (2.4.2.34-0~kolab1) unstable; urgency=low
4
 
5
   * Release version 2.4.2
6
 
7
kolab-syncroton-2.4.2.tar.gz/bin/analyzelogs.php Added
151
 
1
@@ -0,0 +1,149 @@
2
+#!/usr/bin/env php
3
+<?php
4
+/*
5
+ +--------------------------------------------------------------------------+
6
+ | Kolab Sync (ActiveSync for Kolab)                                        |
7
+ |                                                                          |
8
+ | Copyright (C) 2024, Apheleia IT AG <contact@apheleia-it.ch>         |
9
+ |                                                                          |
10
+ | This program is free software: you can redistribute it and/or modify     |
11
+ | it under the terms of the GNU Affero General Public License as published |
12
+ | by the Free Software Foundation, either version 3 of the License, or     |
13
+ | (at your option) any later version.                                      |
14
+ |                                                                          |
15
+ | This program is distributed in the hope that it will be useful,          |
16
+ | but WITHOUT ANY WARRANTY; without even the implied warranty of           |
17
+ | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the             |
18
+ | GNU Affero General Public License for more details.                      |
19
+ |                                                                          |
20
+ | You should have received a copy of the GNU Affero General Public License |
21
+ | along with this program. If not, see <http://www.gnu.org/licenses/>      |
22
+ +--------------------------------------------------------------------------+
23
+ | Author: Christian Mollekopf <mollekopf@apheleia-it.ch>                      |
24
+ +--------------------------------------------------------------------------+
25
+*/
26
+
27
+
28
+define('RCUBE_INSTALL_PATH', realpath(dirname(__FILE__) . '/../') . '/');
29
+
30
+// Define include path
31
+$include_path  = RCUBE_INSTALL_PATH . 'lib' . PATH_SEPARATOR;
32
+$include_path .= RCUBE_INSTALL_PATH . 'lib/ext' . PATH_SEPARATOR;
33
+$include_path .= ini_get('include_path');
34
+set_include_path($include_path);
35
+
36
+require_once "Syncroton/Command/ICommand.php";
37
+require_once "Syncroton/Command/Wbxml.php";
38
+require_once "Syncroton/Command/Sync.php";
39
+require_once "Syncroton/Command/Ping.php";
40
+require_once "Syncroton/Command/MoveItems.php";
41
+require_once "Syncroton/Command/FolderSync.php";
42
+
43
+$filename = $argv1;
44
+
45
+$content = file_get_contents($filename);
46
+
47
+// Split up the log files into chunks that hopefully match the commands
48
+$parts = preg_split("/\.*\: " . preg_quote("DEBUG Syncroton_Server::handle::65 REQUEST METHOD: POST", '/') . "/", $content, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
49
+
50
+function getStatusConstants($classname)
51
+{
52
+    $reflect = new ReflectionClass($classname);
53
+    $result = $reflect->getConstants();
54
+    $result = array_filter($result, function ($val) {
55
+        return str_starts_with($val, "STATUS_");
56
+    }, ARRAY_FILTER_USE_KEY);
57
+    $result = array_flip($result);
58
+    return $result;
59
+}
60
+
61
+function explainStatus($command, $status)
62
+{
63
+    if (!$status) {
64
+        return "none";
65
+    }
66
+    switch ($command) {
67
+        case "Ping":
68
+            $result = getStatusConstants("Syncroton_Command_Ping");
69
+            return $result$status ?? "Unknown";
70
+        case "Sync":
71
+            $result = getStatusConstants("Syncroton_Command_Sync");
72
+            return $result$status ?? "Unknown";
73
+        case "MoveItems":
74
+            $result = getStatusConstants("Syncroton_Command_MoveItems");
75
+            return $result$status ?? "Unknown";
76
+        case "FolderSync":
77
+            $result = getStatusConstants("Syncroton_Command_FolderSync");
78
+            return $result$status ?? "Unknown";
79
+    }
80
+    return "Unknown command";
81
+}
82
+
83
+foreach ($parts as $part) {
84
+    preg_match('/\(.*)\: /', $part, $matches);
85
+    $timestamp = $matches1;
86
+
87
+    preg_match('/\command\ => (.*)/', $part, $matches);
88
+    $command = $matches1;
89
+
90
+    preg_match('/\<Status\>(.*)\<\/Status\>/', $part, $matches);
91
+    $status = $matches1 ?? null;
92
+
93
+    $statusExplained = explainStatus($command, $status);
94
+
95
+    print(" Command: " . str_pad($command, 10) . str_pad("\tStatus: $status ($statusExplained)", 45) . "\tTimestamp: $timestamp\n");
96
+    if ($command == "Sync") {
97
+        // Find collections within this sync
98
+        // 25-Sep-2024 09:16:35.347730: INFO Syncroton_Command_Sync::handle::221 SyncKey is 7301 Class: Email CollectionId: 38b950ebd62cd9a66929c89615d0fc04
99
+        if (preg_match_all('/SyncKey is (.*) Class: (.*) CollectionId: (.*)/', $part, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER)) {
100
+            foreach ($matches as $set) {
101
+                $offset = $set01;
102
+                $collectionId = $set30;
103
+                $class = $set20;
104
+                $synckey = $set10;
105
+                print(str_pad("  Collection: $collectionId ($class)", 58) . "\tSyncKey: $synckey\n");
106
+
107
+                //Find the offset for this collections messages
108
+                if (preg_match("/Processing $collectionId\.\.\./", $part, $match, PREG_OFFSET_CAPTURE, $offset)) {
109
+                    // print_r($match);
110
+                    $offset = $match01 ?? null;
111
+                    //Find the actual changes
112
+                    if ($offset && preg_match('/found \(added\/changed\/deleted\) (.*)\/(.*)\/(.*) entries for sync from server to client/', $part, $changesMatch, PREG_OFFSET_CAPTURE, $offset)) {
113
+                        // If the offset is too large we are looking at the next collection.
114
+                        if ($changesMatch01 - $offset < 200) {
115
+                            print("    " . $changesMatch00 . "\n");
116
+                        }
117
+                    }
118
+                }
119
+                //TODO We could figure out what the diff per collection was in terms of synckey to the last sync
120
+                //TODO We could figure out what we actually return in the response compared to the detected changeset
121
+                //TODO Warn if a collection is repeatedly synced with the same synckey, but changes are detected. It may be stuck in a sync loop.
122
+            }
123
+        }
124
+
125
+        // Detect entries that are being added from the client
126
+        if (preg_match_all('/found (.*) entries to be added on server/', $part, $matches)) {
127
+            foreach ($matches0 ??  as $match) {
128
+                print("  " . $match . "\n");
129
+            }
130
+        }
131
+
132
+        if (preg_match_all('/found (.*) entries to be updated on server/', $part, $matches)) {
133
+            foreach ($matches0 ??  as $match) {
134
+                print("  " . $match . "\n");
135
+            }
136
+        }
137
+
138
+        if (preg_match_all('/found (.*) entries to be deleted on server/', $part, $matches)) {
139
+            foreach ($matches0 ??  as $match) {
140
+                print("  " . $match . "\n");
141
+            }
142
+        }
143
+    }
144
+    //TODO on Sync:
145
+    //* number of Add/Change/Remove from client and from server
146
+    //* Synckey
147
+    //* list involved folders
148
+    //TODO on Sync:
149
+    //* Reason for interruption
150
+}
151
kolab-syncroton-2.4.2.tar.gz/bin/inspect.php Changed
189
 
1
@@ -41,6 +41,38 @@
2
 // include global functions from Roundcube Framework
3
 require_once 'Roundcube/bootstrap.php';
4
 
5
+
6
+function filterTypeToIMAPSearch($filter_type = 0)
7
+{
8
+    switch ($filter_type) {
9
+        case 1:
10
+            $mod = '-1 day';
11
+            break;
12
+        case 2:
13
+            $mod = '-3 days';
14
+            break;
15
+        case 3:
16
+            $mod = '-1 week';
17
+            break;
18
+        case 4:
19
+            $mod = '-2 weeks';
20
+            break;
21
+        case 5:
22
+            $mod = '-1 month';
23
+            break;
24
+    }
25
+
26
+    if (!empty($mod)) {
27
+        $dt = new DateTime('now', new DateTimeZone('UTC'));
28
+        $dt->modify($mod);
29
+        // RFC3501: IMAP SEARCH
30
+        return 'SINCE ' . $dt->format('d-M-Y');
31
+    }
32
+
33
+    return "";
34
+}
35
+
36
+
37
 $opts = rcube_utils::get_opt(
38
     'e' => 'email',
39
     'p' => 'adminpassword',
40
@@ -78,8 +110,8 @@
41
     'ssl' => 
42
         'verify_peer_name' => false,
43
         'verify_peer' => false,
44
-        'allow_self_signed' => true
45
-    
46
+        'allow_self_signed' => true,
47
+    ,
48
 ;
49
 
50
 ini_set('display_errors', 1);
51
@@ -163,17 +195,23 @@
52
             $result$device_id'folders'$folder'id' = 
53
                 "counter" => $data'counter',
54
                 "lastsync" => $data'lastsync',
55
-                "lastfiltertype" => $data'lastfiltertype' ?? null,
56
                 "modseq" => $data'extra_data' ? json_decode($data'extra_data')->modseq : null,
57
             ;
58
         }
59
 
60
         $result$device_id'folders'$folder'id''name' = $folder'displayname';
61
+        $result$device_id'folders'$folder'id''class' = $folder'class';
62
+        $result$device_id'folders'$folder'id''lastfiltertype' = $folder'lastfiltertype' ?? null;
63
 
64
         $imap->select($folder'displayname');
65
         $result$device_id'folders'$folder'id''imapModseq' = $imap->data'HIGHESTMODSEQ' ?? null;
66
 
67
-        $index = $imap->search($folder'displayname', 'ALL UNDELETED', false, 'COUNT');
68
+        $index = $imap->search(
69
+            $folder'displayname',
70
+            'ALL UNDELETED ' . filterTypeToIMAPSearch($folder'lastfiltertype'),
71
+            false,
72
+            'COUNT'
73
+        );
74
         if (!$index->is_error()) {
75
             $result$device_id'folders'$folder'id''imapMessagecount' = $index->count();
76
         }
77
@@ -195,26 +233,54 @@
78
     var_export($result);
79
 }
80
 
81
-function println($output) {
82
+function println($output)
83
+{
84
     print("{$output}\n");
85
 }
86
 
87
-function filterType($value) {
88
-   if (!$value) {
89
-       return "No filter";
90
-   }
91
-   switch($value) {
92
-       case 0: return "No filter";
93
-       case 1: return "1 day";
94
-       case 2: return "3 days";
95
-       case 3: return "1 week";
96
-       case 4: return "2 weeks";
97
-       case 5: return "1 month";
98
-       case 6: return "3 months";
99
-       case 7: return "6 months";
100
-       case 8: return "Filter by incomplete tasks";
101
-   }
102
-   return "Unknown value: $value";
103
+function filterType($value)
104
+{
105
+    if (!$value) {
106
+        return "No filter";
107
+    }
108
+    switch($value) {
109
+        case 0: return "No filter";
110
+        case 1: return "1 day";
111
+        case 2: return "3 days";
112
+        case 3: return "1 week";
113
+        case 4: return "2 weeks";
114
+        case 5: return "1 month";
115
+        case 6: return "3 months (WARNING: not implemented)";
116
+        case 7: return "6 months (WARNING: not implemented)";
117
+        case 8: return "Filter by incomplete tasks";
118
+    }
119
+    return "Unknown value: $value";
120
+}
121
+
122
+function getContentUids($db, $device_id, $folder_id)
123
+{
124
+    $contentSelect = $db->query(
125
+        "SELECT contentid FROM `syncroton_content`"
126
+        . " WHERE `device_id` = ? AND `folder_id` = ? AND `is_deleted` = 0",
127
+        $device_id,
128
+        $folder_id
129
+    );
130
+
131
+    $contentUids = ;
132
+    while ($content = $db->fetch_assoc($contentSelect)) {
133
+        $contentUids = explode('::', $content'contentid')1;
134
+    }
135
+    return $contentUids;
136
+}
137
+
138
+function getImapUids($imap, $folder, $lastfiltertype)
139
+{
140
+    $imap->select($folder);
141
+    $index = $imap->search($folder, 'ALL UNDELETED ' . filterTypeToIMAPSearch($lastfiltertype), true);
142
+    if (!$index->is_error()) {
143
+        return $index->get();
144
+    }
145
+    return ;
146
 }
147
 
148
 println("");
149
@@ -239,6 +305,39 @@
150
         println("    Last sync: " . ($folder'lastsync' ?? "None"));
151
         println("    Number of syncs: " . ($folder'counter' ?? "None"));
152
         println("    Filter type: " . filterType($folder'lastfiltertype' ?? null));
153
+
154
+        if (($folder'class' == "Email") && ($folder'counter' ?? false) && $messageCount != $totalCount && ($modseq == "none" || $modseq == $imapModseq)) {
155
+            if (($folder'lastfiltertype' ?? false) && $messageCount > $totalCount) {
156
+                // 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.
157
+            } else {
158
+                println("    Issue Detected: The sync state seems to be inconsistent. The device should be fully synced, but the sync counts differ.");
159
+                println("    There are $messageCount ContentParts (should match number of messages on the device), but $totalCount messages in IMAP matching the filter.");
160
+
161
+                $contentUids = getContentUids($db, $deviceId, $folderId);
162
+                $imapUids = getImapUids($imap, $folder'name', $folder'lastfiltertype' ?? null);
163
+
164
+                $entries = array_diff($imapUids, $contentUids);
165
+                if (!empty($entries)) {
166
+                    println("       The following messages are on the server, but not the device:");
167
+                    foreach ($entries as $uid) {
168
+                        println("           $uid");
169
+                        //TODO get details from imap?
170
+                    }
171
+                }
172
+
173
+                $entries = array_diff($contentUids, $imapUids);
174
+                if (!empty($entries)) {
175
+                    println("       The following messages are on the device, but not the server:");
176
+                    foreach ($entries as $uid) {
177
+                        println("           $uid");
178
+                        //TODO get details from the content part?
179
+                        //TODO display creation_synckey?
180
+                    }
181
+                }
182
+                println("");
183
+            }
184
+        }
185
+
186
         println("");
187
     }
188
 }
189
kolab-syncroton-2.4.2.tar.gz/bin/resync.php Added
118
 
1
@@ -0,0 +1,116 @@
2
+#!/usr/bin/php
3
+<?php
4
+/*
5
+ +--------------------------------------------------------------------------+
6
+ | Kolab Sync (ActiveSync for Kolab)                                        |
7
+ |                                                                          |
8
+ | Copyright (C) 2024, Apheleia IT AG <contact@apheleia-it.ch>              |
9
+ |                                                                          |
10
+ | This program is free software: you can redistribute it and/or modify     |
11
+ | it under the terms of the GNU Affero General Public License as published |
12
+ | by the Free Software Foundation, either version 3 of the License, or     |
13
+ | (at your option) any later version.                                      |
14
+ |                                                                          |
15
+ | This program is distributed in the hope that it will be useful,          |
16
+ | but WITHOUT ANY WARRANTY; without even the implied warranty of           |
17
+ | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the             |
18
+ | GNU Affero General Public License for more details.                      |
19
+ |                                                                          |
20
+ | You should have received a copy of the GNU Affero General Public License |
21
+ | along with this program. If not, see <http://www.gnu.org/licenses/>      |
22
+ +--------------------------------------------------------------------------+
23
+ | Author: Aleksander Machniak <machniak@apheleia-it.ch>                    |
24
+ +--------------------------------------------------------------------------+
25
+*/
26
+
27
+define('RCUBE_INSTALL_PATH', realpath(dirname(__FILE__) . '/../') . '/');
28
+define('RCUBE_PLUGINS_DIR', RCUBE_INSTALL_PATH . 'lib/plugins/');
29
+
30
+// Define include path
31
+$include_path  = RCUBE_INSTALL_PATH . 'lib' . PATH_SEPARATOR;
32
+$include_path .= RCUBE_INSTALL_PATH . 'lib/ext' . PATH_SEPARATOR;
33
+$include_path .= ini_get('include_path');
34
+set_include_path($include_path);
35
+
36
+// include composer autoloader (if available)
37
+if (@file_exists(RCUBE_INSTALL_PATH . 'vendor/autoload.php')) {
38
+    require RCUBE_INSTALL_PATH . 'vendor/autoload.php';
39
+}
40
+
41
+// include global functions from Roundcube Framework
42
+require_once 'Roundcube/bootstrap.php';
43
+
44
+$opts = rcube_utils::get_opt(
45
+    'o' => 'owner',
46
+    'f' => 'folder',
47
+    'd' => 'deviceid',
48
+    't' => 'devicetype', // e.g. WindowsOutlook15 or iPhone
49
+);
50
+
51
+$rcube = \rcube::get_instance();
52
+$db    = $rcube->get_dbh();
53
+
54
+if (empty($opts'owner')) {
55
+    rcube::raise_error("Owner not specified (--owner).", false, true);
56
+}
57
+if (empty($opts'folder')) {
58
+    rcube::raise_error("Folder name not specified (--folder).", false, true);
59
+}
60
+
61
+$select = $db->query(
62
+    "SELECT `user_id` FROM `users` WHERE `username` = ? ORDER BY `user_id` DESC",
63
+    \strtolower($opts'owner')
64
+);
65
+
66
+if ($data = $db->fetch_assoc($select)) {
67
+    $userid = $data'user_id';
68
+} else {
69
+    rcube::raise_error("User not found in Roundcube database.", false, true);
70
+}
71
+
72
+$devices = ;
73
+if (!empty($opts'deviceid')) {
74
+    $select = $db->query(
75
+        "SELECT `id` FROM `syncroton_device` WHERE `owner_id` = ? AND `deviceid` = ?",
76
+        $userid,
77
+        $opts'deviceid'
78
+    );
79
+    while ($record = $db->fetch_assoc($select)) {
80
+        $devices = $record'id';
81
+    }
82
+} elseif (!empty($opts'devicetype')) {
83
+    $select = $db->query(
84
+        "SELECT `id` FROM `syncroton_device` WHERE `owner_id` = ? AND `devicetype` = ?",
85
+        $userid,
86
+        $opts'devicetype'
87
+    );
88
+    while ($record = $db->fetch_assoc($select)) {
89
+        $devices = $record'id';
90
+    }
91
+} else {
92
+    $select = $db->query("SELECT `id` FROM `syncroton_device` WHERE `owner_id` = ?", $userid);
93
+    while ($record = $db->fetch_assoc($select)) {
94
+        $devices = $record'id';
95
+    }
96
+}
97
+
98
+if (empty($devices)) {
99
+    rcube::raise_error("Device not found.", false, true);
100
+}
101
+
102
+// TODO: Support not only top-level folders
103
+
104
+$select = $db->query(
105
+    "SELECT `id`, `displayname`, `folderid` FROM `syncroton_folder`"
106
+    . " WHERE `device_id` IN (" . $db->array2list($devices) . ")"
107
+    . " AND `parentid` = '0' AND `displayname` = " . $db->quote($opts'folder')
108
+);
109
+
110
+while ($record = $db->fetch_assoc($select)) {
111
+    if (!empty($opts'dry-run')) {
112
+        print("DRY-RUN {$record'displayname'} ({$record'id'}:{$record'folderid'})\n");
113
+    } else {
114
+        $db->query("UPDATE `syncroton_folder` SET `resync` = 1 WHERE id = ?", $record'id');
115
+        print("{$record'displayname'} ({$record'id'}:{$record'folderid'})\n");
116
+    }
117
+}
118
kolab-syncroton-2.4.2.tar.gz/docs/SQL/mysql.initial.sql Changed
15
 
1
@@ -44,6 +44,7 @@
2
     `displayname` varchar(254) NOT NULL,
3
     `type` int(11) NOT NULL,
4
     `creation_time` datetime NOT NULL,
5
+    `creation_synckey` int(11) NOT NULL DEFAULT '0',
6
     `lastfiltertype` int(11) DEFAULT NULL,
7
     `supportedfields` longblob DEFAULT NULL,
8
     PRIMARY KEY (`id`),
9
@@ -115,4 +116,4 @@
10
  PRIMARY KEY(`name`)
11
 ) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
12
 
13
-INSERT INTO `system` (`name`, `value`) VALUES ('syncroton-version', '2024031100');
14
+INSERT INTO `system` (`name`, `value`) VALUES ('syncroton-version', '2024102300');
15
kolab-syncroton-2.4.2.tar.gz/docs/SQL/mysql/2024101700.sql Added
3
 
1
@@ -0,0 +1,1 @@
2
+ALTER TABLE `syncroton_folder` ADD `resync` tinyint(1) DEFAULT NULL;
3
kolab-syncroton-2.4.2.tar.gz/docs/SQL/mysql/2024102300.sql Added
3
 
1
@@ -0,0 +1,1 @@
2
+ALTER TABLE `syncroton_folder` ADD `creation_synckey` int(11) NOT NULL DEFAULT '0';
3
kolab-syncroton-2.4.2.tar.gz/lib/ext/Syncroton/Backend/IFolder.php Changed
13
 
1
@@ -32,9 +32,10 @@
2
      *
3
      * @param  Syncroton_Model_Device|string  $deviceId
4
      * @param  string                         $class
5
+     * @param  int                            $syncKey
6
      * @return array
7
      */
8
-    public function getFolderState($deviceId, $class);
9
+    public function getFolderState($deviceId, $class, $syncKey);
10
 
11
     /**
12
      * delete all stored folderId's for given device
13
kolab-syncroton-2.4.2.tar.gz/lib/ext/Syncroton/Command/FolderCreate.php Changed
9
 
1
@@ -100,6 +100,7 @@
2
                 $this->_folder->class        = $folder->class;
3
                 $this->_folder->deviceId     = $this->_device->id;
4
                 $this->_folder->creationTime = $this->_syncTimeStamp;
5
+                $this->_folder->creationSynckey = $this->_syncState->counter;
6
 
7
                 // Check if the folder already exists to avoid a duplicate insert attempt in db
8
                 try {
9
kolab-syncroton-2.4.2.tar.gz/lib/ext/Syncroton/Command/FolderSync.php Changed
70
 
1
@@ -94,6 +94,9 @@
2
         }
3
 
4
         if (!($this->_syncState = $this->_syncStateBackend->validate($this->_device, 'FolderSync', $syncKey)) instanceof Syncroton_Model_SyncState) {
5
+            if ($this->_logger instanceof Zend_Log) {
6
+                $this->_logger->warn(__METHOD__ . '::' . __LINE__ . " invalidating sync state");
7
+            }
8
             $this->_syncStateBackend->resetState($this->_device, 'FolderSync');
9
         }
10
     }
11
@@ -143,7 +146,7 @@
12
                 $serverFolders = $dataController->getAllFolders();
13
 
14
                 // retrieve all folders sent to client
15
-                $clientFolders = $this->_folderBackend->getFolderState($this->_device, $class);
16
+                $clientFolders = $this->_folderBackend->getFolderState($this->_device, $class, $this->_syncState->counter);
17
 
18
                 if ($this->_syncState->counter > 0) {
19
                     // retrieve all folders changed since last sync
20
@@ -194,6 +197,7 @@
21
                 } else {
22
                     $add = $serverFolders$serverFolderId;
23
                     $add->creationTime = $this->_syncTimeStamp;
24
+                    $add->creationSynckey = $this->_syncState->counter + 1;
25
                     $add->deviceId     = $this->_device->id;
26
                     unset($add->id);
27
                 }
28
@@ -234,6 +238,12 @@
29
                 }
30
             }
31
 
32
+            // Handle folders set for forced re-sync, we'll send a delete action to the client,
33
+            // but because the folder is still existing and subscribed on the backend it should
34
+            // "immediately" be added again (and re-synced).
35
+            $forceDeleteIds = array_keys(array_filter($clientFolders, function ($f) { return !empty($f->resync); }));
36
+            $serverFoldersIds = array_diff($serverFoldersIds, $forceDeleteIds);
37
+
38
             // calculate deleted entries
39
             $serverDiff = array_diff($clientFoldersIds, $serverFoldersIds);
40
             foreach ($serverDiff as $serverFolderId) {
41
@@ -262,7 +272,12 @@
42
 
43
             // store folder in backend
44
             if (empty($folder->id)) {
45
-                $this->_folderBackend->create($folder);
46
+                try {
47
+                    $this->_folderBackend->create($folder);
48
+                } catch(Exception $zdse) {
49
+                    //This can happen if we rerun a previous sync-key
50
+                    $this->_folderBackend->update($folder);
51
+                }
52
             }
53
         }
54
 
55
@@ -281,10 +296,10 @@
56
             $this->_folderBackend->delete($folder);
57
         }
58
 
59
-        if (empty($this->_syncState->id)) {
60
-            $this->_syncStateBackend->create($this->_syncState);
61
-        } else {
62
-            $this->_syncStateBackend->update($this->_syncState);
63
+        // Only create this syncstate if it isn't already existing (which happens if we a sync key is re-sent)
64
+        if (!$this->_syncStateBackend->haveNext($this->_device, 'FolderSync', $this->_syncState->counter - 1)) {
65
+            // Keep previous sync states in case a sync key is re-sent
66
+            $this->_syncStateBackend->create($this->_syncState, true);
67
         }
68
 
69
         return $this->_outputDom;
70
kolab-syncroton-2.4.2.tar.gz/lib/ext/Syncroton/Command/MoveItems.php Changed
51
 
1
@@ -59,11 +59,11 @@
2
             $response = $moves->appendChild($this->_outputDom->createElementNS('uri:Move', 'Response'));
3
             $response->appendChild($this->_outputDom->createElementNS('uri:Move', 'SrcMsgId', $move'srcMsgId'));
4
 
5
-            try {
6
-                if ($move'srcFldId' === $move'dstFldId') {
7
-                    throw new Syncroton_Exception_Status_MoveItems(Syncroton_Exception_Status_MoveItems::SAME_FOLDER);
8
-                }
9
+            if ($this->_logger instanceof Zend_Log) {
10
+                $this->_logger->info(__METHOD__ . '::' . __LINE__ . " Moving from " . $move'srcFldId' . " to " . $move'dstFldId');
11
+            }
12
 
13
+            try {
14
                 try {
15
                     $sourceFolder = $this->_folderBackend->getFolder($this->_device, $move'srcFldId');
16
                 } catch (Syncroton_Exception_NotFound $e) {
17
@@ -76,19 +76,30 @@
18
                     throw new Syncroton_Exception_Status_MoveItems(Syncroton_Exception_Status_MoveItems::INVALID_DESTINATION);
19
                 }
20
 
21
+                if ($move'srcFldId' === $move'dstFldId') {
22
+                    throw new Syncroton_Exception_Status_MoveItems(Syncroton_Exception_Status_MoveItems::SAME_FOLDER);
23
+                }
24
+
25
                 $dataController = Syncroton_Data_Factory::factory($sourceFolder->class, $this->_device, $this->_syncTimeStamp);
26
                 $newId          = $dataController->moveItem($move'srcFldId', $move'srcMsgId', $move'dstFldId');
27
-
28
                 if (!$newId) {
29
+                    // 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,
30
+                    // and we rule out most other reasons before.
31
                     throw new Syncroton_Exception_Status_MoveItems(Syncroton_Exception_Status_MoveItems::INVALID_SOURCE);
32
                 }
33
 
34
                 $response->appendChild($this->_outputDom->createElementNS('uri:Move', 'Status', Syncroton_Command_MoveItems::STATUS_SUCCESS));
35
                 $response->appendChild($this->_outputDom->createElementNS('uri:Move', 'DstMsgId', $newId));
36
-            } catch (Syncroton_Exception_Status $e) {
37
+            } catch (Syncroton_Exception_Status_MoveItems $e) {
38
+                if ($this->_logger instanceof Zend_Log) {
39
+                    $this->_logger->warn(__METHOD__ . '::' . __LINE__ . " Move failed: " . $e->getMessage());
40
+                }
41
                 $response->appendChild($this->_outputDom->createElementNS('uri:Move', 'Status', $e->getCode()));
42
             } catch (Exception $e) {
43
-                $response->appendChild($this->_outputDom->createElementNS('uri:Move', 'Status', Syncroton_Exception_Status::SERVER_ERROR));
44
+                if ($this->_logger instanceof Zend_Log) {
45
+                    $this->_logger->warn(__METHOD__ . '::' . __LINE__ . " Move failed: " . $e->getMessage());
46
+                }
47
+                $response->appendChild($this->_outputDom->createElementNS('uri:Move', 'Status', Syncroton_Exception_Status_MoveItems::FOLDER_LOCKED));
48
             }
49
         }
50
 
51
kolab-syncroton-2.4.2.tar.gz/lib/ext/Syncroton/Command/Ping.php Changed
10
 
1
@@ -124,7 +124,7 @@
2
             do {
3
                 // take a break to save battery lifetime
4
                 call_user_func($sleepCallback);
5
-                sleep(Syncroton_Registry::getPingTimeout());
6
+                sleep(min(Syncroton_Registry::getPingTimeout(), $lifeTime));
7
 
8
                 // make sure the connection is still alive, abort otherwise
9
                 if (connection_aborted()) {
10
kolab-syncroton-2.4.2.tar.gz/lib/ext/Syncroton/Command/Sync.php Changed
201
 
1
@@ -467,6 +467,93 @@
2
         }
3
     }
4
 
5
+    private function getServerModifications($dataController, $collectionData, $clientModifications)
6
+    {
7
+        $serverModifications = 
8
+            'added'   => ,
9
+            'changed' => ,
10
+            'deleted' => ,
11
+        ;
12
+
13
+        // We first use hasChanges because it has a fast path for when there are no changes by fetching the count of messages only.
14
+        // However, in all other cases we will end up fetching the same entries as below, which is less than ideal.
15
+        // TODO: We should create a new method, which checks if there are no changes, and otherwise just let the code below figure out
16
+        // if there are any changes to process.
17
+        if (!$dataController->hasChanges($this->_contentStateBackend, $collectionData->folder, $collectionData->syncState)) {
18
+            return $serverModifications;
19
+        }
20
+
21
+        // update _syncTimeStamp as $dataController->hasChanges might have spent some time
22
+        $this->_syncTimeStamp = new DateTime('now', new DateTimeZone('UTC'));
23
+
24
+        // fetch entries added since last sync
25
+        $allClientEntries = $this->_contentStateBackend->getFolderState(
26
+            $this->_device,
27
+            $collectionData->folder,
28
+            $collectionData->syncState->counter
29
+        );
30
+
31
+        // fetch entries changed since last sync
32
+        $allChangedEntries = $dataController->getChangedEntries(
33
+            $collectionData->collectionId,
34
+            $collectionData->syncState,
35
+            $collectionData->options'filterType'
36
+        );
37
+
38
+        // fetch all entries
39
+        $allServerEntries = $dataController->getServerEntries(
40
+            $collectionData->collectionId,
41
+            $collectionData->options'filterType'
42
+        );
43
+
44
+        // add entries
45
+        $serverDiff = array_diff($allServerEntries, $allClientEntries);
46
+        // add entries which produced problems during delete from client
47
+        $serverModifications'added' = $clientModifications'forceAdd';
48
+        // add entries not yet sent to client
49
+        $serverModifications'added' = array_unique(array_merge($serverModifications'added', $serverDiff));
50
+
51
+        // @todo still needed?
52
+        foreach($serverModifications'added' as $id => $serverId) {
53
+            // skip entries added by client during this sync session
54
+            if(isset($clientModifications'added'$serverId) && !isset($clientModifications'forceAdd'$serverId)) {
55
+                if ($this->_logger instanceof Zend_Log) {
56
+                    $this->_logger->info(__METHOD__ . '::' . __LINE__ . " skipped added entry: " . $serverId);
57
+                }
58
+                unset($serverModifications'added'$id);
59
+            }
60
+        }
61
+
62
+        // entries to be deleted
63
+        $serverModifications'deleted' = array_diff($allClientEntries, $allServerEntries);
64
+
65
+        // entries changed since last sync
66
+        $serverModifications'changed' = array_merge($allChangedEntries, $clientModifications'forceChange');
67
+
68
+        foreach($serverModifications'changed' as $id => $serverId) {
69
+            // skip entry, if it got changed by client during current sync
70
+            if(isset($clientModifications'changed'$serverId) && !isset($clientModifications'forceChange'$serverId)) {
71
+                if ($this->_logger instanceof Zend_Log) {
72
+                    $this->_logger->info(__METHOD__ . '::' . __LINE__ . " skipped changed entry: " . $serverId);
73
+                }
74
+                unset($serverModifications'changed'$id);
75
+            }
76
+            // skip entry, make sure we don't sent entries already added by client in this request
77
+            elseif (isset($clientModifications'added'$serverId) && !isset($clientModifications'forceAdd'$serverId)) {
78
+                if ($this->_logger instanceof Zend_Log) {
79
+                    $this->_logger->info(__METHOD__ . '::' . __LINE__ . " skipped change for added entry: " . $serverId);
80
+                }
81
+                unset($serverModifications'changed'$id);
82
+            }
83
+        }
84
+
85
+        // entries comeing in scope are already in $serverModifications'added' and do not need to
86
+        // be send with $serverCanges
87
+        $serverModifications'changed' = array_diff($serverModifications'changed', $serverModifications'added');
88
+
89
+        return $serverModifications;
90
+    }
91
+
92
     /**
93
      * (non-PHPdoc)
94
      * @see Syncroton_Command_Wbxml::getResponse()
95
@@ -663,18 +750,17 @@
96
                 ;
97
 
98
                 $status = self::STATUS_SUCCESS;
99
-                $hasChanges = 0;
100
 
101
                 if ($collectionData->getChanges === true) {
102
                     // continue sync session?
103
                     if(is_array($collectionData->syncState->pendingdata)) {
104
                         $serverModifications = $collectionData->syncState->pendingdata;
105
                         if ($this->_logger instanceof Zend_Log) {
106
-                            $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');
107
+                            $this->_logger->info(__METHOD__ . '::' . __LINE__ . " restored from sync state.");
108
                         }
109
                     } else {
110
                         try {
111
-                            $hasChanges = $dataController->hasChanges($this->_contentStateBackend, $collectionData->folder, $collectionData->syncState);
112
+                            $serverModifications = $this->getServerModifications($dataController, $collectionData, $clientModifications);
113
                         } catch (Syncroton_Exception_NotFound $e) {
114
                             if ($this->_logger instanceof Zend_Log) {
115
                                 $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " Folder changes checking failed (not found): " . $e->getTraceAsString());
116
@@ -686,85 +772,6 @@
117
                                 $this->_logger->crit(__METHOD__ . '::' . __LINE__ . " Folder changes checking failed: " . $e->getMessage());
118
                             }
119
 
120
-                            // Prevent from removing client entries when getServerEntries() fails
121
-                            // @todo: should we break the loop here?
122
-                            $status = self::STATUS_SERVER_ERROR;
123
-                        }
124
-                    }
125
-
126
-                    if ($hasChanges) {
127
-                        // update _syncTimeStamp as $dataController->hasChanges might have spent some time
128
-                        $this->_syncTimeStamp = new DateTime('now', new DateTimeZone('UTC'));
129
-
130
-                        try {
131
-                            // fetch entries added since last sync
132
-                            $allClientEntries = $this->_contentStateBackend->getFolderState(
133
-                                $this->_device,
134
-                                $collectionData->folder,
135
-                                $collectionData->syncState->counter
136
-                            );
137
-
138
-                            // fetch entries changed since last sync
139
-                            $allChangedEntries = $dataController->getChangedEntries(
140
-                                $collectionData->collectionId,
141
-                                $collectionData->syncState,
142
-                                $collectionData->options'filterType'
143
-                            );
144
-
145
-                            // fetch all entries
146
-                            $allServerEntries = $dataController->getServerEntries(
147
-                                $collectionData->collectionId,
148
-                                $collectionData->options'filterType'
149
-                            );
150
-
151
-                            // add entries
152
-                            $serverDiff = array_diff($allServerEntries, $allClientEntries);
153
-                            // add entries which produced problems during delete from client
154
-                            $serverModifications'added' = $clientModifications'forceAdd';
155
-                            // add entries not yet sent to client
156
-                            $serverModifications'added' = array_unique(array_merge($serverModifications'added', $serverDiff));
157
-
158
-                            // @todo still needed?
159
-                            foreach($serverModifications'added' as $id => $serverId) {
160
-                                // skip entries added by client during this sync session
161
-                                if(isset($clientModifications'added'$serverId) && !isset($clientModifications'forceAdd'$serverId)) {
162
-                                    if ($this->_logger instanceof Zend_Log) {
163
-                                        $this->_logger->info(__METHOD__ . '::' . __LINE__ . " skipped added entry: " . $serverId);
164
-                                    }
165
-                                    unset($serverModifications'added'$id);
166
-                                }
167
-                            }
168
-
169
-                            // entries to be deleted
170
-                            $serverModifications'deleted' = array_diff($allClientEntries, $allServerEntries);
171
-
172
-                            // entries changed since last sync
173
-                            $serverModifications'changed' = array_merge($allChangedEntries, $clientModifications'forceChange');
174
-
175
-                            foreach($serverModifications'changed' as $id => $serverId) {
176
-                                // skip entry, if it got changed by client during current sync
177
-                                if(isset($clientModifications'changed'$serverId) && !isset($clientModifications'forceChange'$serverId)) {
178
-                                    if ($this->_logger instanceof Zend_Log) {
179
-                                        $this->_logger->info(__METHOD__ . '::' . __LINE__ . " skipped changed entry: " . $serverId);
180
-                                    }
181
-                                    unset($serverModifications'changed'$id);
182
-                                }
183
-                                // skip entry, make sure we don't sent entries already added by client in this request
184
-                                elseif (isset($clientModifications'added'$serverId) && !isset($clientModifications'forceAdd'$serverId)) {
185
-                                    if ($this->_logger instanceof Zend_Log) {
186
-                                        $this->_logger->info(__METHOD__ . '::' . __LINE__ . " skipped change for added entry: " . $serverId);
187
-                                    }
188
-                                    unset($serverModifications'changed'$id);
189
-                                }
190
-                            }
191
-
192
-                            // entries comeing in scope are already in $serverModifications'added' and do not need to
193
-                            // be send with $serverCanges
194
-                            $serverModifications'changed' = array_diff($serverModifications'changed', $serverModifications'added');
195
-                        } catch (Exception $e) {
196
-                            if ($this->_logger instanceof Zend_Log) {
197
-                                $this->_logger->crit(__METHOD__ . '::' . __LINE__ . " Folder state checking failed: " . $e->getMessage());
198
-                            }
199
                             if ($this->_logger instanceof Zend_Log) {
200
                                 $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " Folder state checking failed: " . $e->getTraceAsString());
201
kolab-syncroton-2.4.2.tar.gz/lib/ext/Syncroton/Model/Folder.php Changed
11
 
1
@@ -33,7 +33,9 @@
2
             'ownerId'        => 'type' => 'string',
3
             'class'          => 'type' => 'string',
4
             'creationTime'   => 'type' => 'datetime',
5
+            'creationSynckey' => 'type' => 'number',
6
             'lastfiltertype' => 'type' => 'number',
7
+            'resync'         => 'type' => 'number',
8
         ,
9
     ;
10
 }
11
kolab-syncroton-2.4.2.tar.gz/lib/ext/Syncroton/Model/IFolder.php Changed
9
 
1
@@ -21,6 +21,7 @@
2
  * @property    string   $parentId
3
  * @property    string   $displayName
4
  * @property    DateTime $creationTime
5
+ * @property    int      $creationSynckey
6
  * @property    int      $lastfiltertype
7
  * @property    int      $type
8
  */
9
kolab-syncroton-2.4.2.tar.gz/lib/kolab_sync.php Changed
31
 
1
@@ -135,13 +135,15 @@
2
             $userid = $this->authenticate($_SERVER'PHP_AUTH_USER', $_SERVER'PHP_AUTH_PW');
3
         }
4
 
5
-        $this->plugins->exec_hook('ready', 'task' => 'syncroton');
6
+        if (!empty($userid)) {
7
+            $this->plugins->exec_hook('ready', 'task' => 'syncroton');
8
 
9
-        // Set log directory per-user (again, in case the username changed above)
10
-        $this->set_log_dir();
11
+            // Set log directory per-user (again, in case the username changed above)
12
+            $this->set_log_dir();
13
 
14
-        // Save user password for Roundcube Framework
15
-        $this->password = $_SERVER'PHP_AUTH_PW';
16
+            // Save user password for Roundcube Framework
17
+            $this->password = $_SERVER'PHP_AUTH_PW';
18
+        }
19
 
20
         // Register Syncroton backends/callbacks
21
         Syncroton_Registry::set(Syncroton_Registry::LOGGERBACKEND, $this->logger);
22
@@ -171,7 +173,7 @@
23
         Syncroton_Registry::set(Syncroton_Registry::MAX_COLLECTIONS, (int) $this->config->get('activesync_max_folders', 100));
24
 
25
         // Run Syncroton
26
-        $syncroton = new Syncroton_Server($userid);
27
+        $syncroton = new Syncroton_Server($userid ?? null);
28
         $syncroton->handle();
29
     }
30
 
31
kolab-syncroton-2.4.2.tar.gz/lib/kolab_sync_backend_folder.php Changed
68
 
1
@@ -50,15 +50,19 @@
2
      *
3
      * @param Syncroton_Model_Device|string $deviceid Device object or identifier
4
      * @param string                        $class    Class name
5
+     * @param int                           $syncKey  Sync key
6
      *
7
      * @return array List of object identifiers
8
      */
9
-    public function getFolderState($deviceid, $class)
10
+    public function getFolderState($deviceid, $class, $syncKey = null)
11
     {
12
         $device_id = $deviceid instanceof Syncroton_Model_IDevice ? $deviceid->id : $deviceid;
13
 
14
         $where = $this->db->quote_identifier('device_id') . ' = ' . $this->db->quote($device_id);
15
         $where = $this->db->quote_identifier('class') . ' = ' . $this->db->quote($class);
16
+        if ($syncKey) {
17
+            $where = $this->db->quote_identifier('creation_synckey') . ' < ' . $this->db->quote($syncKey + 1);
18
+        }
19
 
20
         $select = $this->db->query('SELECT * FROM `' . $this->table_name . '` WHERE ' . implode(' AND ', $where));
21
         $result = ;
22
@@ -88,8 +92,11 @@
23
         $select = $this->db->query('SELECT * FROM `' . $this->table_name . '` WHERE ' . implode(' AND ', $where));
24
         $folder = $this->db->fetch_assoc($select);
25
 
26
+        if (!empty($folder'resync')) {
27
+            throw new Syncroton_Exception_NotFound("Folder $folderid not found because of resync");
28
+        }
29
         if (empty($folder)) {
30
-            throw new Syncroton_Exception_NotFound('Folder not found');
31
+            throw new Syncroton_Exception_NotFound("Folder $folderid not found");
32
         }
33
 
34
         return $this->get_object($folder);
35
@@ -120,6 +127,18 @@
36
         // Reset imap cache so we work with up-to-date folders list
37
         rcube::get_instance()->get_storage()->clear_cache('mailboxes', true);
38
 
39
+        // Retrieve all folders already sent to the client
40
+        $select = $this->db->query("SELECT * FROM `{$this->table_name}` WHERE `device_id` = ?", $device->id);
41
+
42
+        while ($folder = $this->db->fetch_assoc($select)) {
43
+            if (!empty($folder'resync')) {
44
+                // Folder re-sync requested
45
+                return true;
46
+            }
47
+
48
+            $client_folders$folder'folderid' = $this->get_object($folder);
49
+        }
50
+
51
         foreach ($folder_classes as $class) {
52
             try {
53
                 // retrieve all folders available in data backend
54
@@ -132,13 +151,6 @@
55
             }
56
         }
57
 
58
-        // retrieve all folders sent to the client
59
-        $select = $this->db->query("SELECT * FROM `{$this->table_name}` WHERE `device_id` = ?", $device->id);
60
-
61
-        while ($folder = $this->db->fetch_assoc($select)) {
62
-            $client_folders$folder'folderid' = $this->get_object($folder);
63
-        }
64
-
65
         ksort($client_folders);
66
         ksort($server_folders);
67
 
68
kolab-syncroton-2.4.2.tar.gz/lib/kolab_sync_data_email.php Changed
28
 
1
@@ -674,7 +674,7 @@
2
         $result = ;
3
 
4
         if (!is_array($list)) {
5
-            throw new Syncroton_Exception_NotFound('Folder not found');
6
+            throw new Syncroton_Exception_NotFound("Folder $folder_id not found: no folders available");
7
         }
8
 
9
         // device supports multiple folders?
10
@@ -691,7 +691,7 @@
11
         }
12
 
13
         if (empty($result)) {
14
-            throw new Syncroton_Exception_NotFound('Folder not found');
15
+            throw new Syncroton_Exception_NotFound("Folder $folder_id not found.");
16
         }
17
 
18
         return $result;
19
@@ -1137,7 +1137,7 @@
20
         $message = $this->getObject($fileReference);
21
 
22
         if (!$message) {
23
-            throw new Syncroton_Exception_NotFound('Message not found');
24
+            throw new Syncroton_Exception_NotFound("Message $fileReference not found");
25
         }
26
 
27
         $part = $message->mime_parts$part_id;
28
kolab-syncroton-2.4.2.tar.gz/lib/kolab_sync_logger.php Changed
12
 
1
@@ -161,8 +161,8 @@
2
             }
3
 
4
             foreach ($params as $key => $val) {
5
-                if ($val = $_GET$val) {
6
-                    $device$key = $val;
7
+                if (isset($_GET$val)) {
8
+                    $device$key = $_GET$val;
9
                 }
10
             }
11
 
12
kolab-syncroton-2.4.2.tar.gz/lib/kolab_sync_storage.php Changed
23
 
1
@@ -1042,6 +1042,12 @@
2
 
3
             // Use COPYUID feature (RFC2359) to get the new UID of the copied message
4
             if (empty($this->storage->conn->data'COPYUID')) {
5
+                // Check if the source item actually exists. Cyrus IMAP reports
6
+                // OK on a MOVE with an invalid UID, But COPYUID will be empty.
7
+                // This way we only incur the cost of the extra check once the move fails.
8
+                if (!$this->storage->get_message_headers($uid, $src_name)) {
9
+                    throw new Syncroton_Exception_Status_MoveItems(Syncroton_Exception_Status_MoveItems::INVALID_SOURCE);
10
+                }
11
                 throw new Syncroton_Exception_Status(Syncroton_Exception_Status::SERVER_ERROR);
12
             }
13
 
14
@@ -1960,7 +1966,7 @@
15
 
16
         //If we had a collision before
17
         if (isset($this->relations$folderid$synctime . "-1")) {
18
-            return $this->relations$folderid$synctime. "-1";
19
+            return $this->relations$folderid$synctime . "-1";
20
         }
21
         if (!isset($this->relations$folderid$synctime)) {
22
             $rcube = rcube::get_instance();
23
kolab-syncroton-2.4.2.tar.gz/tests/Sync/FoldersTest.php Changed
201
 
1
@@ -3,16 +3,230 @@
2
 class FoldersTest extends Tests\SyncTestCase
3
 {
4
     /**
5
-     * Test FolderSync command
6
+     * Cleanup folders
7
      */
8
-    public function testFolderSync()
9
+    public function setUp(): void
10
     {
11
         // Note: We essentially assume the test account is in an initial state, extra folders may break tests
12
         // Anyway, we first remove folders that might have been created during tests in this file
13
         $this->deleteTestFolder('Test Folder', 'mail');
14
+        $this->deleteTestFolder('NewFolder', 'mail');
15
+        $this->deleteTestFolder('NewFolder2', 'mail');
16
         $this->deleteTestFolder('Test Folder New', 'mail');
17
         $this->deleteTestFolder('Test Contacts Folder', 'contact');
18
         $this->deleteTestFolder('Test Contacts New', 'contact');
19
+        parent::setUp();
20
+    }
21
+
22
+    /**
23
+     * Test FolderSync command
24
+     */
25
+    public function testFolderSyncBasic()
26
+    {
27
+        $request = <<<EOF
28
+            <?xml version="1.0" encoding="utf-8"?>
29
+            <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
30
+            <FolderSync xmlns="uri:FolderHierarchy">
31
+                <SyncKey>0</SyncKey>
32
+            </FolderSync>
33
+            EOF;
34
+
35
+        $response = $this->request($request, 'FolderSync');
36
+
37
+        $this->assertEquals(200, $response->getStatusCode());
38
+
39
+        $dom = $this->fromWbxml($response->getBody());
40
+        $xpath = $this->xpath($dom);
41
+
42
+        $this->assertSame('1', $xpath->query("//ns:FolderSync/ns:Status")->item(0)->nodeValue);
43
+        $this->assertSame('1', $xpath->query("//ns:FolderSync/ns:SyncKey")->item(0)->nodeValue);
44
+        // We expect some folders to exist (dont' know how many)
45
+        $this->assertTrue(intval($xpath->query("//ns:FolderSync/ns:Changes/ns:Count")->item(0)->nodeValue) > 2);
46
+
47
+        $request = <<<EOF
48
+            <?xml version="1.0" encoding="utf-8"?>
49
+            <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
50
+            <FolderSync xmlns="uri:FolderHierarchy">
51
+                <SyncKey>1</SyncKey>
52
+            </FolderSync>
53
+            EOF;
54
+
55
+        $response = $this->request($request, 'FolderSync');
56
+
57
+        $this->assertEquals(200, $response->getStatusCode());
58
+
59
+        $dom = $this->fromWbxml($response->getBody());
60
+        $xpath = $this->xpath($dom);
61
+
62
+        $this->assertSame('1', $xpath->query("//ns:FolderSync/ns:Status")->item(0)->nodeValue);
63
+        $this->assertSame('1', $xpath->query("//ns:FolderSync/ns:SyncKey")->item(0)->nodeValue);
64
+        // No changes on second sync
65
+        $this->assertSame(strval(0), $xpath->query("//ns:FolderSync/ns:Changes/ns:Count")->item(0)->nodeValue);
66
+
67
+
68
+        //Clear the creation_synckey (that's the migration scenario)
69
+        //Shouldn't trigger a change
70
+        $rcube = \rcube::get_instance();
71
+        $db    = $rcube->get_dbh();
72
+        $result = $db->query(
73
+            "UPDATE `syncroton_folder` SET `creation_synckey` = null",
74
+        );
75
+
76
+        $request = <<<EOF
77
+            <?xml version="1.0" encoding="utf-8"?>
78
+            <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
79
+            <FolderSync xmlns="uri:FolderHierarchy">
80
+                <SyncKey>1</SyncKey>
81
+            </FolderSync>
82
+            EOF;
83
+
84
+        $response = $this->request($request, 'FolderSync');
85
+        $this->assertEquals(200, $response->getStatusCode());
86
+        $dom = $this->fromWbxml($response->getBody());
87
+        $xpath = $this->xpath($dom);
88
+        $this->printDom($dom);
89
+        $this->assertSame('1', $xpath->query("//ns:FolderSync/ns:Status")->item(0)->nodeValue);
90
+        $this->assertSame('1', $xpath->query("//ns:FolderSync/ns:SyncKey")->item(0)->nodeValue);
91
+        // No changes on second sync
92
+        $this->assertSame(strval(0), $xpath->query("//ns:FolderSync/ns:Changes/ns:Count")->item(0)->nodeValue);
93
+    }
94
+
95
+    /**
96
+     * Test invalid sync key
97
+     */
98
+    public function testFolderInvalidSyncKey()
99
+    {
100
+        $request = <<<EOF
101
+            <?xml version="1.0" encoding="utf-8"?>
102
+            <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
103
+            <FolderSync xmlns="uri:FolderHierarchy">
104
+                <SyncKey>999</SyncKey>
105
+            </FolderSync>
106
+            EOF;
107
+
108
+        $response = $this->request($request, 'FolderSync');
109
+
110
+        $this->assertEquals(200, $response->getStatusCode());
111
+
112
+        $dom = $this->fromWbxml($response->getBody());
113
+        $xpath = $this->xpath($dom);
114
+
115
+        $this->assertSame('9', $xpath->query("//ns:FolderSync/ns:Status")->item(0)->nodeValue);
116
+    }
117
+
118
+
119
+    /**
120
+     * Test synckey reuse
121
+     */
122
+    public function testSyncKeyResend()
123
+    {
124
+        $this->deleteTestFolder('NewFolder', 'mail');
125
+        $this->deleteTestFolder('NewFolder2', 'mail');
126
+        $request = <<<EOF
127
+            <?xml version="1.0" encoding="utf-8"?>
128
+            <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
129
+            <FolderSync xmlns="uri:FolderHierarchy">
130
+                <SyncKey>0</SyncKey>
131
+            </FolderSync>
132
+            EOF;
133
+        $response = $this->request($request, 'FolderSync');
134
+        $this->assertEquals(200, $response->getStatusCode());
135
+        $dom = $this->fromWbxml($response->getBody());
136
+        $xpath = $this->xpath($dom);
137
+        $this->assertSame('1', $xpath->query("//ns:FolderSync/ns:Status")->item(0)->nodeValue);
138
+
139
+        //Now change something
140
+        $this->createTestFolder("NewFolder", "mail");
141
+        $request = <<<EOF
142
+            <?xml version="1.0" encoding="utf-8"?>
143
+            <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
144
+            <FolderSync xmlns="uri:FolderHierarchy">
145
+                <SyncKey>1</SyncKey>
146
+            </FolderSync>
147
+            EOF;
148
+
149
+        $response = $this->request($request, 'FolderSync');
150
+        $this->assertEquals(200, $response->getStatusCode());
151
+        $dom = $this->fromWbxml($response->getBody());
152
+        $xpath = $this->xpath($dom);
153
+        $this->assertSame('1', $xpath->query("//ns:FolderSync/ns:Status")->item(0)->nodeValue);
154
+        $this->assertSame('2', $xpath->query("//ns:FolderSync/ns:SyncKey")->item(0)->nodeValue);
155
+        $this->assertSame(strval(1), $xpath->query("//ns:FolderSync/ns:Changes/ns:Count")->item(0)->nodeValue);
156
+
157
+        //Resend the same synckey
158
+        $request = <<<EOF
159
+            <?xml version="1.0" encoding="utf-8"?>
160
+            <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
161
+            <FolderSync xmlns="uri:FolderHierarchy">
162
+                <SyncKey>1</SyncKey>
163
+            </FolderSync>
164
+            EOF;
165
+
166
+        $response = $this->request($request, 'FolderSync');
167
+        $this->assertEquals(200, $response->getStatusCode());
168
+        $dom = $this->fromWbxml($response->getBody());
169
+        $xpath = $this->xpath($dom);
170
+        $this->assertSame('1', $xpath->query("//ns:FolderSync/ns:Status")->item(0)->nodeValue);
171
+        $this->assertSame('2', $xpath->query("//ns:FolderSync/ns:SyncKey")->item(0)->nodeValue);
172
+        $this->assertSame(strval(1), $xpath->query("//ns:FolderSync/ns:Changes/ns:Count")->item(0)->nodeValue);
173
+
174
+        //And now make sure we can still move on
175
+        $request = <<<EOF
176
+            <?xml version="1.0" encoding="utf-8"?>
177
+            <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
178
+            <FolderSync xmlns="uri:FolderHierarchy">
179
+                <SyncKey>2</SyncKey>
180
+            </FolderSync>
181
+            EOF;
182
+        $response = $this->request($request, 'FolderSync');
183
+        $this->assertEquals(200, $response->getStatusCode());
184
+        $dom = $this->fromWbxml($response->getBody());
185
+        $xpath = $this->xpath($dom);
186
+        $this->assertSame('1', $xpath->query("//ns:FolderSync/ns:Status")->item(0)->nodeValue);
187
+        $this->assertSame('2', $xpath->query("//ns:FolderSync/ns:SyncKey")->item(0)->nodeValue);
188
+
189
+        //Now add another folder
190
+        $this->createTestFolder("NewFolder2", "mail");
191
+        $request = <<<EOF
192
+            <?xml version="1.0" encoding="utf-8"?>
193
+            <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
194
+            <FolderSync xmlns="uri:FolderHierarchy">
195
+                <SyncKey>2</SyncKey>
196
+            </FolderSync>
197
+            EOF;
198
+        $response = $this->request($request, 'FolderSync');
199
+        $this->assertEquals(200, $response->getStatusCode());
200
+        $dom = $this->fromWbxml($response->getBody());
201
kolab-syncroton-2.4.2.tar.gz/tests/Sync/MoveItemsTest.php Changed
105
 
1
@@ -134,14 +134,78 @@
2
         $this->assertSame('test sync', $xpath->query("ns:Commands/ns:Add/ns:ApplicationData/Email:Subject", $root)->item(0)->nodeValue);
3
     }
4
 
5
+    public function testInvalidMove()
6
+    {
7
+        $this->emptyTestFolder('INBOX', 'mail');
8
+        $this->emptyTestFolder('Trash', 'mail');
9
+        $uid = $this->appendMail('INBOX', 'mail.sync1');
10
+        $this->registerDevice();
11
+        $inbox = array_search('INBOX', $this->folders);
12
+        $trash = array_search('Trash', $this->folders);
13
+
14
+        // Move item that doesn't exist
15
+        $request = <<<EOF
16
+            <?xml version="1.0" encoding="utf-8"?>
17
+            <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
18
+            <MoveItems xmlns="uri:Move">
19
+                <Move>
20
+                    <SrcMsgId>foobar::99999</SrcMsgId>
21
+                    <SrcFldId>foobar</SrcFldId>
22
+                    <DstFldId>foobar</DstFldId>
23
+                </Move>
24
+            </MoveItems>
25
+            EOF;
26
+
27
+        $response = $this->request($request, 'MoveItems');
28
+
29
+        $this->assertEquals(200, $response->getStatusCode());
30
+
31
+        $dom = $this->fromWbxml($response->getBody());
32
+        $xpath = $this->xpath($dom);
33
+        $xpath->registerNamespace('Move', 'uri:Move');
34
+
35
+        $root = $xpath->query("//Move:MoveItems/Move:Response")->item(0);
36
+        $this->assertSame('1', $xpath->query("Move:Status", $root)->item(0)->nodeValue);
37
+
38
+        // Move item that doesn't exist
39
+        $request = <<<EOF
40
+            <?xml version="1.0" encoding="utf-8"?>
41
+            <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
42
+            <MoveItems xmlns="uri:Move">
43
+                <Move>
44
+                    <SrcMsgId>{$inbox}::99999</SrcMsgId>
45
+                    <SrcFldId>{$inbox}</SrcFldId>
46
+                    <DstFldId>{$trash}</DstFldId>
47
+                </Move>
48
+            </MoveItems>
49
+            EOF;
50
+
51
+        $response = $this->request($request, 'MoveItems');
52
+
53
+        $this->assertEquals(200, $response->getStatusCode());
54
+
55
+        $dom = $this->fromWbxml($response->getBody());
56
+        $xpath = $this->xpath($dom);
57
+        $xpath->registerNamespace('Move', 'uri:Move');
58
+
59
+        $root = $xpath->query("//Move:MoveItems/Move:Response")->item(0);
60
+        $this->assertSame('1', $xpath->query("Move:Status", $root)->item(0)->nodeValue);
61
+    }
62
+
63
     /**
64
      * Test moving a contact
65
      */
66
     public function testMoveContact()
67
     {
68
+        if ($this->isStorageDriver('kolab')) {
69
+            // The Contacts folder is not available, and consequently appendObject fails
70
+            $this->markTestSkipped('This test only works with the DAV backend.');
71
+        }
72
+
73
         // Test with multi-folder support enabled
74
         self::$deviceType = 'iphone';
75
 
76
+        // @phpstan-ignore-next-line
77
         $davFolder = $this->isStorageDriver('kolab') ? 'Contacts' : 'Addressbook';
78
         $this->emptyTestFolder($davFolder, 'contact');
79
         $this->deleteTestFolder($folderName = 'Test Contacts Folder', 'contact');
80
@@ -150,6 +214,7 @@
81
         $this->registerDevice();
82
 
83
         $srcFolderId = array_search($davFolder, $this->folders);
84
+        $this->assertTrue(!empty($srcFolderId));
85
 
86
         // Create a contacts folder
87
         $folderType = Syncroton_Command_FolderSync::FOLDERTYPE_CONTACT_USER_CREATED;
88
@@ -196,6 +261,8 @@
89
         $response = $this->request($request, 'Sync');
90
 
91
         $this->assertEquals(200, $response->getStatusCode());
92
+        $root = $xpath->query("//ns:Sync/ns:Collections/ns:Collection")->item(0);
93
+        $this->assertSame('1', $xpath->query("ns:Status", $root)->item(0)->nodeValue);
94
 
95
         $request = <<<EOF
96
             <?xml version="1.0" encoding="utf-8"?>
97
@@ -242,6 +309,7 @@
98
         $xpath = $this->xpath($dom);
99
 
100
         $root = $xpath->query("//ns:Sync/ns:Collections/ns:Collection")->item(0);
101
+        $this->assertSame('1', $xpath->query("ns:Status", $root)->item(0)->nodeValue);
102
         $this->assertSame($srcFolderId, $xpath->query("ns:CollectionId", $root)->item(0)->nodeValue);
103
         $this->assertSame(1, $xpath->query("ns:Commands/ns:Add", $root)->count());
104
         $this->assertSame('Jane', $xpath->query("ns:Commands/ns:Add/ns:ApplicationData/Contacts:FirstName", $root)->item(0)->nodeValue);
105
kolab-syncroton-2.4.2.tar.gz/tests/Sync/PingTest.php Added
104
 
1
@@ -0,0 +1,102 @@
2
+<?php
3
+
4
+class PingTest extends Tests\SyncTestCase
5
+{
6
+
7
+    /**
8
+     * Test Ping command
9
+     */
10
+    public function testFolderSyncBasic()
11
+    {
12
+        $request = <<<EOF
13
+            <?xml version="1.0" encoding="utf-8"?>
14
+            <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
15
+            <Ping xmlns="uri:Ping">
16
+                <HeartbeatInterval>900</HeartbeatInterval>
17
+                <Folders>
18
+                    <Folder>
19
+                        <Id>38b950ebd62cd9a66929c89615d0fc04</Id>
20
+                        <Class>Email</Class>
21
+                    </Folder>
22
+                </Folders>
23
+            </Ping>
24
+            EOF;
25
+
26
+        $response = $this->request($request, 'Ping');
27
+
28
+        $this->assertEquals(200, $response->getStatusCode());
29
+
30
+        $dom = $this->fromWbxml($response->getBody());
31
+        $xpath = $this->xpath($dom);
32
+        $this->printDom($dom);
33
+
34
+        //Initially we know no folders
35
+        $this->assertSame('7', $xpath->query("//ns:Ping/ns:Status")->item(0)->nodeValue);
36
+
37
+
38
+        //We discover folders with a foldersync
39
+        $request = <<<EOF
40
+            <?xml version="1.0" encoding="utf-8"?>
41
+            <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
42
+            <FolderSync xmlns="uri:FolderHierarchy">
43
+                <SyncKey>0</SyncKey>
44
+            </FolderSync>
45
+            EOF;
46
+
47
+        $response = $this->request($request, 'FolderSync');
48
+        $this->assertEquals(200, $response->getStatusCode());
49
+
50
+        //Now we get to the actual ping
51
+        $request = <<<EOF
52
+            <?xml version="1.0" encoding="utf-8"?>
53
+            <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
54
+            <Ping xmlns="uri:Ping">
55
+                <HeartbeatInterval>0</HeartbeatInterval>
56
+                <Folders>
57
+                    <Folder>
58
+                        <Id>38b950ebd62cd9a66929c89615d0fc04</Id>
59
+                        <Class>Email</Class>
60
+                    </Folder>
61
+                </Folders>
62
+            </Ping>
63
+            EOF;
64
+
65
+        $response = $this->request($request, 'Ping');
66
+        $this->assertEquals(200, $response->getStatusCode());
67
+        $dom = $this->fromWbxml($response->getBody());
68
+        $xpath = $this->xpath($dom);
69
+        // $this->printDom($dom);
70
+        //Initially we know no folders
71
+        $this->assertSame('2', $xpath->query("//ns:Ping/ns:Status")->item(0)->nodeValue);
72
+    }
73
+
74
+    /**
75
+     * Test Ping command
76
+     */
77
+    public function testUnknownFolder()
78
+    {
79
+        $request = <<<EOF
80
+            <?xml version="1.0" encoding="utf-8"?>
81
+            <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
82
+            <Ping xmlns="uri:Ping">
83
+                <HeartbeatInterval>900</HeartbeatInterval>
84
+                <Folders>
85
+                    <Folder>
86
+                        <Id>foobar</Id>
87
+                        <Class>Email</Class>
88
+                    </Folder>
89
+                </Folders>
90
+            </Ping>
91
+            EOF;
92
+
93
+        $response = $this->request($request, 'Ping');
94
+
95
+        $this->assertEquals(200, $response->getStatusCode());
96
+
97
+        $dom = $this->fromWbxml($response->getBody());
98
+        $xpath = $this->xpath($dom);
99
+        // $this->printDom($dom);
100
+
101
+        $this->assertSame('7', $xpath->query("//ns:Ping/ns:Status")->item(0)->nodeValue);
102
+    }
103
+}
104
kolab-syncroton-2.4.2.tar.gz/tests/Sync/Sync/InconsistencyTest.php Added
148
 
1
@@ -0,0 +1,146 @@
2
+<?php
3
+
4
+namespace Tests\Sync\Sync;
5
+
6
+class InconsistencyTest extends \Tests\SyncTestCase
7
+{
8
+    /**
9
+     * Test Sync command
10
+     */
11
+    public function testSync()
12
+    {
13
+        $this->emptyTestFolder('INBOX', 'mail');
14
+        $this->registerDevice();
15
+
16
+        // Append two mail messages
17
+        $uid1 = $this->appendMail('INBOX', 'mail.sync1');
18
+        $this->appendMail('INBOX', 'mail.sync2');
19
+
20
+        // Initial sync
21
+        $folderId = '38b950ebd62cd9a66929c89615d0fc04';
22
+        $syncKey = 0;
23
+        $request = <<<EOF
24
+            <?xml version="1.0" encoding="utf-8"?>
25
+            <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
26
+            <Sync xmlns="uri:AirSync">
27
+                <Collections>
28
+                    <Collection>
29
+                        <SyncKey>{$syncKey}</SyncKey>
30
+                        <CollectionId>{$folderId}</CollectionId>
31
+                    </Collection>
32
+                </Collections>
33
+            </Sync>
34
+            EOF;
35
+
36
+        $response = $this->request($request, 'Sync');
37
+        $this->assertEquals(200, $response->getStatusCode());
38
+        $syncKey++;
39
+
40
+        $request = <<<EOF
41
+            <?xml version="1.0" encoding="utf-8"?>
42
+            <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
43
+            <Sync xmlns="uri:AirSync" xmlns:AirSyncBase="uri:AirSyncBase">
44
+                <Collections>
45
+                    <Collection>
46
+                        <SyncKey>{$syncKey}</SyncKey>
47
+                        <CollectionId>{$folderId}</CollectionId>
48
+                        <DeletesAsMoves>1</DeletesAsMoves>
49
+                        <GetChanges>1</GetChanges>
50
+                        <WindowSize>2</WindowSize>
51
+                        <Options>
52
+                            <FilterType>0</FilterType>
53
+                            <Conflict>1</Conflict>
54
+                            <BodyPreference xmlns="uri:AirSyncBase">
55
+                                <Type>2</Type>
56
+                                <TruncationSize>51200</TruncationSize>
57
+                                <AllOrNone>0</AllOrNone>
58
+                            </BodyPreference>
59
+                        </Options>
60
+                    </Collection>
61
+                </Collections>
62
+            </Sync>
63
+            EOF;
64
+
65
+        $response = $this->request($request, 'Sync');
66
+
67
+        $this->assertEquals(200, $response->getStatusCode());
68
+
69
+        $dom = $this->fromWbxml($response->getBody());
70
+        $xpath = $this->xpath($dom);
71
+
72
+        $root = "//ns:Sync/ns:Collections/ns:Collection";
73
+        $this->assertSame('1', $xpath->query("{$root}/ns:Status")->item(0)->nodeValue);
74
+        $this->assertSame(strval(++$syncKey), $xpath->query("{$root}/ns:SyncKey")->item(0)->nodeValue);
75
+        $this->assertSame($folderId, $xpath->query("{$root}/ns:CollectionId")->item(0)->nodeValue);
76
+        $this->assertSame(2, $xpath->query("{$root}/ns:Commands/ns:Add")->count());
77
+
78
+        // Initial sync is complete
79
+        // We now artifically create a sync inconsistency be deleting the content part of the first mail.
80
+        // This replicates a situation that we've seen, but don't know yet how it was created in the first place.
81
+        $rcube = \rcube::get_instance();
82
+        $db    = $rcube->get_dbh();
83
+        $result = $db->query(
84
+            "DELETE FROM `syncroton_content`"
85
+            . " WHERE `contentid` = ?",
86
+            "$folderId::$uid1"
87
+        );
88
+        $this->assertNull($db->is_error($result));
89
+
90
+        // Now sync again
91
+        $request = <<<EOF
92
+            <?xml version="1.0" encoding="utf-8"?>
93
+            <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
94
+            <Sync xmlns="uri:AirSync" xmlns:AirSyncBase="uri:AirSyncBase">
95
+                <Collections>
96
+                    <Collection>
97
+                        <SyncKey>{$syncKey}</SyncKey>
98
+                        <CollectionId>{$folderId}</CollectionId>
99
+                        <DeletesAsMoves>1</DeletesAsMoves>
100
+                        <GetChanges>1</GetChanges>
101
+                        <Options>
102
+                            <FilterType>0</FilterType>
103
+                            <Conflict>1</Conflict>
104
+                            <BodyPreference xmlns="uri:AirSyncBase">
105
+                                <Type>2</Type>
106
+                                <TruncationSize>51200</TruncationSize>
107
+                                <AllOrNone>0</AllOrNone>
108
+                            </BodyPreference>
109
+                        </Options>
110
+                    </Collection>
111
+                </Collections>
112
+            </Sync>
113
+            EOF;
114
+
115
+        $response = $this->request($request, 'Sync');
116
+
117
+        $this->assertEquals(200, $response->getStatusCode());
118
+
119
+        $dom = $this->fromWbxml($response->getBody());
120
+        $xpath = $this->xpath($dom);
121
+
122
+        $root = "//ns:Sync/ns:Collections/ns:Collection";
123
+        $this->assertSame('1', $xpath->query("{$root}/ns:Status")->item(0)->nodeValue);
124
+        $this->assertSame(strval(++$syncKey), $xpath->query("{$root}/ns:SyncKey")->item(0)->nodeValue);
125
+        $this->assertSame($folderId, $xpath->query("{$root}/ns:CollectionId")->item(0)->nodeValue);
126
+        $this->assertSame(1, $xpath->query("{$root}/ns:Commands/ns:Add")->count());
127
+
128
+
129
+        //Assert that we have all content parts back
130
+        $sync = \kolab_sync::get_instance();
131
+        $device = $sync->storage()->device_get(self::$deviceId);
132
+
133
+        $result = $db->query(
134
+            "SELECT `contentid` FROM `syncroton_content`"
135
+            . " WHERE `device_id` = ?",
136
+            $device'ID'
137
+        );
138
+        $data = ;
139
+        while ($state = $db->fetch_assoc($result)) {
140
+            $data = $state;
141
+        }
142
+        $this->assertSame(2, count($data));
143
+
144
+        return $syncKey;
145
+    }
146
+
147
+}
148
kolab-syncroton-2.4.2.tar.gz/tests/Sync/Sync/RelationsTest.php Changed
45
 
1
@@ -4,8 +4,8 @@
2
 
3
 class RelationsTest extends \Tests\SyncTestCase
4
 {
5
-
6
-    protected function initialSyncRequest($folderId) {
7
+    protected function initialSyncRequest($folderId)
8
+    {
9
         $request = <<<EOF
10
             <?xml version="1.0" encoding="utf-8"?>
11
             <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
12
@@ -21,7 +21,8 @@
13
         return $this->request($request, 'Sync');
14
     }
15
 
16
-    protected function syncRequest($syncKey, $folderId, $windowSize = null) {
17
+    protected function syncRequest($syncKey, $folderId, $windowSize = null)
18
+    {
19
         $request = <<<EOF
20
             <?xml version="1.0" encoding="utf-8"?>
21
             <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
22
@@ -77,7 +78,7 @@
23
         $this->assertSame('Email', $xpath->query("//ns:Sync/ns:Collections/ns:Collection/ns:Class")->item(0)->nodeValue);
24
         $this->assertSame($folderId, $xpath->query("//ns:Sync/ns:Collections/ns:Collection/ns:CollectionId")->item(0)->nodeValue);
25
 
26
-        // First we append 
27
+        // First we append
28
         $uid1 = $this->appendMail('INBOX', 'mail.sync1');
29
         $uid2 = $this->appendMail('INBOX', 'mail.sync2');
30
         $this->appendMail('INBOX', 'mail.sync1', 'sync1' => 'sync3');
31
@@ -184,7 +185,7 @@
32
         $this->assertSame(0, $xpath->query("{$root}/ns:ApplicationData/Email:Categories")->count());
33
         //FIXME this currently fails because we omit the empty categories element
34
         // $this->assertSame("", $xpath->query("{$root}/ns:ApplicationData/Email:Categories")->item(0)->nodeValue);
35
-        
36
+
37
 
38
         // Assert the db state
39
         $result = $db->query(
40
@@ -209,4 +210,3 @@
41
         return $syncKey;
42
     }
43
 }
44
-
45
kolab-syncroton-2.4.2.tar.gz/tests/SyncTestCase.php Changed
56
 
1
@@ -175,9 +175,9 @@
2
             if ($imap->folder_exists($foldername)) {
3
                 // TODO
4
                 exit("Not implemented for Kolab v3 storage driver");
5
+            } else {
6
+                exit("Folder is missing");
7
             }
8
-
9
-            return;
10
         }
11
 
12
         $dav = $this->getDavStorage();
13
@@ -222,6 +222,27 @@
14
     }
15
 
16
     /**
17
+     * Create a folder
18
+     */
19
+    protected function createTestFolder($name, $type)
20
+    {
21
+        // Create IMAP folders
22
+        if ($type == 'mail' || $this->isStorageDriver('kolab')) {
23
+            $imap = $this->getImapStorage();
24
+            //TODO set type if not mail
25
+            $imap->create_folder($name, true);
26
+
27
+            $metadata = ;
28
+            $metadata'FOLDER' = ;
29
+            $metadata'FOLDER'self::$deviceId = ;
30
+            $metadata'FOLDER'self::$deviceId'S' = '1';
31
+            $imap->set_metadata($name, '/private/vendor/kolab/activesync' => json_encode($metadata));
32
+
33
+            return;
34
+        }
35
+    }
36
+
37
+    /**
38
      * Remove all objects from a folder
39
      */
40
     protected function emptyTestFolder($name, $type)
41
@@ -392,6 +413,14 @@
42
         return $xpath;
43
     }
44
 
45
+    /**
46
+      * Pretty print the DOM
47
+      */
48
+    protected function printDom($dom)
49
+    {
50
+        $dom->formatOutput = true;
51
+        print($dom->saveXML());
52
+    }
53
 
54
     /**
55
      * adapter for phpunit < 9
56
kolab-syncroton.dsc Changed
10
 
1
@@ -2,7 +2,7 @@
2
 Source: kolab-syncroton
3
 Binary: kolab-syncroton
4
 Architecture: all
5
-Version: 1:2.4.2.31-1~kolab1
6
+Version: 1:2.4.2.34-1~kolab1
7
 Maintainer: Jeroen van Meeuwen <vanmeeuwen@kolabsys.com>
8
 Uploaders: Jeroen van Meeuwen <vanmeeuwen@kolabsys.com>
9
 Homepage: http://www.kolab.org/
10