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