Add high-level abstraction for launching Syncthing

Add new SyncthingLauncher class which lauches Syncthing
under the hood via external SyncthingProcess or using
libsyncthing.

Note: Launching via libsyncthing is still experimental.
This commit is contained in:
Martchus 2018-04-11 23:15:15 +02:00
parent 278ba521d9
commit 0ceb8d5e79
17 changed files with 388 additions and 101 deletions

View File

@ -29,9 +29,9 @@ SyncthingNotifier::SyncthingNotifier(const SyncthingConnection &connection, QObj
: QObject(parent) : QObject(parent)
, m_connection(connection) , m_connection(connection)
#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD #ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
, m_service(syncthingService()) , m_service(SyncthingService::mainInstance())
#endif #endif
, m_process(syncthingProcess()) , m_process(SyncthingProcess::mainInstance())
, m_enabledNotifications(SyncthingHighLevelNotification::None) , m_enabledNotifications(SyncthingHighLevelNotification::None)
, m_previousStatus(SyncthingStatus::Disconnected) , m_previousStatus(SyncthingStatus::Disconnected)
, m_ignoreInavailabilityAfterStart(15) , m_ignoreInavailabilityAfterStart(15)
@ -74,22 +74,22 @@ bool SyncthingNotifier::isDisconnectRelevant() const
} }
// consider process/launcher or systemd unit status // consider process/launcher or systemd unit status
if (m_process.isManuallyStopped()) { if (m_process && m_process->isManuallyStopped()) {
return false; return false;
} }
#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD #ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
const SyncthingService &service(syncthingService()); if (m_service && m_service->isManuallyStopped()) {
if (m_service.isManuallyStopped()) {
return false; return false;
} }
#endif #endif
// ignore inavailability after start or standby-wakeup // ignore inavailability after start or standby-wakeup
if (m_ignoreInavailabilityAfterStart) { if (m_ignoreInavailabilityAfterStart) {
if (m_process.isRunning() if ((m_process && m_process->isRunning())
#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD #ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
&& ((m_service.isSystemdAvailable() && !service.isActiveWithoutSleepFor(m_process.activeSince(), m_ignoreInavailabilityAfterStart)) && ((m_service && m_service->isSystemdAvailable()
|| !m_process.isActiveFor(m_ignoreInavailabilityAfterStart)) && !m_service->isActiveWithoutSleepFor(m_process->activeSince(), m_ignoreInavailabilityAfterStart))
|| !m_process->isActiveFor(m_ignoreInavailabilityAfterStart))
#else #else
&& !m_process.isActiveFor(m_ignoreInavailabilityAfterStart) && !m_process.isActiveFor(m_ignoreInavailabilityAfterStart)
#endif #endif
@ -97,7 +97,7 @@ bool SyncthingNotifier::isDisconnectRelevant() const
return false; return false;
} }
#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD #ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
if (m_service.isRunning() && !m_service.isActiveWithoutSleepFor(m_ignoreInavailabilityAfterStart)) { if (m_service->isRunning() && !m_service->isActiveWithoutSleepFor(m_ignoreInavailabilityAfterStart)) {
return false; return false;
} }
#endif #endif

View File

@ -81,9 +81,9 @@ private:
const SyncthingConnection &m_connection; const SyncthingConnection &m_connection;
#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD #ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
const SyncthingService &m_service; const SyncthingService *const m_service;
#endif #endif
const SyncthingProcess &m_process; const SyncthingProcess *const m_process;
SyncthingHighLevelNotification m_enabledNotifications; SyncthingHighLevelNotification m_enabledNotifications;
SyncthingStatus m_previousStatus; SyncthingStatus m_previousStatus;
unsigned int m_ignoreInavailabilityAfterStart; unsigned int m_ignoreInavailabilityAfterStart;

View File

@ -6,6 +6,8 @@ using namespace ChronoUtilities;
namespace Data { namespace Data {
SyncthingProcess *SyncthingProcess::s_mainInstance = nullptr;
SyncthingProcess::SyncthingProcess(QObject *parent) SyncthingProcess::SyncthingProcess(QObject *parent)
: QProcess(parent) : QProcess(parent)
, m_manuallyStopped(false) , m_manuallyStopped(false)
@ -89,10 +91,4 @@ void SyncthingProcess::killToRestart()
} }
} }
SyncthingProcess &syncthingProcess()
{
static SyncthingProcess process;
return process;
}
} // namespace Data } // namespace Data

View File

@ -22,6 +22,8 @@ public:
ChronoUtilities::DateTime activeSince() const; ChronoUtilities::DateTime activeSince() const;
bool isActiveFor(unsigned int atLeastSeconds) const; bool isActiveFor(unsigned int atLeastSeconds) const;
bool isManuallyStopped() const; bool isManuallyStopped() const;
static SyncthingProcess *mainInstance();
static void setMainInstance(SyncthingProcess *mainInstance);
Q_SIGNALS: Q_SIGNALS:
void confirmKill(); void confirmKill();
@ -42,6 +44,7 @@ private:
ChronoUtilities::DateTime m_activeSince; ChronoUtilities::DateTime m_activeSince;
QTimer m_killTimer; QTimer m_killTimer;
bool m_manuallyStopped; bool m_manuallyStopped;
static SyncthingProcess *s_mainInstance;
}; };
inline bool SyncthingProcess::isRunning() const inline bool SyncthingProcess::isRunning() const
@ -64,7 +67,15 @@ inline bool SyncthingProcess::isManuallyStopped() const
return m_manuallyStopped; return m_manuallyStopped;
} }
SyncthingProcess LIB_SYNCTHING_CONNECTOR_EXPORT &syncthingProcess(); inline SyncthingProcess *SyncthingProcess::mainInstance()
{
return s_mainInstance;
}
inline void SyncthingProcess::setMainInstance(SyncthingProcess *mainInstance)
{
s_mainInstance = mainInstance;
}
} // namespace Data } // namespace Data

View File

