From 32f78b74fd6a4994b7301ca509a8e69211c53a21 Mon Sep 17 00:00:00 2001 From: Martchus Date: Wed, 18 Dec 2019 00:18:46 +0100 Subject: [PATCH] Overhaul systemd integration, support system-wide units * Lazy initialize systemd interface and don't initialize it at all if the unit name is empty * Allow to supervise/control system-wide units in addition to user units (see https://github.com/Martchus/syncthingtray/issues/61) * Avoid redundant code --- connector/syncthingservice.cpp | 271 ++++++++++++++++++++------ connector/syncthingservice.h | 34 +++- plasmoid/lib/syncthingapplet.cpp | 2 +- tray/application/main.cpp | 13 +- widgets/settings/settings.cpp | 11 +- widgets/settings/settings.h | 3 + widgets/settings/settingsdialog.cpp | 8 + widgets/settings/settingsdialog.h | 1 + widgets/settings/systemdoptionpage.ui | 40 +++- 9 files changed, 305 insertions(+), 78 deletions(-) diff --git a/connector/syncthingservice.cpp b/connector/syncthingservice.cpp index e5af76a..3abf0bc 100644 --- a/connector/syncthingservice.cpp +++ b/connector/syncthingservice.cpp @@ -58,7 +58,8 @@ constexpr DateTime dateTimeFromSystemdTimeStamp(qulonglong timeStamp) } SyncthingService *SyncthingService::s_mainInstance = nullptr; -OrgFreedesktopSystemd1ManagerInterface *SyncthingService::s_manager = nullptr; +OrgFreedesktopSystemd1ManagerInterface *SyncthingService::s_systemdUserInterface = nullptr; +OrgFreedesktopSystemd1ManagerInterface *SyncthingService::s_systemdSystemInterface = nullptr; OrgFreedesktopLogin1ManagerInterface *SyncthingService::s_loginManager = nullptr; DateTime SyncthingService::s_lastWakeUp = DateTime(); bool SyncthingService::s_fallingAsleep = false; @@ -68,37 +69,19 @@ bool SyncthingService::s_fallingAsleep = false; /*! * \brief Creates a new SyncthingService instance. */ -SyncthingService::SyncthingService(QObject *parent) +SyncthingService::SyncthingService(SystemdScope scope, QObject *parent) : QObject(parent) , m_unit(nullptr) , m_service(nullptr) , m_properties(nullptr) + , m_currentSystemdInterface(nullptr) + , m_scope(scope) , m_manuallyStopped(false) , m_unitAvailable(false) { -#ifndef LIB_SYNCTHING_CONNECTOR_SERVICE_MOCKED - if (!s_manager) { - // register custom data types - qDBusRegisterMetaType(); - qDBusRegisterMetaType(); + setupFreedesktopLoginInterface(); - s_manager = new OrgFreedesktopSystemd1ManagerInterface( - QStringLiteral("org.freedesktop.systemd1"), QStringLiteral("/org/freedesktop/systemd1"), QDBusConnection::sessionBus()); - - // enable systemd to emit signals - s_manager->Subscribe(); - } - if (!s_loginManager) { - s_loginManager = new OrgFreedesktopLogin1ManagerInterface( - QStringLiteral("org.freedesktop.login1"), QStringLiteral("/org/freedesktop/login1"), QDBusConnection::systemBus()); - connect(s_loginManager, &OrgFreedesktopLogin1ManagerInterface::PrepareForSleep, &SyncthingService::handlePrepareForSleep); - } - connect(s_manager, &OrgFreedesktopSystemd1ManagerInterface::UnitNew, this, &SyncthingService::handleUnitAdded); - connect(s_manager, &OrgFreedesktopSystemd1ManagerInterface::UnitRemoved, this, &SyncthingService::handleUnitRemoved); - m_serviceWatcher = new QDBusServiceWatcher(s_manager->service(), s_manager->connection()); - connect(m_serviceWatcher, &QDBusServiceWatcher::serviceRegistered, this, &SyncthingService::handleServiceRegisteredChanged); - connect(m_serviceWatcher, &QDBusServiceWatcher::serviceUnregistered, this, &SyncthingService::handleServiceRegisteredChanged); -#else +#ifdef LIB_SYNCTHING_CONNECTOR_SERVICE_MOCKED // let the mocked service initially be stopped and simulate start after 5 seconds, then stop after 10 seconds and start after 15 seconds QTimer::singleShot(5000, this, [this] { m_activeSince = DateTime::gmtNow() - TimeSpan::fromMilliseconds(250); @@ -124,6 +107,147 @@ SyncthingService::SyncthingService(QObject *parent) #endif } +/*! + * \brief Initializes m_currentSystemdInterface and its connection and service watcher for the current m_scope. + */ +void SyncthingService::setupSystemdInterface() +{ +#ifndef LIB_SYNCTHING_CONNECTOR_SERVICE_MOCKED + clearSystemdInterface(); + + // ensure the static systemd interface for the current scope is initialized + const auto isUserScope = m_scope == SystemdScope::User; + OrgFreedesktopSystemd1ManagerInterface *&staticSystemdInterface + = isUserScope ? s_systemdUserInterface : s_systemdSystemInterface; + if (!staticSystemdInterface) { + // register custom data types + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); + + staticSystemdInterface = new OrgFreedesktopSystemd1ManagerInterface( + QStringLiteral("org.freedesktop.systemd1"), QStringLiteral("/org/freedesktop/systemd1"), isUserScope ? QDBusConnection::sessionBus() : QDBusConnection::systemBus()); + + // enable systemd to emit signals + staticSystemdInterface->Subscribe(); + } + + // use the static systemd interface for the current scope + m_currentSystemdInterface = staticSystemdInterface; + connect(m_currentSystemdInterface, &OrgFreedesktopSystemd1ManagerInterface::UnitNew, this, &SyncthingService::handleUnitAdded); + connect(m_currentSystemdInterface, &OrgFreedesktopSystemd1ManagerInterface::UnitRemoved, this, &SyncthingService::handleUnitRemoved); + m_serviceWatcher = new QDBusServiceWatcher(m_currentSystemdInterface->service(), m_currentSystemdInterface->connection()); + connect(m_serviceWatcher, &QDBusServiceWatcher::serviceRegistered, this, &SyncthingService::handleServiceRegisteredChanged); + connect(m_serviceWatcher, &QDBusServiceWatcher::serviceUnregistered, this, &SyncthingService::handleServiceRegisteredChanged); +#endif +} + +/*! + * \brief Initializes s_loginManager. + */ +void SyncthingService::setupFreedesktopLoginInterface() +{ +#ifndef LIB_SYNCTHING_CONNECTOR_SERVICE_MOCKED + // ensure the static login interface is initialized and use it + if (s_loginManager) { + return; + } + s_loginManager = new OrgFreedesktopLogin1ManagerInterface( + QStringLiteral("org.freedesktop.login1"), QStringLiteral("/org/freedesktop/login1"), QDBusConnection::systemBus()); + connect(s_loginManager, &OrgFreedesktopLogin1ManagerInterface::PrepareForSleep, &SyncthingService::handlePrepareForSleep); +#endif +} + +/*! + * \brief Registers the specified D-Bus \a call to invoke \a handler when it has been concluded. + */ +template +void SyncthingService::makeAsyncCall(const QDBusPendingCall &call, HandlerType &&handler) +{ + if (m_currentSystemdInterface) { + // disconnect from unit add/removed signals because these seem to be spammed when waiting for permissions + disconnect(m_currentSystemdInterface, &OrgFreedesktopSystemd1ManagerInterface::UnitNew, this, &SyncthingService::handleUnitAdded); + disconnect(m_currentSystemdInterface, &OrgFreedesktopSystemd1ManagerInterface::UnitRemoved, this, &SyncthingService::handleUnitRemoved); + } + auto *const watcher = new QDBusPendingCallWatcher(call, this); + m_pendingCalls.emplace(watcher); + connect(watcher, &QDBusPendingCallWatcher::finished, this, handler); +} + +/*! + * \brief Registers a generic error handler for the specifeid D-Bus \a call. + */ +void SyncthingService::registerErrorHandler(const QDBusPendingCall &call, const char *context) +{ + makeAsyncCall(call, bind(&SyncthingService::handleError, this, context, _1)); +} + +/*! + * \brief Determines whether the specified \a watcher is still relevant and ensures it is being deleted later. + */ +bool SyncthingService::concludeAsyncCall(QDBusPendingCallWatcher *watcher) +{ + watcher->deleteLater(); + + const auto i = m_pendingCalls.find(watcher); + const auto resultStillRelevant = i != m_pendingCalls.cend(); + if (resultStillRelevant) { + m_pendingCalls.erase(i); + } + if (m_currentSystemdInterface && m_pendingCalls.empty()) { + // ensure we listen to unit add/removed signals again if there are no pending calls anymore + connect(m_currentSystemdInterface, &OrgFreedesktopSystemd1ManagerInterface::UnitNew, this, &SyncthingService::handleUnitAdded); + connect(m_currentSystemdInterface, &OrgFreedesktopSystemd1ManagerInterface::UnitRemoved, this, &SyncthingService::handleUnitRemoved); + } + return resultStillRelevant; +} + +/*! + * \brief Unties the current instance from its current systemd interface. + */ +void Data::SyncthingService::clearSystemdInterface() +{ + m_pendingCalls.clear(); + if (m_currentSystemdInterface) { + disconnect(m_currentSystemdInterface, &OrgFreedesktopSystemd1ManagerInterface::UnitNew, this, &SyncthingService::handleUnitAdded); + disconnect(m_currentSystemdInterface, &OrgFreedesktopSystemd1ManagerInterface::UnitRemoved, this, &SyncthingService::handleUnitRemoved); + delete m_serviceWatcher; + m_currentSystemdInterface = nullptr; + } +} + +/*! + * \brief Clears everything we know about the systemd unit. + */ +void SyncthingService::clearUnitData() +{ + // clean up data from previous unit + delete m_service; + m_service = nullptr; + delete m_unit; + m_unit = nullptr; + delete m_properties; + m_properties = nullptr; +} + +/*! + * \brief Queries m_unit from m_currentSystemdInterface. + */ +void Data::SyncthingService::queryUnitFromSystemdInterface() +{ + clearUnitData(); + setProperties(false, QString(), QString(), QString(), QString()); + +#ifndef LIB_SYNCTHING_CONNECTOR_SERVICE_MOCKED + if (!m_currentSystemdInterface) { + setupSystemdInterface(); + } + if (!m_currentSystemdInterface->isValid()) { + return; + } + makeAsyncCall(m_currentSystemdInterface->GetUnit(m_unitName), &SyncthingService::handleUnitGet); +#endif +} + /*! * \brief Sets the \a unitName of the systemd user service to be controlled/monitored, e.g. "syncthing.service". */ @@ -134,20 +258,37 @@ void SyncthingService::setUnitName(const QString &unitName) } m_unitName = unitName; - delete m_service, delete m_unit, delete m_properties; - m_service = nullptr, m_unit = nullptr, m_properties = nullptr; - setProperties(false, QString(), QString(), QString(), QString()); - -#ifndef LIB_SYNCTHING_CONNECTOR_SERVICE_MOCKED - if (s_manager->isValid()) { - connect(new QDBusPendingCallWatcher(s_manager->GetUnit(m_unitName), this), &QDBusPendingCallWatcher::finished, this, - &SyncthingService::handleUnitGet); + if (!m_unitName.isEmpty()) { + queryUnitFromSystemdInterface(); } -#endif - emit unitNameChanged(unitName); } +/*! + * \brief Sets the \a scope and \a unitName (see scope() and unitName()). + */ +void SyncthingService::setScopeAndUnitName(SystemdScope scope, const QString &unitName) +{ + const auto scopeChanged = m_scope != scope; + const auto unitNameChanged = m_unitName != unitName; + if (!scopeChanged && !unitNameChanged) { + return; + } + if (scopeChanged) { + m_scope = scope; + clearSystemdInterface(); + } + if (unitNameChanged) { + m_unitName = unitName; + } + if (!unitName.isEmpty()) { + queryUnitFromSystemdInterface(); + } + if (unitNameChanged) { + emit this->unitNameChanged(unitName); + } +} + /*! * \brief Returns whether systemd (and specificly its D-Bus interface for user services) is available. * \remarks The availability might not be instantly detected and may change at any time. Use the systemdAvailableChanged() @@ -156,7 +297,7 @@ void SyncthingService::setUnitName(const QString &unitName) bool SyncthingService::isSystemdAvailable() const { #ifndef LIB_SYNCTHING_CONNECTOR_SERVICE_MOCKED - return s_manager && s_manager->isValid(); + return m_currentSystemdInterface && m_currentSystemdInterface->isValid(); #else return true; #endif @@ -194,6 +335,19 @@ bool SyncthingService::isActiveWithoutSleepFor(DateTime activeSince, unsigned in return ((now - activeSince).totalSeconds() > atLeastSeconds) && (s_lastWakeUp.isNull() || ((now - s_lastWakeUp).totalSeconds() > atLeastSeconds)); } +/*! + * \brief Sets the scope the current instance is tuned to. + */ +void SyncthingService::setScope(SystemdScope scope) +{ + if (m_scope == scope) { + return; + } + m_scope = scope; + clearSystemdInterface(); + queryUnitFromSystemdInterface(); +} + /*! * \brief Starts the unit if \a running is true and stops the unit if \a running is false. */ @@ -201,10 +355,13 @@ void SyncthingService::setRunning(bool running) { #ifndef LIB_SYNCTHING_CONNECTOR_SERVICE_MOCKED m_manuallyStopped = !running; + if (!m_currentSystemdInterface) { + setupSystemdInterface(); + } if (running) { - registerErrorHandler(s_manager->StartUnit(m_unitName, QStringLiteral("replace")), QT_TR_NOOP_UTF8("start unit")); + registerErrorHandler(m_currentSystemdInterface->StartUnit(m_unitName, QStringLiteral("replace")), QT_TR_NOOP_UTF8("start unit")); } else { - registerErrorHandler(s_manager->StopUnit(m_unitName, QStringLiteral("replace")), QT_TR_NOOP_UTF8("stop unit")); + registerErrorHandler(m_currentSystemdInterface->StopUnit(m_unitName, QStringLiteral("replace")), QT_TR_NOOP_UTF8("stop unit")); } #endif } @@ -215,10 +372,13 @@ void SyncthingService::setRunning(bool running) void SyncthingService::setEnabled(bool enabled) { #ifndef LIB_SYNCTHING_CONNECTOR_SERVICE_MOCKED + if (!m_currentSystemdInterface) { + setupSystemdInterface(); + } if (enabled) { - registerErrorHandler(s_manager->EnableUnitFiles(QStringList(m_unitName), false, true), QT_TR_NOOP_UTF8("enable unit")); + registerErrorHandler(m_currentSystemdInterface->EnableUnitFiles(QStringList(m_unitName), false, true), QT_TR_NOOP_UTF8("enable unit")); } else { - registerErrorHandler(s_manager->DisableUnitFiles(QStringList(m_unitName), false), QT_TR_NOOP_UTF8("disable unit")); + registerErrorHandler(m_currentSystemdInterface->DisableUnitFiles(QStringList(m_unitName), false), QT_TR_NOOP_UTF8("disable unit")); } #endif } @@ -249,13 +409,13 @@ void SyncthingService::handleUnitRemoved(const QString &unitName, const QDBusObj */ void SyncthingService::handleUnitGet(QDBusPendingCallWatcher *watcher) { - watcher->deleteLater(); - + if (!concludeAsyncCall(watcher)) { + return; + } const QDBusPendingReply unitReply = *watcher; if (unitReply.isError()) { return; } - setUnit(unitReply.value()); } @@ -307,7 +467,9 @@ void SyncthingService::handlePropertiesChanged( */ void SyncthingService::handleError(const char *context, QDBusPendingCallWatcher *watcher) { - watcher->deleteLater(); + if (!concludeAsyncCall(watcher)) { + return; + } const QDBusError error = watcher->error(); if (error.isValid()) { emit errorOccurred(tr(context), error.name(), error.message()); @@ -319,8 +481,8 @@ void SyncthingService::handleError(const char *context, QDBusPendingCallWatcher */ void SyncthingService::handleServiceRegisteredChanged(const QString &service) { - if (service == s_manager->service()) { - emit systemdAvailableChanged(s_manager->isValid()); + if (m_currentSystemdInterface && service == m_currentSystemdInterface->service()) { + emit systemdAvailableChanged(m_currentSystemdInterface->isValid()); } } @@ -376,36 +538,26 @@ bool SyncthingService::handlePropertyChanged( return false; } -/*! - * \brief Registers error handler for D-Bus errors. - */ -void SyncthingService::registerErrorHandler(const QDBusPendingCall &call, const char *context) -{ - connect(new QDBusPendingCallWatcher(call, this), &QDBusPendingCallWatcher::finished, bind(&SyncthingService::handleError, this, context, _1)); -} - /*! * \brief Sets the current unit data. */ void SyncthingService::setUnit(const QDBusObjectPath &objectPath) { - // cleanup - delete m_service, delete m_unit, delete m_properties; - m_service = nullptr, m_unit = nullptr, m_properties = nullptr; + clearUnitData(); const QString path = objectPath.path(); - if (path.isEmpty()) { + if (!m_currentSystemdInterface || path.isEmpty()) { setProperties(false, QString(), QString(), QString(), QString()); return; } // init unit - m_unit = new OrgFreedesktopSystemd1UnitInterface(s_manager->service(), path, s_manager->connection()); + m_unit = new OrgFreedesktopSystemd1UnitInterface(m_currentSystemdInterface->service(), path, m_currentSystemdInterface->connection()); m_activeSince = dateTimeFromSystemdTimeStamp(m_unit->activeEnterTimestamp()); setProperties(m_unit->isValid(), m_unit->activeState(), m_unit->subState(), m_unit->unitFileState(), m_unit->description()); // init properties - m_properties = new OrgFreedesktopDBusPropertiesInterface(s_manager->service(), path, s_manager->connection()); + m_properties = new OrgFreedesktopDBusPropertiesInterface(m_currentSystemdInterface->service(), path, m_currentSystemdInterface->connection()); connect(m_properties, &OrgFreedesktopDBusPropertiesInterface::PropertiesChanged, this, &SyncthingService::handlePropertiesChanged); } @@ -443,7 +595,6 @@ void SyncthingService::setProperties( if (enabled != isEnabled()) { emit enabledChanged(isEnabled()); } - if (m_description != description) { emit descriptionChanged(m_description = description); } diff --git a/connector/syncthingservice.h b/connector/syncthingservice.h index 1ac9320..eba1b6c 100644 --- a/connector/syncthingservice.h +++ b/connector/syncthingservice.h @@ -6,6 +6,8 @@ #include #include +#include + QT_FORWARD_DECLARE_CLASS(QDBusServiceWatcher) QT_FORWARD_DECLARE_CLASS(QDBusArgument) QT_FORWARD_DECLARE_CLASS(QDBusObjectPath) @@ -31,6 +33,8 @@ const QDBusArgument &operator>>(const QDBusArgument &argument, ManagerDBusUnitFi typedef QList ManagerDBusUnitFileChangeList; +enum class SystemdScope { System, User }; + class SyncthingService : public QObject { Q_OBJECT Q_PROPERTY(QString unitName READ unitName WRITE setUnitName NOTIFY unitNameChanged) @@ -44,9 +48,10 @@ class SyncthingService : public QObject { Q_PROPERTY(bool running READ isRunning WRITE setRunning NOTIFY runningChanged) Q_PROPERTY(bool enable READ isEnabled WRITE setEnabled NOTIFY enabledChanged) Q_PROPERTY(bool manuallyStopped READ isManuallyStopped) + Q_PROPERTY(SystemdScope scope READ scope WRITE setScope) public: - explicit SyncthingService(QObject *parent = nullptr); + explicit SyncthingService(SystemdScope scope = SystemdScope::User, QObject *parent = nullptr); const QString &unitName() const; bool isSystemdAvailable() const; @@ -63,6 +68,9 @@ public: bool isRunning() const; bool isEnabled() const; bool isManuallyStopped() const; + SystemdScope scope() const; + void setScope(SystemdScope scope); + void setScopeAndUnitName(SystemdScope scope, const QString &unitName); static SyncthingService *mainInstance(); static void setMainInstance(SyncthingService *mainInstance); @@ -102,13 +110,22 @@ private Q_SLOTS: bool unitAvailable, const QString &activeState, const QString &subState, const QString &unitFileState, const QString &description); private: + void setupSystemdInterface(); + void setupFreedesktopLoginInterface(); + template + void makeAsyncCall(const QDBusPendingCall &call, HandlerType &&handler); + void registerErrorHandler(const QDBusPendingCall &call, const char *context); + bool concludeAsyncCall(QDBusPendingCallWatcher *watcher); + void clearSystemdInterface(); + void clearUnitData(); + void queryUnitFromSystemdInterface(); bool handlePropertyChanged(QString &variable, void (SyncthingService::*signal)(const QString &), const QString &propertyName, const QVariantMap &changedProperties, const QStringList &invalidatedProperties); bool handlePropertyChanged(CppUtilities::DateTime &variable, const QString &propertyName, const QVariantMap &changedProperties, const QStringList &invalidatedProperties); - void registerErrorHandler(const QDBusPendingCall &call, const char *context); - static OrgFreedesktopSystemd1ManagerInterface *s_manager; + static OrgFreedesktopSystemd1ManagerInterface *s_systemdUserInterface; + static OrgFreedesktopSystemd1ManagerInterface *s_systemdSystemInterface; static OrgFreedesktopLogin1ManagerInterface *s_loginManager; static bool s_fallingAsleep; static CppUtilities::DateTime s_lastWakeUp; @@ -123,6 +140,9 @@ private: QString m_subState; QString m_unitFileState; CppUtilities::DateTime m_activeSince; + OrgFreedesktopSystemd1ManagerInterface *m_currentSystemdInterface; + std::unordered_set m_pendingCalls; + SystemdScope m_scope; bool m_manuallyStopped; bool m_unitAvailable; }; @@ -221,6 +241,14 @@ inline bool SyncthingService::isManuallyStopped() const return m_manuallyStopped; } +/*! + * \brief Returns the scope the current instance is tuned to. + */ +inline SystemdScope SyncthingService::scope() const +{ + return m_scope; +} + /*! * \brief Returns since when the unit is active. */ diff --git a/plasmoid/lib/syncthingapplet.cpp b/plasmoid/lib/syncthingapplet.cpp index ea11113..69eff41 100644 --- a/plasmoid/lib/syncthingapplet.cpp +++ b/plasmoid/lib/syncthingapplet.cpp @@ -111,7 +111,7 @@ void SyncthingApplet::init() // initialize systemd service support #ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD SyncthingService::setMainInstance(&m_service); - m_service.setUnitName(Settings::values().systemd.syncthingUnit); + Settings::values().systemd.setupService(m_service); connect(&m_service, &SyncthingService::systemdAvailableChanged, this, &SyncthingApplet::handleSystemdStatusChanged); connect(&m_service, &SyncthingService::stateChanged, this, &SyncthingApplet::handleSystemdStatusChanged); connect(&m_service, &SyncthingService::errorOccurred, this, &SyncthingApplet::handleSystemdServiceError); diff --git a/tray/application/main.cpp b/tray/application/main.cpp index 0da0fe0..c093264 100644 --- a/tray/application/main.cpp +++ b/tray/application/main.cpp @@ -40,11 +40,12 @@ ENABLE_QT_RESOURCES_OF_STATIC_DEPENDENCIES #ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD void handleSystemdServiceError(const QString &context, const QString &name, const QString &message) { - QMessageBox msgBox; - msgBox.setIcon(QMessageBox::Critical); - msgBox.setText(QCoreApplication::translate("main", "Unable to ") + context); - msgBox.setInformativeText(name % QStringLiteral(":\n") % message); - msgBox.exec(); + auto *const msgBox = new QMessageBox; + msgBox->setAttribute(Qt::WA_DeleteOnClose); + msgBox->setIcon(QMessageBox::Critical); + msgBox->setText(QCoreApplication::translate("main", "Unable to ") + context); + msgBox->setInformativeText(name % QStringLiteral(":\n") % message); + msgBox->show(); } #endif @@ -175,7 +176,7 @@ int runApplication(int argc, const char *const *argv) #ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD SyncthingService service; SyncthingService::setMainInstance(&service); - service.setUnitName(Settings::values().systemd.syncthingUnit); + Settings::values().systemd.setupService(service); QObject::connect(&service, &SyncthingService::errorOccurred, &handleSystemdServiceError); #endif diff --git a/widgets/settings/settings.cpp b/widgets/settings/settings.cpp index 0a1dcb0..195ebf2 100644 --- a/widgets/settings/settings.cpp +++ b/widgets/settings/settings.cpp @@ -291,6 +291,7 @@ void restore() #ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD auto &systemd = v.systemd; systemd.syncthingUnit = settings.value(QStringLiteral("syncthingUnit"), systemd.syncthingUnit).toString(); + systemd.systemUnit = settings.value(QStringLiteral("systemUnit"), systemd.systemUnit).toBool(); systemd.showButton = settings.value(QStringLiteral("showButton"), systemd.showButton).toBool(); systemd.considerForReconnect = settings.value(QStringLiteral("considerForReconnect"), systemd.considerForReconnect).toBool(); #endif @@ -388,6 +389,7 @@ void save() #ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD const auto &systemd = v.systemd; settings.setValue(QStringLiteral("syncthingUnit"), systemd.syncthingUnit); + settings.setValue(QStringLiteral("systemUnit"), systemd.systemUnit); settings.setValue(QStringLiteral("showButton"), systemd.showButton); settings.setValue(QStringLiteral("considerForReconnect"), systemd.considerForReconnect); #endif @@ -435,6 +437,14 @@ void Settings::apply(SyncthingNotifier ¬ifier) const } #ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD +/*! + * \brief Sets the scope and unit name of the specified \a service according to the settings. + */ +void Systemd::setupService(SyncthingService &service) const +{ + service.setScopeAndUnitName(systemUnit ? SystemdScope::System : SystemdScope::User, syncthingUnit); +} + /*! * \brief Applies the systemd settings to the specified \a connection considering the status of the global SyncthingService instance. * \remarks @@ -497,7 +507,6 @@ Systemd::ServiceStatus Systemd::status(SyncthingConnection &connection) const const auto isRelevant = service->isSystemdAvailable() && connection.isLocal(); return ServiceStatus{ isRelevant, service->isRunning(), considerForReconnect && isRelevant, showButton && isRelevant }; } - #endif } // namespace Settings diff --git a/widgets/settings/settings.h b/widgets/settings/settings.h index 4f3961d..a346fea 100644 --- a/widgets/settings/settings.h +++ b/widgets/settings/settings.h @@ -26,6 +26,7 @@ namespace Data { class SyncthingProcess; class SyncthingNotifier; class SyncthingConnection; +class SyncthingService; } // namespace Data namespace Settings { @@ -101,6 +102,7 @@ struct SYNCTHINGWIDGETS_EXPORT Launcher { #ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD struct SYNCTHINGWIDGETS_EXPORT Systemd { QString syncthingUnit = QStringLiteral("syncthing.service"); + bool systemUnit = false; bool showButton = false; bool considerForReconnect = false; @@ -110,6 +112,7 @@ struct SYNCTHINGWIDGETS_EXPORT Systemd { bool consideredForReconnect = false; bool showStartStopButton = false; }; + void setupService(Data::SyncthingService &) const; ServiceStatus apply(Data::SyncthingConnection &connection, const Data::SyncthingConnectionSettings *currentConnectionSettings, bool preventReconnect = false) const; ServiceStatus status(Data::SyncthingConnection &connection) const; diff --git a/widgets/settings/settingsdialog.cpp b/widgets/settings/settingsdialog.cpp index 03c9d46..96125f8 100644 --- a/widgets/settings/settingsdialog.cpp +++ b/widgets/settings/settingsdialog.cpp @@ -1129,6 +1129,7 @@ QWidget *SystemdOptionPage::setupWidget() return widget; } QObject::connect(ui()->syncthingUnitLineEdit, &QLineEdit::textChanged, m_service, &SyncthingService::setUnitName); + QObject::connect(ui()->systemUnitCheckBox, &QCheckBox::clicked, m_service, bind(&SystemdOptionPage::handleSystemUnitChanged, this)); 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); @@ -1145,6 +1146,7 @@ bool SystemdOptionPage::apply() auto &systemdSettings = settings.systemd; auto &launcherSettings = settings.launcher; systemdSettings.syncthingUnit = ui()->syncthingUnitLineEdit->text(); + systemdSettings.systemUnit = ui()->systemUnitCheckBox->isChecked(); systemdSettings.showButton = ui()->showButtonCheckBox->isChecked(); systemdSettings.considerForReconnect = ui()->considerForReconnectCheckBox->isChecked(); auto result = true; @@ -1167,6 +1169,7 @@ void SystemdOptionPage::reset() { const auto &settings = values().systemd; ui()->syncthingUnitLineEdit->setText(settings.syncthingUnit); + ui()->systemUnitCheckBox->setChecked(settings.systemUnit); ui()->showButtonCheckBox->setChecked(settings.showButton); ui()->considerForReconnectCheckBox->setChecked(settings.considerForReconnect); if (!m_service) { @@ -1177,6 +1180,11 @@ void SystemdOptionPage::reset() handleEnabledChanged(m_service->unitFileState()); } +void SystemdOptionPage::handleSystemUnitChanged() +{ + m_service->setScope(ui()->systemUnitCheckBox->isChecked() ? SystemdScope::System : SystemdScope::User); +} + void SystemdOptionPage::handleDescriptionChanged(const QString &description) { ui()->descriptionValueLabel->setText(description.isEmpty() diff --git a/widgets/settings/settingsdialog.h b/widgets/settings/settingsdialog.h index bebb596..409882e 100644 --- a/widgets/settings/settingsdialog.h +++ b/widgets/settings/settingsdialog.h @@ -134,6 +134,7 @@ private: BEGIN_DECLARE_UI_FILE_BASED_OPTION_PAGE(SystemdOptionPage) private: DECLARE_SETUP_WIDGETS +void handleSystemUnitChanged(); void handleDescriptionChanged(const QString &description); void handleStatusChanged(const QString &activeState, const QString &subState, CppUtilities::DateTime activeSince); void handleEnabledChanged(const QString &unitFileState); diff --git a/widgets/settings/systemdoptionpage.ui b/widgets/settings/systemdoptionpage.ui index 4b685f3..808fd74 100644 --- a/widgets/settings/systemdoptionpage.ui +++ b/widgets/settings/systemdoptionpage.ui @@ -66,7 +66,7 @@ - + @@ -74,17 +74,23 @@ 0 + + + 0 + 32 + + Description - + - + 0 0 @@ -102,7 +108,7 @@ - + @@ -115,7 +121,7 @@ - + @@ -179,7 +185,7 @@ - + @@ -192,7 +198,7 @@ - + @@ -256,9 +262,29 @@ + + + + System unit + + + + + + + Qt::Vertical + + + + 20 + 40 + + + +