File indexing completed on 2024-05-12 05:35:55

0001 /*
0002     SPDX-FileCopyrightText: 2009-2010 Trever Fischer <tdfischer@fedoraproject.org>
0003     SPDX-FileCopyrightText: 2015 Kai Uwe Broulik <kde@privat.broulik.de>
0004     SPDX-FileCopyrightText: 2021 Ismael Asensio <isma.af@gmail.com>
0005 
0006     SPDX-License-Identifier: GPL-2.0-or-later
0007 */
0008 
0009 #include "DeviceModel.h"
0010 
0011 #include <QIcon>
0012 #include <QTimer>
0013 
0014 #include <KLocalizedString>
0015 #include <Solid/Device>
0016 #include <Solid/DeviceNotifier>
0017 #include <Solid/StorageAccess>
0018 #include <Solid/StorageVolume>
0019 
0020 #include "AutomounterSettings.h"
0021 
0022 DeviceModel::DeviceModel(AutomounterSettings *m_settings, QObject *parent)
0023     : QAbstractItemModel(parent)
0024     , m_settings(m_settings)
0025 {
0026     reload();
0027 
0028     connect(Solid::DeviceNotifier::instance(), &Solid::DeviceNotifier::deviceAdded, this, &DeviceModel::deviceAttached);
0029     connect(Solid::DeviceNotifier::instance(), &Solid::DeviceNotifier::deviceRemoved, this, &DeviceModel::deviceRemoved);
0030 }
0031 
0032 void DeviceModel::forgetDevice(const QString &udi)
0033 {
0034     if (m_disconnected.contains(udi)) {
0035         const int deviceIndex = m_disconnected.indexOf(udi);
0036         beginRemoveRows(index(RowDetached, 0), deviceIndex, deviceIndex);
0037         m_disconnected.removeOne(udi);
0038         endRemoveRows();
0039     } else if (m_attached.contains(udi)) {
0040         const int deviceIndex = m_attached.indexOf(udi);
0041         beginRemoveRows(index(RowAttached, 0), deviceIndex, deviceIndex);
0042         m_attached.removeOne(udi);
0043         endRemoveRows();
0044     }
0045 }
0046 
0047 QVariant DeviceModel::headerData(int section, Qt::Orientation orientation, int role) const
0048 {
0049     if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
0050         switch (section) {
0051         case 0:
0052             return i18n("Automount Device");
0053         case 1:
0054             return i18nc("As in automount on login", "On Login");
0055         case 2:
0056             return i18nc("As in automount on attach", "On Attach");
0057         }
0058     }
0059     return QVariant();
0060 }
0061 
0062 void DeviceModel::deviceAttached(const QString &udi)
0063 {
0064     const Solid::Device device(udi);
0065     auto volume = device.as<Solid::StorageVolume>();
0066 
0067     if (volume && !volume->isIgnored()) {
0068         if (m_disconnected.contains(udi)) {
0069             const int deviceIndex = m_disconnected.indexOf(udi);
0070             beginRemoveRows(index(RowDetached, 0), deviceIndex, deviceIndex);
0071             m_disconnected.removeOne(udi);
0072             endRemoveRows();
0073         }
0074 
0075         addNewDevice(udi);
0076     }
0077 }
0078 
0079 void DeviceModel::deviceRemoved(const QString &udi)
0080 {
0081     if (m_attached.contains(udi)) {
0082         const int deviceIndex = m_attached.indexOf(udi);
0083 
0084         beginRemoveRows(index(RowAttached, 0), deviceIndex, deviceIndex);
0085         m_attached.removeOne(udi);
0086         endRemoveRows();
0087 
0088         // We move the device to the "Disconnected" section only if it
0089         // is a known device, meaning we have some setting for this device.
0090         // Otherwise the device is not moved to the "Disconnected" section
0091         // because we need to check whether the device that just got detached is ignored
0092         // (don't show partition tables and other garbage) but this information
0093         // is no longer available once the device is gone
0094         if (m_settings->knownDevices().contains(udi)) {
0095             beginInsertRows(index(RowDetached, 0), m_disconnected.size(), m_disconnected.size());
0096             m_disconnected << udi;
0097             endInsertRows();
0098         }
0099     }
0100 }
0101 
0102 void DeviceModel::addNewDevice(const QString &udi)
0103 {
0104     m_settings->load();
0105 
0106     // The kded module might not have updated the settings yet with the new device.
0107     // Let's try again  for a limited number of times
0108     static int loadTryouts = 0;
0109     if (!m_settings->hasDeviceInfo(udi)) {
0110         if (loadTryouts < 5) {
0111             loadTryouts++;
0112             QTimer::singleShot(100, this, [this, udi]() {
0113                 addNewDevice(udi);
0114             });
0115         }
0116         return;
0117     }
0118     loadTryouts = 0;
0119 
0120     const Solid::Device dev(udi);
0121     if (dev.isValid()) {
0122         if (dev.is<Solid::StorageAccess>()) {
0123             const Solid::StorageAccess *access = dev.as<Solid::StorageAccess>();
0124             if (!access->isIgnored() || !access->isAccessible()) {
0125                 beginInsertRows(index(RowAttached, 0), m_attached.size(), m_attached.size());
0126                 m_attached << udi;
0127                 endInsertRows();
0128             }
0129         }
0130     } else {
0131         beginInsertRows(index(RowDetached, 0), m_disconnected.size(), m_disconnected.size());
0132         m_disconnected << udi;
0133         endInsertRows();
0134     }
0135 }
0136 
0137 void DeviceModel::reload()
0138 {
0139     beginResetModel();
0140     m_attached.clear();
0141     m_disconnected.clear();
0142 
0143     const auto knownDevices = m_settings->knownDevices();
0144     for (const QString &dev : knownDevices) {
0145         addNewDevice(dev);
0146     }
0147     endResetModel();
0148 }
0149 
0150 QModelIndex DeviceModel::index(int row, int column, const QModelIndex &parent) const
0151 {
0152     if (column < 0 || column >= columnCount()) {
0153         return QModelIndex();
0154     }
0155     if (parent.isValid()) {
0156         if (parent.column() > 0 || parent.row() == RowAll) {
0157             return QModelIndex();
0158         }
0159 
0160         const int deviceCount = (parent.row() == RowAttached) ? m_attached.size() : m_disconnected.size();
0161         if (row < deviceCount) {
0162             return createIndex(row, column, parent.row());
0163         }
0164     } else {
0165         if (row < rowCount()) {
0166             return createIndex(row, column, 3);
0167         }
0168     }
0169     return QModelIndex();
0170 }
0171 
0172 QModelIndex DeviceModel::parent(const QModelIndex &index) const
0173 {
0174     if (index.isValid()) {
0175         if (index.internalId() == 3)
0176             return QModelIndex();
0177         return createIndex(index.internalId(), 0, 3);
0178     }
0179     return QModelIndex();
0180 }
0181 
0182 Qt::ItemFlags DeviceModel::flags(const QModelIndex &index) const
0183 {
0184     if (!index.isValid()) {
0185         return Qt::NoItemFlags;
0186     }
0187 
0188     if (!index.parent().isValid()) {
0189         if (index.row() == RowAll) {
0190             return Qt::ItemIsEnabled | (index.column() > 0 ? Qt::ItemIsUserCheckable : Qt::NoItemFlags);
0191         }
0192         return (m_settings->automountOnLogin() && m_settings->automountOnPlugin()) ? Qt::NoItemFlags : Qt::ItemIsEnabled;
0193     }
0194 
0195     // Select only detached devices to be removed
0196     Qt::ItemFlag selectableFlag = index.parent().row() == RowDetached ? Qt::ItemIsSelectable : Qt::NoItemFlags;
0197 
0198     switch (index.column()) {
0199     case 0:
0200         if (m_settings->automountOnLogin() && m_settings->automountOnPlugin()) {
0201             return Qt::NoItemFlags;
0202         }
0203         return selectableFlag | Qt::ItemIsEnabled;
0204     case 1:
0205         return Qt::ItemIsUserCheckable | selectableFlag | (m_settings->automountOnLogin() ? Qt::NoItemFlags : Qt::ItemIsEnabled);
0206     case 2:
0207         return Qt::ItemIsUserCheckable | selectableFlag | (m_settings->automountOnPlugin() ? Qt::NoItemFlags : Qt::ItemIsEnabled);
0208     default:
0209         Q_UNREACHABLE();
0210     }
0211 }
0212 
0213 bool DeviceModel::setData(const QModelIndex &index, const QVariant &value, int role)
0214 {
0215     if (!index.isValid() || role != Qt::CheckStateRole || index.column() == 0) {
0216         return false;
0217     }
0218 
0219     if (!index.parent().isValid() && index.row() == RowAll) {
0220         switch (index.column()) {
0221         case 1:
0222             setAutomaticMountOnLogin(value.toInt() == Qt::Checked);
0223             break;
0224         case 2:
0225             setAutomaticMountOnPlugin(value.toInt() == Qt::Checked);
0226             break;
0227         }
0228         Q_EMIT dataChanged(index, index);
0229         return true;
0230     }
0231 
0232     const QString &udi = index.data(Qt::UserRole).toString();
0233     Q_ASSERT(m_settings->hasDeviceInfo(udi));
0234 
0235     switch (index.column()) {
0236     case 1:
0237         m_settings->deviceSettings(udi)->setMountOnLogin(value.toInt() == Qt::Checked);
0238         break;
0239     case 2:
0240         m_settings->deviceSettings(udi)->setMountOnAttach(value.toInt() == Qt::Checked);
0241         break;
0242     }
0243 
0244     Q_EMIT dataChanged(index, index);
0245     return true;
0246 }
0247 
0248 QVariant DeviceModel::data(const QModelIndex &index, int role) const
0249 {
0250     if (!index.isValid()) {
0251         return QVariant();
0252     }
0253 
0254     if (!index.parent().isValid()) {
0255         if (role == Qt::DisplayRole && index.column() == 0) {
0256             switch (index.row()) {
0257             case RowAll:
0258                 return m_settings->automountUnknownDevices() ? i18n("All Devices") : i18n("All Known Devices");
0259             case RowAttached:
0260                 return i18n("Attached Devices");
0261             case RowDetached:
0262                 return i18n("Disconnected Devices");
0263             }
0264         }
0265         if (role == Qt::CheckStateRole && index.row() == RowAll) {
0266             if (index.column() == 1) {
0267                 return m_settings->automountOnLogin() ? Qt::Checked : Qt::Unchecked;
0268             } else if (index.column() == 2) {
0269                 return m_settings->automountOnPlugin() ? Qt::Checked : Qt::Unchecked;
0270             }
0271         }
0272         return QVariant();
0273     }
0274 
0275     if (index.parent().row() > RowDetached || index.column() >= columnCount() || index.row() >= rowCount(index.parent())) {
0276         return QVariant();
0277     }
0278 
0279     const bool isAttached = index.parent().row() == RowAttached;
0280     if (role == TypeRole) {
0281         return isAttached ? Attached : Detached;
0282     }
0283 
0284     const QString &udi = isAttached ? m_attached.at(index.row()) : m_disconnected.at(index.row());
0285     if (role == Qt::UserRole) {
0286         return udi;
0287     }
0288 
0289     Q_ASSERT(m_settings->hasDeviceInfo(udi));
0290 
0291     if (index.column() == 0) {
0292         if (isAttached) {
0293             Solid::Device dev(udi);
0294             switch (role) {
0295             case Qt::DisplayRole:
0296                 return dev.description();
0297             case Qt::ToolTipRole:
0298                 return i18n("UDI: %1", udi);
0299             case Qt::DecorationRole:
0300                 return QIcon::fromTheme(dev.icon());
0301             }
0302         } else {
0303             switch (role) {
0304             case Qt::DisplayRole:
0305                 return m_settings->deviceSettings(udi)->name();
0306             case Qt::ToolTipRole:
0307                 return i18n("UDI: %1", udi);
0308             case Qt::DecorationRole:
0309                 return QIcon::fromTheme(m_settings->deviceSettings(udi)->icon());
0310             }
0311         }
0312     } else if (index.column() == 1) {
0313         const bool automount = m_settings->shouldAutomountDevice(udi, AutomounterSettings::Login);
0314         switch (role) {
0315         case Qt::CheckStateRole:
0316             return automount ? Qt::Checked : Qt::Unchecked;
0317         case Qt::ToolTipRole:
0318             return automount ? i18n("This device will be automatically mounted at login.") : i18n("This device will not be automatically mounted at login.");
0319         }
0320     } else if (index.column() == 2) {
0321         const bool automount = m_settings->shouldAutomountDevice(udi, AutomounterSettings::Attach);
0322         switch (role) {
0323         case Qt::CheckStateRole:
0324             return automount ? Qt::Checked : Qt::Unchecked;
0325         case Qt::ToolTipRole:
0326             return automount ? i18n("This device will be automatically mounted when attached.")
0327                              : i18n("This device will not be automatically mounted when attached.");
0328         }
0329     }
0330     return QVariant();
0331 }
0332 
0333 int DeviceModel::rowCount(const QModelIndex &parent) const
0334 {
0335     if (!parent.isValid()) {
0336         return 3;
0337     }
0338     if (parent.internalId() < 3 || parent.column() > 0) {
0339         return 0;
0340     }
0341 
0342     switch (parent.row()) {
0343     case RowAll:
0344         return 0;
0345     case RowAttached:
0346         return m_attached.size();
0347     case RowDetached:
0348         return m_disconnected.size();
0349     }
0350 
0351     return 0;
0352 }
0353 
0354 int DeviceModel::columnCount(const QModelIndex &parent) const
0355 {
0356     Q_UNUSED(parent)
0357     return 3;
0358 }
0359 
0360 void DeviceModel::setAutomaticMountOnLogin(bool automaticLogin)
0361 {
0362     if (m_settings->automountOnLogin() == automaticLogin) {
0363         return;
0364     }
0365 
0366     m_settings->setAutomountOnLogin(automaticLogin);
0367     updateCheckedColumns(1);
0368 }
0369 
0370 void DeviceModel::setAutomaticMountOnPlugin(bool automaticAttached)
0371 {
0372     if (m_settings->automountOnPlugin() == automaticAttached) {
0373         return;
0374     }
0375 
0376     m_settings->setAutomountOnPlugin(automaticAttached);
0377     updateCheckedColumns(2);
0378 }
0379 
0380 void DeviceModel::setAutomaticUnknown(bool automaticUnknown)
0381 {
0382     if (m_settings->automountUnknownDevices() == automaticUnknown) {
0383         return;
0384     }
0385 
0386     m_settings->setAutomountUnknownDevices(automaticUnknown);
0387     Q_EMIT dataChanged(index(0, 0), index(0, 0), {Qt::DisplayRole});
0388     updateCheckedColumns();
0389 }
0390 
0391 void DeviceModel::updateCheckedColumns(int column)
0392 {
0393     for (int parent = RowAttached; parent < rowCount(); parent++) {
0394         const auto parentIndex = index(parent, 0);
0395         Q_EMIT dataChanged(index(0, (column > 0 ? column : 1), parentIndex),
0396                            index(rowCount(parentIndex), column > 0 ? column : 2, parentIndex),
0397                            {Qt::CheckStateRole, Qt::ToolTipRole});
0398     }
0399 }