From 2112b003470d901c2729eeb81a30be0dbe87b2ef Mon Sep 17 00:00:00 2001 From: Martchus Date: Fri, 12 Jul 2019 20:26:56 +0200 Subject: [PATCH] Allow to show start/stop button for launcher --- plasmoid/lib/syncthingapplet.cpp | 7 +- tray/gui/traywidget.cpp | 145 +++++++++++++++++++------ tray/gui/traywidget.h | 10 +- widgets/misc/syncthinglauncher.cpp | 31 +++++- widgets/misc/syncthinglauncher.h | 6 + widgets/settings/launcheroptionpage.ui | 34 ++---- widgets/settings/settings.cpp | 95 +++++++++++++--- widgets/settings/settings.h | 25 ++++- widgets/settings/settingsdialog.cpp | 58 +++++++--- widgets/settings/settingsdialog.h | 1 + 10 files changed, 314 insertions(+), 98 deletions(-) diff --git a/plasmoid/lib/syncthingapplet.cpp b/plasmoid/lib/syncthingapplet.cpp index 5d66b6c..f3d92eb 100644 --- a/plasmoid/lib/syncthingapplet.cpp +++ b/plasmoid/lib/syncthingapplet.cpp @@ -210,12 +210,13 @@ void SyncthingApplet::setCurrentConnectionConfigIndex(int index) // apply systemd settings, reconnect if required and possible #ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD - settings.systemd.apply(m_connection, currentConnectionConfig(), reconnectRequired); + const auto systemdRelevantForReconnect = settings.systemd.apply(m_connection, currentConnectionConfig(), reconnectRequired).relevant; #else - if (reconnectRequired || !m_connection.isConnected()) { + const auto systemdRelevantForReconnect = false; +#endif + if (!systemdRelevantForReconnect && (reconnectRequired || !m_connection.isConnected())) { m_connection.reconnect(); } -#endif } bool SyncthingApplet::isStartStopEnabled() const diff --git a/tray/gui/traywidget.cpp b/tray/gui/traywidget.cpp index 032fd58..2a41e99 100644 --- a/tray/gui/traywidget.cpp +++ b/tray/gui/traywidget.cpp @@ -4,6 +4,7 @@ #include "../../widgets/misc/otherdialogs.h" #include "../../widgets/misc/textviewdialog.h" +#include "../../widgets/misc/syncthinglauncher.h" #include "../../widgets/settings/settingsdialog.h" #include "../../widgets/webview/webviewdialog.h" @@ -73,6 +74,7 @@ TrayWidget::TrayWidget(TrayMenu *parent) , m_devModel(m_connection) , m_dlModel(m_connection) , m_selectedConnection(nullptr) + , m_startStopButtonTarget(StartStopButtonTarget::None) { m_instances.push_back(this); @@ -134,9 +136,6 @@ TrayWidget::TrayWidget(TrayMenu *parent) m_ui->localTextLabel->setPixmap( QIcon::fromTheme(QStringLiteral("user-home"), QIcon(QStringLiteral(":/icons/hicolor/scalable/places/user-home.svg"))).pixmap(16)); updateTraffic(); -#ifndef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD - delete m_ui->startStopPushButton; -#endif // connect signals and slots connect(m_ui->statusPushButton, &QPushButton::clicked, this, &TrayWidget::changeStatus); @@ -161,9 +160,12 @@ TrayWidget::TrayWidget(TrayMenu *parent) connect(m_connectionsActionGroup, &QActionGroup::triggered, this, &TrayWidget::handleConnectionSelected); connect(m_ui->actionShowNotifications, &QAction::triggered, this, &TrayWidget::showNotifications); connect(m_ui->actionDismissNotifications, &QAction::triggered, this, &TrayWidget::dismissNotifications); + connect(m_ui->startStopPushButton, &QPushButton::clicked, this, &TrayWidget::toggleRunning); + if (const auto *const launcher = SyncthingLauncher::mainInstance()) { + connect(launcher, &SyncthingLauncher::runningChanged, this, &TrayWidget::handleLauncherStatusChanged); + } #ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD if (const auto *const service = SyncthingService::mainInstance()) { - connect(m_ui->startStopPushButton, &QPushButton::clicked, service, &SyncthingService::toggleRunning); connect(service, &SyncthingService::systemdAvailableChanged, this, &TrayWidget::handleSystemdStatusChanged); connect(service, &SyncthingService::stateChanged, this, &TrayWidget::handleSystemdStatusChanged); } @@ -376,15 +378,22 @@ void TrayWidget::applySettings(const QString &connectionConfig) // apply notification settings settings.apply(m_notifier); + // apply systemd and launcher settings enforcing a reconnect if required and possible #ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD - // apply systemd settings, also enforce reconnect if required and possible - applySystemdSettings(reconnectRequired); + const auto systemdStatus = applySystemdSettings(reconnectRequired); + const auto launcherStatus = applyLauncherSettings(reconnectRequired, systemdStatus.relevant, systemdStatus.showStartStopButton); + const auto showStartStopButton = systemdStatus.showStartStopButton || launcherStatus.showStartStopButton; + const auto systemdOrLauncherRelevantForReconnect = systemdStatus.relevant || launcherStatus.relevant; #else - // reconnect if required, not checking whether possible - if (reconnectRequired) { + const auto launcherStatus = applyLauncherSettings(reconnectRequired); + const auto showStartStopButton = launcherStatus.showStartStopButton; + const auto systemdOrLauncherRelevantForReconnect = launcherStatus.relevant; +#endif + m_ui->startStopPushButton->setVisible(showStartStopButton); + if (reconnectRequired && !systemdOrLauncherRelevantForReconnect) { + // simply enforce the reconnect for this connection if the systemd or launcher status are relevant for it m_connection.reconnect(); } -#endif #ifndef SYNCTHINGWIDGETS_NO_WEBVIEW // web view @@ -538,38 +547,112 @@ void TrayWidget::updateOverallStatistics() m_ui->localStatisticsLabel->setText(directoryStatusString(overallStats.local)); } -#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD -bool TrayWidget::handleSystemdStatusChanged() +void TrayWidget::toggleRunning() { - return applySystemdSettings(); + switch(m_startStopButtonTarget) { +#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD + case StartStopButtonTarget::Service: + if (auto *const service = SyncthingService::mainInstance()) { + service->toggleRunning(); + } + break; +#endif + case StartStopButtonTarget::Launcher: + if (auto *const launcher = SyncthingLauncher::mainInstance()) { + if (launcher->isRunning()) { + launcher->terminate(); + } else { + launcher->launch(Settings::values().launcher); + } + } + break; + default: + ; + } } -bool TrayWidget::applySystemdSettings(bool reconnectRequired) +Settings::Launcher::LauncherStatus TrayWidget::handleLauncherStatusChanged() +{ +#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD + const auto systemdStatus = Settings::values().systemd.status(m_connection); + const auto launcherStatus = applyLauncherSettings(false, systemdStatus.relevant, systemdStatus.showStartStopButton); + const auto showStartStopButton = systemdStatus.showStartStopButton || launcherStatus.showStartStopButton; +#else + const auto launcherStatus = applyLauncherSettings(reconnectRequired); + const auto showStartStopButton = launcherStatus.showStartStopButton; +#endif + m_ui->startStopPushButton->setVisible(showStartStopButton); + return launcherStatus; +} + +Settings::Launcher::LauncherStatus TrayWidget::applyLauncherSettings(bool reconnectRequired, bool skipApplyingToConnection, bool skipStartStopButton) { // update connection - const Settings::Systemd &systemdSettings(Settings::values().systemd); - bool isServiceRelevant, isServiceRunning; - tie(isServiceRelevant, isServiceRunning) = systemdSettings.apply(m_connection, m_selectedConnection, reconnectRequired); + const auto &launcherSettings = Settings::values().launcher; + const auto launcherStatus = skipApplyingToConnection + ? launcherSettings.status(m_connection) + : launcherSettings.apply(m_connection, m_selectedConnection, reconnectRequired); + + if (skipStartStopButton || !launcherStatus.showStartStopButton) { + return launcherStatus; + } // update start/stop button - if (isServiceRelevant && systemdSettings.showButton) { + m_startStopButtonTarget = StartStopButtonTarget::Launcher; + if (launcherStatus.running) { + m_ui->startStopPushButton->setText(tr("Stop")); + m_ui->startStopPushButton->setToolTip(tr("Stop Syncthing instance launched via tray icon")); + m_ui->startStopPushButton->setIcon( + QIcon::fromTheme(QStringLiteral("process-stop"), QIcon(QStringLiteral(":/icons/hicolor/scalable/actions/process-stop.svg")))); + } else { + m_ui->startStopPushButton->setText(tr("Start")); + m_ui->startStopPushButton->setToolTip(tr("Launch Syncthing instance via tray icon")); + m_ui->startStopPushButton->setIcon( + QIcon::fromTheme(QStringLiteral("system-run"), QIcon(QStringLiteral(":/icons/hicolor/scalable/apps/system-run.svg")))); + } + return launcherStatus; +} + +#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD +Settings::Systemd::ServiceStatus TrayWidget::handleSystemdStatusChanged() +{ + const auto systemdStatus = applySystemdSettings(); + if (systemdStatus.showStartStopButton) { m_ui->startStopPushButton->setVisible(true); - if (isServiceRunning) { - m_ui->startStopPushButton->setText(tr("Stop")); - m_ui->startStopPushButton->setToolTip(QStringLiteral("systemctl --user stop ") + systemdSettings.syncthingUnit); - m_ui->startStopPushButton->setIcon( - QIcon::fromTheme(QStringLiteral("process-stop"), QIcon(QStringLiteral(":/icons/hicolor/scalable/actions/process-stop.svg")))); - } else { - m_ui->startStopPushButton->setText(tr("Start")); - m_ui->startStopPushButton->setToolTip(QStringLiteral("systemctl --user start ") + systemdSettings.syncthingUnit); - m_ui->startStopPushButton->setIcon( - QIcon::fromTheme(QStringLiteral("system-run"), QIcon(QStringLiteral(":/icons/hicolor/scalable/apps/system-run.svg")))); - } + return systemdStatus; } - if (!systemdSettings.showButton || !isServiceRelevant) { - m_ui->startStopPushButton->setVisible(false); + + // update the start/stop button which might now control the internal launcher + const auto launcherStatus = applyLauncherSettings(false, true, false); + m_ui->startStopPushButton->setVisible(launcherStatus.showStartStopButton); + + return systemdStatus; +} + +Settings::Systemd::ServiceStatus TrayWidget::applySystemdSettings(bool reconnectRequired) +{ + // update connection + const auto &systemdSettings = Settings::values().systemd; + const auto serviceStatus = systemdSettings.apply(m_connection, m_selectedConnection, reconnectRequired); + + if (!serviceStatus.showStartStopButton) { + return serviceStatus; } - return isServiceRelevant && isServiceRunning; + + // update start/stop button + m_startStopButtonTarget = StartStopButtonTarget::Service; + if (serviceStatus.running) { + m_ui->startStopPushButton->setText(tr("Stop")); + m_ui->startStopPushButton->setToolTip(QStringLiteral("systemctl --user stop ") + systemdSettings.syncthingUnit); + m_ui->startStopPushButton->setIcon( + QIcon::fromTheme(QStringLiteral("process-stop"), QIcon(QStringLiteral(":/icons/hicolor/scalable/actions/process-stop.svg")))); + } else { + m_ui->startStopPushButton->setText(tr("Start")); + m_ui->startStopPushButton->setToolTip(QStringLiteral("systemctl --user start ") + systemdSettings.syncthingUnit); + m_ui->startStopPushButton->setIcon( + QIcon::fromTheme(QStringLiteral("system-run"), QIcon(QStringLiteral(":/icons/hicolor/scalable/apps/system-run.svg")))); + } + return serviceStatus; } #endif diff --git a/tray/gui/traywidget.h b/tray/gui/traywidget.h index 78d9b31..1079ec6 100644 --- a/tray/gui/traywidget.h +++ b/tray/gui/traywidget.h @@ -76,9 +76,12 @@ private slots: void changeStatus(); void updateTraffic(); void updateOverallStatistics(); + void toggleRunning(); + Settings::Launcher::LauncherStatus handleLauncherStatusChanged(); + Settings::Launcher::LauncherStatus applyLauncherSettings(bool reconnectRequired = false, bool skipApplyingToConnection = false, bool skipStartStopButton = false); #ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD - bool handleSystemdStatusChanged(); - bool applySystemdSettings(bool reconnectRequired = false); + Settings::Systemd::ServiceStatus handleSystemdStatusChanged(); + Settings::Systemd::ServiceStatus applySystemdSettings(bool reconnectRequired = false); #endif #ifndef SYNCTHINGWIDGETS_NO_WEBVIEW void handleWebViewDeleted(); @@ -106,6 +109,9 @@ private: Data::SyncthingConnectionSettings *m_selectedConnection; QMenu *m_notificationsMenu; std::vector m_notifications; + enum class StartStopButtonTarget { + None, Service, Launcher + } m_startStopButtonTarget; static std::vector m_instances; }; diff --git a/widgets/misc/syncthinglauncher.cpp b/widgets/misc/syncthinglauncher.cpp index 02d1014..39a1dc7 100644 --- a/widgets/misc/syncthinglauncher.cpp +++ b/widgets/misc/syncthinglauncher.cpp @@ -1,5 +1,7 @@ #include "./syncthinglauncher.h" +#include "../settings/settings.h" + #include #include @@ -20,6 +22,7 @@ SyncthingLauncher::SyncthingLauncher(QObject *parent) connect(&m_process, &SyncthingProcess::readyRead, this, &SyncthingLauncher::handleProcessReadyRead); connect(&m_process, static_cast(&SyncthingProcess::finished), this, &SyncthingLauncher::handleProcessFinished); + connect(&m_process, &SyncthingProcess::stateChanged, this, &SyncthingLauncher::handleProcessStateChanged); connect(&m_process, &SyncthingProcess::errorOccurred, this, &SyncthingLauncher::errorOccurred); connect(&m_process, &SyncthingProcess::confirmKill, this, &SyncthingLauncher::confirmKill); } @@ -57,6 +60,15 @@ void SyncthingLauncher::launch(const QString &program, const QStringList &argume } } +void SyncthingLauncher::launch(const Settings::Launcher &launcherSettings) +{ + if (!launcherSettings.useLibSyncthing && launcherSettings.syncthingPath.isEmpty()) { + emit errorOccurred(QProcess::FailedToStart); + return; + } + launch(launcherSettings.useLibSyncthing ? QString() : launcherSettings.syncthingPath, SyncthingProcess::splitArguments(launcherSettings.syncthingArgs)); +} + /*! * \brief Launches a Syncthing instance using the internal library with the specified \a runtimeOptions. */ @@ -102,9 +114,22 @@ void SyncthingLauncher::handleProcessReadyRead() emit outputAvailable(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 runningChanged(false); emit exited(exitCode, exitStatus); } @@ -143,8 +168,10 @@ void SyncthingLauncher::runLibSyncthing(const LibSyncthing::RuntimeOptions &runt { #ifdef SYNCTHINGWIDGETS_USE_LIBSYNCTHING LibSyncthing::setLoggingCallback(bind(&SyncthingLauncher::handleLoggingCallback, this, _1, _2, _3)); + emit runningChanged(true); const auto exitCode = LibSyncthing::runSyncthing(runtimeOptions); emit exited(static_cast(exitCode), exitCode == 0 ? QProcess::NormalExit : QProcess::CrashExit); + emit runningChanged(false); #else CPP_UTILITIES_UNUSED(runtimeOptions) emit outputAvailable("libsyncthing support not enabled"); @@ -156,8 +183,10 @@ void SyncthingLauncher::runLibSyncthing(const std::vector &arguments) { #ifdef SYNCTHINGWIDGETS_USE_LIBSYNCTHING LibSyncthing::setLoggingCallback(bind(&SyncthingLauncher::handleLoggingCallback, this, _1, _2, _3)); + emit runningChanged(true); const auto exitCode = LibSyncthing::runSyncthing(arguments); emit exited(static_cast(exitCode), exitCode == 0 ? QProcess::NormalExit : QProcess::CrashExit); + emit runningChanged(false); #else CPP_UTILITIES_UNUSED(arguments) emit outputAvailable("libsyncthing support not enabled"); diff --git a/widgets/misc/syncthinglauncher.h b/widgets/misc/syncthinglauncher.h index 813fffa..0d457a7 100644 --- a/widgets/misc/syncthinglauncher.h +++ b/widgets/misc/syncthinglauncher.h @@ -12,6 +12,10 @@ namespace LibSyncthing { struct RuntimeOptions; } +namespace Settings { +struct Launcher; +} + namespace Data { class SYNCTHINGWIDGETS_EXPORT SyncthingLauncher : public QObject { @@ -43,12 +47,14 @@ Q_SIGNALS: public Q_SLOTS: void setUseLibSyncthing(bool useLibSyncthing); void launch(const QString &program, const QStringList &arguments); + void launch(const Settings::Launcher &launcherSettings); void launch(const LibSyncthing::RuntimeOptions &runtimeOptions); void terminate(); void kill(); private Q_SLOTS: void handleProcessReadyRead(); + void handleProcessStateChanged(QProcess::ProcessState newState); void handleProcessFinished(int exitCode, QProcess::ExitStatus exitStatus); void handleLoggingCallback(LibSyncthing::LogLevel, const char *message, std::size_t messageSize); void runLibSyncthing(const LibSyncthing::RuntimeOptions &runtimeOptions); diff --git a/widgets/settings/launcheroptionpage.ui b/widgets/settings/launcheroptionpage.ui index 09a3029..f45df4d 100644 --- a/widgets/settings/launcheroptionpage.ui +++ b/widgets/settings/launcheroptionpage.ui @@ -2,14 +2,6 @@ QtGui::LauncherOptionPage - - - 0 - 0 - 507 - 391 - - Syncthing launcher @@ -29,9 +21,6 @@ - - false - 30 @@ -75,6 +64,13 @@ + + + + Show start/stop button on tray for local instance + + + @@ -198,22 +194,6 @@ - - enabledCheckBox - toggled(bool) - launcherFormWidget - setEnabled(bool) - - - 142 - 17 - - - 142 - 65 - - - enabledCheckBox toggled(bool) diff --git a/widgets/settings/settings.cpp b/widgets/settings/settings.cpp index 33b37f5..2780be3 100644 --- a/widgets/settings/settings.cpp +++ b/widgets/settings/settings.cpp @@ -74,9 +74,8 @@ std::vector Launcher::allProcesses() void Launcher::autostart() const { auto *const launcher(SyncthingLauncher::mainInstance()); - // TODO: allow using libsyncthing - if (enabled && !syncthingPath.isEmpty() && launcher) { - launcher->launch(useLibSyncthing ? QString() : syncthingPath, SyncthingProcess::splitArguments(syncthingArgs)); + if (autostartEnabled && launcher) { + launcher->launch(*this); } for (auto i = tools.cbegin(), end = tools.cend(); i != end; ++i) { const ToolParameter &toolParams = i.value(); @@ -95,6 +94,62 @@ void Launcher::terminate() QtGui::SyncthingKiller(allProcesses()).waitForFinished(); } +/*! + * \brief Applies the launcher settings to the specified \a connection considering the status of the main SyncthingLauncher instance. + * \remarks + * - Called by TrayWidget when the launcher settings have been changed. + * - \a currentConnectionSettings might be nullptr. + * - Currently this is only about the auto-reconnect interval and connecting instantly. + * \returns Returns the launcher status with respect to the specified \a connection. + */ +Launcher::LauncherStatus Launcher::apply( + Data::SyncthingConnection &connection, const SyncthingConnectionSettings *currentConnectionSettings, bool reconnectRequired) const +{ + auto *const launcher(SyncthingLauncher::mainInstance()); + if (!launcher) { + return LauncherStatus{}; + } + const auto isRelevant = connection.isLocal(); + const auto isRunning = launcher->isRunning(); + const auto consideredForReconnect = considerForReconnect && isRelevant; + + if (currentConnectionSettings && (!considerForReconnect || !isRelevant || isRunning)) { + // ensure auto-reconnect is configured according to settings + connection.setAutoReconnectInterval(currentConnectionSettings->reconnectInterval); + } else { + // disable auto-reconnect regardless of the overall settings + connection.setAutoReconnectInterval(0); + } + + // connect instantly if service is running + if (consideredForReconnect) { + // give the service (which has just started) a few seconds to initialize + constexpr auto minActiveTimeInSeconds(5); + if (reconnectRequired) { + connection.reconnectLater(minActiveTimeInSeconds * 1000); + } else if (isRunning && !connection.isConnected()) { + connection.connectLater(minActiveTimeInSeconds * 1000); + } + } + + return LauncherStatus{isRelevant, isRunning, consideredForReconnect, showButton && isRelevant}; +} + +/*! + * \brief Returns the launcher status with respect to the specified \a connection. + */ +Launcher::LauncherStatus Launcher::status(SyncthingConnection &connection) const +{ + auto *const launcher(SyncthingLauncher::mainInstance()); + if (!launcher) { + return LauncherStatus{}; + } + const auto isRelevant = connection.isLocal(); + return LauncherStatus{ + isRelevant, launcher->isRunning(), considerForReconnect && isRelevant, showButton && isRelevant + }; +} + Settings &values() { static Settings settings; @@ -181,11 +236,12 @@ void restore() settings.beginGroup(QStringLiteral("startup")); auto &launcher = v.launcher; - launcher.enabled = settings.value(QStringLiteral("syncthingAutostart"), launcher.enabled).toBool(); + launcher.autostartEnabled = settings.value(QStringLiteral("syncthingAutostart"), launcher.autostartEnabled).toBool(); launcher.useLibSyncthing = settings.value(QStringLiteral("useLibSyncthing"), launcher.useLibSyncthing).toBool(); launcher.syncthingPath = settings.value(QStringLiteral("syncthingPath"), launcher.syncthingPath).toString(); launcher.syncthingArgs = settings.value(QStringLiteral("syncthingArgs"), launcher.syncthingArgs).toString(); launcher.considerForReconnect = settings.value(QStringLiteral("considerLauncherForReconnect"), launcher.considerForReconnect).toBool(); + launcher.showButton = settings.value(QStringLiteral("showLauncherButton"), launcher.showButton).toBool(); settings.beginGroup(QStringLiteral("tools")); for (const QString &tool : settings.childGroups()) { settings.beginGroup(tool); @@ -270,11 +326,12 @@ void save() settings.beginGroup(QStringLiteral("startup")); const auto &launcher = v.launcher; - settings.setValue(QStringLiteral("syncthingAutostart"), launcher.enabled); + settings.setValue(QStringLiteral("syncthingAutostart"), launcher.autostartEnabled); settings.setValue(QStringLiteral("useLibSyncthing"), launcher.useLibSyncthing); settings.setValue(QStringLiteral("syncthingPath"), launcher.syncthingPath); settings.setValue(QStringLiteral("syncthingArgs"), launcher.syncthingArgs); settings.setValue(QStringLiteral("considerLauncherForReconnect"), launcher.considerForReconnect); + settings.setValue(QStringLiteral("showLauncherButton"), launcher.showButton); settings.beginGroup(QStringLiteral("tools")); for (auto i = launcher.tools.cbegin(), end = launcher.tools.cend(); i != end; ++i) { const ToolParameter &toolParams = i.value(); @@ -338,20 +395,21 @@ void Settings::apply(SyncthingNotifier ¬ifier) const /*! * \brief Applies the systemd settings to the specified \a connection considering the status of the global SyncthingService instance. * \remarks - * - Called by SyncthingApplet and TrayWidget when the status of the SyncthingService changes. + * - Called by SyncthingApplet and TrayWidget when the status of the SyncthingService changes or the systemd settings have been changed. * - \a currentConnectionSettings might be nullptr. * - Currently this is only about the auto-reconnect interval and connecting instantly. - * \returns Returns whether the service is relevant and running. + * \returns Returns the service status with respect to the specified \a connection. */ -std::tuple Systemd::apply( +Systemd::ServiceStatus Systemd::apply( Data::SyncthingConnection &connection, const SyncthingConnectionSettings *currentConnectionSettings, bool reconnectRequired) const { auto *const service(SyncthingService::mainInstance()); if (!service) { - return make_tuple(false, false); + return ServiceStatus{}; } const auto isRelevant = service->isSystemdAvailable() && connection.isLocal(); const auto isRunning = service->isRunning(); + const auto consideredForReconnect = considerForReconnect && isRelevant; if (currentConnectionSettings && (!considerForReconnect || !isRelevant || isRunning)) { // ensure auto-reconnect is configured according to settings @@ -362,7 +420,7 @@ std::tuple Systemd::apply( } // connect instantly if service is running - if (considerForReconnect && isRelevant) { + if (consideredForReconnect) { constexpr auto minActiveTimeInSeconds(5); if (reconnectRequired) { if (service->isActiveWithoutSleepFor(minActiveTimeInSeconds)) { @@ -379,11 +437,22 @@ std::tuple Systemd::apply( connection.connectLater(minActiveTimeInSeconds * 1000); } } - } else if (reconnectRequired) { - connection.reconnect(); } - return make_tuple(isRelevant, isRunning); + return ServiceStatus{isRelevant, isRunning, consideredForReconnect, showButton && isRelevant}; +} + +/*! + * \brief Returns the service status with respect to the specified \a connection. + */ +Systemd::ServiceStatus Systemd::status(SyncthingConnection &connection) const +{ + auto *const service(SyncthingService::mainInstance()); + if (!service) { + return ServiceStatus{}; + } + const auto isRelevant = service->isSystemdAvailable() && connection.isLocal(); + return ServiceStatus{isRelevant, service->isRunning(), considerForReconnect && isRelevant, showButton && isRelevant}; } #endif diff --git a/widgets/settings/settings.h b/widgets/settings/settings.h index 33c15c1..76a7f90 100644 --- a/widgets/settings/settings.h +++ b/widgets/settings/settings.h @@ -37,6 +37,7 @@ struct SYNCTHINGWIDGETS_EXPORT Connection { struct SYNCTHINGWIDGETS_EXPORT NotifyOn { bool disconnect = true; bool internalErrors = true; + bool launcherErrors = true; bool localSyncComplete = false; bool remoteSyncComplete = false; bool syncthingErrors = true; @@ -59,7 +60,7 @@ struct SYNCTHINGWIDGETS_EXPORT ToolParameter { }; struct SYNCTHINGWIDGETS_EXPORT Launcher { - bool enabled = false; + bool autostartEnabled = false; bool useLibSyncthing = false; QString syncthingPath = #ifdef PLATFORM_WINDOWS @@ -70,10 +71,21 @@ struct SYNCTHINGWIDGETS_EXPORT Launcher { QString syncthingArgs = QStringLiteral("-no-browser -no-restart -logflags=3"); QHash tools; bool considerForReconnect = false; + bool showButton = false; + static Data::SyncthingProcess &toolProcess(const QString &tool); static std::vector allProcesses(); void autostart() const; static void terminate(); + struct LauncherStatus { + bool relevant = false; + bool running = false; + bool consideredForReconnect = false; + bool showStartStopButton = false; + }; + LauncherStatus apply(Data::SyncthingConnection &connection, const Data::SyncthingConnectionSettings *currentConnectionSettings, + bool preventReconnect = false) const; + LauncherStatus status(Data::SyncthingConnection &connection) const; }; #ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD @@ -82,10 +94,15 @@ struct SYNCTHINGWIDGETS_EXPORT Systemd { bool showButton = false; bool considerForReconnect = false; -#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD - std::tuple apply(Data::SyncthingConnection &connection, const Data::SyncthingConnectionSettings *currentConnectionSettings, + struct ServiceStatus { + bool relevant = false; + bool running = false; + bool consideredForReconnect = false; + bool showStartStopButton = false; + }; + ServiceStatus apply(Data::SyncthingConnection &connection, const Data::SyncthingConnectionSettings *currentConnectionSettings, bool preventReconnect = false) const; -#endif + ServiceStatus status(Data::SyncthingConnection &connection) const; }; #endif diff --git a/widgets/settings/settingsdialog.cpp b/widgets/settings/settingsdialog.cpp index 9e20fdb..8938462 100644 --- a/widgets/settings/settingsdialog.cpp +++ b/widgets/settings/settingsdialog.cpp @@ -759,18 +759,20 @@ QWidget *LauncherOptionPage::setupWidget() auto *const widget = LauncherOptionPageBase::setupWidget(); // adjust labels to use name of additional tool instead of "Syncthing" - if (!m_tool.isEmpty()) { + const auto isSyncthing = m_tool.isEmpty(); + if (!isSyncthing) { widget->setWindowTitle(tr("%1-launcher").arg(m_tool)); ui()->enabledCheckBox->setText(tr("Launch %1 when starting the tray icon").arg(m_tool)); ui()->syncthingPathLabel->setText(tr("%1 executable").arg(m_tool)); ui()->logLabel->setText(tr("%1 log (interleaved stdout/stderr)").arg(m_tool)); + + // hide "consider for reconnect" and "show start/stop button on tray" checkboxes for tools + ui()->considerForReconnectCheckBox->setVisible(false); + ui()->showButtonCheckBox->setVisible(false); } - // hide "consider for reconnect" checkbox for tools - ui()->considerForReconnectCheckBox->setVisible(m_tool.isEmpty()); - - // add "restore to defaults" action for arguments - if (m_tool.isEmpty()) { + // add "restore to defaults" action for Syncthing arguments + if (isSyncthing) { m_restoreArgsButton = new IconButton(ui()->argumentsLineEdit); m_restoreArgsButton->setPixmap( QIcon::fromTheme(QStringLiteral("edit-undo"), QIcon(QStringLiteral(":/icons/hicolor/scalable/actions/edit-paste.svg"))).pixmap(16)); @@ -794,6 +796,7 @@ QWidget *LauncherOptionPage::setupWidget() &LauncherOptionPage::handleSyncthingExited, Qt::QueuedConnection); connect(m_process, &SyncthingProcess::errorOccurred, this, &LauncherOptionPage::handleSyncthingError, Qt::QueuedConnection); } else if (m_launcher) { + connect(m_launcher, &SyncthingLauncher::runningChanged, this, &LauncherOptionPage::handleSyncthingLaunched); connect(m_launcher, &SyncthingLauncher::outputAvailable, this, &LauncherOptionPage::handleSyncthingOutputAvailable, Qt::QueuedConnection); connect(m_launcher, &SyncthingLauncher::exited, this, &LauncherOptionPage::handleSyncthingExited, Qt::QueuedConnection); connect(m_launcher, &SyncthingLauncher::errorOccurred, this, &LauncherOptionPage::handleSyncthingError, Qt::QueuedConnection); @@ -808,11 +811,12 @@ bool LauncherOptionPage::apply() { auto &settings = values().launcher; if (m_tool.isEmpty()) { - settings.enabled = ui()->enabledCheckBox->isChecked(); + settings.autostartEnabled = ui()->enabledCheckBox->isChecked(); settings.useLibSyncthing = ui()->useBuiltInVersionCheckBox->isChecked(); settings.syncthingPath = ui()->syncthingPathSelection->lineEdit()->text(); settings.syncthingArgs = ui()->argumentsLineEdit->text(); settings.considerForReconnect = ui()->considerForReconnectCheckBox->isChecked(); + settings.showButton = ui()->showButtonCheckBox->isChecked(); } else { ToolParameter ¶ms = settings.tools[m_tool]; params.autostart = ui()->enabledCheckBox->isChecked(); @@ -826,12 +830,13 @@ void LauncherOptionPage::reset() { const auto &settings = values().launcher; if (m_tool.isEmpty()) { - ui()->enabledCheckBox->setChecked(settings.enabled); + ui()->enabledCheckBox->setChecked(settings.autostartEnabled); ui()->useBuiltInVersionCheckBox->setChecked(settings.useLibSyncthing); ui()->useBuiltInVersionCheckBox->setVisible(settings.useLibSyncthing || SyncthingLauncher::isLibSyncthingAvailable()); ui()->syncthingPathSelection->lineEdit()->setText(settings.syncthingPath); ui()->argumentsLineEdit->setText(settings.syncthingArgs); ui()->considerForReconnectCheckBox->setChecked(settings.considerForReconnect); + ui()->showButtonCheckBox->setChecked(settings.showButton); } else { const ToolParameter params = settings.tools.value(m_tool); ui()->useBuiltInVersionCheckBox->setChecked(false); @@ -842,6 +847,17 @@ void LauncherOptionPage::reset() } } +void LauncherOptionPage::handleSyncthingLaunched(bool running) +{ + if (!running) { + return; // Syncthing being stopped is handled elsewhere + } + ui()->launchNowPushButton->hide(); + ui()->stopPushButton->show(); + ui()->stopPushButton->setText(tr("Stop launched instance")); + m_kill = false; +} + void LauncherOptionPage::handleSyncthingReadyRead() { handleSyncthingOutputAvailable(m_process->readAll()); @@ -943,10 +959,6 @@ void LauncherOptionPage::launch() if (isRunning()) { return; } - ui()->launchNowPushButton->hide(); - ui()->stopPushButton->show(); - ui()->stopPushButton->setText(tr("Stop launched instance")); - m_kill = false; const auto launcherSettings(values().launcher); if (m_tool.isEmpty()) { m_launcher->launch(launcherSettings.useLibSyncthing ? QString() : launcherSettings.syncthingPath, @@ -954,6 +966,7 @@ void LauncherOptionPage::launch() } else { const auto toolParams(launcherSettings.tools.value(m_tool)); m_process->startSyncthing(toolParams.path, SyncthingProcess::splitArguments(toolParams.args)); + handleSyncthingLaunched(true); } } @@ -1018,11 +1031,22 @@ QWidget *SystemdOptionPage::setupWidget() bool SystemdOptionPage::apply() { - auto &settings = values().systemd; - settings.syncthingUnit = ui()->syncthingUnitLineEdit->text(); - settings.showButton = ui()->showButtonCheckBox->isChecked(); - settings.considerForReconnect = ui()->considerForReconnectCheckBox->isChecked(); - return true; + auto &settings = values(); + auto &systemdSettings = settings.systemd; + auto &launcherSettings = settings.launcher; + systemdSettings.syncthingUnit = ui()->syncthingUnitLineEdit->text(); + systemdSettings.showButton = ui()->showButtonCheckBox->isChecked(); + systemdSettings.considerForReconnect = ui()->considerForReconnectCheckBox->isChecked(); + auto result = true; + if (systemdSettings.showButton && launcherSettings.showButton) { + errors().append(QCoreApplication::translate("QtGui::SystemdOptionPage", "It is not possible to show the start/stop button for the systemd service and the internal launcher at the same time. The systemd service precedes.")); + result = false; + } + if (systemdSettings.considerForReconnect && launcherSettings.considerForReconnect) { + errors().append(QCoreApplication::translate("QtGui::SystemdOptionPage", "It is not possible to consider the systemd service and the internal launcher for reconnects at the same time. The systemd service precedes.")); + result = false; + } + return result; } void SystemdOptionPage::reset() diff --git a/widgets/settings/settingsdialog.h b/widgets/settings/settingsdialog.h index ffe3188..1021a82 100644 --- a/widgets/settings/settingsdialog.h +++ b/widgets/settings/settingsdialog.h @@ -106,6 +106,7 @@ public: void reset() override; private slots: + void handleSyncthingLaunched(bool running); void handleSyncthingReadyRead(); void handleSyncthingOutputAvailable(const QByteArray &output); void handleSyncthingExited(int exitCode, QProcess::ExitStatus exitStatus);