Add option to use new config route instead of deprecated one

For the sake of compatibility with older Syncthing versions, this option is
not enabled by default.
This commit is contained in:
Martchus 2023-02-03 12:21:18 +01:00
parent c7f6da6a17
commit 46c2333230
6 changed files with 74 additions and 8 deletions

View File

@ -13,7 +13,7 @@ set(META_VERSION_MAJOR 1)
set(META_VERSION_MINOR 3)
set(META_VERSION_PATCH 3)
set(META_RELEASE_DATE "2023-01-02")
set(META_SOVERSION 4)
set(META_SOVERSION 5)
set(META_ADD_DEFAULT_CPP_UNIT_TEST_APPLICATION ON)
project(${META_PROJECT_NAME})

View File

@ -505,6 +505,8 @@ It is possible to turn on logging of the underlying library by setting environme
* `SYNCTHING_PORT`: override the port of the Syncthing test instance spawned when running tests
* `SYNCTHINGTRAY_SYSTEMD_USER_UNIT`: override the name of the systemd user-unit checked by the wizard's
setup detection
* `LIB_SYNCTHING_CONNECTOR_USE_DEPRECATED_ROUTES`: change whether to use deprecated routes (enabled by
default for compatibility with older Syncthing versions, set to `0` to change the behavior)
## Known bugs and workarounds
The following bugs are caused by dependencies or limitations of certain

View File

