File indexing completed on 2024-05-05 05:48:34

0001 // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0002 // SPDX-FileCopyrightText: 2020-2022 Harald Sitter <sitter@kde.org>
0003 
0004 #include <sys/types.h>
0005 #include <unistd.h>
0006 
0007 #include <chrono>
0008 
0009 #include <QPointer>
0010 #include <QTimer>
0011 
0012 #include <KAuth/Action>
0013 #include <KAuth/ExecuteJob>
0014 #include <KDEDModule>
0015 #include <KLocalizedString>
0016 #include <KNotification>
0017 #include <KPluginFactory>
0018 
0019 #include <entries.h>
0020 
0021 using namespace std::chrono_literals;
0022 
0023 static constexpr auto warningPercent = 90;
0024 
0025 class Notifier : public QObject
0026 {
0027     Q_OBJECT
0028 public:
0029     struct Context {
0030         const qulonglong percent;
0031         const QString eventId;
0032         const QString title;
0033         const QString text;
0034         bool actionable;
0035         const QString actionLabel;
0036         const QString authAction;
0037     };
0038 
0039     using QObject::QObject;
0040 
0041     void process(const Context &context)
0042     {
0043         if (context.percent < warningPercent) {
0044             m_notified = false;
0045             m_notification = nullptr;
0046         } else if (!m_notified) {
0047             m_notified = true;
0048             m_notification = new KNotification(context.eventId);
0049             m_notification->setComponentName(QStringLiteral("org.kde.kded.inotify"));
0050             m_notification->setTitle(context.title);
0051             m_notification->setText(context.text);
0052 
0053             if (context.actionable) {
0054                 m_notification->setFlags(KNotification::Persistent);
0055                 const QString authAction = context.authAction; // so we don't need to keep the entire context
0056                 auto action = m_notification->addAction(context.actionLabel);
0057                 connect(action, &KNotificationAction::activated, this, [this, authAction]() {
0058                     m_notification->disconnect(this);
0059                     m_notification->deleteLater();
0060 
0061                     KAuth::Action action(authAction);
0062                     action.setHelperId(QStringLiteral("org.kde.kded.inotify"));
0063 
0064                     KAuth::ExecuteJob *job = action.execute();
0065                     connect(job, &KJob::result, this, [this, job] {
0066                         job->deleteLater();
0067                         Q_EMIT actioned();
0068                     });
0069                     job->start();
0070                 });
0071             }
0072             m_notification->sendEvent();
0073         }
0074     }
0075 
0076 Q_SIGNALS:
0077     void actioned();
0078 
0079 private:
0080     bool m_notified = false;
0081     QPointer<KNotification> m_notification; // Notifications auto-delete use a QPointer to track them properly.
0082 };
0083 
0084 class InotifyModule : public KDEDModule
0085 {
0086     Q_OBJECT
0087     Q_CLASSINFO("D-Bus Interface", "org.kde.inotify")
0088 public:
0089     explicit InotifyModule(QObject *parent, const QVariantList &args)
0090         : KDEDModule(parent)
0091     {
0092         Q_UNUSED(args);
0093 
0094         connect(&m_instanceNotifier, &Notifier::actioned, this, &InotifyModule::refresh);
0095         connect(&m_watchNotifier, &Notifier::actioned, this, &InotifyModule::refresh);
0096 
0097         m_timer.setInterval(10min);
0098         connect(&m_timer, &QTimer::timeout, this, &InotifyModule::refresh);
0099         m_timer.start();
0100     }
0101 
0102 public Q_SLOTS:
0103     Q_SCRIPTABLE void refresh()
0104     {
0105         const auto entries = collectEntries();
0106 
0107         quint64 allInstances = 0;
0108         quint64 allWatches = 0;
0109         for (const auto &entry : entries) {
0110             if (entry.uid != getuid()) {
0111                 continue;
0112             }
0113             allInstances += entry.instances;
0114             allWatches += entry.watches;
0115         }
0116 
0117         const auto capacity = inotifyCapacity();
0118         const auto instancePercent = 100 * allInstances / capacity.max_user_instances;
0119         const auto watchPercent = 100 * allWatches / capacity.max_user_watches;
0120         const auto maxCapacity = maximumINotifyCapacity();
0121 
0122         m_instanceNotifier.process(Notifier::Context{
0123             .percent = instancePercent,
0124             .eventId = QStringLiteral("inotifyinstancelow"),
0125             .title = i18nc("@title", "Inotify Instance Capacity Low"),
0126             .text = capacity.max_user_instances < maxCapacity.max_user_instances
0127                 ? i18nc("@info",
0128                         "You have too many applications wanting to monitor file changes! When the capacity is "
0129                         "exhausted it will prevent further file monitoring from working correctly. Either close "
0130                         "some applications or increase the limit. "
0131                         "Currently using %1% of instances and %2% of watches.",
0132                         QString::number(instancePercent),
0133                         QString::number(watchPercent))
0134                 : i18nc("@info",
0135                         "You have too many applications wanting to monitor file changes! When the capacity is "
0136                         "exhausted it will prevent further file monitoring from working correctly. Your capacity cannot "
0137                         "be increased. Try closing some applications to recover additional resources. "
0138                         "Currently using %1% of instances and %2% of watches.",
0139                         QString::number(instancePercent),
0140                         QString::number(watchPercent)),
0141             .actionable = capacity.max_user_instances < maxCapacity.max_user_instances,
0142             .actionLabel = i18nc("@action", "Increase Instance Limit"),
0143             .authAction = QStringLiteral("org.kde.kded.inotify.increaseinstancelimit"),
0144         });
0145 
0146         m_watchNotifier.process(Notifier::Context{
0147             .percent = watchPercent,
0148             .eventId = QStringLiteral("inotifywatchlow"),
0149             .title = i18nc("@title", "Inotify Watch Capacity Low"),
0150             .text = capacity.max_user_watches < maxCapacity.max_user_watches
0151                 ? i18nc("@info",
0152                         "Your open applications want to watch too many files for changes! When the capacity is "
0153                         "exhausted it will prevent further file monitoring from working correctly. Either close "
0154                         "some applications or increase the limit. "
0155                         "Currently using %1% of instances and %2% of watches.",
0156                         QString::number(instancePercent),
0157                         QString::number(watchPercent))
0158                 : i18nc("@info",
0159                         "Your open applications want to watch too many files for changes! When the capacity is "
0160                         "exhausted it will prevent further file monitoring from working correctly. Your capacity cannot "
0161                         "be increased. Try closing some applications to recover additional resources. "
0162                         "Currently using %1% of instances and %2% of watches.",
0163                         QString::number(instancePercent),
0164                         QString::number(watchPercent)),
0165             .actionable = capacity.max_user_watches < maxCapacity.max_user_watches,
0166             .actionLabel = i18nc("@action", "Increase Watch Limit"),
0167             .authAction = QStringLiteral("org.kde.kded.inotify.increasewatchlimit"),
0168         });
0169     }
0170 
0171 private:
0172     QTimer m_timer;
0173     Notifier m_instanceNotifier;
0174     Notifier m_watchNotifier;
0175 };
0176 
0177 K_PLUGIN_CLASS_WITH_JSON(InotifyModule, "inotify.json")
0178 
0179 #include "kded.moc"