From 447928a0184beacea6348fa48fd20a63dfdbf3b7 Mon Sep 17 00:00:00 2001 From: Martchus Date: Wed, 24 Jan 2018 21:46:18 +0100 Subject: [PATCH] Request device/directory completion --- CMakeLists.txt | 2 +- connector/syncthingconnection.cpp | 106 +++++++++++++++++++++++++++++- connector/syncthingconnection.h | 25 +++++++ connector/syncthingdir.h | 10 +++ 4 files changed, 140 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 505c510..cfea7df 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,7 +10,7 @@ set(META_APP_CATEGORIES "System;Utility;Network;FileTransfer") set(META_GUI_OPTIONAL false) set(META_VERSION_MAJOR 0) set(META_VERSION_MINOR 7) -set(META_VERSION_PATCH 2) +set(META_VERSION_PATCH 3) set(META_VERSION_EXACT_SONAME ON) project(${META_PROJECT_NAME}) diff --git a/connector/syncthingconnection.cpp b/connector/syncthingconnection.cpp index 58537b6..a9d8073 100644 --- a/connector/syncthingconnection.cpp +++ b/connector/syncthingconnection.cpp @@ -56,6 +56,7 @@ SyncthingConnection::SyncthingConnection(const QString &syncthingUrl, const QByt , m_status(SyncthingStatus::Disconnected) , m_keepPolling(false) , m_reconnecting(false) + , m_requestCompletion(true) , m_lastEventId(0) , m_autoReconnectTries(0) , m_totalIncomingTraffic(unknownTraffic) @@ -671,6 +672,11 @@ void SyncthingConnection::continueConnecting() requestErrors(); for (const SyncthingDir &dir : m_dirs) { requestDirStatus(dir.id); + if (m_requestCompletion) { + for (const QString &devId : dir.deviceIds) { + requestCompletion(devId, dir.id); + } + } } // since config and status could be read successfully, let's poll for events m_lastEventId = 0; @@ -771,11 +777,25 @@ void SyncthingConnection::requestDirStatus(const QString &dirId) { QUrlQuery query; query.addQueryItem(QStringLiteral("folder"), dirId); - QNetworkReply *reply = requestData(QStringLiteral("db/status"), query); + auto *const reply = requestData(QStringLiteral("db/status"), query); reply->setProperty("dirId", dirId); QObject::connect(reply, &QNetworkReply::finished, this, &SyncthingConnection::readDirStatus); } +/*! + * \brief Requests completion for \a devId and \a dirId asynchronously. + */ +void SyncthingConnection::requestCompletion(const QString &devId, const QString &dirId) +{ + QUrlQuery query; + query.addQueryItem(QStringLiteral("device"), devId); + query.addQueryItem(QStringLiteral("folder"), dirId); + auto *const reply = requestData(QStringLiteral("db/completion"), query); + reply->setProperty("devId", devId); + reply->setProperty("dirId", dirId); + QObject::connect(reply, &QNetworkReply::finished, this, &SyncthingConnection::readCompletion); +} + /*! * \brief Requests device statistics asynchronously. */ @@ -1436,6 +1456,8 @@ void SyncthingConnection::readEvents() readItemStarted(eventTime, eventData); } else if (eventType == QLatin1String("ItemFinished")) { readItemFinished(eventTime, eventData); + } else if (eventType == QLatin1String("RemoteIndexUpdated")) { + readRemoteIndexUpdated(eventTime, eventData); } else if (eventType == QLatin1String("ConfigSaved")) { requestConfig(); // just consider current config as invalidated } @@ -1700,7 +1722,7 @@ void SyncthingConnection::readItemStarted(DateTime eventTime, const QJsonObject */ void SyncthingConnection::readItemFinished(DateTime eventTime, const QJsonObject &eventData) { - const QString dir(eventData.value(QStringLiteral("folder")).toString()); + const auto dir(eventData.value(QLatin1String("folder")).toString()); if (dir.isEmpty()) { return; } @@ -1728,6 +1750,40 @@ void SyncthingConnection::readItemFinished(DateTime eventTime, const QJsonObject } } +/*! + * \brief Reads results of requestEvents(). + */ +void SyncthingConnection::readRemoteIndexUpdated(DateTime eventTime, const QJsonObject &eventData) +{ + // ignore those events if we're not updating completion automatically + if (!m_requestCompletion) { + return; + } + + // find dev/dir + const auto devId(eventData.value(QLatin1String("device")).toString()); + const auto dirId(eventData.value(QLatin1String("folder")).toString()); + if (dirId.isEmpty()) { + return; + } + int index; + auto *const dirInfo = findDirInfo(dirId, index); + if (!dirInfo) { + return; + } + + // ignore event if we don't share the directory with the device + if (!dirInfo->deviceIds.contains(devId)) { + return; + } + + // request completion again if out-of-date + const auto &completion = dirInfo->completionByDevice[devId]; + if (completion.lastUpdate < eventTime) { + requestCompletion(devId, dirId); + } +} + /*! * \brief Reads results of rescan(). */ @@ -1896,6 +1952,52 @@ bool SyncthingConnection::readDirSummary(DateTime eventTime, const QJsonObject & return stateChanged; } +/*! + * \brief Reads data from requestCompletion(). + */ +void SyncthingConnection::readCompletion() +{ + auto *const reply = static_cast(sender()); + const auto devId(reply->property("devId").toString()); + const auto dirId(reply->property("dirId").toString()); + reply->deleteLater(); + + switch (reply->error()) { + case QNetworkReply::NoError: { + // determine relevant dev/dir + int index; + SyncthingDir *const dir = findDirInfo(dirId, index); + // discard status for unknown dirs + if (!dir) { + return; + } + + // parse JSON + QJsonParseError jsonError; + const auto response(reply->readAll()); + const auto replyDoc(QJsonDocument::fromJson(response, &jsonError)); + if (jsonError.error != QJsonParseError::NoError) { + emitError(tr("Unable to parse completion for device/directory %1/%2: ").arg(devId, dirId), jsonError, reply, response); + return; + } + + // update the relevant completion info + const auto replyObj(replyDoc.object()); + auto &completion = dir->completionByDevice[devId]; + completion.lastUpdate = DateTime::gmtNow(); + completion.percentage = replyObj.value(QLatin1String("completion")).toInt(); + completion.globalBytes = toUInt64(replyObj.value(QLatin1String("globalBytes"))); + completion.neededBytes = toUInt64(replyObj.value(QLatin1String("needBytes"))); + completion.neededItems = toUInt64(replyObj.value(QLatin1String("needItems"))); + completion.neededDeletes = toUInt64(replyObj.value(QLatin1String("needDeletes"))); + emit dirStatusChanged(*dir, index); + break; + } + default: + emitError(tr("Unable to request completion for device/directory %1/%2: ").arg(devId, dirId), SyncthingErrorCategory::SpecificRequest, reply); + } +} + /*! * \brief Sets the connection status. Ensures statusChanged() is emitted. * \param status Specifies the status; should be either SyncthingStatus::Disconnected, SyncthingStatus::Reconnecting, or diff --git a/connector/syncthingconnection.h b/connector/syncthingconnection.h index 21c23b7..93cd37f 100644 --- a/connector/syncthingconnection.h +++ b/connector/syncthingconnection.h @@ -69,6 +69,7 @@ class LIB_SYNCTHING_CONNECTOR_EXPORT SyncthingConnection : public QObject { Q_PROPERTY(bool connected READ isConnected NOTIFY statusChanged) Q_PROPERTY(bool hasUnreadNotifications READ hasUnreadNotifications) Q_PROPERTY(bool hasOutOfSyncDirs READ hasOutOfSyncDirs) + Q_PROPERTY(bool requestingCompletionEnabled READ isRequestingCompletionEnabled WRITE setRequestingCompletionEnabled) Q_PROPERTY(int trafficPollInterval READ trafficPollInterval WRITE setTrafficPollInterval) Q_PROPERTY(int devStatsPollInterval READ devStatsPollInterval WRITE setDevStatsPollInterval) Q_PROPERTY(QString configDir READ configDir NOTIFY configDirChanged) @@ -97,6 +98,8 @@ public: bool isConnected() const; bool hasUnreadNotifications() const; bool hasOutOfSyncDirs() const; + bool isRequestingCompletionEnabled() const; + void setRequestingCompletionEnabled(bool requestingCompletionEnabled); int trafficPollInterval() const; void setTrafficPollInterval(int trafficPollInterval); int devStatsPollInterval() const; @@ -180,6 +183,7 @@ private Q_SLOTS: void requestClearingErrors(); void requestDirStatistics(); void requestDirStatus(const QString &dirId); + void requestCompletion(const QString &devId, const QString &dirId); void requestDeviceStatistics(); void requestEvents(); @@ -203,6 +207,7 @@ private Q_SLOTS: void readDeviceEvent(ChronoUtilities::DateTime eventTime, const QString &eventType, const QJsonObject &eventData); void readItemStarted(ChronoUtilities::DateTime eventTime, const QJsonObject &eventData); void readItemFinished(ChronoUtilities::DateTime eventTime, const QJsonObject &eventData); + void readRemoteIndexUpdated(ChronoUtilities::DateTime eventTime, const QJsonObject &eventData); void readRescan(); void readDevPauseResume(); void readDirPauseResume(); @@ -210,6 +215,7 @@ private Q_SLOTS: void readShutdown(); void readDirStatus(); bool readDirSummary(ChronoUtilities::DateTime eventTime, const QJsonObject &summary, SyncthingDir &dirInfo, int index); + void readCompletion(); void continueConnecting(); void continueReconnecting(); @@ -238,6 +244,7 @@ private: SyncthingStatus m_status; bool m_keepPolling; bool m_reconnecting; + bool m_requestCompletion; int m_lastEventId; QTimer m_trafficPollTimer; QTimer m_devStatsPollTimer; @@ -352,6 +359,24 @@ inline bool SyncthingConnection::hasUnreadNotifications() const return m_unreadNotifications; } +/*! + * \brief Returns whether completion for all directories of all devices should be requested automatically. + * \remarks Completion can be requested manually using requestCompletion(). + */ +inline bool SyncthingConnection::isRequestingCompletionEnabled() const +{ + return m_requestCompletion; +} + +/*! + * \brief Sets whether completion for all directories of all devices should be requested automatically. + * \remarks Completion can be requested manually using requestCompletion(). + */ +inline void SyncthingConnection::setRequestingCompletionEnabled(bool requestingCompletionEnabled) +{ + m_requestCompletion = requestingCompletionEnabled; +} + /*! * \brief Considers all notifications as read; hence might trigger a status update. */ diff --git a/connector/syncthingdir.h b/connector/syncthingdir.h index b56ffe6..0d3f878 100644 --- a/connector/syncthingdir.h +++ b/connector/syncthingdir.h @@ -51,6 +51,15 @@ struct LIB_SYNCTHING_CONNECTOR_EXPORT SyncthingItemDownloadProgress { static constexpr unsigned int syncthingBlockSize = 128 * 1024; }; +struct LIB_SYNCTHING_CONNECTOR_EXPORT SyncthingCompletion { + ChronoUtilities::DateTime lastUpdate; + quint64 globalBytes = 0; + quint64 neededBytes = 0; + quint64 neededItems = 0; + quint64 neededDeletes = 0; + int percentage = 0; +}; + struct LIB_SYNCTHING_CONNECTOR_EXPORT SyncthingDir { SyncthingDir(const QString &id = QString(), const QString &label = QString(), const QString &path = QString()); bool assignStatus(const QString &statusStr, ChronoUtilities::DateTime time); @@ -75,6 +84,7 @@ struct LIB_SYNCTHING_CONNECTOR_EXPORT SyncthingDir { int completionPercentage = 0; int scanningPercentage = 0; double scanningRate = 0; + std::map completionByDevice; QString globalError; std::vector itemErrors; std::vector previousItemErrors;