Implement launcher and systemd configuration in wizard

This commit is contained in:
Martchus 2022-10-02 19:24:26 +02:00
parent db69a148a9
commit b0d1197cb7
6 changed files with 189 additions and 20 deletions

View File

@ -479,9 +479,7 @@ void TrayWidget::handleStatusChanged(SyncthingStatus status)
break;
default:;
}
if (m_applyingSettingsForWizard) {
concludeWizard();
}
concludeWizard();
}
void TrayWidget::applySettings(const QString &connectionConfig)

View File

@ -10,6 +10,13 @@
<normaloff>:/icons/hicolor/scalable/apps/internet-web-browser.svg</normaloff>:/icons/hicolor/scalable/apps/internet-web-browser.svg</iconset>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="invalidConfigLabel">
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="cfgCurrentlyRunningRadioButton">
<property name="text">

View File

@ -21,6 +21,7 @@ SetupDetection::SetupDetection(QObject *parent)
#endif
// configure launcher to test invocation of "syncthing --version" capturing output
defaultSyncthingArgs = launcherSettings.syncthingArgs;
launcherSettings.syncthingArgs = QStringLiteral("--version");
launcher.setEmittingOutput(true);

View File

@ -57,6 +57,7 @@ public:
Data::SyncthingService systemService = Data::SyncthingService(Data::SystemdScope::System);
#endif
Settings::Launcher launcherSettings;
QString defaultSyncthingArgs;
Data::SyncthingLauncher launcher;
std::optional<int> launcherExitCode;
std::optional<QProcess::ExitStatus> launcherExitStatus;

View File

