File indexing completed on 2024-05-19 05:37:50

0001 /*
0002     SPDX-FileCopyrightText: 2007 Menard Alexis <darktears31@gmail.com>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "hotplugengine.h"
0008 #include "hotplugservice.h"
0009 
0010 #include <QDir>
0011 #include <QDirIterator>
0012 #include <QStandardPaths>
0013 #include <QTimer>
0014 
0015 #include <KConfigGroup>
0016 #include <KDesktopFile>
0017 #include <KDirWatch>
0018 #include <KService>
0019 #include <Plasma5Support/DataContainer>
0020 #include <QDebug>
0021 
0022 // solid specific includes
0023 #include <Solid/Device>
0024 #include <Solid/DeviceInterface>
0025 #include <Solid/DeviceNotifier>
0026 #include <Solid/OpticalDisc>
0027 #include <Solid/StorageDrive>
0028 #include <Solid/StorageVolume>
0029 
0030 // #define HOTPLUGENGINE_TIMING
0031 
0032 HotplugEngine::HotplugEngine(QObject *parent)
0033     : Plasma5Support::DataEngine(parent)
0034     , m_dirWatch(new KDirWatch(this))
0035 {
0036     const QStringList folders =
0037         QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("solid/actions"), QStandardPaths::LocateDirectory);
0038 
0039     for (const QString &folder : folders) {
0040         m_dirWatch->addDir(folder, KDirWatch::WatchFiles);
0041     }
0042     connect(m_dirWatch, &KDirWatch::created, this, &HotplugEngine::updatePredicates);
0043     connect(m_dirWatch, &KDirWatch::deleted, this, &HotplugEngine::updatePredicates);
0044     connect(m_dirWatch, &KDirWatch::dirty, this, &HotplugEngine::updatePredicates);
0045     init();
0046 }
0047 
0048 HotplugEngine::~HotplugEngine()
0049 {
0050 }
0051 
0052 void HotplugEngine::init()
0053 {
0054     findPredicates();
0055 
0056     Solid::Predicate p(Solid::DeviceInterface::StorageAccess);
0057     p |= Solid::Predicate(Solid::DeviceInterface::StorageDrive);
0058     p |= Solid::Predicate(Solid::DeviceInterface::StorageVolume);
0059     p |= Solid::Predicate(Solid::DeviceInterface::OpticalDrive);
0060     p |= Solid::Predicate(Solid::DeviceInterface::OpticalDisc);
0061     p |= Solid::Predicate(Solid::DeviceInterface::PortableMediaPlayer);
0062     p |= Solid::Predicate(Solid::DeviceInterface::Camera);
0063     const QList<Solid::Device> devices = Solid::Device::listFromQuery(p);
0064     for (const Solid::Device &dev : devices) {
0065         m_startList.insert(dev.udi(), dev);
0066     }
0067 
0068     connect(Solid::DeviceNotifier::instance(), &Solid::DeviceNotifier::deviceAdded, this, &HotplugEngine::onDeviceAdded);
0069     connect(Solid::DeviceNotifier::instance(), &Solid::DeviceNotifier::deviceRemoved, this, &HotplugEngine::onDeviceRemoved);
0070 
0071     m_encryptedPredicate = Solid::Predicate(QStringLiteral("StorageVolume"), QStringLiteral("usage"), "Encrypted");
0072 
0073     processNextStartupDevice();
0074 }
0075 
0076 Plasma5Support::Service *HotplugEngine::serviceForSource(const QString &source)
0077 {
0078     return new HotplugService(this, source);
0079 }
0080 
0081 void HotplugEngine::processNextStartupDevice()
0082 {
0083     if (!m_startList.isEmpty()) {
0084         QHash<QString, Solid::Device>::iterator it = m_startList.begin();
0085         // Solid::Device dev = const_cast<Solid::Device &>(m_startList.takeFirst());
0086         handleDeviceAdded(it.value(), false);
0087         m_startList.erase(it);
0088     }
0089 
0090     if (m_startList.isEmpty()) {
0091         m_predicates.clear();
0092     } else {
0093         QTimer::singleShot(0, this, &HotplugEngine::processNextStartupDevice);
0094     }
0095 }
0096 
0097 void HotplugEngine::findPredicates()
0098 {
0099     m_predicates.clear();
0100     QStringList files;
0101     const QStringList dirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("solid/actions"), QStandardPaths::LocateDirectory);
0102     for (const QString &dir : dirs) {
0103         QDirIterator it(dir, QStringList() << QStringLiteral("*.desktop"));
0104         while (it.hasNext()) {
0105             files.prepend(it.next());
0106         }
0107     }
0108     // qDebug() << files;
0109     for (const QString &path : std::as_const(files)) {
0110         KDesktopFile cfg(path);
0111         const QString string_predicate = cfg.desktopGroup().readEntry("X-KDE-Solid-Predicate");
0112         // qDebug() << path << string_predicate;
0113         m_predicates.insert(QUrl(path).fileName(), Solid::Predicate::fromString(string_predicate));
0114     }
0115 
0116     if (m_predicates.isEmpty()) {
0117         m_predicates.insert(QString(), Solid::Predicate::fromString(QString()));
0118     }
0119 }
0120 
0121 void HotplugEngine::updatePredicates(const QString &path)
0122 {
0123     Q_UNUSED(path)
0124 
0125     findPredicates();
0126 
0127     QHashIterator<QString, Solid::Device> it(m_devices);
0128     while (it.hasNext()) {
0129         it.next();
0130         Solid::Device device(it.value());
0131         QString udi(it.key());
0132 
0133         const QStringList predicates = predicatesForDevice(device);
0134         if (!predicates.isEmpty()) {
0135             if (sources().contains(udi)) {
0136                 Plasma5Support::DataEngine::Data data;
0137                 data.insert(QStringLiteral("predicateFiles"), predicates);
0138                 data.insert(QStringLiteral("actions"), actionsForPredicates(predicates));
0139                 setData(udi, data);
0140             } else {
0141                 handleDeviceAdded(device, false);
0142             }
0143         } else if (!m_encryptedPredicate.matches(device) && sources().contains(udi)) {
0144             removeSource(udi);
0145         }
0146     }
0147 }
0148 
0149 QStringList HotplugEngine::predicatesForDevice(Solid::Device &device) const
0150 {
0151     QStringList interestingDesktopFiles;
0152     // search in all desktop configuration file if the device inserted is a correct device
0153     QHashIterator<QString, Solid::Predicate> it(m_predicates);
0154     // qDebug() << "=================" << udi;
0155     while (it.hasNext()) {
0156         it.next();
0157         if (it.value().matches(device)) {
0158             // qDebug() << "     hit" << it.key();
0159             interestingDesktopFiles << it.key();
0160         }
0161     }
0162 
0163     return interestingDesktopFiles;
0164 }
0165 
0166 QVariantList HotplugEngine::actionsForPredicates(const QStringList &predicates) const
0167 {
0168     QVariantList actions;
0169     actions.reserve(predicates.count());
0170 
0171     for (const QString &desktop : predicates) {
0172         const QString actionUrl = QStandardPaths::locate(QStandardPaths::GenericDataLocation, "solid/actions/" + desktop);
0173         auto services = KService(actionUrl).actions();
0174         if (!services.isEmpty()) {
0175             Plasma5Support::DataEngine::Data action;
0176             action.insert(QStringLiteral("predicate"), desktop);
0177             action.insert(QStringLiteral("text"), services[0].text());
0178             action.insert(QStringLiteral("icon"), services[0].icon());
0179             actions << action;
0180         }
0181     }
0182 
0183     return actions;
0184 }
0185 
0186 void HotplugEngine::onDeviceAdded(const QString &udi)
0187 {
0188     Solid::Device device(udi);
0189     handleDeviceAdded(device);
0190 }
0191 
0192 void HotplugEngine::handleDeviceAdded(Solid::Device &device, bool added)
0193 {
0194     // qDebug() << "adding" << device.udi();
0195 #ifdef HOTPLUGENGINE_TIMING
0196     QTime t;
0197     t.start();
0198 #endif
0199     // Skip things we know we don't care about
0200     if (device.is<Solid::StorageDrive>()) {
0201         Solid::StorageDrive *drive = device.as<Solid::StorageDrive>();
0202         if (!drive->isHotpluggable()) {
0203 #ifdef HOTPLUGENGINE_TIMING
0204             qDebug() << "storage, but not pluggable, returning" << t.restart();
0205 #endif
0206             return;
0207         }
0208     } else if (device.is<Solid::StorageVolume>()) {
0209         Solid::StorageVolume *volume = device.as<Solid::StorageVolume>();
0210         Solid::StorageVolume::UsageType type = volume->usage();
0211         if ((type == Solid::StorageVolume::Unused || type == Solid::StorageVolume::PartitionTable) && !device.is<Solid::OpticalDisc>()) {
0212 #ifdef HOTPLUGENGINE_TIMING
0213             qDebug() << "storage volume, but not of interest" << t.restart();
0214 #endif
0215             return;
0216         }
0217     }
0218 
0219     m_devices.insert(device.udi(), device);
0220 
0221     if (m_predicates.isEmpty()) {
0222         findPredicates();
0223     }
0224 
0225     const QStringList interestingDesktopFiles = predicatesForDevice(device);
0226     const bool isEncryptedContainer = m_encryptedPredicate.matches(device);
0227 
0228     if (!interestingDesktopFiles.isEmpty() || isEncryptedContainer) {
0229         // qDebug() << device.product();
0230         // qDebug() << device.vendor();
0231         // qDebug() << "number of interesting desktop file : " << interestingDesktopFiles.size();
0232         Plasma5Support::DataEngine::Data data;
0233         data.insert(QStringLiteral("added"), added);
0234         data.insert(QStringLiteral("udi"), device.udi());
0235 
0236         if (!device.description().isEmpty()) {
0237             data.insert(QStringLiteral("text"), device.description());
0238         } else {
0239             data.insert(QStringLiteral("text"), QString(device.vendor() + QLatin1Char(' ') + device.product()));
0240         }
0241         data.insert(QStringLiteral("icon"), device.icon());
0242         data.insert(QStringLiteral("emblems"), device.emblems());
0243         data.insert(QStringLiteral("predicateFiles"), interestingDesktopFiles);
0244         data.insert(QStringLiteral("actions"), actionsForPredicates(interestingDesktopFiles));
0245 
0246         data.insert(QStringLiteral("isEncryptedContainer"), isEncryptedContainer);
0247 
0248         setData(device.udi(), data);
0249         // qDebug() << "add hardware solid : " << udi;
0250     }
0251 
0252 #ifdef HOTPLUGENGINE_TIMING
0253     qDebug() << "total time" << t.restart();
0254 #endif
0255 }
0256 
0257 void HotplugEngine::onDeviceRemoved(const QString &udi)
0258 {
0259     // qDebug() << "remove hardware:" << udi;
0260 
0261     if (m_startList.contains(udi)) {
0262         m_startList.remove(udi);
0263         return;
0264     }
0265 
0266     m_devices.remove(udi);
0267     removeSource(udi);
0268 }
0269 
0270 K_PLUGIN_CLASS_WITH_JSON(HotplugEngine, "plasma-dataengine-hotplug.json")
0271 
0272 #include "hotplugengine.moc"