1 /*
2     SPDX-FileCopyrightText: 2007 Christopher Blauvelt <cblauvelt@gmail.com>
3 
4     SPDX-License-Identifier: LGPL-2.0-only
5 */
6 
7 #include "soliddeviceengine.h"
8 #include "soliddeviceservice.h"
9 
10 #include <QDateTime>
11 #include <QMetaEnum>
12 #include <Solid/GenericInterface>
13 #include <klocalizedstring.h>
14 
15 #include <KFormat>
16 #include <KNotification>
17 #include <QApplication>
18 #include <QDebug>
19 
20 #include <Plasma/DataContainer>
21 
22 // TODO: implement in libsolid2
23 namespace
24 {
25 template<class DevIface>
getAncestorAs(const Solid::Device & device)26 DevIface *getAncestorAs(const Solid::Device &device)
27 {
28     for (Solid::Device parent = device.parent(); parent.isValid(); parent = parent.parent()) {
29         if (parent.is<DevIface>()) {
30             return parent.as<DevIface>();
31         }
32     }
33     return nullptr;
34 }
35 }
36 
SolidDeviceEngine(QObject * parent,const QVariantList & args)37 SolidDeviceEngine::SolidDeviceEngine(QObject *parent, const QVariantList &args)
38     : Plasma::DataEngine(parent, args)
39     , m_temperature(nullptr)
40     , m_notifier(nullptr)
41 {
42     Q_UNUSED(args)
43     m_signalmanager = new DeviceSignalMapManager(this);
44 
45     listenForNewDevices();
46     setMinimumPollingInterval(1000);
47     connect(this, &Plasma::DataEngine::sourceRemoved, this, &SolidDeviceEngine::sourceWasRemoved);
48 }
49 
~SolidDeviceEngine()50 SolidDeviceEngine::~SolidDeviceEngine()
51 {
52 }
53 
serviceForSource(const QString & source)54 Plasma::Service *SolidDeviceEngine::serviceForSource(const QString &source)
55 {
56     return new SolidDeviceService(this, source);
57 }
58 
listenForNewDevices()59 void SolidDeviceEngine::listenForNewDevices()
60 {
61     if (m_notifier) {
62         return;
63     }
64 
65     // detect when new devices are added
66     m_notifier = Solid::DeviceNotifier::instance();
67     connect(m_notifier, &Solid::DeviceNotifier::deviceAdded, this, &SolidDeviceEngine::deviceAdded);
68     connect(m_notifier, &Solid::DeviceNotifier::deviceRemoved, this, &SolidDeviceEngine::deviceRemoved);
69 }
70 
sourceRequestEvent(const QString & name)71 bool SolidDeviceEngine::sourceRequestEvent(const QString &name)
72 {
73     if (name.startsWith('/')) {
74         Solid::Device device = Solid::Device(name);
75         if (device.isValid()) {
76             if (m_devicemap.contains(name)) {
77                 return true;
78             } else {
79                 m_devicemap[name] = device;
80                 return populateDeviceData(name);
81             }
82         }
83     } else {
84         Solid::Predicate predicate = Solid::Predicate::fromString(name);
85         if (predicate.isValid() && !m_predicatemap.contains(name)) {
86             foreach (const Solid::Device &device, Solid::Device::listFromQuery(predicate)) {
87                 m_predicatemap[name] << device.udi();
88             }
89 
90             setData(name, m_predicatemap[name]);
91             return true;
92         }
93     }
94 
95     qDebug() << "Source is not a predicate or a device.";
96     return false;
97 }
98 
sourceWasRemoved(const QString & source)99 void SolidDeviceEngine::sourceWasRemoved(const QString &source)
100 {
101     m_devicemap.remove(source);
102     m_predicatemap.remove(source);
103 }
104 
populateDeviceData(const QString & name)105 bool SolidDeviceEngine::populateDeviceData(const QString &name)
106 {
107     Solid::Device device = m_devicemap.value(name);
108     if (!device.isValid()) {
109         return false;
110     }
111 
112     QStringList devicetypes;
113     setData(name, I18N_NOOP("Parent UDI"), device.parentUdi());
114     setData(name, I18N_NOOP("Vendor"), device.vendor());
115     setData(name, I18N_NOOP("Product"), device.product());
116     setData(name, I18N_NOOP("Description"), device.description());
117     setData(name, I18N_NOOP("Icon"), device.icon());
118     setData(name, I18N_NOOP("Emblems"), device.emblems());
119     setData(name, I18N_NOOP("State"), Idle);
120     setData(name, I18N_NOOP("Operation result"), Working);
121     setData(name, I18N_NOOP("Timestamp"), QDateTime::currentDateTimeUtc());
122 
123     if (device.is<Solid::Processor>()) {
124         Solid::Processor *processor = device.as<Solid::Processor>();
125         if (!processor) {
126             return false;
127         }
128 
129         devicetypes << I18N_NOOP("Processor");
130         setData(name, I18N_NOOP("Number"), processor->number());
131         setData(name, I18N_NOOP("Max Speed"), processor->maxSpeed());
132         setData(name, I18N_NOOP("Can Change Frequency"), processor->canChangeFrequency());
133     }
134     if (device.is<Solid::Block>()) {
135         Solid::Block *block = device.as<Solid::Block>();
136         if (!block) {
137             return false;
138         }
139 
140         devicetypes << I18N_NOOP("Block");
141         setData(name, I18N_NOOP("Major"), block->deviceMajor());
142         setData(name, I18N_NOOP("Minor"), block->deviceMinor());
143         setData(name, I18N_NOOP("Device"), block->device());
144     }
145     if (device.is<Solid::StorageAccess>()) {
146         Solid::StorageAccess *storageaccess = device.as<Solid::StorageAccess>();
147         if (!storageaccess) {
148             return false;
149         }
150 
151         devicetypes << I18N_NOOP("Storage Access");
152         setData(name, I18N_NOOP("Accessible"), storageaccess->isAccessible());
153         setData(name, I18N_NOOP("File Path"), storageaccess->filePath());
154 
155         if (storageaccess->isAccessible()) {
156             updateStorageSpace(name);
157         }
158 
159         m_signalmanager->mapDevice(storageaccess, device.udi());
160     }
161 
162     if (device.is<Solid::StorageDrive>()) {
163         Solid::StorageDrive *storagedrive = device.as<Solid::StorageDrive>();
164         if (!storagedrive) {
165             return false;
166         }
167 
168         devicetypes << I18N_NOOP("Storage Drive");
169 
170         QStringList bus;
171         bus << I18N_NOOP("Ide") << I18N_NOOP("Usb") << I18N_NOOP("Ieee1394") << I18N_NOOP("Scsi") << I18N_NOOP("Sata") << I18N_NOOP("Platform");
172         QStringList drivetype;
173         drivetype << I18N_NOOP("Hard Disk") << I18N_NOOP("Cdrom Drive") << I18N_NOOP("Floppy") << I18N_NOOP("Tape") << I18N_NOOP("Compact Flash")
174                   << I18N_NOOP("Memory Stick") << I18N_NOOP("Smart Media") << I18N_NOOP("SdMmc") << I18N_NOOP("Xd");
175 
176         setData(name, I18N_NOOP("Bus"), bus.at((int)storagedrive->bus()));
177         setData(name, I18N_NOOP("Drive Type"), drivetype.at((int)storagedrive->driveType()));
178         setData(name, I18N_NOOP("Removable"), storagedrive->isRemovable());
179         setData(name, I18N_NOOP("Hotpluggable"), storagedrive->isHotpluggable());
180 
181         updateHardDiskTemperature(name);
182     } else {
183         bool isRemovable = false;
184         bool isHotpluggable = false;
185         Solid::StorageDrive *drive = getAncestorAs<Solid::StorageDrive>(device);
186         if (drive) {
187             // remove check for isHotpluggable() when plasmoids are changed to check for both properties
188             isRemovable = (drive->isRemovable() || drive->isHotpluggable());
189             isHotpluggable = drive->isHotpluggable();
190         }
191         setData(name, I18N_NOOP("Removable"), isRemovable);
192         setData(name, I18N_NOOP("Hotpluggable"), isHotpluggable);
193     }
194 
195     if (device.is<Solid::OpticalDrive>()) {
196         Solid::OpticalDrive *opticaldrive = device.as<Solid::OpticalDrive>();
197         if (!opticaldrive) {
198             return false;
199         }
200 
201         devicetypes << I18N_NOOP("Optical Drive");
202 
203         QStringList supportedtypes;
204         Solid::OpticalDrive::MediumTypes mediatypes = opticaldrive->supportedMedia();
205         if (mediatypes & Solid::OpticalDrive::Cdr) {
206             supportedtypes << I18N_NOOP("CD-R");
207         }
208         if (mediatypes & Solid::OpticalDrive::Cdrw) {
209             supportedtypes << I18N_NOOP("CD-RW");
210         }
211         if (mediatypes & Solid::OpticalDrive::Dvd) {
212             supportedtypes << I18N_NOOP("DVD");
213         }
214         if (mediatypes & Solid::OpticalDrive::Dvdr) {
215             supportedtypes << I18N_NOOP("DVD-R");
216         }
217         if (mediatypes & Solid::OpticalDrive::Dvdrw) {
218             supportedtypes << I18N_NOOP("DVD-RW");
219         }
220         if (mediatypes & Solid::OpticalDrive::Dvdram) {
221             supportedtypes << I18N_NOOP("DVD-RAM");
222         }
223         if (mediatypes & Solid::OpticalDrive::Dvdplusr) {
224             supportedtypes << I18N_NOOP("DVD+R");
225         }
226         if (mediatypes & Solid::OpticalDrive::Dvdplusrw) {
227             supportedtypes << I18N_NOOP("DVD+RW");
228         }
229         if (mediatypes & Solid::OpticalDrive::Dvdplusdl) {
230             supportedtypes << I18N_NOOP("DVD+DL");
231         }
232         if (mediatypes & Solid::OpticalDrive::Dvdplusdlrw) {
233             supportedtypes << I18N_NOOP("DVD+DLRW");
234         }
235         if (mediatypes & Solid::OpticalDrive::Bd) {
236             supportedtypes << I18N_NOOP("BD");
237         }
238         if (mediatypes & Solid::OpticalDrive::Bdr) {
239             supportedtypes << I18N_NOOP("BD-R");
240         }
241         if (mediatypes & Solid::OpticalDrive::Bdre) {
242             supportedtypes << I18N_NOOP("BD-RE");
243         }
244         if (mediatypes & Solid::OpticalDrive::HdDvd) {
245             supportedtypes << I18N_NOOP("HDDVD");
246         }
247         if (mediatypes & Solid::OpticalDrive::HdDvdr) {
248             supportedtypes << I18N_NOOP("HDDVD-R");
249         }
250         if (mediatypes & Solid::OpticalDrive::HdDvdrw) {
251             supportedtypes << I18N_NOOP("HDDVD-RW");
252         }
253         setData(name, I18N_NOOP("Supported Media"), supportedtypes);
254 
255         setData(name, I18N_NOOP("Read Speed"), opticaldrive->readSpeed());
256         setData(name, I18N_NOOP("Write Speed"), opticaldrive->writeSpeed());
257 
258         // the following method return QList<int> so we need to convert it to QList<QVariant>
259         const QList<int> writespeeds = opticaldrive->writeSpeeds();
260         QList<QVariant> variantlist;
261         foreach (int num, writespeeds) {
262             variantlist << num;
263         }
264         setData(name, I18N_NOOP("Write Speeds"), variantlist);
265     }
266     if (device.is<Solid::StorageVolume>()) {
267         Solid::StorageVolume *storagevolume = device.as<Solid::StorageVolume>();
268         if (!storagevolume) {
269             return false;
270         }
271 
272         devicetypes << I18N_NOOP("Storage Volume");
273 
274         QStringList usagetypes;
275         usagetypes << i18n("Other") << i18n("Unused") << i18n("File System") << i18n("Partition Table") << i18n("Raid") << i18n("Encrypted");
276 
277         if (usagetypes.count() > storagevolume->usage()) {
278             setData(name, I18N_NOOP("Usage"), usagetypes.at((int)storagevolume->usage()));
279         } else {
280             setData(name, I18N_NOOP("Usage"), i18n("Unknown"));
281         }
282 
283         setData(name, I18N_NOOP("Ignored"), storagevolume->isIgnored());
284         setData(name, I18N_NOOP("File System Type"), storagevolume->fsType());
285         setData(name, I18N_NOOP("Label"), storagevolume->label());
286         setData(name, I18N_NOOP("UUID"), storagevolume->uuid());
287         updateInUse(name);
288 
289         // Check if the volume is part of an encrypted container
290         // This needs to trigger an update for the encrypted container volume since
291         // libsolid cannot notify us when the accessibility of the container changes
292         Solid::Device encryptedContainer = storagevolume->encryptedContainer();
293         if (encryptedContainer.isValid()) {
294             const QString containerUdi = encryptedContainer.udi();
295             setData(name, I18N_NOOP("Encrypted Container"), containerUdi);
296             m_encryptedContainerMap[name] = containerUdi;
297             // TODO: compress the calls?
298             forceUpdateAccessibility(containerUdi);
299         }
300     }
301     if (device.is<Solid::OpticalDisc>()) {
302         Solid::OpticalDisc *opticaldisc = device.as<Solid::OpticalDisc>();
303         if (!opticaldisc) {
304             return false;
305         }
306 
307         devicetypes << I18N_NOOP("OpticalDisc");
308 
309         // get the content types
310         QStringList contenttypelist;
311         const Solid::OpticalDisc::ContentTypes contenttypes = opticaldisc->availableContent();
312         if (contenttypes.testFlag(Solid::OpticalDisc::Audio)) {
313             contenttypelist << I18N_NOOP("Audio");
314         }
315         if (contenttypes.testFlag(Solid::OpticalDisc::Data)) {
316             contenttypelist << I18N_NOOP("Data");
317         }
318         if (contenttypes.testFlag(Solid::OpticalDisc::VideoCd)) {
319             contenttypelist << I18N_NOOP("Video CD");
320         }
321         if (contenttypes.testFlag(Solid::OpticalDisc::SuperVideoCd)) {
322             contenttypelist << I18N_NOOP("Super Video CD");
323         }
324         if (contenttypes.testFlag(Solid::OpticalDisc::VideoDvd)) {
325             contenttypelist << I18N_NOOP("Video DVD");
326         }
327         if (contenttypes.testFlag(Solid::OpticalDisc::VideoBluRay)) {
328             contenttypelist << I18N_NOOP("Video Blu Ray");
329         }
330         setData(name, I18N_NOOP("Available Content"), contenttypelist);
331 
332         QStringList disctypes;
333         disctypes << I18N_NOOP("Unknown Disc Type") << I18N_NOOP("CD Rom") << I18N_NOOP("CD Recordable") << I18N_NOOP("CD Rewritable") << I18N_NOOP("DVD Rom")
334                   << I18N_NOOP("DVD Ram") << I18N_NOOP("DVD Recordable") << I18N_NOOP("DVD Rewritable") << I18N_NOOP("DVD Plus Recordable")
335                   << I18N_NOOP("DVD Plus Rewritable") << I18N_NOOP("DVD Plus Recordable Duallayer") << I18N_NOOP("DVD Plus Rewritable Duallayer")
336                   << I18N_NOOP("Blu Ray Rom") << I18N_NOOP("Blu Ray Recordable") << I18N_NOOP("Blu Ray Rewritable") << I18N_NOOP("HD DVD Rom")
337                   << I18N_NOOP("HD DVD Recordable") << I18N_NOOP("HD DVD Rewritable");
338         //+1 because the enum starts at -1
339         setData(name, I18N_NOOP("Disc Type"), disctypes.at((int)opticaldisc->discType() + 1));
340         setData(name, I18N_NOOP("Appendable"), opticaldisc->isAppendable());
341         setData(name, I18N_NOOP("Blank"), opticaldisc->isBlank());
342         setData(name, I18N_NOOP("Rewritable"), opticaldisc->isRewritable());
343         setData(name, I18N_NOOP("Capacity"), opticaldisc->capacity());
344     }
345     if (device.is<Solid::Camera>()) {
346         Solid::Camera *camera = device.as<Solid::Camera>();
347         if (!camera) {
348             return false;
349         }
350 
351         devicetypes << I18N_NOOP("Camera");
352 
353         setData(name, I18N_NOOP("Supported Protocols"), camera->supportedProtocols());
354         setData(name, I18N_NOOP("Supported Drivers"), camera->supportedDrivers());
355         // Cameras are necessarily Removable and Hotpluggable
356         setData(name, I18N_NOOP("Removable"), true);
357         setData(name, I18N_NOOP("Hotpluggable"), true);
358     }
359     if (device.is<Solid::PortableMediaPlayer>()) {
360         Solid::PortableMediaPlayer *mediaplayer = device.as<Solid::PortableMediaPlayer>();
361         if (!mediaplayer) {
362             return false;
363         }
364 
365         devicetypes << I18N_NOOP("Portable Media Player");
366 
367         setData(name, I18N_NOOP("Supported Protocols"), mediaplayer->supportedProtocols());
368         setData(name, I18N_NOOP("Supported Drivers"), mediaplayer->supportedDrivers());
369         // Portable Media Players are necessarily Removable and Hotpluggable
370         setData(name, I18N_NOOP("Removable"), true);
371         setData(name, I18N_NOOP("Hotpluggable"), true);
372     }
373     if (device.is<Solid::Battery>()) {
374         Solid::Battery *battery = device.as<Solid::Battery>();
375         if (!battery) {
376             return false;
377         }
378 
379         devicetypes << I18N_NOOP("Battery");
380 
381         QStringList batterytype;
382         batterytype << I18N_NOOP("Unknown Battery") << I18N_NOOP("PDA Battery") << I18N_NOOP("UPS Battery") << I18N_NOOP("Primary Battery")
383                     << I18N_NOOP("Mouse Battery") << I18N_NOOP("Keyboard Battery") << I18N_NOOP("Keyboard Mouse Battery") << I18N_NOOP("Camera Battery")
384                     << I18N_NOOP("Phone Battery") << I18N_NOOP("Monitor Battery") << I18N_NOOP("Gaming Input Battery") << I18N_NOOP("Bluetooth Battery");
385 
386         QStringList chargestate;
387         chargestate << I18N_NOOP("Not Charging") << I18N_NOOP("Charging") << I18N_NOOP("Discharging") << I18N_NOOP("Fully Charged");
388 
389         setData(name, I18N_NOOP("Plugged In"), battery->isPresent()); // FIXME Rename when interested parties are adjusted
390         setData(name, I18N_NOOP("Type"), batterytype.value((int)battery->type()));
391         setData(name, I18N_NOOP("Charge Percent"), battery->chargePercent());
392         setData(name, I18N_NOOP("Rechargeable"), battery->isRechargeable());
393         setData(name, I18N_NOOP("Charge State"), chargestate.at((int)battery->chargeState()));
394 
395         m_signalmanager->mapDevice(battery, device.udi());
396     }
397 
398     using namespace Solid;
399     // we cannot just iterate the enum in reverse order since Battery comes second to last
400     // and then our phone which also has a battery gets treated as battery :(
401     static const Solid::DeviceInterface::Type typeOrder[] = {
402         Solid::DeviceInterface::PortableMediaPlayer,
403         Solid::DeviceInterface::Camera,
404         Solid::DeviceInterface::OpticalDisc,
405         Solid::DeviceInterface::StorageVolume,
406         Solid::DeviceInterface::OpticalDrive,
407         Solid::DeviceInterface::StorageDrive,
408         Solid::DeviceInterface::NetworkShare,
409         Solid::DeviceInterface::StorageAccess,
410         Solid::DeviceInterface::Block,
411         Solid::DeviceInterface::Battery,
412         Solid::DeviceInterface::Processor,
413     };
414 
415     for (int i = 0; i < 11; ++i) {
416         const Solid::DeviceInterface *interface = device.asDeviceInterface(typeOrder[i]);
417         if (interface) {
418             setData(name, I18N_NOOP("Type Description"), Solid::DeviceInterface::typeDescription(typeOrder[i]));
419             break;
420         }
421     }
422 
423     setData(name, I18N_NOOP("Device Types"), devicetypes);
424     return true;
425 }
426 
deviceAdded(const QString & udi)427 void SolidDeviceEngine::deviceAdded(const QString &udi)
428 {
429     Solid::Device device(udi);
430 
431     foreach (const QString &query, m_predicatemap.keys()) {
432         Solid::Predicate predicate = Solid::Predicate::fromString(query);
433         if (predicate.matches(device)) {
434             m_predicatemap[query] << udi;
435             setData(query, m_predicatemap[query]);
436         }
437     }
438 
439     if (device.is<Solid::OpticalDisc>()) {
440         Solid::OpticalDrive *drive = getAncestorAs<Solid::OpticalDrive>(device);
441         if (drive) {
442             connect(drive, &Solid::OpticalDrive::ejectRequested, this, &SolidDeviceEngine::setUnmountingState);
443             connect(drive, &Solid::OpticalDrive::ejectDone, this, &SolidDeviceEngine::setIdleState);
444         }
445     } else if (device.is<Solid::StorageVolume>()) {
446         // update the volume in case of 2-stage devices
447         if (m_devicemap.contains(udi) && containerForSource(udi)->data().value(I18N_NOOP("Size")).toULongLong() == 0) {
448             Solid::GenericInterface *iface = device.as<Solid::GenericInterface>();
449             if (iface) {
450                 iface->setProperty("udi", udi);
451                 connect(iface, SIGNAL(propertyChanged(QMap<QString, int>)), this, SLOT(deviceChanged(QMap<QString, int>)));
452             }
453         }
454 
455         Solid::StorageAccess *access = device.as<Solid::StorageAccess>();
456         if (access) {
457             connect(access, &Solid::StorageAccess::setupRequested, this, &SolidDeviceEngine::setMountingState);
458             connect(access, &Solid::StorageAccess::setupDone, this, &SolidDeviceEngine::setIdleState);
459             connect(access, &Solid::StorageAccess::teardownRequested, this, &SolidDeviceEngine::setUnmountingState);
460             connect(access, &Solid::StorageAccess::teardownDone, this, &SolidDeviceEngine::setIdleState);
461         }
462     }
463 }
464 
setMountingState(const QString & udi)465 void SolidDeviceEngine::setMountingState(const QString &udi)
466 {
467     setData(udi, I18N_NOOP("State"), Mounting);
468     setData(udi, I18N_NOOP("Operation result"), Working);
469 }
470 
setUnmountingState(const QString & udi)471 void SolidDeviceEngine::setUnmountingState(const QString &udi)
472 {
473     setData(udi, I18N_NOOP("State"), Unmounting);
474     setData(udi, I18N_NOOP("Operation result"), Working);
475 }
476 
setIdleState(Solid::ErrorType error,QVariant errorData,const QString & udi)477 void SolidDeviceEngine::setIdleState(Solid::ErrorType error, QVariant errorData, const QString &udi)
478 {
479     Q_UNUSED(errorData)
480 
481     if (error == Solid::NoError) {
482         setData(udi, I18N_NOOP("Operation result"), Successful);
483     } else {
484         setData(udi, I18N_NOOP("Operation result"), Unsuccessful);
485     }
486     setData(udi, I18N_NOOP("State"), Idle);
487 
488     Solid::Device device = m_devicemap.value(udi);
489     if (!device.isValid()) {
490         return;
491     }
492 
493     Solid::StorageAccess *storageaccess = device.as<Solid::StorageAccess>();
494     if (!storageaccess) {
495         return;
496     }
497 
498     setData(udi, I18N_NOOP("Accessible"), storageaccess->isAccessible());
499     setData(udi, I18N_NOOP("File Path"), storageaccess->filePath());
500 }
501 
deviceChanged(const QMap<QString,int> & props)502 void SolidDeviceEngine::deviceChanged(const QMap<QString, int> &props)
503 {
504     Solid::GenericInterface *iface = qobject_cast<Solid::GenericInterface *>(sender());
505     if (iface && iface->isValid() && props.contains(QLatin1String("Size")) && iface->property(QStringLiteral("Size")).toInt() > 0) {
506         const QString udi = qobject_cast<QObject *>(iface)->property("udi").toString();
507         if (populateDeviceData(udi))
508             forceImmediateUpdateOfAllVisualizations();
509     }
510 }
511 
updateStorageSpace(const QString & udi)512 bool SolidDeviceEngine::updateStorageSpace(const QString &udi)
513 {
514     Solid::Device device = m_devicemap.value(udi);
515 
516     Solid::StorageAccess *storageaccess = device.as<Solid::StorageAccess>();
517     if (!storageaccess || !storageaccess->isAccessible()) {
518         return false;
519     }
520 
521     QString path = storageaccess->filePath();
522     if (!m_paths.contains(path)) {
523         QTimer *timer = new QTimer(this);
524         timer->setSingleShot(true);
525         connect(timer, &QTimer::timeout, [path]() {
526             KNotification::event(KNotification::Error, i18n("Filesystem is not responding"), i18n("Filesystem mounted at '%1' is not responding", path));
527         });
528 
529         m_paths.insert(path);
530 
531         // create job
532         KIO::FileSystemFreeSpaceJob *job = KIO::fileSystemFreeSpace(QUrl::fromLocalFile(path));
533 
534         // delete later timer
535         connect(job, &KIO::FileSystemFreeSpaceJob::result, timer, &QTimer::deleteLater);
536 
537         // collect and process info
538         connect(job, &KIO::FileSystemFreeSpaceJob::result, this, [this, timer, path, udi](KIO::Job *job, KIO::filesize_t size, KIO::filesize_t available) {
539             timer->stop();
540 
541             if (!job->error()) {
542                 setData(udi, I18N_NOOP("Free Space"), QVariant(available).toDouble());
543                 setData(udi, I18N_NOOP("Free Space Text"), KFormat().formatByteSize(available));
544                 setData(udi, I18N_NOOP("Size"), QVariant(size).toDouble());
545                 setData(udi, I18N_NOOP("Size Text"), KFormat().formatByteSize(size));
546             }
547 
548             m_paths.remove(path);
549         });
550 
551         // start timer: after 15 seconds we will get an error
552         timer->start(15000);
553     }
554 
555     return false;
556 }
557 
updateHardDiskTemperature(const QString & udi)558 bool SolidDeviceEngine::updateHardDiskTemperature(const QString &udi)
559 {
560     Solid::Device device = m_devicemap.value(udi);
561     Solid::Block *block = device.as<Solid::Block>();
562     if (!block) {
563         return false;
564     }
565 
566     if (!m_temperature) {
567         m_temperature = new HddTemp(this);
568     }
569 
570     if (m_temperature->sources().contains(block->device())) {
571         setData(udi, I18N_NOOP("Temperature"), m_temperature->data(block->device(), HddTemp::Temperature));
572         setData(udi, I18N_NOOP("Temperature Unit"), m_temperature->data(block->device(), HddTemp::Unit));
573         return true;
574     }
575 
576     return false;
577 }
578 
updateEmblems(const QString & udi)579 bool SolidDeviceEngine::updateEmblems(const QString &udi)
580 {
581     Solid::Device device = m_devicemap.value(udi);
582 
583     setData(udi, I18N_NOOP("Emblems"), device.emblems());
584     return true;
585 }
586 
forceUpdateAccessibility(const QString & udi)587 bool SolidDeviceEngine::forceUpdateAccessibility(const QString &udi)
588 {
589     Solid::Device device = m_devicemap.value(udi);
590     if (!device.isValid()) {
591         return false;
592     }
593 
594     updateEmblems(udi);
595     Solid::StorageAccess *storageaccess = device.as<Solid::StorageAccess>();
596     if (storageaccess) {
597         setData(udi, I18N_NOOP("Accessible"), storageaccess->isAccessible());
598     }
599 
600     return true;
601 }
602 
updateInUse(const QString & udi)603 bool SolidDeviceEngine::updateInUse(const QString &udi)
604 {
605     Solid::Device device = m_devicemap.value(udi);
606     if (!device.isValid()) {
607         return false;
608     }
609 
610     Solid::StorageAccess *storageaccess = device.as<Solid::StorageAccess>();
611     if (!storageaccess) {
612         return false;
613     }
614 
615     if (storageaccess->isAccessible()) {
616         setData(udi, I18N_NOOP("In Use"), true);
617     } else {
618         Solid::StorageDrive *drive = getAncestorAs<Solid::StorageDrive>(Solid::Device(udi));
619         if (drive) {
620             setData(udi, I18N_NOOP("In Use"), drive->isInUse());
621         }
622     }
623 
624     return true;
625 }
626 
updateSourceEvent(const QString & source)627 bool SolidDeviceEngine::updateSourceEvent(const QString &source)
628 {
629     bool update1 = updateStorageSpace(source);
630     bool update2 = updateHardDiskTemperature(source);
631     bool update3 = updateEmblems(source);
632     bool update4 = updateInUse(source);
633 
634     return (update1 || update2 || update3 || update4);
635 }
636 
deviceRemoved(const QString & udi)637 void SolidDeviceEngine::deviceRemoved(const QString &udi)
638 {
639     // libsolid cannot notify us when an encrypted container is closed,
640     // hence we trigger an update when a device contained in an encrypted container device dies
641     const QString containerUdi = m_encryptedContainerMap.value(udi, QString());
642 
643     if (!containerUdi.isEmpty()) {
644         forceUpdateAccessibility(containerUdi);
645         m_encryptedContainerMap.remove(udi);
646     }
647 
648     foreach (const QString &query, m_predicatemap.keys()) {
649         m_predicatemap[query].removeAll(udi);
650         setData(query, m_predicatemap[query]);
651     }
652 
653     Solid::Device device(udi);
654     if (device.is<Solid::StorageVolume>()) {
655         Solid::StorageAccess *access = device.as<Solid::StorageAccess>();
656         if (access) {
657             disconnect(access, nullptr, this, nullptr);
658         }
659     } else if (device.is<Solid::OpticalDisc>()) {
660         Solid::OpticalDrive *drive = getAncestorAs<Solid::OpticalDrive>(device);
661         if (drive) {
662             disconnect(drive, nullptr, this, nullptr);
663         }
664     }
665 
666     m_devicemap.remove(udi);
667     removeSource(udi);
668 }
669 
deviceChanged(const QString & udi,const QString & property,const QVariant & value)670 void SolidDeviceEngine::deviceChanged(const QString &udi, const QString &property, const QVariant &value)
671 {
672     setData(udi, property, value);
673     updateSourceEvent(udi);
674 }
675 
676 K_PLUGIN_CLASS_WITH_JSON(SolidDeviceEngine, "plasma-dataengine-soliddevice.json")
677 
678 #include "soliddeviceengine.moc"
679