@ -45,6 +45,7 @@ constexpr DateTime dateTimeFromSystemdTimeStamp(qulonglong timeStamp)
return DateTime(DateTime::unixEpochStart().totalTicks() + timeStamp * 10); return DateTime(DateTime::unixEpochStart().totalTicks() + timeStamp * 10);
} }
SyncthingService *SyncthingService::s_mainInstance = nullptr;
OrgFreedesktopSystemd1ManagerInterface *SyncthingService::s_manager = nullptr; OrgFreedesktopSystemd1ManagerInterface *SyncthingService::s_manager = nullptr;
OrgFreedesktopLogin1ManagerInterface *SyncthingService::s_loginManager = nullptr; OrgFreedesktopLogin1ManagerInterface *SyncthingService::s_loginManager = nullptr;
DateTime SyncthingService::s_lastWakeUp = DateTime(); DateTime SyncthingService::s_lastWakeUp = DateTime();
@ -324,12 +325,6 @@ void SyncthingService::setProperties(
} }
} }
SyncthingService &syncthingService()
{
static SyncthingService service;
return service;
}
} // namespace Data } // namespace Data
#endif #endif

View File

@ -63,6 +63,8 @@ public:
bool isRunning() const; bool isRunning() const;
bool isEnabled() const; bool isEnabled() const;
bool isManuallyStopped() const; bool isManuallyStopped() const;
static SyncthingService *mainInstance();
static void setMainInstance(SyncthingService *mainInstance);
public Q_SLOTS: public Q_SLOTS:
void setUnitName(const QString &unitName); void setUnitName(const QString &unitName);
@ -110,6 +112,7 @@ private:
static OrgFreedesktopLogin1ManagerInterface *s_loginManager; static OrgFreedesktopLogin1ManagerInterface *s_loginManager;
static bool s_fallingAsleep; static bool s_fallingAsleep;
static ChronoUtilities::DateTime s_lastWakeUp; static ChronoUtilities::DateTime s_lastWakeUp;
static SyncthingService *s_mainInstance;
QString m_unitName; QString m_unitName;
QDBusServiceWatcher *m_serviceWatcher; QDBusServiceWatcher *m_serviceWatcher;
OrgFreedesktopSystemd1UnitInterface *m_unit; OrgFreedesktopSystemd1UnitInterface *m_unit;
@ -209,7 +212,15 @@ inline void SyncthingService::disable()
setEnabled(false); setEnabled(false);
} }
SyncthingService &syncthingService(); inline SyncthingService *SyncthingService::mainInstance()
{
return s_mainInstance;
}
inline void SyncthingService::setMainInstance(SyncthingService *mainInstance)
{
s_mainInstance = mainInstance;
}
} // namespace Data } // namespace Data

View File

@ -67,6 +67,9 @@ SyncthingApplet::~SyncthingApplet()
#ifndef SYNCTHINGWIDGETS_NO_WEBVIEW #ifndef SYNCTHINGWIDGETS_NO_WEBVIEW
delete m_webViewDlg; delete m_webViewDlg;
#endif #endif
#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
SyncthingService::setMainInstance(nullptr);
#endif
} }
void SyncthingApplet::init() void SyncthingApplet::init()
@ -99,10 +102,10 @@ void SyncthingApplet::init()
// initialize systemd service support // initialize systemd service support
#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD #ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
SyncthingService &service = syncthingService(); SyncthingService::setMainInstance(&m_service);
service.setUnitName(Settings::values().systemd.syncthingUnit); m_service.setUnitName(Settings::values().systemd.syncthingUnit);
connect(&service, &SyncthingService::systemdAvailableChanged, this, &SyncthingApplet::handleSystemdStatusChanged); connect(&m_service, &SyncthingService::systemdAvailableChanged, this, &SyncthingApplet::handleSystemdStatusChanged);
connect(&service, &SyncthingService::errorOccurred, this, &SyncthingApplet::handleSystemdServiceError); connect(&m_service, &SyncthingService::errorOccurred, this, &SyncthingApplet::handleSystemdServiceError);
#endif #endif
m_initialized = true; m_initialized = true;

View File

