syncthingtray/fileitemactionplugin/syncthingfileitemaction.cpp

303 lines
12 KiB
C++
Raw Normal View History

2017-02-20 18:37:11 +01:00
#include "./syncthingfileitemaction.h"
#include "./syncthingdiractions.h"
#include "./syncthinginfoaction.h"
#include "./syncthingmenuaction.h"
2017-02-20 18:37:11 +01:00
#include <syncthingmodel/syncthingicons.h>
#include <qtutilities/misc/desktoputils.h>
2017-02-20 18:37:11 +01:00
#include <KFileItem>
#include <KPluginFactory>
#include <QAction>
2022-12-17 20:39:40 +01:00
#include <QDir>
#include <QEvent>
#include <QRegularExpression>
2017-02-20 18:37:11 +01:00
#include <functional>
#include <utility>
2017-02-20 18:37:11 +01:00
using namespace std;
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
K_PLUGIN_CLASS_WITH_JSON(SyncthingFileItemAction, "metadata.json");
#else
2017-02-20 18:37:11 +01:00
K_PLUGIN_FACTORY(SyncthingFileItemActionFactory, registerPlugin<SyncthingFileItemAction>();)
#endif
2017-02-20 18:37:11 +01:00
2017-05-01 03:34:43 +02:00
struct SyncthingItem {
2024-04-01 18:57:18 +02:00
SyncthingItem(const Data::SyncthingDir *dir, const QString &path);
const Data::SyncthingDir *dir;
2017-02-20 18:37:11 +01:00
QString path;
QString name;
};
2024-04-01 18:57:18 +02:00
SyncthingItem::SyncthingItem(const Data::SyncthingDir *dir, const QString &path)
2017-05-01 03:34:43 +02:00
: dir(dir)
, path(path)
2017-02-20 18:37:11 +01:00
{
const auto lastSep = path.lastIndexOf(QChar('/'));
if (lastSep >= 0) {
2017-02-20 18:37:11 +01:00
name = path.mid(lastSep + 1);
} else {
name = path;
}
}
SyncthingFileItemActionStaticData SyncthingFileItemAction::s_data;
SyncthingFileItemAction::SyncthingFileItemAction(QObject *parent, const QVariantList &)
: KAbstractFileItemActionPlugin(parent)
, m_parentWidget(nullptr)
{
s_data.initialize();
}
QList<QAction *> SyncthingFileItemAction::actions(const KFileItemListProperties &fileItemInfo, QWidget *parentWidget)
{
Q_UNUSED(parentWidget)
2018-12-24 17:19:11 +01:00
// create actions
const QList<QAction *> subActions = createActions(fileItemInfo, this);
QList<QAction *> topLevelActions;
// don't show anything if no relevant actions could be determined but successfully connected
if (s_data.connection().isConnected() && !s_data.hasError() && subActions.isEmpty()) {
return topLevelActions;
2017-02-20 18:37:11 +01:00
}
if ((m_parentWidget = parentWidget)) {
s_data.applyBrightCustomColorsSetting(QtUtilities::isPaletteDark(parentWidget->palette()));
parentWidget->installEventFilter(this);
}
topLevelActions << new SyncthingMenuAction(fileItemInfo, subActions, parentWidget);
return topLevelActions;
}
struct DirStats {
2024-04-01 18:57:18 +02:00
explicit DirStats(const QList<const Data::SyncthingDir *> &dirs);
QStringList ids;
bool anyPaused = false;
bool allPaused = true;
};
2024-04-01 18:57:18 +02:00
DirStats::DirStats(const QList<const Data::SyncthingDir *> &dirs)
{
ids.reserve(dirs.size());
2024-04-01 18:57:18 +02:00
for (const Data::SyncthingDir *const dir : dirs) {
ids << dir->id;
if (dir->paused) {
anyPaused = true;
if (!allPaused) {
break;
}
} else {
allPaused = false;
if (anyPaused) {
break;
}
}
}
}
QList<QAction *> SyncthingFileItemAction::createActions(const KFileItemListProperties &fileItemInfo, QObject *parent)
{
auto actions = QList<QAction *>();
auto &data = s_data;
auto &connection = data.connection();
const auto &dirs = connection.dirInfo();
2017-02-20 18:37:11 +01:00
// get all paths
auto paths = QStringList();
2017-02-20 18:37:11 +01:00
paths.reserve(fileItemInfo.items().size());
const auto items = fileItemInfo.items();
for (const KFileItem &item : items) {
2017-05-01 03:34:43 +02:00
if (!item.isLocalFile()) {
2017-02-20 18:37:11 +01:00
// don't show any actions when remote files are selected
return actions;
2017-02-20 18:37:11 +01:00
}
2022-12-17 20:39:40 +01:00
paths << QDir::cleanPath(item.localPath());
2017-02-20 18:37:11 +01:00
}
// determine relevant Syncthing dirs
2024-04-01 18:57:18 +02:00
QList<const Data::SyncthingDir *> detectedDirs;
QList<const Data::SyncthingDir *> containingDirs;
2017-02-20 18:37:11 +01:00
QList<SyncthingItem> detectedItems;
2024-04-01 18:57:18 +02:00
const Data::SyncthingDir *lastDir = nullptr;
for (const Data::SyncthingDir &dir : dirs) {
2022-12-17 20:39:40 +01:00
auto dirPath = QDir::cleanPath(dir.path);
auto dirPathWithSlash = dirPath + QChar('/');
for (const QString &path : std::as_const(paths)) {
2017-05-01 03:34:43 +02:00
if (path == dirPath) {
2017-02-25 18:35:27 +01:00
lastDir = &dir;
2017-05-01 03:34:43 +02:00
if (!detectedDirs.contains(lastDir)) {
2017-02-25 18:35:27 +01:00
detectedDirs << lastDir;
}
2022-12-17 20:39:40 +01:00
} else if (path.startsWith(dirPathWithSlash)) {
detectedItems << SyncthingItem(&dir, path.mid(dirPathWithSlash.size()));
2017-02-25 18:28:20 +01:00
lastDir = &dir;
2017-05-01 03:34:43 +02:00
if (!containingDirs.contains(lastDir)) {
2017-02-25 18:28:20 +01:00
containingDirs << lastDir;
}
2017-02-20 18:37:11 +01:00
}
}
}
// compute dir stats
const auto detectedDirsStats = DirStats(detectedDirs);
const auto containingDirsStats = DirStats(containingDirs);
2017-02-25 18:28:20 +01:00
// add actions for the selected items itself
actions.reserve(32);
2017-05-01 03:34:43 +02:00
if (!detectedItems.isEmpty()) {
QString rescanLabel;
if (detectedItems.size() > 1) {
rescanLabel = tr("Rescan selected items");
} else {
rescanLabel = tr("Rescan \"%1\"").arg(detectedItems.front().name);
}
actions << new QAction(QIcon::fromTheme(QStringLiteral("view-refresh")), rescanLabel, parent);
if (connection.isConnected() && !containingDirsStats.allPaused) {
for (const SyncthingItem &item : std::as_const(detectedItems)) {
connect(actions.back(), &QAction::triggered, bind(&SyncthingFileItemActionStaticData::rescanDir, &data, item.dir->id, item.path));
2017-02-25 18:28:20 +01:00
}
} else {
actions.back()->setEnabled(false);
}
}
// add actions for explicitly selected Syncthing dirs
2017-05-01 03:34:43 +02:00
if (!detectedDirs.isEmpty()) {
2017-02-25 18:28:20 +01:00
// rescan item
2017-05-01 03:34:43 +02:00
actions << new QAction(QIcon::fromTheme(QStringLiteral("folder-sync")),
detectedDirs.size() == 1 ? tr("Rescan \"%1\"").arg(detectedDirs.front()->displayName()) : tr("Rescan selected folders"), parent);
if (connection.isConnected() && !detectedDirsStats.allPaused) {
2024-04-01 18:57:18 +02:00
for (const Data::SyncthingDir *dir : std::as_const(detectedDirs)) {
connect(actions.back(), &QAction::triggered, bind(&SyncthingFileItemActionStaticData::rescanDir, &data, dir->id, QString()));
2017-02-20 18:37:11 +01:00
containingDirs.removeAll(dir);
}
} else {
actions.back()->setEnabled(false);
}
2017-02-25 18:28:20 +01:00
// pause/resume item
if (detectedDirsStats.anyPaused) {
2017-05-01 03:34:43 +02:00
actions << new QAction(QIcon::fromTheme(QStringLiteral("media-playback-start")),
detectedDirs.size() == 1 ? tr("Resume \"%1\"").arg(detectedDirs.front()->displayName()) : tr("Resume selected folders"), parent);
2017-02-25 18:28:20 +01:00
} else {
2017-05-01 03:34:43 +02:00
actions << new QAction(QIcon::fromTheme(QStringLiteral("media-playback-pause")),
detectedDirs.size() == 1 ? tr("Pause \"%1\"").arg(detectedDirs.front()->displayName()) : tr("Pause selected folders"), parent);
2017-02-25 18:28:20 +01:00
}
if (connection.isConnected()) {
2017-05-01 03:34:43 +02:00
connect(actions.back(), &QAction::triggered,
2024-04-01 18:57:18 +02:00
bind(detectedDirsStats.anyPaused ? &Data::SyncthingConnection::resumeDirectories : &Data::SyncthingConnection::pauseDirectories,
&connection, detectedDirsStats.ids));
2017-02-25 18:28:20 +01:00
} else {
actions.back()->setEnabled(false);
}
2017-02-20 18:37:11 +01:00
}
// add actions for the Syncthing dirs containing selected items
2017-05-01 03:34:43 +02:00
if (!containingDirs.isEmpty()) {
2017-02-25 18:28:20 +01:00
// rescan item
2017-05-01 03:34:43 +02:00
actions << new QAction(QIcon::fromTheme(QStringLiteral("folder-sync")),
2023-09-18 22:12:59 +02:00
containingDirs.size() == 1 ? tr("Rescan \"%1\"").arg(containingDirs.front()->displayName()) : tr("Rescan containing folders"), parent);
if (connection.isConnected() && !containingDirsStats.allPaused) {
2024-04-01 18:57:18 +02:00
for (const Data::SyncthingDir *dir : std::as_const(containingDirs)) {
connect(actions.back(), &QAction::triggered, bind(&SyncthingFileItemActionStaticData::rescanDir, &data, dir->id, QString()));
2017-02-20 18:37:11 +01:00
}
} else {
actions.back()->setEnabled(false);
}
2017-02-25 18:28:20 +01:00
// pause/resume item
if (containingDirsStats.anyPaused) {
2017-05-01 03:34:43 +02:00
actions << new QAction(QIcon::fromTheme(QStringLiteral("media-playback-start")),
containingDirs.size() == 1 ? tr("Resume \"%1\"").arg(containingDirs.front()->displayName()) : tr("Resume containing folders"),
parent);
2017-02-25 18:28:20 +01:00
} else {
2017-05-01 03:34:43 +02:00
actions << new QAction(QIcon::fromTheme(QStringLiteral("media-playback-pause")),
2023-09-18 22:12:59 +02:00
containingDirs.size() == 1 ? tr("Pause \"%1\"").arg(containingDirs.front()->displayName()) : tr("Pause containing folders"), parent);
2017-02-25 18:28:20 +01:00
}
if (connection.isConnected()) {
2017-05-01 03:34:43 +02:00
connect(actions.back(), &QAction::triggered,
2024-04-01 18:57:18 +02:00
bind(containingDirsStats.anyPaused ? &Data::SyncthingConnection::resumeDirectories : &Data::SyncthingConnection::pauseDirectories,
&connection, containingDirsStats.ids));
2017-02-20 18:37:11 +01:00
} else {
actions.back()->setEnabled(false);
}
}
2017-03-13 00:40:48 +01:00
// add actions to show further information about directory if the selection is only about one particular Syncthing dir
if (lastDir && detectedDirs.size() + containingDirs.size() == 1) {
auto *statusActions = new SyncthingDirActions(*lastDir, &data, parent);
2024-04-01 18:57:18 +02:00
connect(&connection, &Data::SyncthingConnection::newDirs, statusActions,
static_cast<void (SyncthingDirActions::*)(const std::vector<Data::SyncthingDir> &)>(&SyncthingDirActions::updateStatus));
connect(&connection, &Data::SyncthingConnection::dirStatusChanged, statusActions,
static_cast<bool (SyncthingDirActions::*)(const Data::SyncthingDir &)>(&SyncthingDirActions::updateStatus));
2017-03-13 00:40:48 +01:00
actions << *statusActions;
2017-02-20 18:37:11 +01:00
}
// add note if no actions are available within the current folder
if (actions.isEmpty()) {
auto *const note = new QAction(parent);
note->setText(tr("Not a shared directory"));
note->setEnabled(false);
actions << note;
}
// add separator
auto *const separator = new QAction(parent);
separator->setSeparator(true);
actions << separator;
// add error action
QAction *const errorAction = new SyncthingInfoAction(parent);
errorAction->setText(data.currentError());
errorAction->setIcon(QIcon::fromTheme(QStringLiteral("state-error")));
errorAction->setVisible(data.hasError());
connect(&data, &SyncthingFileItemActionStaticData::currentErrorChanged, errorAction, &QAction::setText);
connect(&data, &SyncthingFileItemActionStaticData::hasErrorChanged, errorAction, &QAction::setVisible);
connect(&data, &SyncthingFileItemActionStaticData::currentErrorChanged, errorAction, &QAction::changed);
actions << errorAction;
// show Syncthing version
if (!connection.syncthingVersion().isEmpty()) {
static const auto versionRegex = QRegularExpression("(syncthing.*v.* \".*\").*");
auto *const versionAction = new QAction(parent);
if (const auto match = versionRegex.match(connection.syncthingVersion()); match.isValid()) {
versionAction->setText(match.captured(1));
} else {
versionAction->setText(connection.syncthingVersion());
}
versionAction->setEnabled(false);
actions << versionAction;
}
// add config items
QAction *const configFileAction = new QAction(QIcon::fromTheme(QStringLiteral("settings-configure")), tr("Select Syncthing config ..."), parent);
connect(configFileAction, &QAction::triggered, &data, &SyncthingFileItemActionStaticData::selectSyncthingConfig);
actions << configFileAction;
2017-02-20 18:37:11 +01:00
// about about action
QAction *const aboutAction = new QAction(QIcon::fromTheme(QStringLiteral("help-about")), tr("About"), parent);
connect(aboutAction, &QAction::triggered, &SyncthingFileItemActionStaticData::showAboutDialog);
actions << aboutAction;
2017-02-20 18:37:11 +01:00
return actions;
2017-02-20 18:37:11 +01:00
}
bool SyncthingFileItemAction::eventFilter(QObject *object, QEvent *event)
{
if (object == m_parentWidget && event->type() == QEvent::PaletteChange) {
s_data.handlePaletteChanged(m_parentWidget->palette());
}
return false;
}
2017-02-20 18:37:11 +01:00
#include <syncthingfileitemaction.moc>