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.)
This commit is contained in:
Martchus 2023-04-14 23:26:03 +02:00
parent ecfb346344
commit 843f164df1
7 changed files with 181 additions and 150 deletions

View File

@ -9,6 +9,8 @@
namespace Data { namespace Data {
using SyncthingEventId = quint64;
struct LIB_SYNCTHING_CONNECTOR_EXPORT SyncthingCompletion { struct LIB_SYNCTHING_CONNECTOR_EXPORT SyncthingCompletion {
CppUtilities::DateTime lastUpdate; CppUtilities::DateTime lastUpdate;
double percentage = 0; 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);
constexpr Needed &operator-=(const Needed &other); constexpr Needed &operator-=(const Needed &other);
} needed; } needed;
bool requested = false; SyncthingEventId requestedForEventId = 0;
constexpr SyncthingCompletion &operator+=(const SyncthingCompletion &other); constexpr SyncthingCompletion &operator+=(const SyncthingCompletion &other);
constexpr SyncthingCompletion &operator-=(const SyncthingCompletion &other); constexpr SyncthingCompletion &operator-=(const SyncthingCompletion &other);
void recomputePercentage(); void recomputePercentage();

View File

@ -441,7 +441,9 @@ void SyncthingConnection::continueReconnecting()
m_hasDiskEvents = false; m_hasDiskEvents = false;
m_dirs.clear(); m_dirs.clear();
m_devs.clear(); m_devs.clear();
m_lastConnectionsUpdate = DateTime(); m_lastConnectionsUpdateEvent = 0;
m_lastConnectionsUpdateTime = DateTime();
m_lastFileEvent = 0;
m_lastFileTime = DateTime(); m_lastFileTime = DateTime();
m_lastErrorTime = DateTime(); m_lastErrorTime = DateTime();
m_startTime = DateTime(); m_startTime = DateTime();

View File

@ -295,25 +295,27 @@ private Q_SLOTS:
void readErrors(); void readErrors();
void readClearingErrors(); void readClearingErrors();
void readEvents(); void readEvents();
void readEventsFromJsonArray(const QJsonArray &events, int &idVariable); void readEventsFromJsonArray(const QJsonArray &events, quint64 &idVariable);
void readStartingEvent(const QJsonObject &eventData); void readStartingEvent(const QJsonObject &eventData);
void readStatusChangedEvent(CppUtilities::DateTime eventTime, const QJsonObject &eventData); void readStatusChangedEvent(SyncthingEventId eventId, CppUtilities::DateTime eventTime, const QJsonObject &eventData);
void readDownloadProgressEvent(CppUtilities::DateTime eventTime, const QJsonObject &eventData); void readDownloadProgressEvent(const QJsonObject &eventData);
void readDirEvent(CppUtilities::DateTime eventTime, const QString &eventType, const QJsonObject &eventData); void readDirEvent(SyncthingEventId eventId, CppUtilities::DateTime eventTime, const QString &eventType, const QJsonObject &eventData);
void readDeviceEvent(CppUtilities::DateTime eventTime, const QString &eventType, const QJsonObject &eventData); void readDeviceEvent(SyncthingEventId eventId, CppUtilities::DateTime eventTime, const QString &eventType, const QJsonObject &eventData);
void readItemStarted(CppUtilities::DateTime eventTime, const QJsonObject &eventData); void readItemStarted(SyncthingEventId eventId, CppUtilities::DateTime eventTime, const QJsonObject &eventData);
void readItemFinished(CppUtilities::DateTime eventTime, const QJsonObject &eventData); void readItemFinished(SyncthingEventId eventId, CppUtilities::DateTime eventTime, const QJsonObject &eventData);
void readFolderErrors(CppUtilities::DateTime eventTime, const QJsonObject &eventData, Data::SyncthingDir &dirInfo, int index); void readFolderErrors(
void readFolderCompletion( SyncthingEventId eventId, CppUtilities::DateTime eventTime, const QJsonObject &eventData, Data::SyncthingDir &dirInfo, int index);
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 &dirId,
void readFolderCompletion(CppUtilities::DateTime eventTime, const QJsonObject &eventData, const QString &devId, Data::SyncthingDev *devInfo, Data::SyncthingDir *dirInfo, int dirIndex);
int devIndex, const QString &dirId, Data::SyncthingDir *dirInfo, int dirIndex); void readFolderCompletion(SyncthingEventId eventId, CppUtilities::DateTime eventTime, const QJsonObject &eventData, const QString &devId,
void readLocalFolderCompletion(CppUtilities::DateTime eventTime, const QJsonObject &eventData, Data::SyncthingDir &dirInfo, int index); 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, void readRemoteFolderCompletion(CppUtilities::DateTime eventTime, const QJsonObject &eventData, const QString &devId, Data::SyncthingDev *devInfo,
int devIndex, const QString &dirId, Data::SyncthingDir *dirInfo, int dirIndex); int devIndex, const QString &dirId, Data::SyncthingDir *dirInfo, int dirIndex);
void readRemoteFolderCompletion(const Data::SyncthingCompletion &completion, const QString &devId, Data::SyncthingDev *devInfo, int devIndex, void readRemoteFolderCompletion(const Data::SyncthingCompletion &completion, const QString &devId, Data::SyncthingDev *devInfo, int devIndex,
const QString &dirId, Data::SyncthingDir *dirInfo, int dirIndex); 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 readPostConfig();
void readRescan(); void readRescan();
void readDevPauseResume(); void readDevPauseResume();
@ -322,7 +324,8 @@ private Q_SLOTS:
void readShutdown(); void readShutdown();
void readDirStatus(); void readDirStatus();
void readDirPullErrors(); 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 readDirRejected(CppUtilities::DateTime eventTime, const QString &dirId, const QJsonObject &eventData);
void readDevRejected(CppUtilities::DateTime eventTime, const QString &devId, const QJsonObject &eventData); void readDevRejected(CppUtilities::DateTime eventTime, const QString &devId, const QJsonObject &eventData);
void readCompletion(); void readCompletion();
@ -388,8 +391,8 @@ private:
bool m_connectionAborted; bool m_connectionAborted;
bool m_abortingToReconnect; bool m_abortingToReconnect;
bool m_requestCompletion; bool m_requestCompletion;
int m_lastEventId; SyncthingEventId m_lastEventId;
int m_lastDiskEventId; SyncthingEventId m_lastDiskEventId;
QTimer m_trafficPollTimer; QTimer m_trafficPollTimer;
QTimer m_devStatsPollTimer; QTimer m_devStatsPollTimer;
QTimer m_errorsPollTimer; QTimer m_errorsPollTimer;
@ -421,7 +424,9 @@ private:
bool m_hasDiskEvents; bool m_hasDiskEvents;
std::vector<SyncthingDir> m_dirs; std::vector<SyncthingDir> m_dirs;
std::vector<SyncthingDev> m_devs; std::vector<SyncthingDev> m_devs;
CppUtilities::DateTime m_lastConnectionsUpdate; SyncthingEventId m_lastConnectionsUpdateEvent;
CppUtilities::DateTime m_lastConnectionsUpdateTime;
SyncthingEventId m_lastFileEvent = 0;
CppUtilities::DateTime m_lastFileTime; CppUtilities::DateTime m_lastFileTime;
CppUtilities::DateTime m_lastErrorTime; CppUtilities::DateTime m_lastErrorTime;
CppUtilities::DateTime m_startTime; CppUtilities::DateTime m_startTime;

View File

@ -18,6 +18,7 @@
#include <QTimer> #include <QTimer>
#include <QUrlQuery> #include <QUrlQuery>
#include <algorithm>
#include <iostream> #include <iostream>
#include <utility> #include <utility>
@ -808,8 +809,9 @@ void SyncthingConnection::requestConnections()
if (m_connectionsReply) { if (m_connectionsReply) {
return; return;
} }
QObject::connect(m_connectionsReply = requestData(QStringLiteral("system/connections"), QUrlQuery()), &QNetworkReply::finished, this, m_connectionsReply = requestData(QStringLiteral("system/connections"), QUrlQuery());
&SyncthingConnection::readConnections); 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; const std::uint64_t totalOutgoingTraffic = totalOutgoingTrafficValue.isDouble() ? jsonValueToInt(totalOutgoingTrafficValue) : unknownTraffic;
double transferTime = 0.0; double transferTime = 0.0;
const bool hasDelta 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) m_totalIncomingRate = (hasDelta && totalIncomingTraffic != unknownTraffic && m_totalIncomingTraffic != unknownTraffic)
? static_cast<double>(totalIncomingTraffic - m_totalIncomingTraffic) * 0.008 / transferTime ? static_cast<double>(totalIncomingTraffic - m_totalIncomingTraffic) * 0.008 / transferTime
: 0.0; : 0.0;
@ -886,7 +888,8 @@ void SyncthingConnection::readConnections()
++index; ++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 // since there seems no event for this data, keep polling
if (m_keepPolling) { if (m_keepPolling) {
@ -980,8 +983,9 @@ void SyncthingConnection::requestDirStatistics()
if (m_dirStatsReply) { if (m_dirStatsReply) {
return; return;
} }
QObject::connect(m_dirStatsReply = requestData(QStringLiteral("stats/folder"), QUrlQuery()), &QNetworkReply::finished, this, m_dirStatsReply = requestData(QStringLiteral("stats/folder"), QUrlQuery());
&SyncthingConnection::readDirStatistics); m_dirStatsReply->setProperty("lastEventId", m_lastEventId);
QObject::connect(m_dirStatsReply, &QNetworkReply::finished, this, &SyncthingConnection::readDirStatistics);
} }
/*! /*!
@ -1012,20 +1016,23 @@ void SyncthingConnection::readDirStatistics()
continue; continue;
} }
bool dirModified = false; auto dirModified = false;
const auto eventId = reply->property("lastEventId").toULongLong();
const auto lastScan = dirObj.value(QLatin1String("lastScan")).toString().toUtf8(); const auto lastScan = dirObj.value(QLatin1String("lastScan")).toString().toUtf8();
if (!lastScan.isEmpty()) { if (!lastScan.isEmpty()) {
dirModified = true; dirModified = true;
dirInfo.lastScanTime = parseTimeStamp(dirObj.value(QLatin1String("lastScan")), QStringLiteral("last scan")); 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()) { if (!lastFileObj.isEmpty()) {
dirInfo.lastFileEvent = eventId;
dirInfo.lastFileName = lastFileObj.value(QLatin1String("filename")).toString(); dirInfo.lastFileName = lastFileObj.value(QLatin1String("filename")).toString();
dirModified = true; dirModified = true;
if (!dirInfo.lastFileName.isEmpty()) { if (!dirInfo.lastFileName.isEmpty()) {
dirInfo.lastFileDeleted = lastFileObj.value(QLatin1String("deleted")).toBool(false); dirInfo.lastFileDeleted = lastFileObj.value(QLatin1String("deleted")).toBool(false);
dirInfo.lastFileTime = parseTimeStamp(lastFileObj.value(QLatin1String("at")), QStringLiteral("dir statistics")); 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_lastFileTime = dirInfo.lastFileTime;
m_lastFileName = dirInfo.lastFileName; m_lastFileName = dirInfo.lastFileName;
m_lastFileDeleted = dirInfo.lastFileDeleted; m_lastFileDeleted = dirInfo.lastFileDeleted;
@ -1060,6 +1067,7 @@ void SyncthingConnection::requestDirStatus(const QString &dirId)
query.addQueryItem(QStringLiteral("folder"), dirId); query.addQueryItem(QStringLiteral("folder"), dirId);
auto *const reply = requestData(QStringLiteral("db/status"), query); auto *const reply = requestData(QStringLiteral("db/status"), query);
reply->setProperty("dirId", dirId); reply->setProperty("dirId", dirId);
reply->setProperty("lastEventId", m_lastEventId);
m_otherReplies << reply; m_otherReplies << reply;
QObject::connect(reply, &QNetworkReply::finished, this, &SyncthingConnection::readDirStatus, Qt::QueuedConnection); QObject::connect(reply, &QNetworkReply::finished, this, &SyncthingConnection::readDirStatus, Qt::QueuedConnection);
} }
@ -1078,7 +1086,7 @@ void SyncthingConnection::readDirStatus()
case QNetworkReply::NoError: { case QNetworkReply::NoError: {
// determine relevant dir // determine relevant dir
int index; int index;
const QString dirId(reply->property("dirId").toString()); const auto dirId = reply->property("dirId").toString();
SyncthingDir *const dir = findDirInfo(dirId, index); SyncthingDir *const dir = findDirInfo(dirId, index);
if (!dir) { if (!dir) {
// discard status for unknown dirs // discard status for unknown dirs
@ -1093,7 +1101,7 @@ void SyncthingConnection::readDirStatus()
return; return;
} }
readDirSummary(DateTime::now(), replyDoc.object(), *dir, index); readDirSummary(reply->property("lastEventId").toULongLong(), DateTime::now(), replyDoc.object(), *dir, index);
if (m_keepPolling) { if (m_keepPolling) {
concludeConnection(); concludeConnection();
@ -1123,6 +1131,7 @@ void SyncthingConnection::requestDirPullErrors(const QString &dirId, int page, i
} }
auto *const reply = requestData(folderErrorsPath(), query); auto *const reply = requestData(folderErrorsPath(), query);
reply->setProperty("dirId", dirId); reply->setProperty("dirId", dirId);
reply->setProperty("lastEventId", m_lastEventId);
QObject::connect(reply, &QNetworkReply::finished, this, &SyncthingConnection::readDirPullErrors); QObject::connect(reply, &QNetworkReply::finished, this, &SyncthingConnection::readDirPullErrors);
} }
@ -1155,7 +1164,7 @@ void SyncthingConnection::readDirPullErrors()
return; return;
} }
readFolderErrors(DateTime::now(), replyDoc.object(), *dir, index); readFolderErrors(reply->property("lastEventId").toULongLong(), DateTime::now(), replyDoc.object(), *dir, index);
break; break;
} }
case QNetworkReply::OperationCanceledError: 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) static void ensureCompletionNotConsideredRequested(const QString &devId, SyncthingDev *devInfo, const QString &dirId, SyncthingDir *dirInfo)
{ {
if (devInfo) { if (devInfo) {
devInfo->completionByDir[dirId].requested = false; devInfo->completionByDir[dirId].requestedForEventId = 0;
} }
if (dirInfo) { if (dirInfo) {
dirInfo->completionByDevice[devId].requested = false; dirInfo->completionByDevice[devId].requestedForEventId = 0;
} }
} }
/// \endcond /// \endcond
@ -1543,9 +1552,9 @@ void SyncthingConnection::readPostConfig()
/*! /*!
* \brief Reads data from requestDirStatus() and FolderSummary-event and stores them to \a dir. * \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; return;
} }
@ -1553,7 +1562,7 @@ void SyncthingConnection::readDirSummary(DateTime eventTime, const QJsonObject &
auto &globalStats(dir.globalStats); auto &globalStats(dir.globalStats);
auto &localStats(dir.localStats); auto &localStats(dir.localStats);
auto &neededStats(dir.neededStats); auto &neededStats(dir.neededStats);
const auto previouslyUpdated(!dir.lastStatisticsUpdate.isNull()); const auto previouslyUpdated(!dir.lastStatisticsUpdateTime.isNull());
const auto previouslyGlobal(globalStats); const auto previouslyGlobal(globalStats);
const auto previouslyNeeded(neededStats); const auto previouslyNeeded(neededStats);
@ -1577,17 +1586,19 @@ void SyncthingConnection::readDirSummary(DateTime eventTime, const QJsonObject &
m_dirStatsAltered = true; m_dirStatsAltered = true;
dir.ignorePatterns = summary.value(QLatin1String("ignorePatterns")).toBool(); dir.ignorePatterns = summary.value(QLatin1String("ignorePatterns")).toBool();
dir.lastStatisticsUpdate = eventTime; dir.lastStatisticsUpdateEvent = eventId;
dir.lastStatisticsUpdateTime = eventTime;
// update status // 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) { if (dir.pullErrorCount) {
// consider the directory still as out-of-sync if there are still pull errors // consider the directory still as out-of-sync if there are still pull errors
// note: Syncthing can report an "idle" status despite pull errors. // note: Syncthing can report an "idle" status despite pull errors.
dir.status = SyncthingDirStatus::OutOfSync; 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()) { } 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<int>((globalStats.bytes - neededStats.bytes) * 100 / globalStats.bytes) : 100; dir.completionPercentage = globalStats.bytes ? static_cast<int>((globalStats.bytes - neededStats.bytes) * 100 / globalStats.bytes) : 100;
@ -1746,32 +1757,34 @@ void SyncthingConnection::readEvents()
/*! /*!
* \brief Reads results of requestEvents(). * \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) { for (const auto &eventVal : events) {
const auto event = eventVal.toObject(); const auto event = eventVal.toObject();
const auto eventTime = parseTimeStamp(event.value(QLatin1String("time")), QStringLiteral("event time")); const auto eventTime = parseTimeStamp(event.value(QLatin1String("time")), QStringLiteral("event time"));
const auto eventType = event.value(QLatin1String("type")).toString(); const auto eventType = event.value(QLatin1String("type")).toString();
const auto eventData = event.value(QLatin1String("data")).toObject(); const auto eventData = event.value(QLatin1String("data")).toObject();
const auto eventIdValue = event.value(QLatin1String("id"));
idVariable = event.value(QLatin1String("id")).toInt(idVariable); const auto eventId = static_cast<quint64>(std::max(eventIdValue.toDouble(), 0.0));
if (eventIdValue.isDouble()) {
idVariable = eventId;
}
if (eventType == QLatin1String("Starting")) { if (eventType == QLatin1String("Starting")) {
readStartingEvent(eventData); readStartingEvent(eventData);
} else if (eventType == QLatin1String("StateChanged")) { } else if (eventType == QLatin1String("StateChanged")) {
readStatusChangedEvent(eventTime, eventData); readStatusChangedEvent(eventId, eventTime, eventData);
} else if (eventType == QLatin1String("DownloadProgress")) { } else if (eventType == QLatin1String("DownloadProgress")) {
readDownloadProgressEvent(eventTime, eventData); readDownloadProgressEvent(eventData);
} else if (eventType.startsWith(QLatin1String("Folder"))) { } else if (eventType.startsWith(QLatin1String("Folder"))) {
readDirEvent(eventTime, eventType, eventData); readDirEvent(eventId, eventTime, eventType, eventData);
} else if (eventType.startsWith(QLatin1String("Device"))) { } else if (eventType.startsWith(QLatin1String("Device"))) {
readDeviceEvent(eventTime, eventType, eventData); readDeviceEvent(eventId, eventTime, eventType, eventData);
} else if (eventType == QLatin1String("ItemStarted")) { } else if (eventType == QLatin1String("ItemStarted")) {
readItemStarted(eventTime, eventData); readItemStarted(eventId, eventTime, eventData);
} else if (eventType == QLatin1String("ItemFinished")) { } else if (eventType == QLatin1String("ItemFinished")) {
readItemFinished(eventTime, eventData); readItemFinished(eventId, eventTime, eventData);
} else if (eventType == QLatin1String("RemoteIndexUpdated")) { } else if (eventType == QLatin1String("RemoteIndexUpdated")) {
readRemoteIndexUpdated(eventTime, eventData); readRemoteIndexUpdated(eventId, eventData);
} else if (eventType == QLatin1String("ConfigSaved")) { } else if (eventType == QLatin1String("ConfigSaved")) {
requestConfig(); // just consider current config as invalidated requestConfig(); // just consider current config as invalidated
} else if (eventType.endsWith(QLatin1String("ChangeDetected"))) { } else if (eventType.endsWith(QLatin1String("ChangeDetected"))) {
@ -1796,7 +1809,7 @@ void SyncthingConnection::readStartingEvent(const QJsonObject &eventData)
/*! /*!
* \brief Reads results of requestEvents(). * \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()); const QString dir(eventData.value(QLatin1String("folder")).toString());
if (dir.isEmpty()) { if (dir.isEmpty()) {
@ -1815,7 +1828,7 @@ void SyncthingConnection::readStatusChangedEvent(DateTime eventTime, const QJson
} }
// assign new status // 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) { if (dirInfo->status == SyncthingDirStatus::OutOfSync) {
const QString errorMessage(eventData.value(QLatin1String("error")).toString()); const QString errorMessage(eventData.value(QLatin1String("error")).toString());
if (!errorMessage.isEmpty()) { if (!errorMessage.isEmpty()) {
@ -1837,9 +1850,8 @@ void SyncthingConnection::readStatusChangedEvent(DateTime eventTime, const QJson
/*! /*!
* \brief Reads results of requestEvents(). * \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) { for (SyncthingDir &dirInfo : m_dirs) {
// disappearing implies that the download has been finished so just wipe old entries // disappearing implies that the download has been finished so just wipe old entries
dirInfo.downloadingItems.clear(); dirInfo.downloadingItems.clear();
@ -1877,7 +1889,7 @@ void SyncthingConnection::readDownloadProgressEvent(DateTime eventTime, const QJ
/*! /*!
* \brief Reads results of requestEvents(). * \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 // read dir ID
const auto dirId([&eventData] { const auto dirId([&eventData] {
@ -1890,7 +1902,7 @@ void SyncthingConnection::readDirEvent(DateTime eventTime, const QString &eventT
if (dirId.isEmpty()) { if (dirId.isEmpty()) {
// handle events which don't necessarily require a corresponding dir info // handle events which don't necessarily require a corresponding dir info
if (eventType == QLatin1String("FolderCompletion")) { if (eventType == QLatin1String("FolderCompletion")) {
readFolderCompletion(eventTime, eventData, dirId, nullptr, -1); readFolderCompletion(eventId, eventTime, eventData, dirId, nullptr, -1);
} }
return; return;
} }
@ -1910,11 +1922,11 @@ void SyncthingConnection::readDirEvent(DateTime eventTime, const QString &eventT
// distinguish specific events // distinguish specific events
if (eventType == QLatin1String("FolderErrors")) { if (eventType == QLatin1String("FolderErrors")) {
readFolderErrors(eventTime, eventData, *dirInfo, index); readFolderErrors(eventId, eventTime, eventData, *dirInfo, index);
} else if (eventType == QLatin1String("FolderSummary")) { } else if (eventType == QLatin1String("FolderSummary")) {
readDirSummary(eventTime, eventData.value(QLatin1String("summary")).toObject(), *dirInfo, index); readDirSummary(eventId, eventTime, eventData.value(QLatin1String("summary")).toObject(), *dirInfo, index);
} else if (eventType == QLatin1String("FolderCompletion") && dirInfo->lastStatisticsUpdate < eventTime) { } else if (eventType == QLatin1String("FolderCompletion") && dirInfo->lastStatisticsUpdateEvent <= eventId) {
readFolderCompletion(eventTime, eventData, dirId, dirInfo, index); readFolderCompletion(eventId, eventTime, eventData, dirId, dirInfo, index);
} else if (eventType == QLatin1String("FolderScanProgress")) { } else if (eventType == QLatin1String("FolderScanProgress")) {
const double current = eventData.value(QLatin1String("current")).toDouble(0); const double current = eventData.value(QLatin1String("current")).toDouble(0);
const double total = eventData.value(QLatin1String("total")).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) { if (current > 0 && total > 0) {
dirInfo->scanningPercentage = static_cast<int>(current * 100 / total); dirInfo->scanningPercentage = static_cast<int>(current * 100 / total);
dirInfo->scanningRate = rate; 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); emit dirStatusChanged(*dirInfo, index);
} }
} else if (eventType == QLatin1String("FolderPaused")) { } else if (eventType == QLatin1String("FolderPaused")) {
@ -1941,10 +1953,10 @@ void SyncthingConnection::readDirEvent(DateTime eventTime, const QString &eventT
/*! /*!
* \brief Reads results of requestEvents(). * \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 // ignore device events happened before the last connections update
if (eventTime.isNull() && m_lastConnectionsUpdate.isNull() && eventTime < m_lastConnectionsUpdate) { if (eventId < m_lastConnectionsUpdateEvent) {
return; return;
} }
const QString dev(eventData.value(QLatin1String("device")).toString()); 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(). * \brief Reads results of requestEvents().
* \todo Implement this. * \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(eventTime)
CPP_UTILITIES_UNUSED(eventData) CPP_UTILITIES_UNUSED(eventData)
} }
@ -2013,7 +2026,7 @@ void SyncthingConnection::readItemStarted(DateTime eventTime, const QJsonObject
/*! /*!
* \brief Reads results of requestEvents(). * \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; int index;
auto *const dirInfo = findDirInfo(QLatin1String("folder"), eventData, &index); auto *const dirInfo = findDirInfo(QLatin1String("folder"), eventData, &index);
@ -2046,11 +2059,13 @@ void SyncthingConnection::readItemFinished(DateTime eventTime, const QJsonObject
} }
// update last file // update last file
if (dirInfo->lastFileTime.isNull() || eventTime < dirInfo->lastFileTime) { if (dirInfo->lastFileTime.isNull() || eventId >= dirInfo->lastFileEvent) {
dirInfo->lastFileEvent = eventId;
dirInfo->lastFileTime = eventTime; dirInfo->lastFileTime = eventTime;
dirInfo->lastFileName = item; dirInfo->lastFileName = item;
dirInfo->lastFileDeleted = (eventData.value(QLatin1String("action")) != QLatin1String("delete")); dirInfo->lastFileDeleted = (eventData.value(QLatin1String("action")) != QLatin1String("delete"));
if (eventTime > m_lastFileTime) { if (eventId >= m_lastFileEvent) {
m_lastFileEvent = eventId;
m_lastFileTime = dirInfo->lastFileTime; m_lastFileTime = dirInfo->lastFileTime;
m_lastFileName = dirInfo->lastFileName; m_lastFileName = dirInfo->lastFileName;
m_lastFileDeleted = dirInfo->lastFileDeleted; m_lastFileDeleted = dirInfo->lastFileDeleted;
@ -2062,10 +2077,11 @@ void SyncthingConnection::readItemFinished(DateTime eventTime, const QJsonObject
/*! /*!
* \brief Reads results of requestEvents() and requestDirPullErrors(). * \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) // 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; return;
} }
@ -2090,7 +2106,7 @@ void SyncthingConnection::readFolderErrors(DateTime eventTime, const QJsonObject
// ensure the directory is considered out-of-sync // ensure the directory is considered out-of-sync
if (dirInfo.pullErrorCount) { if (dirInfo.pullErrorCount) {
dirInfo.assignStatus(SyncthingDirStatus::OutOfSync, eventTime); dirInfo.assignStatus(SyncthingDirStatus::OutOfSync, eventId, eventTime);
} }
emit dirStatusChanged(dirInfo, index); emit dirStatusChanged(dirInfo, index);
@ -2100,44 +2116,46 @@ void SyncthingConnection::readFolderErrors(DateTime eventTime, const QJsonObject
* \brief Reads results of requestEvents(). * \brief Reads results of requestEvents().
*/ */
void SyncthingConnection::readFolderCompletion( 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(); const auto devId = eventData.value(QLatin1String("device")).toString();
int devIndex; int devIndex;
auto *const devInfo = findDevInfo(devId, 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(). * \brief Reads results of requestEvents().
*/ */
void SyncthingConnection::readFolderCompletion(DateTime eventTime, const QJsonObject &eventData, const QString &devId, SyncthingDev *devInfo, void SyncthingConnection::readFolderCompletion(SyncthingEventId eventId, DateTime eventTime, const QJsonObject &eventData, const QString &devId,
int devIndex, const QString &dirId, SyncthingDir *dirInfo, int dirIndex) SyncthingDev *devInfo, int devIndex, const QString &dirId, SyncthingDir *dirInfo, int dirIndex)
{ {
if (devInfo && !devId.isEmpty() && devId != myId()) { if (devInfo && !devId.isEmpty() && devId != myId()) {
readRemoteFolderCompletion(eventTime, eventData, devId, devInfo, devIndex, dirId, dirInfo, dirIndex); readRemoteFolderCompletion(eventTime, eventData, devId, devInfo, devIndex, dirId, dirInfo, dirIndex);
} else if (dirInfo) { } else if (dirInfo) {
readLocalFolderCompletion(eventTime, eventData, *dirInfo, dirIndex); readLocalFolderCompletion(eventId, eventTime, eventData, *dirInfo, dirIndex);
} }
} }
/*! /*!
* \brief Reads results of requestEvents(). * \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 &neededStats(dirInfo.neededStats);
auto &globalStats(dirInfo.globalStats); auto &globalStats(dirInfo.globalStats);
// backup previous statistics -> if there's no difference after all, don't emit completed event // backup previous statistics -> if there's no difference after all, don't emit completed event
const auto previouslyUpdated(!dirInfo.lastStatisticsUpdate.isNull()); const auto previouslyUpdated = !dirInfo.lastStatisticsUpdateTime.isNull();
const auto previouslyNeeded(neededStats); const auto previouslyNeeded = neededStats;
const auto previouslyGlobal(globalStats); const auto previouslyGlobal = globalStats;
// read values from event data // read values from event data
globalStats.bytes = jsonValueToInt(eventData.value(QLatin1String("globalBytes")), static_cast<double>(globalStats.bytes)); globalStats.bytes = jsonValueToInt(eventData.value(QLatin1String("globalBytes")), static_cast<double>(globalStats.bytes));
neededStats.bytes = jsonValueToInt(eventData.value(QLatin1String("needBytes")), static_cast<double>(neededStats.bytes)); neededStats.bytes = jsonValueToInt(eventData.value(QLatin1String("needBytes")), static_cast<double>(neededStats.bytes));
neededStats.deletes = jsonValueToInt(eventData.value(QLatin1String("needDeletes")), static_cast<double>(neededStats.deletes)); neededStats.deletes = jsonValueToInt(eventData.value(QLatin1String("needDeletes")), static_cast<double>(neededStats.deletes));
neededStats.deletes = jsonValueToInt(eventData.value(QLatin1String("needItems")), static_cast<double>(neededStats.files)); neededStats.deletes = jsonValueToInt(eventData.value(QLatin1String("needItems")), static_cast<double>(neededStats.files));
dirInfo.lastStatisticsUpdate = eventTime; dirInfo.lastStatisticsUpdateEvent = eventId;
dirInfo.lastStatisticsUpdateTime = eventTime;
dirInfo.completionPercentage = globalStats.bytes ? static_cast<int>((globalStats.bytes - neededStats.bytes) * 100 / globalStats.bytes) : 100; dirInfo.completionPercentage = globalStats.bytes ? static_cast<int>((globalStats.bytes - neededStats.bytes) * 100 / globalStats.bytes) : 100;
emit dirStatusChanged(dirInfo, index); emit dirStatusChanged(dirInfo, index);
if (neededStats.isNull() && previouslyUpdated && (neededStats != previouslyNeeded || globalStats != previouslyGlobal) if (neededStats.isNull() && previouslyUpdated && (neededStats != previouslyNeeded || globalStats != previouslyGlobal)
@ -2154,7 +2172,7 @@ void SyncthingConnection::readRemoteFolderCompletion(DateTime eventTime, const Q
{ {
// make new completion // make new completion
auto completion = SyncthingCompletion(); auto completion = SyncthingCompletion();
auto &needed(completion.needed); auto &needed = completion.needed;
completion.lastUpdate = eventTime; completion.lastUpdate = eventTime;
completion.percentage = eventData.value(QLatin1String("completion")).toDouble(); completion.percentage = eventData.value(QLatin1String("completion")).toDouble();
completion.globalBytes = jsonValueToInt(eventData.value(QLatin1String("globalBytes"))); completion.globalBytes = jsonValueToInt(eventData.value(QLatin1String("globalBytes")));
@ -2201,7 +2219,7 @@ void SyncthingConnection::readRemoteFolderCompletion(const SyncthingCompletion &
/*! /*!
* \brief Reads results of requestEvents(). * \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 // ignore those events if we're not updating completion automatically
if (!m_requestCompletion && !m_keepPolling) { if (!m_requestCompletion && !m_keepPolling) {
@ -2234,20 +2252,15 @@ void SyncthingConnection::readRemoteIndexUpdated(DateTime eventTime, const QJson
// might meddle with that. // might meddle with that.
auto *const completionFromDirInfo = dirInfo ? &dirInfo->completionByDevice[devId] : nullptr; auto *const completionFromDirInfo = dirInfo ? &dirInfo->completionByDevice[devId] : nullptr;
auto *const completionFromDevInfo = devInfo ? &devInfo->completionByDir[dirId] : nullptr; auto *const completionFromDevInfo = devInfo ? &devInfo->completionByDir[dirId] : nullptr;
if ((completionFromDirInfo && completionFromDirInfo->requested) || (completionFromDevInfo && completionFromDevInfo->requested)) { if ((completionFromDirInfo && completionFromDirInfo->requestedForEventId >= eventId)
return; || (completionFromDevInfo && completionFromDevInfo->requestedForEventId >= eventId)) {
}
const auto lastUpdate = completionFromDirInfo && completionFromDevInfo
? min(completionFromDirInfo->lastUpdate, completionFromDevInfo->lastUpdate)
: (completionFromDirInfo ? completionFromDirInfo->lastUpdate : completionFromDevInfo->lastUpdate);
if (lastUpdate >= eventTime) {
return; return;
} }
if (completionFromDirInfo) { if (completionFromDirInfo) {
completionFromDirInfo->requested = true; completionFromDirInfo->requestedForEventId = eventId;
} }
if (completionFromDevInfo) { if (completionFromDevInfo) {
completionFromDevInfo->requested = true; completionFromDevInfo->requestedForEventId = eventId;
} }
if (devInfo && dirInfo && !devInfo->paused && !dirInfo->paused) { if (devInfo && dirInfo && !devInfo->paused && !dirInfo->paused) {
requestCompletion(devId, dirId); requestCompletion(devId, dirId);

View File

@ -52,27 +52,30 @@ QString dirTypeString(SyncthingDirType dirType)
return QString(); return QString();
} }
bool SyncthingDir::checkWhetherStatusUpdateRelevant(DateTime time) bool SyncthingDir::checkWhetherStatusUpdateRelevant(SyncthingEventId eventId, DateTime time)
{ {
// ignore old updates // ignore old updates
if (lastStatusUpdate > time) { if (lastStatusUpdateEvent > eventId) {
return false; return false;
} }
lastStatusUpdate = time; lastStatusUpdateEvent = eventId;
lastStatusUpdateTime = time;
return true; 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 // 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) { if (newStatus == SyncthingDirStatus::PreparingToSync || newStatus == SyncthingDirStatus::Synchronizing) {
// update time of last "sync" state and obsolete currently assigned errors // 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(); itemErrors.clear();
pullErrorCount = 0; 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 // 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 // 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. * \brief Assigns the status from the specified status string.
* \returns Returns whether the status has actually changed. * \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; return false;
} }
@ -147,7 +150,7 @@ bool SyncthingDir::assignStatus(const QString &statusStr, CppUtilities::DateTime
rawStatus = statusStr; rawStatus = statusStr;
return finalizeStatusUpdate(newStatus, time); return finalizeStatusUpdate(newStatus, eventId, time);
} }
bool SyncthingDir::assignDirType(const QString &dirTypeStr) bool SyncthingDir::assignDirType(const QString &dirTypeStr)

View File

@ -126,8 +126,8 @@ constexpr bool SyncthingStatistics::operator!=(const SyncthingStatistics &other)
struct LIB_SYNCTHING_CONNECTOR_EXPORT SyncthingDir { struct LIB_SYNCTHING_CONNECTOR_EXPORT SyncthingDir {
explicit SyncthingDir(const QString &id = QString(), const QString &label = QString(), const QString &path = QString()); explicit SyncthingDir(const QString &id = QString(), const QString &label = QString(), const QString &path = QString());
bool assignStatus(const QString &statusStr, CppUtilities::DateTime time); bool assignStatus(const QString &statusStr, SyncthingEventId eventId, CppUtilities::DateTime time);
bool assignStatus(SyncthingDirStatus newStatus, CppUtilities::DateTime time); bool assignStatus(SyncthingDirStatus newStatus, SyncthingEventId eventId, CppUtilities::DateTime time);
bool assignDirType(const QString &dirType); bool assignDirType(const QString &dirType);
const QString &displayName() const; const QString &displayName() const;
QString statusString() const; QString statusString() const;
@ -146,8 +146,10 @@ struct LIB_SYNCTHING_CONNECTOR_EXPORT SyncthingDir {
int rescanInterval = 0; int rescanInterval = 0;
int minDiskFreePercentage = 0; int minDiskFreePercentage = 0;
SyncthingDirStatus status = SyncthingDirStatus::Unknown; SyncthingDirStatus status = SyncthingDirStatus::Unknown;
CppUtilities::DateTime lastStatusUpdate; SyncthingEventId lastStatusUpdateEvent = 0;
CppUtilities::DateTime lastSyncStarted; CppUtilities::DateTime lastStatusUpdateTime;
SyncthingEventId lastSyncStartedEvent = 0;
CppUtilities::DateTime lastSyncStartedTime;
int completionPercentage = 0; int completionPercentage = 0;
int scanningPercentage = 0; int scanningPercentage = 0;
double scanningRate = 0; double scanningRate = 0;
@ -158,8 +160,10 @@ struct LIB_SYNCTHING_CONNECTOR_EXPORT SyncthingDir {
std::vector<SyncthingItemError> itemErrors; std::vector<SyncthingItemError> itemErrors;
std::vector<SyncthingFileChange> recentChanges; std::vector<SyncthingFileChange> recentChanges;
SyncthingStatistics globalStats, localStats, neededStats; SyncthingStatistics globalStats, localStats, neededStats;
CppUtilities::DateTime lastStatisticsUpdate; SyncthingEventId lastStatisticsUpdateEvent = 0;
CppUtilities::DateTime lastStatisticsUpdateTime;
CppUtilities::DateTime lastScanTime; CppUtilities::DateTime lastScanTime;
SyncthingEventId lastFileEvent;
CppUtilities::DateTime lastFileTime; CppUtilities::DateTime lastFileTime;
QString lastFileName; QString lastFileName;
std::vector<SyncthingItemDownloadProgress> downloadingItems; std::vector<SyncthingItemDownloadProgress> downloadingItems;
@ -177,8 +181,8 @@ struct LIB_SYNCTHING_CONNECTOR_EXPORT SyncthingDir {
bool paused = false; bool paused = false;
private: private:
bool checkWhetherStatusUpdateRelevant(CppUtilities::DateTime time); bool checkWhetherStatusUpdateRelevant(SyncthingEventId eventId, CppUtilities::DateTime time);
bool finalizeStatusUpdate(SyncthingDirStatus newStatus, CppUtilities::DateTime time); bool finalizeStatusUpdate(SyncthingDirStatus newStatus, SyncthingEventId eventId, CppUtilities::DateTime time);
}; };
inline SyncthingDir::SyncthingDir(const QString &id, const QString &label, const QString &path) 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); 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; return false;
} }
rawStatus.clear(); rawStatus.clear();
return finalizeStatusUpdate(newStatus, time); return finalizeStatusUpdate(newStatus, eventId, time);
} }
struct LIB_SYNCTHING_CONNECTOR_EXPORT SyncthingOverallDirStatistics { struct LIB_SYNCTHING_CONNECTOR_EXPORT SyncthingOverallDirStatistics {

View File

@ -199,74 +199,76 @@ void MiscTests::testSyncthingDir()
SyncthingDir dir; SyncthingDir dir;
dir.status = SyncthingDirStatus::Unknown; dir.status = SyncthingDirStatus::Unknown;
DateTime updateTime(DateTime::fromDate(2005, 2, 3)); auto updateEvent = static_cast<SyncthingEventId>(42);
CPPUNIT_ASSERT(dir.assignStatus(SyncthingDirStatus::Idle, updateTime)); auto updateTime = DateTime(DateTime::fromDate(2005, 2, 3));
CPPUNIT_ASSERT_EQUAL(QStringLiteral("unshared"), dir.statusString()); CPPUNIT_ASSERT(dir.assignStatus(SyncthingDirStatus::Idle, updateEvent, updateTime));
CPPUNIT_ASSERT_EQUAL(updateTime, dir.lastStatusUpdate); 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"); dir.deviceIds << QStringLiteral("dev1") << QStringLiteral("dev2");
CPPUNIT_ASSERT(!dir.assignStatus(SyncthingDirStatus::Scanning, DateTime::fromDate(2003, 6, 7))); CPPUNIT_ASSERT(!dir.assignStatus(SyncthingDirStatus::Scanning, updateEvent - 1, updateTime + TimeSpan::fromDays(1.0)));
CPPUNIT_ASSERT_EQUAL(updateTime, dir.lastStatusUpdate); CPPUNIT_ASSERT_EQUAL_MESSAGE("status not updated", QStringLiteral("idle"), dir.statusString());
CPPUNIT_ASSERT_EQUAL(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()); const auto lastScanTime = DateTime(DateTime::now());
updateTime += TimeSpan::fromSeconds(5); CPPUNIT_ASSERT(dir.assignStatus(SyncthingDirStatus::WaitingToScan, updateEvent += 1, updateTime += TimeSpan::fromSeconds(5)));
CPPUNIT_ASSERT(dir.assignStatus(SyncthingDirStatus::WaitingToScan, updateTime));
CPPUNIT_ASSERT(dir.lastScanTime.isNull()); CPPUNIT_ASSERT(dir.lastScanTime.isNull());
CPPUNIT_ASSERT_EQUAL(QStringLiteral("waiting to scan"), dir.statusString()); 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(dir.lastScanTime.isNull());
CPPUNIT_ASSERT_EQUAL(QStringLiteral("scanning"), dir.statusString()); CPPUNIT_ASSERT_EQUAL(QStringLiteral("scanning"), dir.statusString());
CPPUNIT_ASSERT(dir.assignStatus(SyncthingDirStatus::Idle, updateTime += TimeSpan::fromSeconds(2))); CPPUNIT_ASSERT(dir.assignStatus(SyncthingDirStatus::Idle, updateEvent += 1, updateTime += TimeSpan::fromSeconds(2)));
CPPUNIT_ASSERT_EQUAL(updateTime, dir.lastStatusUpdate); CPPUNIT_ASSERT_EQUAL_MESSAGE("event updated", updateEvent, dir.lastStatusUpdateEvent);
CPPUNIT_ASSERT_EQUAL_MESSAGE("time updated", updateTime, dir.lastStatusUpdateTime);
CPPUNIT_ASSERT(dir.lastScanTime >= lastScanTime); CPPUNIT_ASSERT(dir.lastScanTime >= lastScanTime);
dir.status = SyncthingDirStatus::Unknown; dir.status = SyncthingDirStatus::Unknown;
dir.lastSyncStarted = DateTime(1); dir.lastSyncStartedTime = DateTime(1);
dir.itemErrors.emplace_back(QStringLiteral("message"), QStringLiteral("path")); 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(QStringLiteral("idle"), dir.statusString());
CPPUNIT_ASSERT_EQUAL(1_st, dir.itemErrors.size()); CPPUNIT_ASSERT_EQUAL(1_st, dir.itemErrors.size());
dir.lastSyncStarted = DateTime(); dir.lastSyncStartedTime = DateTime();
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(updateTime, dir.lastSyncStarted); CPPUNIT_ASSERT_EQUAL(updateTime, dir.lastSyncStartedTime);
const auto lastSyncTime(updateTime += TimeSpan::fromMinutes(1.5)); const auto lastSyncTime = updateTime += TimeSpan::fromMinutes(1.5);
dir.itemErrors.emplace_back(); 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(QStringLiteral("synchronizing"), dir.statusString());
CPPUNIT_ASSERT_EQUAL(0_st, dir.itemErrors.size()); CPPUNIT_ASSERT_EQUAL(0_st, dir.itemErrors.size());
CPPUNIT_ASSERT_EQUAL(lastSyncTime, dir.lastSyncStarted); CPPUNIT_ASSERT_EQUAL(lastSyncTime, dir.lastSyncStartedTime);
const auto lastSyncTime2(updateTime += TimeSpan::fromMinutes(2.0)); const auto lastSyncTime2 = updateTime += TimeSpan::fromMinutes(2.0);
dir.itemErrors.emplace_back(); 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(QStringLiteral("preparing to sync"), dir.statusString());
CPPUNIT_ASSERT_EQUAL(0_st, dir.itemErrors.size()); 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(dir.assignStatus(SyncthingDirStatus::Idle, updateEvent += 1, updateTime += TimeSpan::fromMinutes(1.5)));
CPPUNIT_ASSERT_EQUAL(lastSyncTime2, dir.lastSyncStarted); CPPUNIT_ASSERT_EQUAL(lastSyncTime2, dir.lastSyncStartedTime);
CPPUNIT_ASSERT(dir.assignStatus(QStringLiteral("syncing"), updateTime += TimeSpan::fromMinutes(1.5))); CPPUNIT_ASSERT(dir.assignStatus(QStringLiteral("syncing"), updateEvent += 1, updateTime += TimeSpan::fromMinutes(1.5)));
CPPUNIT_ASSERT_EQUAL(updateTime, dir.lastSyncStarted); CPPUNIT_ASSERT_EQUAL(updateTime, dir.lastSyncStartedTime);
dir.itemErrors.clear(); 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_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_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()); CPPUNIT_ASSERT_EQUAL(QStringLiteral("idle"), dir.statusString());
dir.deviceIds.clear(); 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_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()); CPPUNIT_ASSERT_EQUAL_MESSAGE("dir considered unshared when no devs present", QStringLiteral("unshared"), dir.statusString());
CPPUNIT_ASSERT_MESSAGE("same status again not considered an update",
updateTime += TimeSpan::fromMinutes(1.5); !dir.assignStatus(QStringLiteral("idle"), updateEvent += 1, updateTime += TimeSpan::fromMinutes(1.5)));
CPPUNIT_ASSERT_MESSAGE("same status again not considered an update", !dir.assignStatus(QStringLiteral("idle"), updateTime));
} }