File indexing completed on 2025-01-05 05:07:03

0001 // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0002 // SPDX-FileCopyrightText: 2018 Alexis Lopes Zubeta <contact@azubieta.net>
0003 // SPDX-FileCopyrightText: 2020 Tomaz Canabrava <tcanabrava@kde.org>
0004 
0005 #include "connectionsmodel.h"
0006 
0007 #include <chrono>
0008 
0009 #include <QDebug>
0010 #include <QMetaEnum>
0011 
0012 #include <KLocalizedString>
0013 
0014 using namespace std::chrono_literals;
0015 
0016 ConnectionsModel::ConnectionsModel(QObject *parent)
0017     : QAbstractTableModel(parent)
0018 {
0019     connect(&m_netstatHelper, &NetstatHelper::queryFinished, this, &ConnectionsModel::refreshConnections);
0020     connect(&timer, &QTimer::timeout, &m_netstatHelper, &NetstatHelper::query);
0021     timer.setInterval(10s); // arbitrary query interval, should be long enough to let the helper finish on its own
0022 }
0023 
0024 void ConnectionsModel::start()
0025 {
0026     m_busy = true;
0027     Q_EMIT busyChanged();
0028     timer.start();
0029     QTimer::singleShot(0, &m_netstatHelper, &NetstatHelper::query);
0030 }
0031 
0032 void ConnectionsModel::stop()
0033 {
0034     timer.stop();
0035 }
0036 
0037 int ConnectionsModel::rowCount(const QModelIndex &parent) const
0038 {
0039     if (parent.isValid()) {
0040         return 0;
0041     }
0042 
0043     return m_connectionsData.size();
0044 }
0045 
0046 int ConnectionsModel::columnCount(const QModelIndex &parent) const
0047 {
0048     Q_UNUSED(parent)
0049     return QMetaEnum::fromType<ConnectionsModelColumns>().keyCount();
0050 }
0051 
0052 bool ConnectionsModel::busy() const
0053 {
0054     return m_busy;
0055 }
0056 
0057 QVariant ConnectionsModel::data(const QModelIndex &index, int role) const
0058 {
0059     Q_UNUSED(role);
0060     const auto checkIndexFlags = QAbstractItemModel::CheckIndexOption::IndexIsValid | QAbstractItemModel::CheckIndexOption::ParentIsInvalid;
0061 
0062     if (!checkIndex(index, checkIndexFlags)) {
0063         return {};
0064     }
0065 
0066     ConnectionsData data = m_connectionsData.at(index.row());
0067 
0068     switch (static_cast<ConnectionsModelColumns>(index.column())) {
0069     case ProtocolColumn:
0070         return data.protocol;
0071     case LocalAddressColumn:
0072         return data.localAddress;
0073     case ForeignAddressColumn:
0074         return data.foreignAddress;
0075     case StatusColumn:
0076         return data.status;
0077     case PidColumn:
0078         return data.pid;
0079     case ProgramColumn:
0080         // HACK. Firefox reports as MainThread
0081         if (data.program == QLatin1String("MainThread")) {
0082             return "Firefox";
0083         } else {
0084             return data.program;
0085         }
0086         break;
0087     }
0088     return {};
0089 }
0090 
0091 QVariant ConnectionsModel::headerData(int section, Qt::Orientation orientation, int role) const
0092 {
0093     Q_UNUSED(orientation)
0094     Q_UNUSED(role)
0095     switch (static_cast<ConnectionsModelColumns>(section)) {
0096     case ProtocolColumn:
0097         return i18nc("@title:column", "Protocol");
0098     case LocalAddressColumn:
0099         return i18nc("@title:column", "Local address");
0100     case ForeignAddressColumn:
0101         return i18nc("@title:column", "Foreign address");
0102     case StatusColumn:
0103         return i18nc("@title:column", "Status");
0104     case PidColumn:
0105         return i18nc("@title:column", "PID");
0106     case ProgramColumn:
0107         return i18nc("@title:column", "Program");
0108     }
0109     return {};
0110 }
0111 
0112 void ConnectionsModel::refreshConnections(const QList<QStringList> &result)
0113 {
0114     if (m_netstatHelper.hasError()) {
0115         Q_EMIT showErrorMessage(i18n("Failed to get connections: %1", m_netstatHelper.errorString()));
0116         return;
0117     }
0118 
0119     const auto oldConnectionsData = m_connectionsData;
0120     QList<ConnectionsData> newConnectionsData;
0121 
0122     beginResetModel();
0123     m_connectionsData.clear();
0124     for (const auto &connection : result) {
0125         ConnectionsData conn{.protocol = connection.at(0),
0126                              .localAddress = connection.at(1),
0127                              .foreignAddress = connection.at(2),
0128                              .status = connection.at(3),
0129                              .pid = connection.at(4),
0130                              .program = connection.at(5)};
0131 
0132         if (conn.status == QLatin1String("UNCONN")) {
0133             conn.status = i18n("Not Connected");
0134         } else if (conn.status == QLatin1String("ESTAB")) {
0135             conn.status = i18n("Established");
0136         } else if (conn.status == QLatin1String("LISTEN")) {
0137             conn.status = i18n("Listening");
0138         }
0139 
0140         newConnectionsData.append(conn);
0141     }
0142 
0143     if (newConnectionsData != oldConnectionsData) {
0144         beginResetModel();
0145         m_connectionsData = newConnectionsData;
0146         endResetModel();
0147     }
0148 
0149     if (newConnectionsData.count() != oldConnectionsData.count()) {
0150         Q_EMIT countChanged();
0151     }
0152 
0153     if (m_busy) {
0154         m_busy = false;
0155         Q_EMIT busyChanged();
0156     }
0157 }