Refactor preventing unwanted error messages

* See https://github.com/Martchus/syncthingtray/issues/15
* Not tested yet
This commit is contained in:
Martchus 2018-04-02 20:26:00 +02:00
parent b81c316d4a
commit 146d4870e4
20 changed files with 432 additions and 187 deletions

View File

@ -184,6 +184,15 @@ void SyncthingConnection::connect(SyncthingConnectionSettings &connectionSetting
}
}
void SyncthingConnection::connectLater(int milliSeconds)
{
// skip if conneting via auto-reconnect anyways
if (autoReconnectInterval() > 0 && milliSeconds < autoReconnectInterval()) {
return;
}
QTimer::singleShot(milliSeconds, this, static_cast<void (SyncthingConnection::*)(void)>(&SyncthingConnection::connect));
}
/*!
* \brief Disconnects. Does nothing if not connected.
*/
@ -223,6 +232,11 @@ void SyncthingConnection::reconnect(SyncthingConnectionSettings &connectionSetti
reconnect();
}
void SyncthingConnection::reconnectLater(int milliSeconds)
{
QTimer::singleShot(milliSeconds, this, static_cast<void (SyncthingConnection::*)(void)>(&SyncthingConnection::reconnect));
}
/*!
* \brief Internally called to reconnect; ensures currently cached config is cleared.
*/

View File

@ -137,9 +137,11 @@ public Q_SLOTS:
bool applySettings(SyncthingConnectionSettings &connectionSettings);
void connect();
void connect(SyncthingConnectionSettings &connectionSettings);
void connectLater(int milliSeconds);
void disconnect();
void reconnect();
void reconnect(SyncthingConnectionSettings &connectionSettings);
void reconnectLater(int milliSeconds);
bool pauseDevice(const QStringList &devIds);
bool pauseAllDevs();
bool resumeDevice(const QStringList &devIds);

View File

