diff --git a/bin/analyzelogs.php b/bin/analyzelogs.php
new file mode 100755
index 0000000..910f15b
--- /dev/null
+++ b/bin/analyzelogs.php
@@ -0,0 +1,141 @@
+#!/usr/bin/env php
+ |
+ | |
+ | 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 |
+ +--------------------------------------------------------------------------+
+ | Author: Christian Mollekopf |
+ +--------------------------------------------------------------------------+
+*/
+
+
+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 = $argv[1];
+
+$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 = $matches[1];
+
+ preg_match('/\[command\] => (.*)/', $part, $matches);
+ $command = $matches[1];
+
+ preg_match('/\(.*)\<\/Status\>/', $part, $matches);
+ $status = $matches[1] ?? 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 = $set[0][1];
+ $collectionId = $set[3][0];
+ $class = $set[2][0];
+ $synckey = $set[1][0];
+ 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 = $match[0][1] ?? 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 ($changesMatch[0][1] - $offset < 200) {
+ print(" " . $changesMatch[0][0] . "\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 ($matches[0] ?? [] as $match) {
+ print(" " . $match. "\n");
+ }
+ }
+
+ if (preg_match_all('/found (.*) entries to be updated on server/', $part, $matches)) {
+ foreach ($matches[0] ?? [] as $match) {
+ print(" " . $match. "\n");
+ }
+ }
+
+ if (preg_match_all('/found (.*) entries to be deleted on server/', $part, $matches)) {
+ foreach ($matches[0] ?? [] 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
+}
+