Use signals for logAvailable() and qrCodeAvailable()

This commit is contained in:
Martchus 2018-10-20 22:08:25 +02:00
parent c7bccf023a
commit 2e67e6b2de
6 changed files with 122 additions and 104 deletions

View File

@ -316,7 +316,8 @@ void Application::handleError(
void Application::requestLog(const ArgumentOccurrence &)
{
m_connection.requestLog(&Application::printLog);
connect(&m_connection, &SyncthingConnection::logAvailable, printLog);
m_connection.requestLog();
cerr << "Request log from " << m_settings.syncthingUrl.toLocal8Bit().data() << " ...";
cerr.flush();
}

View File

@ -78,6 +78,7 @@ SyncthingConnection::SyncthingConnection(const QString &syncthingUrl, const QByt
, m_eventsReply(nullptr)
, m_versionReply(nullptr)
, m_diskEventsReply(nullptr)
, m_logReply(nullptr)
, m_unreadNotifications(false)
, m_hasConfig(false)
, m_hasStatus(false)
@ -775,6 +776,9 @@ void SyncthingConnection::abortAllRequests()
if (m_diskEventsReply) {
m_diskEventsReply->abort();
}
if (m_logReply) {
m_logReply->abort();
}
}
/*!
@ -944,58 +948,30 @@ void SyncthingConnection::requestEvents()
/*!
* \brief Requests a QR code for the specified \a text.
*
* The specified \a callback is called on success; otherwise error() is emitted.
* qrCodeAvailable() is emitted on success; otherwise error() is emitted.
*/
QMetaObject::Connection SyncthingConnection::requestQrCode(const QString &text, const std::function<void(const QByteArray &)> &callback)
void SyncthingConnection::requestQrCode(const QString &text)
{
QUrlQuery query;
query.addQueryItem(QStringLiteral("text"), text);
QNetworkReply *const reply = requestData(QStringLiteral("/qr/"), query, false);
return QObject::connect(reply, &QNetworkReply::finished, [this, reply, &callback] {
reply->deleteLater();
switch (reply->error()) {
case QNetworkReply::NoError:
callback(reply->readAll());
break;
default:
emit error(tr("Unable to request QR-Code: ") + reply->errorString(), SyncthingErrorCategory::SpecificRequest, reply->error());
}
});
QNetworkReply *reply = requestData(QStringLiteral("/qr/"), query, false);
reply->setProperty("qrText", text);
QObject::connect(reply, &QNetworkReply::finished, this, &SyncthingConnection::readQrCode);
}
/*!
* \brief Requests the Syncthing log.
*
* The specified \a callback is called on success; otherwise error() is emitted.
* logAvailable() is emitted on success; otherwise error() is emitted.
*/
QMetaObject::Connection SyncthingConnection::requestLog(const std::function<void(const std::vector<SyncthingLogEntry> &)> &callback)
void SyncthingConnection::requestLog()
{
QNetworkReply *const reply = requestData(QStringLiteral("system/log"), QUrlQuery());
return QObject::connect(reply, &QNetworkReply::finished, [this, reply, &callback] {
reply->deleteLater();
switch (reply->error()) {
case QNetworkReply::NoError: {
QJsonParseError jsonError;
const QJsonDocument replyDoc = QJsonDocument::fromJson(reply->readAll(), &jsonError);
if (jsonError.error != QJsonParseError::NoError) {
emit error(tr("Unable to parse Syncthing log: ") + jsonError.errorString(), SyncthingErrorCategory::Parsing, QNetworkReply::NoError);
return;
}
const QJsonArray log(replyDoc.object().value(QLatin1String("messages")).toArray());
vector<SyncthingLogEntry> logEntries;
logEntries.reserve(static_cast<size_t>(log.size()));
for (const QJsonValue &logVal : log) {
const QJsonObject logObj(logVal.toObject());
logEntries.emplace_back(logObj.value(QLatin1String("when")).toString(), logObj.value(QLatin1String("message")).toString());
}
callback(logEntries);
break;
}
default:
emit error(tr("Unable to request Syncthing log: ") + reply->errorString(), SyncthingErrorCategory::SpecificRequest, reply->error());
}
});
// skip if already requesting log
if (m_logReply != nullptr) {
return;
}
m_logReply = requestData(QStringLiteral("system/log"), QUrlQuery());
QObject::connect(m_logReply, &QNetworkReply::finished, this, &SyncthingConnection::readLog);
}
/*!
@ -2397,6 +2373,62 @@ void SyncthingConnection::readChangeEvent(DateTime eventTime, const QString &eve
emit dirStatusChanged(*dirInfo, index);
}
/*!
* \brief Reads log entries queried via requestLog().
*/
void SyncthingConnection::readLog()
{
auto *const reply = static_cast<QNetworkReply *>(sender());
reply->deleteLater();
if (reply == m_logReply) {
m_logReply = nullptr;
}
switch (reply->error()) {
case QNetworkReply::NoError: {
QJsonParseError jsonError;
const QJsonDocument replyDoc = QJsonDocument::fromJson(reply->readAll(), &jsonError);
if (jsonError.error != QJsonParseError::NoError) {
emit error(tr("Unable to parse Syncthing log: ") + jsonError.errorString(), SyncthingErrorCategory::Parsing, QNetworkReply::NoError);
return;
}
const QJsonArray log(replyDoc.object().value(QLatin1String("messages")).toArray());
vector<SyncthingLogEntry> logEntries;
logEntries.reserve(static_cast<size_t>(log.size()));
for (const QJsonValue &logVal : log) {
const QJsonObject logObj(logVal.toObject());
logEntries.emplace_back(logObj.value(QLatin1String("when")).toString(), logObj.value(QLatin1String("message")).toString());
}
emit logAvailable(logEntries);
break;
}
case QNetworkReply::OperationCanceledError:
break;
default:
emit error(tr("Unable to request Syncthing log: ") + reply->errorString(), SyncthingErrorCategory::SpecificRequest, reply->error());
}
}
/*!
* \brief Reads the QR code queried via requestQrCode().
*/
void SyncthingConnection::readQrCode()
{
auto *const reply = static_cast<QNetworkReply *>(sender());
reply->deleteLater();
switch (reply->error()) {
case QNetworkReply::NoError:
emit qrCodeAvailable(reply->property("qrText").toString(), reply->readAll());
break;
case QNetworkReply::OperationCanceledError:
break;
default:
emit error(tr("Unable to request QR-Code: ") + reply->errorString(), SyncthingErrorCategory::SpecificRequest, reply->error());
}
}
/*!
* \brief Sets the connection status. Ensures statusChanged() is emitted.
* \param status Specifies the status; should be either SyncthingStatus::Disconnected, SyncthingStatus::Reconnecting, or

View File

@ -136,8 +136,8 @@ public:
ChronoUtilities::DateTime startTime() const;
ChronoUtilities::TimeSpan uptime() const;
const QString &syncthingVersion() const;
QMetaObject::Connection requestQrCode(const QString &text, const std::function<void(const QByteArray &)> &callback);
QMetaObject::Connection requestLog(const std::function<void(const std::vector<SyncthingLogEntry> &)> &callback);
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);
@ -217,6 +217,8 @@ Q_SIGNALS:
void directoryResumeTriggered(const QStringList &dirIds);
void restartTriggered();
void shutdownTriggered();
void logAvailable(const std::vector<SyncthingLogEntry> &logEntries);
void qrCodeAvailable(const QString &text, const QByteArray &qrCodeData);
private Q_SLOTS:
void abortAllRequests();
@ -262,6 +264,8 @@ private Q_SLOTS:
void readVersion();
void readDiskEvents();
void readChangeEvent(ChronoUtilities::DateTime eventTime, const QString &eventType, const QJsonObject &eventData);
void readLog();
void readQrCode();
void continueConnecting();
void continueReconnecting();
@ -311,6 +315,7 @@ private:
QNetworkReply *m_eventsReply;
QNetworkReply *m_versionReply;
QNetworkReply *m_diskEventsReply;
QNetworkReply *m_logReply;
bool m_unreadNotifications;
bool m_hasConfig;
bool m_hasStatus;

View File

@ -79,7 +79,9 @@ public:
void tearDown();
private:
template <typename Action, typename... Signalinfo> void waitForConnection(Action action, int timeout, const Signalinfo &... signalinfo);
template <typename Action, typename... Signalinfo> void waitForConnection(Action action, int timeout, const Signalinfo &... signalInfo);
template <typename Action, typename FailureSignalInfo, typename... Signalinfo>
void waitForConnectionOrFail(Action action, int timeout, const FailureSignalInfo &failureSignalInfo, const Signalinfo &... signalInfo);
template <typename Signal, typename Handler = function<void(void)>>
SignalInfo<Signal, Handler> connectionSignal(
Signal signal, const Handler &handler = function<void(void)>(), bool *correctSignalEmitted = nullptr);
@ -149,12 +151,22 @@ void ConnectionTests::tearDown()
//
/*!
* \brief Variant of waitForSignal() where sender is the connection and the action is a method of the connection.
* \brief Variant of waitForSignal() where the sender is the connection and the action is a method of the connection.
*/
template <typename Action, typename... Signalinfo>
void ConnectionTests::waitForConnection(Action action, int timeout, const Signalinfo &... signalinfo)
template <typename Action, typename... SignalInfo>
void ConnectionTests::waitForConnection(Action action, int timeout, const SignalInfo &... signalInfo)
{
waitForSignals(bind(action, &m_connection), timeout, signalinfo...);
waitForSignals(bind(action, &m_connection), timeout, signalInfo...);
}
/*!
* \brief Variant of waitForSignalOrFail() where the sender is the connection and the action is a method of the connection.
*/
template <typename Action, typename FailureSignalInfo, typename... SignalInfo>
void ConnectionTests::waitForConnectionOrFail(
Action action, int timeout, const FailureSignalInfo &failureSignalInfo, const SignalInfo &... signalInfo)
{
waitForSignalsOrFail(bind(action, &m_connection), timeout, failureSignalInfo, signalInfo...);
}
/*!
@ -555,30 +567,13 @@ void ConnectionTests::testRequestingLog()
cerr << "\n - Requesting log ..." << endl;
waitForConnected();
// timeout after 1 second
QTimer timeout;
timeout.setSingleShot(true);
timeout.setInterval(SYNCTHINGTESTHELPER_TIMEOUT(5000));
QEventLoop loop;
CPPUNIT_ASSERT(QObject::connect(&timeout, &QTimer::timeout, &loop, &QEventLoop::quit));
CPPUNIT_ASSERT(QObject::connect(&m_connection, &SyncthingConnection::error, &loop, &QEventLoop::quit));
bool callbackOk = false;
const auto request = m_connection.requestLog([&callbackOk, &loop](const std::vector<SyncthingLogEntry> &logEntries) {
callbackOk = true;
const function<void(const vector<SyncthingLogEntry> &)> handleLogAvailable = [&](const vector<SyncthingLogEntry> &logEntries) {
CPPUNIT_ASSERT(!logEntries.empty());
CPPUNIT_ASSERT(!logEntries[0].when.isEmpty());
CPPUNIT_ASSERT(!logEntries[0].message.isEmpty());
loop.quit();
});
CPPUNIT_ASSERT(request);
timeout.start();
loop.exec();
QObject::disconnect(request); // ensure callback is not called after return (in error case)
CPPUNIT_ASSERT_MESSAGE(
argsToString("log entries returned before timeout of ", timeout.interval(), " ms (set SYNCTHING_TEST_TIMEOUT_FACTOR to increase timeout)"),
callbackOk);
};
waitForConnectionOrFail(&SyncthingConnection::requestLog, 5000, connectionSignal(&SyncthingConnection::error),
connectionSignal(&SyncthingConnection::logAvailable, handleLogAvailable));
}
void ConnectionTests::testRequestingQrCode()
@ -586,28 +581,12 @@ void ConnectionTests::testRequestingQrCode()
cerr << "\n - Requesting QR-Code for own device ID ..." << endl;
waitForConnected();
// timeout after 2 seconds
QTimer timeout;
timeout.setSingleShot(true);
timeout.setInterval(SYNCTHINGTESTHELPER_TIMEOUT(5000));
QEventLoop loop;
CPPUNIT_ASSERT(QObject::connect(&timeout, &QTimer::timeout, &loop, &QEventLoop::quit));
CPPUNIT_ASSERT(QObject::connect(&m_connection, &SyncthingConnection::error, &loop, &QEventLoop::quit));
bool callbackOk = false;
const auto request = m_connection.requestQrCode(m_ownDevId, [&callbackOk, &loop](const QByteArray &data) {
callbackOk = true;
const function<void(const QString &, const QByteArray &)> handleQrCodeAvailable = [&](const QString &qrText, const QByteArray &data) {
CPPUNIT_ASSERT_EQUAL(QStringLiteral("some text"), qrText);
CPPUNIT_ASSERT(!data.isEmpty());
loop.quit();
});
CPPUNIT_ASSERT(request);
timeout.start();
loop.exec();
QObject::disconnect(request); // ensure callback is not called after return (in error case)
CPPUNIT_ASSERT_MESSAGE(argsToString("QR code returned before an error occurred or timeout of ", timeout.interval(),
" ms (set SYNCTHING_TEST_TIMEOUT_FACTOR to increase timeout)"),
callbackOk);
};
waitForSignalsOrFail(bind(&SyncthingConnection::requestQrCode, &m_connection, QStringLiteral("some text")), 5000,
connectionSignal(&SyncthingConnection::error), connectionSignal(&SyncthingConnection::qrCodeAvailable, handleQrCodeAvailable));
}
void ConnectionTests::testDisconnecting()

View File

@ -46,13 +46,18 @@ QDialog *ownDeviceIdDialog(Data::SyncthingConnection &connection)
QObject::connect(
copyPushButton, &QPushButton::clicked, bind(&QClipboard::setText, QGuiApplication::clipboard(), connection.myId(), QClipboard::Clipboard));
layout->addWidget(copyPushButton);
QObject::connect(dlg, &QWidget::destroyed,
connection.requestQrCode(connection.myId());
QObject::connect(dlg, &QObject::destroyed,
bind(static_cast<bool (*)(const QMetaObject::Connection &)>(&QObject::disconnect),
connection.requestQrCode(connection.myId(), [pixmapLabel](const QByteArray &data) {
QPixmap pixmap;
pixmap.loadFromData(data);
pixmapLabel->setPixmap(pixmap);
})));
QObject::connect(&connection, &SyncthingConnection::qrCodeAvailable,
[pixmapLabel, devId = connection.myId()](const QString &text, const QByteArray &data) {
if (text != devId) {
return;
}
QPixmap pixmap;
pixmap.loadFromData(data);
pixmapLabel->setPixmap(pixmap);
})));
dlg->setLayout(layout);
return dlg;
}

View File

@ -142,13 +142,9 @@ TextViewDialog *TextViewDialog::forDirectoryErrors(const Data::SyncthingDir &dir
TextViewDialog *TextViewDialog::forLogEntries(SyncthingConnection &connection)
{
auto *const dlg = new TextViewDialog(tr("Log"));
const auto loadLog = [dlg, &connection] {
connect(dlg, &QWidget::destroyed,
bind(static_cast<bool (*)(const QMetaObject::Connection &)>(&QObject::disconnect),
connection.requestLog(bind(&TextViewDialog::showLogEntries, dlg, _1))));
};
connect(dlg, &TextViewDialog::reload, loadLog);
loadLog();
QObject::connect(&connection, &SyncthingConnection::logAvailable, dlg, &TextViewDialog::showLogEntries);
connect(dlg, &TextViewDialog::reload, &connection, &SyncthingConnection::requestLog);
connection.requestLog();
return dlg;
}