syncthingtray/plasmoid/lib/syncthingapplet.cpp
Martchus 2b1c993a64 Handle systemd service state change in Plasmoid
Previously only the initial service state was considered.
After refactoring the SyncthingService initialization the
state is not known when the Plasmoid is initialized so
this limitation became obvious and had to be fixed.
2018-04-12 23:16:51 +02:00

421 lines
14 KiB
C++

#include "./syncthingapplet.h"
#include "./settingsdialog.h"
#include "../../connector/syncthingservice.h"
#include "../../connector/utils.h"
#include "../../widgets/misc/errorviewdialog.h"
#include "../../widgets/misc/otherdialogs.h"
#include "../../widgets/misc/textviewdialog.h"
#include "../../widgets/settings/settings.h"
#include "../../widgets/settings/settingsdialog.h"
#include "../../widgets/webview/webviewdialog.h"
#include "../../model/syncthingicons.h"
#include "../../connector/utils.h"
#include "resources/config.h"
#include <qtutilities/misc/desktoputils.h>
#include <qtutilities/misc/dialogutils.h>
#include <qtutilities/resources/resources.h>
#include <c++utilities/application/argumentparser.h>
#include <KConfigGroup>
#include <QClipboard>
#include <QDesktopServices>
#include <QGuiApplication>
#include <QNetworkReply>
#include <QQmlEngine>
#include <QStringBuilder>
#include <iostream>
using namespace std;
using namespace Data;
using namespace Plasma;
using namespace Dialogs;
using namespace QtGui;
using namespace ChronoUtilities;
namespace Plasmoid {
SyncthingApplet::SyncthingApplet(QObject *parent, const QVariantList &data)
: Applet(parent, data)
, m_aboutDlg(nullptr)
, m_connection()
, m_notifier(m_connection)
, m_dirModel(m_connection)
, m_devModel(m_connection)
, m_downloadModel(m_connection)
, m_settingsDlg(nullptr)
#ifndef SYNCTHINGWIDGETS_NO_WEBVIEW
, m_webViewDlg(nullptr)
#endif
, m_currentConnectionConfig(-1)
, m_initialized(false)
{
qmlRegisterUncreatableMetaObject(Data::staticMetaObject, "martchus.syncthingplasmoid", 0, 6, "Data", QStringLiteral("only enums"));
}
SyncthingApplet::~SyncthingApplet()
{
delete m_settingsDlg;
#ifndef SYNCTHINGWIDGETS_NO_WEBVIEW
delete m_webViewDlg;
#endif
#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
SyncthingService::setMainInstance(nullptr);
#endif
}
void SyncthingApplet::init()
{
LOAD_QT_TRANSLATIONS;
Applet::init();
// connect signals and slots
connect(&m_notifier, &SyncthingNotifier::statusChanged, this, &SyncthingApplet::handleConnectionStatusChanged);
connect(&m_notifier, &SyncthingNotifier::syncComplete, &m_dbusNotifier, &DBusStatusNotifier::showSyncComplete);
connect(&m_notifier, &SyncthingNotifier::disconnected, &m_dbusNotifier, &DBusStatusNotifier::showDisconnect);
connect(&m_connection, &SyncthingConnection::newDevices, this, &SyncthingApplet::handleDevicesChanged);
connect(&m_connection, &SyncthingConnection::devStatusChanged, this, &SyncthingApplet::handleDevicesChanged);
connect(&m_connection, &SyncthingConnection::error, this, &SyncthingApplet::handleInternalError);
connect(&m_connection, &SyncthingConnection::trafficChanged, this, &SyncthingApplet::trafficChanged);
connect(&m_connection, &SyncthingConnection::newNotification, this, &SyncthingApplet::handleNewNotification);
connect(&m_dbusNotifier, &DBusStatusNotifier::connectRequested, &m_connection,
static_cast<void (SyncthingConnection::*)(void)>(&SyncthingConnection::connect));
connect(&m_dbusNotifier, &DBusStatusNotifier::dismissNotificationsRequested, this, &SyncthingApplet::dismissNotifications);
connect(&m_dbusNotifier, &DBusStatusNotifier::showNotificationsRequested, this, &SyncthingApplet::showNotificationsDialog);
connect(&m_dbusNotifier, &DBusStatusNotifier::errorDetailsRequested, this, &SyncthingApplet::showInternalErrorsDialog);
// restore settings
Settings::restore();
// load primary connection config
setCurrentConnectionConfigIndex(config().readEntry<int>("selectedConfig", 0));
// initialize systemd service support
#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
SyncthingService::setMainInstance(&m_service);
m_service.setUnitName(Settings::values().systemd.syncthingUnit);
connect(&m_service, &SyncthingService::systemdAvailableChanged, this, &SyncthingApplet::handleSystemdStatusChanged);
connect(&m_service, &SyncthingService::stateChanged, this, &SyncthingApplet::handleSystemdStatusChanged);
connect(&m_service, &SyncthingService::errorOccurred, this, &SyncthingApplet::handleSystemdServiceError);
#endif
// apply settings and connect according to settings
handleSettingsChanged();
m_initialized = true;
}
QIcon SyncthingApplet::statusIcon() const
{
return m_statusInfo.statusIcon();
}
QString SyncthingApplet::incomingTraffic() const
{
return trafficString(m_connection.totalIncomingTraffic(), m_connection.totalIncomingRate());
}
QString SyncthingApplet::outgoingTraffic() const
{
return trafficString(m_connection.totalOutgoingTraffic(), m_connection.totalOutgoingRate());
}
QStringList SyncthingApplet::connectionConfigNames() const
{
const auto &settings = Settings::values().connection;
QStringList names;
names.reserve(static_cast<int>(settings.secondary.size() + 1));
names << settings.primary.label;
for (const auto &setting : settings.secondary) {
names << setting.label;
}
return names;
}
QString SyncthingApplet::currentConnectionConfigName() const
{
const auto &settings = Settings::values().connection;
if (m_currentConnectionConfig == 0) {
return settings.primary.label;
} else if (m_currentConnectionConfig > 0 && static_cast<unsigned>(m_currentConnectionConfig) <= settings.secondary.size()) {
return settings.secondary[static_cast<unsigned>(m_currentConnectionConfig) - 1].label;
}
return QString();
}
Data::SyncthingConnectionSettings *SyncthingApplet::connectionConfig(int index)
{
auto &connectionSettings = Settings::values().connection;
if (index >= 0 && static_cast<unsigned>(index) <= connectionSettings.secondary.size()) {
return index == 0 ? &connectionSettings.primary : &connectionSettings.secondary[static_cast<unsigned>(index) - 1];
}
return nullptr;
}
void SyncthingApplet::setCurrentConnectionConfigIndex(int index)
{
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);
}
#endif
config().writeEntry<int>("selectedConfig", 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::isStartStopEnabled() const
{
return Settings::values().systemd.showButton;
}
bool SyncthingApplet::areNotificationsAvailable() const
{
return !m_notifications.empty();
}
void SyncthingApplet::updateStatusIconAndTooltip()
{
m_statusInfo.updateConnectionStatus(m_connection);
m_statusInfo.updateConnectedDevices(m_connection);
emit connectionStatusChanged();
}
void SyncthingApplet::showSettingsDlg()
{
if (!m_settingsDlg) {
m_settingsDlg = setupSettingsDialog(*this);
// ensure settings take effect when applied
connect(m_settingsDlg, &Dialogs::SettingsDialog::applied, this, &SyncthingApplet::handleSettingsChanged);
// save plasmoid specific settings to disk when applied
connect(m_settingsDlg, &Dialogs::SettingsDialog::applied, this, &SyncthingApplet::configChanged);
// save global/general settings to disk when applied
connect(m_settingsDlg, &Dialogs::SettingsDialog::applied, &Settings::save);
}
Dialogs::centerWidget(m_settingsDlg);
m_settingsDlg->show();
m_settingsDlg->activateWindow();
}
void SyncthingApplet::showWebUI()
{
#ifndef SYNCTHINGWIDGETS_NO_WEBVIEW
if (Settings::values().webView.disabled) {
#endif
QDesktopServices::openUrl(m_connection.syncthingUrl());
#ifndef SYNCTHINGWIDGETS_NO_WEBVIEW
} else {
if (!m_webViewDlg) {
m_webViewDlg = new WebViewDialog;
if (const auto *connectionConfig = currentConnectionConfig()) {
m_webViewDlg->applySettings(*connectionConfig);
}
connect(m_webViewDlg, &WebViewDialog::destroyed, this, &SyncthingApplet::handleWebViewDeleted);
}
m_webViewDlg->show();
m_webViewDlg->activateWindow();
}
#endif
}
void SyncthingApplet::showLog()
{
auto *const dlg = TextViewDialog::forLogEntries(m_connection);
dlg->setAttribute(Qt::WA_DeleteOnClose, true);
centerWidget(dlg);
dlg->show();
}
void SyncthingApplet::showOwnDeviceId()
{
auto *const dlg = ownDeviceIdDialog(m_connection);
dlg->setAttribute(Qt::WA_DeleteOnClose, true);
centerWidget(dlg);
dlg->show();
}
void SyncthingApplet::showAboutDialog()
{
if (!m_aboutDlg) {
m_aboutDlg = new AboutDialog(nullptr, QStringLiteral(APP_NAME), QStringLiteral(APP_AUTHOR "\nSyncthing icons from Syncthing project"),
QStringLiteral(APP_VERSION), ApplicationUtilities::dependencyVersions, QStringLiteral(APP_URL), QStringLiteral(APP_DESCRIPTION),
QImage(statusIcons().scanninig.pixmap(128).toImage()));
m_aboutDlg->setWindowTitle(tr("About") + QStringLiteral(" - " APP_NAME));
m_aboutDlg->setWindowIcon(QIcon::fromTheme(QStringLiteral("syncthingtray")));
m_aboutDlg->setAttribute(Qt::WA_DeleteOnClose);
connect(m_aboutDlg, &QObject::destroyed, this, &SyncthingApplet::handleAboutDialogDeleted);
}
centerWidget(m_aboutDlg);
m_aboutDlg->show();
m_aboutDlg->activateWindow();
}
void SyncthingApplet::showNotificationsDialog()
{
auto *const dlg = TextViewDialog::forLogEntries(m_notifications, tr("New notifications"));
dlg->setAttribute(Qt::WA_DeleteOnClose, true);
centerWidget(dlg);
dlg->show();
dismissNotifications();
}
void SyncthingApplet::dismissNotifications()
{
m_connection.considerAllNotificationsRead();
if (!m_notifications.empty()) {
m_notifications.clear();
emit notificationsAvailableChanged(false);
// update status as well because having or not having notifications is relevant for status text/icon
updateStatusIconAndTooltip();
}
}
void SyncthingApplet::showInternalErrorsDialog()
{
auto *const errorViewDlg = ErrorViewDialog::instance();
connect(errorViewDlg, &ErrorViewDialog::errorsCleared, this, &SyncthingApplet::handleErrorsCleared);
centerWidget(errorViewDlg);
errorViewDlg->show();
}
void SyncthingApplet::showDirectoryErrors(unsigned int directoryIndex) const
{
const auto &dirs = m_connection.dirInfo();
if (directoryIndex < dirs.size()) {
auto *const dlg = TextViewDialog::forDirectoryErrors(dirs[directoryIndex]);
dlg->setAttribute(Qt::WA_DeleteOnClose, true);
centerWidget(dlg);
dlg->show();
}
}
void SyncthingApplet::copyToClipboard(const QString &text)
{
QGuiApplication::clipboard()->setText(text);
}
/*!
* \brief Ensures settings take effect when applied via the settings dialog.
* \remarks Does not save the settings to disk. This is done in Settings::save() and Applet::configChanged().
*/
void SyncthingApplet::handleSettingsChanged()
{
const KConfigGroup config(this->config());
const auto &settings(Settings::values());
// apply notifiction settings
settings.apply(m_notifier);
// apply appearance settings
setSize(config.readEntry<QSize>("size", QSize(25, 25)));
const bool brightColors = config.readEntry<bool>("brightColors", false);
m_dirModel.setBrightColors(brightColors);
m_devModel.setBrightColors(brightColors);
m_downloadModel.setBrightColors(brightColors);
// apply connection config
const int currentConfig = m_currentConnectionConfig;
m_currentConnectionConfig = -1; // force update
setCurrentConnectionConfigIndex(currentConfig);
emit settingsChanged();
}
void SyncthingApplet::handleConnectionStatusChanged(SyncthingStatus status)
{
VAR_UNUSED(status)
if (!m_initialized) {
return;
}
// update status icon and tooltip text
m_statusInfo.updateConnectionStatus(m_connection);
m_statusInfo.updateConnectedDevices(m_connection);
emit connectionStatusChanged();
}
void SyncthingApplet::handleDevicesChanged()
{
m_statusInfo.updateConnectedDevices(m_connection);
}
void SyncthingApplet::handleInternalError(
const QString &errorMsg, SyncthingErrorCategory category, int networkError, const QNetworkRequest &request, const QByteArray &response)
{
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()
{
}
void SyncthingApplet::handleAboutDialogDeleted()
{
m_aboutDlg = nullptr;
}
void SyncthingApplet::handleWebViewDeleted()
{
m_webViewDlg = nullptr;
}
void SyncthingApplet::handleNewNotification(DateTime when, const QString &msg)
{
m_notifications.emplace_back(QString::fromLocal8Bit(when.toString(DateTimeOutputFormat::DateAndTime, true).data()), msg);
if (Settings::values().notifyOn.syncthingErrors) {
m_dbusNotifier.showSyncthingNotification(when, msg);
}
if (m_notifications.size() == 1) {
emit notificationsAvailableChanged(true);
// update status as well because having or not having notifications is relevant for status text/icon
updateStatusIconAndTooltip();
}
}
void SyncthingApplet::handleSystemdServiceError(const QString &context, const QString &name, const QString &message)
{
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")
#include "syncthingapplet.moc"