Warning, file /plasma/discover/notifier/DiscoverNotifier.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
0001 /* 0002 * SPDX-FileCopyrightText: 2014 Aleix Pol Gonzalez <aleixpol@blue-systems.com> 0003 * 0004 * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0005 */ 0006 0007 #include "DiscoverNotifier.h" 0008 #include "BackendNotifierFactory.h" 0009 #include "UnattendedUpdates.h" 0010 #include <KLocalizedString> 0011 #include <KNotificationJobUiDelegate> 0012 #include <KPluginFactory> 0013 #include <QDBusConnection> 0014 #include <QDBusMessage> 0015 #include <QDBusPendingCall> 0016 #include <QDebug> 0017 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0018 #include <QNetworkConfigurationManager> 0019 #else 0020 #include <QNetworkInformation> 0021 #endif 0022 #include <QProcess> 0023 0024 #include <KIO/ApplicationLauncherJob> 0025 #include <KIO/CommandLauncherJob> 0026 0027 #include "../libdiscover/utils.h" 0028 #include "updatessettings.h" 0029 #include <chrono> 0030 0031 using namespace std::chrono_literals; 0032 0033 DiscoverNotifier::DiscoverNotifier(QObject *parent) 0034 : QObject(parent) 0035 { 0036 m_settings = new UpdatesSettings(this); 0037 m_settingsWatcher = KConfigWatcher::create(m_settings->sharedConfig()); 0038 #if QT_VERSION >= QT_VERSION_CHECK(6, 3, 0) 0039 QNetworkInformation::instance()->load(QNetworkInformation::Feature::Reachability | QNetworkInformation::Feature::TransportMedium); 0040 connect(QNetworkInformation::instance(), &QNetworkInformation::reachabilityChanged, this, &DiscoverNotifier::stateChanged); 0041 connect(QNetworkInformation::instance(), &QNetworkInformation::transportMediumChanged, this, &DiscoverNotifier::stateChanged); 0042 #endif 0043 0044 refreshUnattended(); 0045 connect(m_settingsWatcher.data(), &KConfigWatcher::configChanged, this, [this](const KConfigGroup &group, const QByteArrayList &names) { 0046 if (group.config()->name() == m_settings->config()->name() && group.name() == "Global" && names.contains("UseUnattendedUpdates")) { 0047 refreshUnattended(); 0048 } 0049 }); 0050 0051 m_backends = BackendNotifierFactory().allBackends(); 0052 for (BackendNotifierModule *module : qAsConst(m_backends)) { 0053 connect(module, &BackendNotifierModule::foundUpdates, this, &DiscoverNotifier::updateStatusNotifier); 0054 connect(module, &BackendNotifierModule::needsRebootChanged, this, [this]() { 0055 // If we are using offline updates, there is no need to badger the user to 0056 // reboot since it is safe to continue using the system in its current state 0057 if (!m_needsReboot && !m_settings->useUnattendedUpdates()) { 0058 m_needsReboot = true; 0059 showRebootNotification(); 0060 Q_EMIT stateChanged(); 0061 Q_EMIT needsRebootChanged(true); 0062 } 0063 }); 0064 0065 connect(module, &BackendNotifierModule::foundUpgradeAction, this, &DiscoverNotifier::foundUpgradeAction); 0066 } 0067 connect(&m_timer, &QTimer::timeout, this, &DiscoverNotifier::showUpdatesNotification); 0068 m_timer.setSingleShot(true); 0069 m_timer.setInterval(1s); 0070 updateStatusNotifier(); 0071 0072 // Only fetch updates after the system is comfortably booted 0073 QTimer::singleShot(0s, this, &DiscoverNotifier::recheckSystemUpdateNeeded); 0074 } 0075 0076 DiscoverNotifier::~DiscoverNotifier() = default; 0077 0078 void DiscoverNotifier::showDiscover(const QString &xdgActivationToken) 0079 { 0080 auto *job = new KIO::ApplicationLauncherJob(KService::serviceByDesktopName(QStringLiteral("org.kde.discover"))); 0081 job->setStartupId(xdgActivationToken.toUtf8()); 0082 job->setUiDelegate(new KNotificationJobUiDelegate(KJobUiDelegate::AutoErrorHandlingEnabled)); 0083 job->start(); 0084 0085 if (m_updatesAvailableNotification) { 0086 m_updatesAvailableNotification->close(); 0087 } 0088 } 0089 0090 void DiscoverNotifier::showDiscoverUpdates(const QString &xdgActivationToken) 0091 { 0092 auto *job = new KIO::CommandLauncherJob(QStringLiteral("plasma-discover"), {QStringLiteral("--mode"), QStringLiteral("update")}); 0093 job->setUiDelegate(new KNotificationJobUiDelegate(KJobUiDelegate::AutoErrorHandlingEnabled)); 0094 job->setDesktopName(QStringLiteral("org.kde.discover")); 0095 job->setStartupId(xdgActivationToken.toUtf8()); 0096 job->start(); 0097 0098 if (m_updatesAvailableNotification) { 0099 m_updatesAvailableNotification->close(); 0100 } 0101 } 0102 0103 bool DiscoverNotifier::notifyAboutUpdates() const 0104 { 0105 if (state() != NormalUpdates && state() != SecurityUpdates) { 0106 // it's not very helpful to notify that everything is in order 0107 return false; 0108 } 0109 0110 if (m_settings->requiredNotificationInterval() < 0) { 0111 return false; 0112 } 0113 0114 // To configure to a random value, execute: 0115 // kwriteconfig5 --file PlasmaDiscoverUpdates --group Global --key RequiredNotificationInterval 3600 0116 const QDateTime earliestNextNotificationTime = m_settings->lastNotificationTime().addSecs(m_settings->requiredNotificationInterval()); 0117 if (earliestNextNotificationTime.isValid() && earliestNextNotificationTime > QDateTime::currentDateTimeUtc()) { 0118 return false; 0119 } 0120 0121 m_settings->setLastNotificationTime(QDateTime::currentDateTimeUtc()); 0122 m_settings->save(); 0123 0124 auto method = QDBusMessage::createMethodCall(QStringLiteral("org.kde.discover"), 0125 QStringLiteral("/"), 0126 QStringLiteral("org.freedesktop.DBus.Peer"), 0127 QStringLiteral("Ping")); 0128 auto call = QDBusConnection::sessionBus().asyncCall(method); 0129 call.waitForFinished(); 0130 if (call.isValid()) { 0131 return false; 0132 } 0133 return true; 0134 } 0135 0136 void DiscoverNotifier::showUpdatesNotification() 0137 { 0138 if (m_updatesAvailableNotification) { 0139 m_updatesAvailableNotification->close(); 0140 } 0141 0142 if (!notifyAboutUpdates()) { 0143 return; 0144 } 0145 0146 m_updatesAvailableNotification = KNotification::event(QStringLiteral("Update"), 0147 message(), 0148 {}, 0149 iconName(), 0150 nullptr, 0151 KNotification::CloseOnTimeout, 0152 QStringLiteral("discoverabstractnotifier")); 0153 m_updatesAvailableNotification->setHint(QStringLiteral("resident"), true); 0154 const QString name = i18n("View Updates"); 0155 m_updatesAvailableNotification->setDefaultAction(name); 0156 m_updatesAvailableNotification->setActions({name}); 0157 connect(m_updatesAvailableNotification, QOverload<unsigned int>::of(&KNotification::activated), this, [this] { 0158 showDiscoverUpdates(m_updatesAvailableNotification->xdgActivationToken()); 0159 }); 0160 } 0161 0162 void DiscoverNotifier::updateStatusNotifier() 0163 { 0164 const bool hasSecurityUpdates = kContains(m_backends, [](BackendNotifierModule *module) { 0165 return module->hasSecurityUpdates(); 0166 }); 0167 const bool hasUpdates = hasSecurityUpdates || kContains(m_backends, [](BackendNotifierModule *module) { 0168 return module->hasUpdates(); 0169 }); 0170 0171 if (m_hasUpdates == hasUpdates && m_hasSecurityUpdates == hasSecurityUpdates) 0172 return; 0173 0174 m_hasSecurityUpdates = hasSecurityUpdates; 0175 m_hasUpdates = hasUpdates; 0176 0177 if (state() != NoUpdates) { 0178 m_timer.start(); 0179 } 0180 0181 Q_EMIT stateChanged(); 0182 } 0183 0184 // we only want to do unattended updates when on an ethernet or wlan network 0185 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0186 static bool isConnectionAdequate(const QNetworkConfiguration &network) 0187 { 0188 return (network.bearerType() == QNetworkConfiguration::BearerEthernet || network.bearerType() == QNetworkConfiguration::BearerWLAN); 0189 } 0190 #elif QT_VERSION >= QT_VERSION_CHECK(6, 3, 0) 0191 static bool isConnectionAdequate() 0192 { 0193 const auto info = QNetworkInformation::instance(); 0194 if (info->supports(QNetworkInformation::Feature::Metered)) { 0195 return !info->isMetered(); 0196 } else { 0197 const auto transport = info->transportMedium(); 0198 return transport == QNetworkInformation::TransportMedium::Ethernet || transport == QNetworkInformation::TransportMedium::WiFi; 0199 } 0200 } 0201 #endif 0202 0203 void DiscoverNotifier::refreshUnattended() 0204 { 0205 m_settings->read(); 0206 0207 if (!notifyAboutUpdates()) { 0208 return; 0209 } 0210 0211 const auto enabled = m_settings->useUnattendedUpdates() 0212 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0213 && m_manager->isOnline() && isConnectionAdequate(m_manager->defaultConfiguration()); 0214 #elif QT_VERSION >= QT_VERSION_CHECK(6, 3, 0) 0215 && QNetworkInformation::instance()->reachability() == QNetworkInformation::Reachability::Online && isConnectionAdequate(); 0216 #endif 0217 if (bool(m_unattended) == enabled) 0218 return; 0219 0220 if (enabled) { 0221 m_unattended = new UnattendedUpdates(this); 0222 } else { 0223 delete m_unattended; 0224 m_unattended = nullptr; 0225 } 0226 } 0227 0228 DiscoverNotifier::State DiscoverNotifier::state() const 0229 { 0230 if (m_needsReboot) 0231 return RebootRequired; 0232 else if (m_isBusy) 0233 return Busy; 0234 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0235 else if (m_manager && !m_manager->isOnline()) 0236 #else 0237 else if (QNetworkInformation::instance()->reachability() != QNetworkInformation::Reachability::Online) 0238 #endif 0239 return Offline; 0240 else if (m_hasSecurityUpdates) 0241 return SecurityUpdates; 0242 else if (m_hasUpdates) 0243 return NormalUpdates; 0244 else 0245 return NoUpdates; 0246 } 0247 0248 QString DiscoverNotifier::iconName() const 0249 { 0250 switch (state()) { 0251 case SecurityUpdates: 0252 return QStringLiteral("update-high"); 0253 case NormalUpdates: 0254 return QStringLiteral("update-low"); 0255 case NoUpdates: 0256 return QStringLiteral("update-none"); 0257 case RebootRequired: 0258 return QStringLiteral("system-reboot"); 0259 case Offline: 0260 return QStringLiteral("offline"); 0261 case Busy: 0262 return QStringLiteral("state-download"); 0263 } 0264 return QString(); 0265 } 0266 0267 QString DiscoverNotifier::message() const 0268 { 0269 switch (state()) { 0270 case SecurityUpdates: 0271 return i18n("Security updates available"); 0272 case NormalUpdates: 0273 return i18n("Updates available"); 0274 case NoUpdates: 0275 return i18n("System up to date"); 0276 case RebootRequired: 0277 return i18n("Computer needs to restart"); 0278 case Offline: 0279 return i18n("Offline"); 0280 case Busy: 0281 return i18n("Applying unattended updates…"); 0282 } 0283 return QString(); 0284 } 0285 0286 void DiscoverNotifier::recheckSystemUpdateNeeded() 0287 { 0288 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0289 if (!m_manager) { 0290 m_manager = new QNetworkConfigurationManager(this); 0291 connect(m_manager, &QNetworkConfigurationManager::onlineStateChanged, this, &DiscoverNotifier::stateChanged); 0292 if (!m_manager->isOnline()) { 0293 Q_EMIT stateChanged(); 0294 } 0295 } 0296 #endif 0297 0298 for (BackendNotifierModule *module : qAsConst(m_backends)) 0299 module->recheckSystemUpdateNeeded(); 0300 0301 refreshUnattended(); 0302 } 0303 0304 QStringList DiscoverNotifier::loadedModules() const 0305 { 0306 QStringList ret; 0307 for (BackendNotifierModule *module : m_backends) 0308 ret += QString::fromLatin1(module->metaObject()->className()); 0309 return ret; 0310 } 0311 0312 void DiscoverNotifier::showRebootNotification() 0313 { 0314 KNotification *notification = KNotification::event(QStringLiteral("UpdateRestart"), 0315 i18n("Restart is required"), 0316 i18n("The system needs to be restarted for the updates to take effect."), 0317 QStringLiteral("system-software-update"), 0318 nullptr, 0319 KNotification::Persistent | KNotification::DefaultEvent, 0320 QStringLiteral("discoverabstractnotifier")); 0321 0322 notification->setActions(QStringList{i18nc("@action:button", "Restart")}); 0323 notification->setDefaultAction(notification->actions().constFirst()); 0324 connect(notification, &KNotification::action1Activated, this, &DiscoverNotifier::reboot); 0325 0326 notification->sendEvent(); 0327 } 0328 0329 void DiscoverNotifier::reboot() 0330 { 0331 auto method = QDBusMessage::createMethodCall(QStringLiteral("org.kde.LogoutPrompt"), 0332 QStringLiteral("/LogoutPrompt"), 0333 QStringLiteral("org.kde.LogoutPrompt"), 0334 QStringLiteral("promptReboot")); 0335 QDBusConnection::sessionBus().asyncCall(method); 0336 } 0337 0338 void DiscoverNotifier::foundUpgradeAction(UpgradeAction *action) 0339 { 0340 KNotification *notification = new KNotification(QStringLiteral("distupgrade-notification"), KNotification::Persistent | KNotification::DefaultEvent); 0341 notification->setIconName(QStringLiteral("system-software-update")); 0342 notification->setActions(QStringList{i18nc("@action:button", "Upgrade")}); 0343 notification->setTitle(i18n("Upgrade available")); 0344 notification->setText(i18n("New version: %1", action->description())); 0345 0346 connect(notification, &KNotification::action1Activated, this, [action]() { 0347 action->trigger(); 0348 }); 0349 0350 notification->sendEvent(); 0351 } 0352 0353 void DiscoverNotifier::setBusy(bool isBusy) 0354 { 0355 if (isBusy == m_isBusy) 0356 return; 0357 0358 m_isBusy = isBusy; 0359 Q_EMIT busyChanged(isBusy); 0360 Q_EMIT stateChanged(); 0361 }