@ -2,6 +2,7 @@
#include "./setupdetection.h"
#include "../misc/statusinfo.h"
#include "../misc/syncthinglauncher.h"
// use meta-data of syncthingtray application here
#include "resources/../../tray/resources/config.h"
@ -17,6 +18,7 @@
#include <QDesktopServices>
#include <QDialog>
#include <QFileDialog>
#include <QFileInfo>
#include <QFrame>
#include <QLabel>
#include <QMessageBox>
@ -36,6 +38,10 @@
namespace QtGui {
constexpr int syncthingPollTimeout = 60 * 1000;
constexpr int syncthingPollInterval = 2000;
constexpr int systemdPollTimeout = 10 * 1000;
Wizard *Wizard::s_instance = nullptr;
Wizard::Wizard(QWidget *parent, Qt::WindowFlags flags)
@ -52,6 +58,8 @@ Wizard::Wizard(QWidget *parent, Qt::WindowFlags flags)
auto *const finalPage = new FinalWizardPage(this);
connect(mainConfigPage, &MainConfigWizardPage::retry, detectionPage, &DetectionWizardPage::refresh);
connect(mainConfigPage, &MainConfigWizardPage::configurationSelected, this, &Wizard::handleConfigurationSelected);
connect(this, &Wizard::configApplied, finalPage, &FinalWizardPage::completeChanged);
connect(this, &Wizard::configApplied, finalPage, &FinalWizardPage::showResults);
#ifdef SETTINGS_WIZARD_AUTOSTART
auto *const autostartPage = new AutostartWizardPage(this);
connect(autostartPage, &AutostartWizardPage::autostartSelected, this, &Wizard::handleAutostartSelected);
@ -97,29 +105,91 @@ bool Wizard::changeSettings()
const auto &detection = setupDetection();
auto &settings = Settings::values();
// clear state/error from possible previous attempt
m_configApplied = false;
m_configError.clear();
// set settings for configured "main config"
switch (mainConfig()) {
case MainConfiguration::None:
break;
case MainConfiguration::CurrentlyRunning: {
case MainConfiguration::CurrentlyRunning:
// apply changes to current primary config if necessary
settings.connection.addConfigFromWizard(detection.config);
break;
}
case MainConfiguration::LauncherExternal:
// configure to use external Syncthing executable
settings.launcher.useLibSyncthing = false;
settings.launcher.syncthingPath = detection.launcherSettings.syncthingPath;
// restore args to defaults that are known to work (maybe warn about this if user had custom args configured?)
settings.launcher.syncthingArgs = detection.defaultSyncthingArgs;
break;
case MainConfiguration::LauncherBuiltIn:
// TODO
// configure to use built-in Syncthing executable
settings.launcher.useLibSyncthing = true;
#ifdef SYNCTHINGWIDGETS_USE_LIBSYNCTHING
settings.launcher.libSyncthing.configDir = QFileInfo(detection.configFilePath).canonicalPath();
#endif
break;
case MainConfiguration::SystemdUserUnit:
#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
// configure systemd integration for user unit
settings.systemd.syncthingUnit = detection.userService.unitName();
settings.systemd.systemUnit = false;
#endif
break;
case MainConfiguration::SystemdSystemUnit:
#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
// TODO
// configure systemd integration for system unit
settings.systemd.syncthingUnit = detection.systemService.unitName();
settings.systemd.systemUnit = true;
#endif
break;
}
// let the tray widget / plasmoid apply the settings
// note: The tray widget / plasmoid is expected to call handleConfigurationApplied().
emit settingsChanged();
// enable/disable integrations accordingly
settings.launcher.considerForReconnect = settings.launcher.showButton = settings.launcher.autostartEnabled
= (mainConfig() == MainConfiguration::LauncherExternal || mainConfig() == MainConfiguration::LauncherBuiltIn);
#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
settings.systemd.considerForReconnect = settings.systemd.showButton = extraConfig() & ExtraConfiguration::SystemdIntegration;
#endif
// invoke next step
switch (mainConfig()) {
case MainConfiguration::None:
case MainConfiguration::CurrentlyRunning:
// let the tray widget / plasmoid apply the settings immediately
// note: The tray widget / plasmoid is expected to call handleConfigurationApplied().
emit settingsChanged();
return true;
case MainConfiguration::LauncherExternal:
case MainConfiguration::LauncherBuiltIn:
// launch Syncthing via launcher (before trying to connect to it)
if (auto *const launcher = Data::SyncthingLauncher::mainInstance()) {
launcher->launch(settings.launcher);
} else {
handleConfigurationApplied(tr("The internal launcher has not been initialized."));
return true;
}
break;
case MainConfiguration::SystemdUserUnit:
case MainConfiguration::SystemdSystemUnit:
#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
if (auto *const service = Data::SyncthingService::mainInstance()) {
settings.systemd.setupService(*service);
service->start();
service->enable();
} else {
handleConfigurationApplied(tr("The service handler has not been initialized."));
return true;
}
#endif
break;
}
// poll until Syncthing config with API key has been created
m_elapsedPollTime = 0;
pollForSyncthingConfig();
return true;
}
@ -227,10 +297,86 @@ void Wizard::handleAutostartSelected(bool autostartEnabled)
m_autoStart = autostartEnabled;
}
void Wizard::pollForSyncthingConfig()
{
// check if Syncthing is still running (if not that's an error and we can stop polling)
switch (mainConfig()) {
case MainConfiguration::LauncherExternal:
case MainConfiguration::LauncherBuiltIn:
// launch Syncthing via launcher (before trying to connect to it)
if (auto *const launcher = Data::SyncthingLauncher::mainInstance()) {
if (!launcher->isRunning()) {
handleConfigurationApplied(tr("The Syncthing process exited prematurely. ") + hintAboutSyncthingLog());
return;
}
}
break;
case MainConfiguration::SystemdUserUnit:
case MainConfiguration::SystemdSystemUnit:
#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
if (m_elapsedPollTime < systemdPollTimeout) {
break; // don't consider the systemd unit status immediately; also give it a short time to adjust
}
if (auto *const service = Data::SyncthingService::mainInstance()) {
if (!service->isRunning()) {
handleConfigurationApplied(tr("The Syncthing service stopped prematurely. ") + hintAboutSyncthingLog());
return;
}
}
#endif
break;
default:;
}
// check for config and if present let the tray widget / plasmoid apply the settings
// note: The tray widget / plasmoid is expected to call handleConfigurationApplied().
auto &detection = setupDetection();
detection.determinePaths();
if (!detection.configFilePath.isEmpty()) {
detection.restoreConfig();
if (detection.hasConfig()) {
Settings::values().connection.addConfigFromWizard(detection.config);
emit settingsChanged();
return;
}
}
// keep polling
if (m_elapsedPollTime > syncthingPollTimeout) {
handleConfigurationApplied(tr("Ran into timeout while waiting for Syncthing to create config file."
"Maybe Syncthing created its config file under an unexpected location. ")
+ hintAboutSyncthingLog());
return;
}
m_elapsedPollTime += syncthingPollInterval;
QTimer::singleShot(syncthingPollInterval, Qt::VeryCoarseTimer, this, &Wizard::pollForSyncthingConfig);
}
QString Wizard::hintAboutSyncthingLog() const
{
auto res = tr("Checkout Syncthing's log for details.");
switch (mainConfig()) {
case MainConfiguration::LauncherExternal:
case MainConfiguration::LauncherBuiltIn:
res += tr(" It can be accessed within the launcher settings.");
break;
case MainConfiguration::SystemdUserUnit:
case MainConfiguration::SystemdSystemUnit:
#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
res += tr(" It is normally written to the system journal (and can be accessed via e.g. journalctl).");
#endif
break;
default:;
}
return res;
}
void Wizard::handleConfigurationApplied(const QString &configError)
{
m_configApplied = true;
m_configError = configError;
if (m_configError.isEmpty()) {
m_configError = configError;
}
emit configApplied();
}
@ -456,6 +602,7 @@ void MainConfigWizardPage::initializePage()
setSubTitle(tr("Looks like Syncthing is already running and Syncthing Tray can be configured accordingly automatically."));
m_ui->cfgCurrentlyRunningRadioButton->show();
m_ui->cfgCurrentlyRunningRadioButton->setChecked(true);
m_ui->invalidConfigLabel->hide();
} else {
// propose options to launch Syncthing if it is not running
auto launchOptions = QStringList();
@ -467,9 +614,11 @@ void MainConfigWizardPage::initializePage()
const auto canLaunchViaSystemUnit = detection.systemService.canEnableOrStart();
if (canLaunchViaUserUnit) {
m_ui->cfgSystemdUserUnitRadioButton->show();
m_ui->cfgSystemdUserUnitRadioButton->setText(m_ui->cfgSystemdUserUnitRadioButton->text().arg(detection.userService.unitName()));
}
if (canLaunchViaSystemUnit) {
m_ui->cfgSystemdSystemUnitRadioButton->show();
m_ui->cfgSystemdSystemUnitRadioButton->setText(m_ui->cfgSystemdSystemUnitRadioButton->text().arg(detection.systemService.unitName()));
}
if (canLaunchViaUserUnit) {
m_ui->cfgSystemdUserUnitRadioButton->setChecked(true);
@ -484,17 +633,19 @@ void MainConfigWizardPage::initializePage()
// enable options to launch Syncthing via built-in launcher if Syncthing executable found or libsyncthing available
const auto successfulTestLaunch = detection.launcherExitCode.has_value() && detection.launcherExitStatus.value() == QProcess::NormalExit;
if (successfulTestLaunch || Data::SyncthingLauncher::isLibSyncthingAvailable()) {
launchOptions << tr("Syncthing Tray");
launchOptions << tr("Syncthing Tray's launcher");
if (successfulTestLaunch) {
m_ui->cfgLauncherExternalRadioButton->show();
}
if (Data::SyncthingLauncher::isLibSyncthingAvailable()) {
m_ui->cfgLauncherBuiltInRadioButton->show();
}
if (successfulTestLaunch) {
m_ui->cfgLauncherExternalRadioButton->setChecked(true);
} else {
m_ui->cfgLauncherBuiltInRadioButton->setChecked(true);
if (launchOptions.isEmpty()) {
if (successfulTestLaunch) {
m_ui->cfgLauncherExternalRadioButton->setChecked(true);
} else {
m_ui->cfgLauncherBuiltInRadioButton->setChecked(true);
}
}
}
@ -503,6 +654,15 @@ void MainConfigWizardPage::initializePage()
} else {
setSubTitle(tr("Looks like Syncthing is not running yet and needs to be installed before Syncthing Tray can be configured."));
}
if (detection.configFilePath.isEmpty() || detection.configOk) {
m_ui->invalidConfigLabel->hide();
} else {
m_ui->invalidConfigLabel->setText(tr("<b>The Syncthing config could be located under \"%1\" but it seems invalid/incomplete.</b> Hence "
"Syncthing is assumed to be not running.")
.arg(detection.configFilePath));
m_ui->invalidConfigLabel->show();
}
}
handleSelectionChanged();
@ -689,12 +849,12 @@ void ApplyWizardPage::initializePage()
mainConfig = tr("Start Syncthing via Syncthing Tray's launcher");
extraInfo = wizard->mainConfig() == MainConfiguration::LauncherExternal
? tr("executable from PATH as separate process, \"%1\"").arg(QString::fromLocal8Bit(detection.launcherOutput.trimmed()))
: tr("built-in Syncthing library, \"%1\"").arg(Data::SyncthingLauncher::isLibSyncthingAvailable());
: tr("built-in Syncthing library, \"%1\"").arg(Data::SyncthingLauncher::libSyncthingVersionInfo());
break;
case MainConfiguration::SystemdUserUnit:
case MainConfiguration::SystemdSystemUnit:
#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
mainConfig = tr("Start Syncthing by enabling and starting its systemd unit ()");
mainConfig = tr("Start Syncthing by enabling and starting its systemd unit");
extraInfo = wizard->mainConfig() == MainConfiguration::SystemdUserUnit
? tr("Using user unit \"%1\"").arg(detection.userService.unitName())
: tr("Using system unit \"%1\"").arg(detection.systemService.unitName());
@ -737,8 +897,6 @@ FinalWizardPage::FinalWizardPage(QWidget *parent)
m_label->setWordWrap(true);
m_progressBar->setMaximum(0);
auto *const wizard = qobject_cast<Wizard *>(this->wizard());
connect(wizard, &Wizard::configApplied, this, &QWizardPage::completeChanged);
connect(m_label, &QLabel::linkActivated, this, &FinalWizardPage::handleLinkActivated);
}

View File

@ -66,8 +66,11 @@ private Q_SLOTS:
void showDetailsFromSetupDetection();
void handleConfigurationSelected(MainConfiguration mainConfig, ExtraConfiguration extraConfig);
void handleAutostartSelected(bool autostartEnabled);
void pollForSyncthingConfig();
private:
QString hintAboutSyncthingLog() const;
static Wizard *s_instance;
std::unique_ptr<SetupDetection> m_setupDetection;
MainConfiguration m_mainConfig = MainConfiguration::None;
@ -75,6 +78,7 @@ private:
bool m_autoStart = false;
bool m_configApplied = false;
QString m_configError;
int m_elapsedPollTime;
};
inline MainConfiguration Wizard::mainConfig() const