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
This commit is contained in:
Martchus 2019-12-18 00:18:46 +01:00
parent 131050b275
commit 32f78b74fd
9 changed files with 305 additions and 78 deletions

View File

@ -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<ManagerDBusUnitFileChange>();
qDBusRegisterMetaType<ManagerDBusUnitFileChangeList>();
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<ManagerDBusUnitFileChange>();
qDBusRegisterMetaType<ManagerDBusUnitFileChangeList>();
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<typename HandlerType>
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<QDBusObjectPath> 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);
}

View File

@ -6,6 +6,8 @@
#include <QObject>
#include <QVariantMap>
#include <unordered_set>
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<ManagerDBusUnitFileChange> 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<typename HandlerType>
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<QDBusPendingCallWatcher *> 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.
*/

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -66,7 +66,7 @@
<item row="0" column="1">
<widget class="QtUtilities::ClearLineEdit" name="syncthingUnitLineEdit"/>
</item>
<item row="1" column="0">
<item row="2" column="0">
<widget class="QLabel" name="descriptionLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
@ -74,17 +74,23 @@
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>32</height>
</size>
</property>
<property name="text">
<string>Description</string>
</property>
</widget>
</item>
<item row="1" column="1">
<item row="2" column="1">
<layout class="QHBoxLayout" name="descriptionHorizontalLayout">
<item>
<widget class="QLabel" name="descriptionValueLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
<sizepolicy hsizetype="Expanding" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
@ -102,7 +108,7 @@
</item>
</layout>
</item>
<item row="2" column="0">
<item row="3" column="0">
<widget class="QLabel" name="statusLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
@ -115,7 +121,7 @@
</property>
</widget>
</item>
<item row="2" column="1">
<item row="3" column="1">
<layout class="QHBoxLayout" name="statusHorizontalLayout">
<item>
<widget class="QWidget" name="statusIndicator" native="true">
@ -179,7 +185,7 @@
</item>
</layout>
</item>
<item row="3" column="0">
<item row="4" column="0">
<widget class="QLabel" name="unitFileStateLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
@ -192,7 +198,7 @@
</property>
</widget>
</item>
<item row="3" column="1">
<item row="4" column="1">
<layout class="QHBoxLayout" name="unitFileStateHorizontalLayout">
<item>
<widget class="QWidget" name="enabledIndicator" native="true">
@ -256,9 +262,29 @@
</item>
</layout>
</item>
<item row="1" column="1">
<widget class="QCheckBox" name="systemUnitCheckBox">
<property name="text">
<string>System unit</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<customwidgets>