WIP: Add capture/replay feature for easier debugging
This commit is contained in:
parent
c51379cdbd
commit
fc74d2b1de
|
@ -27,6 +27,7 @@ set(SRC_FILES
|
|||
syncthingdev.cpp
|
||||
syncthingconnection.cpp
|
||||
syncthingconnection_requests.cpp
|
||||
syncthingconnection_capture_replay.cpp
|
||||
syncthingconnectionsettings.cpp
|
||||
syncthingnotifier.cpp
|
||||
syncthingconfig.cpp
|
||||
|
|
|
@ -71,6 +71,8 @@ SyncthingConnection::SyncthingConnection(const QString &syncthingUrl, const QByt
|
|||
: QObject(parent)
|
||||
, m_syncthingUrl(syncthingUrl)
|
||||
, m_apiKey(apiKey)
|
||||
, m_captureFile(nullptr)
|
||||
, m_replayFile(nullptr)
|
||||
, m_status(SyncthingStatus::Disconnected)
|
||||
, m_statusComputionFlags(SyncthingStatusComputionFlags::Default)
|
||||
, m_keepPolling(false)
|
||||
|
@ -120,7 +122,7 @@ SyncthingConnection::SyncthingConnection(const QString &syncthingUrl, const QByt
|
|||
QObject::connect(&m_autoReconnectTimer, &QTimer::timeout, this, &SyncthingConnection::autoReconnect);
|
||||
|
||||
#ifdef LIB_SYNCTHING_CONNECTOR_CONNECTION_MOCKED
|
||||
setupTestData();
|
||||
setupTestData(); // TODO: remove this in favor of capturing/replay feature
|
||||
#endif
|
||||
|
||||
#ifdef LIB_SYNCTHING_CONNECTOR_LOG_SYNCTHING_EVENTS
|
||||
|
@ -133,6 +135,8 @@ SyncthingConnection::SyncthingConnection(const QString &syncthingUrl, const QByt
|
|||
std::cerr << displayNames(devs).join(QStringLiteral(", ")).toStdString() << endl;
|
||||
});
|
||||
#endif
|
||||
|
||||
initCaptureReplay();
|
||||
}
|
||||
|
||||
/*!
|
||||
|
|
|
@ -304,6 +304,11 @@ private Q_SLOTS:
|
|||
void handleAdditionalRequestCanceled();
|
||||
void recalculateStatus();
|
||||
|
||||
// capture/replay feature
|
||||
void initCaptureReplay();
|
||||
void captureResponse(std::string_view context, const QByteArray &response);
|
||||
void replyCapturedResponses();
|
||||
|
||||
private:
|
||||
// internal helper methods
|
||||
QNetworkRequest prepareRequest(const QString &path, const QUrlQuery &query, bool rest = true);
|
||||
|
@ -321,6 +326,8 @@ private:
|
|||
QByteArray m_apiKey;
|
||||
QString m_user;
|
||||
QString m_password;
|
||||
QFile *m_captureFile, *m_replayFile;
|
||||
CppUtilities::DateTime m_lastReplyEvent;
|
||||
SyncthingStatus m_status;
|
||||
SyncthingStatusComputionFlags m_statusComputionFlags;
|
||||
|
||||
|
|
|
@ -0,0 +1,146 @@
|
|||
#include "./syncthingconnection.h"
|
||||
|
||||
#include <c++utilities/conversion/conversionexception.h>
|
||||
#include <c++utilities/conversion/stringconversion.h>
|
||||
|
||||
#include <QFile>
|
||||
|
||||
namespace Data {
|
||||
|
||||
/// \cond
|
||||
static QString readEnvVar(const char *name)
|
||||
{
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
||||
return qEnvironmentVariable(name);
|
||||
#else
|
||||
return QString::fromLocal8Bit(qgetenv(name));
|
||||
#endif
|
||||
}
|
||||
/// \endcond
|
||||
|
||||
/*!
|
||||
* \brief Initializes capturing/replay if corresponding environment variables are set.
|
||||
*/
|
||||
void SyncthingConnection::initCaptureReplay()
|
||||
{
|
||||
const auto captureFilePath = readEnvVar("SYNCTHING_TRAY_CAPTURE_FILE");
|
||||
if (!captureFilePath.isEmpty()) {
|
||||
m_captureFile = new QFile(captureFilePath, this);
|
||||
m_captureFile->open(QFile::WriteOnly); // TODO: error handling
|
||||
}
|
||||
const auto replyFilePath = readEnvVar("SYNCTHING_TRAY_REPLAY_FILE");
|
||||
if (!replyFilePath.isEmpty()) {
|
||||
m_replayFile = new QFile(replyFilePath, this);
|
||||
m_replayFile->open(QFile::ReadOnly); // TODO: error handling
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
* \brief Records the responsive if a capture file is initialized.
|
||||
*/
|
||||
void SyncthingConnection::captureResponse(std::string_view context, const QByteArray &response)
|
||||
{
|
||||
if (!m_captureFile) {
|
||||
return;
|
||||
}
|
||||
const char marker[] = "--- ";
|
||||
const auto space = ' ', newLine = '\n';
|
||||
const auto timeStamp = CppUtilities::DateTime::exactGmtNow().toIsoString();
|
||||
const auto size = CppUtilities::numberToString(response.size());
|
||||
m_captureFile->write(marker, 4);
|
||||
m_captureFile->write(timeStamp.data(), timeStamp.size());
|
||||
m_captureFile->write(&space, 1);
|
||||
m_captureFile->write(context.data(), context.size());
|
||||
m_captureFile->write(&space, 1);
|
||||
m_captureFile->write(response);
|
||||
m_captureFile->write(&newLine, 1);
|
||||
m_captureFile->write(response);
|
||||
m_captureFile->write(&newLine, 1);
|
||||
// TODO: error handling
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Replays the captured responses if a reply file is initialized.
|
||||
*/
|
||||
void SyncthingConnection::replyCapturedResponses()
|
||||
{
|
||||
if (!m_replayFile) {
|
||||
return;
|
||||
}
|
||||
enum State {
|
||||
Marker0, Marker1, Marker2, Marker3, TimeStamp, Size, Context,
|
||||
} state = Marker0;
|
||||
auto c = char();
|
||||
auto timeStampChars = std::string();
|
||||
auto timeStamp = CppUtilities::DateTime();
|
||||
auto sizeFactor = 1ul;
|
||||
auto size = 0ul;
|
||||
auto context = std::string();
|
||||
while (m_replayFile->getChar(&c)) { // TODO: error handling
|
||||
switch (state) {
|
||||
case Marker0:
|
||||
case Marker1:
|
||||
case Marker2:
|
||||
switch (c) {
|
||||
case '-':
|
||||
state = static_cast<State>(state + 1);
|
||||
break;
|
||||
default:
|
||||
state = Marker0;
|
||||
}
|
||||
break;
|
||||
case Marker3:
|
||||
switch (c) {
|
||||
case ' ':
|
||||
state = TimeStamp;
|
||||
timeStampChars.clear();
|
||||
break;
|
||||
default:
|
||||
state = Marker0;
|
||||
}
|
||||
break;
|
||||
case TimeStamp:
|
||||
switch (c) {
|
||||
case ' ':
|
||||
try {
|
||||
timeStamp = CppUtilities::DateTime::fromIsoStringGmt(timeStampChars.data());
|
||||
state = Size;
|
||||
sizeFactor = 0ul, size = 0ul;
|
||||
} catch (const CppUtilities::ConversionException &c) {
|
||||
state = Marker0; // TODO: error handling
|
||||
}
|
||||
break;
|
||||
default:
|
||||
timeStampChars += c;
|
||||
}
|
||||
break;
|
||||
case Size:
|
||||
if (c >= '0' && c <= '9') {
|
||||
size += static_cast<decltype(size)>(c) * sizeFactor;
|
||||
sizeFactor *= 10ul;
|
||||
} else if (c == ' ') {
|
||||
state = Context;
|
||||
context.clear();
|
||||
} else {
|
||||
state = Marker0; // TODO: error handling
|
||||
}
|
||||
break;
|
||||
case Context:
|
||||
switch (c) {
|
||||
case '\n':
|
||||
state = Marker0;
|
||||
m_replayFile->read(size);
|
||||
// TODO: error handling
|
||||
// TODO: add to some kind of queue for replays (by context)
|
||||
// TODO: return if queue has enough items
|
||||
break;
|
||||
default:
|
||||
context += c;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Data
|
Loading…
Reference in New Issue