From 843f164df113190a9f0ff122c3a04ae235ed2a42 Mon Sep 17 00:00:00 2001 From: Martchus Date: Fri, 14 Apr 2023 23:26:03 +0200 Subject: [PATCH] Avoid potentially losing events I have observed that Syncthing Tray can get stuck thinking a remote device still needs data. Likely the update got lost. The code contains certain conditions in which folder completion events and requesting completion are supressed. Those conditions are based on timestamps. That is not ideal as the accuracy is only one second (so different timestamps might be considered equal as they are rounded to be the same). Additionally, it makes no sense to assume a timestamp upon receiving a response as the information might be older than the time it was received. This change avoids those conditions. It rather uses the event ID to decide what event/reply is newer. This change also uses quint64 instead of int for event IDs to avoid running into an overflow (or rather conversion error) when deserializing the ID which might be bigger than int. (Not sure how big the ID can become; this is to be on the safe side.) --- syncthingconnector/syncthingcompletion.h | 4 +- syncthingconnector/syncthingconnection.cpp | 4 +- syncthingconnector/syncthingconnection.h | 41 +++-- .../syncthingconnection_requests.cpp | 159 ++++++++++-------- syncthingconnector/syncthingdir.cpp | 23 +-- syncthingconnector/syncthingdir.h | 24 +-- syncthingconnector/tests/misctests.cpp | 76 +++++---- 7 files changed, 181 insertions(+), 150 deletions(-) diff --git a/syncthingconnector/syncthingcompletion.h b/syncthingconnector/syncthingcompletion.h index d0deb1f..2ca04e7 100644 --- a/syncthingconnector/syncthingcompletion.h +++ b/syncthingconnector/syncthingcompletion.h @@ -9,6 +9,8 @@ namespace Data { +using SyncthingEventId = quint64; + struct LIB_SYNCTHING_CONNECTOR_EXPORT SyncthingCompletion { CppUtilities::DateTime lastUpdate; double percentage = 0; @@ -23,7 +25,7 @@ struct LIB_SYNCTHING_CONNECTOR_EXPORT SyncthingCompletion { constexpr Needed &operator+=(const Needed &other); constexpr Needed &operator-=(const Needed &other); } needed; - bool requested = false; + SyncthingEventId requestedForEventId = 0; constexpr SyncthingCompletion &operator+=(const SyncthingCompletion &other); constexpr SyncthingCompletion &operator-=(const SyncthingCompletion &other); void recomputePercentage(); diff --git a/syncthingconnector/syncthingconnection.cpp b/syncthingconnector/syncthingconnection.cpp index 43105ee..b2c890d 100644 --- a/syncthingconnector/syncthingconnection.cpp +++ b/syncthingconnector/syncthingconnection.cpp @@ -441,7 +441,9 @@ void SyncthingConnection::continueReconnecting() m_hasDiskEvents = false; m_dirs.clear(); m_devs.clear(); - m_lastConnectionsUpdate = DateTime(); + m_lastConnectionsUpdateEvent = 0; + m_lastConnectionsUpdateTime = DateTime(); + m_lastFileEvent = 0; m_lastFileTime = DateTime(); m_lastErrorTime = DateTime(); m_startTime = DateTime(); diff --git a/syncthingconnector/syncthingconnection.h b/syncthingconnector/syncthingconnection.h index 08c8dc8..6f5d496 100644 --- a/syncthingconnector/syncthingconnection.h +++ b/syncthingconnector/syncthingconnection.h @@ -295,25 +295,27 @@ private Q_SLOTS: void readErrors(); void readClearingErrors(); void readEvents(); - void readEventsFromJsonArray(const QJsonArray &events, int &idVariable); + void readEventsFromJsonArray(const QJsonArray &events, quint64 &idVariable); void readStartingEvent(const QJsonObject &eventData); - void readStatusChangedEvent(CppUtilities::DateTime eventTime, const QJsonObject &eventData); - void readDownloadProgressEvent(CppUtilities::DateTime eventTime, const QJsonObject &eventData); - void readDirEvent(CppUtilities::DateTime eventTime, const QString &eventType, const QJsonObject &eventData); - void readDeviceEvent(CppUtilities::DateTime eventTime, const QString &eventType, const QJsonObject &eventData); - void readItemStarted(CppUtilities::DateTime eventTime, const QJsonObject &eventData); - void readItemFinished(CppUtilities::DateTime eventTime, const QJsonObject &eventData); - void readFolderErrors(CppUtilities::DateTime eventTime, const QJsonObject &eventData, Data::SyncthingDir &dirInfo, int index); - void readFolderCompletion( - CppUtilities::DateTime eventTime, const QJsonObject &eventData, const QString &dirId, Data::SyncthingDir *dirInfo, int dirIndex); - void readFolderCompletion(CppUtilities::DateTime eventTime, const QJsonObject &eventData, const QString &devId, Data::SyncthingDev *devInfo, - int devIndex, const QString &dirId, Data::SyncthingDir *dirInfo, int dirIndex); - void readLocalFolderCompletion(CppUtilities::DateTime eventTime, const QJsonObject &eventData, Data::SyncthingDir &dirInfo, int index); + void readStatusChangedEvent(SyncthingEventId eventId, CppUtilities::DateTime eventTime, const QJsonObject &eventData); + void readDownloadProgressEvent(const QJsonObject &eventData); + void readDirEvent(SyncthingEventId eventId, CppUtilities::DateTime eventTime, const QString &eventType, const QJsonObject &eventData); + void readDeviceEvent(SyncthingEventId eventId, CppUtilities::DateTime eventTime, const QString &eventType, const QJsonObject &eventData); + void readItemStarted(SyncthingEventId eventId, CppUtilities::DateTime eventTime, const QJsonObject &eventData); + void readItemFinished(SyncthingEventId eventId, CppUtilities::DateTime eventTime, const QJsonObject &eventData); + void readFolderErrors( + SyncthingEventId eventId, CppUtilities::DateTime eventTime, const QJsonObject &eventData, Data::SyncthingDir &dirInfo, int index); + void readFolderCompletion(SyncthingEventId eventId, CppUtilities::DateTime eventTime, const QJsonObject &eventData, const QString &dirId, + Data::SyncthingDir *dirInfo, int dirIndex); + void readFolderCompletion(SyncthingEventId eventId, CppUtilities::DateTime eventTime, const QJsonObject &eventData, const QString &devId, + Data::SyncthingDev *devInfo, int devIndex, const QString &dirId, Data::SyncthingDir *dirInfo, int dirIndex); + void readLocalFolderCompletion( + SyncthingEventId eventId, CppUtilities::DateTime eventTime, const QJsonObject &eventData, Data::SyncthingDir &dirInfo, int index); void readRemoteFolderCompletion(CppUtilities::DateTime eventTime, const QJsonObject &eventData, const QString &devId, Data::SyncthingDev *devInfo, int devIndex, const QString &dirId, Data::SyncthingDir *dirInfo, int dirIndex); void readRemoteFolderCompletion(const Data::SyncthingCompletion &completion, const QString &devId, Data::SyncthingDev *devInfo, int devIndex, const QString &dirId, Data::SyncthingDir *dirInfo, int dirIndex); - void readRemoteIndexUpdated(CppUtilities::DateTime eventTime, const QJsonObject &eventData); + void readRemoteIndexUpdated(SyncthingEventId eventId, const QJsonObject &eventData); void readPostConfig(); void readRescan(); void readDevPauseResume(); @@ -322,7 +324,8 @@ private Q_SLOTS: void readShutdown(); void readDirStatus(); void readDirPullErrors(); - void readDirSummary(CppUtilities::DateTime eventTime, const QJsonObject &summary, Data::SyncthingDir &dirInfo, int index); + void readDirSummary( + SyncthingEventId eventId, CppUtilities::DateTime eventTime, const QJsonObject &summary, Data::SyncthingDir &dirInfo, int index); void readDirRejected(CppUtilities::DateTime eventTime, const QString &dirId, const QJsonObject &eventData); void readDevRejected(CppUtilities::DateTime eventTime, const QString &devId, const QJsonObject &eventData); void readCompletion(); @@ -388,8 +391,8 @@ private: bool m_connectionAborted; bool m_abortingToReconnect; bool m_requestCompletion; - int m_lastEventId; - int m_lastDiskEventId; + SyncthingEventId m_lastEventId; + SyncthingEventId m_lastDiskEventId; QTimer m_trafficPollTimer; QTimer m_devStatsPollTimer; QTimer m_errorsPollTimer; @@ -421,7 +424,9 @@ private: bool m_hasDiskEvents; std::vector m_dirs; std::vector m_devs; - CppUtilities::DateTime m_lastConnectionsUpdate; + SyncthingEventId m_lastConnectionsUpdateEvent; + CppUtilities::DateTime m_lastConnectionsUpdateTime; + SyncthingEventId m_lastFileEvent = 0; CppUtilities::DateTime m_lastFileTime; CppUtilities::DateTime m_lastErrorTime; CppUtilities::DateTime m_startTime; diff --git a/syncthingconnector/syncthingconnection_requests.cpp b/syncthingconnector/syncthingconnection_requests.cpp index b19d619..4422bd3 100644 --- a/syncthingconnector/syncthingconnection_requests.cpp +++ b/syncthingconnector/syncthingconnection_requests.cpp @@ -18,6 +18,7 @@ #include #include +#include #include #include @@ -808,8 +809,9 @@ void SyncthingConnection::requestConnections() if (m_connectionsReply) { return; } - QObject::connect(m_connectionsReply = requestData(QStringLiteral("system/connections"), QUrlQuery()), &QNetworkReply::finished, this, - &SyncthingConnection::readConnections); + m_connectionsReply = requestData(QStringLiteral("system/connections"), QUrlQuery()); + m_connectionsReply->setProperty("lastEventId", m_lastEventId); + QObject::connect(m_connectionsReply, &QNetworkReply::finished, this, &SyncthingConnection::readConnections); } /*! @@ -841,7 +843,7 @@ void SyncthingConnection::readConnections() const std::uint64_t totalOutgoingTraffic = totalOutgoingTrafficValue.isDouble() ? jsonValueToInt(totalOutgoingTrafficValue) : unknownTraffic; double transferTime = 0.0; const bool hasDelta - = !m_lastConnectionsUpdate.isNull() && ((transferTime = (DateTime::gmtNow() - m_lastConnectionsUpdate).totalSeconds()) != 0.0); + = !m_lastConnectionsUpdateTime.isNull() && ((transferTime = (DateTime::gmtNow() - m_lastConnectionsUpdateTime).totalSeconds()) != 0.0); m_totalIncomingRate = (hasDelta && totalIncomingTraffic != unknownTraffic && m_totalIncomingTraffic != unknownTraffic) ? static_cast(totalIncomingTraffic - m_totalIncomingTraffic) * 0.008 / transferTime : 0.0; @@ -886,7 +888,8 @@ void SyncthingConnection::readConnections() ++index; } - m_lastConnectionsUpdate = DateTime::gmtNow(); + m_lastConnectionsUpdateEvent = reply->property("lastEventId").toULongLong(); + m_lastConnectionsUpdateTime = DateTime::gmtNow(); // since there seems no event for this data, keep polling if (m_keepPolling) { @@ -980,8 +983,9 @@ void SyncthingConnection::requestDirStatistics() if (m_dirStatsReply) { return; } - QObject::connect(m_dirStatsReply = requestData(QStringLiteral("stats/folder"), QUrlQuery()), &QNetworkReply::finished, this, - &SyncthingConnection::readDirStatistics); + m_dirStatsReply = requestData(QStringLiteral("stats/folder"), QUrlQuery()); + m_dirStatsReply->setProperty("lastEventId", m_lastEventId); + QObject::connect(m_dirStatsReply, &QNetworkReply::finished, this, &SyncthingConnection::readDirStatistics); } /*! @@ -1012,20 +1016,23 @@ void SyncthingConnection::readDirStatistics() continue; } - bool dirModified = false; + auto dirModified = false; + const auto eventId = reply->property("lastEventId").toULongLong(); const auto lastScan = dirObj.value(QLatin1String("lastScan")).toString().toUtf8(); if (!lastScan.isEmpty()) { dirModified = true; dirInfo.lastScanTime = parseTimeStamp(dirObj.value(QLatin1String("lastScan")), QStringLiteral("last scan")); } - const QJsonObject lastFileObj(dirObj.value(QLatin1String("lastFile")).toObject()); + const auto lastFileObj = dirObj.value(QLatin1String("lastFile")).toObject(); if (!lastFileObj.isEmpty()) { + dirInfo.lastFileEvent = eventId; dirInfo.lastFileName = lastFileObj.value(QLatin1String("filename")).toString(); dirModified = true; if (!dirInfo.lastFileName.isEmpty()) { dirInfo.lastFileDeleted = lastFileObj.value(QLatin1String("deleted")).toBool(false); dirInfo.lastFileTime = parseTimeStamp(lastFileObj.value(QLatin1String("at")), QStringLiteral("dir statistics")); - if (!dirInfo.lastFileTime.isNull() && dirInfo.lastFileTime > m_lastFileTime) { + if (!dirInfo.lastFileTime.isNull() && eventId >= m_lastFileEvent) { + m_lastFileEvent = eventId; m_lastFileTime = dirInfo.lastFileTime; m_lastFileName = dirInfo.lastFileName; m_lastFileDeleted = dirInfo.lastFileDeleted; @@ -1060,6 +1067,7 @@ void SyncthingConnection::requestDirStatus(const QString &dirId) query.addQueryItem(QStringLiteral("folder"), dirId); auto *const reply = requestData(QStringLiteral("db/status"), query); reply->setProperty("dirId", dirId); + reply->setProperty("lastEventId", m_lastEventId); m_otherReplies << reply; QObject::connect(reply, &QNetworkReply::finished, this, &SyncthingConnection::readDirStatus, Qt::QueuedConnection); } @@ -1078,7 +1086,7 @@ void SyncthingConnection::readDirStatus() case QNetworkReply::NoError: { // determine relevant dir int index; - const QString dirId(reply->property("dirId").toString()); + const auto dirId = reply->property("dirId").toString(); SyncthingDir *const dir = findDirInfo(dirId, index); if (!dir) { // discard status for unknown dirs @@ -1093,7 +1101,7 @@ void SyncthingConnection::readDirStatus() return; } - readDirSummary(DateTime::now(), replyDoc.object(), *dir, index); + readDirSummary(reply->property("lastEventId").toULongLong(), DateTime::now(), replyDoc.object(), *dir, index); if (m_keepPolling) { concludeConnection(); @@ -1123,6 +1131,7 @@ void SyncthingConnection::requestDirPullErrors(const QString &dirId, int page, i } auto *const reply = requestData(folderErrorsPath(), query); reply->setProperty("dirId", dirId); + reply->setProperty("lastEventId", m_lastEventId); QObject::connect(reply, &QNetworkReply::finished, this, &SyncthingConnection::readDirPullErrors); } @@ -1155,7 +1164,7 @@ void SyncthingConnection::readDirPullErrors() return; } - readFolderErrors(DateTime::now(), replyDoc.object(), *dir, index); + readFolderErrors(reply->property("lastEventId").toULongLong(), DateTime::now(), replyDoc.object(), *dir, index); break; } case QNetworkReply::OperationCanceledError: @@ -1184,10 +1193,10 @@ void SyncthingConnection::requestCompletion(const QString &devId, const QString static void ensureCompletionNotConsideredRequested(const QString &devId, SyncthingDev *devInfo, const QString &dirId, SyncthingDir *dirInfo) { if (devInfo) { - devInfo->completionByDir[dirId].requested = false; + devInfo->completionByDir[dirId].requestedForEventId = 0; } if (dirInfo) { - dirInfo->completionByDevice[devId].requested = false; + dirInfo->completionByDevice[devId].requestedForEventId = 0; } } /// \endcond @@ -1543,9 +1552,9 @@ void SyncthingConnection::readPostConfig() /*! * \brief Reads data from requestDirStatus() and FolderSummary-event and stores them to \a dir. */ -void SyncthingConnection::readDirSummary(DateTime eventTime, const QJsonObject &summary, SyncthingDir &dir, int index) +void SyncthingConnection::readDirSummary(SyncthingEventId eventId, DateTime eventTime, const QJsonObject &summary, SyncthingDir &dir, int index) { - if (summary.isEmpty() || dir.lastStatisticsUpdate > eventTime) { + if (summary.isEmpty() || dir.lastStatisticsUpdateEvent > eventId) { return; } @@ -1553,7 +1562,7 @@ void SyncthingConnection::readDirSummary(DateTime eventTime, const QJsonObject & auto &globalStats(dir.globalStats); auto &localStats(dir.localStats); auto &neededStats(dir.neededStats); - const auto previouslyUpdated(!dir.lastStatisticsUpdate.isNull()); + const auto previouslyUpdated(!dir.lastStatisticsUpdateTime.isNull()); const auto previouslyGlobal(globalStats); const auto previouslyNeeded(neededStats); @@ -1577,17 +1586,19 @@ void SyncthingConnection::readDirSummary(DateTime eventTime, const QJsonObject & m_dirStatsAltered = true; dir.ignorePatterns = summary.value(QLatin1String("ignorePatterns")).toBool(); - dir.lastStatisticsUpdate = eventTime; + dir.lastStatisticsUpdateEvent = eventId; + dir.lastStatisticsUpdateTime = eventTime; // update status - const auto lastStatusUpdate = parseTimeStamp(summary.value(QLatin1String("stateChanged")), QStringLiteral("state changed"), dir.lastStatusUpdate); + const auto lastStatusUpdate + = parseTimeStamp(summary.value(QLatin1String("stateChanged")), QStringLiteral("state changed"), dir.lastStatusUpdateTime); if (dir.pullErrorCount) { // consider the directory still as out-of-sync if there are still pull errors // note: Syncthing can report an "idle" status despite pull errors. dir.status = SyncthingDirStatus::OutOfSync; - dir.lastStatusUpdate = std::max(dir.lastStatusUpdate, lastStatusUpdate); + dir.lastStatusUpdateTime = std::max(dir.lastStatusUpdateTime, lastStatusUpdate); } else if (const auto state = summary.value(QLatin1String("state")).toString(); !state.isEmpty()) { - dir.assignStatus(state, lastStatusUpdate); + dir.assignStatus(state, eventId, lastStatusUpdate); } dir.completionPercentage = globalStats.bytes ? static_cast((globalStats.bytes - neededStats.bytes) * 100 / globalStats.bytes) : 100; @@ -1746,32 +1757,34 @@ void SyncthingConnection::readEvents() /*! * \brief Reads results of requestEvents(). */ -void SyncthingConnection::readEventsFromJsonArray(const QJsonArray &events, int &idVariable) +void SyncthingConnection::readEventsFromJsonArray(const QJsonArray &events, quint64 &idVariable) { for (const auto &eventVal : events) { const auto event = eventVal.toObject(); const auto eventTime = parseTimeStamp(event.value(QLatin1String("time")), QStringLiteral("event time")); const auto eventType = event.value(QLatin1String("type")).toString(); const auto eventData = event.value(QLatin1String("data")).toObject(); - - idVariable = event.value(QLatin1String("id")).toInt(idVariable); - + const auto eventIdValue = event.value(QLatin1String("id")); + const auto eventId = static_cast(std::max(eventIdValue.toDouble(), 0.0)); + if (eventIdValue.isDouble()) { + idVariable = eventId; + } if (eventType == QLatin1String("Starting")) { readStartingEvent(eventData); } else if (eventType == QLatin1String("StateChanged")) { - readStatusChangedEvent(eventTime, eventData); + readStatusChangedEvent(eventId, eventTime, eventData); } else if (eventType == QLatin1String("DownloadProgress")) { - readDownloadProgressEvent(eventTime, eventData); + readDownloadProgressEvent(eventData); } else if (eventType.startsWith(QLatin1String("Folder"))) { - readDirEvent(eventTime, eventType, eventData); + readDirEvent(eventId, eventTime, eventType, eventData); } else if (eventType.startsWith(QLatin1String("Device"))) { - readDeviceEvent(eventTime, eventType, eventData); + readDeviceEvent(eventId, eventTime, eventType, eventData); } else if (eventType == QLatin1String("ItemStarted")) { - readItemStarted(eventTime, eventData); + readItemStarted(eventId, eventTime, eventData); } else if (eventType == QLatin1String("ItemFinished")) { - readItemFinished(eventTime, eventData); + readItemFinished(eventId, eventTime, eventData); } else if (eventType == QLatin1String("RemoteIndexUpdated")) { - readRemoteIndexUpdated(eventTime, eventData); + readRemoteIndexUpdated(eventId, eventData); } else if (eventType == QLatin1String("ConfigSaved")) { requestConfig(); // just consider current config as invalidated } else if (eventType.endsWith(QLatin1String("ChangeDetected"))) { @@ -1796,7 +1809,7 @@ void SyncthingConnection::readStartingEvent(const QJsonObject &eventData) /*! * \brief Reads results of requestEvents(). */ -void SyncthingConnection::readStatusChangedEvent(DateTime eventTime, const QJsonObject &eventData) +void SyncthingConnection::readStatusChangedEvent(SyncthingEventId eventId, DateTime eventTime, const QJsonObject &eventData) { const QString dir(eventData.value(QLatin1String("folder")).toString()); if (dir.isEmpty()) { @@ -1815,7 +1828,7 @@ void SyncthingConnection::readStatusChangedEvent(DateTime eventTime, const QJson } // assign new status - bool statusChanged = dirInfo->assignStatus(eventData.value(QLatin1String("to")).toString(), eventTime); + bool statusChanged = dirInfo->assignStatus(eventData.value(QLatin1String("to")).toString(), eventId, eventTime); if (dirInfo->status == SyncthingDirStatus::OutOfSync) { const QString errorMessage(eventData.value(QLatin1String("error")).toString()); if (!errorMessage.isEmpty()) { @@ -1837,9 +1850,8 @@ void SyncthingConnection::readStatusChangedEvent(DateTime eventTime, const QJson /*! * \brief Reads results of requestEvents(). */ -void SyncthingConnection::readDownloadProgressEvent(DateTime eventTime, const QJsonObject &eventData) +void SyncthingConnection::readDownloadProgressEvent(const QJsonObject &eventData) { - CPP_UTILITIES_UNUSED(eventTime) for (SyncthingDir &dirInfo : m_dirs) { // disappearing implies that the download has been finished so just wipe old entries dirInfo.downloadingItems.clear(); @@ -1877,7 +1889,7 @@ void SyncthingConnection::readDownloadProgressEvent(DateTime eventTime, const QJ /*! * \brief Reads results of requestEvents(). */ -void SyncthingConnection::readDirEvent(DateTime eventTime, const QString &eventType, const QJsonObject &eventData) +void SyncthingConnection::readDirEvent(SyncthingEventId eventId, DateTime eventTime, const QString &eventType, const QJsonObject &eventData) { // read dir ID const auto dirId([&eventData] { @@ -1890,7 +1902,7 @@ void SyncthingConnection::readDirEvent(DateTime eventTime, const QString &eventT if (dirId.isEmpty()) { // handle events which don't necessarily require a corresponding dir info if (eventType == QLatin1String("FolderCompletion")) { - readFolderCompletion(eventTime, eventData, dirId, nullptr, -1); + readFolderCompletion(eventId, eventTime, eventData, dirId, nullptr, -1); } return; } @@ -1910,11 +1922,11 @@ void SyncthingConnection::readDirEvent(DateTime eventTime, const QString &eventT // distinguish specific events if (eventType == QLatin1String("FolderErrors")) { - readFolderErrors(eventTime, eventData, *dirInfo, index); + readFolderErrors(eventId, eventTime, eventData, *dirInfo, index); } else if (eventType == QLatin1String("FolderSummary")) { - readDirSummary(eventTime, eventData.value(QLatin1String("summary")).toObject(), *dirInfo, index); - } else if (eventType == QLatin1String("FolderCompletion") && dirInfo->lastStatisticsUpdate < eventTime) { - readFolderCompletion(eventTime, eventData, dirId, dirInfo, index); + readDirSummary(eventId, eventTime, eventData.value(QLatin1String("summary")).toObject(), *dirInfo, index); + } else if (eventType == QLatin1String("FolderCompletion") && dirInfo->lastStatisticsUpdateEvent <= eventId) { + readFolderCompletion(eventId, eventTime, eventData, dirId, dirInfo, index); } else if (eventType == QLatin1String("FolderScanProgress")) { const double current = eventData.value(QLatin1String("current")).toDouble(0); const double total = eventData.value(QLatin1String("total")).toDouble(0); @@ -1922,7 +1934,7 @@ void SyncthingConnection::readDirEvent(DateTime eventTime, const QString &eventT if (current > 0 && total > 0) { dirInfo->scanningPercentage = static_cast(current * 100 / total); dirInfo->scanningRate = rate; - dirInfo->assignStatus(SyncthingDirStatus::Scanning, eventTime); // ensure state is scanning + dirInfo->assignStatus(SyncthingDirStatus::Scanning, eventId, eventTime); // ensure state is scanning emit dirStatusChanged(*dirInfo, index); } } else if (eventType == QLatin1String("FolderPaused")) { @@ -1941,10 +1953,10 @@ void SyncthingConnection::readDirEvent(DateTime eventTime, const QString &eventT /*! * \brief Reads results of requestEvents(). */ -void SyncthingConnection::readDeviceEvent(DateTime eventTime, const QString &eventType, const QJsonObject &eventData) +void SyncthingConnection::readDeviceEvent(SyncthingEventId eventId, DateTime eventTime, const QString &eventType, const QJsonObject &eventData) { // ignore device events happened before the last connections update - if (eventTime.isNull() && m_lastConnectionsUpdate.isNull() && eventTime < m_lastConnectionsUpdate) { + if (eventId < m_lastConnectionsUpdateEvent) { return; } const QString dev(eventData.value(QLatin1String("device")).toString()); @@ -2004,8 +2016,9 @@ void SyncthingConnection::readDeviceEvent(DateTime eventTime, const QString &eve * \brief Reads results of requestEvents(). * \todo Implement this. */ -void SyncthingConnection::readItemStarted(DateTime eventTime, const QJsonObject &eventData) +void SyncthingConnection::readItemStarted(SyncthingEventId eventId, DateTime eventTime, const QJsonObject &eventData) { + CPP_UTILITIES_UNUSED(eventId) CPP_UTILITIES_UNUSED(eventTime) CPP_UTILITIES_UNUSED(eventData) } @@ -2013,7 +2026,7 @@ void SyncthingConnection::readItemStarted(DateTime eventTime, const QJsonObject /*! * \brief Reads results of requestEvents(). */ -void SyncthingConnection::readItemFinished(DateTime eventTime, const QJsonObject &eventData) +void SyncthingConnection::readItemFinished(SyncthingEventId eventId, DateTime eventTime, const QJsonObject &eventData) { int index; auto *const dirInfo = findDirInfo(QLatin1String("folder"), eventData, &index); @@ -2046,11 +2059,13 @@ void SyncthingConnection::readItemFinished(DateTime eventTime, const QJsonObject } // update last file - if (dirInfo->lastFileTime.isNull() || eventTime < dirInfo->lastFileTime) { + if (dirInfo->lastFileTime.isNull() || eventId >= dirInfo->lastFileEvent) { + dirInfo->lastFileEvent = eventId; dirInfo->lastFileTime = eventTime; dirInfo->lastFileName = item; dirInfo->lastFileDeleted = (eventData.value(QLatin1String("action")) != QLatin1String("delete")); - if (eventTime > m_lastFileTime) { + if (eventId >= m_lastFileEvent) { + m_lastFileEvent = eventId; m_lastFileTime = dirInfo->lastFileTime; m_lastFileName = dirInfo->lastFileName; m_lastFileDeleted = dirInfo->lastFileDeleted; @@ -2062,10 +2077,11 @@ void SyncthingConnection::readItemFinished(DateTime eventTime, const QJsonObject /*! * \brief Reads results of requestEvents() and requestDirPullErrors(). */ -void SyncthingConnection::readFolderErrors(DateTime eventTime, const QJsonObject &eventData, SyncthingDir &dirInfo, int index) +void SyncthingConnection::readFolderErrors( + SyncthingEventId eventId, DateTime eventTime, const QJsonObject &eventData, SyncthingDir &dirInfo, int index) { // ignore errors occurred before the last time the directory was in "sync" state (Syncthing re-emits recurring errors) - if (dirInfo.lastSyncStarted > eventTime) { + if (dirInfo.lastSyncStartedEvent > eventId) { return; } @@ -2090,7 +2106,7 @@ void SyncthingConnection::readFolderErrors(DateTime eventTime, const QJsonObject // ensure the directory is considered out-of-sync if (dirInfo.pullErrorCount) { - dirInfo.assignStatus(SyncthingDirStatus::OutOfSync, eventTime); + dirInfo.assignStatus(SyncthingDirStatus::OutOfSync, eventId, eventTime); } emit dirStatusChanged(dirInfo, index); @@ -2100,44 +2116,46 @@ void SyncthingConnection::readFolderErrors(DateTime eventTime, const QJsonObject * \brief Reads results of requestEvents(). */ void SyncthingConnection::readFolderCompletion( - DateTime eventTime, const QJsonObject &eventData, const QString &dirId, SyncthingDir *dirInfo, int dirIndex) + SyncthingEventId eventId, DateTime eventTime, const QJsonObject &eventData, const QString &dirId, SyncthingDir *dirInfo, int dirIndex) { const auto devId = eventData.value(QLatin1String("device")).toString(); int devIndex; auto *const devInfo = findDevInfo(devId, devIndex); - readFolderCompletion(eventTime, eventData, devId, devInfo, devIndex, dirId, dirInfo, dirIndex); + readFolderCompletion(eventId, eventTime, eventData, devId, devInfo, devIndex, dirId, dirInfo, dirIndex); } /*! * \brief Reads results of requestEvents(). */ -void SyncthingConnection::readFolderCompletion(DateTime eventTime, const QJsonObject &eventData, const QString &devId, SyncthingDev *devInfo, - int devIndex, const QString &dirId, SyncthingDir *dirInfo, int dirIndex) +void SyncthingConnection::readFolderCompletion(SyncthingEventId eventId, DateTime eventTime, const QJsonObject &eventData, const QString &devId, + SyncthingDev *devInfo, int devIndex, const QString &dirId, SyncthingDir *dirInfo, int dirIndex) { if (devInfo && !devId.isEmpty() && devId != myId()) { readRemoteFolderCompletion(eventTime, eventData, devId, devInfo, devIndex, dirId, dirInfo, dirIndex); } else if (dirInfo) { - readLocalFolderCompletion(eventTime, eventData, *dirInfo, dirIndex); + readLocalFolderCompletion(eventId, eventTime, eventData, *dirInfo, dirIndex); } } /*! * \brief Reads results of requestEvents(). */ -void SyncthingConnection::readLocalFolderCompletion(DateTime eventTime, const QJsonObject &eventData, SyncthingDir &dirInfo, int index) +void SyncthingConnection::readLocalFolderCompletion( + SyncthingEventId eventId, DateTime eventTime, const QJsonObject &eventData, SyncthingDir &dirInfo, int index) { auto &neededStats(dirInfo.neededStats); auto &globalStats(dirInfo.globalStats); // backup previous statistics -> if there's no difference after all, don't emit completed event - const auto previouslyUpdated(!dirInfo.lastStatisticsUpdate.isNull()); - const auto previouslyNeeded(neededStats); - const auto previouslyGlobal(globalStats); + const auto previouslyUpdated = !dirInfo.lastStatisticsUpdateTime.isNull(); + const auto previouslyNeeded = neededStats; + const auto previouslyGlobal = globalStats; // read values from event data globalStats.bytes = jsonValueToInt(eventData.value(QLatin1String("globalBytes")), static_cast(globalStats.bytes)); neededStats.bytes = jsonValueToInt(eventData.value(QLatin1String("needBytes")), static_cast(neededStats.bytes)); neededStats.deletes = jsonValueToInt(eventData.value(QLatin1String("needDeletes")), static_cast(neededStats.deletes)); neededStats.deletes = jsonValueToInt(eventData.value(QLatin1String("needItems")), static_cast(neededStats.files)); - dirInfo.lastStatisticsUpdate = eventTime; + dirInfo.lastStatisticsUpdateEvent = eventId; + dirInfo.lastStatisticsUpdateTime = eventTime; dirInfo.completionPercentage = globalStats.bytes ? static_cast((globalStats.bytes - neededStats.bytes) * 100 / globalStats.bytes) : 100; emit dirStatusChanged(dirInfo, index); if (neededStats.isNull() && previouslyUpdated && (neededStats != previouslyNeeded || globalStats != previouslyGlobal) @@ -2154,7 +2172,7 @@ void SyncthingConnection::readRemoteFolderCompletion(DateTime eventTime, const Q { // make new completion auto completion = SyncthingCompletion(); - auto &needed(completion.needed); + auto &needed = completion.needed; completion.lastUpdate = eventTime; completion.percentage = eventData.value(QLatin1String("completion")).toDouble(); completion.globalBytes = jsonValueToInt(eventData.value(QLatin1String("globalBytes"))); @@ -2201,7 +2219,7 @@ void SyncthingConnection::readRemoteFolderCompletion(const SyncthingCompletion & /*! * \brief Reads results of requestEvents(). */ -void SyncthingConnection::readRemoteIndexUpdated(DateTime eventTime, const QJsonObject &eventData) +void SyncthingConnection::readRemoteIndexUpdated(SyncthingEventId eventId, const QJsonObject &eventData) { // ignore those events if we're not updating completion automatically if (!m_requestCompletion && !m_keepPolling) { @@ -2234,20 +2252,15 @@ void SyncthingConnection::readRemoteIndexUpdated(DateTime eventTime, const QJson // might meddle with that. auto *const completionFromDirInfo = dirInfo ? &dirInfo->completionByDevice[devId] : nullptr; auto *const completionFromDevInfo = devInfo ? &devInfo->completionByDir[dirId] : nullptr; - if ((completionFromDirInfo && completionFromDirInfo->requested) || (completionFromDevInfo && completionFromDevInfo->requested)) { - return; - } - const auto lastUpdate = completionFromDirInfo && completionFromDevInfo - ? min(completionFromDirInfo->lastUpdate, completionFromDevInfo->lastUpdate) - : (completionFromDirInfo ? completionFromDirInfo->lastUpdate : completionFromDevInfo->lastUpdate); - if (lastUpdate >= eventTime) { + if ((completionFromDirInfo && completionFromDirInfo->requestedForEventId >= eventId) + || (completionFromDevInfo && completionFromDevInfo->requestedForEventId >= eventId)) { return; } if (completionFromDirInfo) { - completionFromDirInfo->requested = true; + completionFromDirInfo->requestedForEventId = eventId; } if (completionFromDevInfo) { - completionFromDevInfo->requested = true; + completionFromDevInfo->requestedForEventId = eventId; } if (devInfo && dirInfo && !devInfo->paused && !dirInfo->paused) { requestCompletion(devId, dirId); diff --git a/syncthingconnector/syncthingdir.cpp b/syncthingconnector/syncthingdir.cpp index f8f9d68..56a674c 100644 --- a/syncthingconnector/syncthingdir.cpp +++ b/syncthingconnector/syncthingdir.cpp @@ -52,27 +52,30 @@ QString dirTypeString(SyncthingDirType dirType) return QString(); } -bool SyncthingDir::checkWhetherStatusUpdateRelevant(DateTime time) +bool SyncthingDir::checkWhetherStatusUpdateRelevant(SyncthingEventId eventId, DateTime time) { // ignore old updates - if (lastStatusUpdate > time) { + if (lastStatusUpdateEvent > eventId) { return false; } - lastStatusUpdate = time; + lastStatusUpdateEvent = eventId; + lastStatusUpdateTime = time; return true; } -bool SyncthingDir::finalizeStatusUpdate(SyncthingDirStatus newStatus, DateTime time) +bool SyncthingDir::finalizeStatusUpdate(SyncthingDirStatus newStatus, SyncthingEventId eventId, DateTime time) { // handle obsoletion of out-of-sync items: no FolderErrors are accepted older than the last "sync" state are accepted if (newStatus == SyncthingDirStatus::PreparingToSync || newStatus == SyncthingDirStatus::Synchronizing) { // update time of last "sync" state and obsolete currently assigned errors - lastSyncStarted = time; // used internally and not displayed, hence keep it GMT + lastSyncStartedEvent = eventId; + lastSyncStartedTime = time; // used internally and not displayed, hence keep it GMT itemErrors.clear(); pullErrorCount = 0; - } else if (lastSyncStarted.isNull() && newStatus != SyncthingDirStatus::OutOfSync) { + } else if (lastSyncStartedTime.isNull() && newStatus != SyncthingDirStatus::OutOfSync) { // prevent adding new errors from "before the first status" if the time of the last "sync" state is unknown - lastSyncStarted = time; + lastSyncStartedEvent = eventId; + lastSyncStartedTime = time; } // clear global error if not out-of-sync anymore @@ -105,9 +108,9 @@ bool SyncthingDir::finalizeStatusUpdate(SyncthingDirStatus newStatus, DateTime t * \brief Assigns the status from the specified status string. * \returns Returns whether the status has actually changed. */ -bool SyncthingDir::assignStatus(const QString &statusStr, CppUtilities::DateTime time) +bool SyncthingDir::assignStatus(const QString &statusStr, SyncthingEventId eventId, CppUtilities::DateTime time) { - if (!checkWhetherStatusUpdateRelevant(time)) { + if (!checkWhetherStatusUpdateRelevant(eventId, time)) { return false; } @@ -147,7 +150,7 @@ bool SyncthingDir::assignStatus(const QString &statusStr, CppUtilities::DateTime rawStatus = statusStr; - return finalizeStatusUpdate(newStatus, time); + return finalizeStatusUpdate(newStatus, eventId, time); } bool SyncthingDir::assignDirType(const QString &dirTypeStr) diff --git a/syncthingconnector/syncthingdir.h b/syncthingconnector/syncthingdir.h index 69e1676..1a6a2f7 100644 --- a/syncthingconnector/syncthingdir.h +++ b/syncthingconnector/syncthingdir.h @@ -126,8 +126,8 @@ constexpr bool SyncthingStatistics::operator!=(const SyncthingStatistics &other) struct LIB_SYNCTHING_CONNECTOR_EXPORT SyncthingDir { explicit SyncthingDir(const QString &id = QString(), const QString &label = QString(), const QString &path = QString()); - bool assignStatus(const QString &statusStr, CppUtilities::DateTime time); - bool assignStatus(SyncthingDirStatus newStatus, CppUtilities::DateTime time); + bool assignStatus(const QString &statusStr, SyncthingEventId eventId, CppUtilities::DateTime time); + bool assignStatus(SyncthingDirStatus newStatus, SyncthingEventId eventId, CppUtilities::DateTime time); bool assignDirType(const QString &dirType); const QString &displayName() const; QString statusString() const; @@ -146,8 +146,10 @@ struct LIB_SYNCTHING_CONNECTOR_EXPORT SyncthingDir { int rescanInterval = 0; int minDiskFreePercentage = 0; SyncthingDirStatus status = SyncthingDirStatus::Unknown; - CppUtilities::DateTime lastStatusUpdate; - CppUtilities::DateTime lastSyncStarted; + SyncthingEventId lastStatusUpdateEvent = 0; + CppUtilities::DateTime lastStatusUpdateTime; + SyncthingEventId lastSyncStartedEvent = 0; + CppUtilities::DateTime lastSyncStartedTime; int completionPercentage = 0; int scanningPercentage = 0; double scanningRate = 0; @@ -158,8 +160,10 @@ struct LIB_SYNCTHING_CONNECTOR_EXPORT SyncthingDir { std::vector itemErrors; std::vector recentChanges; SyncthingStatistics globalStats, localStats, neededStats; - CppUtilities::DateTime lastStatisticsUpdate; + SyncthingEventId lastStatisticsUpdateEvent = 0; + CppUtilities::DateTime lastStatisticsUpdateTime; CppUtilities::DateTime lastScanTime; + SyncthingEventId lastFileEvent; CppUtilities::DateTime lastFileTime; QString lastFileName; std::vector downloadingItems; @@ -177,8 +181,8 @@ struct LIB_SYNCTHING_CONNECTOR_EXPORT SyncthingDir { bool paused = false; private: - bool checkWhetherStatusUpdateRelevant(CppUtilities::DateTime time); - bool finalizeStatusUpdate(SyncthingDirStatus newStatus, CppUtilities::DateTime time); + bool checkWhetherStatusUpdateRelevant(SyncthingEventId eventId, CppUtilities::DateTime time); + bool finalizeStatusUpdate(SyncthingDirStatus newStatus, SyncthingEventId eventId, CppUtilities::DateTime time); }; inline SyncthingDir::SyncthingDir(const QString &id, const QString &label, const QString &path) @@ -208,13 +212,13 @@ inline bool SyncthingDir::isUnshared() const return deviceIds.empty() && (status == SyncthingDirStatus::Idle || status == SyncthingDirStatus::Unknown); } -inline bool SyncthingDir::assignStatus(SyncthingDirStatus newStatus, CppUtilities::DateTime time) +inline bool SyncthingDir::assignStatus(SyncthingDirStatus newStatus, SyncthingEventId eventId, CppUtilities::DateTime time) { - if (!checkWhetherStatusUpdateRelevant(time)) { + if (!checkWhetherStatusUpdateRelevant(eventId, time)) { return false; } rawStatus.clear(); - return finalizeStatusUpdate(newStatus, time); + return finalizeStatusUpdate(newStatus, eventId, time); } struct LIB_SYNCTHING_CONNECTOR_EXPORT SyncthingOverallDirStatistics { diff --git a/syncthingconnector/tests/misctests.cpp b/syncthingconnector/tests/misctests.cpp index d8bfda3..4bda9fa 100644 --- a/syncthingconnector/tests/misctests.cpp +++ b/syncthingconnector/tests/misctests.cpp @@ -199,74 +199,76 @@ void MiscTests::testSyncthingDir() SyncthingDir dir; dir.status = SyncthingDirStatus::Unknown; - DateTime updateTime(DateTime::fromDate(2005, 2, 3)); - CPPUNIT_ASSERT(dir.assignStatus(SyncthingDirStatus::Idle, updateTime)); - CPPUNIT_ASSERT_EQUAL(QStringLiteral("unshared"), dir.statusString()); - CPPUNIT_ASSERT_EQUAL(updateTime, dir.lastStatusUpdate); + auto updateEvent = static_cast(42); + auto updateTime = DateTime(DateTime::fromDate(2005, 2, 3)); + CPPUNIT_ASSERT(dir.assignStatus(SyncthingDirStatus::Idle, updateEvent, updateTime)); + CPPUNIT_ASSERT_EQUAL_MESSAGE("status updated", QStringLiteral("unshared"), dir.statusString()); + CPPUNIT_ASSERT_EQUAL_MESSAGE("event updated", updateEvent, dir.lastStatusUpdateEvent); + CPPUNIT_ASSERT_EQUAL_MESSAGE("time updated", updateTime, dir.lastStatusUpdateTime); dir.deviceIds << QStringLiteral("dev1") << QStringLiteral("dev2"); - CPPUNIT_ASSERT(!dir.assignStatus(SyncthingDirStatus::Scanning, DateTime::fromDate(2003, 6, 7))); - CPPUNIT_ASSERT_EQUAL(updateTime, dir.lastStatusUpdate); - CPPUNIT_ASSERT_EQUAL(QStringLiteral("idle"), dir.statusString()); + CPPUNIT_ASSERT(!dir.assignStatus(SyncthingDirStatus::Scanning, updateEvent - 1, updateTime + TimeSpan::fromDays(1.0))); + CPPUNIT_ASSERT_EQUAL_MESSAGE("status not updated", QStringLiteral("idle"), dir.statusString()); + CPPUNIT_ASSERT_EQUAL_MESSAGE("event not updated", updateEvent, dir.lastStatusUpdateEvent); + CPPUNIT_ASSERT_EQUAL_MESSAGE("time not updated", updateTime, dir.lastStatusUpdateTime); - const DateTime lastScanTime(DateTime::now()); - updateTime += TimeSpan::fromSeconds(5); - CPPUNIT_ASSERT(dir.assignStatus(SyncthingDirStatus::WaitingToScan, updateTime)); + const auto lastScanTime = DateTime(DateTime::now()); + CPPUNIT_ASSERT(dir.assignStatus(SyncthingDirStatus::WaitingToScan, updateEvent += 1, updateTime += TimeSpan::fromSeconds(5))); CPPUNIT_ASSERT(dir.lastScanTime.isNull()); CPPUNIT_ASSERT_EQUAL(QStringLiteral("waiting to scan"), dir.statusString()); - updateTime += TimeSpan::fromSeconds(5); - CPPUNIT_ASSERT(dir.assignStatus(SyncthingDirStatus::Scanning, updateTime)); + + CPPUNIT_ASSERT(dir.assignStatus(SyncthingDirStatus::Scanning, updateEvent += 1, updateTime += TimeSpan::fromSeconds(5))); CPPUNIT_ASSERT(dir.lastScanTime.isNull()); CPPUNIT_ASSERT_EQUAL(QStringLiteral("scanning"), dir.statusString()); - CPPUNIT_ASSERT(dir.assignStatus(SyncthingDirStatus::Idle, updateTime += TimeSpan::fromSeconds(2))); - CPPUNIT_ASSERT_EQUAL(updateTime, dir.lastStatusUpdate); + CPPUNIT_ASSERT(dir.assignStatus(SyncthingDirStatus::Idle, updateEvent += 1, updateTime += TimeSpan::fromSeconds(2))); + CPPUNIT_ASSERT_EQUAL_MESSAGE("event updated", updateEvent, dir.lastStatusUpdateEvent); + CPPUNIT_ASSERT_EQUAL_MESSAGE("time updated", updateTime, dir.lastStatusUpdateTime); CPPUNIT_ASSERT(dir.lastScanTime >= lastScanTime); dir.status = SyncthingDirStatus::Unknown; - dir.lastSyncStarted = DateTime(1); + dir.lastSyncStartedTime = DateTime(1); dir.itemErrors.emplace_back(QStringLiteral("message"), QStringLiteral("path")); - CPPUNIT_ASSERT(dir.assignStatus(SyncthingDirStatus::Idle, updateTime += TimeSpan::fromMinutes(1.5))); + CPPUNIT_ASSERT(dir.assignStatus(SyncthingDirStatus::Idle, updateEvent += 1, updateTime += TimeSpan::fromMinutes(1.5))); CPPUNIT_ASSERT_EQUAL(QStringLiteral("idle"), dir.statusString()); CPPUNIT_ASSERT_EQUAL(1_st, dir.itemErrors.size()); - dir.lastSyncStarted = DateTime(); - CPPUNIT_ASSERT(!dir.assignStatus(SyncthingDirStatus::Idle, updateTime += TimeSpan::fromMinutes(1.5))); - CPPUNIT_ASSERT_EQUAL(updateTime, dir.lastSyncStarted); - const auto lastSyncTime(updateTime += TimeSpan::fromMinutes(1.5)); + dir.lastSyncStartedTime = DateTime(); + CPPUNIT_ASSERT(!dir.assignStatus(SyncthingDirStatus::Idle, updateEvent += 1, updateTime += TimeSpan::fromMinutes(1.5))); + CPPUNIT_ASSERT_EQUAL(updateTime, dir.lastSyncStartedTime); + const auto lastSyncTime = updateTime += TimeSpan::fromMinutes(1.5); dir.itemErrors.emplace_back(); - CPPUNIT_ASSERT(dir.assignStatus(SyncthingDirStatus::Synchronizing, lastSyncTime)); + CPPUNIT_ASSERT(dir.assignStatus(SyncthingDirStatus::Synchronizing, updateEvent += 1, lastSyncTime)); CPPUNIT_ASSERT_EQUAL(QStringLiteral("synchronizing"), dir.statusString()); CPPUNIT_ASSERT_EQUAL(0_st, dir.itemErrors.size()); - CPPUNIT_ASSERT_EQUAL(lastSyncTime, dir.lastSyncStarted); - const auto lastSyncTime2(updateTime += TimeSpan::fromMinutes(2.0)); + CPPUNIT_ASSERT_EQUAL(lastSyncTime, dir.lastSyncStartedTime); + const auto lastSyncTime2 = updateTime += TimeSpan::fromMinutes(2.0); dir.itemErrors.emplace_back(); - CPPUNIT_ASSERT(dir.assignStatus(SyncthingDirStatus::PreparingToSync, lastSyncTime2)); + CPPUNIT_ASSERT(dir.assignStatus(SyncthingDirStatus::PreparingToSync, updateEvent += 1, lastSyncTime2)); CPPUNIT_ASSERT_EQUAL(QStringLiteral("preparing to sync"), dir.statusString()); CPPUNIT_ASSERT_EQUAL(0_st, dir.itemErrors.size()); - CPPUNIT_ASSERT_EQUAL(lastSyncTime2, dir.lastSyncStarted); + CPPUNIT_ASSERT_EQUAL(lastSyncTime2, dir.lastSyncStartedTime); - CPPUNIT_ASSERT(dir.assignStatus(SyncthingDirStatus::Idle, updateTime += TimeSpan::fromMinutes(1.5))); - CPPUNIT_ASSERT_EQUAL(lastSyncTime2, dir.lastSyncStarted); - CPPUNIT_ASSERT(dir.assignStatus(QStringLiteral("syncing"), updateTime += TimeSpan::fromMinutes(1.5))); - CPPUNIT_ASSERT_EQUAL(updateTime, dir.lastSyncStarted); + CPPUNIT_ASSERT(dir.assignStatus(SyncthingDirStatus::Idle, updateEvent += 1, updateTime += TimeSpan::fromMinutes(1.5))); + CPPUNIT_ASSERT_EQUAL(lastSyncTime2, dir.lastSyncStartedTime); + CPPUNIT_ASSERT(dir.assignStatus(QStringLiteral("syncing"), updateEvent += 1, updateTime += TimeSpan::fromMinutes(1.5))); + CPPUNIT_ASSERT_EQUAL(updateTime, dir.lastSyncStartedTime); dir.itemErrors.clear(); - CPPUNIT_ASSERT(dir.assignStatus(QStringLiteral("error"), updateTime += TimeSpan::fromMinutes(1.5))); + CPPUNIT_ASSERT(dir.assignStatus(QStringLiteral("error"), updateEvent += 1, updateTime += TimeSpan::fromMinutes(1.5))); CPPUNIT_ASSERT_EQUAL(QStringLiteral("out of sync"), dir.statusString()); - CPPUNIT_ASSERT(dir.assignStatus(QStringLiteral("wrong status"), updateTime += TimeSpan::fromMinutes(1.5))); + CPPUNIT_ASSERT(dir.assignStatus(QStringLiteral("wrong status"), updateEvent += 1, updateTime += TimeSpan::fromMinutes(1.5))); CPPUNIT_ASSERT_EQUAL_MESSAGE("wrong status treated as idle", QStringLiteral("idle"), dir.statusString()); - CPPUNIT_ASSERT_MESSAGE("older status discarded", !dir.assignStatus(QStringLiteral("scanning"), updateTime - TimeSpan::fromSeconds(1))); + CPPUNIT_ASSERT_MESSAGE("older status discarded", !dir.assignStatus(QStringLiteral("scanning"), updateEvent - 1, updateTime)); CPPUNIT_ASSERT_EQUAL(QStringLiteral("idle"), dir.statusString()); dir.deviceIds.clear(); - CPPUNIT_ASSERT(!dir.assignStatus(QStringLiteral("idle"), updateTime += TimeSpan::fromMinutes(1.5))); + CPPUNIT_ASSERT(!dir.assignStatus(QStringLiteral("idle"), updateEvent += 1, updateTime += TimeSpan::fromMinutes(1.5))); CPPUNIT_ASSERT_EQUAL_MESSAGE("dir considered unshared when no devs present", QStringLiteral("unshared"), dir.statusString()); - CPPUNIT_ASSERT(!dir.assignStatus(SyncthingDirStatus::Idle, updateTime += TimeSpan::fromMinutes(1.5))); + CPPUNIT_ASSERT(!dir.assignStatus(SyncthingDirStatus::Idle, updateEvent += 1, updateTime += TimeSpan::fromMinutes(1.5))); CPPUNIT_ASSERT_EQUAL_MESSAGE("dir considered unshared when no devs present", QStringLiteral("unshared"), dir.statusString()); - - updateTime += TimeSpan::fromMinutes(1.5); - CPPUNIT_ASSERT_MESSAGE("same status again not considered an update", !dir.assignStatus(QStringLiteral("idle"), updateTime)); + CPPUNIT_ASSERT_MESSAGE("same status again not considered an update", + !dir.assignStatus(QStringLiteral("idle"), updateEvent += 1, updateTime += TimeSpan::fromMinutes(1.5))); }