diff --git a/fileitemactionplugin/CMakeLists.txt b/fileitemactionplugin/CMakeLists.txt index cd646ca..6846535 100644 --- a/fileitemactionplugin/CMakeLists.txt +++ b/fileitemactionplugin/CMakeLists.txt @@ -7,10 +7,18 @@ set(META_QT5_VERSION 5.8) # add project files set(HEADER_FILES - ${META_PROJECT_NAME}.h + syncthingmenuaction.h + syncthinginfoaction.h + syncthingdiractions.h + syncthingfileitemactionstaticdata.h + syncthingfileitemaction.h ) set(SRC_FILES - ${META_PROJECT_NAME}.cpp + syncthingmenuaction.cpp + syncthinginfoaction.cpp + syncthingdiractions.cpp + syncthingfileitemactionstaticdata.cpp + syncthingfileitemaction.cpp ) set(TS_FILES diff --git a/fileitemactionplugin/syncthingdiractions.cpp b/fileitemactionplugin/syncthingdiractions.cpp new file mode 100644 index 0000000..0e7b458 --- /dev/null +++ b/fileitemactionplugin/syncthingdiractions.cpp @@ -0,0 +1,79 @@ +#include "./syncthingdiractions.h" + +#include "../model/syncthingicons.h" + +#include "../connector/syncthingdir.h" +#include "../connector/utils.h" + +using namespace Data; + +SyncthingDirActions::SyncthingDirActions(const SyncthingDir &dir, QObject *parent) + : QObject(parent) + , m_dirId(dir.id) +{ + m_infoAction.setSeparator(true); + updateStatus(dir); +} + +void SyncthingDirActions::updateStatus(const std::vector &dirs) +{ + for (const SyncthingDir &dir : dirs) { + if (updateStatus(dir)) { + return; + } + } + m_statusAction.setText(tr("Status: not available anymore")); + m_statusAction.setIcon(statusIcons().disconnected); +} + +bool SyncthingDirActions::updateStatus(const SyncthingDir &dir) +{ + if (dir.id != m_dirId) { + return false; + } + m_infoAction.setText(tr("Directory info for %1").arg(dir.displayName())); + m_infoAction.setIcon(QIcon::fromTheme(QStringLiteral("dialog-information"))); + m_statusAction.setText(tr("Status: ") + dir.statusString()); + if (dir.paused && dir.status != SyncthingDirStatus::OutOfSync) { + m_statusAction.setIcon(statusIcons().pause); + } else if (dir.isUnshared()) { + m_statusAction.setIcon(statusIcons().disconnected); + } else { + switch (dir.status) { + case SyncthingDirStatus::Unknown: + m_statusAction.setIcon(statusIcons().disconnected); + break; + case SyncthingDirStatus::Idle: + m_statusAction.setIcon(statusIcons().idling); + break; + case SyncthingDirStatus::Scanning: + m_statusAction.setIcon(statusIcons().scanninig); + break; + case SyncthingDirStatus::Synchronizing: + m_statusAction.setIcon(statusIcons().sync); + break; + case SyncthingDirStatus::OutOfSync: + m_statusAction.setIcon(statusIcons().error); + break; + } + } + m_globalStatusAction.setText(tr("Global: ") + directoryStatusString(dir.globalStats)); + m_localStatusAction.setText(tr("Local: ") + directoryStatusString(dir.localStats)); + m_lastScanAction.setText(tr("Last scan time: ") + agoString(dir.lastScanTime)); + m_lastScanAction.setIcon(QIcon::fromTheme(QStringLiteral("accept_time_event"))); + m_rescanIntervalAction.setText(tr("Rescan interval: %1 seconds").arg(dir.rescanInterval)); + if (!dir.pullErrorCount) { + m_errorsAction.setVisible(false); + } else { + m_errorsAction.setVisible(true); + m_errorsAction.setIcon(QIcon::fromTheme(QStringLiteral("dialog-error"))); + m_errorsAction.setText(tr("%1 item(s) out-of-sync", nullptr, trQuandity(dir.pullErrorCount)).arg(dir.pullErrorCount)); + } + return true; +} + +QList &operator<<(QList &actions, SyncthingDirActions &dirActions) +{ + return actions << &dirActions.m_infoAction << &dirActions.m_statusAction << &dirActions.m_globalStatusAction << &dirActions.m_localStatusAction + << &dirActions.m_lastScanAction << &dirActions.m_rescanIntervalAction << &dirActions.m_errorsAction; +} diff --git a/fileitemactionplugin/syncthingdiractions.h b/fileitemactionplugin/syncthingdiractions.h new file mode 100644 index 0000000..c92776e --- /dev/null +++ b/fileitemactionplugin/syncthingdiractions.h @@ -0,0 +1,32 @@ +#ifndef SYNCTHINGDIRACTIONS_H +#define SYNCTHINGDIRACTIONS_H + +#include "../connector/syncthingdir.h" + +#include "./syncthinginfoaction.h" + +class SyncthingDirActions : public QObject { + Q_OBJECT + friend QList &operator<<(QList &, SyncthingDirActions &); + +public: + explicit SyncthingDirActions(const Data::SyncthingDir &dir, QObject *parent = nullptr); + +public Q_SLOTS: + void updateStatus(const std::vector &dirs); + bool updateStatus(const Data::SyncthingDir &dir); + +private: + QString m_dirId; + QAction m_infoAction; + SyncthingInfoAction m_statusAction; + SyncthingInfoAction m_globalStatusAction; + SyncthingInfoAction m_localStatusAction; + SyncthingInfoAction m_lastScanAction; + SyncthingInfoAction m_rescanIntervalAction; + SyncthingInfoAction m_errorsAction; +}; + +QList &operator<<(QList &actions, SyncthingDirActions &dirActions); + +#endif // SYNCTHINGDIRACTIONS_H diff --git a/fileitemactionplugin/syncthingfileitemaction.cpp b/fileitemactionplugin/syncthingfileitemaction.cpp index c9888e8..16e17e9 100644 --- a/fileitemactionplugin/syncthingfileitemaction.cpp +++ b/fileitemactionplugin/syncthingfileitemaction.cpp @@ -1,38 +1,19 @@ #include "./syncthingfileitemaction.h" +#include "./syncthingdiractions.h" +#include "./syncthinginfoaction.h" +#include "./syncthingmenuaction.h" + #include "../model/syncthingicons.h" -#include "../connector/syncthingconfig.h" -#include "../connector/syncthingconnectionsettings.h" -#include "../connector/syncthingdir.h" -#include "../connector/utils.h" - -#include -#include - -#include - #include #include #include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include -#include - -#include "resources/config.h" using namespace std; -using namespace Dialogs; using namespace Data; K_PLUGIN_FACTORY(SyncthingFileItemActionFactory, registerPlugin();) @@ -56,295 +37,6 @@ SyncthingItem::SyncthingItem(const SyncthingDir *dir, const QString &path) } } -SyncthingMenuAction::SyncthingMenuAction(const KFileItemListProperties &properties, const QList &actions, QWidget *parentWidget) - : QAction(parentWidget) - , m_properties(properties) -{ - if (!actions.isEmpty()) { - auto *menu = new QMenu(parentWidget); - menu->addActions(actions); - setMenu(menu); - } - updateStatus(SyncthingFileItemAction::staticData().connection().status()); -} - -void SyncthingMenuAction::updateStatus(SyncthingStatus status) -{ - if (status != SyncthingStatus::Disconnected && status != SyncthingStatus::Reconnecting && status != SyncthingStatus::BeingDestroyed) { - setText(tr("Syncthing")); - setIcon(statusIcons().scanninig); - if (!menu()) { - const QList actions = SyncthingFileItemAction::createActions(m_properties, parentWidget()); - if (!actions.isEmpty()) { - auto *menu = new QMenu(parentWidget()); - menu->addActions(actions); - setMenu(menu); - } - } - } else { - if (status != SyncthingStatus::Reconnecting) { - SyncthingFileItemAction::staticData().connection().connect(); - } - setText(tr("Syncthing - connecting")); - setIcon(statusIcons().disconnected); - if (QMenu *menu = this->menu()) { - setMenu(nullptr); - delete menu; - } - } -} - -SyncthingInfoWidget::SyncthingInfoWidget(const SyncthingInfoAction *action, QWidget *parent) - : QWidget(parent) - , m_textLabel(new QLabel(parent)) - , m_iconLabel(new QLabel(parent)) -{ - auto *const layout = new QHBoxLayout(parent); - layout->setMargin(4); - layout->setSpacing(5); - m_iconLabel->setFixedWidth(16); - m_iconLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Minimum); - layout->addWidget(m_iconLabel); - layout->addWidget(m_textLabel); - setLayout(layout); - updateFromAction(action); - connect(action, &QAction::changed, this, &SyncthingInfoWidget::updateFromSender); -} - -void SyncthingInfoWidget::updateFromSender() -{ - updateFromAction(qobject_cast(QObject::sender())); -} - -void SyncthingInfoWidget::updateFromAction(const SyncthingInfoAction *action) -{ - auto text(action->text()); - m_textLabel->setText(text.startsWith(QChar('&')) ? text.mid(1) : std::move(text)); - m_iconLabel->setPixmap(action->icon().pixmap(16)); - setVisible(action->isVisible()); -} - -SyncthingInfoAction::SyncthingInfoAction(QObject *parent) - : QWidgetAction(parent) -{ -} - -QWidget *SyncthingInfoAction::createWidget(QWidget *parent) -{ - return new SyncthingInfoWidget(this, parent); -} - -SyncthingDirActions::SyncthingDirActions(const SyncthingDir &dir, QObject *parent) - : QObject(parent) - , m_dirId(dir.id) -{ - m_infoAction.setSeparator(true); - updateStatus(dir); -} - -void SyncthingDirActions::updateStatus(const std::vector &dirs) -{ - for (const SyncthingDir &dir : dirs) { - if (updateStatus(dir)) { - return; - } - } - m_statusAction.setText(tr("Status: not available anymore")); - m_statusAction.setIcon(statusIcons().disconnected); -} - -bool SyncthingDirActions::updateStatus(const SyncthingDir &dir) -{ - if (dir.id != m_dirId) { - return false; - } - m_infoAction.setText(tr("Directory info for %1").arg(dir.displayName())); - m_infoAction.setIcon(QIcon::fromTheme(QStringLiteral("dialog-information"))); - m_statusAction.setText(tr("Status: ") + dir.statusString()); - if (dir.paused && dir.status != SyncthingDirStatus::OutOfSync) { - m_statusAction.setIcon(statusIcons().pause); - } else if (dir.isUnshared()) { - m_statusAction.setIcon(statusIcons().disconnected); - } else { - switch (dir.status) { - case SyncthingDirStatus::Unknown: - m_statusAction.setIcon(statusIcons().disconnected); - break; - case SyncthingDirStatus::Idle: - m_statusAction.setIcon(statusIcons().idling); - break; - case SyncthingDirStatus::Scanning: - m_statusAction.setIcon(statusIcons().scanninig); - break; - case SyncthingDirStatus::Synchronizing: - m_statusAction.setIcon(statusIcons().sync); - break; - case SyncthingDirStatus::OutOfSync: - m_statusAction.setIcon(statusIcons().error); - break; - } - } - m_globalStatusAction.setText(tr("Global: ") + directoryStatusString(dir.globalStats)); - m_localStatusAction.setText(tr("Local: ") + directoryStatusString(dir.localStats)); - m_lastScanAction.setText(tr("Last scan time: ") + agoString(dir.lastScanTime)); - m_lastScanAction.setIcon(QIcon::fromTheme(QStringLiteral("accept_time_event"))); - m_rescanIntervalAction.setText(tr("Rescan interval: %1 seconds").arg(dir.rescanInterval)); - if (!dir.pullErrorCount) { - m_errorsAction.setVisible(false); - } else { - m_errorsAction.setVisible(true); - m_errorsAction.setIcon(QIcon::fromTheme(QStringLiteral("dialog-error"))); - m_errorsAction.setText(tr("%1 item(s) out-of-sync", nullptr, trQuandity(dir.pullErrorCount)).arg(dir.pullErrorCount)); - } - return true; -} - -QList &operator<<(QList &actions, SyncthingDirActions &dirActions) -{ - return actions << &dirActions.m_infoAction << &dirActions.m_statusAction << &dirActions.m_globalStatusAction << &dirActions.m_localStatusAction - << &dirActions.m_lastScanAction << &dirActions.m_rescanIntervalAction << &dirActions.m_errorsAction; -} - -SyncthingFileItemActionStaticData::SyncthingFileItemActionStaticData() - : m_initialized(false) -{ -} - -SyncthingFileItemActionStaticData::~SyncthingFileItemActionStaticData() -{ -} - -void SyncthingFileItemActionStaticData::initialize() -{ - if (m_initialized) { - return; - } - - LOAD_QT_TRANSLATIONS; - - // load settings - const QSettings settingsFile(QSettings::IniFormat, QSettings::UserScope, QStringLiteral(PROJECT_NAME)); - - // determine path of Syncthing config file - m_configFilePath = [&] { - const QByteArray configPathFromEnv(qgetenv("KIO_SYNCTHING_CONFIG_PATH")); - if (!configPathFromEnv.isEmpty()) { - return QString::fromLocal8Bit(configPathFromEnv); - } - const QString configPathFromSettings = settingsFile.value(QStringLiteral("syncthingConfigPath")).toString(); - if (!configPathFromSettings.isEmpty()) { - return configPathFromSettings; - } - return SyncthingConfig::locateConfigFile(); - }(); - applySyncthingConfiguration(m_configFilePath); - - // prevent unnecessary API calls (for the purpose of the context menu) - m_connection.disablePolling(); - - // connect Signals & Slots for logging - connect(&m_connection, &SyncthingConnection::error, this, &SyncthingFileItemActionStaticData::logConnectionError); - if (qEnvironmentVariableIsSet("KIO_SYNCTHING_LOG_STATUS")) { - connect(&m_connection, &SyncthingConnection::statusChanged, this, &SyncthingFileItemActionStaticData::logConnectionStatus); - } - - m_initialized = true; -} - -void SyncthingFileItemActionStaticData::logConnectionStatus() -{ - cerr << "Syncthing connection status changed to: " << m_connection.statusText().toLocal8Bit().data() << endl; -} - -void SyncthingFileItemActionStaticData::logConnectionError(const QString &errorMessage, SyncthingErrorCategory errorCategory) -{ - switch (errorCategory) { - case SyncthingErrorCategory::Parsing: - case SyncthingErrorCategory::SpecificRequest: - QMessageBox::critical(nullptr, tr("Syncthing connection error"), errorMessage); - break; - default: - cerr << "Syncthing connection error: " << errorMessage.toLocal8Bit().data() << endl; - } -} - -void SyncthingFileItemActionStaticData::rescanDir(const QString &dirId, const QString &relpath) -{ - m_connection.rescan(dirId, relpath); -} - -void SyncthingFileItemActionStaticData::showAboutDialog() -{ - auto *aboutDialog = new AboutDialog(nullptr, QStringLiteral(APP_NAME), QStringLiteral(APP_AUTHOR "\nSyncthing icons from Syncthing project"), - QStringLiteral(APP_VERSION), ApplicationUtilities::dependencyVersions2, QStringLiteral(APP_URL), QStringLiteral(APP_DESCRIPTION), - QImage(statusIcons().scanninig.pixmap(128).toImage())); - aboutDialog->setWindowTitle(tr("About") + QStringLiteral(" - " APP_NAME)); - aboutDialog->setWindowIcon(QIcon::fromTheme(QStringLiteral("syncthingtray"))); - aboutDialog->setAttribute(Qt::WA_DeleteOnClose); - aboutDialog->show(); -} - -void SyncthingFileItemActionStaticData::selectSyncthingConfig() -{ - const auto configFilePath = QFileDialog::getOpenFileName(nullptr, tr("Select Syncthing config file") + QStringLiteral(" - " APP_NAME)); - if (!configFilePath.isEmpty() && applySyncthingConfiguration(configFilePath)) { - QSettings(QSettings::IniFormat, QSettings::UserScope, QStringLiteral(PROJECT_NAME)) - .setValue(QStringLiteral("syncthingConfigPath"), m_configFilePath = configFilePath); - } -} - -bool SyncthingFileItemActionStaticData::applySyncthingConfiguration(const QString &syncthingConfigFilePath) -{ - clearCurrentError(); - - // check for empty path - if (syncthingConfigFilePath.isEmpty()) { - setCurrentError(tr("Syncthing config file can not be automatically located")); - return false; - } - - // load Syncthing config - SyncthingConfig config; - if (!config.restore(syncthingConfigFilePath)) { - auto errorMessage = tr("Unable to load Syncthing config from \"%1\"").arg(syncthingConfigFilePath); - if (!m_configFilePath.isEmpty() && m_configFilePath != syncthingConfigFilePath) { - errorMessage += QChar('\n'); - errorMessage += tr("(still using config from \"%1\")").arg(m_configFilePath); - } - setCurrentError(errorMessage); - return false; - } - cerr << "Syncthing config loaded from \"" << syncthingConfigFilePath.toLocal8Bit().data() << "\"" << endl; - - // make connection settings - SyncthingConnectionSettings settings; - settings.syncthingUrl = config.syncthingUrl(); - settings.apiKey.append(config.guiApiKey); - - // establish connection - bool ok; - int reconnectInterval = qEnvironmentVariableIntValue("KIO_SYNCTHING_RECONNECT_INTERVAL", &ok); - if (!ok || reconnectInterval < 0) { - reconnectInterval = 10000; - } - m_connection.setAutoReconnectInterval(reconnectInterval); - m_connection.reconnect(settings); - return true; -} - -void SyncthingFileItemActionStaticData::setCurrentError(const QString ¤tError) -{ - if (m_currentError == currentError) { - return; - } - const bool hadError = hasError(); - m_currentError = currentError; - if (hadError != hasError()) { - emit hasErrorChanged(hasError()); - } - emit currentErrorChanged(m_currentError); -} - SyncthingFileItemActionStaticData SyncthingFileItemAction::s_data; SyncthingFileItemAction::SyncthingFileItemAction(QObject *parent, const QVariantList &) diff --git a/fileitemactionplugin/syncthingfileitemaction.h b/fileitemactionplugin/syncthingfileitemaction.h index f7d976f..cf8104a 100644 --- a/fileitemactionplugin/syncthingfileitemaction.h +++ b/fileitemactionplugin/syncthingfileitemaction.h @@ -1,158 +1,12 @@ #ifndef SYNCTHINGFILEITEMACTION_H #define SYNCTHINGFILEITEMACTION_H -#include "../connector/syncthingconnection.h" +#include "./syncthingfileitemactionstaticdata.h" #include -#include - -#include -#include -#include - -QT_FORWARD_DECLARE_CLASS(QLabel) class KFileItemListProperties; -class SyncthingMenuAction : public QAction { - Q_OBJECT - -public: - explicit SyncthingMenuAction(const KFileItemListProperties &properties = KFileItemListProperties(), - const QList &actions = QList(), QWidget *parentWidget = nullptr); - -public Q_SLOTS: - void updateStatus(Data::SyncthingStatus status); - -private: - KFileItemListProperties m_properties; -}; - -class SyncthingInfoAction; - -class SyncthingInfoWidget : public QWidget { - Q_OBJECT - -public: - explicit SyncthingInfoWidget(const SyncthingInfoAction *action, QWidget *parent = nullptr); - -private Q_SLOTS: - void updateFromSender(); - void updateFromAction(const SyncthingInfoAction *action); - -private: - QLabel *const m_textLabel; - QLabel *const m_iconLabel; -}; - -class SyncthingInfoAction : public QWidgetAction { - Q_OBJECT - -public: - explicit SyncthingInfoAction(QObject *parent = nullptr); - -protected: - QWidget *createWidget(QWidget *parent) override; -}; - -class SyncthingDirActions : public QObject { - Q_OBJECT - friend QList &operator<<(QList &, SyncthingDirActions &); - -public: - explicit SyncthingDirActions(const Data::SyncthingDir &dir, QObject *parent = nullptr); - -public Q_SLOTS: - void updateStatus(const std::vector &dirs); - bool updateStatus(const Data::SyncthingDir &dir); - -private: - QString m_dirId; - QAction m_infoAction; - SyncthingInfoAction m_statusAction; - SyncthingInfoAction m_globalStatusAction; - SyncthingInfoAction m_localStatusAction; - SyncthingInfoAction m_lastScanAction; - SyncthingInfoAction m_rescanIntervalAction; - SyncthingInfoAction m_errorsAction; -}; - -QList &operator<<(QList &actions, SyncthingDirActions &dirActions); - -class SyncthingFileItemActionStaticData : public QObject { - Q_OBJECT - Q_PROPERTY(QString configPath READ configPath) - Q_PROPERTY(QString currentError READ currentError WRITE setCurrentError NOTIFY currentErrorChanged RESET clearCurrentError) - Q_PROPERTY(bool hasError READ hasError NOTIFY hasErrorChanged) - Q_PROPERTY(bool initialized READ isInitialized) - -public: - explicit SyncthingFileItemActionStaticData(); - ~SyncthingFileItemActionStaticData(); - Data::SyncthingConnection &connection(); - const Data::SyncthingConnection &connection() const; - const QString &configPath() const; - const QString ¤tError() const; - bool hasError() const; - bool isInitialized() const; - -public Q_SLOTS: - void initialize(); - bool applySyncthingConfiguration(const QString &syncthingConfigFilePath); - void logConnectionStatus(); - void logConnectionError(const QString &errorMessage, Data::SyncthingErrorCategory errorCategory); - void rescanDir(const QString &dirId, const QString &relpath = QString()); - static void showAboutDialog(); - void selectSyncthingConfig(); - void setCurrentError(const QString ¤tError); - void clearCurrentError(); - -Q_SIGNALS: - void currentErrorChanged(const QString &error); - void hasErrorChanged(bool hasError); - -private: - Data::SyncthingConnection m_connection; - QString m_configFilePath; - QString m_currentError; - bool m_initialized; -}; - -inline Data::SyncthingConnection &SyncthingFileItemActionStaticData::connection() -{ - return m_connection; -} - -inline const Data::SyncthingConnection &SyncthingFileItemActionStaticData::connection() const -{ - return m_connection; -} - -inline const QString &SyncthingFileItemActionStaticData::configPath() const -{ - return m_configFilePath; -} - -inline const QString &SyncthingFileItemActionStaticData::currentError() const -{ - return m_currentError; -} - -inline bool SyncthingFileItemActionStaticData::hasError() const -{ - return !currentError().isEmpty(); -} - -inline bool SyncthingFileItemActionStaticData::isInitialized() const -{ - return m_initialized; -} - -inline void SyncthingFileItemActionStaticData::clearCurrentError() -{ - m_currentError.clear(); -} - class SyncthingFileItemAction : public KAbstractFileItemActionPlugin { Q_OBJECT diff --git a/fileitemactionplugin/syncthingfileitemactionstaticdata.cpp b/fileitemactionplugin/syncthingfileitemactionstaticdata.cpp new file mode 100644 index 0000000..b2c7a1a --- /dev/null +++ b/fileitemactionplugin/syncthingfileitemactionstaticdata.cpp @@ -0,0 +1,160 @@ +#include "./syncthingfileitemactionstaticdata.h" + +#include "../model/syncthingicons.h" + +#include "../connector/syncthingconfig.h" +#include "../connector/syncthingconnection.h" +#include "../connector/syncthingconnectionsettings.h" + +#include + +#include +#include + +#include +#include +#include + +#include + +#include "resources/config.h" + +using namespace std; +using namespace Dialogs; +using namespace Data; + +SyncthingFileItemActionStaticData::SyncthingFileItemActionStaticData() + : m_initialized(false) +{ +} + +void SyncthingFileItemActionStaticData::initialize() +{ + if (m_initialized) { + return; + } + + LOAD_QT_TRANSLATIONS; + + // load settings + const QSettings settingsFile(QSettings::IniFormat, QSettings::UserScope, QStringLiteral(PROJECT_NAME)); + + // determine path of Syncthing config file + m_configFilePath = [&] { + const QByteArray configPathFromEnv(qgetenv("KIO_SYNCTHING_CONFIG_PATH")); + if (!configPathFromEnv.isEmpty()) { + return QString::fromLocal8Bit(configPathFromEnv); + } + const QString configPathFromSettings = settingsFile.value(QStringLiteral("syncthingConfigPath")).toString(); + if (!configPathFromSettings.isEmpty()) { + return configPathFromSettings; + } + return SyncthingConfig::locateConfigFile(); + }(); + applySyncthingConfiguration(m_configFilePath); + + // prevent unnecessary API calls (for the purpose of the context menu) + m_connection.disablePolling(); + + // connect Signals & Slots for logging + connect(&m_connection, &SyncthingConnection::error, this, &SyncthingFileItemActionStaticData::logConnectionError); + if (qEnvironmentVariableIsSet("KIO_SYNCTHING_LOG_STATUS")) { + connect(&m_connection, &SyncthingConnection::statusChanged, this, &SyncthingFileItemActionStaticData::logConnectionStatus); + } + + m_initialized = true; +} + +void SyncthingFileItemActionStaticData::logConnectionStatus() +{ + cerr << "Syncthing connection status changed to: " << m_connection.statusText().toLocal8Bit().data() << endl; +} + +void SyncthingFileItemActionStaticData::logConnectionError(const QString &errorMessage, SyncthingErrorCategory errorCategory) +{ + switch (errorCategory) { + case SyncthingErrorCategory::Parsing: + case SyncthingErrorCategory::SpecificRequest: + QMessageBox::critical(nullptr, tr("Syncthing connection error"), errorMessage); + break; + default: + cerr << "Syncthing connection error: " << errorMessage.toLocal8Bit().data() << endl; + } +} + +void SyncthingFileItemActionStaticData::rescanDir(const QString &dirId, const QString &relpath) +{ + m_connection.rescan(dirId, relpath); +} + +void SyncthingFileItemActionStaticData::showAboutDialog() +{ + auto *aboutDialog = new AboutDialog(nullptr, QStringLiteral(APP_NAME), QStringLiteral(APP_AUTHOR "\nSyncthing icons from Syncthing project"), + QStringLiteral(APP_VERSION), ApplicationUtilities::dependencyVersions2, QStringLiteral(APP_URL), QStringLiteral(APP_DESCRIPTION), + QImage(statusIcons().scanninig.pixmap(128).toImage())); + aboutDialog->setWindowTitle(tr("About") + QStringLiteral(" - " APP_NAME)); + aboutDialog->setWindowIcon(QIcon::fromTheme(QStringLiteral("syncthingtray"))); + aboutDialog->setAttribute(Qt::WA_DeleteOnClose); + aboutDialog->show(); +} + +void SyncthingFileItemActionStaticData::selectSyncthingConfig() +{ + const auto configFilePath = QFileDialog::getOpenFileName(nullptr, tr("Select Syncthing config file") + QStringLiteral(" - " APP_NAME)); + if (!configFilePath.isEmpty() && applySyncthingConfiguration(configFilePath)) { + QSettings(QSettings::IniFormat, QSettings::UserScope, QStringLiteral(PROJECT_NAME)) + .setValue(QStringLiteral("syncthingConfigPath"), m_configFilePath = configFilePath); + } +} + +bool SyncthingFileItemActionStaticData::applySyncthingConfiguration(const QString &syncthingConfigFilePath) +{ + clearCurrentError(); + + // check for empty path + if (syncthingConfigFilePath.isEmpty()) { + setCurrentError(tr("Syncthing config file can not be automatically located")); + return false; + } + + // load Syncthing config + SyncthingConfig config; + if (!config.restore(syncthingConfigFilePath)) { + auto errorMessage = tr("Unable to load Syncthing config from \"%1\"").arg(syncthingConfigFilePath); + if (!m_configFilePath.isEmpty() && m_configFilePath != syncthingConfigFilePath) { + errorMessage += QChar('\n'); + errorMessage += tr("(still using config from \"%1\")").arg(m_configFilePath); + } + setCurrentError(errorMessage); + return false; + } + cerr << "Syncthing config loaded from \"" << syncthingConfigFilePath.toLocal8Bit().data() << "\"" << endl; + + // make connection settings + SyncthingConnectionSettings settings; + settings.syncthingUrl = config.syncthingUrl(); + settings.apiKey.append(config.guiApiKey); + + // establish connection + bool ok; + int reconnectInterval = qEnvironmentVariableIntValue("KIO_SYNCTHING_RECONNECT_INTERVAL", &ok); + if (!ok || reconnectInterval < 0) { + reconnectInterval = 10000; + } + m_connection.setAutoReconnectInterval(reconnectInterval); + m_connection.reconnect(settings); + return true; +} + +void SyncthingFileItemActionStaticData::setCurrentError(const QString ¤tError) +{ + if (m_currentError == currentError) { + return; + } + const bool hadError = hasError(); + m_currentError = currentError; + if (hadError != hasError()) { + emit hasErrorChanged(hasError()); + } + emit currentErrorChanged(m_currentError); +} diff --git a/fileitemactionplugin/syncthingfileitemactionstaticdata.h b/fileitemactionplugin/syncthingfileitemactionstaticdata.h new file mode 100644 index 0000000..5f90fad --- /dev/null +++ b/fileitemactionplugin/syncthingfileitemactionstaticdata.h @@ -0,0 +1,79 @@ +#ifndef SYNCTHINGFILEITEMACTIONSTATICDATA_H +#define SYNCTHINGFILEITEMACTIONSTATICDATA_H + +#include "../connector/syncthingconnection.h" + +class SyncthingFileItemActionStaticData : public QObject { + Q_OBJECT + Q_PROPERTY(QString configPath READ configPath) + Q_PROPERTY(QString currentError READ currentError WRITE setCurrentError NOTIFY currentErrorChanged RESET clearCurrentError) + Q_PROPERTY(bool hasError READ hasError NOTIFY hasErrorChanged) + Q_PROPERTY(bool initialized READ isInitialized) + +public: + explicit SyncthingFileItemActionStaticData(); + Data::SyncthingConnection &connection(); + const Data::SyncthingConnection &connection() const; + const QString &configPath() const; + const QString ¤tError() const; + bool hasError() const; + bool isInitialized() const; + +public Q_SLOTS: + void initialize(); + bool applySyncthingConfiguration(const QString &syncthingConfigFilePath); + void logConnectionStatus(); + void logConnectionError(const QString &errorMessage, Data::SyncthingErrorCategory errorCategory); + void rescanDir(const QString &dirId, const QString &relpath = QString()); + static void showAboutDialog(); + void selectSyncthingConfig(); + void setCurrentError(const QString ¤tError); + void clearCurrentError(); + +Q_SIGNALS: + void currentErrorChanged(const QString &error); + void hasErrorChanged(bool hasError); + +private: + Data::SyncthingConnection m_connection; + QString m_configFilePath; + QString m_currentError; + bool m_initialized; +}; + +inline Data::SyncthingConnection &SyncthingFileItemActionStaticData::connection() +{ + return m_connection; +} + +inline const Data::SyncthingConnection &SyncthingFileItemActionStaticData::connection() const +{ + return m_connection; +} + +inline const QString &SyncthingFileItemActionStaticData::configPath() const +{ + return m_configFilePath; +} + +inline const QString &SyncthingFileItemActionStaticData::currentError() const +{ + return m_currentError; +} + +inline bool SyncthingFileItemActionStaticData::hasError() const +{ + return !currentError().isEmpty(); +} + +inline bool SyncthingFileItemActionStaticData::isInitialized() const +{ + return m_initialized; +} + +inline void SyncthingFileItemActionStaticData::clearCurrentError() +{ + m_currentError.clear(); +} + +#endif // SYNCTHINGFILEITEMACTIONSTATICDATA_H diff --git a/fileitemactionplugin/syncthinginfoaction.cpp b/fileitemactionplugin/syncthinginfoaction.cpp new file mode 100644 index 0000000..3e2625e --- /dev/null +++ b/fileitemactionplugin/syncthinginfoaction.cpp @@ -0,0 +1,45 @@ +#include "./syncthinginfoaction.h" + +#include +#include +#include + +SyncthingInfoWidget::SyncthingInfoWidget(const SyncthingInfoAction *action, QWidget *parent) + : QWidget(parent) + , m_textLabel(new QLabel(parent)) + , m_iconLabel(new QLabel(parent)) +{ + auto *const layout = new QHBoxLayout(parent); + layout->setMargin(4); + layout->setSpacing(5); + m_iconLabel->setFixedWidth(16); + m_iconLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Minimum); + layout->addWidget(m_iconLabel); + layout->addWidget(m_textLabel); + setLayout(layout); + updateFromAction(action); + connect(action, &QAction::changed, this, &SyncthingInfoWidget::updateFromSender); +} + +void SyncthingInfoWidget::updateFromSender() +{ + updateFromAction(qobject_cast(QObject::sender())); +} + +void SyncthingInfoWidget::updateFromAction(const SyncthingInfoAction *action) +{ + auto text(action->text()); + m_textLabel->setText(text.startsWith(QChar('&')) ? text.mid(1) : std::move(text)); + m_iconLabel->setPixmap(action->icon().pixmap(16)); + setVisible(action->isVisible()); +} + +SyncthingInfoAction::SyncthingInfoAction(QObject *parent) + : QWidgetAction(parent) +{ +} + +QWidget *SyncthingInfoAction::createWidget(QWidget *parent) +{ + return new SyncthingInfoWidget(this, parent); +} diff --git a/fileitemactionplugin/syncthinginfoaction.h b/fileitemactionplugin/syncthinginfoaction.h new file mode 100644 index 0000000..c8bbd5e --- /dev/null +++ b/fileitemactionplugin/syncthinginfoaction.h @@ -0,0 +1,36 @@ +#ifndef SYNCTHINGINFOACTION_H +#define SYNCTHINGINFOACTION_H + +#include +#include + +QT_FORWARD_DECLARE_CLASS(QLabel) + +class SyncthingInfoAction; + +class SyncthingInfoWidget : public QWidget { + Q_OBJECT + +public: + explicit SyncthingInfoWidget(const SyncthingInfoAction *action, QWidget *parent = nullptr); + +private Q_SLOTS: + void updateFromSender(); + void updateFromAction(const SyncthingInfoAction *action); + +private: + QLabel *const m_textLabel; + QLabel *const m_iconLabel; +}; + +class SyncthingInfoAction : public QWidgetAction { + Q_OBJECT + +public: + explicit SyncthingInfoAction(QObject *parent = nullptr); + +protected: + QWidget *createWidget(QWidget *parent) override; +}; + +#endif // SYNCTHINGINFOACTION_H diff --git a/fileitemactionplugin/syncthingmenuaction.cpp b/fileitemactionplugin/syncthingmenuaction.cpp new file mode 100644 index 0000000..1e08c79 --- /dev/null +++ b/fileitemactionplugin/syncthingmenuaction.cpp @@ -0,0 +1,49 @@ +#include "./syncthingmenuaction.h" +#include "./syncthingfileitemaction.h" + +#include "../model/syncthingicons.h" + +#include "../connector/syncthingconnection.h" + +#include +#include + +using namespace Data; + +SyncthingMenuAction::SyncthingMenuAction(const KFileItemListProperties &properties, const QList &actions, QWidget *parentWidget) + : QAction(parentWidget) + , m_properties(properties) +{ + if (!actions.isEmpty()) { + auto *menu = new QMenu(parentWidget); + menu->addActions(actions); + setMenu(menu); + } + updateStatus(SyncthingFileItemAction::staticData().connection().status()); +} + +void SyncthingMenuAction::updateStatus(SyncthingStatus status) +{ + if (status != SyncthingStatus::Disconnected && status != SyncthingStatus::Reconnecting && status != SyncthingStatus::BeingDestroyed) { + setText(tr("Syncthing")); + setIcon(statusIcons().scanninig); + if (!menu()) { + const QList actions = SyncthingFileItemAction::createActions(m_properties, parentWidget()); + if (!actions.isEmpty()) { + auto *menu = new QMenu(parentWidget()); + menu->addActions(actions); + setMenu(menu); + } + } + } else { + if (status != SyncthingStatus::Reconnecting) { + SyncthingFileItemAction::staticData().connection().connect(); + } + setText(tr("Syncthing - connecting")); + setIcon(statusIcons().disconnected); + if (QMenu *menu = this->menu()) { + setMenu(nullptr); + delete menu; + } + } +} diff --git a/fileitemactionplugin/syncthingmenuaction.h b/fileitemactionplugin/syncthingmenuaction.h new file mode 100644 index 0000000..04439ed --- /dev/null +++ b/fileitemactionplugin/syncthingmenuaction.h @@ -0,0 +1,26 @@ +#ifndef SYNCTHINGMENUACTION_H +#define SYNCTHINGMENUACTION_H + +#include + +#include + +namespace Data { +enum class SyncthingStatus; +} + +class SyncthingMenuAction : public QAction { + Q_OBJECT + +public: + explicit SyncthingMenuAction(const KFileItemListProperties &properties = KFileItemListProperties(), + const QList &actions = QList(), QWidget *parentWidget = nullptr); + +public Q_SLOTS: + void updateStatus(Data::SyncthingStatus status); + +private: + KFileItemListProperties m_properties; +}; + +#endif // SYNCTHINGMENUACTION_H