File indexing completed on 2024-04-21 16:19:53

0001 // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0002 // SPDX-FileCopyrightText: 2020-2021 Harald Sitter <sitter@kde.org>
0003 
0004 #include "smartmonitor.h"
0005 
0006 #include <chrono>
0007 
0008 #include <KLocalizedString>
0009 
0010 #include "device.h"
0011 #include "devicenotifier.h"
0012 #include "instabilities.h"
0013 #include "kded_debug.h"
0014 #include "smartctl.h"
0015 #include "smartdata.h"
0016 
0017 using namespace std::chrono_literals;
0018 
0019 SMARTMonitor::SMARTMonitor(std::unique_ptr<AbstractSMARTCtl> ctl, std::unique_ptr<DeviceNotifier> deviceNotifier, QObject *parent)
0020     : QObject(parent)
0021     , m_ctl(std::move(ctl))
0022     , m_deviceNotifier(std::move(deviceNotifier))
0023 {
0024     connect(&m_reloadTimer, &QTimer::timeout, this, &SMARTMonitor::reloadData);
0025     connect(m_ctl.get(), &AbstractSMARTCtl::finished, this, &SMARTMonitor::onSMARTCtlFinished);
0026     m_reloadTimer.setInterval(24h);
0027 }
0028 
0029 void SMARTMonitor::start()
0030 {
0031     qCDebug(KDED) << "starting";
0032     connect(m_deviceNotifier.get(), &DeviceNotifier::addDevice, this, &SMARTMonitor::addDevice);
0033     connect(m_deviceNotifier.get(), &DeviceNotifier::removeUDI, this, &SMARTMonitor::removeUDI);
0034     QMetaObject::invokeMethod(m_deviceNotifier.get(), &DeviceNotifier::start, Qt::QueuedConnection); // async to ensure listeners are ready
0035     m_reloadTimer.start();
0036 }
0037 
0038 QVector<Device *> SMARTMonitor::devices() const
0039 {
0040     return m_devices;
0041 }
0042 
0043 void SMARTMonitor::removeUDI(const QString &udi)
0044 {
0045     auto newEnd = std::remove_if(m_devices.begin(), m_devices.end(), [this, udi](Device *dev) {
0046         if (dev->udi() != udi) {
0047             return false;
0048         }
0049 
0050         Q_EMIT deviceRemoved(dev);
0051         dev->deleteLater();
0052         return true;
0053     });
0054     m_devices.erase(newEnd, m_devices.end());
0055 }
0056 
0057 void SMARTMonitor::reloadData()
0058 {
0059     m_deviceNotifier->loadData();
0060     m_reloadTimer.start();
0061 }
0062 
0063 void SMARTMonitor::onSMARTCtlFinished(const QString &devicePath, const QJsonDocument &document, const QString &textDocument)
0064 {
0065     auto pendingIt = m_pendingDevices.find(devicePath);
0066     if (pendingIt == m_pendingDevices.end()) {
0067         qCDebug(KDED) << "unexpected pending result for" << devicePath;
0068         return;
0069     }
0070     Device *device = *pendingIt;
0071     m_pendingDevices.erase(pendingIt);
0072 
0073     if (document.isEmpty()) { // failed to get data, ignore the device
0074         qCDebug(KDED) << "Received no data for" << devicePath;
0075         device->deleteLater();
0076         return;
0077     }
0078 
0079     SMARTData data(document);
0080     if (!data.m_valid) {
0081         qCDebug(KDED) << "Invalid SMART data; skipping" << devicePath;
0082         device->deleteLater();
0083         return;
0084     }
0085 
0086     if (!devicePath.endsWith(QStringLiteral(".json"))) { // simulation data
0087         Q_ASSERT(devicePath == data.m_device);
0088     }
0089 
0090     auto existingIt = std::find_if(m_devices.begin(), m_devices.end(), [&device](Device *existing) {
0091         return *existing == *device;
0092     });
0093     if (existingIt != m_devices.cend()) {
0094         device->deleteLater(); // won't be needing this
0095 
0096         Device *existing = *existingIt;
0097         // update failure and call it a day. Notification is handled by the Device.
0098         existing->setInstabilities(Instabilities::from(data));
0099         existing->setFailed(!data.m_status.m_passed);
0100         existing->setAdvancedReport(textDocument);
0101 
0102         return;
0103     }
0104     device->setInstabilities(Instabilities::from(data));
0105     device->setFailed(!data.m_status.m_passed);
0106     device->setAdvancedReport(textDocument);
0107 
0108     m_devices << device;
0109     Q_EMIT deviceAdded(device);
0110 }
0111 
0112 void SMARTMonitor::addDevice(Device *device)
0113 {
0114     m_pendingDevices[device->path()] = device;
0115     m_ctl->run(device->path());
0116 }