diff --git a/connector/syncthingprocess.cpp b/connector/syncthingprocess.cpp index a4a2195..1184181 100644 --- a/connector/syncthingprocess.cpp +++ b/connector/syncthingprocess.cpp @@ -16,7 +16,7 @@ void SyncthingProcess::restartSyncthing(const QString &cmd) if(state() == QProcess::Running) { m_cmd = cmd; // give Syncthing 5 seconds to terminate, otherwise kill it - QTimer::singleShot(5000, this, SLOT(killToRestart())); + QTimer::singleShot(5000, this, &SyncthingProcess::killToRestart); terminate(); } else { startSyncthing(cmd); @@ -34,6 +34,15 @@ void SyncthingProcess::startSyncthing(const QString &cmd) } } +void SyncthingProcess::stopSyncthing() +{ + if(state() == QProcess::Running) { + // give Syncthing 5 seconds to terminate, otherwise kill it + QTimer::singleShot(5000, this, &SyncthingProcess::kill); + terminate(); + } +} + void SyncthingProcess::handleFinished(int exitCode, QProcess::ExitStatus exitStatus) { Q_UNUSED(exitCode) diff --git a/connector/syncthingprocess.h b/connector/syncthingprocess.h index bb4466a..97d1264 100644 --- a/connector/syncthingprocess.h +++ b/connector/syncthingprocess.h @@ -16,6 +16,7 @@ public: public Q_SLOTS: void restartSyncthing(const QString &cmd); void startSyncthing(const QString &cmd); + void stopSyncthing(); private Q_SLOTS: void handleFinished(int exitCode, QProcess::ExitStatus exitStatus); diff --git a/tray/application/main.cpp b/tray/application/main.cpp index 4c10bd8..adda17d 100644 --- a/tray/application/main.cpp +++ b/tray/application/main.cpp @@ -54,18 +54,14 @@ int initSyncthingTray(bool windowed, bool waitForTray) QObject::connect(&service, &SyncthingService::errorOccurred, &handleSystemdServiceError); #endif if(windowed) { - if(v.launcher.enabled) { - syncthingProcess().startSyncthing(v.launcher.syncthingCmd()); - } + v.launcher.autostart(); auto *trayWidget = new TrayWidget; trayWidget->setAttribute(Qt::WA_DeleteOnClose); trayWidget->show(); } else { #ifndef QT_NO_SYSTEMTRAYICON if(QSystemTrayIcon::isSystemTrayAvailable() || waitForTray) { - if(v.launcher.enabled) { - syncthingProcess().startSyncthing(v.launcher.syncthingCmd()); - } + v.launcher.autostart(); auto *trayIcon = new TrayIcon; trayIcon->show(); if(v.firstLaunch) { @@ -151,6 +147,7 @@ int runApplication(int argc, const char *const *argv) res = application.exec(); } + Settings::Launcher::terminate(); Settings::save(); return res; } else { diff --git a/tray/application/settings.cpp b/tray/application/settings.cpp index 8d79935..80386b8 100644 --- a/tray/application/settings.cpp +++ b/tray/application/settings.cpp @@ -1,4 +1,5 @@ #include "./settings.h" +#include "../../connector/syncthingprocess.h" #include "resources/config.h" @@ -15,19 +16,81 @@ #include #include +#include + using namespace std; using namespace Data; #ifdef QT_UTILITIES_SUPPORT_DBUS_NOTIFICATIONS using namespace MiscUtils; #endif +namespace std { + +template <> struct hash +{ + std::size_t operator()(const QString &str) const + { + return qHash(str); + } +}; + +} + namespace Settings { +/*! + * \brief Contains the processes for launching extra tools. + * \remarks Using std::unordered_map instead of QHash because SyncthingProcess can not be copied. + */ +static unordered_map toolProcesses; + QString Launcher::syncthingCmd() const { return syncthingPath % QChar(' ') % syncthingArgs; } +QString Launcher::toolCmd(const QString &tool) const +{ + const ToolParameter toolParams = tools.value(tool); + if(toolParams.path.isEmpty()) { + return QString(); + } + return toolParams.path % QChar(' ') % toolParams.args; +} + +SyncthingProcess &Launcher::toolProcess(const QString &tool) +{ + return toolProcesses[tool]; +} + +/*! + * \brief Starts all processes (Syncthing and tools) if autostart is enabled. + */ +void Launcher::autostart() const +{ + if(enabled && !syncthingPath.isEmpty()) { + syncthingProcess().startSyncthing(syncthingCmd()); + } + for(auto i = tools.cbegin(), end = tools.cend(); i != end; ++i) { + const ToolParameter &toolParams = i.value(); + if(toolParams.autostart && !toolParams.path.isEmpty()) { + toolProcesses[i.key()].startSyncthing(toolParams.path % QChar(' ') % toolParams.args); + } + } +} + +void Launcher::terminate() +{ + syncthingProcess().stopSyncthing(); + for(auto &process : toolProcesses) { + process.second.stopSyncthing(); + } + syncthingProcess().waitForFinished(); + for(auto &process : toolProcesses) { + process.second.waitForFinished(); + } +} + Settings &values() { static Settings settings; @@ -102,9 +165,22 @@ void restore() settings.beginGroup(QStringLiteral("startup")); auto &launcher = v.launcher; - launcher.enabled = settings.value(QStringLiteral("launchSynchting"), launcher.enabled).toBool(); + launcher.enabled = settings.value(QStringLiteral("syncthingAutostart"), launcher.enabled).toBool(); launcher.syncthingPath = settings.value(QStringLiteral("syncthingPath"), launcher.syncthingPath).toString(); launcher.syncthingArgs = settings.value(QStringLiteral("syncthingArgs"), launcher.syncthingArgs).toString(); + settings.beginGroup(QStringLiteral("tools")); + for(const QString &tool : settings.childGroups()) { + settings.beginGroup(tool); + ToolParameter &toolParams = launcher.tools[tool]; + toolParams.autostart = settings.value(QStringLiteral("autostart"), toolParams.autostart).toBool(); + toolParams.path = settings.value(QStringLiteral("path"), toolParams.path).toString(); + toolParams.args = settings.value(QStringLiteral("args"), toolParams.args).toString(); + settings.endGroup(); + } + for(auto i = launcher.tools.cbegin(), end = launcher.tools.cend(); i != end; ++i) { + + } + settings.endGroup(); #ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD auto &systemd = v.systemd; systemd.syncthingUnit = settings.value(QStringLiteral("syncthingUnit"), systemd.syncthingUnit).toString(); @@ -173,9 +249,19 @@ void save() settings.beginGroup(QStringLiteral("startup")); const auto &launcher = v.launcher; - settings.setValue(QStringLiteral("launchSynchting"), launcher.enabled); + settings.setValue(QStringLiteral("syncthingAutostart"), launcher.enabled); settings.setValue(QStringLiteral("syncthingPath"), launcher.syncthingPath); settings.setValue(QStringLiteral("syncthingArgs"), launcher.syncthingArgs); + settings.beginGroup(QStringLiteral("tools")); + for(auto i = launcher.tools.cbegin(), end = launcher.tools.cend(); i != end; ++i) { + const ToolParameter &toolParams = i.value(); + settings.beginGroup(i.key()); + settings.setValue(QStringLiteral("autostart"), toolParams.autostart); + settings.setValue(QStringLiteral("path"), toolParams.path); + settings.setValue(QStringLiteral("args"), toolParams.args); + settings.endGroup(); + } + settings.endGroup(); #ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD const auto &systemd = v.systemd; settings.setValue(QStringLiteral("syncthingUnit"), systemd.syncthingUnit); diff --git a/tray/application/settings.h b/tray/application/settings.h index b5dab3d..3266fd3 100644 --- a/tray/application/settings.h +++ b/tray/application/settings.h @@ -12,6 +12,7 @@ #include #include #include +#include #include @@ -24,6 +25,10 @@ namespace Dialogs { class QtSettings; } +namespace Data { +class SyncthingProcess; +} + namespace Settings { struct Connection @@ -49,6 +54,13 @@ struct Appearance bool brightTextColors = false; }; +struct ToolParameter +{ + QString path; + QString args; + bool autostart = false; +}; + struct Launcher { bool enabled = false; @@ -59,7 +71,12 @@ struct Launcher QStringLiteral("syncthing"); #endif QString syncthingArgs; + QHash tools; QString syncthingCmd() const; + QString toolCmd(const QString &tool) const; + static Data::SyncthingProcess &toolProcess(const QString &tool); + void autostart() const; + static void terminate(); }; #ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD diff --git a/tray/gui/settingsdialog.cpp b/tray/gui/settingsdialog.cpp index fe8d518..a9da4a7 100644 --- a/tray/gui/settingsdialog.cpp +++ b/tray/gui/settingsdialog.cpp @@ -473,9 +473,17 @@ void AutostartOptionPage::reset() // LauncherOptionPage LauncherOptionPage::LauncherOptionPage(QWidget *parentWidget) : LauncherOptionPageBase(parentWidget), + m_process(syncthingProcess()), m_kill(false) {} +LauncherOptionPage::LauncherOptionPage(const QString &tool, QWidget *parentWidget) : + LauncherOptionPageBase(parentWidget), + m_process(Launcher::toolProcess(tool)), + m_kill(false), + m_tool(tool) +{} + LauncherOptionPage::~LauncherOptionPage() { for(const QMetaObject::Connection &connection : m_connections) { @@ -486,15 +494,24 @@ LauncherOptionPage::~LauncherOptionPage() QWidget *LauncherOptionPage::setupWidget() { auto *widget = LauncherOptionPageBase::setupWidget(); + // adjust labels to use name of additional tool instead of "Syncthing" + if(!m_tool.isEmpty()) { + widget->setWindowTitle(QCoreApplication::translate("QtGui::LauncherOptionPage", "%1-launcher").arg(m_tool)); + ui()->enabledCheckBox->setText(QCoreApplication::translate("QtGui::LauncherOptionPage", "Launch %1 when starting the tray icon").arg(m_tool)); + ui()->syncthingPathLabel->setText(QCoreApplication::translate("QtGui::LauncherOptionPage", "%1 executable").arg(m_tool)); + ui()->logLabel->setText(QCoreApplication::translate("QtGui::LauncherOptionPage", "%1 log (interleaved stdout/stderr)").arg(m_tool)); + } + // setup other widgets ui()->syncthingPathSelection->provideCustomFileMode(QFileDialog::ExistingFile); ui()->logTextEdit->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); - m_connections << QObject::connect(&syncthingProcess(), &SyncthingProcess::readyRead, bind(&LauncherOptionPage::handleSyncthingReadyRead, this)); - m_connections << QObject::connect(&syncthingProcess(), static_cast(&SyncthingProcess::finished), 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)); - const bool running = syncthingProcess().state() != QProcess::NotRunning; + const bool running = m_process.state() != QProcess::NotRunning; 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)); + QObject::connect(ui()->launchNowPushButton, &QPushButton::clicked, bind(&LauncherOptionPage::launch, this)); + QObject::connect(ui()->stopPushButton, &QPushButton::clicked, bind(&LauncherOptionPage::stop, this)); return widget; } @@ -502,9 +519,16 @@ bool LauncherOptionPage::apply() { if(hasBeenShown()) { auto &settings = values().launcher; - settings.enabled = ui()->enabledCheckBox->isChecked(); - settings.syncthingPath = ui()->syncthingPathSelection->lineEdit()->text(); - settings.syncthingArgs = ui()->argumentsLineEdit->text(); + if(m_tool.isEmpty()) { + settings.enabled = ui()->enabledCheckBox->isChecked(); + settings.syncthingPath = ui()->syncthingPathSelection->lineEdit()->text(); + settings.syncthingArgs = ui()->argumentsLineEdit->text(); + } else { + ToolParameter ¶ms = settings.tools[m_tool]; + params.autostart = ui()->enabledCheckBox->isChecked(); + params.path = ui()->syncthingPathSelection->lineEdit()->text(); + params.args = ui()->argumentsLineEdit->text(); + } } return true; } @@ -513,9 +537,16 @@ void LauncherOptionPage::reset() { if(hasBeenShown()) { const auto &settings = values().launcher; - ui()->enabledCheckBox->setChecked(settings.enabled); - ui()->syncthingPathSelection->lineEdit()->setText(settings.syncthingPath); - ui()->argumentsLineEdit->setText(settings.syncthingArgs); + if(m_tool.isEmpty()) { + ui()->enabledCheckBox->setChecked(settings.enabled); + ui()->syncthingPathSelection->lineEdit()->setText(settings.syncthingPath); + ui()->argumentsLineEdit->setText(settings.syncthingArgs); + } else { + const ToolParameter params = settings.tools.value(m_tool); + ui()->enabledCheckBox->setChecked(params.autostart); + ui()->syncthingPathSelection->lineEdit()->setText(params.path); + ui()->argumentsLineEdit->setText(params.args); + } } } @@ -524,7 +555,7 @@ void LauncherOptionPage::handleSyncthingReadyRead() if(hasBeenShown()) { QTextCursor cursor = ui()->logTextEdit->textCursor(); cursor.movePosition(QTextCursor::End); - cursor.insertText(QString::fromLocal8Bit(syncthingProcess().readAll())); + cursor.insertText(QString::fromLocal8Bit(m_process.readAll())); if(ui()->ensureCursorVisibleCheckBox->isChecked()) { ui()->logTextEdit->ensureCursorVisible(); } @@ -538,10 +569,10 @@ void LauncherOptionPage::handleSyncthingExited(int exitCode, QProcess::ExitStatu cursor.movePosition(QTextCursor::End); switch(exitStatus) { case QProcess::NormalExit: - cursor.insertText(QCoreApplication::translate("QtGui::LauncherOptionPage", "Syncthing exited with exit code %1\n").arg(exitCode)); + cursor.insertText(QCoreApplication::translate("QtGui::LauncherOptionPage", "%1 exited with exit code %2\n").arg(m_tool.isEmpty() ? QStringLiteral("Syncthing") : m_tool, QString::number(exitCode))); break; case QProcess::CrashExit: - cursor.insertText(QCoreApplication::translate("QtGui::LauncherOptionPage", "Syncthing crashed with exit code %1\n").arg(exitCode)); + cursor.insertText(QCoreApplication::translate("QtGui::LauncherOptionPage", "%1 crashed with exit code %2\n").arg(m_tool.isEmpty() ? QStringLiteral("Syncthing") : m_tool, QString::number(exitCode))); break; } ui()->stopPushButton->hide(); @@ -553,11 +584,15 @@ void LauncherOptionPage::launch() { if(hasBeenShown()) { apply(); - if(syncthingProcess().state() == QProcess::NotRunning) { + if(m_process.state() == QProcess::NotRunning) { ui()->launchNowPushButton->hide(); ui()->stopPushButton->show(); m_kill = false; - syncthingProcess().startSyncthing(values().launcher.syncthingCmd()); + if(m_tool.isEmpty()) { + m_process.startSyncthing(values().launcher.syncthingCmd()); + } else { + m_process.startSyncthing(values().launcher.toolCmd(m_tool)); + } } } } @@ -565,12 +600,12 @@ void LauncherOptionPage::launch() void LauncherOptionPage::stop() { if(hasBeenShown()) { - if(syncthingProcess().state() != QProcess::NotRunning) { + if(m_process.state() != QProcess::NotRunning) { if(m_kill) { - syncthingProcess().kill(); + m_process.kill(); } else { m_kill = true; - syncthingProcess().terminate(); + m_process.terminate(); } } } @@ -742,7 +777,7 @@ SettingsDialog::SettingsDialog(Data::SyncthingConnection *connection, QWidget *p category = new OptionCategory(this); category->setDisplayName(tr("Startup")); - category->assignPages(QList() << new AutostartOptionPage << new LauncherOptionPage + category->assignPages(QList() << new AutostartOptionPage << new LauncherOptionPage << new LauncherOptionPage(QStringLiteral("Inotify")) #ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD << new SystemdOptionPage #endif diff --git a/tray/gui/settingsdialog.h b/tray/gui/settingsdialog.h index 029cc2a..5e37eb1 100644 --- a/tray/gui/settingsdialog.h +++ b/tray/gui/settingsdialog.h @@ -17,6 +17,7 @@ class DateTime; namespace Data { class SyncthingConnection; class SyncthingService; +class SyncthingProcess; } namespace QtGui { @@ -46,15 +47,20 @@ DECLARE_UI_FILE_BASED_OPTION_PAGE(AppearanceOptionPage) DECLARE_UI_FILE_BASED_OPTION_PAGE_CUSTOM_SETUP(AutostartOptionPage) -BEGIN_DECLARE_UI_FILE_BASED_OPTION_PAGE(LauncherOptionPage) +BEGIN_DECLARE_UI_FILE_BASED_OPTION_PAGE_CUSTOM_CTOR(LauncherOptionPage) +public: + LauncherOptionPage(QWidget *parentWidget = nullptr); + LauncherOptionPage(const QString &tool, QWidget *parentWidget = nullptr); private: DECLARE_SETUP_WIDGETS void handleSyncthingReadyRead(); void handleSyncthingExited(int exitCode, QProcess::ExitStatus exitStatus); void launch(); void stop(); + Data::SyncthingProcess &m_process; QList m_connections; bool m_kill; + QString m_tool; END_DECLARE_OPTION_PAGE #ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD