Use Boost.Process for launcher to cope with further sub processes
* Use a process group / job object via Boost.Process to be able to terminate sub processes as well * Do not try to stop the process gracefully under Windows by posting WM_CLOSE because this has no effect on Syncthing anyways * See https://github.com/Martchus/syncthingtray/issues/94
This commit is contained in:
parent
7f55afc51c
commit
9ce9b11ba4
|
@ -48,6 +48,34 @@ use_cpp_utilities(VISIBILITY PUBLIC)
|
||||||
find_package(qtutilities${CONFIGURATION_PACKAGE_SUFFIX_QTUTILITIES} 6.3.0 REQUIRED)
|
find_package(qtutilities${CONFIGURATION_PACKAGE_SUFFIX_QTUTILITIES} 6.3.0 REQUIRED)
|
||||||
use_qt_utilities(ONLY_HEADERS VISIBILITY PUBLIC)
|
use_qt_utilities(ONLY_HEADERS VISIBILITY PUBLIC)
|
||||||
|
|
||||||
|
# find boost libraries
|
||||||
|
option(USE_BOOST_PROCESS "enables Boost.Process for improved behavior of the launcher" ON)
|
||||||
|
if (USE_BOOST_PROCESS)
|
||||||
|
option(BOOST_STATIC_LINKAGE "${STATIC_LINKAGE}" "link statically against Boost (instead of dynamically)")
|
||||||
|
set(Boost_USE_MULTITHREADED ON)
|
||||||
|
if (BOOST_STATIC_LINKAGE)
|
||||||
|
set(Boost_USE_STATIC_LIBS ON)
|
||||||
|
endif ()
|
||||||
|
# add Boost::boost target which represents include directory for header-only deps add Boost::filesystem when building for
|
||||||
|
# Windows as it is needed by Boost.Process there
|
||||||
|
set(BOOST_ARGS REQUIRED)
|
||||||
|
if (WIN32)
|
||||||
|
list(APPEND BOOST_ARGS COMPONENTS filesystem)
|
||||||
|
endif ()
|
||||||
|
use_package(TARGET_NAME Boost::boost PACKAGE_NAME Boost PACKAGE_ARGS "${BOOST_ARGS}")
|
||||||
|
if (WIN32)
|
||||||
|
use_package(TARGET_NAME Boost::filesystem PACKAGE_NAME Boost PACKAGE_ARGS "${BOOST_ARGS}")
|
||||||
|
endif ()
|
||||||
|
list(APPEND META_PUBLIC_COMPILE_DEFINITIONS ${META_PROJECT_VARNAME_UPPER}_BOOST_PROCESS)
|
||||||
|
if (MINGW)
|
||||||
|
# workaround https://github.com/boostorg/process/issues/96
|
||||||
|
set_property(
|
||||||
|
SOURCE syncthingprocess.cpp
|
||||||
|
APPEND
|
||||||
|
PROPERTY COMPILE_DEFINITIONS BOOST_USE_WINDOWS_H WIN32_LEAN_AND_MEAN)
|
||||||
|
endif ()
|
||||||
|
endif ()
|
||||||
|
|
||||||
# link also explicitely against the following Qt modules
|
# link also explicitely against the following Qt modules
|
||||||
list(APPEND ADDITIONAL_QT_MODULES Network)
|
list(APPEND ADDITIONAL_QT_MODULES Network)
|
||||||
set(META_PUBLIC_QT_MODULES Core ${ADDITIONAL_QT_MODULES})
|
set(META_PUBLIC_QT_MODULES Core ${ADDITIONAL_QT_MODULES})
|
||||||
|
|
|
@ -42,7 +42,7 @@ SyncthingNotifier::SyncthingNotifier(const SyncthingConnection &connection, QObj
|
||||||
connect(&connection, &SyncthingConnection::newDevAvailable, this, &SyncthingNotifier::handleNewDevEvent);
|
connect(&connection, &SyncthingConnection::newDevAvailable, this, &SyncthingNotifier::handleNewDevEvent);
|
||||||
connect(&connection, &SyncthingConnection::newDirAvailable, this, &SyncthingNotifier::handleNewDirEvent);
|
connect(&connection, &SyncthingConnection::newDirAvailable, this, &SyncthingNotifier::handleNewDirEvent);
|
||||||
if (m_process) {
|
if (m_process) {
|
||||||
connect(m_process, &QProcess::errorOccurred, this, &SyncthingNotifier::handleSyncthingProcessError);
|
connect(m_process, &SyncthingProcess::errorOccurred, this, &SyncthingNotifier::handleSyncthingProcessError);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,16 +101,17 @@ void SyncthingNotifier::handleSyncthingProcessError(QProcess::ProcessError proce
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const auto error = m_process->errorString();
|
||||||
switch (processError) {
|
switch (processError) {
|
||||||
case QProcess::FailedToStart:
|
case QProcess::FailedToStart:
|
||||||
emit syncthingProcessError(
|
emit syncthingProcessError(tr("Failed to start Syncthing"),
|
||||||
tr("Failed to start Syncthing"), tr("Maybe the configured binary path is wrong or the binary is not marked as executable."));
|
error.isEmpty() ? tr("Maybe the configured binary path is wrong or the binary is not marked as executable.") : error);
|
||||||
break;
|
break;
|
||||||
case QProcess::Crashed:
|
case QProcess::Crashed:
|
||||||
emit syncthingProcessError(tr("Syncthing crashed with exit code %1").arg(m_process->exitCode()), QString());
|
emit syncthingProcessError(tr("Syncthing crashed"), error);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
emit syncthingProcessError(tr("Syncthing launcher error occurred"), m_process->errorString());
|
emit syncthingProcessError(tr("Syncthing launcher error occurred"), error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,10 +2,69 @@
|
||||||
|
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
|
|
||||||
|
#ifdef LIB_SYNCTHING_CONNECTOR_BOOST_PROCESS
|
||||||
|
#include <c++utilities/io/ansiescapecodes.h>
|
||||||
|
|
||||||
|
#include <boost/asio/executor_work_guard.hpp>
|
||||||
|
#include <boost/asio/io_context.hpp>
|
||||||
|
#include <boost/process/async.hpp>
|
||||||
|
#include <boost/process/async_pipe.hpp>
|
||||||
|
#include <boost/process/child.hpp>
|
||||||
|
#include <boost/process/extend.hpp>
|
||||||
|
#include <boost/process/group.hpp>
|
||||||
|
#include <boost/process/io.hpp>
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <chrono>
|
||||||
|
#include <csignal>
|
||||||
|
#include <iostream>
|
||||||
|
#include <limits>
|
||||||
|
#include <mutex>
|
||||||
|
#include <system_error>
|
||||||
|
#include <thread>
|
||||||
|
#endif
|
||||||
|
|
||||||
using namespace CppUtilities;
|
using namespace CppUtilities;
|
||||||
|
|
||||||
namespace Data {
|
namespace Data {
|
||||||
|
|
||||||
|
/// \cond
|
||||||
|
#ifdef LIB_SYNCTHING_CONNECTOR_BOOST_PROCESS
|
||||||
|
struct SyncthingProcessInternalData : std::enable_shared_from_this<SyncthingProcessInternalData> {
|
||||||
|
static constexpr std::size_t bufferCapacity = 0x1000;
|
||||||
|
static_assert(SyncthingProcessInternalData::bufferCapacity <= std::numeric_limits<qint64>::max());
|
||||||
|
|
||||||
|
explicit SyncthingProcessInternalData(boost::asio::io_context &ioc);
|
||||||
|
struct Lock {
|
||||||
|
explicit Lock(const std::weak_ptr<SyncthingProcessInternalData> &weak);
|
||||||
|
operator bool() const;
|
||||||
|
std::shared_ptr<SyncthingProcessInternalData> process;
|
||||||
|
std::unique_lock<std::mutex> lock;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::mutex mutex;
|
||||||
|
QString program;
|
||||||
|
QStringList arguments;
|
||||||
|
boost::process::group group;
|
||||||
|
boost::process::child child;
|
||||||
|
boost::process::async_pipe pipe;
|
||||||
|
char buffer[bufferCapacity];
|
||||||
|
std::atomic_size_t bytesBuffered = 0;
|
||||||
|
std::size_t bytesRead = 0;
|
||||||
|
QProcess::ProcessState state = QProcess::NotRunning;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SyncthingProcessIOHandler {
|
||||||
|
explicit SyncthingProcessIOHandler();
|
||||||
|
~SyncthingProcessIOHandler();
|
||||||
|
|
||||||
|
boost::asio::io_context ioc;
|
||||||
|
boost::asio::executor_work_guard<boost::asio::io_context::executor_type> guard;
|
||||||
|
std::thread t;
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
/// \endcond
|
||||||
|
|
||||||
SyncthingProcess *SyncthingProcess::s_mainInstance = nullptr;
|
SyncthingProcess *SyncthingProcess::s_mainInstance = nullptr;
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
@ -20,18 +79,35 @@ SyncthingProcess *SyncthingProcess::s_mainInstance = nullptr;
|
||||||
* \brief Constructs a new Syncthing process.
|
* \brief Constructs a new Syncthing process.
|
||||||
*/
|
*/
|
||||||
SyncthingProcess::SyncthingProcess(QObject *parent)
|
SyncthingProcess::SyncthingProcess(QObject *parent)
|
||||||
: QProcess(parent)
|
: SyncthingProcessBase(parent)
|
||||||
, m_manuallyStopped(true)
|
, m_manuallyStopped(true)
|
||||||
{
|
{
|
||||||
m_killTimer.setInterval(3000);
|
m_killTimer.setInterval(3000);
|
||||||
m_killTimer.setSingleShot(true);
|
m_killTimer.setSingleShot(true);
|
||||||
|
#ifndef LIB_SYNCTHING_CONNECTOR_BOOST_PROCESS
|
||||||
setProcessChannelMode(QProcess::MergedChannels);
|
setProcessChannelMode(QProcess::MergedChannels);
|
||||||
|
#endif
|
||||||
connect(this, &SyncthingProcess::started, this, &SyncthingProcess::handleStarted);
|
connect(this, &SyncthingProcess::started, this, &SyncthingProcess::handleStarted);
|
||||||
connect(this, static_cast<void (SyncthingProcess::*)(int exitCode, QProcess::ExitStatus exitStatus)>(&SyncthingProcess::finished), this,
|
connect(this, static_cast<void (SyncthingProcess::*)(int exitCode, QProcess::ExitStatus exitStatus)>(&SyncthingProcess::finished), this,
|
||||||
&SyncthingProcess::handleFinished);
|
&SyncthingProcess::handleFinished);
|
||||||
connect(&m_killTimer, &QTimer::timeout, this, &SyncthingProcess::confirmKill);
|
connect(&m_killTimer, &QTimer::timeout, this, &SyncthingProcess::confirmKill);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Destroys the process.
|
||||||
|
*/
|
||||||
|
SyncthingProcess::~SyncthingProcess()
|
||||||
|
{
|
||||||
|
#ifdef LIB_SYNCTHING_CONNECTOR_BOOST_PROCESS
|
||||||
|
// block until all callbacks have been processed
|
||||||
|
if (!m_process) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto lock = std::lock_guard<std::mutex>(m_process->mutex);
|
||||||
|
m_process.reset();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* \brief Splits the given arguments similar to how a shell would split it. So whitespaces are considered seperators unless quotes are used.
|
* \brief Splits the given arguments similar to how a shell would split it. So whitespaces are considered seperators unless quotes are used.
|
||||||
*/
|
*/
|
||||||
|
@ -129,7 +205,7 @@ void SyncthingProcess::startSyncthing(const QString &program, const QStringList
|
||||||
}
|
}
|
||||||
m_manuallyStopped = false;
|
m_manuallyStopped = false;
|
||||||
m_killTimer.stop();
|
m_killTimer.stop();
|
||||||
start(program, arguments, QProcess::ReadOnly);
|
start(program, arguments, QIODevice::ReadOnly);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
@ -137,9 +213,6 @@ void SyncthingProcess::startSyncthing(const QString &program, const QStringList
|
||||||
*/
|
*/
|
||||||
void SyncthingProcess::stopSyncthing()
|
void SyncthingProcess::stopSyncthing()
|
||||||
{
|
{
|
||||||
if (!isRunning()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
m_manuallyStopped = true;
|
m_manuallyStopped = true;
|
||||||
m_killTimer.start();
|
m_killTimer.start();
|
||||||
terminate();
|
terminate();
|
||||||
|
@ -150,9 +223,6 @@ void SyncthingProcess::stopSyncthing()
|
||||||
*/
|
*/
|
||||||
void SyncthingProcess::killSyncthing()
|
void SyncthingProcess::killSyncthing()
|
||||||
{
|
{
|
||||||
if (!isRunning()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
m_manuallyStopped = true;
|
m_manuallyStopped = true;
|
||||||
m_killTimer.stop();
|
m_killTimer.stop();
|
||||||
kill();
|
kill();
|
||||||
|
@ -183,4 +253,342 @@ void SyncthingProcess::killToRestart()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// \cond
|
||||||
|
/// \remarks The functions below are for using Boost.Process (instead of QProcess) to be able to
|
||||||
|
/// terminate the process better by using a group.
|
||||||
|
#ifdef LIB_SYNCTHING_CONNECTOR_BOOST_PROCESS
|
||||||
|
SyncthingProcessIOHandler::SyncthingProcessIOHandler()
|
||||||
|
: ioc()
|
||||||
|
, guard(boost::asio::make_work_guard(ioc))
|
||||||
|
, t([this] { ioc.run(); })
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
SyncthingProcessIOHandler::~SyncthingProcessIOHandler()
|
||||||
|
{
|
||||||
|
ioc.stop();
|
||||||
|
guard.reset();
|
||||||
|
t.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
Data::SyncthingProcessInternalData::SyncthingProcessInternalData(boost::asio::io_context &ioc)
|
||||||
|
: pipe(ioc)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Data::SyncthingProcessInternalData::Lock::Lock(const std::weak_ptr<SyncthingProcessInternalData> &weak)
|
||||||
|
: process(weak.lock())
|
||||||
|
, lock(process ? decltype(lock)(process->mutex, std::try_to_lock) : decltype(lock)())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Data::SyncthingProcessInternalData::Lock::operator bool() const
|
||||||
|
{
|
||||||
|
return process && lock;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SyncthingProcess::handleError(QProcess::ProcessError error, const QString &errorMessage, bool closed)
|
||||||
|
{
|
||||||
|
setErrorString(errorMessage);
|
||||||
|
errorOccurred(error);
|
||||||
|
if (closed) {
|
||||||
|
setOpenMode(QIODevice::NotOpen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QProcess::ProcessState SyncthingProcess::state() const
|
||||||
|
{
|
||||||
|
return !m_process ? QProcess::NotRunning : m_process->state;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SyncthingProcess::start(const QString &program, const QStringList &arguments, QIODevice::OpenMode openMode)
|
||||||
|
{
|
||||||
|
// ensure the dev is only opened in read-only mode because writing is not implemented here
|
||||||
|
if (openMode != QIODevice::ReadOnly) {
|
||||||
|
setErrorString(QStringLiteral("only read-only openmode supported"));
|
||||||
|
std::cerr << EscapeCodes::Phrases::Error << "Unable to launch process: only read-only openmode supported" << EscapeCodes::Phrases::End;
|
||||||
|
emit errorOccurred(QProcess::FailedToStart);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle current/previous process
|
||||||
|
if (m_process) {
|
||||||
|
const auto lock = std::lock_guard<std::mutex>(m_process->mutex);
|
||||||
|
if (m_process->state != QProcess::NotRunning) {
|
||||||
|
setErrorString(QStringLiteral("process is still running"));
|
||||||
|
std::cerr << EscapeCodes::Phrases::Error << "Unable to launch process: previous process still running" << EscapeCodes::Phrases::End;
|
||||||
|
emit errorOccurred(QProcess::FailedToStart);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_process.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
// setup new handler/process
|
||||||
|
if (!m_handler) {
|
||||||
|
m_handler = std::make_unique<SyncthingProcessIOHandler>();
|
||||||
|
}
|
||||||
|
m_process = std::make_shared<SyncthingProcessInternalData>(m_handler->ioc);
|
||||||
|
emit stateChanged(m_process->state = QProcess::Starting);
|
||||||
|
|
||||||
|
// convert args
|
||||||
|
auto args = std::vector<std::string>();
|
||||||
|
args.reserve(1 + static_cast<std::size_t>(arguments.size()));
|
||||||
|
args.emplace_back(program.toStdString());
|
||||||
|
for (const auto &arg : arguments) {
|
||||||
|
args.emplace_back(arg.toStdString());
|
||||||
|
}
|
||||||
|
m_process->program = program;
|
||||||
|
m_process->arguments = arguments;
|
||||||
|
|
||||||
|
// start the process within a new process group (or job object under Windows)
|
||||||
|
try {
|
||||||
|
m_process->child = boost::process::child(
|
||||||
|
m_handler->ioc, m_process->group, args, (boost::process::std_out & boost::process::std_err) > m_process->pipe,
|
||||||
|
boost::process::extend::on_success =
|
||||||
|
[this, maybeProcess = m_process->weak_from_this()](auto &executor) {
|
||||||
|
std::cerr << EscapeCodes::Phrases::Info << "Launched process, PID: "
|
||||||
|
<< executor
|
||||||
|
#ifdef PLATFORM_WINDOWS
|
||||||
|
.proc_info.dwProcessId
|
||||||
|
#else
|
||||||
|
.pid
|
||||||
|
#endif
|
||||||
|
<< EscapeCodes::Phrases::End;
|
||||||
|
if (const auto lock = SyncthingProcessInternalData::Lock(maybeProcess)) {
|
||||||
|
emit stateChanged(m_process->state = QProcess::Running);
|
||||||
|
emit started();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
boost::process::on_exit =
|
||||||
|
[this, maybeProcess = m_process->weak_from_this()](int rc, const std::error_code &ec) {
|
||||||
|
const auto lock = SyncthingProcessInternalData::Lock(maybeProcess);
|
||||||
|
if (!lock) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
handleLeftoverProcesses();
|
||||||
|
emit stateChanged(m_process->state = QProcess::NotRunning);
|
||||||
|
emit finished(rc, rc != 0 ? QProcess::CrashExit : QProcess::NormalExit);
|
||||||
|
if (ec) {
|
||||||
|
const auto msg = ec.message();
|
||||||
|
std::cerr << EscapeCodes::Phrases::Error << "Launched process " << m_process->child.native_handle()
|
||||||
|
<< " exited with error: " << msg << EscapeCodes::Phrases::End;
|
||||||
|
QMetaObject::invokeMethod(this, "handleError", Qt::QueuedConnection, Q_ARG(QProcess::ProcessError, QProcess::Crashed),
|
||||||
|
Q_ARG(QString, QString::fromStdString(msg)), Q_ARG(bool, false));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
boost::process::extend::on_error =
|
||||||
|
[this, maybeProcess = m_process->weak_from_this()](auto &, const std::error_code &ec) {
|
||||||
|
const auto lock = SyncthingProcessInternalData::Lock(maybeProcess);
|
||||||
|
if (!lock) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
handleLeftoverProcesses();
|
||||||
|
const auto started = m_process->state == QProcess::Running;
|
||||||
|
if (m_process->state != QProcess::NotRunning) {
|
||||||
|
emit stateChanged(m_process->state = QProcess::NotRunning);
|
||||||
|
}
|
||||||
|
if (started) {
|
||||||
|
emit finished(0, QProcess::CrashExit);
|
||||||
|
}
|
||||||
|
const auto error = ec == std::errc::timed_out || ec == std::errc::stream_timeout ? QProcess::Timedout : QProcess::Crashed;
|
||||||
|
const auto msg = ec.message();
|
||||||
|
std::cerr << EscapeCodes::Phrases::Error << "Unable to launch process: " << msg << EscapeCodes::Phrases::End;
|
||||||
|
QMetaObject::invokeMethod(this, "handleError", Qt::QueuedConnection, Q_ARG(QProcess::ProcessError, error),
|
||||||
|
Q_ARG(QString, QString::fromStdString(msg)), Q_ARG(bool, false));
|
||||||
|
});
|
||||||
|
} catch (const boost::process::process_error &e) {
|
||||||
|
std::cerr << EscapeCodes::Phrases::Error << "Unable to launch process: " << e.what() << EscapeCodes::Phrases::End;
|
||||||
|
emit stateChanged(m_process->state = QProcess::NotRunning);
|
||||||
|
handleError(QProcess::FailedToStart, QString::fromUtf8(e.what()), false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// start reading the process' output
|
||||||
|
open(QIODevice::ReadOnly);
|
||||||
|
bufferOutput();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SyncthingProcess::terminate()
|
||||||
|
{
|
||||||
|
if (!m_process) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#ifdef PLATFORM_UNIX
|
||||||
|
auto lock = std::unique_lock<std::mutex>(m_process->mutex);
|
||||||
|
if (!m_process->group.valid()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto groupId = m_process->group.native_handle();
|
||||||
|
lock.unlock();
|
||||||
|
if (::killpg(groupId, SIGTERM) == -1) {
|
||||||
|
if (const auto ec = boost::process::detail::get_last_error(); ec != std::errc::no_such_process) {
|
||||||
|
const auto msg = ec.message();
|
||||||
|
std::cerr << EscapeCodes::Phrases::Error << "Unable to kill process group " << groupId << ": " << msg << EscapeCodes::Phrases::End;
|
||||||
|
setErrorString(QString::fromStdString(msg));
|
||||||
|
errorOccurred(QProcess::UnknownError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
// there seems no way to stop the process group gracefully under Windows so just kill it
|
||||||
|
// note: Posting a WM_CLOSE message like QProcess would attempt doesn't work for Syncthing.
|
||||||
|
kill();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void SyncthingProcess::kill()
|
||||||
|
{
|
||||||
|
if (!m_process) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto ec = std::error_code();
|
||||||
|
auto lock = std::unique_lock<std::mutex>(m_process->mutex);
|
||||||
|
if (!m_process->group.valid()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto groupId = m_process->group.native_handle();
|
||||||
|
m_process->group.terminate(ec);
|
||||||
|
lock.unlock();
|
||||||
|
if (ec && ec != std::errc::no_such_process) {
|
||||||
|
const auto msg = ec.message();
|
||||||
|
std::cerr << EscapeCodes::Phrases::Error << "Unable to kill process group " << groupId << ": " << msg << EscapeCodes::Phrases::End;
|
||||||
|
setErrorString(QString::fromStdString(msg));
|
||||||
|
errorOccurred(QProcess::UnknownError);
|
||||||
|
}
|
||||||
|
// note: No need to emit finished() signal here, the on_exit handler will fire
|
||||||
|
// also in case of a forceful termination.
|
||||||
|
}
|
||||||
|
|
||||||
|
void SyncthingProcess::bufferOutput()
|
||||||
|
{
|
||||||
|
m_process->pipe.async_read_some(boost::asio::buffer(m_process->buffer, m_process->bufferCapacity),
|
||||||
|
[this, maybeProcess = m_process->weak_from_this()](const boost::system::error_code &ec, auto bytesRead) {
|
||||||
|
const auto lock = SyncthingProcessInternalData::Lock(maybeProcess);
|
||||||
|
if (!lock) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_process->bytesBuffered = bytesRead;
|
||||||
|
if (ec == boost::asio::error::eof
|
||||||
|
#ifdef PLATFORM_WINDOWS // looks like we're getting broken pipe (and not just eof) under Windows when stopping the process
|
||||||
|
|| ec == boost::asio::error::broken_pipe
|
||||||
|
#endif
|
||||||
|
) {
|
||||||
|
m_process->pipe.async_close();
|
||||||
|
setOpenMode(QIODevice::NotOpen);
|
||||||
|
} else if (ec) {
|
||||||
|
const auto msg = ec.message();
|
||||||
|
std::cerr << EscapeCodes::Phrases::Error << "Unable to read output of process " << m_process->child.native_handle() << ": " << msg
|
||||||
|
<< EscapeCodes::Phrases::End;
|
||||||
|
QMetaObject::invokeMethod(this, "handleError", Qt::QueuedConnection, Q_ARG(QProcess::ProcessError, QProcess::ReadError),
|
||||||
|
Q_ARG(QString, QString::fromStdString(msg)), Q_ARG(bool, true));
|
||||||
|
}
|
||||||
|
if (!ec || bytesRead) {
|
||||||
|
emit readyRead();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void SyncthingProcess::handleLeftoverProcesses()
|
||||||
|
{
|
||||||
|
if (!m_process->group.valid()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto ec = std::error_code();
|
||||||
|
m_process->group.terminate(ec);
|
||||||
|
if (ec && ec != std::errc::no_such_process) {
|
||||||
|
std::cerr << EscapeCodes::Phrases::Error << "Unable to kill leftover processes in group " << m_process->group.native_handle() << ": "
|
||||||
|
<< ec.message() << EscapeCodes::Phrases::End;
|
||||||
|
}
|
||||||
|
m_process->group.wait(ec);
|
||||||
|
if (ec && ec != std::errc::no_such_process) {
|
||||||
|
std::cerr << EscapeCodes::Phrases::Error << "Unable to wait for leftover processes in group " << m_process->group.native_handle() << ": "
|
||||||
|
<< ec.message() << EscapeCodes::Phrases::End;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
qint64 SyncthingProcess::bytesAvailable() const
|
||||||
|
{
|
||||||
|
return (m_process ? static_cast<qint64>(m_process->bytesBuffered) : 0) + QIODevice::bytesAvailable();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SyncthingProcess::close()
|
||||||
|
{
|
||||||
|
emit aboutToClose();
|
||||||
|
if (m_process) {
|
||||||
|
const auto lock = std::lock_guard<std::mutex>(m_process->mutex);
|
||||||
|
m_process->pipe.async_close();
|
||||||
|
kill();
|
||||||
|
}
|
||||||
|
setOpenMode(QIODevice::NotOpen);
|
||||||
|
}
|
||||||
|
|
||||||
|
int SyncthingProcess::exitCode() const
|
||||||
|
{
|
||||||
|
return m_process ? m_process->child.exit_code() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SyncthingProcess::waitForFinished(int msecs)
|
||||||
|
{
|
||||||
|
if (!m_process) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
auto ec = std::error_code();
|
||||||
|
if (msecs < 0) {
|
||||||
|
m_process->group.wait(ec);
|
||||||
|
} else {
|
||||||
|
m_process->group.wait_for(std::chrono::milliseconds(msecs), ec);
|
||||||
|
}
|
||||||
|
return !ec || ec == std::errc::no_such_process || ec == std::errc::no_child_process;
|
||||||
|
}
|
||||||
|
|
||||||
|
qint64 SyncthingProcess::processId() const
|
||||||
|
{
|
||||||
|
return m_process ? m_process->child.id() : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString SyncthingProcess::program() const
|
||||||
|
{
|
||||||
|
return m_process ? m_process->program : QString();
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList SyncthingProcess::arguments() const
|
||||||
|
{
|
||||||
|
return m_process ? m_process->arguments : QStringList();
|
||||||
|
}
|
||||||
|
|
||||||
|
qint64 SyncthingProcess::readData(char *data, qint64 maxSize)
|
||||||
|
{
|
||||||
|
if (!m_process) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (maxSize < 1) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (!m_process->bytesBuffered) {
|
||||||
|
bufferOutput();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto bytesAvailable = m_process->bytesBuffered - m_process->bytesRead;
|
||||||
|
if (bytesAvailable > static_cast<std::size_t>(maxSize)) {
|
||||||
|
std::memcpy(data, m_process->buffer + m_process->bytesRead, static_cast<std::size_t>(maxSize));
|
||||||
|
m_process->bytesRead += static_cast<std::size_t>(maxSize);
|
||||||
|
return maxSize;
|
||||||
|
} else {
|
||||||
|
std::memcpy(data, m_process->buffer + m_process->bytesRead, bytesAvailable);
|
||||||
|
m_process->bytesBuffered = 0;
|
||||||
|
m_process->bytesRead = 0;
|
||||||
|
bufferOutput();
|
||||||
|
return static_cast<qint64>(bytesAvailable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
qint64 SyncthingProcess::writeData(const char *data, qint64 len)
|
||||||
|
{
|
||||||
|
Q_UNUSED(data)
|
||||||
|
Q_UNUSED(len)
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
/// \endcond
|
||||||
|
|
||||||
} // namespace Data
|
} // namespace Data
|
||||||
|
|
|
@ -9,9 +9,21 @@
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
|
|
||||||
|
#ifdef LIB_SYNCTHING_CONNECTOR_BOOST_PROCESS
|
||||||
|
#include <memory>
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace Data {
|
namespace Data {
|
||||||
|
|
||||||
class LIB_SYNCTHING_CONNECTOR_EXPORT SyncthingProcess : public QProcess {
|
#ifdef LIB_SYNCTHING_CONNECTOR_BOOST_PROCESS
|
||||||
|
struct SyncthingProcessInternalData;
|
||||||
|
struct SyncthingProcessIOHandler;
|
||||||
|
using SyncthingProcessBase = QIODevice;
|
||||||
|
#else
|
||||||
|
using SyncthingProcessBase = QProcess;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
class LIB_SYNCTHING_CONNECTOR_EXPORT SyncthingProcess : public SyncthingProcessBase {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Q_PROPERTY(bool running READ isRunning)
|
Q_PROPERTY(bool running READ isRunning)
|
||||||
Q_PROPERTY(CppUtilities::DateTime activeSince READ activeSince)
|
Q_PROPERTY(CppUtilities::DateTime activeSince READ activeSince)
|
||||||
|
@ -19,6 +31,7 @@ class LIB_SYNCTHING_CONNECTOR_EXPORT SyncthingProcess : public QProcess {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit SyncthingProcess(QObject *parent = nullptr);
|
explicit SyncthingProcess(QObject *parent = nullptr);
|
||||||
|
~SyncthingProcess() override;
|
||||||
bool isRunning() const;
|
bool isRunning() const;
|
||||||
CppUtilities::DateTime activeSince() const;
|
CppUtilities::DateTime activeSince() const;
|
||||||
bool isActiveFor(unsigned int atLeastSeconds) const;
|
bool isActiveFor(unsigned int atLeastSeconds) const;
|
||||||
|
@ -26,26 +39,62 @@ public:
|
||||||
static SyncthingProcess *mainInstance();
|
static SyncthingProcess *mainInstance();
|
||||||
static void setMainInstance(SyncthingProcess *mainInstance);
|
static void setMainInstance(SyncthingProcess *mainInstance);
|
||||||
static QStringList splitArguments(const QString &arguments);
|
static QStringList splitArguments(const QString &arguments);
|
||||||
|
#ifdef LIB_SYNCTHING_CONNECTOR_BOOST_PROCESS
|
||||||
Q_SIGNALS:
|
QProcess::ProcessState state() const;
|
||||||
void confirmKill();
|
void start(const QString &program, const QStringList &arguments, QIODevice::OpenMode openMode = QIODevice::ReadOnly);
|
||||||
|
qint64 bytesAvailable() const override;
|
||||||
|
void close() override;
|
||||||
|
int exitCode() const;
|
||||||
|
bool waitForFinished(int msecs = 30000);
|
||||||
|
qint64 processId() const;
|
||||||
|
QString program() const;
|
||||||
|
QStringList arguments() const;
|
||||||
|
#endif
|
||||||
|
|
||||||
public Q_SLOTS:
|
public Q_SLOTS:
|
||||||
void restartSyncthing(const QString &program, const QStringList &arguments);
|
void restartSyncthing(const QString &program, const QStringList &arguments);
|
||||||
void startSyncthing(const QString &program, const QStringList &arguments);
|
void startSyncthing(const QString &program, const QStringList &arguments);
|
||||||
void stopSyncthing();
|
void stopSyncthing();
|
||||||
void killSyncthing();
|
void killSyncthing();
|
||||||
|
#ifdef LIB_SYNCTHING_CONNECTOR_BOOST_PROCESS
|
||||||
|
void terminate();
|
||||||
|
void kill();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
Q_SIGNALS:
|
||||||
|
#ifdef LIB_SYNCTHING_CONNECTOR_BOOST_PROCESS
|
||||||
|
void started();
|
||||||
|
void finished(int exitCode, QProcess::ExitStatus exitStatus);
|
||||||
|
void errorOccurred(QProcess::ProcessError error);
|
||||||
|
void stateChanged(QProcess::ProcessState newState);
|
||||||
|
#endif
|
||||||
|
void confirmKill();
|
||||||
|
|
||||||
|
#ifdef LIB_SYNCTHING_CONNECTOR_BOOST_PROCESS
|
||||||
|
protected:
|
||||||
|
qint64 readData(char *data, qint64 maxSize) override;
|
||||||
|
qint64 writeData(const char *data, qint64 len) override;
|
||||||
|
#endif
|
||||||
|
|
||||||
private Q_SLOTS:
|
private Q_SLOTS:
|
||||||
void handleStarted();
|
void handleStarted();
|
||||||
void handleFinished(int exitCode, QProcess::ExitStatus exitStatus);
|
void handleFinished(int exitCode, QProcess::ExitStatus exitStatus);
|
||||||
void killToRestart();
|
void killToRestart();
|
||||||
|
#ifdef LIB_SYNCTHING_CONNECTOR_BOOST_PROCESS
|
||||||
|
void handleError(QProcess::ProcessError error, const QString &errorMessage, bool closed);
|
||||||
|
void bufferOutput();
|
||||||
|
void handleLeftoverProcesses();
|
||||||
|
#endif
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QString m_program;
|
QString m_program;
|
||||||
QStringList m_arguments;
|
QStringList m_arguments;
|
||||||
CppUtilities::DateTime m_activeSince;
|
CppUtilities::DateTime m_activeSince;
|
||||||
QTimer m_killTimer;
|
QTimer m_killTimer;
|
||||||
|
#ifdef LIB_SYNCTHING_CONNECTOR_BOOST_PROCESS
|
||||||
|
std::shared_ptr<SyncthingProcessInternalData> m_process;
|
||||||
|
std::unique_ptr<SyncthingProcessIOHandler> m_handler;
|
||||||
|
#endif
|
||||||
bool m_manuallyStopped;
|
bool m_manuallyStopped;
|
||||||
static SyncthingProcess *s_mainInstance;
|
static SyncthingProcess *s_mainInstance;
|
||||||
};
|
};
|
||||||
|
|
|
@ -7,7 +7,9 @@ function handle_int {
|
||||||
}
|
}
|
||||||
trap "handle_int" SIGINT SIGTERM
|
trap "handle_int" SIGINT SIGTERM
|
||||||
|
|
||||||
|
i=0
|
||||||
while [[ true ]]; do
|
while [[ true ]]; do
|
||||||
echo $RANDOM
|
echo "$i : $RANDOM"
|
||||||
|
i=$((i + 1))
|
||||||
sleep 1
|
sleep 1
|
||||||
done
|
done
|
||||||
|
|
|
@ -94,13 +94,14 @@ void SyncthingTestInstance::start()
|
||||||
*/
|
*/
|
||||||
void SyncthingTestInstance::stop()
|
void SyncthingTestInstance::stop()
|
||||||
{
|
{
|
||||||
if (m_syncthingProcess.state() == QProcess::Running) {
|
if (m_syncthingProcess.isRunning()) {
|
||||||
cerr << "\n - Waiting for Syncthing to terminate ..." << endl;
|
cerr << "\n - Waiting for Syncthing to terminate ..." << endl;
|
||||||
m_syncthingProcess.terminate();
|
m_syncthingProcess.terminate();
|
||||||
m_syncthingProcess.waitForFinished();
|
m_syncthingProcess.waitForFinished();
|
||||||
}
|
}
|
||||||
if (m_syncthingProcess.isOpen()) {
|
if (m_syncthingProcess.isOpen()) {
|
||||||
cerr << "\n - Syncthing terminated with exit code " << m_syncthingProcess.exitCode() << ".\n";
|
cerr << "\n - Syncthing terminated with exit code " << m_syncthingProcess.exitCode() << ".\n";
|
||||||
|
/*
|
||||||
const auto stdOut(m_syncthingProcess.readAllStandardOutput());
|
const auto stdOut(m_syncthingProcess.readAllStandardOutput());
|
||||||
if (!stdOut.isEmpty()) {
|
if (!stdOut.isEmpty()) {
|
||||||
cerr << "\n - Syncthing stdout during the testrun:\n" << stdOut.data();
|
cerr << "\n - Syncthing stdout during the testrun:\n" << stdOut.data();
|
||||||
|
@ -114,6 +115,7 @@ void SyncthingTestInstance::stop()
|
||||||
cerr << "\n - Syncthing exited: " << stdOut.count("INFO: Syncthing exited: exit status") << " times";
|
cerr << "\n - Syncthing exited: " << stdOut.count("INFO: Syncthing exited: exit status") << " times";
|
||||||
cerr << "\n - Syncthing panicked: " << stdOut.count("WARNING: Panic detected") << " times";
|
cerr << "\n - Syncthing panicked: " << stdOut.count("WARNING: Panic detected") << " times";
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,7 +128,7 @@ void SyncthingTestInstance::setInterleavedOutputEnabled(bool interleavedOutputEn
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
m_interleavedOutput = interleavedOutputEnabled;
|
m_interleavedOutput = interleavedOutputEnabled;
|
||||||
m_syncthingProcess.setProcessChannelMode(interleavedOutputEnabled ? QProcess::ForwardedChannels : QProcess::SeparateChannels);
|
//m_syncthingProcess.setProcessChannelMode(interleavedOutputEnabled ? QProcess::ForwardedChannels : QProcess::SeparateChannels);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
|
|
@ -25,7 +25,7 @@ public:
|
||||||
const QString &apiKey() const;
|
const QString &apiKey() const;
|
||||||
const QString &syncthingPort() const;
|
const QString &syncthingPort() const;
|
||||||
QCoreApplication &application();
|
QCoreApplication &application();
|
||||||
QProcess &syncthingProcess();
|
Data::SyncthingProcess &syncthingProcess();
|
||||||
|
|
||||||
public Q_SLOTS:
|
public Q_SLOTS:
|
||||||
void start();
|
void start();
|
||||||
|
@ -57,7 +57,7 @@ inline QCoreApplication &SyncthingTestInstance::application()
|
||||||
return m_app;
|
return m_app;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline QProcess &SyncthingTestInstance::syncthingProcess()
|
inline Data::SyncthingProcess &SyncthingTestInstance::syncthingProcess()
|
||||||
{
|
{
|
||||||
return m_syncthingProcess;
|
return m_syncthingProcess;
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,8 +19,9 @@ int main(int argc, char **argv)
|
||||||
|
|
||||||
SyncthingTestInstance testInstance;
|
SyncthingTestInstance testInstance;
|
||||||
auto &syncthingProcess(testInstance.syncthingProcess());
|
auto &syncthingProcess(testInstance.syncthingProcess());
|
||||||
syncthingProcess.setProcessChannelMode(QProcess::ForwardedChannels);
|
//syncthingProcess.setProcessChannelMode(QProcess::ForwardedChannels);
|
||||||
QObject::connect(&syncthingProcess, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), &QCoreApplication::exit);
|
QObject::connect(&syncthingProcess, static_cast<void (Data::SyncthingProcess::*)(int, QProcess::ExitStatus)>(&Data::SyncthingProcess::finished),
|
||||||
|
&QCoreApplication::exit);
|
||||||
testInstance.start();
|
testInstance.start();
|
||||||
|
|
||||||
const int res = testInstance.application().exec();
|
const int res = testInstance.application().exec();
|
||||||
|
|
|
@ -59,11 +59,11 @@ void SyncthingKiller::confirmKill() const
|
||||||
msgBox->addButton(tr("Keep running"), QMessageBox::RejectRole);
|
msgBox->addButton(tr("Keep running"), QMessageBox::RejectRole);
|
||||||
msgBox->addButton(tr("Kill process"), QMessageBox::AcceptRole);
|
msgBox->addButton(tr("Kill process"), QMessageBox::AcceptRole);
|
||||||
connect(process,
|
connect(process,
|
||||||
#if QT_VERSION < QT_VERSION_CHECK(5, 13, 0) || QT_DEPRECATED_SINCE(5, 13)
|
#if !defined(LIB_SYNCTHING_CONNECTOR_BOOST_PROCESS) && (QT_VERSION < QT_VERSION_CHECK(5, 13, 0) || QT_DEPRECATED_SINCE(5, 13))
|
||||||
static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(
|
static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(
|
||||||
#endif
|
#endif
|
||||||
&SyncthingProcess::finished
|
&SyncthingProcess::finished
|
||||||
#if QT_VERSION < QT_VERSION_CHECK(5, 13, 0) || QT_DEPRECATED_SINCE(5, 13)
|
#if !defined(LIB_SYNCTHING_CONNECTOR_BOOST_PROCESS) && (QT_VERSION < QT_VERSION_CHECK(5, 13, 0) || QT_DEPRECATED_SINCE(5, 13))
|
||||||
)
|
)
|
||||||
#endif
|
#endif
|
||||||
,
|
,
|
||||||
|
|
|
@ -39,8 +39,8 @@ SyncthingLauncher::SyncthingLauncher(QObject *parent)
|
||||||
connect(&m_process, &SyncthingProcess::readyRead, this, &SyncthingLauncher::handleProcessReadyRead);
|
connect(&m_process, &SyncthingProcess::readyRead, this, &SyncthingLauncher::handleProcessReadyRead);
|
||||||
connect(&m_process, static_cast<void (SyncthingProcess::*)(int exitCode, QProcess::ExitStatus exitStatus)>(&SyncthingProcess::finished), this,
|
connect(&m_process, static_cast<void (SyncthingProcess::*)(int exitCode, QProcess::ExitStatus exitStatus)>(&SyncthingProcess::finished), this,
|
||||||
&SyncthingLauncher::handleProcessFinished);
|
&SyncthingLauncher::handleProcessFinished);
|
||||||
connect(&m_process, &SyncthingProcess::stateChanged, this, &SyncthingLauncher::handleProcessStateChanged);
|
connect(&m_process, &SyncthingProcess::stateChanged, this, &SyncthingLauncher::handleProcessStateChanged, Qt::QueuedConnection);
|
||||||
connect(&m_process, &SyncthingProcess::errorOccurred, this, &SyncthingLauncher::errorOccurred);
|
connect(&m_process, &SyncthingProcess::errorOccurred, this, &SyncthingLauncher::errorOccurred, Qt::QueuedConnection);
|
||||||
connect(&m_process, &SyncthingProcess::confirmKill, this, &SyncthingLauncher::confirmKill);
|
connect(&m_process, &SyncthingProcess::confirmKill, this, &SyncthingLauncher::confirmKill);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,7 @@ public:
|
||||||
bool isManuallyStopped() const;
|
bool isManuallyStopped() const;
|
||||||
bool isEmittingOutput() const;
|
bool isEmittingOutput() const;
|
||||||
void setEmittingOutput(bool emittingOutput);
|
void setEmittingOutput(bool emittingOutput);
|
||||||
|
QString errorString() const;
|
||||||
#ifdef SYNCTHINGWIDGETS_USE_LIBSYNCTHING
|
#ifdef SYNCTHINGWIDGETS_USE_LIBSYNCTHING
|
||||||
LibSyncthing::LogLevel libSyncthingLogLevel() const;
|
LibSyncthing::LogLevel libSyncthingLogLevel() const;
|
||||||
void setLibSyncthingLogLevel(LibSyncthing::LogLevel logLevel);
|
void setLibSyncthingLogLevel(LibSyncthing::LogLevel logLevel);
|
||||||
|
@ -129,6 +130,12 @@ inline bool SyncthingLauncher::isEmittingOutput() const
|
||||||
return m_emittingOutput;
|
return m_emittingOutput;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// \brief Returns the last error message.
|
||||||
|
inline QString SyncthingLauncher::errorString() const
|
||||||
|
{
|
||||||
|
return m_process.errorString();
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef SYNCTHINGWIDGETS_USE_LIBSYNCTHING
|
#ifdef SYNCTHINGWIDGETS_USE_LIBSYNCTHING
|
||||||
/// \brief Returns the log level used for libsyncthing.
|
/// \brief Returns the log level used for libsyncthing.
|
||||||
inline LibSyncthing::LogLevel SyncthingLauncher::libSyncthingLogLevel() const
|
inline LibSyncthing::LogLevel SyncthingLauncher::libSyncthingLogLevel() const
|
||||||
|
|
|
@ -80,7 +80,7 @@ struct SYNCTHINGWIDGETS_EXPORT Launcher {
|
||||||
#else
|
#else
|
||||||
QStringLiteral("syncthing");
|
QStringLiteral("syncthing");
|
||||||
#endif
|
#endif
|
||||||
QString syncthingArgs = QStringLiteral("-no-browser -no-restart -logflags=3");
|
QString syncthingArgs = QStringLiteral("-no-browser -no-console -no-restart -logflags=3");
|
||||||
QHash<QString, ToolParameter> tools;
|
QHash<QString, ToolParameter> tools;
|
||||||
bool considerForReconnect = false;
|
bool considerForReconnect = false;
|
||||||
bool showButton = false;
|
bool showButton = false;
|
||||||
|
|
|
@ -1094,25 +1094,32 @@ void LauncherOptionPage::handleSyncthingError(QProcess::ProcessError error)
|
||||||
cursor.movePosition(QTextCursor::End);
|
cursor.movePosition(QTextCursor::End);
|
||||||
cursor.insertBlock();
|
cursor.insertBlock();
|
||||||
|
|
||||||
QString errorString;
|
auto errorString = QString();
|
||||||
switch (error) {
|
if (m_launcher) {
|
||||||
case QProcess::FailedToStart:
|
errorString = m_launcher->errorString();
|
||||||
errorString = tr("failed to start (e.g. executable does not exist or not permission error)");
|
} else if (m_process) {
|
||||||
break;
|
errorString = m_process->errorString();
|
||||||
case QProcess::Crashed:
|
}
|
||||||
errorString = tr("process crashed");
|
if (errorString.isEmpty()) {
|
||||||
break;
|
switch (error) {
|
||||||
case QProcess::Timedout:
|
case QProcess::FailedToStart:
|
||||||
errorString = tr("timeout error");
|
errorString = tr("failed to start (e.g. executable does not exist or not permission error)");
|
||||||
break;
|
break;
|
||||||
case QProcess::ReadError:
|
case QProcess::Crashed:
|
||||||
errorString = tr("read error");
|
errorString = tr("process crashed");
|
||||||
break;
|
break;
|
||||||
case QProcess::WriteError:
|
case QProcess::Timedout:
|
||||||
errorString = tr("write error");
|
errorString = tr("timeout error");
|
||||||
break;
|
break;
|
||||||
default:
|
case QProcess::ReadError:
|
||||||
errorString = tr("unknown process error");
|
errorString = tr("read error");
|
||||||
|
break;
|
||||||
|
case QProcess::WriteError:
|
||||||
|
errorString = tr("write error");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
errorString = tr("unknown process error");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
cursor.insertText(tr("An error occurred when running %1: %2").arg(m_tool.isEmpty() ? QStringLiteral("Syncthing") : m_tool, errorString));
|
cursor.insertText(tr("An error occurred when running %1: %2").arg(m_tool.isEmpty() ? QStringLiteral("Syncthing") : m_tool, errorString));
|
||||||
cursor.insertBlock();
|
cursor.insertBlock();
|
||||||
|
|
Loading…
Reference in New Issue