Rework reply/connection handling in SyncthingConnection
* Improve consistency * Ensure all relevant requests are have finished before setting status to connected
This commit is contained in:
parent
ece582db04
commit
a024e36ffd
|
@ -49,8 +49,22 @@ QNetworkAccessManager &networkAccessManager()
|
|||
|
||||
/*!
|
||||
* \class SyncthingConnection
|
||||
* \brief The SyncthingConnection class allows Qt applications to access Syncthing.
|
||||
* \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 authentification
|
||||
* 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 occured. 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.
|
||||
*/
|
||||
|
||||
/*!
|
||||
|
@ -75,6 +89,8 @@ SyncthingConnection::SyncthingConnection(const QString &syncthingUrl, const QByt
|
|||
, m_statusReply(nullptr)
|
||||
, m_connectionsReply(nullptr)
|
||||
, m_errorsReply(nullptr)
|
||||
, m_dirStatsReply(nullptr)
|
||||
, m_devStatsReply(nullptr)
|
||||
, m_eventsReply(nullptr)
|
||||
, m_versionReply(nullptr)
|
||||
, m_diskEventsReply(nullptr)
|
||||
|
@ -82,6 +98,8 @@ SyncthingConnection::SyncthingConnection(const QString &syncthingUrl, const QByt
|
|||
, m_unreadNotifications(false)
|
||||
, m_hasConfig(false)
|
||||
, m_hasStatus(false)
|
||||
, m_hasEvents(false)
|
||||
, m_hasDiskEvents(false)
|
||||
, m_lastFileDeleted(false)
|
||||
{
|
||||
m_trafficPollTimer.setInterval(2000);
|
||||
|
@ -112,6 +130,9 @@ SyncthingConnection::~SyncthingConnection()
|
|||
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));
|
||||
|
@ -166,7 +187,7 @@ void SyncthingConnection::connect()
|
|||
return;
|
||||
}
|
||||
|
||||
m_reconnecting = m_hasConfig = m_hasStatus = false;
|
||||
m_reconnecting = m_hasConfig = m_hasStatus = m_hasEvents = m_hasDiskEvents = false;
|
||||
if (m_apiKey.isEmpty() || m_syncthingUrl.isEmpty()) {
|
||||
emit error(tr("Connection configuration is insufficient."), SyncthingErrorCategory::OverallConnection, QNetworkReply::NoError);
|
||||
return;
|
||||
|
@ -217,13 +238,15 @@ void SyncthingConnection::reconnect()
|
|||
{
|
||||
m_autoReconnectTimer.stop();
|
||||
m_autoReconnectTries = 0;
|
||||
if (isConnected()) {
|
||||
m_reconnecting = true;
|
||||
m_hasConfig = m_hasStatus = false;
|
||||
abortAllRequests();
|
||||
} else {
|
||||
// reconnect right now if not connected and no pending requests
|
||||
if (!isConnected() && !hasPendingRequests()) {
|
||||
continueReconnecting();
|
||||
return;
|
||||
}
|
||||
// abort pending requests before connecting again
|
||||
m_keepPolling = m_reconnecting = true;
|
||||
m_hasConfig = m_hasStatus = m_hasEvents = m_hasDiskEvents = false;
|
||||
abortAllRequests();
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -236,6 +259,9 @@ void SyncthingConnection::reconnect(SyncthingConnectionSettings &connectionSetti
|
|||
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));
|
||||
|
@ -246,8 +272,15 @@ void SyncthingConnection::reconnectLater(int milliSeconds)
|
|||
*/
|
||||
void SyncthingConnection::continueReconnecting()
|
||||
{
|
||||
emit newConfig(QJsonObject()); // configuration will be invalidated
|
||||
setStatus(SyncthingStatus::Reconnecting);
|
||||
// postpone if there are still pending requests
|
||||
if (hasPendingRequests()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// invalidate configuration
|
||||
emit newConfig(QJsonObject());
|
||||
|
||||
// cleanup information from previous connection
|
||||
m_keepPolling = true;
|
||||
m_reconnecting = false;
|
||||
m_lastEventId = 0;
|
||||
|
@ -262,6 +295,8 @@ void SyncthingConnection::continueReconnecting()
|
|||
m_unreadNotifications = false;
|
||||
m_hasConfig = false;
|
||||
m_hasStatus = false;
|
||||
m_hasEvents = false;
|
||||
m_hasDiskEvents = false;
|
||||
m_dirs.clear();
|
||||
m_devs.clear();
|
||||
m_lastConnectionsUpdate = DateTime();
|
||||
|
@ -271,12 +306,16 @@ void SyncthingConnection::continueReconnecting()
|
|||
m_lastFileName.clear();
|
||||
m_lastFileDeleted = false;
|
||||
m_syncthingVersion.clear();
|
||||
|
||||
if (m_apiKey.isEmpty() || m_syncthingUrl.isEmpty()) {
|
||||
emit error(tr("Connection configuration is insufficient."), SyncthingErrorCategory::OverallConnection, QNetworkReply::NoError);
|
||||
return;
|
||||
}
|
||||
|
||||
requestConfig();
|
||||
requestStatus();
|
||||
|
||||
setStatus(SyncthingStatus::Reconnecting);
|
||||
}
|
||||
|
||||
void SyncthingConnection::autoReconnect()
|
||||
|
@ -727,9 +766,13 @@ SyncthingDev *SyncthingConnection::addDevInfo(std::vector<SyncthingDev> &devs, c
|
|||
*/
|
||||
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();
|
||||
|
@ -744,7 +787,8 @@ void SyncthingConnection::continueConnecting()
|
|||
requestCompletion(devId, dir.id);
|
||||
}
|
||||
}
|
||||
// since config and status could be read successfully, let's poll for events
|
||||
|
||||
// poll for events
|
||||
m_lastEventId = m_lastDiskEventId = 0;
|
||||
requestEvents();
|
||||
requestDiskEvents();
|
||||
|
@ -767,6 +811,12 @@ void SyncthingConnection::abortAllRequests()
|
|||
if (m_errorsReply) {
|
||||
m_errorsReply->abort();
|
||||
}
|
||||
if (m_dirStatsReply) {
|
||||
m_dirStatsReply->abort();
|
||||
}
|
||||
if (m_devStatsReply) {
|
||||
m_devStatsReply->abort();
|
||||
}
|
||||
if (m_eventsReply) {
|
||||
m_eventsReply->abort();
|
||||
}
|
||||
|
@ -779,6 +829,9 @@ void SyncthingConnection::abortAllRequests()
|
|||
if (m_logReply) {
|
||||
m_logReply->abort();
|
||||
}
|
||||
for (auto *const reply : m_otherReplies) {
|
||||
reply->abort();
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -788,6 +841,9 @@ void SyncthingConnection::abortAllRequests()
|
|||
*/
|
||||
void SyncthingConnection::requestConfig()
|
||||
{
|
||||
if (m_configReply) {
|
||||
return;
|
||||
}
|
||||
QObject::connect(
|
||||
m_configReply = requestData(QStringLiteral("system/config"), QUrlQuery()), &QNetworkReply::finished, this, &SyncthingConnection::readConfig);
|
||||
}
|
||||
|
@ -795,10 +851,13 @@ void SyncthingConnection::requestConfig()
|
|||
/*!
|
||||
* \brief Requests the Syncthing status asynchronously.
|
||||
*
|
||||
* The signals configDirChanged() and myIdChanged() are emitted when those values have changed; error() is emitted in the error case.
|
||||
* The signals myIdChanged() are emitted when those values have changed; error() is emitted in the error case.
|
||||
*/
|
||||
void SyncthingConnection::requestStatus()
|
||||
{
|
||||
if (m_statusReply) {
|
||||
return;
|
||||
}
|
||||
QObject::connect(
|
||||
m_statusReply = requestData(QStringLiteral("system/status"), QUrlQuery()), &QNetworkReply::finished, this, &SyncthingConnection::readStatus);
|
||||
}
|
||||
|
@ -821,6 +880,9 @@ void SyncthingConnection::requestConfigAndStatus()
|
|||
*/
|
||||
void SyncthingConnection::requestConnections()
|
||||
{
|
||||
if (m_connectionsReply) {
|
||||
return;
|
||||
}
|
||||
QObject::connect(m_connectionsReply = requestData(QStringLiteral("system/connections"), QUrlQuery()), &QNetworkReply::finished, this,
|
||||
&SyncthingConnection::readConnections);
|
||||
}
|
||||
|
@ -832,6 +894,9 @@ void SyncthingConnection::requestConnections()
|
|||
*/
|
||||
void SyncthingConnection::requestErrors()
|
||||
{
|
||||
if (m_errorsReply) {
|
||||
return;
|
||||
}
|
||||
QObject::connect(
|
||||
m_errorsReply = requestData(QStringLiteral("system/error"), QUrlQuery()), &QNetworkReply::finished, this, &SyncthingConnection::readErrors);
|
||||
}
|
||||
|
@ -852,8 +917,11 @@ void SyncthingConnection::requestClearingErrors()
|
|||
*/
|
||||
void SyncthingConnection::requestDirStatistics()
|
||||
{
|
||||
QObject::connect(
|
||||
requestData(QStringLiteral("stats/folder"), QUrlQuery()), &QNetworkReply::finished, this, &SyncthingConnection::readDirStatistics);
|
||||
if (m_dirStatsReply) {
|
||||
return;
|
||||
}
|
||||
QObject::connect(m_dirStatsReply = requestData(QStringLiteral("stats/folder"), QUrlQuery()), &QNetworkReply::finished, this,
|
||||
&SyncthingConnection::readDirStatistics);
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -865,6 +933,7 @@ void SyncthingConnection::requestDirStatus(const QString &dirId)
|
|||
query.addQueryItem(QStringLiteral("folder"), dirId);
|
||||
auto *const reply = requestData(QStringLiteral("db/status"), query);
|
||||
reply->setProperty("dirId", dirId);
|
||||
m_otherReplies << reply;
|
||||
QObject::connect(reply, &QNetworkReply::finished, this, &SyncthingConnection::readDirStatus);
|
||||
}
|
||||
|
||||
|
@ -879,6 +948,7 @@ void SyncthingConnection::requestCompletion(const QString &devId, const QString
|
|||
auto *const reply = requestData(QStringLiteral("db/completion"), query);
|
||||
reply->setProperty("devId", devId);
|
||||
reply->setProperty("dirId", dirId);
|
||||
m_otherReplies << reply;
|
||||
QObject::connect(reply, &QNetworkReply::finished, this, &SyncthingConnection::readCompletion);
|
||||
}
|
||||
|
||||
|
@ -887,18 +957,27 @@ void SyncthingConnection::requestCompletion(const QString &devId, const QString
|
|||
*/
|
||||
void SyncthingConnection::requestDeviceStatistics()
|
||||
{
|
||||
if (m_devStatsReply) {
|
||||
return;
|
||||
}
|
||||
QObject::connect(
|
||||
requestData(QStringLiteral("stats/device"), QUrlQuery()), &QNetworkReply::finished, this, &SyncthingConnection::readDeviceStatistics);
|
||||
}
|
||||
|
||||
void SyncthingConnection::requestVersion()
|
||||
{
|
||||
if (m_versionReply) {
|
||||
return;
|
||||
}
|
||||
QObject::connect(m_versionReply = requestData(QStringLiteral("system/version"), QUrlQuery()), &QNetworkReply::finished, this,
|
||||
&SyncthingConnection::readVersion);
|
||||
}
|
||||
|
||||
void SyncthingConnection::requestDiskEvents(int limit)
|
||||
{
|
||||
if (m_diskEventsReply) {
|
||||
return;
|
||||
}
|
||||
QUrlQuery query;
|
||||
query.addQueryItem(QStringLiteral("limit"), QString::number(limit));
|
||||
if (m_lastDiskEventId) {
|
||||
|
@ -938,6 +1017,9 @@ void SyncthingConnection::postConfigFromByteArray(const QByteArray &rawConfig)
|
|||
*/
|
||||
void SyncthingConnection::requestEvents()
|
||||
{
|
||||
if (m_eventsReply) {
|
||||
return;
|
||||
}
|
||||
QUrlQuery query;
|
||||
if (m_lastEventId) {
|
||||
query.addQueryItem(QStringLiteral("since"), QString::number(m_lastEventId));
|
||||
|
@ -966,12 +1048,11 @@ void SyncthingConnection::requestQrCode(const QString &text)
|
|||
*/
|
||||
void SyncthingConnection::requestLog()
|
||||
{
|
||||
// skip if already requesting log
|
||||
if (m_logReply != nullptr) {
|
||||
if (m_logReply) {
|
||||
return;
|
||||
}
|
||||
m_logReply = requestData(QStringLiteral("system/log"), QUrlQuery());
|
||||
QObject::connect(m_logReply, &QNetworkReply::finished, this, &SyncthingConnection::readLog);
|
||||
QObject::connect(
|
||||
m_logReply = requestData(QStringLiteral("system/log"), QUrlQuery()), &QNetworkReply::finished, this, &SyncthingConnection::readLog);
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -1095,14 +1176,15 @@ void SyncthingConnection::readConfig()
|
|||
|
||||
if (m_keepPolling) {
|
||||
concludeReadingConfigAndStatus();
|
||||
} else {
|
||||
readDevs(m_rawConfig.value(QLatin1String("devices")).toArray());
|
||||
readDirs(m_rawConfig.value(QLatin1String("folders")).toArray());
|
||||
return;
|
||||
}
|
||||
|
||||
readDevs(m_rawConfig.value(QLatin1String("devices")).toArray());
|
||||
readDirs(m_rawConfig.value(QLatin1String("folders")).toArray());
|
||||
break;
|
||||
}
|
||||
case QNetworkReply::OperationCanceledError:
|
||||
return; // intended, not an error
|
||||
return;
|
||||
default:
|
||||
emitError(tr("Unable to request Syncthing config: "), SyncthingErrorCategory::OverallConnection, reply);
|
||||
handleFatalConnectionError();
|
||||
|
@ -1221,7 +1303,7 @@ void SyncthingConnection::readStatus()
|
|||
break;
|
||||
}
|
||||
case QNetworkReply::OperationCanceledError:
|
||||
return; // intended, not an error
|
||||
return;
|
||||
default:
|
||||
emitError(tr("Unable to request Syncthing status: "), SyncthingErrorCategory::OverallConnection, reply);
|
||||
handleFatalConnectionError();
|
||||
|
@ -1241,13 +1323,20 @@ void SyncthingConnection::concludeReadingConfigAndStatus()
|
|||
|
||||
readDevs(m_rawConfig.value(QLatin1String("devices")).toArray());
|
||||
readDirs(m_rawConfig.value(QLatin1String("folders")).toArray());
|
||||
continueConnecting();
|
||||
}
|
||||
|
||||
if (isConnected()) {
|
||||
setStatus(SyncthingStatus::Idle);
|
||||
/*!
|
||||
* \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;
|
||||
}
|
||||
|
||||
continueConnecting();
|
||||
setStatus(SyncthingStatus::Idle);
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -1329,14 +1418,18 @@ void SyncthingConnection::readConnections()
|
|||
m_lastConnectionsUpdate = DateTime::gmtNow();
|
||||
|
||||
// since there seems no event for this data, keep polling
|
||||
if (m_keepPolling && m_trafficPollTimer.interval()) {
|
||||
m_trafficPollTimer.start();
|
||||
if (m_keepPolling) {
|
||||
concludeConnection();
|
||||
if (m_trafficPollTimer.interval()) {
|
||||
m_trafficPollTimer.start();
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case QNetworkReply::OperationCanceledError:
|
||||
return; // intended, not an error
|
||||
handleAdditionalRequestCanceled();
|
||||
return;
|
||||
default:
|
||||
emitError(tr("Unable to request connections: "), SyncthingErrorCategory::OverallConnection, reply);
|
||||
}
|
||||
|
@ -1349,6 +1442,9 @@ void SyncthingConnection::readDirStatistics()
|
|||
{
|
||||
auto *const reply = static_cast<QNetworkReply *>(sender());
|
||||
reply->deleteLater();
|
||||
if (reply == m_dirStatsReply) {
|
||||
m_dirStatsReply = nullptr;
|
||||
}
|
||||
|
||||
switch (reply->error()) {
|
||||
case QNetworkReply::NoError: {
|
||||
|
@ -1399,10 +1495,15 @@ void SyncthingConnection::readDirStatistics()
|
|||
}
|
||||
++index;
|
||||
}
|
||||
|
||||
if (m_keepPolling) {
|
||||
concludeConnection();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case QNetworkReply::OperationCanceledError:
|
||||
return; // intended, not an error
|
||||
handleAdditionalRequestCanceled();
|
||||
return;
|
||||
default:
|
||||
emitError(tr("Unable to request directory statistics: "), SyncthingErrorCategory::OverallConnection, reply);
|
||||
}
|
||||
|
@ -1415,6 +1516,9 @@ void SyncthingConnection::readDeviceStatistics()
|
|||
{
|
||||
auto *const reply = static_cast<QNetworkReply *>(sender());
|
||||
reply->deleteLater();
|
||||
if (reply == m_devStatsReply) {
|
||||
m_devStatsReply = nullptr;
|
||||
}
|
||||
|
||||
switch (reply->error()) {
|
||||
case QNetworkReply::NoError: {
|
||||
|
@ -1441,13 +1545,17 @@ void SyncthingConnection::readDeviceStatistics()
|
|||
++index;
|
||||
}
|
||||
// since there seems no event for this data, keep polling
|
||||
if (m_keepPolling && m_devStatsPollTimer.interval()) {
|
||||
m_devStatsPollTimer.start();
|
||||
if (m_keepPolling) {
|
||||
concludeConnection();
|
||||
if (m_devStatsPollTimer.interval()) {
|
||||
m_devStatsPollTimer.start();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case QNetworkReply::OperationCanceledError:
|
||||
return; // intended, not an error
|
||||
handleAdditionalRequestCanceled();
|
||||
return;
|
||||
default:
|
||||
emitError(tr("Unable to request device statistics: "), SyncthingErrorCategory::OverallConnection, reply);
|
||||
}
|
||||
|
@ -1494,13 +1602,17 @@ void SyncthingConnection::readErrors()
|
|||
}
|
||||
|
||||
// since there seems no event for this data, keep polling
|
||||
if (m_keepPolling && m_errorsPollTimer.interval()) {
|
||||
m_errorsPollTimer.start();
|
||||
if (m_keepPolling) {
|
||||
concludeConnection();
|
||||
if (m_errorsPollTimer.interval()) {
|
||||
m_errorsPollTimer.start();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case QNetworkReply::OperationCanceledError:
|
||||
return; // intended, not an error
|
||||
handleAdditionalRequestCanceled();
|
||||
return;
|
||||
default:
|
||||
emitError(tr("Unable to request errors: "), SyncthingErrorCategory::SpecificRequest, reply);
|
||||
}
|
||||
|
@ -1544,6 +1656,7 @@ void SyncthingConnection::readEvents()
|
|||
return;
|
||||
}
|
||||
|
||||
m_hasEvents = true;
|
||||
const auto replyArray(replyDoc.array());
|
||||
emit newEvents(replyArray);
|
||||
readEventsFromJsonArray(replyArray, m_lastEventId);
|
||||
|
@ -1560,14 +1673,7 @@ void SyncthingConnection::readEvents()
|
|||
// no new events available, keep polling
|
||||
break;
|
||||
case QNetworkReply::OperationCanceledError:
|
||||
// intended disconnect, not an error
|
||||
if (m_reconnecting) {
|
||||
// if reconnection flag is set, instantly etstablish a new connection ...
|
||||
continueReconnecting();
|
||||
} else {
|
||||
// ... otherwise keep disconnected
|
||||
setStatus(SyncthingStatus::Disconnected);
|
||||
}
|
||||
handleAdditionalRequestCanceled();
|
||||
return;
|
||||
default:
|
||||
emitError(tr("Unable to request Syncthing events: "), SyncthingErrorCategory::OverallConnection, reply);
|
||||
|
@ -1577,7 +1683,7 @@ void SyncthingConnection::readEvents()
|
|||
|
||||
if (m_keepPolling) {
|
||||
requestEvents();
|
||||
setStatus(SyncthingStatus::Idle);
|
||||
concludeConnection();
|
||||
} else {
|
||||
setStatus(SyncthingStatus::Disconnected);
|
||||
}
|
||||
|
@ -2122,6 +2228,8 @@ void SyncthingConnection::readDirStatus()
|
|||
{
|
||||
auto *const reply = static_cast<QNetworkReply *>(sender());
|
||||
reply->deleteLater();
|
||||
m_otherReplies.removeAll(reply);
|
||||
|
||||
switch (reply->error()) {
|
||||
case QNetworkReply::NoError: {
|
||||
// determine relevant dir
|
||||
|
@ -2145,8 +2253,15 @@ void SyncthingConnection::readDirStatus()
|
|||
if (readDirSummary(DateTime::gmtNow(), replyDoc.object(), *dir, index)) {
|
||||
recalculateStatus();
|
||||
}
|
||||
|
||||
if (m_keepPolling) {
|
||||
concludeConnection();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case QNetworkReply::OperationCanceledError:
|
||||
handleAdditionalRequestCanceled();
|
||||
return;
|
||||
default:
|
||||
emitError(tr("Unable to request directory statistics: "), SyncthingErrorCategory::SpecificRequest, reply);
|
||||
}
|
||||
|
@ -2250,6 +2365,7 @@ void SyncthingConnection::readCompletion()
|
|||
const auto devId(reply->property("devId").toString());
|
||||
const auto dirId(reply->property("dirId").toString());
|
||||
reply->deleteLater();
|
||||
m_otherReplies.removeAll(reply);
|
||||
|
||||
switch (reply->error()) {
|
||||
case QNetworkReply::NoError: {
|
||||
|
@ -2272,8 +2388,13 @@ void SyncthingConnection::readCompletion()
|
|||
|
||||
// update the relevant completion info
|
||||
readRemoteFolderCompletion(DateTime::gmtNow(), replyDoc.object(), *dir, index, devId);
|
||||
|
||||
concludeConnection();
|
||||
break;
|
||||
}
|
||||
case QNetworkReply::OperationCanceledError:
|
||||
handleAdditionalRequestCanceled();
|
||||
return;
|
||||
default:
|
||||
emitError(tr("Unable to request completion for device/directory %1/%2: ").arg(devId, dirId), SyncthingErrorCategory::SpecificRequest, reply);
|
||||
}
|
||||
|
@ -2303,10 +2424,14 @@ void SyncthingConnection::readVersion()
|
|||
const auto replyObj(replyDoc.object());
|
||||
m_syncthingVersion = replyObj.value(QLatin1String("longVersion")).toString();
|
||||
|
||||
if (m_keepPolling) {
|
||||
concludeConnection();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case QNetworkReply::OperationCanceledError:
|
||||
return; // intended, not an error
|
||||
handleAdditionalRequestCanceled();
|
||||
return;
|
||||
default:
|
||||
emitError(tr("Unable to request version: "), SyncthingErrorCategory::OverallConnection, reply);
|
||||
}
|
||||
|
@ -2333,13 +2458,16 @@ void SyncthingConnection::readDiskEvents()
|
|||
return;
|
||||
}
|
||||
|
||||
m_hasDiskEvents = true;
|
||||
readEventsFromJsonArray(replyDoc.array(), m_lastDiskEventId);
|
||||
break;
|
||||
}
|
||||
case QNetworkReply::TimeoutError:
|
||||
break; // no new events available, keep polling
|
||||
// no new events available, keep polling
|
||||
break;
|
||||
case QNetworkReply::OperationCanceledError:
|
||||
return; // intended, not an error
|
||||
handleAdditionalRequestCanceled();
|
||||
return;
|
||||
default:
|
||||
emitError(tr("Unable to request disk events: "), SyncthingErrorCategory::OverallConnection, reply);
|
||||
handleFatalConnectionError();
|
||||
|
@ -2348,6 +2476,7 @@ void SyncthingConnection::readDiskEvents()
|
|||
|
||||
if (m_keepPolling) {
|
||||
requestDiskEvents();
|
||||
concludeConnection();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2442,9 +2571,10 @@ void SyncthingConnection::setStatus(SyncthingStatus status)
|
|||
}
|
||||
switch (status) {
|
||||
case SyncthingStatus::Disconnected:
|
||||
case SyncthingStatus::Reconnecting:
|
||||
// disable (long) polling
|
||||
m_keepPolling = false;
|
||||
FALLTHROUGH;
|
||||
case SyncthingStatus::Reconnecting:
|
||||
m_devStatsPollTimer.stop();
|
||||
m_trafficPollTimer.stop();
|
||||
m_errorsPollTimer.stop();
|
||||
|
@ -2561,6 +2691,20 @@ void SyncthingConnection::handleFatalConnectionError()
|
|||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Handles cancelation of additional requests done via continueConnecting() method.
|
||||
*/
|
||||
void SyncthingConnection::handleAdditionalRequestCanceled()
|
||||
{
|
||||
if (m_reconnecting) {
|
||||
// if reconnection flag is set, instantly etstablish a new connection ...
|
||||
continueReconnecting();
|
||||
} else if (hasPendingRequests()) {
|
||||
// ... otherwise declare we're disconnected if that was the last pending request
|
||||
setStatus(SyncthingStatus::Disconnected);
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Internally called to recalculate the overall connection status, eg. after the status of a directory
|
||||
* changed.
|
||||
|
|
|
@ -96,6 +96,7 @@ public:
|
|||
const QString &syncthingUrl = QStringLiteral("http://localhost:8080"), const QByteArray &apiKey = QByteArray(), QObject *parent = nullptr);
|
||||
~SyncthingConnection() override;
|
||||
|
||||
// getter/setter for
|
||||
const QString &syncthingUrl() const;
|
||||
void setSyncthingUrl(const QString &url);
|
||||
bool isLocal() const;
|
||||
|
@ -104,12 +105,17 @@ public:
|
|||
const QString &user() const;
|
||||
const QString &password() const;
|
||||
void setCredentials(const QString &user, const QString &password);
|
||||
|
||||
// getter for the status of the connection to Syncthing and of Syncthing itself
|
||||
SyncthingStatus status() const;
|
||||
QString statusText() const;
|
||||
static QString statusText(SyncthingStatus status);
|
||||
bool isConnected() const;
|
||||
bool hasPendingRequests() const;
|
||||
bool hasUnreadNotifications() const;
|
||||
bool hasOutOfSyncDirs() const;
|
||||
|
||||
// getter/setter to configure connection behavior
|
||||
bool isRequestingCompletionEnabled() const;
|
||||
void setRequestingCompletionEnabled(bool requestingCompletionEnabled);
|
||||
int trafficPollInterval() const;
|
||||
|
@ -121,6 +127,8 @@ public:
|
|||
int autoReconnectInterval() const;
|
||||
unsigned int autoReconnectTries() const;
|
||||
void setAutoReconnectInterval(int interval);
|
||||
|
||||
// getter for information retrieved from Syncthing
|
||||
const QString &configDir() const;
|
||||
const QString &myId() const;
|
||||
uint64 totalIncomingTraffic() const;
|
||||
|
@ -136,23 +144,24 @@ public:
|
|||
ChronoUtilities::DateTime startTime() const;
|
||||
ChronoUtilities::TimeSpan uptime() const;
|
||||
const QString &syncthingVersion() const;
|
||||
void requestQrCode(const QString &text);
|
||||
void requestLog();
|
||||
const QList<QSslError> &expectedSslErrors() const;
|
||||
SyncthingDir *findDirInfo(const QString &dirId, int &row);
|
||||
SyncthingDir *findDirInfo(QLatin1String key, const QJsonObject &object, int *row = nullptr);
|
||||
SyncthingDir *findDirInfoByPath(const QString &path, QString &relativePath, int &row);
|
||||
SyncthingDev *findDevInfo(const QString &devId, int &row);
|
||||
SyncthingDev *findDevInfoByName(const QString &devName, int &row);
|
||||
QStringList directoryIds() const;
|
||||
QStringList deviceIds() const;
|
||||
QString deviceNameOrId(const QString &deviceId) const;
|
||||
std::vector<const SyncthingDev *> connectedDevices() const;
|
||||
const QJsonObject &rawConfig() const;
|
||||
SyncthingDir *findDirInfo(const QString &dirId, int &row);
|
||||
SyncthingDir *findDirInfo(QLatin1String key, const QJsonObject &object, int *row = nullptr);
|
||||
SyncthingDir *findDirInfoByPath(const QString &path, QString &relativePath, int &row);
|
||||
SyncthingDev *findDevInfo(const QString &devId, int &row);
|
||||
SyncthingDev *findDevInfoByName(const QString &devName, int &row);
|
||||
|
||||
const QList<QSslError> &expectedSslErrors() const;
|
||||
|
||||
public Q_SLOTS:
|
||||
bool loadSelfSignedCertificate();
|
||||
bool applySettings(SyncthingConnectionSettings &connectionSettings);
|
||||
|
||||
// methods to initiate/close connection
|
||||
void connect();
|
||||
void connect(SyncthingConnectionSettings &connectionSettings);
|
||||
void connectLater(int milliSeconds);
|
||||
|
@ -160,6 +169,9 @@ public Q_SLOTS:
|
|||
void reconnect();
|
||||
void reconnect(SyncthingConnectionSettings &connectionSettings);
|
||||
void reconnectLater(int milliSeconds);
|
||||
void abortAllRequests();
|
||||
|
||||
// methods to trigger certain actions (resume, rescan, restart, ...)
|
||||
bool pauseDevice(const QStringList &devIds);
|
||||
bool pauseAllDevs();
|
||||
bool resumeDevice(const QStringList &devIds);
|
||||
|
@ -174,6 +186,7 @@ public Q_SLOTS:
|
|||
void shutdown();
|
||||
void considerAllNotificationsRead();
|
||||
|
||||
// methods to GET or POST information from/to Syncthing
|
||||
void requestConfig();
|
||||
void requestStatus();
|
||||
void requestConfigAndStatus();
|
||||
|
@ -187,6 +200,8 @@ public Q_SLOTS:
|
|||
void requestDeviceStatistics();
|
||||
void requestVersion();
|
||||
void requestDiskEvents(int limit = 25);
|
||||
void requestQrCode(const QString &text);
|
||||
void requestLog();
|
||||
void postConfigFromJsonObject(const QJsonObject &rawConfig);
|
||||
void postConfigFromByteArray(const QByteArray &rawConfig);
|
||||
|
||||
|
@ -221,13 +236,13 @@ Q_SIGNALS:
|
|||
void qrCodeAvailable(const QString &text, const QByteArray &qrCodeData);
|
||||
|
||||
private Q_SLOTS:
|
||||
void abortAllRequests();
|
||||
|
||||
// handler to evaluate results from request...() methods
|
||||
void readConfig();
|
||||
void readDirs(const QJsonArray &dirs);
|
||||
void readDevs(const QJsonArray &devs);
|
||||
void readStatus();
|
||||
void concludeReadingConfigAndStatus();
|
||||
void concludeConnection();
|
||||
void readConnections();
|
||||
void readDirStatistics();
|
||||
void readDeviceStatistics();
|
||||
|
@ -267,6 +282,7 @@ private Q_SLOTS:
|
|||
void readLog();
|
||||
void readQrCode();
|
||||
|
||||
// internal helper methods
|
||||
void continueConnecting();
|
||||
void continueReconnecting();
|
||||
void autoReconnect();
|
||||
|
@ -276,9 +292,11 @@ private Q_SLOTS:
|
|||
void emitError(const QString &message, SyncthingErrorCategory category, QNetworkReply *reply);
|
||||
void emitMyIdChanged(const QString &newId);
|
||||
void handleFatalConnectionError();
|
||||
void handleAdditionalRequestCanceled();
|
||||
void recalculateStatus();
|
||||
|
||||
private:
|
||||
// internal helper methods
|
||||
QNetworkRequest prepareRequest(const QString &path, const QUrlQuery &query, bool rest = true);
|
||||
QNetworkReply *requestData(const QString &path, const QUrlQuery &query, bool rest = true);
|
||||
QNetworkReply *postData(const QString &path, const QUrlQuery &query, const QByteArray &data = QByteArray());
|
||||
|
@ -312,13 +330,18 @@ private:
|
|||
QNetworkReply *m_statusReply;
|
||||
QNetworkReply *m_connectionsReply;
|
||||
QNetworkReply *m_errorsReply;
|
||||
QNetworkReply *m_dirStatsReply;
|
||||
QNetworkReply *m_devStatsReply;
|
||||
QNetworkReply *m_eventsReply;
|
||||
QNetworkReply *m_versionReply;
|
||||
QNetworkReply *m_diskEventsReply;
|
||||
QNetworkReply *m_logReply;
|
||||
QList<QNetworkReply *> m_otherReplies;
|
||||
bool m_unreadNotifications;
|
||||
bool m_hasConfig;
|
||||
bool m_hasStatus;
|
||||
bool m_hasEvents;
|
||||
bool m_hasDiskEvents;
|
||||
std::vector<SyncthingDir> m_dirs;
|
||||
std::vector<SyncthingDev> m_devs;
|
||||
ChronoUtilities::DateTime m_lastConnectionsUpdate;
|
||||
|
@ -406,13 +429,31 @@ inline SyncthingStatus SyncthingConnection::status() const
|
|||
}
|
||||
|
||||
/*!
|
||||
* \brief Returns whether the connection has been established.
|
||||
* \brief Returns whether the connection to Syncthing has been established.
|
||||
*
|
||||
* If ture, all information like dirInfo() and devInfo() has been populated and will be updated if it changes.
|
||||
*/
|
||||
inline bool SyncthingConnection::isConnected() const
|
||||
{
|
||||
return m_status != SyncthingStatus::Disconnected && m_status != SyncthingStatus::Reconnecting;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Returns whether the SyncthingConnector instance is waiting for Syncthing to respond to a request.
|
||||
* \remarks
|
||||
* - Requests for (disk) events are excluded because those are long polling requests and therefore always pending.
|
||||
* Instead, we take only into account whether those requests have been at least concluded once (since the last
|
||||
* reconnect).
|
||||
* - Only requests which contribute to the overall state and population of myId(), dirInfo(), devInfo(), traffic
|
||||
* statistics, ... are considered. So requests for QR code, logs, clearing errors, rescan, ... are not taken
|
||||
* into account.
|
||||
*/
|
||||
inline bool SyncthingConnection::hasPendingRequests() const
|
||||
{
|
||||
return m_configReply || m_statusReply || (m_eventsReply && !m_hasEvents) || (m_diskEventsReply && !m_hasDiskEvents) || m_connectionsReply
|
||||
|| m_dirStatsReply || m_devStatsReply || m_errorsReply || m_versionReply || !m_otherReplies.isEmpty();
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Returns whether there are unread notifications available.
|
||||
* \remarks This flag is set to true when new notifications become available. It can be unset again by calling considerAllNotificationsRead().
|
||||
|
|
Loading…
Reference in New Issue