@ -1,5 +1,6 @@
#include "./syncthingnotifier.h"
#include "./syncthingconnection.h"
#include "./syncthingprocess.h"
#include "./utils.h"
#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
@ -30,8 +31,10 @@ SyncthingNotifier::SyncthingNotifier(const SyncthingConnection &connection, QObj
#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
, m_service(syncthingService())
#endif
, m_process(syncthingProcess())
, m_enabledNotifications(SyncthingHighLevelNotification::None)
, m_previousStatus(SyncthingStatus::Disconnected)
, m_ignoreInavailabilityAfterStart(15)
, m_initialized(false)
{
connect(&connection, &SyncthingConnection::statusChanged, this, &SyncthingNotifier::handleStatusChangedEvent);
@ -54,6 +57,41 @@ void SyncthingNotifier::handleStatusChangedEvent(SyncthingStatus newStatus)
m_previousStatus = newStatus;
}
bool SyncthingNotifier::isDisconnectRelevant() const
{
// skip disconnect if not initialized
if (!m_initialized) {
return false;
}
// skip further considerations if connection is remote
if (!m_connection.isLocal()) {
return true;
}
// consider process/launcher or systemd unit status
if (m_process.isManuallyStopped()) {
return false;
}
#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
if (m_service.isManuallyStopped()) {
return false;
}
// ignore inavailability after start or standby-wakeup
if (m_ignoreInavailabilityAfterStart) {
if (m_process.isRunning() && !m_service.isActiveWithoutSleepFor(m_process.activeSince(), m_ignoreInavailabilityAfterStart)) {
return false;
}
if (m_service.isRunning() && !m_service.isActiveWithoutSleepFor(m_ignoreInavailabilityAfterStart)) {
return false;
}
}
#endif
return true;
}
/*!
* \brief Emits the connected() or disconnected() signal.
*/
@ -66,11 +104,7 @@ void SyncthingNotifier::emitConnectedAndDisconnected(SyncthingStatus newStatus)
switch (newStatus) {
case SyncthingStatus::Disconnected:
if (m_initialized
#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
&& m_service.isManuallyStopped()
#endif
) {
if (isDisconnectRelevant()) {
emit disconnected();
}
break;

View File

@ -14,6 +14,7 @@ namespace Data {
enum class SyncthingStatus;
class SyncthingConnection;
class SyncthingService;
class SyncthingProcess;
struct SyncthingDir;
struct SyncthingDev;
@ -48,14 +49,17 @@ constexpr bool operator&(SyncthingHighLevelNotification lhs, SyncthingHighLevelN
class LIB_SYNCTHING_CONNECTOR_EXPORT SyncthingNotifier : public QObject {
Q_OBJECT
Q_PROPERTY(SyncthingHighLevelNotification enabledNotifications READ enabledNotifications WRITE setEnabledNotifications)
Q_PROPERTY(bool ignoreInavailabilityAfterStart READ ignoreInavailabilityAfterStart WRITE setIgnoreInavailabilityAfterStart)
public:
SyncthingNotifier(const SyncthingConnection &connection, QObject *parent = nullptr);
SyncthingHighLevelNotification enabledNotifications() const;
unsigned int ignoreInavailabilityAfterStart() const;
public Q_SLOTS:
void setEnabledNotifications(SyncthingHighLevelNotification enabledNotifications);
void setIgnoreInavailabilityAfterStart(unsigned int seconds);
Q_SIGNALS:
///! \brief Emitted when the connection status changes. Also provides the previous status.
@ -71,6 +75,7 @@ private Q_SLOTS:
void handleStatusChangedEvent(SyncthingStatus newStatus);
private:
bool isDisconnectRelevant() const;
void emitConnectedAndDisconnected(SyncthingStatus newStatus);
void emitSyncComplete(ChronoUtilities::DateTime when, const SyncthingDir &dir, int index, const SyncthingDev *remoteDev);
@ -78,8 +83,10 @@ private:
#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
const SyncthingService &m_service;
#endif
const SyncthingProcess &m_process;
SyncthingHighLevelNotification m_enabledNotifications;
SyncthingStatus m_previousStatus;
unsigned int m_ignoreInavailabilityAfterStart;
bool m_initialized;
};
@ -99,6 +106,22 @@ inline void SyncthingNotifier::setEnabledNotifications(SyncthingHighLevelNotific
m_enabledNotifications = enabledNotifications;
}
/*!
* \brief Returns the number of seconds after startup or standby-wakeup to supress disconnect notifications.
*/
inline unsigned int SyncthingNotifier::ignoreInavailabilityAfterStart() const
{
return m_ignoreInavailabilityAfterStart;
}
/*!
* \brief Prevents disconnect notifications in the first \a seconds after startup or standby-wakeup.
*/
inline void SyncthingNotifier::setIgnoreInavailabilityAfterStart(unsigned int seconds)
{
m_ignoreInavailabilityAfterStart = seconds;
}
} // namespace Data
#endif // DATA_SYNCTHINGNOTIFIER_H

View File

@ -2,52 +2,68 @@
#include <QTimer>
using namespace ChronoUtilities;
namespace Data {
SyncthingProcess::SyncthingProcess(QObject *parent)
: QProcess(parent)
, m_manuallyStopped(false)
{
setProcessChannelMode(QProcess::MergedChannels);
connect(this, &SyncthingProcess::started, this, &SyncthingProcess::handleStarted);
connect(this, static_cast<void (SyncthingProcess::*)(int exitCode, QProcess::ExitStatus exitStatus)>(&SyncthingProcess::finished), this,
&SyncthingProcess::handleFinished);
}
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, &SyncthingProcess::killToRestart);
terminate();
} else {
if (!isRunning()) {
startSyncthing(cmd);
return;
}
m_cmd = cmd;
m_manuallyStopped = true;
// give Syncthing 5 seconds to terminate, otherwise kill it
QTimer::singleShot(5000, this, &SyncthingProcess::killToRestart);
terminate();
}
void SyncthingProcess::startSyncthing(const QString &cmd)
{
if (state() == QProcess::NotRunning) {
if (cmd.isEmpty()) {
start(QProcess::ReadOnly);
} else {
start(cmd, QProcess::ReadOnly);
}
if (isRunning()) {
return;
}
m_manuallyStopped = false;
if (cmd.isEmpty()) {
start(QProcess::ReadOnly);
} else {
start(cmd, QProcess::ReadOnly);
}
}
void SyncthingProcess::stopSyncthing()
{
if (state() == QProcess::Running) {
// give Syncthing 5 seconds to terminate, otherwise kill it
QTimer::singleShot(5000, this, &SyncthingProcess::kill);
terminate();
if (!isRunning()) {
return;
}
m_manuallyStopped = true;
// give Syncthing 5 seconds to terminate, otherwise kill it
QTimer::singleShot(5000, this, &SyncthingProcess::kill);
terminate();
}
void SyncthingProcess::handleStarted()
{
m_activeSince = DateTime::gmtNow();
}
void SyncthingProcess::handleFinished(int exitCode, QProcess::ExitStatus exitStatus)
{
Q_UNUSED(exitCode)
Q_UNUSED(exitStatus)
m_activeSince = DateTime();
if (!m_cmd.isEmpty()) {
startSyncthing(m_cmd);
m_cmd.clear();

View File

@ -3,14 +3,24 @@
#include "./global.h"
#include <c++utilities/chrono/datetime.h>
#include <QProcess>
namespace Data {
class LIB_SYNCTHING_CONNECTOR_EXPORT SyncthingProcess : public QProcess {
Q_OBJECT
Q_PROPERTY(bool running READ isRunning)
Q_PROPERTY(ChronoUtilities::DateTime activeSince READ activeSince)
Q_PROPERTY(bool manuallyStopped READ isManuallyStopped)
public:
explicit SyncthingProcess(QObject *parent = nullptr);
bool isRunning() const;
ChronoUtilities::DateTime activeSince() const;
bool isActiveFor(unsigned int atLeastSeconds) const;
bool isManuallyStopped() const;
public Q_SLOTS:
void restartSyncthing(const QString &cmd);
@ -18,13 +28,36 @@ public Q_SLOTS:
void stopSyncthing();
private Q_SLOTS:
void handleStarted();
void handleFinished(int exitCode, QProcess::ExitStatus exitStatus);
void killToRestart();
private:
QString m_cmd;
ChronoUtilities::DateTime m_activeSince;
bool m_manuallyStopped;
};
inline bool SyncthingProcess::isRunning() const
{
return state() != QProcess::NotRunning;
}
inline ChronoUtilities::DateTime SyncthingProcess::activeSince() const
{
return m_activeSince;
}
inline bool SyncthingProcess::isActiveFor(unsigned int atLeastSeconds) const
{
return !m_activeSince.isNull() && (ChronoUtilities::DateTime::gmtNow() - m_activeSince).totalSeconds() > atLeastSeconds;
}
inline bool SyncthingProcess::isManuallyStopped() const
{
return m_manuallyStopped;
}
SyncthingProcess LIB_SYNCTHING_CONNECTOR_EXPORT &syncthingProcess();
} // namespace Data

View File

@ -109,18 +109,17 @@ bool SyncthingService::isUnitAvailable() const
return m_unit && m_unit->isValid();
}
bool SyncthingService::isActiveWithoutSleepFor(unsigned int atLeastSeconds) const
bool SyncthingService::isActiveWithoutSleepFor(DateTime activeSince, unsigned int atLeastSeconds)
{
if (!atLeastSeconds) {
return true;
}
if (m_activeSince.isNull() || s_fallingAsleep) {
if (activeSince.isNull() || s_fallingAsleep) {
return false;
}
const DateTime now(DateTime::gmtNow());
return ((now - m_activeSince).totalSeconds() > atLeastSeconds)
&& (s_lastWakeUp.isNull() || ((now - s_lastWakeUp).totalSeconds() > atLeastSeconds));
return ((now - activeSince).totalSeconds() > atLeastSeconds) && (s_lastWakeUp.isNull() || ((now - s_lastWakeUp).totalSeconds() > atLeastSeconds));
}
void SyncthingService::setRunning(bool running)

View File

@ -56,6 +56,7 @@ public:
ChronoUtilities::DateTime activeSince() const;
bool isActiveFor(unsigned int atLeastSeconds) const;
bool isActiveWithoutSleepFor(unsigned int atLeastSeconds) const;
static bool isActiveWithoutSleepFor(ChronoUtilities::DateTime activeSince, unsigned int atLeastSeconds);
static ChronoUtilities::DateTime lastWakeUp();
const QString &unitFileState() const;
const QString &description() const;
@ -188,6 +189,11 @@ inline bool SyncthingService::isActiveFor(unsigned int atLeastSeconds) const
return !m_activeSince.isNull() && (ChronoUtilities::DateTime::gmtNow() - m_activeSince).totalSeconds() > atLeastSeconds;
}
inline bool SyncthingService::isActiveWithoutSleepFor(unsigned int atLeastSeconds) const
{
return isActiveWithoutSleepFor(m_activeSince, atLeastSeconds);
}
inline ChronoUtilities::DateTime SyncthingService::lastWakeUp()
{
return s_lastWakeUp;

View File

@ -101,6 +101,7 @@ void SyncthingApplet::init()
#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
SyncthingService &service = syncthingService();
service.setUnitName(Settings::values().systemd.syncthingUnit);
connect(&service, &SyncthingService::systemdAvailableChanged, this, &SyncthingApplet::handleSystemdStatusChanged);
connect(&service, &SyncthingService::errorOccurred, this, &SyncthingApplet::handleSystemdServiceError);
#endif
@ -156,10 +157,11 @@ Data::SyncthingConnectionSettings *SyncthingApplet::connectionConfig(int index)
void SyncthingApplet::setCurrentConnectionConfigIndex(int index)
{
auto &settings = Settings::values().connection;
if (index != m_currentConnectionConfig && index >= 0 && static_cast<unsigned>(index) <= settings.secondary.size()) {
auto &selectedConfig = index == 0 ? settings.primary : settings.secondary[static_cast<unsigned>(index) - 1];
m_connection.connect(selectedConfig);
auto &settings = Settings::values();
bool reconnectRequired = false;
if (index != m_currentConnectionConfig && index >= 0 && static_cast<unsigned>(index) <= settings.connection.secondary.size()) {
auto &selectedConfig = index == 0 ? settings.connection.primary : settings.connection.secondary[static_cast<unsigned>(index) - 1];
reconnectRequired = m_connection.applySettings(selectedConfig);
#ifndef SYNCTHINGWIDGETS_NO_WEBVIEW
if (m_webViewDlg) {
m_webViewDlg->applySettings(selectedConfig);
@ -169,9 +171,18 @@ void SyncthingApplet::setCurrentConnectionConfigIndex(int index)
emit currentConnectionConfigIndexChanged(m_currentConnectionConfig = index);
emit localChanged();
}
// apply systemd settings, reconnect if required and possible
#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
settings.systemd.apply(m_connection, currentConnectionConfig(), reconnectRequired);
#else
if (reconnectRequired || !m_connection.isConnected()) {
m_connection.reconnect();
}
#endif
}
bool SyncthingApplet::isStartStopForServiceEnabled() const
bool SyncthingApplet::isStartStopEnabled() const
{
return Settings::values().systemd.showButton;
}
@ -311,7 +322,7 @@ void SyncthingApplet::handleSettingsChanged()
const auto &settings(Settings::values());
// apply notifiction settings
settings.notifyOn.apply(m_notifier);
settings.apply(m_notifier);
// apply appearance settings
setSize(config.readEntry<QSize>("size", QSize(25, 25)));
@ -349,11 +360,12 @@ void SyncthingApplet::handleDevicesChanged()
void SyncthingApplet::handleInternalError(
const QString &errorMsg, SyncthingErrorCategory category, int networkError, const QNetworkRequest &request, const QByteArray &response)
{
if (InternalError::isRelevant(m_connection, category, networkError)) {
InternalError error(errorMsg, request.url(), response);
m_dbusNotifier.showInternalError(error);
ErrorViewDialog::addError(move(error));
if (!InternalError::isRelevant(m_connection, category, networkError)) {
return;
}
InternalError error(errorMsg, request.url(), response);
m_dbusNotifier.showInternalError(error);
ErrorViewDialog::addError(move(error));
}
void SyncthingApplet::handleErrorsCleared()
@ -388,6 +400,14 @@ void SyncthingApplet::handleSystemdServiceError(const QString &context, const QS
handleInternalError(tr("D-Bus error - unable to ") % context % QChar('\n') % name % QChar(':') % message, SyncthingErrorCategory::SpecificRequest,
QNetworkReply::NoError, QNetworkRequest(), QByteArray());
}
#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
void SyncthingApplet::handleSystemdStatusChanged()
{
Settings::values().systemd.apply(m_connection, currentConnectionConfig());
}
#endif
} // namespace Plasmoid
K_EXPORT_PLASMA_APPLET_WITH_JSON(syncthing, Plasmoid::SyncthingApplet, "metadata.json")

View File

@ -56,7 +56,7 @@ class SyncthingApplet : public Plasma::Applet {
Q_PROPERTY(QString currentConnectionConfigName READ currentConnectionConfigName NOTIFY currentConnectionConfigIndexChanged)
Q_PROPERTY(int currentConnectionConfigIndex READ currentConnectionConfigIndex WRITE setCurrentConnectionConfigIndex NOTIFY
currentConnectionConfigIndexChanged)
Q_PROPERTY(bool startStopForServiceEnabled READ isStartStopForServiceEnabled NOTIFY settingsChanged)
Q_PROPERTY(bool startStopEnabled READ isStartStopEnabled NOTIFY settingsChanged)
Q_PROPERTY(QSize size READ size WRITE setSize NOTIFY sizeChanged)
Q_PROPERTY(bool notificationsAvailable READ areNotificationsAvailable NOTIFY notificationsAvailableChanged)
@ -82,7 +82,7 @@ public:
Data::SyncthingConnectionSettings *currentConnectionConfig();
Data::SyncthingConnectionSettings *connectionConfig(int index);
void setCurrentConnectionConfigIndex(int index);
bool isStartStopForServiceEnabled() const;
bool isStartStopEnabled() const;
QSize size() const;
void setSize(const QSize &size);
bool areNotificationsAvailable() const;
@ -133,6 +133,9 @@ private Q_SLOTS:
#endif
void handleNewNotification(ChronoUtilities::DateTime when, const QString &msg);
void handleSystemdServiceError(const QString &context, const QString &name, const QString &message);
#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
void handleSystemdStatusChanged();
#endif
private:
Dialogs::AboutDialog *m_aboutDlg;

View File

@ -280,7 +280,7 @@ ColumnLayout {
var nativeInterface = plasmoid.nativeInterface
// the systemd unit status is only relevant when connected to the local instance
if (!nativeInterface.local
|| !nativeInterface.startStopForServiceEnabled) {
|| !nativeInterface.startStopEnabled) {
return "irrelevant"
}
// show start/stop button only when the configured unit is available

View File

@ -48,68 +48,72 @@ void handleSystemdServiceError(const QString &context, const QString &name, cons
int initSyncthingTray(bool windowed, bool waitForTray, const char *connectionConfig)
{
auto &v = Settings::values();
#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
SyncthingService &service = syncthingService();
service.setUnitName(v.systemd.syncthingUnit);
QObject::connect(&service, &SyncthingService::errorOccurred, &handleSystemdServiceError);
#endif
const QString connectionConfigQStr = connectionConfig ? QString::fromLocal8Bit(connectionConfig) : QString();
// get settings
auto &settings = Settings::values();
const auto connectionConfigQStr(connectionConfig ? QString::fromLocal8Bit(connectionConfig) : QString());
// handle "windowed" case
if (windowed) {
v.launcher.autostart();
auto *trayWidget = new TrayWidget(connectionConfigQStr);
settings.launcher.autostart();
auto *const trayWidget = new TrayWidget(connectionConfigQStr);
trayWidget->setAttribute(Qt::WA_DeleteOnClose);
trayWidget->show();
} else {
#ifndef QT_NO_SYSTEMTRAYICON
if (QSystemTrayIcon::isSystemTrayAvailable() || waitForTray) {
v.launcher.autostart();
auto *trayIcon = new TrayIcon(connectionConfigQStr);
trayIcon->show();
if (v.firstLaunch) {
QMessageBox msgBox;
msgBox.setIcon(QMessageBox::Information);
msgBox.setText(
QCoreApplication::translate("main", "You must configure how to connect to Syncthing when using Syncthing Tray the first time."));
msgBox.setInformativeText(QCoreApplication::translate(
"main", "Note that the settings dialog allows importing URL, credentials and API-key from the local Syncthing configuration."));
msgBox.exec();
trayIcon->trayMenu().widget()->showSettingsDialog();
}
} else {
QMessageBox::critical(nullptr, QApplication::applicationName(),
QApplication::translate("main",
"The system tray is (currently) not available. You could open the tray menu as a regular window using the -w flag, though."));
return -1;
}
#else
QMessageBox::critical(nullptr, QApplication::applicationName(),
QApplication::translate("main",
"The Qt libraries have not been built with tray icon support. You could open the tray menu as a regular "
"window using the -w flag, though."));
return -2;
#endif
return 0;
}
#ifndef QT_NO_SYSTEMTRAYICON
// check whether system tray is available
if (!QSystemTrayIcon::isSystemTrayAvailable() && !waitForTray) {
QMessageBox::critical(nullptr, QApplication::applicationName(),
QApplication::translate(
"main", "The system tray is (currently) not available. You could open the tray menu as a regular window using the -w flag, though."));
return -1;
}
// show tray icon
settings.launcher.autostart();
auto *const trayIcon = new TrayIcon(connectionConfigQStr, QApplication::instance());
trayIcon->show();
if (!settings.firstLaunch) {
return 0;
}
// show "first launch" message box
QMessageBox msgBox;
msgBox.setIcon(QMessageBox::Information);
msgBox.setText(QCoreApplication::translate("main", "You must configure how to connect to Syncthing when using Syncthing Tray the first time."));
msgBox.setInformativeText(QCoreApplication::translate(
"main", "Note that the settings dialog allows importing URL, credentials and API-key from the local Syncthing configuration."));
msgBox.exec();
trayIcon->trayMenu().widget().showSettingsDialog();
return 0;
#else
// show error if system tray is not supported by Qt
QMessageBox::critical(nullptr, QApplication::applicationName(),
QApplication::translate("main",
"The Qt libraries have not been built with tray icon support. You could open the tray menu as a regular "
"window using the -w flag, though."));
return -2;
#endif
}
void trigger(bool tray, bool webUi)
{
if (!TrayWidget::instances().empty() && (tray || webUi)) {
TrayWidget *trayWidget = TrayWidget::instances().front();
if (webUi) {
trayWidget->showWebUi();
}
if (tray) {
trayWidget->showAtCursor();
}
if (TrayWidget::instances().empty() || !(tray || webUi)) {
return;
}
auto *const trayWidget = TrayWidget::instances().front();
if (webUi) {
trayWidget->showWebUi();
}
if (tray) {
trayWidget->showAtCursor();
}
}
int runApplication(int argc, const char *const *argv)
{
static bool firstRun = true;
// setup argument parser
SET_APPLICATION_INFO;
CMD_UTILS_CONVERT_ARGS_TO_UTF8;
@ -140,44 +144,54 @@ int runApplication(int argc, const char *const *argv)
if (!qtConfigArgs.qtWidgetsGuiArg().isPresent()) {
return 0;
}
// check whether runApplication() has been called for the first time
static auto firstRun = true;
if (firstRun) {
firstRun = false;
// do first-time initializations
SET_QT_APPLICATION_INFO;
QApplication application(argc, const_cast<char **>(argv));
QGuiApplication::setQuitOnLastWindowClosed(false);
SingleInstance singleInstance(argc, argv);
networkAccessManager().setParent(&singleInstance);
QObject::connect(&singleInstance, &SingleInstance::newInstance, &runApplication);
Settings::restore();
Settings::values().qt.apply();
qtConfigArgs.applySettings(true);
LOAD_QT_TRANSLATIONS;
#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
SyncthingService &service = syncthingService();
service.setUnitName(Settings::values().systemd.syncthingUnit);
QObject::connect(&service, &SyncthingService::errorOccurred, &handleSystemdServiceError);
#endif
int res = initSyncthingTray(windowedArg.isPresent(), waitForTrayArg.isPresent(), connectionArg.firstValue());
// show (first) tray icon and enter main event loop
auto res = initSyncthingTray(windowedArg.isPresent(), waitForTrayArg.isPresent(), connectionArg.firstValue());
if (!res) {
trigger(triggerArg.isPresent(), showWebUiArg.isPresent());
res = application.exec();
}
// perform cleanup, then terminate
Settings::Launcher::terminate();
Settings::save();
return res;
} else {
if (!TrayWidget::instances().empty() && (showWebUiArg.isPresent() || triggerArg.isPresent())) {
// if --webui or --trigger is present don't create a new tray icon, just trigger actions
trigger(triggerArg.isPresent(), showWebUiArg.isPresent());
} else {
const int res = initSyncthingTray(windowedArg.isPresent(), waitForTrayArg.isPresent(), connectionArg.firstValue());
if (!res) {
trigger(triggerArg.isPresent(), showWebUiArg.isPresent());
}
return res;
}
}
return 0;
// trigger actions if --webui or --trigger is present but don't create a new tray icon
if (!TrayWidget::instances().empty() && (showWebUiArg.isPresent() || triggerArg.isPresent())) {
trigger(triggerArg.isPresent(), showWebUiArg.isPresent());
return 0;
}
// create new/additional tray icon
const auto res = initSyncthingTray(windowedArg.isPresent(), waitForTrayArg.isPresent(), connectionArg.firstValue());
if (!res) {
trigger(triggerArg.isPresent(), showWebUiArg.isPresent());
}
return res;
}
int main(int argc, char *argv[])

View File

@ -45,28 +45,28 @@ TrayIcon::TrayIcon(const QString &connectionConfig, QObject *parent)
connect(m_contextMenu.addAction(QIcon::fromTheme(QStringLiteral("internet-web-browser"),
QIcon(QStringLiteral(":/icons/hicolor/scalable/apps/internet-web-browser.svg"))),
tr("Web UI")),
&QAction::triggered, m_trayMenu.widget(), &TrayWidget::showWebUi);
&QAction::triggered, &m_trayMenu.widget(), &TrayWidget::showWebUi);
connect(m_contextMenu.addAction(
QIcon::fromTheme(QStringLiteral("preferences-other"), QIcon(QStringLiteral(":/icons/hicolor/scalable/apps/preferences-other.svg"))),
tr("Settings")),
&QAction::triggered, m_trayMenu.widget(), &TrayWidget::showSettingsDialog);
&QAction::triggered, &m_trayMenu.widget(), &TrayWidget::showSettingsDialog);
connect(m_contextMenu.addAction(
QIcon::fromTheme(QStringLiteral("folder-sync"), QIcon(QStringLiteral(":/icons/hicolor/scalable/actions/folder-sync.svg"))),
tr("Rescan all")),
&QAction::triggered, &m_trayMenu.widget()->connection(), &SyncthingConnection::rescanAllDirs);
&QAction::triggered, &m_trayMenu.widget().connection(), &SyncthingConnection::rescanAllDirs);
connect(m_contextMenu.addAction(
QIcon::fromTheme(QStringLiteral("text-x-generic"), QIcon(QStringLiteral(":/icons/hicolor/scalable/mimetypes/text-x-generic.svg"))),
tr("Log")),
&QAction::triggered, m_trayMenu.widget(), &TrayWidget::showLog);
&QAction::triggered, &m_trayMenu.widget(), &TrayWidget::showLog);
m_errorsAction = m_contextMenu.addAction(
QIcon::fromTheme(QStringLiteral("emblem-error"), QIcon(QStringLiteral(":/icons/hicolor/scalable/emblems/8/emblem-error.svg"))),
tr("Show internal errors"));
m_errorsAction->setVisible(false);
connect(m_errorsAction, &QAction::triggered, this, &TrayIcon::showInternalErrorsDialog);
m_contextMenu.addMenu(m_trayMenu.widget()->connectionsMenu());
m_contextMenu.addMenu(m_trayMenu.widget().connectionsMenu());
connect(m_contextMenu.addAction(
QIcon::fromTheme(QStringLiteral("help-about"), QIcon(QStringLiteral(":/icons/hicolor/scalable/apps/help-about.svg"))), tr("About")),
&QAction::triggered, m_trayMenu.widget(), &TrayWidget::showAboutDialog);
&QAction::triggered, &m_trayMenu.widget(), &TrayWidget::showAboutDialog);
m_contextMenu.addSeparator();
connect(m_contextMenu.addAction(
QIcon::fromTheme(QStringLiteral("window-close"), QIcon(QStringLiteral(":/icons/hicolor/scalable/actions/window-close.svg"))),
@ -75,8 +75,8 @@ TrayIcon::TrayIcon(const QString &connectionConfig, QObject *parent)
setContextMenu(&m_contextMenu);
// connect signals and slots
const SyncthingConnection &connection = m_trayMenu.widget()->connection();
const SyncthingNotifier &notifier = m_trayMenu.widget()->notifier();
const SyncthingConnection &connection = m_trayMenu.widget().connection();
const SyncthingNotifier &notifier = m_trayMenu.widget().notifier();
connect(this, &TrayIcon::activated, this, &TrayIcon::handleActivated);
connect(this, &TrayIcon::messageClicked, this, &TrayIcon::handleMessageClicked);
connect(&connection, &SyncthingConnection::error, this, &TrayIcon::showInternalError);
@ -89,8 +89,8 @@ TrayIcon::TrayIcon(const QString &connectionConfig, QObject *parent)
#ifdef QT_UTILITIES_SUPPORT_DBUS_NOTIFICATIONS
connect(&m_dbusNotifier, &DBusStatusNotifier::connectRequested, &connection,
static_cast<void (SyncthingConnection::*)(void)>(&SyncthingConnection::connect));
connect(&m_dbusNotifier, &DBusStatusNotifier::dismissNotificationsRequested, m_trayMenu.widget(), &TrayWidget::dismissNotifications);
connect(&m_dbusNotifier, &DBusStatusNotifier::showNotificationsRequested, m_trayMenu.widget(), &TrayWidget::showNotifications);
connect(&m_dbusNotifier, &DBusStatusNotifier::dismissNotificationsRequested, &m_trayMenu.widget(), &TrayWidget::dismissNotifications);
connect(&m_dbusNotifier, &DBusStatusNotifier::showNotificationsRequested, &m_trayMenu.widget(), &TrayWidget::showNotifications);
connect(&m_dbusNotifier, &DBusStatusNotifier::errorDetailsRequested, this, &TrayIcon::showInternalErrorsDialog);
connect(&notifier, &SyncthingNotifier::connected, &m_dbusNotifier, &DBusStatusNotifier::hideDisconnect);
#endif
@ -120,7 +120,7 @@ void TrayIcon::handleActivated(QSystemTrayIcon::ActivationReason reason)
// can't catch that event on Plasma 5 anyways
break;
case QSystemTrayIcon::MiddleClick:
m_trayMenu.widget()->showWebUi();
m_trayMenu.widget().showWebUi();
break;
case QSystemTrayIcon::Trigger: {
m_trayMenu.showAtCursor();
@ -136,7 +136,7 @@ void TrayIcon::handleMessageClicked()
case TrayIconMessageClickedAction::None:
return;
case TrayIconMessageClickedAction::DismissNotification:
m_trayMenu.widget()->dismissNotifications();
m_trayMenu.widget().dismissNotifications();
break;
case TrayIconMessageClickedAction::ShowInternalErrors:
showInternalErrorsDialog();
@ -178,20 +178,21 @@ void TrayIcon::handleErrorsCleared()
void TrayIcon::showInternalError(
const QString &errorMsg, SyncthingErrorCategory category, int networkError, const QNetworkRequest &request, const QByteArray &response)
{
if (InternalError::isRelevant(m_trayMenu.widget()->connection(), category, networkError)) {
InternalError error(errorMsg, request.url(), response);
#ifdef QT_UTILITIES_SUPPORT_DBUS_NOTIFICATIONS
if (Settings::values().dbusNotifications) {
m_dbusNotifier.showInternalError(error);
} else
#endif
{
m_messageClickedAction = TrayIconMessageClickedAction::ShowInternalErrors;
showMessage(tr("Error"), errorMsg, QSystemTrayIcon::Critical);
}
ErrorViewDialog::addError(move(error));
m_errorsAction->setVisible(true);
if (!InternalError::isRelevant(m_trayMenu.widget().connection(), category, networkError)) {
return;
}
InternalError error(errorMsg, request.url(), response);
#ifdef QT_UTILITIES_SUPPORT_DBUS_NOTIFICATIONS
if (Settings::values().dbusNotifications) {
m_dbusNotifier.showInternalError(error);
} else
#endif
{
m_messageClickedAction = TrayIconMessageClickedAction::ShowInternalErrors;
showMessage(tr("Error"), errorMsg, QSystemTrayIcon::Critical);
}
ErrorViewDialog::addError(move(error));
m_errorsAction->setVisible(true);
}
void TrayIcon::showSyncthingNotification(ChronoUtilities::DateTime when, const QString &message)
@ -215,7 +216,7 @@ void TrayIcon::showSyncthingNotification(ChronoUtilities::DateTime when, const Q
void TrayIcon::updateStatusIconAndText()
{
const StatusInfo statusInfo(trayMenu().widget()->connection());
const StatusInfo statusInfo(trayMenu().widget().connection());
if (statusInfo.additionalStatusText().isEmpty()) {
setToolTip(statusInfo.statusText());
} else {

View File

@ -15,8 +15,9 @@ TrayMenu::TrayMenu(const QString &connectionConfig, TrayIcon *trayIcon, QWidget
: QMenu(parent)
, m_trayIcon(trayIcon)
{
auto *menuLayout = new QHBoxLayout;
menuLayout->setMargin(0), menuLayout->setSpacing(0);
auto *const menuLayout = new QHBoxLayout;
menuLayout->setMargin(0);
menuLayout->setSpacing(0);
menuLayout->addWidget(m_trayWidget = new TrayWidget(connectionConfig, this));
setLayout(menuLayout);
setPlatformMenu(nullptr);

View File

@ -15,7 +15,7 @@ public:
TrayMenu(const QString &connectionConfig = QString(), TrayIcon *trayIcon = nullptr, QWidget *parent = nullptr);
QSize sizeHint() const;
TrayWidget *widget();
TrayWidget &widget();
TrayIcon *icon();
public slots:
@ -26,9 +26,9 @@ private:
TrayIcon *m_trayIcon;
};
inline TrayWidget *TrayMenu::widget()
inline TrayWidget &TrayMenu::widget()
{
return m_trayWidget;
return *m_trayWidget;
}
inline TrayIcon *TrayMenu::icon()

View File

@ -365,15 +365,13 @@ void TrayWidget::applySettings(const QString &connectionConfig)
const bool reconnectRequired = m_connection.applySettings(*m_selectedConnection);
// apply notifiction settings
settings.notifyOn.apply(m_notifier);
settings.apply(m_notifier);
#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
// reconnect to apply settings considering systemd
const bool couldReconnect = handleSystemdStatusChanged();
if (reconnectRequired && couldReconnect) {
m_connection.reconnect();
}
// apply systemd settings, also enforce reconnect if required and possible
applySystemdSettings(reconnectRequired);
#else
// reconnect if required, not checking whether possible
if (reconnectRequired) {
m_connection.reconnect();
}
@ -500,56 +498,43 @@ void TrayWidget::updateTraffic()
#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
bool TrayWidget::handleSystemdStatusChanged()
{
const SyncthingService &service = syncthingService();
const Settings::Systemd &settings = Settings::values().systemd;
const bool serviceRelevant = service.isSystemdAvailable() && isLocal(QUrl(m_connection.syncthingUrl()));
bool couldConnectNow = true;
return applySystemdSettings();
}
if (serviceRelevant) {
const bool isRunning = service.isRunning();
if (settings.showButton) {
bool TrayWidget::applySystemdSettings(bool reconnectRequired)
{
// update connection
const Settings::Systemd &systemdSettings(Settings::values().systemd);
bool isServiceRelevant, isServiceRunning;
tie(isServiceRelevant, isServiceRunning) = systemdSettings.apply(m_connection, m_selectedConnection, reconnectRequired);
if (isServiceRelevant) {
// update start/stop button
if (systemdSettings.showButton) {
m_ui->startStopPushButton->setVisible(true);
if (isRunning) {
const auto &unitName(syncthingService().unitName());
if (isServiceRunning) {
m_ui->startStopPushButton->setText(tr("Stop"));
m_ui->startStopPushButton->setToolTip(QStringLiteral("systemctl --user stop ") + service.unitName());
m_ui->startStopPushButton->setToolTip(QStringLiteral("systemctl --user stop ") + unitName);
m_ui->startStopPushButton->setIcon(
QIcon::fromTheme(QStringLiteral("process-stop"), QIcon(QStringLiteral(":/icons/hicolor/scalable/actions/process-stop.svg"))));
} else {
m_ui->startStopPushButton->setText(tr("Start"));
m_ui->startStopPushButton->setToolTip(QStringLiteral("systemctl --user start ") + service.unitName());
m_ui->startStopPushButton->setToolTip(QStringLiteral("systemctl --user start ") + unitName);
m_ui->startStopPushButton->setIcon(
QIcon::fromTheme(QStringLiteral("system-run"), QIcon(QStringLiteral(":/icons/hicolor/scalable/apps/system-run.svg"))));
}
}
if (settings.considerForReconnect) {
if (isRunning && m_selectedConnection) {
// auto-reconnect might have been disabled when unit was inactive before, so re-enable it according current connection settings
m_connection.setAutoReconnectInterval(m_selectedConnection->reconnectInterval);
if (!m_connection.isConnected()) {
// FIXME: This will fail if Syncthing has just been started and isn't ready yet
m_connection.connect();
}
} else {
// disable auto-reconnect if unit isn't running
m_connection.setAutoReconnectInterval(0);
couldConnectNow = false;
}
}
}
if (!settings.showButton || !serviceRelevant) {
if (!systemdSettings.showButton || !isServiceRelevant) {
m_ui->startStopPushButton->setVisible(false);
}
if ((!settings.considerForReconnect || !serviceRelevant) && m_selectedConnection) {
m_connection.setAutoReconnectInterval(m_selectedConnection->reconnectInterval);
}
return couldConnectNow;
return isServiceRelevant && isServiceRunning;
}
void TrayWidget::connectIfServiceRunning()
{
if (Settings::values().systemd.considerForReconnect && isLocal(QUrl(m_connection.syncthingUrl())) && syncthingService().isRunning()) {
if (Settings::values().systemd.considerForReconnect && m_connection.isLocal() && syncthingService().isRunning()) {
m_connection.connect();
}
}

View File

@ -75,6 +75,7 @@ private slots:
void updateTraffic();
#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
bool handleSystemdStatusChanged();
bool applySystemdSettings(bool reconnectRequired = false);
void connectIfServiceRunning();
#endif
#ifndef SYNCTHINGWIDGETS_NO_WEBVIEW

View File

@ -3,8 +3,8 @@
#include "../settings/settings.h"
#include "../../connector/syncthingconnection.h"
#include "../../connector/syncthingprocess.h"
#include "../../connector/syncthingservice.h"
#include "../../connector/utils.h"
#include <QNetworkReply>
@ -12,21 +12,51 @@ using namespace Data;
namespace QtGui {
/*!
* \brief Returns whether the error is relevant. Only in this case a notification for the error should be shown.
*/
bool InternalError::isRelevant(const SyncthingConnection &connection, SyncthingErrorCategory category, int networkError)
{
// ignore overall connection errors when auto reconnect tries >= 1
if (category != SyncthingErrorCategory::OverallConnection && connection.autoReconnectTries() >= 1) {
return false;
}
// ignore errors when disabled in settings
const auto &settings = Settings::values();
if (!settings.notifyOn.internalErrors) {
return false;
}
// skip further considerations if connection is remote
if (!connection.isLocal()) {
return true;
}
// consider process/launcher or systemd unit status
const auto remoteHostClosed(networkError == QNetworkReply::RemoteHostClosedError);
// ignore "remote host closed" error if we've just stopped Syncthing ourselves
const SyncthingProcess &process(syncthingProcess());
if (settings.launcher.considerForReconnect && remoteHostClosed && process.isManuallyStopped()) {
return false;
}
#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
const SyncthingService &service = syncthingService();
const bool serviceRelevant = service.isSystemdAvailable() && isLocal(QUrl(connection.syncthingUrl()));
const SyncthingService &service(syncthingService());
if (settings.systemd.considerForReconnect && remoteHostClosed && service.isManuallyStopped()) {
return false;
}
// ignore inavailability after start or standby-wakeup
if (settings.ignoreInavailabilityAfterStart && networkError == QNetworkReply::ConnectionRefusedError) {
if (process.isRunning() && !service.isActiveWithoutSleepFor(process.activeSince(), settings.ignoreInavailabilityAfterStart)) {
return false;
}
if (service.isRunning() && !service.isActiveWithoutSleepFor(settings.ignoreInavailabilityAfterStart)) {
return false;
}
}
#endif
return settings.notifyOn.internalErrors && (connection.autoReconnectTries() < 1 || category != SyncthingErrorCategory::OverallConnection)
#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
&& (!settings.systemd.considerForReconnect || !serviceRelevant
|| !(networkError == QNetworkReply::RemoteHostClosedError && service.isManuallyStopped()))
&& (settings.ignoreInavailabilityAfterStart == 0
|| !(networkError == QNetworkReply::ConnectionRefusedError && service.isRunning()
&& !service.isActiveWithoutSleepFor(settings.ignoreInavailabilityAfterStart)))
#endif
;
return true;
}
} // namespace QtGui

View File

@ -1,6 +1,10 @@
#include "./settings.h"
#include "../../connector/syncthingnotifier.h"
#include "../../connector/syncthingprocess.h"
#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
#include "../../connector/syncthingconnection.h"
#include "../../connector/syncthingservice.h"
#endif
// use meta-data of syncthingtray application here
#include "resources/../../tray/resources/config.h"
@ -295,19 +299,70 @@ void save()
/*!
* \brief Applies the notification settings on the specified \a notifier.
*/
void NotifyOn::apply(SyncthingNotifier &notifier) const
void Settings::apply(SyncthingNotifier &notifier) const
{
auto notifications(SyncthingHighLevelNotification::None);
if (disconnect) {
if (notifyOn.disconnect) {
notifications |= SyncthingHighLevelNotification::ConnectedDisconnected;
}
if (localSyncComplete) {
if (notifyOn.localSyncComplete) {
notifications |= SyncthingHighLevelNotification::LocalSyncComplete;
}
if (remoteSyncComplete) {
if (notifyOn.remoteSyncComplete) {
notifications |= SyncthingHighLevelNotification::RemoteSyncComplete;
}
notifier.setEnabledNotifications(notifications);
notifier.setIgnoreInavailabilityAfterStart(ignoreInavailabilityAfterStart);
}
#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
/*!
* \brief Applies the systemd settings to the specified \a connection considering the status of the global SyncthingService instance.
* \remarks
* - Called by SyncthingApplet and TrayWidget when the status of the SyncthingService changes.
* - \a currentConnectionSettings might be nullptr.
* - Currently this is only about the auto-reconnect interval and connecting instantly.
* \returns Returns whether the service is relevant and running.
*/
std::tuple<bool, bool> Systemd::apply(
Data::SyncthingConnection &connection, const SyncthingConnectionSettings *currentConnectionSettings, bool reconnectRequired) const
{
const SyncthingService &service(syncthingService());
const auto isRelevant = service.isSystemdAvailable() && connection.isLocal();
const auto isRunning = service.isRunning();
if (currentConnectionSettings && (!considerForReconnect || !isRelevant || isRunning)) {
// ensure auto-reconnect is configured according to settings
connection.setAutoReconnectInterval(currentConnectionSettings->reconnectInterval);
} else {
// disable auto-reconnect regardless of the overall settings
connection.setAutoReconnectInterval(0);
}
// connect instantly if service is running
if (considerForReconnect && isRelevant) {
constexpr auto minActiveTimeInSeconds(5);
if (reconnectRequired) {
if (service.isActiveWithoutSleepFor(minActiveTimeInSeconds)) {
connection.reconnect();
} else {
// give the service (which has just started) a few seconds to initialize
connection.reconnectLater(minActiveTimeInSeconds * 1000);
}
} else if (isRunning && !connection.isConnected()) {
if (service.isActiveWithoutSleepFor(minActiveTimeInSeconds)) {
connection.connect();
} else {
// give the service (which has just started) a few seconds to initialize
connection.connectLater(minActiveTimeInSeconds * 1000);
}
}
} else if (reconnectRequired) {
connection.reconnect();
}
return make_tuple(isRelevant, isRunning);
}
#endif
} // namespace Settings

View File

@ -15,6 +15,7 @@
#include <QString>
#include <QTabWidget>
#include <tuple>
#include <vector>
namespace Dialogs {
@ -24,6 +25,7 @@ class QtSettings;
namespace Data {
class SyncthingProcess;
class SyncthingNotifier;
class SyncthingConnection;
} // namespace Data
namespace Settings {
@ -39,8 +41,6 @@ struct SYNCTHINGWIDGETS_EXPORT NotifyOn {
bool localSyncComplete = false;
bool remoteSyncComplete = false;
bool syncthingErrors = true;
void apply(Data::SyncthingNotifier &notifier) const;
};
struct SYNCTHINGWIDGETS_EXPORT Appearance {
@ -67,6 +67,7 @@ struct SYNCTHINGWIDGETS_EXPORT Launcher {
#endif
QString syncthingArgs;
QHash<QString, ToolParameter> tools;
bool considerForReconnect = false;
QString syncthingCmd() const;
QString toolCmd(const QString &tool) const;
static Data::SyncthingProcess &toolProcess(const QString &tool);
@ -79,6 +80,11 @@ struct SYNCTHINGWIDGETS_EXPORT Systemd {
QString syncthingUnit = QStringLiteral("syncthing.service");
bool showButton = false;
bool considerForReconnect = false;
#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
std::tuple<bool, bool> apply(Data::SyncthingConnection &connection, const Data::SyncthingConnectionSettings *currentConnectionSettings,
bool preventReconnect = false) const;
#endif
};
#endif
@ -108,6 +114,8 @@ struct SYNCTHINGWIDGETS_EXPORT Settings {
WebView webView;
#endif
Dialogs::QtSettings qt;
void apply(Data::SyncthingNotifier &notifier) const;
};
Settings SYNCTHINGWIDGETS_EXPORT &values();