@ -103,6 +103,7 @@ SyncthingConnection::SyncthingConnection(
, m_lastFileDeleted(false)
, m_dirStatsAltered(false)
, m_recordFileChanges(false)
, m_useDeprecatedRoutes(true)
{
m_trafficPollTimer.setInterval(SyncthingConnectionSettings::defaultTrafficPollInterval);
m_trafficPollTimer.setTimerType(Qt::VeryCoarseTimer);
@ -125,6 +126,13 @@ SyncthingConnection::SyncthingConnection(
#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;
}
}
/*!

View File

@ -109,6 +109,7 @@ class LIB_SYNCTHING_CONNECTOR_EXPORT SyncthingConnection : public QObject {
Q_PROPERTY(QStringList directoryIds READ directoryIds)
Q_PROPERTY(QStringList deviceIds READ deviceIds)
Q_PROPERTY(QJsonObject rawConfig READ rawConfig NOTIFY newConfig)
Q_PROPERTY(bool useDeprecatedRoutes READ isUsingDeprecatedRoutes WRITE setUseDeprecatedRoutes)
public:
explicit SyncthingConnection(const QString &syncthingUrl = QStringLiteral("http://localhost:8080"), const QByteArray &apiKey = QByteArray(),
@ -124,6 +125,8 @@ public:
const QString &user() const;
const QString &password() const;
void setCredentials(const QString &user, const QString &password);
bool isUsingDeprecatedRoutes() const;
void setUseDeprecatedRoutes(bool useLegacyRoutes);
// getter for the status of the connection to Syncthing and of Syncthing itself
SyncthingStatus status() const;
@ -340,6 +343,8 @@ private Q_SLOTS:
void handleAdditionalRequestCanceled();
void handleSslErrors(const QList<QSslError> &errors);
void recalculateStatus();
QString configPath() const;
QByteArray changeConfigVerb() const;
private:
// internal helper methods
@ -350,6 +355,7 @@ private:
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());
QNetworkReply *sendData(const QByteArray &verb, const QString &path, const QUrlQuery &query, const QByteArray &data = QByteArray());
Reply prepareReply(bool readData = true, bool handleAborting = true);
Reply prepareReply(QNetworkReply *&expectedReply, bool readData = true, bool handleAborting = true);
Reply prepareReply(QList<QNetworkReply *> &expectedReplies, bool readData = true, bool handleAborting = true);
@ -420,6 +426,7 @@ private:
QJsonObject m_rawConfig;
bool m_dirStatsAltered;
bool m_recordFileChanges;
bool m_useDeprecatedRoutes;
};
/*!
@ -479,6 +486,27 @@ inline void SyncthingConnection::setCredentials(const QString &user, const QStri
m_password = password;
}
/*!
* \brief Returns whether deprecated routes should still be used in order to support older versions of Syncthing.
* \remarks
* - This is still enabled by default but may cease to work once those deprecated routes have been removed by
* Syncthing itself.
* - Disabling this will require Syncthing 1.12.0 or newer.
*/
inline bool SyncthingConnection::isUsingDeprecatedRoutes() const
{
return m_useDeprecatedRoutes;
}
/*!
* \brief Returns whether deprecated routes should still be used in order to support older versions of Syncthing.
* \sa See isUsingLegacyRoutes() for details.
*/
inline void SyncthingConnection::setUseDeprecatedRoutes(bool useDeprecatedRoutes)
{
m_useDeprecatedRoutes = useDeprecatedRoutes;
}
/*!
* \brief Returns the string representation of the current status().
*/

View File

@ -75,6 +75,19 @@ QNetworkReply *SyncthingConnection::postData(const QString &path, const QUrlQuer
return reply;
}
/*!
* \brief Invokes an asynchronous request using the rest API.
*/
QNetworkReply *SyncthingConnection::sendData(const QByteArray &verb, const QString &path, const QUrlQuery &query, const QByteArray &data)
{
auto *const reply = networkAccessManager().sendCustomRequest(prepareRequest(path, query), verb, data);
QObject::connect(reply, &QNetworkReply::sslErrors, this, &SyncthingConnection::handleSslErrors);
if (loggingFlags() & SyncthingConnectionLoggingFlags::ApiCalls) {
cerr << Phrases::Info << "Querying API: " << verb.data() << ' ' << reply->url().toString().toStdString() << Phrases::EndFlush;
}
return reply;
}
/*!
* \brief Prepares the current reply.
*/
@ -195,6 +208,22 @@ SyncthingConnection::Reply SyncthingConnection::handleReply(QNetworkReply *reply
return data;
}
/*!
* \brief Returns the path to Syncthing's "config" route depending on whether deprecated routes should be used.
*/
QString SyncthingConnection::configPath() const
{
return isUsingDeprecatedRoutes() ? QStringLiteral("system/config") : QStringLiteral("config");
}
/*!
* \brief Returns the verb for posting the Syncthing config in accordance to the path returned by configPath().
*/
QByteArray SyncthingConnection::changeConfigVerb() const
{
return isUsingDeprecatedRoutes() ? QByteArray("POST") : QByteArray("PUT");
}
// pause/resume devices
/*!
@ -261,7 +290,7 @@ bool SyncthingConnection::pauseResumeDevice(const QStringList &devIds, bool paus
QJsonDocument doc;
doc.setObject(config);
QNetworkReply *const reply = postData(QStringLiteral("system/config"), QUrlQuery(), doc.toJson(QJsonDocument::Compact));
QNetworkReply *const reply = sendData(changeConfigVerb(), configPath(), QUrlQuery(), doc.toJson(QJsonDocument::Compact));
reply->setProperty("devIds", devIds);
reply->setProperty("resume", !paused);
QObject::connect(reply, &QNetworkReply::finished, this, &SyncthingConnection::readDevPauseResume);
@ -366,7 +395,7 @@ bool SyncthingConnection::pauseResumeDirectory(const QStringList &dirIds, bool p
if (setDirectoriesPaused(config, dirIds, paused)) {
QJsonDocument doc;
doc.setObject(config);
QNetworkReply *const reply = postData(QStringLiteral("system/config"), QUrlQuery(), doc.toJson(QJsonDocument::Compact));
QNetworkReply *const reply = sendData(changeConfigVerb(), configPath(), QUrlQuery(), doc.toJson(QJsonDocument::Compact));
reply->setProperty("dirIds", dirIds);
reply->setProperty("resume", !paused);
QObject::connect(reply, &QNetworkReply::finished, this, &SyncthingConnection::readDirPauseResume);
@ -563,8 +592,7 @@ void SyncthingConnection::requestConfig()
if (m_configReply) {
return;
}
QObject::connect(
m_configReply = requestData(QStringLiteral("system/config"), QUrlQuery()), &QNetworkReply::finished, this, &SyncthingConnection::readConfig);
QObject::connect(m_configReply = requestData(configPath(), QUrlQuery()), &QNetworkReply::finished, this, &SyncthingConnection::readConfig);
}
/*!
@ -1407,7 +1435,7 @@ void SyncthingConnection::readLog()
*/
void SyncthingConnection::postConfigFromJsonObject(const QJsonObject &rawConfig)
{
QObject::connect(postData(QStringLiteral("system/config"), QUrlQuery(), QJsonDocument(rawConfig).toJson(QJsonDocument::Compact)),
QObject::connect(sendData(changeConfigVerb(), configPath(), QUrlQuery(), QJsonDocument(rawConfig).toJson(QJsonDocument::Compact)),
&QNetworkReply::finished, this, &SyncthingConnection::readPostConfig);
}
@ -1420,7 +1448,7 @@ void SyncthingConnection::postConfigFromJsonObject(const QJsonObject &rawConfig)
void SyncthingConnection::postConfigFromByteArray(const QByteArray &rawConfig)
{
QObject::connect(
postData(QStringLiteral("system/config"), QUrlQuery(), rawConfig), &QNetworkReply::finished, this, &SyncthingConnection::readPostConfig);
sendData(changeConfigVerb(), configPath(), QUrlQuery(), rawConfig), &QNetworkReply::finished, this, &SyncthingConnection::readPostConfig);
}
/*!

View File

@ -372,7 +372,7 @@ void ConnectionTests::testErrorCases()
return;
}
if ((errorMessage.startsWith(QStringLiteral("Unable to request Syncthing config: Error transferring "))
&& errorMessage.endsWith(QStringLiteral("/rest/system/config - server replied: Forbidden")))) {
&& errorMessage.endsWith(QStringLiteral("config - server replied: Forbidden")))) {
apiKeyErrorConfig = true;
return;
}