Allow configuring long-polling timeout

This commit is contained in:
Martchus 2023-11-06 15:06:40 +01:00
parent 2b58266d8b
commit fada3c98dd
7 changed files with 84 additions and 17 deletions

View File

@ -83,6 +83,7 @@ SyncthingConnection::SyncthingConnection(
, m_lastDiskEventId(0)
, m_autoReconnectTries(0)
, m_requestTimeout(SyncthingConnectionSettings::defaultRequestTimeout)
, m_longPollingTimeout(SyncthingConnectionSettings::defaultLongPollingTimeout)
, m_totalIncomingTraffic(unknownTraffic)
, m_totalOutgoingTraffic(unknownTraffic)
, m_totalIncomingRate(0.0)
@ -857,6 +858,7 @@ bool SyncthingConnection::applySettings(SyncthingConnectionSettings &connectionS
setErrorsPollInterval(connectionSettings.errorsPollInterval);
setAutoReconnectInterval(connectionSettings.reconnectInterval);
setRequestTimeout(connectionSettings.requestTimeout);
setLongPollingTimeout(connectionSettings.longPollingTimeout);
setStatusComputionFlags(connectionSettings.statusComputionFlags);
return reconnectRequired;

View File

@ -73,6 +73,7 @@ class LIB_SYNCTHING_CONNECTOR_EXPORT SyncthingConnection : public QObject {
Q_PROPERTY(int devStatsPollInterval READ devStatsPollInterval WRITE setDevStatsPollInterval)
Q_PROPERTY(bool recordFileChanges READ recordFileChanges WRITE setRecordFileChanges)
Q_PROPERTY(int requestTimeout READ requestTimeout WRITE setRequestTimeout)
Q_PROPERTY(int longPollingTimeout READ longPollingTimeout WRITE setLongPollingTimeout)
Q_PROPERTY(QString myId READ myId NOTIFY myIdChanged)
Q_PROPERTY(QString tilde READ tilde NOTIFY tildeChanged)
Q_PROPERTY(QString pathSeparator READ pathSeparator NOTIFY tildeChanged)
@ -141,6 +142,8 @@ public:
void setRecordFileChanges(bool recordFileChanges);
int requestTimeout() const;
void setRequestTimeout(int requestTimeout);
int longPollingTimeout() const;
void setLongPollingTimeout(int longPollingTimeout);
// getter for information retrieved from Syncthing
const QString &configDir() const;
@ -345,8 +348,8 @@ private:
QNetworkReply *reply;
QByteArray response;
};
QNetworkRequest prepareRequest(const QString &path, const QUrlQuery &query, bool rest = true, bool noTimeout = false);
QNetworkReply *requestData(const QString &path, const QUrlQuery &query, bool rest = true, bool noTimeout = false);
QNetworkRequest prepareRequest(const QString &path, const QUrlQuery &query, bool rest = true, bool longPolling = false);
QNetworkReply *requestData(const QString &path, const QUrlQuery &query, bool rest = true, bool longPolling = false);
QNetworkReply *postData(const QString &path, const QUrlQuery &query, const QByteArray &data = QByteArray());
QNetworkReply *sendData(const QByteArray &verb, const QString &path, const QUrlQuery &query, const QByteArray &data = QByteArray());
Reply prepareReply(bool readData = true, bool handleAborting = true);
@ -382,6 +385,7 @@ private:
QTimer m_autoReconnectTimer;
unsigned int m_autoReconnectTries;
int m_requestTimeout;
int m_longPollingTimeout;
QString m_configDir;
QString m_myId;
QString m_tilde;
@ -739,6 +743,25 @@ inline void SyncthingConnection::setRequestTimeout(int requestTimeout)
m_requestTimeout = requestTimeout;
}
/*!
* \brief Returns the transfer timeout for long polling requests (event APIs) in milliseconds.
* \sa QNetworkRequest::transferTimeout()
*/
inline int SyncthingConnection::longPollingTimeout() const
{
return m_longPollingTimeout;
}
/*!
* \brief Sets the transfer timeout for long polling requests (event APIs) in milliseconds.
* \remarks Existing requests are not affected. Only effective when compiled against Qt 5.15 or higher.
* \sa QNetworkRequest::setTransferTimeout()
*/
inline void SyncthingConnection::setLongPollingTimeout(int longPollingTimeout)
{
m_longPollingTimeout = longPollingTimeout;
}
/*!
* \brief Returns what information is considered to compute the overall status returned by status().
*/

View File

@ -33,7 +33,7 @@ namespace Data {
/*!
* \brief Prepares a request for the specified \a path and \a query.
*/
QNetworkRequest SyncthingConnection::prepareRequest(const QString &path, const QUrlQuery &query, bool rest, bool noTimeout)
QNetworkRequest SyncthingConnection::prepareRequest(const QString &path, const QUrlQuery &query, bool rest, bool longPolling)
{
QUrl url(m_syncthingUrl);
url.setPath(rest ? (url.path() % QStringLiteral("/rest/") % path) : (url.path() + path));
@ -49,7 +49,8 @@ QNetworkRequest SyncthingConnection::prepareRequest(const QString &path, const Q
request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy);
#endif
#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
request.setTransferTimeout(noTimeout ? 0 : m_requestTimeout);
// give it a few seconds more time than the actual long polling interval set via the timeout query parameter
request.setTransferTimeout(longPolling ? (m_longPollingTimeout ? m_longPollingTimeout + 5000 : 0) : m_requestTimeout);
#endif
return request;
}
@ -57,16 +58,17 @@ QNetworkRequest SyncthingConnection::prepareRequest(const QString &path, const Q
/*!
* \brief Requests asynchronously data using the rest API.
*/
QNetworkReply *SyncthingConnection::requestData(const QString &path, const QUrlQuery &query, bool rest, bool noTimeout)
QNetworkReply *SyncthingConnection::requestData(const QString &path, const QUrlQuery &query, bool rest, bool longPolling)
{
#ifndef LIB_SYNCTHING_CONNECTOR_CONNECTION_MOCKED
auto *const reply = networkAccessManager().get(prepareRequest(path, query, rest, noTimeout));
auto *const reply = networkAccessManager().get(prepareRequest(path, query, rest, longPolling));
QObject::connect(reply, &QNetworkReply::sslErrors, this, &SyncthingConnection::handleSslErrors);
if (loggingFlags() & SyncthingConnectionLoggingFlags::ApiCalls) {
cerr << Phrases::Info << "Querying API: GET " << reply->url().toString().toStdString() << Phrases::EndFlush;
}
return reply;
#else
Q_UNUSED(longPolling)
return MockedReply::forRequest(QStringLiteral("GET"), path, query, rest);
#endif
}
@ -1709,6 +1711,8 @@ void SyncthingConnection::requestEvents()
// force to return immediately after the first call
if (!m_hasEvents) {
query.addQueryItem(QStringLiteral("timeout"), QStringLiteral("0"));
} else if (m_longPollingTimeout) {
query.addQueryItem(QStringLiteral("timeout"), QString::number(m_longPollingTimeout / 1000));
}
QObject::connect(m_eventsReply = requestData(QStringLiteral("events"), query, true, m_hasEvents), &QNetworkReply::finished, this,
&SyncthingConnection::readEvents);
@ -2315,6 +2319,8 @@ void SyncthingConnection::requestDiskEvents(int limit)
// force to return immediately after the first call
if (!m_hasDiskEvents) {
query.addQueryItem(QStringLiteral("timeout"), QStringLiteral("0"));
} else if (m_longPollingTimeout) {
query.addQueryItem(QStringLiteral("timeout"), QString::number(m_longPollingTimeout / 1000));
}
QObject::connect(m_diskEventsReply = requestData(QStringLiteral("events/disk"), query, true, m_hasDiskEvents), &QNetworkReply::finished, this,
&SyncthingConnection::readDiskEvents);

View File

@ -44,6 +44,7 @@ struct LIB_SYNCTHING_CONNECTOR_EXPORT SyncthingConnectionSettings {
int errorsPollInterval = defaultErrorsPollInterval;
int reconnectInterval = defaultReconnectInterval;
int requestTimeout = defaultRequestTimeout;
int longPollingTimeout = defaultLongPollingTimeout;
QString httpsCertPath;
QList<QSslError> expectedSslErrors;
SyncthingStatusComputionFlags statusComputionFlags = SyncthingStatusComputionFlags::Default;
@ -55,6 +56,7 @@ struct LIB_SYNCTHING_CONNECTOR_EXPORT SyncthingConnectionSettings {
static constexpr int defaultErrorsPollInterval = 30000;
static constexpr int defaultReconnectInterval = 30000;
static constexpr int defaultRequestTimeout = 0;
static constexpr int defaultLongPollingTimeout = 0;
};
} // namespace Data

View File

@ -305,7 +305,7 @@
</property>
</widget>
</item>
<item row="18" column="0">
<item row="19" column="0">
<widget class="QLabel" name="pollLabel">
<property name="text">
<string>Poll interval</string>
@ -315,7 +315,7 @@
</property>
</widget>
</item>
<item row="18" column="1">
<item row="19" column="1">
<layout class="QGridLayout" name="gridLayout">
<property name="horizontalSpacing">
<number>10</number>
@ -455,7 +455,7 @@
</item>
</layout>
</item>
<item row="19" column="1">
<item row="20" column="1">
<widget class="QCheckBox" name="autoConnectCheckBox">
<property name="toolTip">
<string>Whether to connect automatically on startup. This setting might be overruled by systemd and launcher settings.</string>
@ -465,7 +465,7 @@
</property>
</widget>
</item>
<item row="20" column="0">
<item row="21" column="0">
<widget class="QLabel" name="overallStatusLabel">
<property name="text">
<string>Overall status</string>
@ -475,7 +475,7 @@
</property>
</widget>
</item>
<item row="20" column="1">
<item row="21" column="1">
<layout class="QVBoxLayout" name="statusComputionFlagsVerticalLayout">
<property name="spacing">
<number>2</number>
@ -520,21 +520,21 @@
</item>
</layout>
</item>
<item row="21" column="0">
<item row="22" column="0">
<widget class="QLabel" name="statusTextLabel">
<property name="text">
<string>Current status</string>
</property>
</widget>
</item>
<item row="21" column="1">
<item row="22" column="1">
<widget class="QLabel" name="statusLabel">
<property name="text">
<string>disconnected</string>
</property>
</widget>
</item>
<item row="24" column="1">
<item row="25" column="1">
<widget class="QPushButton" name="connectPushButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
@ -567,6 +567,9 @@
</item>
<item row="17" column="0">
<widget class="QLabel" name="timeoutLabel">
<property name="toolTip">
<string>The timeout for normal requests to Syncthing's API. Set to zero to enforce no timeout.</string>
</property>
<property name="text">
<string>Transfer timeout</string>
</property>
@ -614,6 +617,32 @@
</property>
</widget>
</item>
<item row="18" column="0">
<widget class="QLabel" name="longPollingLabel">
<property name="toolTip">
<string>The timeout for long-polling requests to Syncthing's event API. Set to zero to use Syncthing's default timeout and no timeout being enforced from Syncthing Tray's side.</string>
</property>
<property name="text">
<string>Long polling int.</string>
</property>
</widget>
</item>
<item row="18" column="1">
<widget class="QSpinBox" name="longPollingSpinBox">
<property name="specialValueText">
<string>Syncthing's default with no timeout</string>
</property>
<property name="suffix">
<string> ms</string>
</property>
<property name="maximum">
<number>1000000000</number>
</property>
<property name="value">
<number>60000</number>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>

View File

@ -310,6 +310,8 @@ bool restore()
connectionSettings->reconnectInterval
= settings.value(QStringLiteral("reconnectInterval"), connectionSettings->reconnectInterval).toInt();
connectionSettings->requestTimeout = settings.value(QStringLiteral("requestTimeout"), connectionSettings->requestTimeout).toInt();
connectionSettings->longPollingTimeout
= settings.value(QStringLiteral("longPollingTimeout"), connectionSettings->longPollingTimeout).toInt();
connectionSettings->autoConnect = settings.value(QStringLiteral("autoConnect"), connectionSettings->autoConnect).toBool();
const auto statusComputionFlags = settings.value(QStringLiteral("statusComputionFlags"),
QVariant::fromValue(static_cast<UnderlyingFlagType>(connectionSettings->statusComputionFlags)));
@ -461,6 +463,7 @@ bool save()
settings.setValue(QStringLiteral("errorsPollInterval"), connectionSettings->errorsPollInterval);
settings.setValue(QStringLiteral("reconnectInterval"), connectionSettings->reconnectInterval);
settings.setValue(QStringLiteral("requestTimeout"), connectionSettings->requestTimeout);
settings.setValue(QStringLiteral("longPollingTimeout"), connectionSettings->longPollingTimeout);
settings.setValue(QStringLiteral("autoConnect"), connectionSettings->autoConnect);
settings.setValue(QStringLiteral("statusComputionFlags"),
QVariant::fromValue(static_cast<std::underlying_type_t<Data::SyncthingStatusComputionFlags>>(connectionSettings->statusComputionFlags)));

View File

@ -214,6 +214,7 @@ bool ConnectionOptionPage::showConnectionSettings(int index)
ui()->apiKeyLineEdit->setText(connectionSettings.apiKey);
ui()->certPathSelection->lineEdit()->setText(connectionSettings.httpsCertPath);
ui()->timeoutSpinBox->setValue(connectionSettings.requestTimeout);
ui()->longPollingSpinBox->setValue(connectionSettings.longPollingTimeout);
ui()->pollTrafficSpinBox->setValue(connectionSettings.trafficPollInterval);
ui()->pollDevStatsSpinBox->setValue(connectionSettings.devStatsPollInterval);
ui()->pollErrorsSpinBox->setValue(connectionSettings.errorsPollInterval);
@ -240,6 +241,7 @@ bool ConnectionOptionPage::cacheCurrentSettings(bool applying)
connectionSettings.expectedSslErrors.clear();
connectionSettings.httpsCertPath = ui()->certPathSelection->lineEdit()->text();
connectionSettings.requestTimeout = ui()->timeoutSpinBox->value();
connectionSettings.longPollingTimeout = ui()->longPollingSpinBox->value();
connectionSettings.trafficPollInterval = ui()->pollTrafficSpinBox->value();
connectionSettings.devStatsPollInterval = ui()->pollDevStatsSpinBox->value();
connectionSettings.errorsPollInterval = ui()->pollErrorsSpinBox->value();
@ -364,9 +366,9 @@ void ConnectionOptionPage::toggleAdvancedSettings(bool show)
return;
}
for (auto *const widget : std::initializer_list<QWidget *>{ ui()->authLabel, ui()->authCheckBox, ui()->userNameLabel, ui()->userNameLineEdit,
ui()->passwordLabel, ui()->passwordLineEdit, ui()->timeoutLabel, ui()->timeoutSpinBox, ui()->pollLabel, ui()->pollDevStatsLabel,
ui()->pollDevStatsSpinBox, ui()->pollErrorsLabel, ui()->pollErrorsSpinBox, ui()->pollTrafficLabel, ui()->pollTrafficSpinBox,
ui()->reconnectLabel, ui()->reconnectSpinBox }) {
ui()->passwordLabel, ui()->passwordLineEdit, ui()->timeoutLabel, ui()->timeoutSpinBox, ui()->longPollingLabel, ui()->longPollingSpinBox,
ui()->pollLabel, ui()->pollDevStatsLabel, ui()->pollDevStatsSpinBox, ui()->pollErrorsLabel, ui()->pollErrorsSpinBox,
ui()->pollTrafficLabel, ui()->pollTrafficSpinBox, ui()->reconnectLabel, ui()->reconnectSpinBox }) {
widget->setVisible(show);
}
}