File indexing completed on 2024-04-28 05:34:57

0001 /*
0002     SPDX-FileCopyrightText: 2006 Lukas Tinkl <ltinkl@suse.cz>
0003     SPDX-FileCopyrightText: 2008 Lubos Lunak <l.lunak@suse.cz>
0004     SPDX-FileCopyrightText: 2009 Ivo Anjo <knuckles@gmail.com>
0005     SPDX-FileCopyrightText: 2020 Kai Uwe Broulik <kde@broulik.de>
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 
0010 #include "freespacenotifier.h"
0011 
0012 #include <KNotification>
0013 #include <KNotificationJobUiDelegate>
0014 
0015 #include <KIO/ApplicationLauncherJob>
0016 #include <KIO/FileSystemFreeSpaceJob>
0017 #include <KIO/OpenUrlJob>
0018 
0019 #include <chrono>
0020 
0021 #include "settings.h"
0022 
0023 FreeSpaceNotifier::FreeSpaceNotifier(const QString &path, const KLocalizedString &notificationText, QObject *parent)
0024     : QObject(parent)
0025     , m_path(path)
0026     , m_notificationText(notificationText)
0027 {
0028     connect(&m_timer, &QTimer::timeout, this, &FreeSpaceNotifier::checkFreeDiskSpace);
0029     m_timer.start(std::chrono::minutes(1));
0030 }
0031 
0032 FreeSpaceNotifier::~FreeSpaceNotifier()
0033 {
0034     if (m_notification) {
0035         m_notification->close();
0036     }
0037 }
0038 
0039 void FreeSpaceNotifier::checkFreeDiskSpace()
0040 {
0041     if (!FreeSpaceNotifierSettings::enableNotification()) {
0042         // do nothing if notifying is disabled;
0043         // also stop the timer that probably got us here in the first place
0044         m_timer.stop();
0045         return;
0046     }
0047 
0048     auto *job = KIO::fileSystemFreeSpace(QUrl::fromLocalFile(m_path));
0049     connect(job, &KJob::result, this, [this, job]() {
0050         if (job->error()) {
0051             return;
0052         }
0053 
0054         const int limit = FreeSpaceNotifierSettings::minimumSpace(); // MiB
0055         const qint64 avail = job->availableSize() / (1024 * 1024); // to MiB
0056 
0057         if (avail >= limit) {
0058             if (m_notification) {
0059                 m_notification->close();
0060             }
0061             return;
0062         }
0063 
0064         const int availPercent = int(100 * job->availableSize() / job->size());
0065         const QString text = m_notificationText.subs(avail).subs(availPercent).toString();
0066 
0067         // Make sure the notification text is always up to date whenever we checked free space
0068         if (m_notification) {
0069             m_notification->setText(text);
0070         }
0071 
0072         // User freed some space, warn if it goes low again
0073         if (m_lastAvail > -1 && avail > m_lastAvail) {
0074             m_lastAvail = avail;
0075             return;
0076         }
0077 
0078         // Always warn the first time or when available space dropped to half of the previous time
0079         const bool warn = (m_lastAvail < 0 || avail < m_lastAvail / 2);
0080         if (!warn) {
0081             return;
0082         }
0083 
0084         m_lastAvail = avail;
0085 
0086         if (!m_notification) {
0087             m_notification = new KNotification(QStringLiteral("freespacenotif"));
0088             m_notification->setComponentName(QStringLiteral("freespacenotifier"));
0089             m_notification->setText(text);
0090 
0091             auto filelight = filelightService();
0092             if (filelight) {
0093                 auto filelightAction = m_notification->addAction(i18n("Open in Filelight"));
0094                 connect(filelightAction, &KNotificationAction::activated, this, [this] {
0095                     exploreDrive();
0096                 });
0097             } else {
0098                 // Do we really want the user opening Root in a file manager?
0099                 auto fileManagerAction = m_notification->addAction(i18n("Open in File Manager"));
0100                 connect(fileManagerAction, &KNotificationAction::activated, this, [this] {
0101                     exploreDrive();
0102                 });
0103             }
0104 
0105             // TODO once we have "configure" action support in KNotification, wire it up instead of a button
0106             auto configureAction = m_notification->addAction(i18n("Configure Warning…"));
0107             connect(configureAction, &KNotificationAction::activated, this, [this] {
0108                 Q_EMIT configureRequested();
0109             });
0110 
0111             connect(m_notification, &KNotification::closed, this, &FreeSpaceNotifier::onNotificationClosed);
0112             m_notification->sendEvent();
0113         }
0114     });
0115 }
0116 
0117 KService::Ptr FreeSpaceNotifier::filelightService() const
0118 {
0119     return KService::serviceByDesktopName(QStringLiteral("org.kde.filelight"));
0120 }
0121 
0122 void FreeSpaceNotifier::exploreDrive()
0123 {
0124     auto service = filelightService();
0125     if (!service) {
0126         auto *job = new KIO::OpenUrlJob({QUrl::fromLocalFile(m_path)});
0127         job->setUiDelegate(new KNotificationJobUiDelegate(KJobUiDelegate::AutoErrorHandlingEnabled));
0128         job->start();
0129         return;
0130     }
0131 
0132     auto *job = new KIO::ApplicationLauncherJob(service);
0133     job->setUrls({QUrl::fromLocalFile(m_path)});
0134     job->setUiDelegate(new KNotificationJobUiDelegate(KJobUiDelegate::AutoErrorHandlingEnabled));
0135     job->start();
0136 }
0137 
0138 void FreeSpaceNotifier::onNotificationClosed()
0139 {
0140     // warn again if constantly below limit for too long
0141     if (!m_lastAvailTimer) {
0142         m_lastAvailTimer = new QTimer(this);
0143         connect(m_lastAvailTimer, &QTimer::timeout, this, &FreeSpaceNotifier::resetLastAvailable);
0144     }
0145     m_lastAvailTimer->start(std::chrono::hours(1));
0146 }
0147 
0148 void FreeSpaceNotifier::resetLastAvailable()
0149 {
0150     m_lastAvail = -1;
0151     m_lastAvailTimer->deleteLater();
0152     m_lastAvailTimer = nullptr;
0153 }