syncthingtray/model/syncthingdirectorymodel.cpp

528 lines
19 KiB
C++
Raw Normal View History

2016-08-25 00:45:32 +02:00
#include "./syncthingdirectorymodel.h"
2016-11-08 19:42:50 +01:00
#include "./colors.h"
2017-05-01 03:34:43 +02:00
#include "./syncthingicons.h"
#include <syncthingconnector/syncthingconnection.h>
#include <syncthingconnector/utils.h>
2016-09-01 16:34:30 +02:00
2017-08-24 00:00:57 +02:00
#include <c++utilities/conversion/stringconversion.h>
2016-10-05 23:17:18 +02:00
#include <QStringBuilder>
using namespace std;
2019-06-10 22:48:26 +02:00
using namespace CppUtilities;
2016-08-25 00:45:32 +02:00
namespace Data {
2020-01-18 16:37:20 +01:00
static int computeDirectoryRowCount(const SyncthingDir &dir)
{
return dir.paused ? 8 : 10;
}
2017-05-01 03:34:43 +02:00
SyncthingDirectoryModel::SyncthingDirectoryModel(SyncthingConnection &connection, QObject *parent)
: SyncthingModel(connection, parent)
, m_dirs(connection.dirInfo())
2016-08-25 00:45:32 +02:00
{
updateRowCount();
2016-08-25 00:45:32 +02:00
connect(&m_connection, &SyncthingConnection::dirStatusChanged, this, &SyncthingDirectoryModel::dirStatusChanged);
}
QHash<int, QByteArray> SyncthingDirectoryModel::roleNames() const
{
2018-11-03 21:47:30 +01:00
const static QHash<int, QByteArray> roles{
{ Qt::DisplayRole, "name" },
{ DirectoryStatus, "status" },
{ Qt::DecorationRole, "statusIcon" },
{ DirectoryStatusString, "statusString" },
{ DirectoryStatusColor, "statusColor" },
{ DirectoryPaused, "paused" },
{ DirectoryId, "dirId" },
{ DirectoryPath, "path" },
{ DirectoryPullErrorCount, "pullErrorCount" },
{ DirectoryDetail, "detail" },
{ DirectoryDetailIcon, "detailIcon" },
2018-11-03 21:47:30 +01:00
};
return roles;
}
2017-09-11 23:44:19 +02:00
const QVector<int> &SyncthingDirectoryModel::colorRoles() const
{
static const QVector<int> colorRoles({ Qt::DecorationRole, Qt::ForegroundRole, DirectoryStatusColor, DirectoryDetailIcon });
2017-09-11 23:44:19 +02:00
return colorRoles;
}
2016-08-25 00:45:32 +02:00
/*!
* \brief Returns the directory info for the spcified \a index. The returned object is not persistent.
*/
const SyncthingDir *SyncthingDirectoryModel::dirInfo(const QModelIndex &index) const
{
2017-05-01 03:34:43 +02:00
return (index.parent().isValid() ? dirInfo(index.parent())
: (static_cast<size_t>(index.row()) < m_dirs.size() ? &m_dirs[static_cast<size_t>(index.row())] : nullptr));
2016-08-25 00:45:32 +02:00
}
QModelIndex SyncthingDirectoryModel::index(int row, int column, const QModelIndex &parent) const
{
2017-05-01 03:34:43 +02:00
if (!parent.isValid()) {
2016-08-25 00:45:32 +02:00
// top-level: all dir labels/IDs
2017-05-01 03:34:43 +02:00
if (row < rowCount(parent)) {
2016-09-21 21:09:12 +02:00
return createIndex(row, column, static_cast<quintptr>(-1));
2016-08-25 00:45:32 +02:00
}
2017-05-01 03:34:43 +02:00
} else if (!parent.parent().isValid()) {
2016-08-25 00:45:32 +02:00
// dir-level: dir attributes
2017-05-01 03:34:43 +02:00
if (row < rowCount(parent)) {
2016-09-21 21:09:12 +02:00
return createIndex(row, column, static_cast<quintptr>(parent.row()));
2016-08-25 00:45:32 +02:00
}
}
return QModelIndex();
}
QModelIndex SyncthingDirectoryModel::parent(const QModelIndex &child) const
{
2016-09-21 21:09:12 +02:00
return child.internalId() != static_cast<quintptr>(-1) ? index(static_cast<int>(child.internalId()), 0, QModelIndex()) : QModelIndex();
2016-08-25 00:45:32 +02:00
}
QVariant SyncthingDirectoryModel::headerData(int section, Qt::Orientation orientation, int role) const
{
2017-05-01 03:34:43 +02:00
switch (orientation) {
2016-08-25 00:45:32 +02:00
case Qt::Horizontal:
2017-05-01 03:34:43 +02:00
switch (role) {
2016-08-25 00:45:32 +02:00
case Qt::DisplayRole:
2017-05-01 03:34:43 +02:00
switch (section) {
case 0:
return tr("ID");
case 1:
return tr("Status");
2016-08-25 00:45:32 +02:00
}
break;
2017-05-01 03:34:43 +02:00
default:;
2016-08-25 00:45:32 +02:00
}
break;
2017-05-01 03:34:43 +02:00
default:;
2016-08-25 00:45:32 +02:00
}
return QVariant();
}
QVariant SyncthingDirectoryModel::data(const QModelIndex &index, int role) const
{
2019-02-16 01:06:08 +01:00
if (!index.isValid()) {
return QVariant();
}
if (index.parent().isValid()) {
// dir attributes
if (static_cast<size_t>(index.parent().row()) >= m_dirs.size()) {
return QVariant();
}
const SyncthingDir &dir = m_dirs[static_cast<size_t>(index.parent().row())];
const auto row = dir.paused && index.row() > 1 ? index.row() + 2 : index.row();
switch (role) {
case Qt::DisplayRole:
case Qt::EditRole:
if (index.column() == 0) {
// attribute names
switch (row) {
case 0:
return tr("ID");
case 1:
return tr("Path");
case 2:
return tr("Global status");
case 3:
return tr("Local status");
case 4:
return tr("Shared with");
case 5:
return tr("Type");
case 6:
return tr("Rescan interval");
case 7:
return tr("Last scan");
case 8:
return tr("Last file");
case 9:
return tr("Errors");
}
break;
}
2019-06-12 21:00:49 +02:00
[[fallthrough]];
2019-02-16 01:06:08 +01:00
case DirectoryDetail:
if (index.column() == 1 || role == DirectoryDetail) {
// attribute values
switch (row) {
case 0:
return dir.id;
case 1:
return dir.path;
case 2:
return directoryStatusString(dir.globalStats);
case 3:
return directoryStatusString(dir.localStats);
case 4:
if (!dir.deviceNames.isEmpty()) {
return dir.deviceNames.join(QStringLiteral(", "));
} else if (!dir.deviceIds.isEmpty()) {
return dir.deviceIds.join(QStringLiteral(", "));
} else {
return tr("not shared");
}
2019-02-16 01:06:08 +01:00
case 5:
return dir.dirTypeString();
case 6:
return rescanIntervalString(dir.rescanInterval, dir.fileSystemWatcherEnabled);
case 7:
return dir.lastScanTime.isNull() ? tr("unknown")
: QString::fromLatin1(dir.lastScanTime.toString(DateTimeOutputFormat::DateAndTime, true).data());
case 8:
return dir.lastFileName.isEmpty() ? tr("unknown") : dir.lastFileName;
case 9:
if (dir.globalError.isEmpty() && !dir.pullErrorCount) {
return tr("none");
2016-08-25 00:45:32 +02:00
}
2019-02-16 01:06:08 +01:00
if (!dir.pullErrorCount) {
return dir.globalError;
}
if (dir.globalError.isEmpty()) {
return tr("%1 item(s) out of sync", nullptr, trQuandity(dir.pullErrorCount)).arg(dir.pullErrorCount);
}
return tr("%1 and %2 item(s) out of sync", nullptr, trQuandity(dir.pullErrorCount)).arg(dir.globalError).arg(dir.pullErrorCount);
}
}
break;
case Qt::DecorationRole:
case DirectoryDetailIcon:
if (index.column() == 0) {
// attribute icons
const auto &icons = m_brightColors ? fontAwesomeIconsForDarkTheme() : fontAwesomeIconsForLightTheme();
switch (row) {
case 0:
return icons.hashtag;
case 1:
return icons.folderOpen;
case 2:
return icons.globe;
case 3:
return icons.home;
case 4:
return icons.shareAlt;
case 5:
return icons.cogs;
case 6:
return icons.refresh;
case 7:
return icons.clock;
case 8:
return icons.exchangeAlt;
case 9:
return icons.exclamationTriangle;
}
}
break;
2019-02-16 01:06:08 +01:00
case Qt::ForegroundRole:
switch (index.column()) {
case 1:
switch (row) {
case 4:
if (dir.deviceIds.isEmpty()) {
return Colors::gray(m_brightColors);
2016-09-01 16:34:30 +02:00
}
break;
2019-02-16 01:06:08 +01:00
case 7:
if (dir.lastScanTime.isNull()) {
return Colors::gray(m_brightColors);
2016-09-01 16:34:30 +02:00
}
2017-08-31 19:49:23 +02:00
break;
2019-02-16 01:06:08 +01:00
case 8:
return dir.lastFileName.isEmpty() ? Colors::gray(m_brightColors)
: (dir.lastFileDeleted ? Colors::red(m_brightColors) : QVariant());
case 9:
return dir.globalError.isEmpty() && !dir.pullErrorCount ? Colors::gray(m_brightColors) : Colors::red(m_brightColors);
2016-08-25 00:45:32 +02:00
}
}
2019-02-16 01:06:08 +01:00
break;
case Qt::ToolTipRole:
switch (index.column()) {
case 1:
switch (row) {
case 3:
if (dir.deviceNames.isEmpty()) {
return dir.deviceIds.join(QChar('\n'));
2017-02-20 21:00:18 +01:00
} else {
2019-02-16 01:06:08 +01:00
return QString(
dir.deviceNames.join(QStringLiteral(", ")) % QChar('\n') % QChar('(') % dir.deviceIds.join(QChar('\n')) % QChar(')'));
}
case 7:
if (!dir.lastScanTime.isNull()) {
return agoString(dir.lastScanTime);
2016-08-25 00:45:32 +02:00
}
break;
2019-02-16 01:06:08 +01:00
case 8:
if (!dir.lastFileTime.isNull()) {
if (dir.lastFileDeleted) {
return tr("Deleted at %1")
2020-01-18 16:37:20 +01:00
.arg(QString::fromStdString(dir.lastFileTime.toString(DateTimeOutputFormat::DateAndTime, true)));
2019-02-16 01:06:08 +01:00
} else {
return tr("Updated at %1")
2020-01-18 16:37:20 +01:00
.arg(QString::fromStdString(dir.lastFileTime.toString(DateTimeOutputFormat::DateAndTime, true)));
2019-02-16 01:06:08 +01:00
}
}
2017-05-01 03:34:43 +02:00
break;
2019-02-16 01:06:08 +01:00
case 9:
if (!dir.itemErrors.empty()) {
QStringList errors;
errors.reserve(static_cast<int>(dir.itemErrors.size()));
for (const auto &error : dir.itemErrors) {
errors << error.path;
}
return QVariant(QStringLiteral("<b>") % tr("Failed items") % QStringLiteral("</b><ul><li>")
% errors.join(QStringLiteral("</li><li>")) % QStringLiteral("</li></ul>") % tr("Click for details"));
}
2016-08-25 00:45:32 +02:00
}
2019-02-16 01:06:08 +01:00
}
break;
default:;
}
return QVariant();
}
if (static_cast<size_t>(index.row()) >= m_dirs.size()) {
return QVariant();
}
// dir IDs and status
const SyncthingDir &dir = m_dirs[static_cast<size_t>(index.row())];
switch (role) {
case Qt::DisplayRole:
case Qt::EditRole:
switch (index.column()) {
case 0:
return dir.label.isEmpty() ? dir.id : dir.label;
case 1:
return dirStatusString(dir);
}
break;
case Qt::DecorationRole:
switch (index.column()) {
case 0:
if (dir.paused && dir.status != SyncthingDirStatus::OutOfSync) {
return statusIcons().pause;
} else if (dir.deviceIds.empty()) {
return statusIcons().disconnected; // "unshared" status
} else {
switch (dir.status) {
case SyncthingDirStatus::Unknown:
return statusIcons().disconnected;
case SyncthingDirStatus::Idle:
case SyncthingDirStatus::Cleaning:
case SyncthingDirStatus::WaitingToClean:
2019-02-16 01:06:08 +01:00
return statusIcons().idling;
case SyncthingDirStatus::WaitingToScan:
2019-02-16 01:06:08 +01:00
case SyncthingDirStatus::Scanning:
return statusIcons().scanninig;
case SyncthingDirStatus::WaitingToSync:
case SyncthingDirStatus::PreparingToSync:
2019-02-16 01:06:08 +01:00
case SyncthingDirStatus::Synchronizing:
return statusIcons().sync;
case SyncthingDirStatus::OutOfSync:
return statusIcons().error;
2016-08-25 00:45:32 +02:00
}
}
2019-02-16 01:06:08 +01:00
break;
2016-08-25 00:45:32 +02:00
}
2019-02-16 01:06:08 +01:00
break;
case Qt::TextAlignmentRole:
switch (index.column()) {
case 0:
break;
case 1:
return static_cast<int>(Qt::AlignRight | Qt::AlignVCenter);
}
break;
case Qt::ForegroundRole:
switch (index.column()) {
case 0:
break;
case 1:
return dirStatusColor(dir);
}
break;
case DirectoryStatus:
return static_cast<int>(dir.status);
case DirectoryPaused:
return dir.paused;
case DirectoryStatusString:
return dirStatusString(dir);
case DirectoryStatusColor:
return dirStatusColor(dir);
case DirectoryId:
return dir.id;
case DirectoryPath:
return dir.path;
case DirectoryPullErrorCount:
return dir.pullErrorCount;
default:;
2016-08-25 00:45:32 +02:00
}
2019-02-16 01:06:08 +01:00
2016-08-25 00:45:32 +02:00
return QVariant();
}
bool SyncthingDirectoryModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
2019-05-04 22:10:37 +02:00
Q_UNUSED(index)
Q_UNUSED(value)
Q_UNUSED(role)
return false;
2016-08-25 00:45:32 +02:00
}
int SyncthingDirectoryModel::rowCount(const QModelIndex &parent) const
{
2017-05-01 03:34:43 +02:00
if (!parent.isValid()) {
return static_cast<int>(m_dirs.size());
} else if (!parent.parent().isValid() && static_cast<size_t>(parent.row()) < m_rowCount.size()) {
return m_rowCount[static_cast<size_t>(parent.row())];
2016-08-25 00:45:32 +02:00
} else {
return 0;
}
}
int SyncthingDirectoryModel::columnCount(const QModelIndex &parent) const
{
2017-05-01 03:34:43 +02:00
if (!parent.isValid()) {
2016-08-25 00:45:32 +02:00
return 2; // label/ID, status/buttons
2017-05-01 03:34:43 +02:00
} else if (!parent.parent().isValid()) {
2016-08-25 00:45:32 +02:00
return 2; // field name and value
} else {
return 0;
}
}
void SyncthingDirectoryModel::dirStatusChanged(const SyncthingDir &dir, int index)
2016-08-25 00:45:32 +02:00
{
if (index < 0 || static_cast<size_t>(index) >= m_rowCount.size()) {
return;
}
// update top-level indizes
2016-08-25 00:45:32 +02:00
const QModelIndex modelIndex1(this->index(index, 0, QModelIndex()));
2018-08-04 15:47:43 +02:00
static const QVector<int> modelRoles1({ Qt::DisplayRole, Qt::EditRole, Qt::DecorationRole, DirectoryPaused, DirectoryStatus,
DirectoryStatusString, DirectoryStatusColor, DirectoryId, DirectoryPath, DirectoryPullErrorCount });
emit dataChanged(modelIndex1, modelIndex1, modelRoles1);
2016-08-25 00:45:32 +02:00
const QModelIndex modelIndex2(this->index(index, 1, QModelIndex()));
static const QVector<int> modelRoles2({ Qt::DisplayRole, Qt::EditRole, Qt::ForegroundRole });
emit dataChanged(modelIndex2, modelIndex2, modelRoles2);
// remove/insert detail rows
const auto oldRowCount = m_rowCount[static_cast<size_t>(index)];
const auto newRowCount = computeDirectoryRowCount(dir);
const auto newLastRow = newRowCount - 1;
if (oldRowCount > newRowCount) {
// begin removing rows for statistics
beginRemoveRows(modelIndex1, 2, 3);
m_rowCount[static_cast<size_t>(index)] = newRowCount;
endRemoveRows();
} else if (newRowCount > oldRowCount) {
// begin inserting rows for statistics
beginInsertRows(modelIndex1, 2, 3);
m_rowCount[static_cast<size_t>(index)] = newRowCount;
endInsertRows();
}
// update detail rows
static const QVector<int> modelRoles3({ Qt::DisplayRole, Qt::EditRole, Qt::ToolTipRole });
emit dataChanged(this->index(0, 1, modelIndex1), this->index(newLastRow, 1, modelIndex1), modelRoles3);
static const QVector<int> modelRoles4({ Qt::DisplayRole, Qt::EditRole, DirectoryDetail });
emit dataChanged(this->index(0, 0, modelIndex1), this->index(newLastRow, 0, modelIndex1), modelRoles4);
}
void SyncthingDirectoryModel::handleConfigInvalidated()
{
beginResetModel();
}
void SyncthingDirectoryModel::handleNewConfigAvailable()
{
updateRowCount();
endResetModel();
}
void SyncthingDirectoryModel::handleStatusIconsChanged()
{
emit dataChanged(index(0, 0), index(static_cast<int>(m_dirs.size()) - 1, 0), QVector<int>({ Qt::DecorationRole }));
}
QString SyncthingDirectoryModel::dirStatusString(const SyncthingDir &dir)
{
if (dir.paused && dir.status != SyncthingDirStatus::OutOfSync) {
return tr("Paused");
}
if (dir.isUnshared()) {
return tr("Unshared");
}
switch (dir.status) {
case SyncthingDirStatus::Unknown:
return dir.rawStatus.isEmpty() ? tr("Unknown status") : QString(dir.rawStatus);
case SyncthingDirStatus::Idle:
return tr("Idle");
case SyncthingDirStatus::WaitingToScan:
return tr("Waiting to scan");
case SyncthingDirStatus::Scanning:
if (dir.scanningPercentage > 0) {
if (dir.scanningRate != 0.0) {
return tr("Scanning (%1 %, %2)").arg(dir.scanningPercentage).arg(bitrateToString(dir.scanningRate * 0.008, true).data());
}
return tr("Scanning (%1 %)").arg(dir.scanningPercentage);
}
return tr("Scanning");
case SyncthingDirStatus::WaitingToSync:
return tr("Waiting to sync");
case SyncthingDirStatus::PreparingToSync:
return tr("Preparing to sync");
case SyncthingDirStatus::Synchronizing:
return dir.completionPercentage > 0 ? tr("Synchronizing (%1 %)").arg(dir.completionPercentage) : tr("Synchronizing");
case SyncthingDirStatus::Cleaning:
return tr("Cleaning");
case SyncthingDirStatus::WaitingToClean:
return tr("Waiting to clean");
case SyncthingDirStatus::OutOfSync:
return tr("Out of sync");
}
return QString();
}
2017-09-01 17:13:01 +02:00
QVariant SyncthingDirectoryModel::dirStatusColor(const SyncthingDir &dir) const
{
if (dir.paused && dir.status != SyncthingDirStatus::OutOfSync) {
2017-09-01 17:13:01 +02:00
return QVariant();
}
if (dir.isUnshared()) {
return Colors::orange(m_brightColors);
}
switch (dir.status) {
case SyncthingDirStatus::Unknown:
break;
case SyncthingDirStatus::Idle:
return Colors::green(m_brightColors);
case SyncthingDirStatus::WaitingToScan:
case SyncthingDirStatus::WaitingToSync:
case SyncthingDirStatus::WaitingToClean:
return Colors::orange(m_brightColors);
case SyncthingDirStatus::Scanning:
case SyncthingDirStatus::PreparingToSync:
case SyncthingDirStatus::Synchronizing:
case SyncthingDirStatus::Cleaning:
return Colors::blue(m_brightColors);
case SyncthingDirStatus::OutOfSync:
return Colors::red(m_brightColors);
}
2017-09-01 17:13:01 +02:00
return QVariant();
2016-08-25 00:45:32 +02:00
}
void SyncthingDirectoryModel::updateRowCount()
{
m_rowCount.clear();
m_rowCount.reserve(m_dirs.size());
for (const auto &dir : m_dirs) {
m_rowCount.emplace_back(computeDirectoryRowCount(dir));
}
}
2016-08-25 00:45:32 +02:00
} // namespace Data