If there's a configured and local Syncthing connection and we're on a non-UNIX platform which doesn't support SIGTERM (basically Windows) it makes sense to use the REST-API instead. That's likely better than just terminating the process forcefully. This doesn't cover the stop button within the launcher settings yet because from this context is isn't clear which connection is relevant as there can be multiple tray icons/widgets but only one settings page.
269 lines
8.2 KiB
C++
269 lines
8.2 KiB
C++
#include "./syncthinglauncher.h"
|
|
|
|
#include "../settings/settings.h"
|
|
|
|
#include <QtConcurrentRun>
|
|
|
|
#include <algorithm>
|
|
#include <functional>
|
|
#include <limits>
|
|
|
|
using namespace std;
|
|
using namespace std::placeholders;
|
|
using namespace CppUtilities;
|
|
|
|
namespace Data {
|
|
|
|
SyncthingLauncher *SyncthingLauncher::s_mainInstance = nullptr;
|
|
|
|
/*!
|
|
* \class SyncthingLauncher
|
|
* \brief The SyncthingLauncher class starts a Syncthing instance either as an external process or using a library version of Syncthing.
|
|
* \remarks
|
|
* - This is *not* strictly a singleton class. However, one instance is supposed to be the "main instance" (see SyncthingLauncher::setMainInstance()).
|
|
* - A SyncthingLauncher instance can only launch one Syncthing instance at a time.
|
|
* - Using Syncthing as library is still under development and must be explicitely enabled by setting the CMake variable USE_LIBSYNCTHING.
|
|
*/
|
|
|
|
/*!
|
|
* \brief Constructs a new Syncthing launcher.
|
|
*/
|
|
SyncthingLauncher::SyncthingLauncher(QObject *parent)
|
|
: QObject(parent)
|
|
#ifdef SYNCTHINGWIDGETS_USE_LIBSYNCTHING
|
|
, m_libsyncthingLogLevel(LibSyncthing::LogLevel::Info)
|
|
#endif
|
|
, m_manuallyStopped(true)
|
|
, m_emittingOutput(false)
|
|
{
|
|
connect(&m_process, &SyncthingProcess::readyRead, this, &SyncthingLauncher::handleProcessReadyRead, Qt::QueuedConnection);
|
|
connect(&m_process, static_cast<void (SyncthingProcess::*)(int exitCode, QProcess::ExitStatus exitStatus)>(&SyncthingProcess::finished), this,
|
|
&SyncthingLauncher::handleProcessFinished, Qt::QueuedConnection);
|
|
connect(&m_process, &SyncthingProcess::stateChanged, this, &SyncthingLauncher::handleProcessStateChanged, Qt::QueuedConnection);
|
|
connect(&m_process, &SyncthingProcess::errorOccurred, this, &SyncthingLauncher::errorOccurred, Qt::QueuedConnection);
|
|
connect(&m_process, &SyncthingProcess::confirmKill, this, &SyncthingLauncher::confirmKill);
|
|
}
|
|
|
|
/*!
|
|
* \brief Sets whether the output/log should be emitted via outputAvailable() signal.
|
|
*/
|
|
void SyncthingLauncher::setEmittingOutput(bool emittingOutput)
|
|
{
|
|
if (m_emittingOutput == emittingOutput || !(m_emittingOutput = emittingOutput) || m_outputBuffer.isEmpty()) {
|
|
return;
|
|
}
|
|
QByteArray data;
|
|
m_outputBuffer.swap(data);
|
|
emit outputAvailable(move(data));
|
|
}
|
|
|
|
/*!
|
|
* \brief Returns whether the built-in Syncthing library is available.
|
|
*/
|
|
bool SyncthingLauncher::isLibSyncthingAvailable()
|
|
{
|
|
#ifdef SYNCTHINGWIDGETS_USE_LIBSYNCTHING
|
|
return true;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
/*!
|
|
* \brief Returns the Syncthing version provided by libsyncthing or "Not built with libsyncthing support." if not built with libsyncthing support.
|
|
*/
|
|
QString SyncthingLauncher::libSyncthingVersionInfo()
|
|
{
|
|
#ifdef SYNCTHINGWIDGETS_USE_LIBSYNCTHING
|
|
return QString::fromStdString(LibSyncthing::longSyncthingVersion());
|
|
#else
|
|
return tr("Not built with libsyncthing support.");
|
|
#endif
|
|
}
|
|
|
|
/*!
|
|
* \brief Launches a Syncthing instance using the specified \a arguments.
|
|
*
|
|
* To use the internal library, leave \a program empty. In this case \a arguments are ignored.
|
|
* Otherwise \a program must be the path the external Syncthing executable.
|
|
*
|
|
* \remarks Does nothing if already running an instance.
|
|
*/
|
|
void SyncthingLauncher::launch(const QString &program, const QStringList &arguments)
|
|
{
|
|
if (isRunning() || m_stopFuture.isRunning()) {
|
|
return;
|
|
}
|
|
m_manuallyStopped = false;
|
|
|
|
// start external process
|
|
if (!program.isEmpty()) {
|
|
m_process.startSyncthing(program, arguments);
|
|
return;
|
|
}
|
|
|
|
#ifdef SYNCTHINGWIDGETS_USE_LIBSYNCTHING
|
|
// use libsyncthing
|
|
m_startFuture = QtConcurrent::run(std::bind(&SyncthingLauncher::runLibSyncthing, this, LibSyncthing::RuntimeOptions{}));
|
|
#else
|
|
showLibSyncthingNotSupported();
|
|
#endif
|
|
}
|
|
|
|
/*!
|
|
* \brief Launches a Syncthing instance according to the specified \a launcherSettings.
|
|
* \remarks Does nothing if already running an instance.
|
|
*/
|
|
void SyncthingLauncher::launch(const Settings::Launcher &launcherSettings)
|
|
{
|
|
if (isRunning()) {
|
|
return;
|
|
}
|
|
if (!launcherSettings.useLibSyncthing && launcherSettings.syncthingPath.isEmpty()) {
|
|
emit errorOccurred(QProcess::FailedToStart);
|
|
return;
|
|
}
|
|
if (launcherSettings.useLibSyncthing) {
|
|
#ifdef SYNCTHINGWIDGETS_USE_LIBSYNCTHING
|
|
LibSyncthing::RuntimeOptions options;
|
|
options.configDir = launcherSettings.libSyncthing.configDir.toStdString();
|
|
options.dataDir = launcherSettings.libSyncthing.configDir.toStdString();
|
|
setLibSyncthingLogLevel(launcherSettings.libSyncthing.logLevel);
|
|
launch(options);
|
|
#else
|
|
showLibSyncthingNotSupported();
|
|
#endif
|
|
} else {
|
|
launch(launcherSettings.syncthingPath, SyncthingProcess::splitArguments(launcherSettings.syncthingArgs));
|
|
}
|
|
}
|
|
|
|
#ifdef SYNCTHINGWIDGETS_USE_LIBSYNCTHING
|
|
/*!
|
|
* \brief Launches a Syncthing instance using the internal library with the specified \a runtimeOptions.
|
|
* \remarks Does nothing if already running an instance.
|
|
*/
|
|
void SyncthingLauncher::launch(const LibSyncthing::RuntimeOptions &runtimeOptions)
|
|
{
|
|
if (isRunning() || m_stopFuture.isRunning()) {
|
|
return;
|
|
}
|
|
m_manuallyStopped = false;
|
|
m_startFuture = QtConcurrent::run(std::bind(&SyncthingLauncher::runLibSyncthing, this, runtimeOptions));
|
|
}
|
|
#endif
|
|
|
|
void SyncthingLauncher::terminate(SyncthingConnection *relevantConnection)
|
|
{
|
|
if (m_process.isRunning()) {
|
|
m_manuallyStopped = true;
|
|
m_process.stopSyncthing(relevantConnection);
|
|
} else {
|
|
tearDownLibSyncthing();
|
|
}
|
|
}
|
|
|
|
void SyncthingLauncher::kill()
|
|
{
|
|
if (m_process.isRunning()) {
|
|
m_manuallyStopped = true;
|
|
m_process.killSyncthing();
|
|
} else {
|
|
tearDownLibSyncthing();
|
|
}
|
|
}
|
|
|
|
void SyncthingLauncher::tearDownLibSyncthing()
|
|
{
|
|
#ifdef SYNCTHINGWIDGETS_USE_LIBSYNCTHING
|
|
if (!m_startFuture.isRunning() || m_stopFuture.isRunning()) {
|
|
return;
|
|
}
|
|
m_manuallyStopped = true;
|
|
m_stopFuture = QtConcurrent::run(std::bind(&SyncthingLauncher::stopLibSyncthing, this));
|
|
#endif
|
|
}
|
|
|
|
void SyncthingLauncher::handleProcessReadyRead()
|
|
{
|
|
handleOutputAvailable(m_process.readAll());
|
|
}
|
|
|
|
void SyncthingLauncher::handleProcessStateChanged(QProcess::ProcessState newState)
|
|
{
|
|
switch (newState) {
|
|
case QProcess::NotRunning:
|
|
emit runningChanged(false);
|
|
break;
|
|
case QProcess::Starting:
|
|
emit runningChanged(true);
|
|
break;
|
|
default:;
|
|
}
|
|
}
|
|
|
|
void SyncthingLauncher::handleProcessFinished(int exitCode, QProcess::ExitStatus exitStatus)
|
|
{
|
|
emit exited(exitCode, exitStatus);
|
|
}
|
|
|
|
#ifdef SYNCTHINGWIDGETS_USE_LIBSYNCTHING
|
|
static const char *const logLevelStrings[] = {
|
|
"[DEBUG] ",
|
|
"[VERBOSE] ",
|
|
"[INFO] ",
|
|
"[WARNING] ",
|
|
"[FATAL] ",
|
|
};
|
|
|
|
void SyncthingLauncher::handleLoggingCallback(LibSyncthing::LogLevel level, const char *message, size_t messageSize)
|
|
{
|
|
if (level < m_libsyncthingLogLevel) {
|
|
return;
|
|
}
|
|
QByteArray messageData;
|
|
messageSize = min<size_t>(numeric_limits<int>::max() - 20, messageSize);
|
|
messageData.reserve(static_cast<int>(messageSize) + 20);
|
|
messageData.append(logLevelStrings[static_cast<int>(level)]);
|
|
messageData.append(message, static_cast<int>(messageSize));
|
|
messageData.append('\n');
|
|
|
|
handleOutputAvailable(move(messageData));
|
|
}
|
|
#endif
|
|
|
|
void SyncthingLauncher::handleOutputAvailable(QByteArray &&data)
|
|
{
|
|
if (isEmittingOutput()) {
|
|
emit outputAvailable(data);
|
|
} else {
|
|
m_outputBuffer += data;
|
|
}
|
|
}
|
|
|
|
#ifdef SYNCTHINGWIDGETS_USE_LIBSYNCTHING
|
|
void SyncthingLauncher::runLibSyncthing(const LibSyncthing::RuntimeOptions &runtimeOptions)
|
|
{
|
|
LibSyncthing::setLoggingCallback(bind(&SyncthingLauncher::handleLoggingCallback, this, _1, _2, _3));
|
|
emit runningChanged(true);
|
|
const auto exitCode = LibSyncthing::runSyncthing(runtimeOptions);
|
|
emit exited(static_cast<int>(exitCode), exitCode == 0 ? QProcess::NormalExit : QProcess::CrashExit);
|
|
emit runningChanged(false);
|
|
}
|
|
|
|
void SyncthingLauncher::stopLibSyncthing()
|
|
{
|
|
LibSyncthing::stopSyncthing();
|
|
// no need to emit exited/runningChanged here; that is already done in runLibSyncthing()
|
|
}
|
|
|
|
#else
|
|
void SyncthingLauncher::showLibSyncthingNotSupported()
|
|
{
|
|
handleOutputAvailable(QByteArray("libsyncthing support not enabled"));
|
|
emit exited(-1, QProcess::CrashExit);
|
|
}
|
|
#endif
|
|
|
|
} // namespace Data
|