Allow launching syncthing-inotify

Provide a way to have multiple instances of
the launcher option page controlling separate
processes.
This commit is contained in:
Martchus 2017-03-17 00:38:42 +01:00
parent 39f8bc36cf
commit 3c2ce3e82f
7 changed files with 181 additions and 30 deletions

View File

@ -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)

View File

@ -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);

View File

@ -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 {

View File

@ -1,4 +1,5 @@
#include "./settings.h"
#include "../../connector/syncthingprocess.h"
#include "resources/config.h"
@ -15,19 +16,81 @@
#include <QMessageBox>
#include <QFile>
#include <unordered_map>
using namespace std;
using namespace Data;
#ifdef QT_UTILITIES_SUPPORT_DBUS_NOTIFICATIONS
using namespace MiscUtils;
#endif
namespace std {
template <> struct hash<QString>
{
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<QString, SyncthingProcess> 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);

View File

@ -12,6 +12,7 @@
#include <QSize>
#include <QFrame>
#include <QTabWidget>
#include <QHash>
#include <vector>
@ -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<QString, ToolParameter> 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

View File

@ -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<void(SyncthingProcess::*)(int exitCode, QProcess::ExitStatus exitStatus)>(&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<void(SyncthingProcess::*)(int exitCode, QProcess::ExitStatus exitStatus)>(&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 &params = 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<Dialogs::OptionPage *>() << new AutostartOptionPage << new LauncherOptionPage
category->assignPages(QList<Dialogs::OptionPage *>() << new AutostartOptionPage << new LauncherOptionPage << new LauncherOptionPage(QStringLiteral("Inotify"))
#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
<< new SystemdOptionPage
#endif

View File

@ -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<QMetaObject::Connection> m_connections;
bool m_kill;
QString m_tool;
END_DECLARE_OPTION_PAGE
#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD