diff --git a/connector/syncthingprocess.cpp b/connector/syncthingprocess.cpp index a063a3b..bc07c7d 100644 --- a/connector/syncthingprocess.cpp +++ b/connector/syncthingprocess.cpp @@ -10,10 +10,13 @@ SyncthingProcess::SyncthingProcess(QObject *parent) : QProcess(parent) , m_manuallyStopped(false) { + m_killTimer.setInterval(3000); + m_killTimer.setSingleShot(true); setProcessChannelMode(QProcess::MergedChannels); connect(this, &SyncthingProcess::started, this, &SyncthingProcess::handleStarted); connect(this, static_cast(&SyncthingProcess::finished), this, &SyncthingProcess::handleFinished); + connect(&m_killTimer, &QTimer::timeout, this, &SyncthingProcess::confirmKill); } void SyncthingProcess::restartSyncthing(const QString &cmd) @@ -22,11 +25,9 @@ void SyncthingProcess::restartSyncthing(const QString &cmd) startSyncthing(cmd); return; } - m_cmd = cmd; m_manuallyStopped = true; - // give Syncthing 5 seconds to terminate, otherwise kill it - QTimer::singleShot(5000, this, &SyncthingProcess::killToRestart); + m_killTimer.start(); terminate(); } @@ -36,6 +37,7 @@ void SyncthingProcess::startSyncthing(const QString &cmd) return; } m_manuallyStopped = false; + m_killTimer.stop(); if (cmd.isEmpty()) { start(QProcess::ReadOnly); } else { @@ -49,11 +51,20 @@ void SyncthingProcess::stopSyncthing() return; } m_manuallyStopped = true; - // give Syncthing 5 seconds to terminate, otherwise kill it - QTimer::singleShot(5000, this, &SyncthingProcess::kill); + m_killTimer.start(); terminate(); } +void SyncthingProcess::killSyncthing() +{ + if (!isRunning()) { + return; + } + m_manuallyStopped = true; + m_killTimer.stop(); + kill(); +} + void SyncthingProcess::handleStarted() { m_activeSince = DateTime::gmtNow(); @@ -64,6 +75,7 @@ void SyncthingProcess::handleFinished(int exitCode, QProcess::ExitStatus exitSta Q_UNUSED(exitCode) Q_UNUSED(exitStatus) m_activeSince = DateTime(); + m_killTimer.stop(); if (!m_cmd.isEmpty()) { startSyncthing(m_cmd); m_cmd.clear(); diff --git a/connector/syncthingprocess.h b/connector/syncthingprocess.h index 2ed2d89..d2c93ea 100644 --- a/connector/syncthingprocess.h +++ b/connector/syncthingprocess.h @@ -6,6 +6,7 @@ #include #include +#include namespace Data { @@ -22,10 +23,14 @@ public: bool isActiveFor(unsigned int atLeastSeconds) const; bool isManuallyStopped() const; +Q_SIGNALS: + void confirmKill(); + public Q_SLOTS: void restartSyncthing(const QString &cmd); void startSyncthing(const QString &cmd); void stopSyncthing(); + void killSyncthing(); private Q_SLOTS: void handleStarted(); @@ -35,6 +40,7 @@ private Q_SLOTS: private: QString m_cmd; ChronoUtilities::DateTime m_activeSince; + QTimer m_killTimer; bool m_manuallyStopped; }; diff --git a/scripts/dummy.sh b/scripts/dummy.sh new file mode 100755 index 0000000..aefaa06 --- /dev/null +++ b/scripts/dummy.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +function handle_int { + echo "Received SIGINT or SIGTERM, keep running for 15 seconds nevertheless" + sleep 15 + exit -5 +} +trap "handle_int" SIGINT SIGTERM + +while [[ true ]]; do + echo $RANDOM + sleep 1 +done diff --git a/widgets/CMakeLists.txt b/widgets/CMakeLists.txt index 17f9de8..0303402 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/syncthingkiller.h ) set(WIDGETS_SRC_FILES settings/settings.cpp @@ -35,6 +36,7 @@ set(WIDGETS_SRC_FILES misc/dbusstatusnotifier.cpp misc/internalerror.cpp misc/otherdialogs.cpp + misc/syncthingkiller.cpp ) set(RES_FILES resources/${META_PROJECT_NAME}icons.qrc diff --git a/widgets/misc/syncthingkiller.cpp b/widgets/misc/syncthingkiller.cpp new file mode 100644 index 0000000..a07f89a --- /dev/null +++ b/widgets/misc/syncthingkiller.cpp @@ -0,0 +1,60 @@ +#include "./syncthingkiller.h" + +#include "../../connector/syncthingprocess.h" +#define SYNCTHINGTESTHELPER_FOR_CLI +#include "../../testhelper/helper.h" + +#include +#include + +using namespace std; +using namespace Data; +using namespace TestUtilities; + +namespace QtGui { + +SyncthingKiller::SyncthingKiller(std::vector &&processes) + : m_processes(processes) +{ + for (auto *process : m_processes) { + process->stopSyncthing(); + connect(process, &SyncthingProcess::confirmKill, this, &SyncthingKiller::confirmKill); + } +} + +void SyncthingKiller::waitForFinished() +{ + for (auto *process : m_processes) { + if (!process->isRunning()) { + continue; + } + if (!waitForSignalsOrFail(noop, 0, signalInfo(this, &SyncthingKiller::ignored), + signalInfo(process, static_cast(&SyncthingProcess::finished)))) { + return; + } + } +} + +void SyncthingKiller::confirmKill() const +{ + auto *const process = static_cast(sender()); + if (!process->isRunning()) { + return; + } + + const auto msg(tr("The process %1 (PID: %2) has been requested to terminate but hasn't reacted yet. " + "Kill the process?\n\n" + "This dialog closes automatically when the process finally terminates.") + .arg(process->program(), QString::number(process->processId()))); + auto *const msgBox = new QMessageBox(QMessageBox::Critical, QCoreApplication::applicationName(), msg); + msgBox->setAttribute(Qt::WA_DeleteOnClose); + msgBox->addButton(tr("Keep running"), QMessageBox::RejectRole); + msgBox->addButton(tr("Kill process"), QMessageBox::AcceptRole); + connect(process, static_cast(&SyncthingProcess::finished), msgBox, &QMessageBox::close); + connect(msgBox, &QMessageBox::accepted, process, &SyncthingProcess::killSyncthing); + // FIXME: can not really ignore, just keep the process running + //connect(msgBox, &QMessageBox::rejected, this, &SyncthingKiller::ignored); + msgBox->show(); +} + +} // namespace QtGui diff --git a/widgets/misc/syncthingkiller.h b/widgets/misc/syncthingkiller.h new file mode 100644 index 0000000..f7e94ae --- /dev/null +++ b/widgets/misc/syncthingkiller.h @@ -0,0 +1,36 @@ +#ifndef SYNCTHINGWIDGETS_SYNCTHINGKILLER_H +#define SYNCTHINGWIDGETS_SYNCTHINGKILLER_H + +#include "../global.h" + +#include + +#include + +namespace Data { +class SyncthingProcess; +} + +namespace QtGui { + +class SYNCTHINGWIDGETS_EXPORT SyncthingKiller : public QObject { + Q_OBJECT +public: + SyncthingKiller(std::vector &&processes); + +Q_SIGNALS: + void ignored(); + +public Q_SLOTS: + void waitForFinished(); + +private Q_SLOTS: + void confirmKill() const; + +private: + std::vector m_processes; +}; + +} // namespace QtGui + +#endif // SYNCTHINGWIDGETS_SYNCTHINGKILLER_H diff --git a/widgets/settings/settings.cpp b/widgets/settings/settings.cpp index 4f0096c..ac781f4 100644 --- a/widgets/settings/settings.cpp +++ b/widgets/settings/settings.cpp @@ -1,6 +1,7 @@ #include "./settings.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" @@ -67,6 +68,17 @@ SyncthingProcess &Launcher::toolProcess(const QString &tool) return toolProcesses[tool]; } +std::vector Launcher::allProcesses() +{ + vector processes; + processes.reserve(1 + toolProcesses.size()); + processes.push_back(&syncthingProcess()); + for (auto &process : toolProcesses) { + processes.push_back(&process.second); + } + return processes; +} + /*! * \brief Starts all processes (Syncthing and tools) if autostart is enabled. */ @@ -83,16 +95,13 @@ void Launcher::autostart() const } } +/*! + * \brief Terminates all launched processes. + * \remarks Waits until all processes have terminated. If a process hangs, the user is asked to kill. + */ void Launcher::terminate() { - syncthingProcess().stopSyncthing(); - for (auto &process : toolProcesses) { - process.second.stopSyncthing(); - } - syncthingProcess().waitForFinished(); - for (auto &process : toolProcesses) { - process.second.waitForFinished(); - } + QtGui::SyncthingKiller(allProcesses()).waitForFinished(); } Settings &values() diff --git a/widgets/settings/settings.h b/widgets/settings/settings.h index 2639eb2..c9bc218 100644 --- a/widgets/settings/settings.h +++ b/widgets/settings/settings.h @@ -71,6 +71,7 @@ struct SYNCTHINGWIDGETS_EXPORT Launcher { QString syncthingCmd() const; QString toolCmd(const QString &tool) const; static Data::SyncthingProcess &toolProcess(const QString &tool); + static std::vector allProcesses(); void autostart() const; static void terminate(); }; diff --git a/widgets/settings/settingsdialog.cpp b/widgets/settings/settingsdialog.cpp index 8189d0b..00b4446 100644 --- a/widgets/settings/settingsdialog.cpp +++ b/widgets/settings/settingsdialog.cpp @@ -728,6 +728,7 @@ void LauncherOptionPage::launch() } ui()->launchNowPushButton->hide(); ui()->stopPushButton->show(); + ui()->stopPushButton->setText(QCoreApplication::translate("QtGui::LauncherOptionPage", "Stop launched instance")); m_kill = false; if (m_tool.isEmpty()) { m_process.startSyncthing(values().launcher.syncthingCmd()); @@ -744,6 +745,7 @@ void LauncherOptionPage::stop() if (m_kill) { m_process.kill(); } else { + ui()->stopPushButton->setText(QCoreApplication::translate("QtGui::LauncherOptionPage", "Kill launched instance")); m_kill = true; m_process.terminate(); } diff --git a/widgets/translations/syncthingwidgets_de_DE.ts b/widgets/translations/syncthingwidgets_de_DE.ts index 809a036..2b26bb8 100644 --- a/widgets/translations/syncthingwidgets_de_DE.ts +++ b/widgets/translations/syncthingwidgets_de_DE.ts @@ -401,6 +401,7 @@ + Stop launched instance Stoppen @@ -446,6 +447,11 @@ %1 ist mit dem Statuscode %2 abgestürzt + + + Kill launched instance + Töten + QtGui::NotificationsOptionPage @@ -546,22 +552,22 @@ QtGui::SettingsDialog - + Tray - + Web view Weboberfläche - + Startup Starten - + Settings Einstellungen @@ -642,6 +648,28 @@ Nicht mit anderen Geräten verbunden + + QtGui::SyncthingKiller + + + The process %1 (PID: %2) has been requested to terminate but hasn't reacted yet. Kill the process? + +This dialog closes automatically when the process finally terminates. + Der Prozess %1 (PID: %2) wurde aufgefordert sich zu beenden, reagiert aber nicht. Soll der Prozess getötet werden? + +Dieser Dialog schließt sich automatisch, wenn der Prozess beendet wird. + + + + Keep running + Prozess laufen lassen + + + + Kill process + Prozess töten + + QtGui::SystemdOptionPage @@ -678,8 +706,8 @@ - - + + unknown unbekannt @@ -713,12 +741,12 @@ Stoppen - + specified unit is either inactive or doesn't exist angegebene Unit ist entweder nicht geladen oder existiert nicht - + since seit @@ -785,7 +813,7 @@ QtGui::WebViewOptionPage - + General Allgemein @@ -815,7 +843,7 @@ Lasse Weboberfläche im Hintgergrund weiter offen, wenn Fenster nicht offen - + Syncthing Tray has not been built with vieb view support utilizing either Qt WebKit or Qt WebEngine. The Web UI will be opened in the default web browser instead. Syncthing Tray wurde nicht mit Unterstützung für die eingebaute Anzeige der Weboberfläche unter Verwendung von Qt WebKit oder Qt WebEngine gebaut. @@ -825,7 +853,7 @@ Die Weboberfläche wird stattdessen im Standardwebrowser geöffnet. Settings::restore - + Unable to load certificate "%1" when restoring settings. Fehler beim laden des Zertifikats "%1" beim wiederherstellen der Einstellungen. diff --git a/widgets/translations/syncthingwidgets_en_US.ts b/widgets/translations/syncthingwidgets_en_US.ts index f73e019..246b856 100644 --- a/widgets/translations/syncthingwidgets_en_US.ts +++ b/widgets/translations/syncthingwidgets_en_US.ts @@ -401,6 +401,7 @@ + Stop launched instance @@ -446,6 +447,11 @@ + + + Kill launched instance + + QtGui::NotificationsOptionPage @@ -546,22 +552,22 @@ QtGui::SettingsDialog - + Tray - + Web view - + Startup - + Settings @@ -642,6 +648,26 @@ + + QtGui::SyncthingKiller + + + The process %1 (PID: %2) has been requested to terminate but hasn't reacted yet. Kill the process? + +This dialog closes automatically when the process finally terminates. + + + + + Keep running + + + + + Kill process + + + QtGui::SystemdOptionPage @@ -675,8 +701,8 @@ - - + + unknown @@ -711,12 +737,12 @@ - + specified unit is either inactive or doesn't exist - + since @@ -783,7 +809,7 @@ QtGui::WebViewOptionPage - + General @@ -813,7 +839,7 @@ - + Syncthing Tray has not been built with vieb view support utilizing either Qt WebKit or Qt WebEngine. The Web UI will be opened in the default web browser instead. @@ -822,7 +848,7 @@ The Web UI will be opened in the default web browser instead. Settings::restore - + Unable to load certificate "%1" when restoring settings.