Apparently the default before introducing the request timeout was not 30000 (which is the value of QNetworkRequest::TransferTimeoutConstant). There was actually no timeout. So let's restore this behavior for now as the main intention was to make the timeout configurable but not change any defaults.
1203 lines
42 KiB
C++
1203 lines
42 KiB
C++
#include "./syncthingconnection.h"
|
|
#include "./syncthingconfig.h"
|
|
#include "./syncthingconnectionsettings.h"
|
|
#include "./utils.h"
|
|
|
|
#ifdef LIB_SYNCTHING_CONNECTOR_CONNECTION_MOCKED
|
|
#include "./syncthingconnectionmockhelpers.h"
|
|
#endif
|
|
|
|
#include "resources/config.h"
|
|
|
|
#include <c++utilities/conversion/stringconversion.h>
|
|
#include <c++utilities/io/ansiescapecodes.h>
|
|
|
|
#include <QAuthenticator>
|
|
#include <QHostAddress>
|
|
#include <QJsonArray>
|
|
#include <QJsonDocument>
|
|
#include <QJsonValue>
|
|
#include <QNetworkAccessManager>
|
|
#include <QNetworkInterface>
|
|
#include <QNetworkReply>
|
|
#include <QStringBuilder>
|
|
#include <QTimer>
|
|
|
|
#include <iostream>
|
|
#include <utility>
|
|
|
|
using namespace std;
|
|
using namespace CppUtilities;
|
|
using namespace CppUtilities::EscapeCodes;
|
|
|
|
namespace Data {
|
|
|
|
/*!
|
|
* \brief Returns the QNetworkAccessManager instance used by SyncthingConnection instances.
|
|
*/
|
|
QNetworkAccessManager &networkAccessManager()
|
|
{
|
|
static auto networkAccessManager = new QNetworkAccessManager;
|
|
return *networkAccessManager;
|
|
}
|
|
|
|
/*!
|
|
* \class SyncthingConnection
|
|
* \brief The SyncthingConnection class allows Qt applications to access Syncthing via its REST API.
|
|
* \remarks All requests are performed asynchronously.
|
|
*
|
|
* The first thing to do when working with that class is setting the URL to connect to and the API key
|
|
* via the constructor or setSyncthingUrl() and setApiKey(). Credentials for the HTTP authentication
|
|
* can be set via setCredentials() if not included in the URL.
|
|
*
|
|
* Requests can then be done via the request...() methods, eg. requestConfig(). This would emit the
|
|
* newConfig() signal on success and the error() signal when an error occurred. The other request...()
|
|
* methods work in a similar way.
|
|
*
|
|
* However, usually it is best to simply call the connect() method. It will do all required requests
|
|
* to populate dirInfo(), devInfo(), myId(), totalIncomingTraffic(), totalOutgoingTraffic() and all
|
|
* the other variables. It will also use Syncthing's event API to listen for changes. The signals
|
|
* newDirs() and newDevs() are can be used to know when dirInfo() and devInfo() become available.
|
|
* Note that in this case the previous dirInfo()/devInfo() is invalidated.
|
|
*/
|
|
|
|
/*!
|
|
* \brief Constructs a new instance ready to connect. To establish the connection, call connect().
|
|
*/
|
|
SyncthingConnection::SyncthingConnection(
|
|
const QString &syncthingUrl, const QByteArray &apiKey, SyncthingConnectionLoggingFlags loggingFlags, QObject *parent)
|
|
: QObject(parent)
|
|
, m_syncthingUrl(syncthingUrl)
|
|
, m_apiKey(apiKey)
|
|
, m_status(SyncthingStatus::Disconnected)
|
|
, m_statusComputionFlags(SyncthingStatusComputionFlags::Default)
|
|
, m_loggingFlags(SyncthingConnectionLoggingFlags::None)
|
|
, m_loggingFlagsHandler(SyncthingConnectionLoggingFlags::None)
|
|
, m_keepPolling(false)
|
|
, m_abortingAllRequests(false)
|
|
, m_connectionAborted(false)
|
|
, m_abortingToReconnect(false)
|
|
, m_requestCompletion(true)
|
|
, m_lastEventId(0)
|
|
, m_lastDiskEventId(0)
|
|
, m_autoReconnectTries(0)
|
|
, m_requestTimeout(SyncthingConnectionSettings::defaultRequestTimeout)
|
|
, m_totalIncomingTraffic(unknownTraffic)
|
|
, m_totalOutgoingTraffic(unknownTraffic)
|
|
, m_totalIncomingRate(0.0)
|
|
, m_totalOutgoingRate(0.0)
|
|
, m_configReply(nullptr)
|
|
, m_statusReply(nullptr)
|
|
, m_connectionsReply(nullptr)
|
|
, m_errorsReply(nullptr)
|
|
, m_dirStatsReply(nullptr)
|
|
, m_devStatsReply(nullptr)
|
|
, m_eventsReply(nullptr)
|
|
, m_versionReply(nullptr)
|
|
, m_diskEventsReply(nullptr)
|
|
, m_logReply(nullptr)
|
|
, m_unreadNotifications(false)
|
|
, m_hasConfig(false)
|
|
, m_hasStatus(false)
|
|
, m_hasEvents(false)
|
|
, m_hasDiskEvents(false)
|
|
, m_lastFileDeleted(false)
|
|
, m_dirStatsAltered(false)
|
|
, m_recordFileChanges(false)
|
|
, m_useDeprecatedRoutes(true)
|
|
{
|
|
m_trafficPollTimer.setInterval(SyncthingConnectionSettings::defaultTrafficPollInterval);
|
|
m_trafficPollTimer.setTimerType(Qt::VeryCoarseTimer);
|
|
m_trafficPollTimer.setSingleShot(true);
|
|
QObject::connect(&m_trafficPollTimer, &QTimer::timeout, this, &SyncthingConnection::requestConnections);
|
|
m_devStatsPollTimer.setInterval(SyncthingConnectionSettings::defaultDevStatusPollInterval);
|
|
m_devStatsPollTimer.setTimerType(Qt::VeryCoarseTimer);
|
|
m_devStatsPollTimer.setSingleShot(true);
|
|
QObject::connect(&m_devStatsPollTimer, &QTimer::timeout, this, &SyncthingConnection::requestDeviceStatistics);
|
|
m_errorsPollTimer.setInterval(SyncthingConnectionSettings::defaultErrorsPollInterval);
|
|
m_errorsPollTimer.setTimerType(Qt::VeryCoarseTimer);
|
|
m_errorsPollTimer.setSingleShot(true);
|
|
QObject::connect(&m_errorsPollTimer, &QTimer::timeout, this, &SyncthingConnection::requestErrors);
|
|
m_autoReconnectTimer.setTimerType(Qt::VeryCoarseTimer);
|
|
m_autoReconnectTimer.setInterval(SyncthingConnectionSettings::defaultReconnectInterval);
|
|
QObject::connect(&m_autoReconnectTimer, &QTimer::timeout, this, &SyncthingConnection::autoReconnect);
|
|
|
|
#ifdef LIB_SYNCTHING_CONNECTOR_CONNECTION_MOCKED
|
|
setupTestData();
|
|
#endif
|
|
|
|
setLoggingFlags(loggingFlags);
|
|
|
|
// allow initializing the default value for m_useDeprecatedRoutes via environment variable
|
|
auto useDeprecatedRoutesIsInt = false;
|
|
auto useDeprecatedRoutesInt = qEnvironmentVariableIntValue(PROJECT_VARNAME_UPPER "_USE_DEPRECATED_ROUTES", &useDeprecatedRoutesIsInt);
|
|
if (useDeprecatedRoutesIsInt) {
|
|
m_useDeprecatedRoutes = useDeprecatedRoutesInt;
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* \brief Destroys the instance. Ongoing requests are aborted.
|
|
*/
|
|
SyncthingConnection::~SyncthingConnection()
|
|
{
|
|
m_status = SyncthingStatus::BeingDestroyed;
|
|
disconnect();
|
|
}
|
|
|
|
/*!
|
|
* \brief Returns whether the currently assigned syncthingUrl() refers to the Syncthing instance on the local machine.
|
|
*/
|
|
bool SyncthingConnection::isLocal() const
|
|
{
|
|
return ::Data::isLocal(QUrl(m_syncthingUrl));
|
|
}
|
|
|
|
/*!
|
|
* \brief Returns the string representation of the specified \a status.
|
|
*/
|
|
QString SyncthingConnection::statusText(SyncthingStatus status)
|
|
{
|
|
switch (status) {
|
|
case SyncthingStatus::Disconnected:
|
|
return tr("disconnected");
|
|
case SyncthingStatus::Reconnecting:
|
|
return tr("reconnecting");
|
|
case SyncthingStatus::Idle:
|
|
return tr("connected");
|
|
case SyncthingStatus::Scanning:
|
|
return tr("connected, scanning");
|
|
case SyncthingStatus::Paused:
|
|
return tr("connected, paused");
|
|
case SyncthingStatus::Synchronizing:
|
|
return tr("connected, synchronizing");
|
|
case SyncthingStatus::RemoteNotInSync:
|
|
return tr("connected, remote not in sync");
|
|
default:
|
|
return tr("unknown");
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* \brief Sets the specified logging \a flags.
|
|
*/
|
|
void SyncthingConnection::setLoggingFlags(SyncthingConnectionLoggingFlags flags)
|
|
{
|
|
m_loggingFlags = flags;
|
|
if (flags & SyncthingConnectionLoggingFlags::FromEnvironment) {
|
|
if (!(flags & SyncthingConnectionLoggingFlags::All) && qEnvironmentVariableIntValue(PROJECT_VARNAME_UPPER "_LOG_ALL")) {
|
|
m_loggingFlags |= SyncthingConnectionLoggingFlags::All;
|
|
} else {
|
|
if (!(flags & SyncthingConnectionLoggingFlags::ApiCalls) && qEnvironmentVariableIntValue(PROJECT_VARNAME_UPPER "_LOG_API_CALLS")) {
|
|
m_loggingFlags |= SyncthingConnectionLoggingFlags::ApiCalls;
|
|
}
|
|
if (!(flags & SyncthingConnectionLoggingFlags::ApiCalls) && qEnvironmentVariableIntValue(PROJECT_VARNAME_UPPER "_LOG_API_REPLIES")) {
|
|
m_loggingFlags |= SyncthingConnectionLoggingFlags::ApiCalls;
|
|
}
|
|
if (!(flags & SyncthingConnectionLoggingFlags::Events) && qEnvironmentVariableIntValue(PROJECT_VARNAME_UPPER "_LOG_EVENTS")) {
|
|
m_loggingFlags |= SyncthingConnectionLoggingFlags::Events;
|
|
}
|
|
if (!(flags & SyncthingConnectionLoggingFlags::DirsOrDevsResetted)
|
|
&& qEnvironmentVariableIntValue(PROJECT_VARNAME_UPPER "_LOG_DIRS_OR_DEVS_RESETTED")) {
|
|
m_loggingFlags |= SyncthingConnectionLoggingFlags::Events;
|
|
}
|
|
}
|
|
}
|
|
if ((m_loggingFlags & SyncthingConnectionLoggingFlags::DirsOrDevsResetted)
|
|
&& !(m_loggingFlagsHandler & SyncthingConnectionLoggingFlags::DirsOrDevsResetted)) {
|
|
QObject::connect(this, &SyncthingConnection::newDirs, [this](const auto &dirs) {
|
|
if (m_loggingFlags & SyncthingConnectionLoggingFlags::DirsOrDevsResetted) {
|
|
std::cerr << Phrases::Info << "Directory list renewed:" << Phrases::End;
|
|
std::cerr << displayNames(dirs).join(QStringLiteral(", ")).toStdString() << endl;
|
|
}
|
|
});
|
|
QObject::connect(this, &SyncthingConnection::newDevices, [this](const auto &devs) {
|
|
if (m_loggingFlags & SyncthingConnectionLoggingFlags::DirsOrDevsResetted) {
|
|
std::cerr << Phrases::Info << "Device list renewed:" << Phrases::End;
|
|
std::cerr << displayNames(devs).join(QStringLiteral(", ")).toStdString() << endl;
|
|
}
|
|
});
|
|
m_loggingFlagsHandler |= SyncthingConnectionLoggingFlags::DirsOrDevsResetted;
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* \brief Returns whether there is at least one directory out-of-sync.
|
|
*/
|
|
bool SyncthingConnection::hasOutOfSyncDirs() const
|
|
{
|
|
for (const SyncthingDir &dir : m_dirs) {
|
|
if (dir.status == SyncthingDirStatus::OutOfSync) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*!
|
|
* \brief Sets all polling intervals (traffic, device statistics, errors) to 0 so polling is disabled.
|
|
* \remarks Does not affect the auto-reconnect and does not affect the *long* polling for the event API.
|
|
*/
|
|
void SyncthingConnection::disablePolling()
|
|
{
|
|
setTrafficPollInterval(0);
|
|
setDevStatsPollInterval(0);
|
|
setErrorsPollInterval(0);
|
|
}
|
|
|
|
/*!
|
|
* \brief Connects asynchronously to Syncthing. Does nothing if already connected.
|
|
*
|
|
* Use this to connect the first time or to connect to the same Syncthing instance again or to ensure
|
|
* the connection to the currently configured instance is established. Use reconnect() to connect to
|
|
* a different instance.
|
|
*
|
|
* \remarks Does not clear data from a previous connection (except error items). Use reconnect() if that
|
|
* is required.
|
|
*/
|
|
void SyncthingConnection::connect()
|
|
{
|
|
// reset auto-reconnect
|
|
m_autoReconnectTimer.stop();
|
|
m_autoReconnectTries = 0;
|
|
|
|
// skip if already connected (see reconnect() to force reconnecting)
|
|
if (isConnected()) {
|
|
return;
|
|
}
|
|
|
|
// reset status
|
|
m_connectionAborted = m_abortingToReconnect = m_hasConfig = m_hasStatus = m_hasEvents = m_hasDiskEvents = false;
|
|
|
|
// check configuration
|
|
if (m_apiKey.isEmpty() || m_syncthingUrl.isEmpty()) {
|
|
emit error(tr("Connection configuration is insufficient."), SyncthingErrorCategory::OverallConnection, QNetworkReply::NoError);
|
|
setStatus(SyncthingStatus::Disconnected);
|
|
return;
|
|
}
|
|
|
|
// start by requesting config and status; if both are available request further info and events
|
|
requestConfig();
|
|
requestStatus();
|
|
m_keepPolling = true;
|
|
}
|
|
|
|
/*!
|
|
* \brief Applies the specified configuration and tries to reconnect via reconnect() if properties requiring reconnect
|
|
* to take effect have changed.
|
|
* \remarks The expected SSL errors of the specified configuration are updated accordingly.
|
|
*/
|
|
void SyncthingConnection::connect(SyncthingConnectionSettings &connectionSettings)
|
|
{
|
|
if (applySettings(connectionSettings) || !isConnected()) {
|
|
reconnect();
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* \brief Connects in \a milliSeconds. Useful to "schedule" another attempt in case of a failure.
|
|
* \remarks Does nothing if the connection attempt would happen anyways though auto-reconnect.
|
|
*/
|
|
void SyncthingConnection::connectLater(int milliSeconds)
|
|
{
|
|
// skip if conneting via auto-reconnect anyways
|
|
if (m_autoReconnectTimer.isActive() && milliSeconds > m_autoReconnectTimer.interval()) {
|
|
return;
|
|
}
|
|
QTimer::singleShot(milliSeconds, this, static_cast<void (SyncthingConnection::*)(void)>(&SyncthingConnection::connect));
|
|
}
|
|
|
|
/*!
|
|
* \brief Disconnects. That means all (long) polling is stopped and ongoing requests are aborted via abortAllRequests().
|
|
*/
|
|
void SyncthingConnection::disconnect()
|
|
{
|
|
m_abortingToReconnect = m_keepPolling = false;
|
|
m_trafficPollTimer.stop();
|
|
m_devStatsPollTimer.stop();
|
|
m_errorsPollTimer.stop();
|
|
m_autoReconnectTimer.stop();
|
|
m_autoReconnectTries = 0;
|
|
abortAllRequests();
|
|
}
|
|
|
|
/*!
|
|
* \brief Aborts the specified \a reply if it is not nullptr.
|
|
*/
|
|
static inline void abortMaybe(QNetworkReply *reply)
|
|
{
|
|
if (reply) {
|
|
reply->abort();
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* \brief Aborts status-relevant, pending requests.
|
|
* \remarks Status-relevant means that requests for triggering actions like rescan() or restart() are excluded. requestQrCode() does not
|
|
* contribute to the status as well and is excluded as well.
|
|
*/
|
|
void SyncthingConnection::abortAllRequests()
|
|
{
|
|
m_connectionAborted = m_abortingAllRequests = true;
|
|
abortMaybe(m_configReply);
|
|
abortMaybe(m_statusReply);
|
|
abortMaybe(m_connectionsReply);
|
|
abortMaybe(m_errorsReply);
|
|
abortMaybe(m_dirStatsReply);
|
|
abortMaybe(m_devStatsReply);
|
|
abortMaybe(m_eventsReply);
|
|
abortMaybe(m_versionReply);
|
|
abortMaybe(m_diskEventsReply);
|
|
abortMaybe(m_logReply);
|
|
abortMaybe(m_configReply);
|
|
abortMaybe(m_configReply);
|
|
for (auto *const reply : std::as_const(m_otherReplies)) {
|
|
reply->abort();
|
|
}
|
|
|
|
m_abortingAllRequests = false;
|
|
handleAdditionalRequestCanceled();
|
|
}
|
|
|
|
/*!
|
|
* \brief Disconnects if connected, then (re-)connects asynchronously.
|
|
* \remarks
|
|
* - Clears the currently cached configuration.
|
|
* - This explicit request to reconnect will reset the autoReconnectTries().
|
|
*/
|
|
void SyncthingConnection::reconnect()
|
|
{
|
|
// reset reconnect timer
|
|
m_autoReconnectTimer.stop();
|
|
m_autoReconnectTries = 0;
|
|
|
|
// stop other timers
|
|
m_trafficPollTimer.stop();
|
|
m_devStatsPollTimer.stop();
|
|
m_errorsPollTimer.stop();
|
|
|
|
// reset variables to track connection progress
|
|
// note: especially resetting events is important as it influences the subsequent hasPendingRequests() call
|
|
m_hasConfig = m_hasStatus = m_hasEvents = m_hasDiskEvents = false;
|
|
|
|
// reconnect right now if no pending requests to be aborted
|
|
if (!hasPendingRequests()) {
|
|
continueReconnecting();
|
|
return;
|
|
}
|
|
|
|
// abort pending requests before connecting again
|
|
m_keepPolling = m_abortingToReconnect = true;
|
|
abortAllRequests();
|
|
}
|
|
|
|
/*!
|
|
* \brief Applies the specified configuration and tries to reconnect via reconnect().
|
|
* \remarks The expected SSL errors of the specified configuration are updated accordingly.
|
|
*/
|
|
void SyncthingConnection::reconnect(SyncthingConnectionSettings &connectionSettings)
|
|
{
|
|
applySettings(connectionSettings);
|
|
reconnect();
|
|
}
|
|
|
|
/*!
|
|
* \brief Reconnects after the specified number of \a milliSeconds.
|
|
*/
|
|
void SyncthingConnection::reconnectLater(int milliSeconds)
|
|
{
|
|
QTimer::singleShot(milliSeconds, this, static_cast<void (SyncthingConnection::*)(void)>(&SyncthingConnection::reconnect));
|
|
}
|
|
|
|
/*!
|
|
* \brief Internally called to reconnect; ensures currently cached config is cleared.
|
|
*/
|
|
void SyncthingConnection::continueReconnecting()
|
|
{
|
|
// notify that we're about to invalidate the configuration if not already invalidated anyways
|
|
const auto isConfigInvalidated = m_rawConfig.isEmpty();
|
|
if (!isConfigInvalidated) {
|
|
emit newConfig(m_rawConfig = QJsonObject());
|
|
}
|
|
|
|
// cleanup information from previous connection
|
|
m_keepPolling = true;
|
|
m_connectionAborted = false;
|
|
m_abortingToReconnect = false;
|
|
m_lastEventId = 0;
|
|
m_lastDiskEventId = 0;
|
|
m_configDir.clear();
|
|
m_myId.clear();
|
|
m_tilde.clear();
|
|
m_pathSeparator.clear();
|
|
m_totalIncomingTraffic = unknownTraffic;
|
|
m_totalOutgoingTraffic = unknownTraffic;
|
|
m_totalIncomingRate = 0.0;
|
|
m_totalOutgoingRate = 0.0;
|
|
emit trafficChanged(unknownTraffic, unknownTraffic);
|
|
m_unreadNotifications = false;
|
|
m_hasConfig = false;
|
|
m_hasStatus = false;
|
|
m_hasEvents = false;
|
|
m_hasDiskEvents = false;
|
|
m_dirs.clear();
|
|
m_devs.clear();
|
|
m_lastConnectionsUpdateEvent = 0;
|
|
m_lastConnectionsUpdateTime = DateTime();
|
|
m_lastFileEvent = 0;
|
|
m_lastFileTime = DateTime();
|
|
m_lastErrorTime = DateTime();
|
|
m_startTime = DateTime();
|
|
m_lastFileName.clear();
|
|
m_lastFileDeleted = false;
|
|
m_syncthingVersion.clear();
|
|
m_dirStatsAltered = false;
|
|
emit dirStatisticsChanged();
|
|
|
|
// notify that the configuration has been invalidated
|
|
if (!isConfigInvalidated) {
|
|
emit newConfigApplied();
|
|
}
|
|
|
|
if (m_apiKey.isEmpty() || m_syncthingUrl.isEmpty()) {
|
|
emit error(tr("Connection configuration is insufficient."), SyncthingErrorCategory::OverallConnection, QNetworkReply::NoError);
|
|
setStatus(SyncthingStatus::Disconnected);
|
|
return;
|
|
}
|
|
|
|
requestConfig();
|
|
requestStatus();
|
|
|
|
setStatus(SyncthingStatus::Reconnecting);
|
|
}
|
|
|
|
/*!
|
|
* \brief Reads results of requestConfig() and requestStatus().
|
|
* \remarks Called in readConfig() or readStatus() to conclude reading parts requiring config *and* status
|
|
* being available. Does nothing if this is not the case (yet).
|
|
*/
|
|
void SyncthingConnection::concludeReadingConfigAndStatus()
|
|
{
|
|
if (!m_hasConfig || !m_hasStatus) {
|
|
return;
|
|
}
|
|
|
|
readDevs(m_rawConfig.value(QLatin1String("devices")).toArray());
|
|
readDirs(m_rawConfig.value(QLatin1String("folders")).toArray());
|
|
emit newConfigApplied();
|
|
|
|
continueConnecting();
|
|
}
|
|
|
|
/*!
|
|
* \brief Sets the state from (re)connecting to Syncthing's actual state if polling but there are no more pending requests.
|
|
* \remarks Called by read...() handlers for requests started in continueConnecting().
|
|
* \sa hasPendingRequests()
|
|
*/
|
|
void SyncthingConnection::concludeConnection()
|
|
{
|
|
if (!m_keepPolling || hasPendingRequests()) {
|
|
return;
|
|
}
|
|
setStatus(SyncthingStatus::Idle);
|
|
emitDirStatisticsChanged();
|
|
}
|
|
|
|
/*!
|
|
* \brief Connects increasing the auto-reconnect tries.
|
|
* \remarks Called via m_autoReconnectTimer.
|
|
*/
|
|
void SyncthingConnection::autoReconnect()
|
|
{
|
|
const auto tmp = m_autoReconnectTries;
|
|
connect();
|
|
m_autoReconnectTries = tmp + 1;
|
|
}
|
|
|
|
/*!
|
|
* \brief Returns the directory info object for the directory with the specified ID.
|
|
* \returns Returns a pointer to the object or nullptr if not found.
|
|
* \remarks The returned object becomes invalid when the newDirs() signal is emitted or the connection is destroyed.
|
|
*/
|
|
SyncthingDir *SyncthingConnection::findDirInfo(const QString &dirId, int &row)
|
|
{
|
|
row = 0;
|
|
for (SyncthingDir &d : m_dirs) {
|
|
if (d.id == dirId) {
|
|
return &d;
|
|
}
|
|
++row;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
/*!
|
|
* \brief Returns the directory info object for the directory with the specified ID or label.
|
|
* \returns Returns a pointer to the object or nullptr if not found.
|
|
* \remarks
|
|
* - IDs have precedence, labels are checked as fallback.
|
|
* - The returned object becomes invalid when the newDirs() signal is emitted or the connection is destroyed.
|
|
*/
|
|
SyncthingDir *SyncthingConnection::findDirInfoConsideringLabels(const QString &dirIdOrLabel, int &row)
|
|
{
|
|
if (auto *const dir = findDirInfo(dirIdOrLabel, row)) {
|
|
return dir;
|
|
}
|
|
row = 0;
|
|
for (SyncthingDir &d : m_dirs) {
|
|
if (d.label == dirIdOrLabel) {
|
|
return &d;
|
|
}
|
|
++row;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
/*!
|
|
* \brief Returns the directory info object for the directory with the ID stored in the specified \a object with the specified \a key.
|
|
*/
|
|
SyncthingDir *SyncthingConnection::findDirInfo(QLatin1String key, const QJsonObject &object, int *row)
|
|
{
|
|
const auto dirId(object.value(key).toString());
|
|
if (dirId.isEmpty()) {
|
|
return nullptr;
|
|
}
|
|
int dummyRow;
|
|
auto &rowRef(row ? *row : dummyRow);
|
|
return findDirInfo(dirId, rowRef);
|
|
}
|
|
|
|
/*!
|
|
* \brief Returns the directory info object for the directory with the specified \a path.
|
|
*
|
|
* If a corresponding Syncthing directory could be found, \a relativePath is set to the path of the item relative
|
|
* to the location of the corresponding Syncthing directory.
|
|
*
|
|
* \returns Returns a pointer to the object or nullptr if not found.
|
|
* \remarks The returned object becomes invalid when the newDirs() signal is emitted or the connection is destroyed.
|
|
*/
|
|
SyncthingDir *SyncthingConnection::findDirInfoByPath(const QString &path, QString &relativePath, int &row)
|
|
{
|
|
row = 0;
|
|
for (SyncthingDir &dir : m_dirs) {
|
|
if (path == dir.pathWithoutTrailingSlash()) {
|
|
relativePath.clear();
|
|
return &dir;
|
|
} else if (path.startsWith(dir.path)) {
|
|
relativePath = path.mid(dir.path.size());
|
|
return &dir;
|
|
}
|
|
++row;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
/*!
|
|
* \brief Appends a directory info object with the specified \a dirId to \a dirs.
|
|
*
|
|
* If such an object already exists, it is recycled by moving it to \a dirs.
|
|
* Otherwise a new, empty object is created.
|
|
*
|
|
* \returns Returns the directory info object or nullptr if \a dirId is invalid.
|
|
*/
|
|
SyncthingDir *SyncthingConnection::addDirInfo(std::vector<SyncthingDir> &dirs, const QString &dirId)
|
|
{
|
|
if (dirId.isEmpty()) {
|
|
return nullptr;
|
|
}
|
|
int row;
|
|
if (auto *const existingDirInfo = findDirInfo(dirId, row)) {
|
|
return &dirs.emplace_back(std::move(*existingDirInfo));
|
|
} else {
|
|
return &dirs.emplace_back(dirId);
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* \brief Returns the device info object for the device with the specified ID.
|
|
* \returns Returns a pointer to the object or nullptr if not found.
|
|
* \remarks The returned object becomes invalid when the newConfig() signal is emitted or the connection is destroyed.
|
|
*/
|
|
SyncthingDev *SyncthingConnection::findDevInfo(const QString &devId, int &row)
|
|
{
|
|
row = 0;
|
|
for (SyncthingDev &d : m_devs) {
|
|
if (d.id == devId) {
|
|
return &d;
|
|
}
|
|
++row;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
/*!
|
|
* \brief Returns the device info object for the first device with the specified name.
|
|
* \returns Returns a pointer to the object or nullptr if not found.
|
|
* \remarks The returned object becomes invalid when the newConfig() signal is emitted or the connection is destroyed.
|
|
*/
|
|
SyncthingDev *SyncthingConnection::findDevInfoByName(const QString &devName, int &row)
|
|
{
|
|
row = 0;
|
|
for (SyncthingDev &d : m_devs) {
|
|
if (d.name == devName) {
|
|
return &d;
|
|
}
|
|
++row;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
/*!
|
|
* \brief Returns all directory IDs for the current configuration.
|
|
* \remarks Computed by looping dirInfo().
|
|
*/
|
|
QStringList SyncthingConnection::directoryIds() const
|
|
{
|
|
return ids(m_dirs);
|
|
}
|
|
|
|
/*!
|
|
* \brief Returns all device IDs for the current configuration.
|
|
* \remarks Computed by looping devInfo().
|
|
*/
|
|
QStringList SyncthingConnection::deviceIds() const
|
|
{
|
|
return ids(m_devs);
|
|
}
|
|
|
|
/*!
|
|
* \brief Returns the device name for the specified \a deviceId if known; otherwise returns just the \a deviceId itself.
|
|
*/
|
|
QString SyncthingConnection::deviceNameOrId(const QString &deviceId) const
|
|
{
|
|
for (const auto &dev : devInfo()) {
|
|
if (dev.id == deviceId) {
|
|
return dev.name;
|
|
}
|
|
}
|
|
return deviceId;
|
|
}
|
|
|
|
/*!
|
|
* \brief Returns the number of devices Syncthing is currently connected to.
|
|
* \remarks Computed by looping devInfo().
|
|
*/
|
|
std::vector<const SyncthingDev *> SyncthingConnection::connectedDevices() const
|
|
{
|
|
std::vector<const SyncthingDev *> connectedDevs;
|
|
connectedDevs.reserve(devInfo().size());
|
|
for (const SyncthingDev &dev : devInfo()) {
|
|
if (dev.isConnected()) {
|
|
connectedDevs.emplace_back(&dev);
|
|
}
|
|
}
|
|
return connectedDevs;
|
|
}
|
|
|
|
/*!
|
|
* \brief Appends a device info object with the specified \a devId to \a devs.
|
|
*
|
|
* If such an object already exists, it is recycled by moving it to \a devs.
|
|
* Otherwise a new, empty object is created.
|
|
*
|
|
* \returns Returns the device info object or nullptr if \a devId is invalid.
|
|
*/
|
|
SyncthingDev *SyncthingConnection::addDevInfo(std::vector<SyncthingDev> &devs, const QString &devId)
|
|
{
|
|
if (devId.isEmpty()) {
|
|
return nullptr;
|
|
}
|
|
int row;
|
|
if (SyncthingDev *const existingDevInfo = findDevInfo(devId, row)) {
|
|
return &devs.emplace_back(std::move(*existingDevInfo));
|
|
} else {
|
|
return &devs.emplace_back(devId);
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* \brief Internally called to parse a time stamp.
|
|
*/
|
|
DateTime SyncthingConnection::parseTimeStamp(const QJsonValue &jsonValue, const QString &context, DateTime defaultValue, bool greaterThanEpoch)
|
|
{
|
|
const auto utf16 = jsonValue.toString();
|
|
const auto utf8 = utf16.toUtf8();
|
|
try {
|
|
const auto [localTime, utcOffset] = DateTime::fromIsoString(utf8.data());
|
|
return !greaterThanEpoch || (localTime - utcOffset) > DateTime::unixEpochStart() ? localTime : defaultValue;
|
|
} catch (const ConversionException &e) {
|
|
emit error(tr("Unable to parse timestamp \"%1\" (%2): %3").arg(utf16, context, QString::fromUtf8(e.what())), SyncthingErrorCategory::Parsing,
|
|
QNetworkReply::NoError);
|
|
}
|
|
return defaultValue;
|
|
}
|
|
|
|
/*!
|
|
* \brief Continues connecting if both - config and status - have been parsed yet and continuous polling is enabled.
|
|
*/
|
|
void SyncthingConnection::continueConnecting()
|
|
{
|
|
// skip if config and status are missing or we're not supposed to actually connect
|
|
if (!m_keepPolling || !m_hasConfig || !m_hasStatus) {
|
|
return;
|
|
}
|
|
|
|
// read additional information (beside config and status)
|
|
// FIXME: make those requests configurable (eg. flag enum)
|
|
requestConnections();
|
|
requestDirStatistics();
|
|
requestDeviceStatistics();
|
|
requestErrors();
|
|
requestVersion();
|
|
for (const SyncthingDir &dir : m_dirs) {
|
|
requestDirStatus(dir.id);
|
|
if (!m_requestCompletion || dir.paused) {
|
|
continue;
|
|
}
|
|
for (const QString &devId : dir.deviceIds) {
|
|
requestCompletion(devId, dir.id);
|
|
}
|
|
}
|
|
|
|
// poll for events
|
|
requestEvents();
|
|
requestDiskEvents();
|
|
}
|
|
|
|
/*!
|
|
* \brief Locates and loads the (self-signed) certificate used by the Syncthing GUI.
|
|
* \remarks
|
|
* - Ensures any previous certificates are cleared in any case.
|
|
* - Emits error() when an error occurs.
|
|
* - Loading the certificate is only possible if the connection object is configured
|
|
* to connect to the locally running Syncthing instance. Otherwise this method will
|
|
* only do the cleanup of previous certificates but not emit any errors.
|
|
* \returns Returns whether a certificate could be loaded.
|
|
*/
|
|
bool SyncthingConnection::loadSelfSignedCertificate()
|
|
{
|
|
// ensure current exceptions for self-signed certificates are cleared
|
|
m_expectedSslErrors.clear();
|
|
|
|
// not required when not using secure connection
|
|
const QUrl syncthingUrl(m_syncthingUrl);
|
|
if (!syncthingUrl.scheme().endsWith(QChar('s'))) {
|
|
return false;
|
|
}
|
|
|
|
// only possible if the Syncthing instance is running on the local machine
|
|
if (!::Data::isLocal(syncthingUrl)) {
|
|
return false;
|
|
}
|
|
|
|
// find cert
|
|
const QString certPath = !m_configDir.isEmpty() ? (m_configDir + QStringLiteral("/https-cert.pem")) : SyncthingConfig::locateHttpsCertificate();
|
|
if (certPath.isEmpty()) {
|
|
emit error(tr("Unable to locate certificate used by Syncthing."), SyncthingErrorCategory::OverallConnection, QNetworkReply::NoError);
|
|
return false;
|
|
}
|
|
// add exception
|
|
const QList<QSslCertificate> certs = QSslCertificate::fromPath(certPath);
|
|
if (certs.isEmpty()) {
|
|
emit error(tr("Unable to load certificate used by Syncthing."), SyncthingErrorCategory::OverallConnection, QNetworkReply::NoError);
|
|
return false;
|
|
}
|
|
const QSslCertificate &cert = certs.at(0);
|
|
m_expectedSslErrors.reserve(4);
|
|
m_expectedSslErrors << QSslError(QSslError::UnableToGetLocalIssuerCertificate, cert) << QSslError(QSslError::UnableToVerifyFirstCertificate, cert)
|
|
<< QSslError(QSslError::SelfSignedCertificate, cert) << QSslError(QSslError::HostNameMismatch, cert);
|
|
return true;
|
|
}
|
|
|
|
/*!
|
|
* \brief Applies the specified configuration.
|
|
* \remarks
|
|
* - The expected SSL errors of the specified configuration are updated accordingly.
|
|
* - The configuration is not used instantly. It will be used on the next reconnect.
|
|
* \returns Returns whether at least one property requiring a reconnect to take effect has changed.
|
|
* \sa reconnect()
|
|
*/
|
|
bool SyncthingConnection::applySettings(SyncthingConnectionSettings &connectionSettings)
|
|
{
|
|
bool reconnectRequired = false;
|
|
if (syncthingUrl() != connectionSettings.syncthingUrl) {
|
|
setSyncthingUrl(connectionSettings.syncthingUrl);
|
|
reconnectRequired = true;
|
|
}
|
|
if (apiKey() != connectionSettings.apiKey) {
|
|
setApiKey(connectionSettings.apiKey);
|
|
reconnectRequired = true;
|
|
}
|
|
if ((connectionSettings.authEnabled && (user() != connectionSettings.userName || password() != connectionSettings.password))
|
|
|| (!connectionSettings.authEnabled && (!user().isEmpty() || !password().isEmpty()))) {
|
|
if (connectionSettings.authEnabled) {
|
|
setCredentials(connectionSettings.userName, connectionSettings.password);
|
|
} else {
|
|
setCredentials(QString(), QString());
|
|
}
|
|
reconnectRequired = true;
|
|
}
|
|
if (connectionSettings.expectedSslErrors.isEmpty()) {
|
|
const bool previouslyHadExpectedSslErrors = !expectedSslErrors().isEmpty();
|
|
const bool ok = loadSelfSignedCertificate();
|
|
connectionSettings.expectedSslErrors = expectedSslErrors();
|
|
if (ok || (previouslyHadExpectedSslErrors && !ok)) {
|
|
reconnectRequired = true;
|
|
}
|
|
} else if (expectedSslErrors() != connectionSettings.expectedSslErrors) {
|
|
m_expectedSslErrors = connectionSettings.expectedSslErrors;
|
|
reconnectRequired = true;
|
|
}
|
|
|
|
setTrafficPollInterval(connectionSettings.trafficPollInterval);
|
|
setDevStatsPollInterval(connectionSettings.devStatsPollInterval);
|
|
setErrorsPollInterval(connectionSettings.errorsPollInterval);
|
|
setAutoReconnectInterval(connectionSettings.reconnectInterval);
|
|
setRequestTimeout(connectionSettings.requestTimeout);
|
|
setStatusComputionFlags(connectionSettings.statusComputionFlags);
|
|
|
|
return reconnectRequired;
|
|
}
|
|
|
|
/*!
|
|
* \brief Sets the connection status. Ensures statusChanged() is emitted if the status has actually changed.
|
|
* \param status Specifies the status; should be either SyncthingStatus::Disconnected, SyncthingStatus::Reconnecting, or
|
|
* SyncthingStatus::Idle. There is no use in specifying other values such as SyncthingStatus::Synchronizing as
|
|
* these are determined automatically within the method according to SyncthingConnection::statusComputionFlags().
|
|
*
|
|
* The precedence of the "connected" states from highest to lowest is:
|
|
* 1. SyncthingStatus::Synchronizing
|
|
* 2. SyncthingStatus::RemoteSynchronizing
|
|
* 3. SyncthingStatus::Scanning
|
|
* 4. SyncthingStatus::Paused
|
|
* 5. SyncthingStatus::Idle
|
|
*
|
|
* \remarks
|
|
* - The "out-of-sync" status is (currently) *not* handled by this function. One needs to query this via
|
|
* the SyncthingConnection::hasOutOfSyncDirs() function.
|
|
* - Whether notifications are available is *not* handled by this function. One needs to query this via
|
|
* SyncthingConnection::hasUnreadNotifications().
|
|
*/
|
|
void SyncthingConnection::setStatus(SyncthingStatus status)
|
|
{
|
|
if (m_status == SyncthingStatus::BeingDestroyed) {
|
|
return;
|
|
}
|
|
switch (status) {
|
|
case SyncthingStatus::Disconnected:
|
|
// disable (long) polling
|
|
m_keepPolling = false;
|
|
m_connectionAborted = true;
|
|
[[fallthrough]];
|
|
case SyncthingStatus::Reconnecting:
|
|
m_devStatsPollTimer.stop();
|
|
m_trafficPollTimer.stop();
|
|
m_errorsPollTimer.stop();
|
|
break;
|
|
default:
|
|
// reset reconnect tries
|
|
m_autoReconnectTries = 0;
|
|
|
|
// skip if no further status information should be gathered
|
|
status = SyncthingStatus::Idle;
|
|
if (m_statusComputionFlags == SyncthingStatusComputionFlags::None) {
|
|
break;
|
|
}
|
|
|
|
// check whether at least one directory is scanning, preparing to synchronize or synchronizing
|
|
// note: We don't distinguish between "preparing to sync" and "synchronizing" for computing the overall
|
|
// status at the moment.
|
|
auto scanning = false, synchronizing = false, remoteSynchronizing = false;
|
|
if (m_statusComputionFlags & SyncthingStatusComputionFlags::Synchronizing
|
|
|| m_statusComputionFlags & SyncthingStatusComputionFlags::Scanning) {
|
|
for (const SyncthingDir &dir : m_dirs) {
|
|
switch (dir.status) {
|
|
case SyncthingDirStatus::WaitingToSync:
|
|
case SyncthingDirStatus::PreparingToSync:
|
|
case SyncthingDirStatus::Synchronizing:
|
|
synchronizing = m_statusComputionFlags & SyncthingStatusComputionFlags::Synchronizing;
|
|
break;
|
|
case SyncthingDirStatus::WaitingToScan:
|
|
case SyncthingDirStatus::Scanning:
|
|
scanning = m_statusComputionFlags & SyncthingStatusComputionFlags::Scanning;
|
|
break;
|
|
default:;
|
|
}
|
|
if (synchronizing) {
|
|
break; // skip remaining dirs, "synchronizing" overrides "scanning" anyways
|
|
}
|
|
}
|
|
}
|
|
|
|
// set the status to "remote synchronizing" if at least one remote device is still in progress
|
|
if (!synchronizing && (m_statusComputionFlags & SyncthingStatusComputionFlags::RemoteSynchronizing)) {
|
|
for (const SyncthingDev &dev : m_devs) {
|
|
if (dev.status == SyncthingDevStatus::Synchronizing) {
|
|
remoteSynchronizing = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (synchronizing) {
|
|
status = SyncthingStatus::Synchronizing;
|
|
} else if (remoteSynchronizing) {
|
|
status = SyncthingStatus::RemoteNotInSync;
|
|
} else if (scanning) {
|
|
status = SyncthingStatus::Scanning;
|
|
} else if (m_statusComputionFlags & SyncthingStatusComputionFlags::DevicePaused) {
|
|
// check whether at least one device is paused
|
|
for (const SyncthingDev &dev : m_devs) {
|
|
if (dev.paused) {
|
|
status = SyncthingStatus::Paused;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (m_status != status || status == SyncthingStatus::Disconnected) {
|
|
// emit event if status changed always for disconnects so isConnecting() is re-evaluated
|
|
emit statusChanged(m_status = status);
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* \brief Internally called to emit a JSON parsing error.
|
|
* \remarks Since in this case the reply has already been read, its response must be passed as extra argument.
|
|
*/
|
|
void SyncthingConnection::emitError(const QString &message, const QJsonParseError &jsonError, QNetworkReply *reply, const QByteArray &response)
|
|
{
|
|
if (loggingFlags() & SyncthingConnectionLoggingFlags::ApiReplies) {
|
|
std::cerr << Phrases::Error << "JSON parsing error: " << message.toLocal8Bit().data() << jsonError.errorString().toLocal8Bit().data()
|
|
<< " (at offset " << jsonError.offset << ')' << Phrases::End;
|
|
}
|
|
emit error(message % jsonError.errorString() % QChar(' ') % QChar('(') % tr("at offset %1").arg(jsonError.offset) % QChar(')'),
|
|
SyncthingErrorCategory::Parsing, QNetworkReply::NoError, reply->request(), response);
|
|
}
|
|
|
|
/*!
|
|
* \brief Internally called to emit a network error (server replied error code are connection or server could not be reached at all).
|
|
*/
|
|
void SyncthingConnection::emitError(const QString &message, SyncthingErrorCategory category, QNetworkReply *reply)
|
|
{
|
|
if (loggingFlags() & SyncthingConnectionLoggingFlags::ApiReplies) {
|
|
cerr << Phrases::Error << "Syncthing connection error: " << message.toLocal8Bit().data() << reply->errorString().toLocal8Bit().data() << endl;
|
|
}
|
|
emit error(message + reply->errorString(), category, reply->error(), reply->request(), reply->bytesAvailable() ? reply->readAll() : QByteArray());
|
|
}
|
|
|
|
/*!
|
|
* \brief Internally called to emit myIdChanged() signal.
|
|
*/
|
|
void SyncthingConnection::emitMyIdChanged(const QString &newId)
|
|
{
|
|
if (newId.isEmpty() || m_myId == newId) {
|
|
return;
|
|
}
|
|
|
|
// adjust device status
|
|
int row = 0;
|
|
for (SyncthingDev &dev : m_devs) {
|
|
if (dev.id == newId) {
|
|
if (dev.status != SyncthingDevStatus::OwnDevice) {
|
|
dev.status = SyncthingDevStatus::OwnDevice;
|
|
dev.paused = false;
|
|
emit devStatusChanged(dev, row);
|
|
}
|
|
} else if (dev.status == SyncthingDevStatus::OwnDevice) {
|
|
dev.status = SyncthingDevStatus::Unknown;
|
|
emit devStatusChanged(dev, row);
|
|
}
|
|
++row;
|
|
}
|
|
|
|
emit myIdChanged(m_myId = newId);
|
|
}
|
|
|
|
/*!
|
|
* \brief Internally called to emit tildeChanged() signal.
|
|
*/
|
|
void SyncthingConnection::emitTildeChanged(const QString &newTilde, const QString &newPathSeparator)
|
|
{
|
|
if ((newTilde.isEmpty() || m_tilde == newTilde) && (newPathSeparator.isEmpty() && m_pathSeparator == newPathSeparator)) {
|
|
return;
|
|
}
|
|
m_tilde = newTilde;
|
|
m_pathSeparator = newPathSeparator;
|
|
emit tildeChanged(m_tilde);
|
|
}
|
|
|
|
/*!
|
|
* \brief Internally called to emit dirStatisticsChanged() event.
|
|
*/
|
|
void SyncthingConnection::emitDirStatisticsChanged()
|
|
{
|
|
if (m_dirStatsAltered) {
|
|
m_dirStatsAltered = false;
|
|
emit dirStatisticsChanged();
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* \brief Internally called to handle a fatal error when reading config (dirs/devs), status and events.
|
|
*/
|
|
void SyncthingConnection::handleFatalConnectionError()
|
|
{
|
|
setStatus(SyncthingStatus::Disconnected);
|
|
abortAllRequests();
|
|
if (m_autoReconnectTimer.interval()) {
|
|
m_autoReconnectTimer.start();
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* \brief Handles cancellation of additional requests done via continueConnecting() method.
|
|
*/
|
|
void SyncthingConnection::handleAdditionalRequestCanceled()
|
|
{
|
|
// postpone handling if there are still other requests pending
|
|
if (hasPendingRequests()) {
|
|
return;
|
|
}
|
|
if (m_abortingToReconnect) {
|
|
// if reconnection flag is set, instantly etstablish a new connection ...
|
|
continueReconnecting();
|
|
} else {
|
|
// ... otherwise declare we're disconnected if that was the last pending request
|
|
setStatus(SyncthingStatus::Disconnected);
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* \brief Internally called to recalculate the overall connection status, e.g. after the status of a directory
|
|
* changed.
|
|
* \remarks
|
|
* - This is achieved by simply setting the status to idle. setStatus() will calculate the specific status.
|
|
* - If not connected, this method does nothing. This is important, because when this method is called when
|
|
* establishing a connection (and the status is hence still disconnected) timers for polling would be killed.
|
|
*/
|
|
void SyncthingConnection::recalculateStatus()
|
|
{
|
|
if (isConnected()) {
|
|
setStatus(SyncthingStatus::Idle);
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* \fn SyncthingConnection::newConfig()
|
|
* \brief Indicates new configuration (dirs, devs, ...) is available.
|
|
* \remarks
|
|
* - Configuration is requested automatically when connecting.
|
|
* - Previous directories (and directory info objects!) are invalidated.
|
|
* - Previous devices (and device info objects!) are invalidated.
|
|
*/
|
|
|
|
/*!
|
|
* \fn SyncthingConnection::newDirs()
|
|
* \brief Indicates new directories are available.
|
|
* \remarks Always emitted after newConfig() as soon as new directory info objects become available.
|
|
*/
|
|
|
|
/*!
|
|
* \fn SyncthingConnection::newDevices()
|
|
* \brief Indicates new devices are available.
|
|
* \remarks Always emitted after newConfig() as soon as new device info objects become available.
|
|
*/
|
|
|
|
/*!
|
|
* \fn SyncthingConnection::newEvents()
|
|
* \brief Indicates new events (dir status changed, ...) are available.
|
|
* \remarks New events are automatically polled when connected.
|
|
*/
|
|
|
|
/*!
|
|
* \fn SyncthingConnection::dirStatusChanged()
|
|
* \brief Indicates the status of the specified \a dir changed.
|
|
*/
|
|
|
|
/*!
|
|
* \fn SyncthingConnection::devStatusChanged()
|
|
* \brief Indicates the status of the specified \a dev changed.
|
|
*/
|
|
|
|
/*!
|
|
* \fn SyncthingConnection::downloadProgressChanged()
|
|
* \brief Indicates the download progress changed.
|
|
*/
|
|
|
|
/*!
|
|
* \fn SyncthingConnection::newNotification()
|
|
* \brief Indicates a new Syncthing notification is available.
|
|
*/
|
|
|
|
/*!
|
|
* \fn SyncthingConnection::newDevAvailable()
|
|
* \brief Indicates another device wants to talk to us.
|
|
*/
|
|
|
|
/*!
|
|
* \fn SyncthingConnection::newDirAvailable()
|
|
* \brief Indicates a device wants to share an so far unknown directory with us.
|
|
* \remarks \a dev might be nullptr. In this case the device which wants to share the directory is unknown as well.
|
|
*/
|
|
|
|
/*!
|
|
* \fn SyncthingConnection::error()
|
|
* \brief Indicates a request (for configuration, events, ...) failed.
|
|
*/
|
|
|
|
/*!
|
|
* \fn SyncthingConnection::statusChanged()
|
|
* \brief Indicates the status of the connection changed (status(), hasOutOfSyncDirs()).
|
|
*/
|
|
|
|
/*!
|
|
* \fn SyncthingConnection::configDirChanged()
|
|
* \brief Indicates the Syncthing home/configuration directory changed.
|
|
*/
|
|
|
|
/*!
|
|
* \fn SyncthingConnection::myIdChanged()
|
|
* \brief Indicates ID of the own Syncthing device changed.
|
|
*/
|
|
|
|
/*!
|
|
* \fn SyncthingConnection::tildeChanged()
|
|
* \brief Indicates the tilde or path separator of the connected Syncthing instance changed.
|
|
*/
|
|
|
|
/*!
|
|
* \fn SyncthingConnection::trafficChanged()
|
|
* \brief Indicates totalIncomingTraffic() or totalOutgoingTraffic() has changed.
|
|
*/
|
|
|
|
/*!
|
|
* \fn SyncthingConnection::newConfigTriggered()
|
|
* \brief Indicates a new configuration has posted successfully via postConfig().
|
|
* \remarks In contrast to newConfig(), this signal is only emitted for configuration changes internally posted via postConfig().
|
|
*/
|
|
|
|
/*!
|
|
* \fn SyncthingConnection::rescanTriggered()
|
|
* \brief Indicates a rescan has been triggered successfully.
|
|
* \remarks Only emitted for rescans triggered internally via rescan() or rescanAll().
|
|
*/
|
|
|
|
/*!
|
|
* \fn SyncthingConnection::pauseTriggered()
|
|
* \brief Indicates a device has been paused successfully.
|
|
* \remarks Only emitted for pausing triggered internally via pause() or pauseAll().
|
|
*/
|
|
|
|
/*!
|
|
* \fn SyncthingConnection::resumeTriggered()
|
|
* \brief Indicates a device has been resumed successfully.
|
|
* \remarks Only emitted for resuming triggered internally via resume() or resumeAll().
|
|
*/
|
|
|
|
/*!
|
|
* \fn SyncthingConnection::restartTriggered()
|
|
* \brief Indicates a restart has been successfully triggered via restart().
|
|
*/
|
|
} // namespace Data
|