@ -141,6 +141,9 @@ private:
Dialogs::AboutDialog *m_aboutDlg; Dialogs::AboutDialog *m_aboutDlg;
Data::SyncthingConnection m_connection; Data::SyncthingConnection m_connection;
Data::SyncthingNotifier m_notifier; Data::SyncthingNotifier m_notifier;
#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
Data::SyncthingService m_service;
#endif
QtGui::StatusInfo m_statusInfo; QtGui::StatusInfo m_statusInfo;
Data::SyncthingDirectoryModel m_dirModel; Data::SyncthingDirectoryModel m_dirModel;
Data::SyncthingDeviceModel m_devModel; Data::SyncthingDeviceModel m_devModel;
@ -179,7 +182,7 @@ inline Data::SyncthingDownloadModel *SyncthingApplet::downloadModel() const
inline Data::SyncthingService *SyncthingApplet::service() const inline Data::SyncthingService *SyncthingApplet::service() const
{ {
#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD #ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
return &Data::syncthingService(); return const_cast<Data::SyncthingService *>(&m_service);
#else #else
return nullptr; return nullptr;
#endif #endif

View File

@ -3,6 +3,7 @@
#include "../gui/trayicon.h" #include "../gui/trayicon.h"
#include "../gui/traywidget.h" #include "../gui/traywidget.h"
#include "../../widgets/misc/syncthinglauncher.h"
#include "../../widgets/settings/settings.h" #include "../../widgets/settings/settings.h"
#include "../../connector/syncthingprocess.h" #include "../../connector/syncthingprocess.h"
@ -161,8 +162,11 @@ int runApplication(int argc, const char *const *argv)
Settings::values().qt.apply(); Settings::values().qt.apply();
qtConfigArgs.applySettings(true); qtConfigArgs.applySettings(true);
LOAD_QT_TRANSLATIONS; LOAD_QT_TRANSLATIONS;
SyncthingLauncher launcher;
SyncthingLauncher::setMainInstance(&launcher);
#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD #ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
SyncthingService &service = syncthingService(); SyncthingService service;
SyncthingService::setMainInstance(&service);
service.setUnitName(Settings::values().systemd.syncthingUnit); service.setUnitName(Settings::values().systemd.syncthingUnit);
QObject::connect(&service, &SyncthingService::errorOccurred, &handleSystemdServiceError); QObject::connect(&service, &SyncthingService::errorOccurred, &handleSystemdServiceError);
#endif #endif

View File

@ -157,10 +157,11 @@ TrayWidget::TrayWidget(const QString &connectionConfig, TrayMenu *parent)
connect(m_ui->actionShowNotifications, &QAction::triggered, this, &TrayWidget::showNotifications); connect(m_ui->actionShowNotifications, &QAction::triggered, this, &TrayWidget::showNotifications);
connect(m_ui->actionDismissNotifications, &QAction::triggered, this, &TrayWidget::dismissNotifications); connect(m_ui->actionDismissNotifications, &QAction::triggered, this, &TrayWidget::dismissNotifications);
#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD #ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
const SyncthingService &service = syncthingService(); if (const auto *const service = SyncthingService::mainInstance()) {
connect(m_ui->startStopPushButton, &QPushButton::clicked, &service, &SyncthingService::toggleRunning); connect(m_ui->startStopPushButton, &QPushButton::clicked, service, &SyncthingService::toggleRunning);
connect(&service, &SyncthingService::systemdAvailableChanged, this, &TrayWidget::handleSystemdStatusChanged); connect(service, &SyncthingService::systemdAvailableChanged, this, &TrayWidget::handleSystemdStatusChanged);
connect(&service, &SyncthingService::stateChanged, this, &TrayWidget::handleSystemdStatusChanged); connect(service, &SyncthingService::stateChanged, this, &TrayWidget::handleSystemdStatusChanged);
}
#endif #endif
} }
@ -508,22 +509,19 @@ bool TrayWidget::applySystemdSettings(bool reconnectRequired)
bool isServiceRelevant, isServiceRunning; bool isServiceRelevant, isServiceRunning;
tie(isServiceRelevant, isServiceRunning) = systemdSettings.apply(m_connection, m_selectedConnection, reconnectRequired); tie(isServiceRelevant, isServiceRunning) = systemdSettings.apply(m_connection, m_selectedConnection, reconnectRequired);
if (isServiceRelevant) { // update start/stop button
// update start/stop button if (isServiceRelevant && systemdSettings.showButton) {
if (systemdSettings.showButton) { m_ui->startStopPushButton->setVisible(true);
m_ui->startStopPushButton->setVisible(true); if (isServiceRunning) {
const auto &unitName(syncthingService().unitName()); m_ui->startStopPushButton->setText(tr("Stop"));
if (isServiceRunning) { m_ui->startStopPushButton->setToolTip(QStringLiteral("systemctl --user stop ") + systemdSettings.syncthingUnit);
m_ui->startStopPushButton->setText(tr("Stop")); m_ui->startStopPushButton->setIcon(
m_ui->startStopPushButton->setToolTip(QStringLiteral("systemctl --user stop ") + unitName); QIcon::fromTheme(QStringLiteral("process-stop"), QIcon(QStringLiteral(":/icons/hicolor/scalable/actions/process-stop.svg"))));
m_ui->startStopPushButton->setIcon( } else {
QIcon::fromTheme(QStringLiteral("process-stop"), QIcon(QStringLiteral(":/icons/hicolor/scalable/actions/process-stop.svg")))); m_ui->startStopPushButton->setText(tr("Start"));
} else { m_ui->startStopPushButton->setToolTip(QStringLiteral("systemctl --user start ") + systemdSettings.syncthingUnit);
m_ui->startStopPushButton->setText(tr("Start")); m_ui->startStopPushButton->setIcon(
m_ui->startStopPushButton->setToolTip(QStringLiteral("systemctl --user start ") + unitName); QIcon::fromTheme(QStringLiteral("system-run"), QIcon(QStringLiteral(":/icons/hicolor/scalable/apps/system-run.svg"))));
m_ui->startStopPushButton->setIcon(
QIcon::fromTheme(QStringLiteral("system-run"), QIcon(QStringLiteral(":/icons/hicolor/scalable/apps/system-run.svg"))));
}
} }
} }
if (!systemdSettings.showButton || !isServiceRelevant) { if (!systemdSettings.showButton || !isServiceRelevant) {
@ -534,7 +532,8 @@ bool TrayWidget::applySystemdSettings(bool reconnectRequired)
void TrayWidget::connectIfServiceRunning() void TrayWidget::connectIfServiceRunning()
{ {
if (Settings::values().systemd.considerForReconnect && m_connection.isLocal() && syncthingService().isRunning()) { const auto *const service(SyncthingService::mainInstance());
if (Settings::values().systemd.considerForReconnect && m_connection.isLocal() && service && service->isRunning()) {
m_connection.connect(); m_connection.connect();
} }
} }

View File

@ -21,6 +21,7 @@ set(WIDGETS_HEADER_FILES
misc/dbusstatusnotifier.h misc/dbusstatusnotifier.h
misc/internalerror.h misc/internalerror.h
misc/otherdialogs.h misc/otherdialogs.h
misc/syncthinglauncher.h
misc/syncthingkiller.h misc/syncthingkiller.h
) )
set(WIDGETS_SRC_FILES set(WIDGETS_SRC_FILES
@ -36,6 +37,7 @@ set(WIDGETS_SRC_FILES
misc/dbusstatusnotifier.cpp misc/dbusstatusnotifier.cpp
misc/internalerror.cpp misc/internalerror.cpp
misc/otherdialogs.cpp misc/otherdialogs.cpp
misc/syncthinglauncher.cpp
misc/syncthingkiller.cpp misc/syncthingkiller.cpp
) )
set(RES_FILES set(RES_FILES
@ -88,6 +90,17 @@ use_syncthingconnector()
find_package(syncthingmodel ${META_APP_VERSION} REQUIRED) find_package(syncthingmodel ${META_APP_VERSION} REQUIRED)
use_syncthingmodel() use_syncthingmodel()
option(USE_LIBSYNCTHING "whether libsyncthing should be included for the launcher" OFF)
if(USE_LIBSYNCTHING)
find_package(syncthing ${META_APP_VERSION} REQUIRED)
use_syncthing()
set_source_files_properties(
misc/syncthinglauncher.cpp
PROPERTIES COMPILE_DEFINITIONS SYNCTHING_WIDGETS_USE_LIBSYNCTHING
)
list(APPEND ADDITIONAL_QT_MODULES Concurrent)
endif()
# link also explicitely against the following Qt 5 modules # link also explicitely against the following Qt 5 modules
list(APPEND ADDITIONAL_QT_MODULES Network) list(APPEND ADDITIONAL_QT_MODULES Network)

View File

@ -1,9 +1,9 @@
#include "./internalerror.h" #include "./internalerror.h"
#include "./syncthinglauncher.h"
#include "../settings/settings.h" #include "../settings/settings.h"
#include "../../connector/syncthingconnection.h" #include "../../connector/syncthingconnection.h"
#include "../../connector/syncthingprocess.h"
#include "../../connector/syncthingservice.h" #include "../../connector/syncthingservice.h"
#include <QNetworkReply> #include <QNetworkReply>
@ -37,23 +37,24 @@ bool InternalError::isRelevant(const SyncthingConnection &connection, SyncthingE
// consider process/launcher or systemd unit status // consider process/launcher or systemd unit status
const auto remoteHostClosed(networkError == QNetworkReply::RemoteHostClosedError); const auto remoteHostClosed(networkError == QNetworkReply::RemoteHostClosedError);
// ignore "remote host closed" error if we've just stopped Syncthing ourselves // ignore "remote host closed" error if we've just stopped Syncthing ourselves
const SyncthingProcess &process(syncthingProcess()); const auto *launcher(SyncthingLauncher::mainInstance());
if (settings.launcher.considerForReconnect && remoteHostClosed && process.isManuallyStopped()) { if (settings.launcher.considerForReconnect && remoteHostClosed && launcher && launcher->isManuallyStopped()) {
return false; return false;
} }
#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD #ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
const SyncthingService &service(syncthingService()); const auto *const service(SyncthingService::mainInstance());
if (settings.systemd.considerForReconnect && remoteHostClosed && service.isManuallyStopped()) { if (settings.systemd.considerForReconnect && remoteHostClosed && service && service->isManuallyStopped()) {
return false; return false;
} }
#endif #endif
// ignore inavailability after start or standby-wakeup // ignore inavailability after start or standby-wakeup
if (settings.ignoreInavailabilityAfterStart && networkError == QNetworkReply::ConnectionRefusedError) { if (settings.ignoreInavailabilityAfterStart && networkError == QNetworkReply::ConnectionRefusedError) {
if (process.isRunning() if ((launcher && launcher->isRunning())
#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD #ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
&& ((service.isSystemdAvailable() && !service.isActiveWithoutSleepFor(process.activeSince(), settings.ignoreInavailabilityAfterStart)) && ((service && service->isSystemdAvailable()
|| !process.isActiveFor(settings.ignoreInavailabilityAfterStart)) && !service->isActiveWithoutSleepFor(launcher->activeSince(), settings.ignoreInavailabilityAfterStart))
|| !launcher->isActiveFor(settings.ignoreInavailabilityAfterStart))
#else #else
&& !process.isActiveFor(settings.ignoreInavailabilityAfterStart) && !process.isActiveFor(settings.ignoreInavailabilityAfterStart)
#endif #endif
@ -61,7 +62,7 @@ bool InternalError::isRelevant(const SyncthingConnection &connection, SyncthingE
return false; return false;
} }
#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD #ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
if (service.isRunning() && !service.isActiveWithoutSleepFor(settings.ignoreInavailabilityAfterStart)) { if (service && !service->isActiveWithoutSleepFor(settings.ignoreInavailabilityAfterStart)) {
return false; return false;
} }
#endif #endif

View File

@ -0,0 +1,100 @@
#include "./syncthinglauncher.h"
#ifdef SYNCTHING_WIDGETS_USE_LIBSYNCTHING
#include "../../libsyncthing/interface.h"
#include <QtConcurrentRun>
#endif
using namespace ChronoUtilities;
namespace Data {
SyncthingLauncher *SyncthingLauncher::s_mainInstance = nullptr;
SyncthingLauncher::SyncthingLauncher(QObject *parent)
: QObject(parent)
{
connect(&m_process, &SyncthingProcess::readyRead, this, &SyncthingLauncher::handleProcessReadyRead);
connect(&m_process, static_cast<void (SyncthingProcess::*)(int exitCode, QProcess::ExitStatus exitStatus)>(&SyncthingProcess::finished), this,
&SyncthingLauncher::handleProcessFinished);
connect(&m_process, &SyncthingProcess::confirmKill, this, &SyncthingLauncher::confirmKill);
}
bool SyncthingLauncher::isLibSyncthingAvailable()
{
#ifdef SYNCTHING_WIDGETS_USE_LIBSYNCTHING
return true;
#else
return false;
#endif
}
void SyncthingLauncher::launch(const QString &cmd)
{
if (isRunning()) {
return;
}
m_manuallyStopped = false;
m_process.startSyncthing(cmd);
}
void SyncthingLauncher::launch(const LibSyncthing::RuntimeOptions &runtimeOptions)
{
if (isRunning()) {
return;
}
m_manuallyStopped = false;
#ifdef SYNCTHING_WIDGETS_USE_LIBSYNCTHING
m_future = QtConcurrent::run(this, &SyncthingLauncher::runLibSyncthing, runtimeOptions);
#else
VAR_UNUSED(runtimeOptions)
emit outputAvailable("libsyncthing support not enabled");
emit exited(-1, QProcess::CrashExit);
#endif
}
void SyncthingLauncher::terminate()
{
if (m_process.isRunning()) {
m_manuallyStopped = true;
m_process.stopSyncthing();
} else if (m_future.isRunning()) {
m_manuallyStopped = true;
m_future.cancel(); // FIXME: this will not work of course
}
}
void SyncthingLauncher::kill()
{
if (m_process.isRunning()) {
m_manuallyStopped = true;
m_process.stopSyncthing();
} else if (m_future.isRunning()) {
m_manuallyStopped = true;
// FIXME
}
}
void SyncthingLauncher::handleProcessReadyRead()
{
emit outputAvailable(m_process.readAll());
}
void SyncthingLauncher::handleProcessFinished(int exitCode, QProcess::ExitStatus exitStatus)
{
emit runningChanged(false);
emit exited(exitCode, exitStatus);
}
void SyncthingLauncher::runLibSyncthing(const LibSyncthing::RuntimeOptions &runtimeOptions)
{
LibSyncthing::runSyncthing(runtimeOptions);
}
SyncthingLauncher &syncthingLauncher()
{
static SyncthingLauncher launcher;
return launcher;
}
} // namespace Data

View File

@ -0,0 +1,100 @@
#ifndef SYNCTHINGWIDGETS_SYNCTHINGLAUNCHER_H
#define SYNCTHINGWIDGETS_SYNCTHINGLAUNCHER_H
#include "../global.h"
#include "../../connector/syncthingprocess.h"
#include <QFuture>
namespace LibSyncthing {
struct RuntimeOptions;
}
namespace Data {
class SYNCTHINGWIDGETS_EXPORT SyncthingLauncher : public QObject {
Q_OBJECT
Q_PROPERTY(bool running READ isRunning NOTIFY runningChanged)
Q_PROPERTY(ChronoUtilities::DateTime activeSince READ activeSince)
Q_PROPERTY(bool manuallyStopped READ isManuallyStopped)
public:
explicit SyncthingLauncher(QObject *parent = nullptr);
bool isRunning() const;
ChronoUtilities::DateTime activeSince() const;
bool isActiveFor(unsigned int atLeastSeconds) const;
bool isManuallyStopped() const;
static bool isLibSyncthingAvailable();
static SyncthingLauncher *mainInstance();
static void setMainInstance(SyncthingLauncher *mainInstance);
Q_SIGNALS:
void confirmKill();
void runningChanged(bool isRunning);
void outputAvailable(const QByteArray &data);
void exited(int exitCode, QProcess::ExitStatus exitStatus);
public Q_SLOTS:
void launch(const QString &cmd);
void launch(const LibSyncthing::RuntimeOptions &runtimeOptions);
void terminate();
void kill();
private Q_SLOTS:
void handleProcessReadyRead();
void handleProcessFinished(int exitCode, QProcess::ExitStatus exitStatus);
void runLibSyncthing(const LibSyncthing::RuntimeOptions &runtimeOptions);
private:
SyncthingProcess m_process;
QFuture<void> m_future;
ChronoUtilities::DateTime m_futureStarted;
bool m_manuallyStopped;
static SyncthingLauncher *s_mainInstance;
};
inline bool SyncthingLauncher::isRunning() const
{
return m_process.isRunning() || m_future.isRunning();
}
inline ChronoUtilities::DateTime SyncthingLauncher::activeSince() const
{
if (m_process.isRunning()) {
return m_process.activeSince();
} else if (m_future.isRunning()) {
return m_futureStarted;
}
return ChronoUtilities::DateTime();
}
inline bool SyncthingLauncher::isActiveFor(unsigned int atLeastSeconds) const
{
const auto activeSince(this->activeSince());
return !activeSince.isNull() && (ChronoUtilities::DateTime::gmtNow() - activeSince).totalSeconds() > atLeastSeconds;
}
inline bool SyncthingLauncher::isManuallyStopped() const
{
return m_manuallyStopped;
}
inline SyncthingLauncher *SyncthingLauncher::mainInstance()
{
return s_mainInstance;
}
inline void SyncthingLauncher::setMainInstance(SyncthingLauncher *mainInstance)
{
if ((s_mainInstance = mainInstance) && !SyncthingProcess::mainInstance()) {
SyncthingProcess::setMainInstance(&mainInstance->m_process);
}
}
SyncthingLauncher SYNCTHINGWIDGETS_EXPORT &syncthingLauncher();
} // namespace Data
#endif // SYNCTHINGWIDGETS_SYNCTHINGLAUNCHER_H

View File

@ -1,7 +1,10 @@
#include "./settings.h" #include "./settings.h"
#include "../misc/syncthingkiller.h"
#include "../misc/syncthinglauncher.h"
#include "../../connector/syncthingnotifier.h" #include "../../connector/syncthingnotifier.h"
#include "../../connector/syncthingprocess.h" #include "../../connector/syncthingprocess.h"
#include "../misc/syncthingkiller.h"
#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD #ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
#include "../../connector/syncthingconnection.h" #include "../../connector/syncthingconnection.h"
#include "../../connector/syncthingservice.h" #include "../../connector/syncthingservice.h"
@ -72,7 +75,9 @@ std::vector<SyncthingProcess *> Launcher::allProcesses()
{ {
vector<SyncthingProcess *> processes; vector<SyncthingProcess *> processes;
processes.reserve(1 + toolProcesses.size()); processes.reserve(1 + toolProcesses.size());
processes.push_back(&syncthingProcess()); if (auto *const syncthingProcess = SyncthingProcess::mainInstance()) {
processes.push_back(syncthingProcess);
}
for (auto &process : toolProcesses) { for (auto &process : toolProcesses) {
processes.push_back(&process.second); processes.push_back(&process.second);
} }
@ -84,8 +89,10 @@ std::vector<SyncthingProcess *> Launcher::allProcesses()
*/ */
void Launcher::autostart() const void Launcher::autostart() const
{ {
if (enabled && !syncthingPath.isEmpty()) { auto *const launcher(SyncthingLauncher::mainInstance());
syncthingProcess().startSyncthing(syncthingCmd()); // TODO: allow using libsyncthing
if (enabled && !syncthingPath.isEmpty() && launcher) {
launcher->launch(syncthingCmd());
} }
for (auto i = tools.cbegin(), end = tools.cend(); i != end; ++i) { for (auto i = tools.cbegin(), end = tools.cend(); i != end; ++i) {
const ToolParameter &toolParams = i.value(); const ToolParameter &toolParams = i.value();
@ -338,9 +345,12 @@ void Settings::apply(SyncthingNotifier &notifier) const
std::tuple<bool, bool> Systemd::apply( std::tuple<bool, bool> Systemd::apply(
Data::SyncthingConnection &connection, const SyncthingConnectionSettings *currentConnectionSettings, bool reconnectRequired) const Data::SyncthingConnection &connection, const SyncthingConnectionSettings *currentConnectionSettings, bool reconnectRequired) const
{ {
const SyncthingService &service(syncthingService()); auto *const service(SyncthingService::mainInstance());
const auto isRelevant = service.isSystemdAvailable() && connection.isLocal(); if (!service) {
const auto isRunning = service.isRunning(); return make_tuple(false, false);
}
const auto isRelevant = service->isSystemdAvailable() && connection.isLocal();
const auto isRunning = service->isRunning();
if (currentConnectionSettings && (!considerForReconnect || !isRelevant || isRunning)) { if (currentConnectionSettings && (!considerForReconnect || !isRelevant || isRunning)) {
// ensure auto-reconnect is configured according to settings // ensure auto-reconnect is configured according to settings
@ -354,14 +364,14 @@ std::tuple<bool, bool> Systemd::apply(
if (considerForReconnect && isRelevant) { if (considerForReconnect && isRelevant) {
constexpr auto minActiveTimeInSeconds(5); constexpr auto minActiveTimeInSeconds(5);
if (reconnectRequired) { if (reconnectRequired) {
if (service.isActiveWithoutSleepFor(minActiveTimeInSeconds)) { if (service->isActiveWithoutSleepFor(minActiveTimeInSeconds)) {
connection.reconnect(); connection.reconnect();
} else { } else {
// give the service (which has just started) a few seconds to initialize // give the service (which has just started) a few seconds to initialize
connection.reconnectLater(minActiveTimeInSeconds * 1000); connection.reconnectLater(minActiveTimeInSeconds * 1000);
} }
} else if (isRunning && !connection.isConnected()) { } else if (isRunning && !connection.isConnected()) {
if (service.isActiveWithoutSleepFor(minActiveTimeInSeconds)) { if (service->isActiveWithoutSleepFor(minActiveTimeInSeconds)) {
connection.connect(); connection.connect();
} else { } else {
// give the service (which has just started) a few seconds to initialize // give the service (which has just started) a few seconds to initialize

View File

@ -1,5 +1,7 @@
#include "./settingsdialog.h" #include "./settingsdialog.h"
#include "../misc/syncthinglauncher.h"
#include "../../connector/syncthingconfig.h" #include "../../connector/syncthingconfig.h"
#include "../../connector/syncthingconnection.h" #include "../../connector/syncthingconnection.h"
#include "../../connector/syncthingprocess.h" #include "../../connector/syncthingprocess.h"
@ -606,14 +608,16 @@ void AutostartOptionPage::reset()
// LauncherOptionPage // LauncherOptionPage
LauncherOptionPage::LauncherOptionPage(QWidget *parentWidget) LauncherOptionPage::LauncherOptionPage(QWidget *parentWidget)
: LauncherOptionPageBase(parentWidget) : LauncherOptionPageBase(parentWidget)
, m_process(syncthingProcess()) , m_process(nullptr)
, m_launcher(SyncthingLauncher::mainInstance())
, m_kill(false) , m_kill(false)
{ {
} }
LauncherOptionPage::LauncherOptionPage(const QString &tool, QWidget *parentWidget) LauncherOptionPage::LauncherOptionPage(const QString &tool, QWidget *parentWidget)
: LauncherOptionPageBase(parentWidget) : LauncherOptionPageBase(parentWidget)
, m_process(Launcher::toolProcess(tool)) , m_process(&Launcher::toolProcess(tool))
, m_launcher(nullptr)
, m_kill(false) , m_kill(false)
, m_tool(tool) , m_tool(tool)
{ {
@ -641,14 +645,20 @@ QWidget *LauncherOptionPage::setupWidget()
// setup other widgets // setup other widgets
ui()->syncthingPathSelection->provideCustomFileMode(QFileDialog::ExistingFile); ui()->syncthingPathSelection->provideCustomFileMode(QFileDialog::ExistingFile);
ui()->logTextEdit->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); ui()->logTextEdit->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont));
const bool running = m_process.state() != QProcess::NotRunning; const auto running(isRunning());
ui()->launchNowPushButton->setHidden(running); ui()->launchNowPushButton->setHidden(running);
ui()->stopPushButton->setHidden(!running); ui()->stopPushButton->setHidden(!running);
// connect signals & slots // connect signals & slots
m_connections << QObject::connect(&m_process, &SyncthingProcess::readyRead, bind(&LauncherOptionPage::handleSyncthingReadyRead, this)); if (m_process) {
m_connections << QObject::connect(&m_process, m_connections << QObject::connect(m_process, &SyncthingProcess::readyRead, bind(&LauncherOptionPage::handleSyncthingReadyRead, this));
static_cast<void (SyncthingProcess::*)(int exitCode, QProcess::ExitStatus exitStatus)>(&SyncthingProcess::finished), m_connections << QObject::connect(m_process,
bind(&LauncherOptionPage::handleSyncthingExited, this, _1, _2)); static_cast<void (SyncthingProcess::*)(int exitCode, QProcess::ExitStatus exitStatus)>(&SyncthingProcess::finished),
bind(&LauncherOptionPage::handleSyncthingExited, this, _1, _2));
} else if (m_launcher) {
m_connections << QObject::connect(
m_launcher, &SyncthingLauncher::outputAvailable, bind(&LauncherOptionPage::handleSyncthingOutputAvailable, this, _1));
m_connections << QObject::connect(m_launcher, &SyncthingLauncher::exited, bind(&LauncherOptionPage::handleSyncthingExited, this, _1, _2));
}
QObject::connect(ui()->launchNowPushButton, &QPushButton::clicked, bind(&LauncherOptionPage::launch, this)); QObject::connect(ui()->launchNowPushButton, &QPushButton::clicked, bind(&LauncherOptionPage::launch, this));
QObject::connect(ui()->stopPushButton, &QPushButton::clicked, bind(&LauncherOptionPage::stop, this)); QObject::connect(ui()->stopPushButton, &QPushButton::clicked, bind(&LauncherOptionPage::stop, this));
return widget; return widget;
@ -688,13 +698,18 @@ void LauncherOptionPage::reset()
} }
void LauncherOptionPage::handleSyncthingReadyRead() void LauncherOptionPage::handleSyncthingReadyRead()
{
handleSyncthingOutputAvailable(m_process->readAll());
}
void LauncherOptionPage::handleSyncthingOutputAvailable(const QByteArray &output)
{ {
if (!hasBeenShown()) { if (!hasBeenShown()) {
return; return;
} }
QTextCursor cursor = ui()->logTextEdit->textCursor(); QTextCursor cursor(ui()->logTextEdit->textCursor());
cursor.movePosition(QTextCursor::End); cursor.movePosition(QTextCursor::End);
cursor.insertText(QString::fromLocal8Bit(m_process.readAll())); cursor.insertText(QString::fromLocal8Bit(output));
if (ui()->ensureCursorVisibleCheckBox->isChecked()) { if (ui()->ensureCursorVisibleCheckBox->isChecked()) {
ui()->logTextEdit->ensureCursorVisible(); ui()->logTextEdit->ensureCursorVisible();
} }
@ -721,13 +736,18 @@ void LauncherOptionPage::handleSyncthingExited(int exitCode, QProcess::ExitStatu
ui()->launchNowPushButton->show(); ui()->launchNowPushButton->show();
} }
bool LauncherOptionPage::isRunning() const
{
return (m_process && m_process->isRunning()) || (m_launcher && m_launcher->isRunning());
}
void LauncherOptionPage::launch() void LauncherOptionPage::launch()
{ {
if (!hasBeenShown()) { if (!hasBeenShown()) {
return; return;
} }
apply(); apply();
if (m_process.state() != QProcess::NotRunning) { if (isRunning()) {
return; return;
} }
ui()->launchNowPushButton->hide(); ui()->launchNowPushButton->hide();
@ -735,23 +755,34 @@ void LauncherOptionPage::launch()
ui()->stopPushButton->setText(QCoreApplication::translate("QtGui::LauncherOptionPage", "Stop launched instance")); ui()->stopPushButton->setText(QCoreApplication::translate("QtGui::LauncherOptionPage", "Stop launched instance"));
m_kill = false; m_kill = false;
if (m_tool.isEmpty()) { if (m_tool.isEmpty()) {
m_process.startSyncthing(values().launcher.syncthingCmd()); // TODO: allow using libsyncthing
m_launcher->launch(values().launcher.syncthingCmd());
} else { } else {
m_process.startSyncthing(values().launcher.toolCmd(m_tool)); m_process->startSyncthing(values().launcher.toolCmd(m_tool));
} }
} }
void LauncherOptionPage::stop() void LauncherOptionPage::stop()
{ {
if (!hasBeenShown() || m_process.state() == QProcess::NotRunning) { if (!hasBeenShown()) {
return; return;
} }
if (m_kill) { if (m_kill) {
m_process.kill(); if (m_process) {
m_process->killSyncthing();
}
if (m_launcher) {
m_launcher->kill();
}
} else { } else {
ui()->stopPushButton->setText(QCoreApplication::translate("QtGui::LauncherOptionPage", "Kill launched instance")); ui()->stopPushButton->setText(QCoreApplication::translate("QtGui::LauncherOptionPage", "Kill launched instance"));
m_kill = true; m_kill = true;
m_process.terminate(); if (m_process) {
m_process->stopSyncthing();
}
if (m_launcher) {
m_launcher->terminate();
}
} }
} }
@ -759,7 +790,7 @@ void LauncherOptionPage::stop()
#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD #ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
SystemdOptionPage::SystemdOptionPage(QWidget *parentWidget) SystemdOptionPage::SystemdOptionPage(QWidget *parentWidget)
: SystemdOptionPageBase(parentWidget) : SystemdOptionPageBase(parentWidget)
, m_service(syncthingService()) , m_service(SyncthingService::mainInstance())
{ {
} }
@ -770,14 +801,17 @@ SystemdOptionPage::~SystemdOptionPage()
QWidget *SystemdOptionPage::setupWidget() QWidget *SystemdOptionPage::setupWidget()
{ {
auto *const widget = SystemdOptionPageBase::setupWidget(); auto *const widget = SystemdOptionPageBase::setupWidget();
QObject::connect(ui()->syncthingUnitLineEdit, &QLineEdit::textChanged, &m_service, &SyncthingService::setUnitName); if (!m_service) {
QObject::connect(ui()->startPushButton, &QPushButton::clicked, &m_service, &SyncthingService::start); return widget;
QObject::connect(ui()->stopPushButton, &QPushButton::clicked, &m_service, &SyncthingService::stop); }
QObject::connect(ui()->enablePushButton, &QPushButton::clicked, &m_service, &SyncthingService::enable); QObject::connect(ui()->syncthingUnitLineEdit, &QLineEdit::textChanged, m_service, &SyncthingService::setUnitName);
QObject::connect(ui()->disablePushButton, &QPushButton::clicked, &m_service, &SyncthingService::disable); QObject::connect(ui()->startPushButton, &QPushButton::clicked, m_service, &SyncthingService::start);
QObject::connect(&m_service, &SyncthingService::descriptionChanged, bind(&SystemdOptionPage::handleDescriptionChanged, this, _1)); QObject::connect(ui()->stopPushButton, &QPushButton::clicked, m_service, &SyncthingService::stop);
QObject::connect(&m_service, &SyncthingService::stateChanged, bind(&SystemdOptionPage::handleStatusChanged, this, _1, _2, _3)); QObject::connect(ui()->enablePushButton, &QPushButton::clicked, m_service, &SyncthingService::enable);
QObject::connect(&m_service, &SyncthingService::unitFileStateChanged, bind(&SystemdOptionPage::handleEnabledChanged, this, _1)); QObject::connect(ui()->disablePushButton, &QPushButton::clicked, m_service, &SyncthingService::disable);
QObject::connect(m_service, &SyncthingService::descriptionChanged, bind(&SystemdOptionPage::handleDescriptionChanged, this, _1));
QObject::connect(m_service, &SyncthingService::stateChanged, bind(&SystemdOptionPage::handleStatusChanged, this, _1, _2, _3));
QObject::connect(m_service, &SyncthingService::unitFileStateChanged, bind(&SystemdOptionPage::handleEnabledChanged, this, _1));
return widget; return widget;
} }
@ -796,9 +830,12 @@ void SystemdOptionPage::reset()
ui()->syncthingUnitLineEdit->setText(settings.syncthingUnit); ui()->syncthingUnitLineEdit->setText(settings.syncthingUnit);
ui()->showButtonCheckBox->setChecked(settings.showButton); ui()->showButtonCheckBox->setChecked(settings.showButton);
ui()->considerForReconnectCheckBox->setChecked(settings.considerForReconnect); ui()->considerForReconnectCheckBox->setChecked(settings.considerForReconnect);
handleDescriptionChanged(m_service.description()); if (!m_service) {
handleStatusChanged(m_service.activeState(), m_service.subState(), m_service.activeSince()); return;
handleEnabledChanged(m_service.unitFileState()); }
handleDescriptionChanged(m_service->description());
handleStatusChanged(m_service->activeState(), m_service->subState(), m_service->activeSince());
handleEnabledChanged(m_service->unitFileState());
} }
void SystemdOptionPage::handleDescriptionChanged(const QString &description) void SystemdOptionPage::handleDescriptionChanged(const QString &description)
@ -823,7 +860,7 @@ void SystemdOptionPage::handleStatusChanged(const QString &activeState, const QS
status << subState; status << subState;
} }
const bool isRunning = m_service.isRunning(); const bool isRunning = m_service && m_service->isRunning();
QString timeStamp; QString timeStamp;
if (isRunning && !activeSince.isNull()) { if (isRunning && !activeSince.isNull()) {
timeStamp = QLatin1Char('\n') % QCoreApplication::translate("QtGui::SystemdOptionPage", "since ") timeStamp = QLatin1Char('\n') % QCoreApplication::translate("QtGui::SystemdOptionPage", "since ")
@ -841,7 +878,7 @@ void SystemdOptionPage::handleStatusChanged(const QString &activeState, const QS
void SystemdOptionPage::handleEnabledChanged(const QString &unitFileState) void SystemdOptionPage::handleEnabledChanged(const QString &unitFileState)
{ {
const bool isEnabled = m_service.isEnabled(); const bool isEnabled = m_service && m_service->isEnabled();
ui()->unitFileStateValueLabel->setText( ui()->unitFileStateValueLabel->setText(
unitFileState.isEmpty() ? QCoreApplication::translate("QtGui::SystemdOptionPage", "unknown") : unitFileState); unitFileState.isEmpty() ? QCoreApplication::translate("QtGui::SystemdOptionPage", "unknown") : unitFileState);
setIndicatorColor( setIndicatorColor(

View File

@ -20,6 +20,7 @@ namespace Data {
class SyncthingConnection; class SyncthingConnection;
class SyncthingService; class SyncthingService;
class SyncthingProcess; class SyncthingProcess;
class SyncthingLauncher;
} // namespace Data } // namespace Data
namespace QtGui { namespace QtGui {
@ -79,10 +80,13 @@ LauncherOptionPage(const QString &tool, QWidget *parentWidget = nullptr);
private: private:
DECLARE_SETUP_WIDGETS DECLARE_SETUP_WIDGETS
void handleSyncthingReadyRead(); void handleSyncthingReadyRead();
void handleSyncthingOutputAvailable(const QByteArray &output);
void handleSyncthingExited(int exitCode, QProcess::ExitStatus exitStatus); void handleSyncthingExited(int exitCode, QProcess::ExitStatus exitStatus);
bool isRunning() const;
void launch(); void launch();
void stop(); void stop();
Data::SyncthingProcess &m_process; Data::SyncthingProcess *const m_process;
Data::SyncthingLauncher *const m_launcher;
QList<QMetaObject::Connection> m_connections; QList<QMetaObject::Connection> m_connections;
bool m_kill; bool m_kill;
QString m_tool; QString m_tool;
@ -95,7 +99,7 @@ DECLARE_SETUP_WIDGETS
void handleDescriptionChanged(const QString &description); void handleDescriptionChanged(const QString &description);
void handleStatusChanged(const QString &activeState, const QString &subState, ChronoUtilities::DateTime activeSince); void handleStatusChanged(const QString &activeState, const QString &subState, ChronoUtilities::DateTime activeSince);
void handleEnabledChanged(const QString &unitFileState); void handleEnabledChanged(const QString &unitFileState);
Data::SyncthingService &m_service; Data::SyncthingService *const m_service;
END_DECLARE_OPTION_PAGE END_DECLARE_OPTION_PAGE
#endif #endif