From 0ceb8d5e79b32059173a1dbbe3c46ff90047338c Mon Sep 17 00:00:00 2001 From: Martchus Date: Wed, 11 Apr 2018 23:15:15 +0200 Subject: [PATCH] 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. --- connector/syncthingnotifier.cpp | 18 ++--- connector/syncthingnotifier.h | 4 +- connector/syncthingprocess.cpp | 8 +-- connector/syncthingprocess.h | 13 +++- connector/syncthingservice.cpp | 7 +- connector/syncthingservice.h | 13 +++- plasmoid/lib/syncthingapplet.cpp | 11 +-- plasmoid/lib/syncthingapplet.h | 5 +- tray/application/main.cpp | 6 +- tray/gui/traywidget.cpp | 41 ++++++------ widgets/CMakeLists.txt | 13 ++++ widgets/misc/internalerror.cpp | 19 +++--- widgets/misc/syncthinglauncher.cpp | 100 ++++++++++++++++++++++++++++ widgets/misc/syncthinglauncher.h | 100 ++++++++++++++++++++++++++++ widgets/settings/settings.cpp | 28 +++++--- widgets/settings/settingsdialog.cpp | 95 ++++++++++++++++++-------- widgets/settings/settingsdialog.h | 8 ++- 17 files changed, 388 insertions(+), 101 deletions(-) create mode 100644 widgets/misc/syncthinglauncher.cpp create mode 100644 widgets/misc/syncthinglauncher.h diff --git a/connector/syncthingnotifier.cpp b/connector/syncthingnotifier.cpp index af17325..c233048 100644 --- a/connector/syncthingnotifier.cpp +++ b/connector/syncthingnotifier.cpp @@ -29,9 +29,9 @@ SyncthingNotifier::SyncthingNotifier(const SyncthingConnection &connection, QObj : QObject(parent) , m_connection(connection) #ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD - , m_service(syncthingService()) + , m_service(SyncthingService::mainInstance()) #endif - , m_process(syncthingProcess()) + , m_process(SyncthingProcess::mainInstance()) , m_enabledNotifications(SyncthingHighLevelNotification::None) , m_previousStatus(SyncthingStatus::Disconnected) , m_ignoreInavailabilityAfterStart(15) @@ -74,22 +74,22 @@ bool SyncthingNotifier::isDisconnectRelevant() const } // consider process/launcher or systemd unit status - if (m_process.isManuallyStopped()) { + if (m_process && m_process->isManuallyStopped()) { return false; } #ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD - const SyncthingService &service(syncthingService()); - if (m_service.isManuallyStopped()) { + if (m_service && m_service->isManuallyStopped()) { return false; } #endif // ignore inavailability after start or standby-wakeup if (m_ignoreInavailabilityAfterStart) { - if (m_process.isRunning() + if ((m_process && m_process->isRunning()) #ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD - && ((m_service.isSystemdAvailable() && !service.isActiveWithoutSleepFor(m_process.activeSince(), m_ignoreInavailabilityAfterStart)) - || !m_process.isActiveFor(m_ignoreInavailabilityAfterStart)) + && ((m_service && m_service->isSystemdAvailable() + && !m_service->isActiveWithoutSleepFor(m_process->activeSince(), m_ignoreInavailabilityAfterStart)) + || !m_process->isActiveFor(m_ignoreInavailabilityAfterStart)) #else && !m_process.isActiveFor(m_ignoreInavailabilityAfterStart) #endif @@ -97,7 +97,7 @@ bool SyncthingNotifier::isDisconnectRelevant() const return false; } #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; } #endif diff --git a/connector/syncthingnotifier.h b/connector/syncthingnotifier.h index 5e6ed56..0654950 100644 --- a/connector/syncthingnotifier.h +++ b/connector/syncthingnotifier.h @@ -81,9 +81,9 @@ private: const SyncthingConnection &m_connection; #ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD - const SyncthingService &m_service; + const SyncthingService *const m_service; #endif - const SyncthingProcess &m_process; + const SyncthingProcess *const m_process; SyncthingHighLevelNotification m_enabledNotifications; SyncthingStatus m_previousStatus; unsigned int m_ignoreInavailabilityAfterStart; diff --git a/connector/syncthingprocess.cpp b/connector/syncthingprocess.cpp index bc07c7d..bfe76e7 100644 --- a/connector/syncthingprocess.cpp +++ b/connector/syncthingprocess.cpp @@ -6,6 +6,8 @@ using namespace ChronoUtilities; namespace Data { +SyncthingProcess *SyncthingProcess::s_mainInstance = nullptr; + SyncthingProcess::SyncthingProcess(QObject *parent) : QProcess(parent) , m_manuallyStopped(false) @@ -89,10 +91,4 @@ void SyncthingProcess::killToRestart() } } -SyncthingProcess &syncthingProcess() -{ - static SyncthingProcess process; - return process; -} - } // namespace Data diff --git a/connector/syncthingprocess.h b/connector/syncthingprocess.h index d2c93ea..a0c7813 100644 --- a/connector/syncthingprocess.h +++ b/connector/syncthingprocess.h @@ -22,6 +22,8 @@ public: ChronoUtilities::DateTime activeSince() const; bool isActiveFor(unsigned int atLeastSeconds) const; bool isManuallyStopped() const; + static SyncthingProcess *mainInstance(); + static void setMainInstance(SyncthingProcess *mainInstance); Q_SIGNALS: void confirmKill(); @@ -42,6 +44,7 @@ private: ChronoUtilities::DateTime m_activeSince; QTimer m_killTimer; bool m_manuallyStopped; + static SyncthingProcess *s_mainInstance; }; inline bool SyncthingProcess::isRunning() const @@ -64,7 +67,15 @@ inline bool SyncthingProcess::isManuallyStopped() const 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 diff --git a/connector/syncthingservice.cpp b/connector/syncthingservice.cpp index 944a90e..614952f 100644 --- a/connector/syncthingservice.cpp +++ b/connector/syncthingservice.cpp @@ -45,6 +45,7 @@ constexpr DateTime dateTimeFromSystemdTimeStamp(qulonglong timeStamp) return DateTime(DateTime::unixEpochStart().totalTicks() + timeStamp * 10); } +SyncthingService *SyncthingService::s_mainInstance = nullptr; OrgFreedesktopSystemd1ManagerInterface *SyncthingService::s_manager = nullptr; OrgFreedesktopLogin1ManagerInterface *SyncthingService::s_loginManager = nullptr; DateTime SyncthingService::s_lastWakeUp = DateTime(); @@ -324,12 +325,6 @@ void SyncthingService::setProperties( } } -SyncthingService &syncthingService() -{ - static SyncthingService service; - return service; -} - } // namespace Data #endif diff --git a/connector/syncthingservice.h b/connector/syncthingservice.h index fae4da3..b5a3915 100644 --- a/connector/syncthingservice.h +++ b/connector/syncthingservice.h @@ -63,6 +63,8 @@ public: bool isRunning() const; bool isEnabled() const; bool isManuallyStopped() const; + static SyncthingService *mainInstance(); + static void setMainInstance(SyncthingService *mainInstance); public Q_SLOTS: void setUnitName(const QString &unitName); @@ -110,6 +112,7 @@ private: static OrgFreedesktopLogin1ManagerInterface *s_loginManager; static bool s_fallingAsleep; static ChronoUtilities::DateTime s_lastWakeUp; + static SyncthingService *s_mainInstance; QString m_unitName; QDBusServiceWatcher *m_serviceWatcher; OrgFreedesktopSystemd1UnitInterface *m_unit; @@ -209,7 +212,15 @@ inline void SyncthingService::disable() setEnabled(false); } -SyncthingService &syncthingService(); +inline SyncthingService *SyncthingService::mainInstance() +{ + return s_mainInstance; +} + +inline void SyncthingService::setMainInstance(SyncthingService *mainInstance) +{ + s_mainInstance = mainInstance; +} } // namespace Data diff --git a/plasmoid/lib/syncthingapplet.cpp b/plasmoid/lib/syncthingapplet.cpp index 64e8a1c..82abece 100644 --- a/plasmoid/lib/syncthingapplet.cpp +++ b/plasmoid/lib/syncthingapplet.cpp @@ -67,6 +67,9 @@ SyncthingApplet::~SyncthingApplet() #ifndef SYNCTHINGWIDGETS_NO_WEBVIEW delete m_webViewDlg; #endif +#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD + SyncthingService::setMainInstance(nullptr); +#endif } void SyncthingApplet::init() @@ -99,10 +102,10 @@ void SyncthingApplet::init() // initialize systemd service support #ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD - SyncthingService &service = syncthingService(); - service.setUnitName(Settings::values().systemd.syncthingUnit); - connect(&service, &SyncthingService::systemdAvailableChanged, this, &SyncthingApplet::handleSystemdStatusChanged); - connect(&service, &SyncthingService::errorOccurred, this, &SyncthingApplet::handleSystemdServiceError); + SyncthingService::setMainInstance(&m_service); + m_service.setUnitName(Settings::values().systemd.syncthingUnit); + connect(&m_service, &SyncthingService::systemdAvailableChanged, this, &SyncthingApplet::handleSystemdStatusChanged); + connect(&m_service, &SyncthingService::errorOccurred, this, &SyncthingApplet::handleSystemdServiceError); #endif m_initialized = true; diff --git a/plasmoid/lib/syncthingapplet.h b/plasmoid/lib/syncthingapplet.h index d853a6d..42b99ad 100644 --- a/plasmoid/lib/syncthingapplet.h +++ b/plasmoid/lib/syncthingapplet.h @@ -141,6 +141,9 @@ private: Dialogs::AboutDialog *m_aboutDlg; Data::SyncthingConnection m_connection; Data::SyncthingNotifier m_notifier; +#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD + Data::SyncthingService m_service; +#endif QtGui::StatusInfo m_statusInfo; Data::SyncthingDirectoryModel m_dirModel; Data::SyncthingDeviceModel m_devModel; @@ -179,7 +182,7 @@ inline Data::SyncthingDownloadModel *SyncthingApplet::downloadModel() const inline Data::SyncthingService *SyncthingApplet::service() const { #ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD - return &Data::syncthingService(); + return const_cast(&m_service); #else return nullptr; #endif diff --git a/tray/application/main.cpp b/tray/application/main.cpp index 45a387d..021d84d 100644 --- a/tray/application/main.cpp +++ b/tray/application/main.cpp @@ -3,6 +3,7 @@ #include "../gui/trayicon.h" #include "../gui/traywidget.h" +#include "../../widgets/misc/syncthinglauncher.h" #include "../../widgets/settings/settings.h" #include "../../connector/syncthingprocess.h" @@ -161,8 +162,11 @@ int runApplication(int argc, const char *const *argv) Settings::values().qt.apply(); qtConfigArgs.applySettings(true); LOAD_QT_TRANSLATIONS; + SyncthingLauncher launcher; + SyncthingLauncher::setMainInstance(&launcher); #ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD - SyncthingService &service = syncthingService(); + SyncthingService service; + SyncthingService::setMainInstance(&service); service.setUnitName(Settings::values().systemd.syncthingUnit); QObject::connect(&service, &SyncthingService::errorOccurred, &handleSystemdServiceError); #endif diff --git a/tray/gui/traywidget.cpp b/tray/gui/traywidget.cpp index 6f4b85c..4813c4e 100644 --- a/tray/gui/traywidget.cpp +++ b/tray/gui/traywidget.cpp @@ -157,10 +157,11 @@ TrayWidget::TrayWidget(const QString &connectionConfig, TrayMenu *parent) connect(m_ui->actionShowNotifications, &QAction::triggered, this, &TrayWidget::showNotifications); connect(m_ui->actionDismissNotifications, &QAction::triggered, this, &TrayWidget::dismissNotifications); #ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD - const SyncthingService &service = syncthingService(); - connect(m_ui->startStopPushButton, &QPushButton::clicked, &service, &SyncthingService::toggleRunning); - connect(&service, &SyncthingService::systemdAvailableChanged, this, &TrayWidget::handleSystemdStatusChanged); - connect(&service, &SyncthingService::stateChanged, this, &TrayWidget::handleSystemdStatusChanged); + 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); + } #endif } @@ -508,22 +509,19 @@ bool TrayWidget::applySystemdSettings(bool reconnectRequired) bool isServiceRelevant, isServiceRunning; tie(isServiceRelevant, isServiceRunning) = systemdSettings.apply(m_connection, m_selectedConnection, reconnectRequired); - if (isServiceRelevant) { - // update start/stop button - if (systemdSettings.showButton) { - m_ui->startStopPushButton->setVisible(true); - const auto &unitName(syncthingService().unitName()); - if (isServiceRunning) { - m_ui->startStopPushButton->setText(tr("Stop")); - m_ui->startStopPushButton->setToolTip(QStringLiteral("systemctl --user stop ") + unitName); - 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 ") + unitName); - m_ui->startStopPushButton->setIcon( - QIcon::fromTheme(QStringLiteral("system-run"), QIcon(QStringLiteral(":/icons/hicolor/scalable/apps/system-run.svg")))); - } + // update start/stop button + if (isServiceRelevant && systemdSettings.showButton) { + 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")))); } } if (!systemdSettings.showButton || !isServiceRelevant) { @@ -534,7 +532,8 @@ bool TrayWidget::applySystemdSettings(bool reconnectRequired) 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(); } } diff --git a/widgets/CMakeLists.txt b/widgets/CMakeLists.txt index 0303402..bb2f5b0 100644 --- a/widgets/CMakeLists.txt +++ b/widgets/CMakeLists.txt @@ -21,6 +21,7 @@ set(WIDGETS_HEADER_FILES misc/dbusstatusnotifier.h misc/internalerror.h misc/otherdialogs.h + misc/syncthinglauncher.h misc/syncthingkiller.h ) set(WIDGETS_SRC_FILES @@ -36,6 +37,7 @@ set(WIDGETS_SRC_FILES misc/dbusstatusnotifier.cpp misc/internalerror.cpp misc/otherdialogs.cpp + misc/syncthinglauncher.cpp misc/syncthingkiller.cpp ) set(RES_FILES @@ -88,6 +90,17 @@ use_syncthingconnector() find_package(syncthingmodel ${META_APP_VERSION} REQUIRED) 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 list(APPEND ADDITIONAL_QT_MODULES Network) diff --git a/widgets/misc/internalerror.cpp b/widgets/misc/internalerror.cpp index da45150..505274f 100644 --- a/widgets/misc/internalerror.cpp +++ b/widgets/misc/internalerror.cpp @@ -1,9 +1,9 @@ #include "./internalerror.h" +#include "./syncthinglauncher.h" #include "../settings/settings.h" #include "../../connector/syncthingconnection.h" -#include "../../connector/syncthingprocess.h" #include "../../connector/syncthingservice.h" #include @@ -37,23 +37,24 @@ bool InternalError::isRelevant(const SyncthingConnection &connection, SyncthingE // consider process/launcher or systemd unit status const auto remoteHostClosed(networkError == QNetworkReply::RemoteHostClosedError); // ignore "remote host closed" error if we've just stopped Syncthing ourselves - const SyncthingProcess &process(syncthingProcess()); - if (settings.launcher.considerForReconnect && remoteHostClosed && process.isManuallyStopped()) { + const auto *launcher(SyncthingLauncher::mainInstance()); + if (settings.launcher.considerForReconnect && remoteHostClosed && launcher && launcher->isManuallyStopped()) { return false; } #ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD - const SyncthingService &service(syncthingService()); - if (settings.systemd.considerForReconnect && remoteHostClosed && service.isManuallyStopped()) { + const auto *const service(SyncthingService::mainInstance()); + if (settings.systemd.considerForReconnect && remoteHostClosed && service && service->isManuallyStopped()) { return false; } #endif // ignore inavailability after start or standby-wakeup if (settings.ignoreInavailabilityAfterStart && networkError == QNetworkReply::ConnectionRefusedError) { - if (process.isRunning() + if ((launcher && launcher->isRunning()) #ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD - && ((service.isSystemdAvailable() && !service.isActiveWithoutSleepFor(process.activeSince(), settings.ignoreInavailabilityAfterStart)) - || !process.isActiveFor(settings.ignoreInavailabilityAfterStart)) + && ((service && service->isSystemdAvailable() + && !service->isActiveWithoutSleepFor(launcher->activeSince(), settings.ignoreInavailabilityAfterStart)) + || !launcher->isActiveFor(settings.ignoreInavailabilityAfterStart)) #else && !process.isActiveFor(settings.ignoreInavailabilityAfterStart) #endif @@ -61,7 +62,7 @@ bool InternalError::isRelevant(const SyncthingConnection &connection, SyncthingE return false; } #ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD - if (service.isRunning() && !service.isActiveWithoutSleepFor(settings.ignoreInavailabilityAfterStart)) { + if (service && !service->isActiveWithoutSleepFor(settings.ignoreInavailabilityAfterStart)) { return false; } #endif diff --git a/widgets/misc/syncthinglauncher.cpp b/widgets/misc/syncthinglauncher.cpp new file mode 100644 index 0000000..00c0fd6 --- /dev/null +++ b/widgets/misc/syncthinglauncher.cpp @@ -0,0 +1,100 @@ +#include "./syncthinglauncher.h" + +#ifdef SYNCTHING_WIDGETS_USE_LIBSYNCTHING +#include "../../libsyncthing/interface.h" +#include +#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(&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 diff --git a/widgets/misc/syncthinglauncher.h b/widgets/misc/syncthinglauncher.h new file mode 100644 index 0000000..35437c0 --- /dev/null +++ b/widgets/misc/syncthinglauncher.h @@ -0,0 +1,100 @@ +#ifndef SYNCTHINGWIDGETS_SYNCTHINGLAUNCHER_H +#define SYNCTHINGWIDGETS_SYNCTHINGLAUNCHER_H + +#include "../global.h" + +#include "../../connector/syncthingprocess.h" + +#include + +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 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 diff --git a/widgets/settings/settings.cpp b/widgets/settings/settings.cpp index 27559f6..494efa1 100644 --- a/widgets/settings/settings.cpp +++ b/widgets/settings/settings.cpp @@ -1,7 +1,10 @@ #include "./settings.h" + +#include "../misc/syncthingkiller.h" +#include "../misc/syncthinglauncher.h" + #include "../../connector/syncthingnotifier.h" #include "../../connector/syncthingprocess.h" -#include "../misc/syncthingkiller.h" #ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD #include "../../connector/syncthingconnection.h" #include "../../connector/syncthingservice.h" @@ -72,7 +75,9 @@ std::vector Launcher::allProcesses() { vector processes; processes.reserve(1 + toolProcesses.size()); - processes.push_back(&syncthingProcess()); + if (auto *const syncthingProcess = SyncthingProcess::mainInstance()) { + processes.push_back(syncthingProcess); + } for (auto &process : toolProcesses) { processes.push_back(&process.second); } @@ -84,8 +89,10 @@ std::vector Launcher::allProcesses() */ void Launcher::autostart() const { - if (enabled && !syncthingPath.isEmpty()) { - syncthingProcess().startSyncthing(syncthingCmd()); + auto *const launcher(SyncthingLauncher::mainInstance()); + // TODO: allow using libsyncthing + if (enabled && !syncthingPath.isEmpty() && launcher) { + launcher->launch(syncthingCmd()); } for (auto i = tools.cbegin(), end = tools.cend(); i != end; ++i) { const ToolParameter &toolParams = i.value(); @@ -338,9 +345,12 @@ void Settings::apply(SyncthingNotifier ¬ifier) const std::tuple Systemd::apply( Data::SyncthingConnection &connection, const SyncthingConnectionSettings *currentConnectionSettings, bool reconnectRequired) const { - const SyncthingService &service(syncthingService()); - const auto isRelevant = service.isSystemdAvailable() && connection.isLocal(); - const auto isRunning = service.isRunning(); + auto *const service(SyncthingService::mainInstance()); + if (!service) { + return make_tuple(false, false); + } + const auto isRelevant = service->isSystemdAvailable() && connection.isLocal(); + const auto isRunning = service->isRunning(); if (currentConnectionSettings && (!considerForReconnect || !isRelevant || isRunning)) { // ensure auto-reconnect is configured according to settings @@ -354,14 +364,14 @@ std::tuple Systemd::apply( if (considerForReconnect && isRelevant) { constexpr auto minActiveTimeInSeconds(5); if (reconnectRequired) { - if (service.isActiveWithoutSleepFor(minActiveTimeInSeconds)) { + if (service->isActiveWithoutSleepFor(minActiveTimeInSeconds)) { connection.reconnect(); } else { // give the service (which has just started) a few seconds to initialize connection.reconnectLater(minActiveTimeInSeconds * 1000); } } else if (isRunning && !connection.isConnected()) { - if (service.isActiveWithoutSleepFor(minActiveTimeInSeconds)) { + if (service->isActiveWithoutSleepFor(minActiveTimeInSeconds)) { connection.connect(); } else { // give the service (which has just started) a few seconds to initialize diff --git a/widgets/settings/settingsdialog.cpp b/widgets/settings/settingsdialog.cpp index 6c40ea5..c704c24 100644 --- a/widgets/settings/settingsdialog.cpp +++ b/widgets/settings/settingsdialog.cpp @@ -1,5 +1,7 @@ #include "./settingsdialog.h" +#include "../misc/syncthinglauncher.h" + #include "../../connector/syncthingconfig.h" #include "../../connector/syncthingconnection.h" #include "../../connector/syncthingprocess.h" @@ -606,14 +608,16 @@ void AutostartOptionPage::reset() // LauncherOptionPage LauncherOptionPage::LauncherOptionPage(QWidget *parentWidget) : LauncherOptionPageBase(parentWidget) - , m_process(syncthingProcess()) + , m_process(nullptr) + , m_launcher(SyncthingLauncher::mainInstance()) , m_kill(false) { } LauncherOptionPage::LauncherOptionPage(const QString &tool, QWidget *parentWidget) : LauncherOptionPageBase(parentWidget) - , m_process(Launcher::toolProcess(tool)) + , m_process(&Launcher::toolProcess(tool)) + , m_launcher(nullptr) , m_kill(false) , m_tool(tool) { @@ -641,14 +645,20 @@ QWidget *LauncherOptionPage::setupWidget() // setup other widgets ui()->syncthingPathSelection->provideCustomFileMode(QFileDialog::ExistingFile); ui()->logTextEdit->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); - const bool running = m_process.state() != QProcess::NotRunning; + const auto running(isRunning()); ui()->launchNowPushButton->setHidden(running); ui()->stopPushButton->setHidden(!running); // connect signals & slots - m_connections << QObject::connect(&m_process, &SyncthingProcess::readyRead, bind(&LauncherOptionPage::handleSyncthingReadyRead, this)); - m_connections << QObject::connect(&m_process, - static_cast(&SyncthingProcess::finished), - bind(&LauncherOptionPage::handleSyncthingExited, this, _1, _2)); + if (m_process) { + m_connections << QObject::connect(m_process, &SyncthingProcess::readyRead, bind(&LauncherOptionPage::handleSyncthingReadyRead, this)); + m_connections << QObject::connect(m_process, + static_cast(&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()->stopPushButton, &QPushButton::clicked, bind(&LauncherOptionPage::stop, this)); return widget; @@ -688,13 +698,18 @@ void LauncherOptionPage::reset() } void LauncherOptionPage::handleSyncthingReadyRead() +{ + handleSyncthingOutputAvailable(m_process->readAll()); +} + +void LauncherOptionPage::handleSyncthingOutputAvailable(const QByteArray &output) { if (!hasBeenShown()) { return; } - QTextCursor cursor = ui()->logTextEdit->textCursor(); + QTextCursor cursor(ui()->logTextEdit->textCursor()); cursor.movePosition(QTextCursor::End); - cursor.insertText(QString::fromLocal8Bit(m_process.readAll())); + cursor.insertText(QString::fromLocal8Bit(output)); if (ui()->ensureCursorVisibleCheckBox->isChecked()) { ui()->logTextEdit->ensureCursorVisible(); } @@ -721,13 +736,18 @@ void LauncherOptionPage::handleSyncthingExited(int exitCode, QProcess::ExitStatu ui()->launchNowPushButton->show(); } +bool LauncherOptionPage::isRunning() const +{ + return (m_process && m_process->isRunning()) || (m_launcher && m_launcher->isRunning()); +} + void LauncherOptionPage::launch() { if (!hasBeenShown()) { return; } apply(); - if (m_process.state() != QProcess::NotRunning) { + if (isRunning()) { return; } ui()->launchNowPushButton->hide(); @@ -735,23 +755,34 @@ void LauncherOptionPage::launch() ui()->stopPushButton->setText(QCoreApplication::translate("QtGui::LauncherOptionPage", "Stop launched instance")); m_kill = false; if (m_tool.isEmpty()) { - m_process.startSyncthing(values().launcher.syncthingCmd()); + // TODO: allow using libsyncthing + m_launcher->launch(values().launcher.syncthingCmd()); } else { - m_process.startSyncthing(values().launcher.toolCmd(m_tool)); + m_process->startSyncthing(values().launcher.toolCmd(m_tool)); } } void LauncherOptionPage::stop() { - if (!hasBeenShown() || m_process.state() == QProcess::NotRunning) { + if (!hasBeenShown()) { return; } if (m_kill) { - m_process.kill(); + if (m_process) { + m_process->killSyncthing(); + } + if (m_launcher) { + m_launcher->kill(); + } } else { ui()->stopPushButton->setText(QCoreApplication::translate("QtGui::LauncherOptionPage", "Kill launched instance")); 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 SystemdOptionPage::SystemdOptionPage(QWidget *parentWidget) : SystemdOptionPageBase(parentWidget) - , m_service(syncthingService()) + , m_service(SyncthingService::mainInstance()) { } @@ -770,14 +801,17 @@ SystemdOptionPage::~SystemdOptionPage() QWidget *SystemdOptionPage::setupWidget() { auto *const widget = SystemdOptionPageBase::setupWidget(); - QObject::connect(ui()->syncthingUnitLineEdit, &QLineEdit::textChanged, &m_service, &SyncthingService::setUnitName); - QObject::connect(ui()->startPushButton, &QPushButton::clicked, &m_service, &SyncthingService::start); - QObject::connect(ui()->stopPushButton, &QPushButton::clicked, &m_service, &SyncthingService::stop); - QObject::connect(ui()->enablePushButton, &QPushButton::clicked, &m_service, &SyncthingService::enable); - 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)); + if (!m_service) { + return widget; + } + QObject::connect(ui()->syncthingUnitLineEdit, &QLineEdit::textChanged, m_service, &SyncthingService::setUnitName); + QObject::connect(ui()->startPushButton, &QPushButton::clicked, m_service, &SyncthingService::start); + QObject::connect(ui()->stopPushButton, &QPushButton::clicked, m_service, &SyncthingService::stop); + QObject::connect(ui()->enablePushButton, &QPushButton::clicked, m_service, &SyncthingService::enable); + 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; } @@ -796,9 +830,12 @@ void SystemdOptionPage::reset() ui()->syncthingUnitLineEdit->setText(settings.syncthingUnit); ui()->showButtonCheckBox->setChecked(settings.showButton); ui()->considerForReconnectCheckBox->setChecked(settings.considerForReconnect); - handleDescriptionChanged(m_service.description()); - handleStatusChanged(m_service.activeState(), m_service.subState(), m_service.activeSince()); - handleEnabledChanged(m_service.unitFileState()); + if (!m_service) { + return; + } + handleDescriptionChanged(m_service->description()); + handleStatusChanged(m_service->activeState(), m_service->subState(), m_service->activeSince()); + handleEnabledChanged(m_service->unitFileState()); } void SystemdOptionPage::handleDescriptionChanged(const QString &description) @@ -823,7 +860,7 @@ void SystemdOptionPage::handleStatusChanged(const QString &activeState, const QS status << subState; } - const bool isRunning = m_service.isRunning(); + const bool isRunning = m_service && m_service->isRunning(); QString timeStamp; if (isRunning && !activeSince.isNull()) { 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) { - const bool isEnabled = m_service.isEnabled(); + const bool isEnabled = m_service && m_service->isEnabled(); ui()->unitFileStateValueLabel->setText( unitFileState.isEmpty() ? QCoreApplication::translate("QtGui::SystemdOptionPage", "unknown") : unitFileState); setIndicatorColor( diff --git a/widgets/settings/settingsdialog.h b/widgets/settings/settingsdialog.h index 16a66bf..981c6b7 100644 --- a/widgets/settings/settingsdialog.h +++ b/widgets/settings/settingsdialog.h @@ -20,6 +20,7 @@ namespace Data { class SyncthingConnection; class SyncthingService; class SyncthingProcess; +class SyncthingLauncher; } // namespace Data namespace QtGui { @@ -79,10 +80,13 @@ LauncherOptionPage(const QString &tool, QWidget *parentWidget = nullptr); private: DECLARE_SETUP_WIDGETS void handleSyncthingReadyRead(); +void handleSyncthingOutputAvailable(const QByteArray &output); void handleSyncthingExited(int exitCode, QProcess::ExitStatus exitStatus); +bool isRunning() const; void launch(); void stop(); -Data::SyncthingProcess &m_process; +Data::SyncthingProcess *const m_process; +Data::SyncthingLauncher *const m_launcher; QList m_connections; bool m_kill; QString m_tool; @@ -95,7 +99,7 @@ DECLARE_SETUP_WIDGETS void handleDescriptionChanged(const QString &description); void handleStatusChanged(const QString &activeState, const QString &subState, ChronoUtilities::DateTime activeSince); void handleEnabledChanged(const QString &unitFileState); -Data::SyncthingService &m_service; +Data::SyncthingService *const m_service; END_DECLARE_OPTION_PAGE #endif