Commit 09db9dd3 authored by Aleix Pol Gonzalez's avatar Aleix Pol Gonzalez 🐧
Browse files

Support unattended updates

Makes it possible to trust the running distribution or fleet admins to
offer the right thing without having to go through pointless GUI steps.
parent a71b7eec
......@@ -24,7 +24,7 @@ include(KDEClangFormat)
find_package(PkgConfig REQUIRED)
find_package(KF5 ${KF5_MIN_VERSION} REQUIRED CoreAddons Config Crash DBusAddons I18n Archive XmlGui ItemModels KIO Declarative)
find_package(KF5 ${KF5_MIN_VERSION} REQUIRED CoreAddons Config Crash DBusAddons I18n Archive XmlGui ItemModels KIO Declarative KCMUtils IdleTime)
find_package(KF5Kirigami2 2.7.0)
find_package(packagekitqt5 1.0.1 CONFIG)
......@@ -44,12 +44,15 @@ if(NOT CMAKE_VERSION VERSION_LESS "3.10.0")
list(APPEND CMAKE_AUTOMOC_MACRO_NAMES "DISCOVER_BACKEND_PLUGIN")
endif()
set(CMAKE_CXX_STANDARD 14)
configure_file(DiscoverVersion.h.in DiscoverVersion.h)
add_subdirectory(libdiscover)
add_subdirectory(discover)
add_subdirectory(exporter)
add_subdirectory(update)
add_subdirectory(kcm)
option(WITH_NOTIFIER "Build and install the notifier plasmoid" ON)
if(WITH_NOTIFIER)
......
......@@ -4,3 +4,4 @@ $EXTRACTRC --context="Category" --tag-group=none --tag=Name `find libdiscover -n
$XGETTEXT rc.cpp `find libdiscover -name \*.cpp` -o $podir/libdiscover.pot
$XGETTEXT `find discover -name \*.cpp -o -name \*.qml -o -name \*.js` -o $podir/plasma-discover.pot
$XGETTEXT `find notifier -name \*.cpp` -o $podir/plasma-discover-notifier.pot
$XGETTEXT `find kcm -name \*.cpp -o -name \*.qml` -o $podir/kcm_updates.pot
#SPDX-FileCopyrightText: (C) 2020 Aleix Pol Gonzalzez <aleixpol@kde.org>
#SPDX-License-Identifier: BSD-3-Clause
add_definitions(-DTRANSLATION_DOMAIN=\"kcm_updates\")
kcmutils_generate_module_data(
kcm_updates_PART_SRCS
MODULE_DATA_HEADER updatesdata.h
MODULE_DATA_CLASS_NAME UpdatesData
SETTINGS_HEADERS updatessettings.h
SETTINGS_CLASSES UpdatesSettings
)
kconfig_add_kcfg_files(kcm_updates_PART_SRCS updatessettings.kcfgc GENERATE_MOC)
add_library(kcm_updates MODULE updates.cpp ${kcm_updates_PART_SRCS})
target_link_libraries(kcm_updates
KF5::I18n
KF5::KCMUtils
KF5::QuickAddons
)
kcoreaddons_desktop_to_json(kcm_updates "kcm_updates.desktop")
install(TARGETS kcm_updates DESTINATION ${KDE_INSTALL_PLUGINDIR}/kcms)
install(FILES kcm_updates.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR})
kpackage_install_package(package kcm_updates kcms)
[Desktop Entry]
Type=Service
X-KDE-ServiceTypes=KCModule
Icon=system-software-update
X-KDE-Library=kcm_updates
X-KDE-ParentApp=kcontrol
X-KDE-System-Settings-Parent-Category=system-administration
Name=Updates
Comment=Configure software updates integration
Categories=Qt;KDE;
/*
* SPDX-FileCopyrightText: 2020 Aleix Pol Gonzalez <aleixpol@kde.org>
*
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
import QtQuick 2.1
import QtQuick.Layouts 1.1
import QtQuick.Controls 2.3 as QQC2
import org.kde.kirigami 2.6 as Kirigami
import org.kde.kcm 1.3
SimpleKCM {
id: root
ConfigModule.buttons: ConfigModule.Default | ConfigModule.Apply
QQC2.ButtonGroup {
id: autoUpdatesGroup
onCheckedButtonChanged: {
kcm.updatesSettings.useUnattendedUpdates = automaticallyRadio.checked
}
}
ColumnLayout {
Kirigami.FormLayout {
QQC2.RadioButton {
Kirigami.FormData.label: i18n("Update software:")
text: i18n("Manually")
QQC2.ButtonGroup.group: autoUpdatesGroup
checked: !kcm.updatesSettings.useUnattendedUpdates
}
QQC2.RadioButton {
id: automaticallyRadio
text: i18n("Automatically")
QQC2.ButtonGroup.group: autoUpdatesGroup
checked: kcm.updatesSettings.useUnattendedUpdates
}
SettingStateBinding {
configObject: kcm.updatesSettings
settingName: "useUnattendedUpdates"
target: automaticallyRadio
}
}
QQC2.Label {
Layout.alignment: Qt.AlignHCenter
Layout.preferredWidth: Math.min(Kirigami.Units.gridUnit * 25, Math.round(root.width * 0.6))
wrapMode: Text.WordWrap
visible: automaticallyRadio.checked
horizontalAlignment: Text.AlignHCenter
font: theme.smallestFont
text: xi18nc("@info", "Software updates will be downloaded automatically when they become available. Updates for applications will be installed immediately, while system updates will be installed the next time the computer is restarted.")
}
}
}
# SPDX-FileCopyrightText: (C) 2020 Aleix Pol Gonzalzez <aleixpol@kde.org>
#
# SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
[Desktop Entry]
Name=Software Updates
Icon=system-software-update
Type=Service
X-KDE-PluginInfo-License=GPL
X-KDE-PluginInfo-Name=kcm_updates
X-KDE-ServiceTypes=Plasma/Generic
X-Plasma-API=declarativeappletscript
X-Plasma-MainScript=ui/main.qml
/*
* SPDX-FileCopyrightText: 2020 Aleix Pol Gonzalez <aleixpol@kde.org>
*
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#include "updates.h"
#include <KConfigGroup>
#include <KPluginFactory>
#include <KAboutData>
#include <KLocalizedString>
#include <updatesdata.h>
#include <updatessettings.h>
K_PLUGIN_FACTORY_WITH_JSON(UpdatesFactory, "kcm_updates.json", registerPlugin<Updates>(); registerPlugin<UpdatesData>();)
Updates::Updates(QObject *parent, const QVariantList &args)
: KQuickAddons::ManagedConfigModule(parent)
, m_data(new UpdatesData(this))
{
Q_UNUSED(args)
qmlRegisterAnonymousType<UpdatesSettings>("org.kde.discover.updates", 1);
setAboutData(new KAboutData(QStringLiteral("kcm_updates"),
i18n("Software Updates"),
QStringLiteral("1.0"), i18n("Configure software update settings"), KAboutLicense::LGPL));
}
Updates::~Updates() = default;
UpdatesSettings *Updates::updatesSettings() const
{
return m_data->settings();
}
#include "updates.moc"
/*
* SPDX-FileCopyrightText: 2020 Aleix Pol Gonzalez <aleixpol@kde.org>
*
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#pragma once
#include <KQuickAddons/ManagedConfigModule>
#include <KSharedConfig>
class UpdatesData;
class UpdatesSettings;
class Updates : public KQuickAddons::ManagedConfigModule
{
Q_OBJECT
Q_PROPERTY(UpdatesSettings *updatesSettings READ updatesSettings CONSTANT)
public:
explicit Updates(QObject *parent = nullptr, const QVariantList &list = QVariantList());
~Updates() override;
UpdatesSettings *updatesSettings() const;
private:
UpdatesData *m_data;
};
<?xml version="1.0" encoding="UTF-8"?>
<!--
SPDX-FileCopyrightText: (C) 2020 Aleix Pol Gonzalzez <aleixpol@kde.org>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
-->
<kcfg xmlns="http://www.kde.org/standards/kcfg/1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.kde.org/standards/kcfg/1.0
http://www.kde.org/standards/kcfg/1.0/kcfg.xsd" >
<kcfgfile name="PlasmaDiscoverUpdates" />
<group name="Global">
<entry name="UseUnattendedUpdates" type="Bool">
<default>false</default>
</entry>
</group>
</kcfg>
; SPDX-FileCopyrightText: (C) 2020 Aleix Pol Gonzalzez <aleixpol@kde.org>
;
; SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
File=updatessettings.kcfg
ClassName=UpdatesSettings
Notifiers=true
Mutators=true
DefaultValueGetters=true
GenerateProperties=true
ParentInConstructor=true
add_definitions(-DTRANSLATION_DOMAIN=\"plasma-discover-notifier\")
add_executable(DiscoverNotifier BackendNotifierFactory.cpp DiscoverNotifier.cpp NotifierItem.cpp main.cpp)
kconfig_add_kcfg_files(notifier_SRCS ../kcm/updatessettings.kcfgc GENERATE_MOC)
add_executable(DiscoverNotifier
BackendNotifierFactory.cpp
DiscoverNotifier.cpp
NotifierItem.cpp
UnattendedUpdates.cpp
main.cpp
${notifier_SRCS}
)
target_link_libraries(DiscoverNotifier
KF5::Notifications
KF5::I18n
KF5::KIOGui
KF5::Crash
KF5::DBusAddons
KF5::ConfigGui
KF5::IdleTime
Discover::Notifiers
)
set_target_properties(DiscoverNotifier PROPERTIES INSTALL_RPATH ${CMAKE_INSTALL_FULL_LIBDIR}/plasma-discover)
install(TARGETS DiscoverNotifier DESTINATION ${KDE_INSTALL_LIBEXECDIR})
......
......@@ -6,6 +6,7 @@
#include "DiscoverNotifier.h"
#include "BackendNotifierFactory.h"
#include "UnattendedUpdates.h"
#include <QDebug>
#include <QDBusConnection>
#include <QDBusPendingCall>
......@@ -18,6 +19,7 @@
#include <KIO/ApplicationLauncherJob>
#include <KIO/CommandLauncherJob>
#include "updatessettings.h"
#include "../libdiscover/utils.h"
DiscoverNotifier::DiscoverNotifier(QObject * parent)
......@@ -44,6 +46,11 @@ DiscoverNotifier::DiscoverNotifier(QObject * parent)
//Only fetch updates after the system is comfortably booted
QTimer::singleShot(20000, this, &DiscoverNotifier::recheckSystemUpdateNeeded);
m_settings = new UpdatesSettings(this);
m_settingsWatcher = KConfigWatcher::create(m_settings->sharedConfig());
refreshUnattended();
connect(m_settingsWatcher.data(), &KConfigWatcher::configChanged, this, &DiscoverNotifier::refreshUnattended);
}
DiscoverNotifier::~DiscoverNotifier() = default;
......@@ -107,10 +114,34 @@ void DiscoverNotifier::updateStatusNotifier()
emit stateChanged();
}
// we only want to do unattended updates when on an ethernet or wlan network
static bool isConnectionAdequate(const QNetworkConfiguration &network)
{
return (network.bearerType() == QNetworkConfiguration::BearerEthernet || network.bearerType() == QNetworkConfiguration::BearerWLAN);
}
void DiscoverNotifier::refreshUnattended()
{
m_settings->read();
const auto enabled = m_settings->useUnattendedUpdates() && m_manager->isOnline() && isConnectionAdequate(m_manager->defaultConfiguration());
if (bool(m_unattended) == enabled)
return;
if (enabled) {
m_unattended = new UnattendedUpdates(this);
} else {
delete m_unattended;
m_unattended = nullptr;
}
}
DiscoverNotifier::State DiscoverNotifier::state() const
{
if (m_needsReboot)
return RebootRequired;
else if (m_isBusy)
return Busy;
else if (m_manager && !m_manager->isOnline())
return Offline;
else if (m_hasSecurityUpdates)
......@@ -134,6 +165,8 @@ QString DiscoverNotifier::iconName() const
return QStringLiteral("system-reboot");
case Offline:
return QStringLiteral("offline");
case Busy:
return QStringLiteral("state-download");
}
return QString();
}
......@@ -151,6 +184,8 @@ QString DiscoverNotifier::message() const
return i18n("Computer needs to restart");
case Offline:
return i18n("Offline");
case Busy:
return i18n("Applying unattended updates...");
}
return QString();
}
......@@ -167,6 +202,8 @@ void DiscoverNotifier::recheckSystemUpdateNeeded()
foreach(BackendNotifierModule* module, m_backends)
module->recheckSystemUpdateNeeded();
refreshUnattended();
}
QStringList DiscoverNotifier::loadedModules() const
......@@ -214,3 +251,13 @@ void DiscoverNotifier::foundUpgradeAction(UpgradeAction* action)
notification->sendEvent();
}
void DiscoverNotifier::setBusy(bool isBusy)
{
if (isBusy == m_isBusy)
return;
m_isBusy = isBusy;
Q_EMIT busyChanged(isBusy);
Q_EMIT stateChanged();
}
......@@ -12,10 +12,12 @@
#include <QTimer>
#include <QPointer>
#include <KConfigWatcher>
#include <KNotification>
class QNetworkConfigurationManager;
class KNotification;
class QNetworkConfigurationManager;
class UnattendedUpdates;
class DiscoverNotifier : public QObject
{
......@@ -25,11 +27,13 @@ Q_PROPERTY(QString iconName READ iconName NOTIFY stateChanged)
Q_PROPERTY(QString message READ message NOTIFY stateChanged)
Q_PROPERTY(State state READ state NOTIFY stateChanged)
Q_PROPERTY(bool needsReboot READ needsReboot NOTIFY needsRebootChanged)
Q_PROPERTY(bool isBusy READ isBusy NOTIFY busyChanged)
public:
enum State {
NoUpdates,
NormalUpdates,
SecurityUpdates,
Busy,
RebootRequired,
Offline,
};
......@@ -47,6 +51,9 @@ public:
QStringList loadedModules() const;
bool needsReboot() const { return m_needsReboot; }
void setBusy(bool isBusy);
bool isBusy() const { return m_isBusy; }
public Q_SLOTS:
void recheckSystemUpdateNeeded();
void showDiscover();
......@@ -59,18 +66,24 @@ Q_SIGNALS:
void stateChanged();
bool needsRebootChanged(bool needsReboot);
void newUpgradeAction(UpgradeAction* action);
bool busyChanged(bool isBusy);
private:
void showRebootNotification();
void updateStatusNotifier();
void refreshUnattended();
QList<BackendNotifierModule*> m_backends;
QTimer m_timer;
bool m_hasSecurityUpdates = false;
bool m_hasUpdates = false;
bool m_needsReboot = false;
bool m_isBusy = false;
QNetworkConfigurationManager* m_manager = nullptr;
QPointer<KNotification> m_updatesAvailableNotification;
UnattendedUpdates* m_unattended = nullptr;
KConfigWatcher::Ptr m_settingsWatcher;
class UpdatesSettings* m_settings;
};
#endif //ABSTRACTKDEDMODULE_H
......@@ -15,6 +15,7 @@ KStatusNotifierItem::ItemStatus sniStatus(DiscoverNotifier::State state)
case DiscoverNotifier::Offline:
case DiscoverNotifier::NoUpdates:
return KStatusNotifierItem::Passive;
case DiscoverNotifier::Busy:
case DiscoverNotifier::NormalUpdates:
case DiscoverNotifier::SecurityUpdates:
case DiscoverNotifier::RebootRequired:
......@@ -23,7 +24,6 @@ KStatusNotifierItem::ItemStatus sniStatus(DiscoverNotifier::State state)
return KStatusNotifierItem::Active;
}
NotifierItem::NotifierItem()
{
}
......
/*
* SPDX-FileCopyrightText: 2020 Aleix Pol Gonzalez <aleixpol@kde.org>
*
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#include "UnattendedUpdates.h"
#include "DiscoverNotifier.h"
#include <KIdleTime>
#include <QProcess>
#include <QDateTime>
#include <QDebug>
#include <chrono>
UnattendedUpdates::UnattendedUpdates(DiscoverNotifier* parent)
: QObject(parent)
{
connect(parent, &DiscoverNotifier::stateChanged, this, &UnattendedUpdates::checkNewState);
connect(KIdleTime::instance(), QOverload<int,int>::of(&KIdleTime::timeoutReached), this, &UnattendedUpdates::triggerUpdate);
checkNewState();
}
UnattendedUpdates::~UnattendedUpdates() noexcept
{
KIdleTime::instance()->removeAllIdleTimeouts();
}
void UnattendedUpdates::checkNewState()
{
DiscoverNotifier* notifier = static_cast<DiscoverNotifier*>(parent());
if (notifier->hasUpdates()) {
qDebug() << "waiting for an idle moment";
// If the system is untouched for 1 hour, trigger the unattened update
using namespace std::chrono_literals;
KIdleTime::instance()->addIdleTimeout(int(std::chrono::milliseconds(1h).count()));
} else {
KIdleTime::instance()->removeAllIdleTimeouts();
}
}
void UnattendedUpdates::triggerUpdate()
{
KIdleTime::instance()->removeAllIdleTimeouts();
DiscoverNotifier* notifier = static_cast<DiscoverNotifier*>(parent());
if (!notifier->hasUpdates() || notifier->isBusy()) {
return;
}
auto process = new QProcess(this);
connect(process, &QProcess::errorOccurred, this, [] (QProcess::ProcessError error) {
qWarning() << "Error running plasma-discover-update" << error;
});
connect(process, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this, [this, process] (int exitCode, QProcess::ExitStatus exitStatus) {
qDebug() << "Finished running plasma-discover-update" << exitCode << exitStatus;
DiscoverNotifier* notifier = static_cast<DiscoverNotifier*>(parent());
notifier->setBusy(false);
process->deleteLater();
});
notifier->setBusy(true);
process->start(QStringLiteral("plasma-discover-update"), { QStringLiteral("--offline") });
qInfo() << "started unattended update" << QDateTime::currentDateTimeUtc();
}
/*
* SPDX-FileCopyrightText: 2020 Aleix Pol Gonzalez <aleixpol@kde.org>
*
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#pragma once
#include <QObject>
class DiscoverNotifier;
class UnattendedUpdates : public QObject
{
Q_OBJECT
public:
UnattendedUpdates(DiscoverNotifier* parent);
~UnattendedUpdates() override;
private:
void checkNewState();
void triggerUpdate();
};
......@@ -19,7 +19,6 @@
#include <QDBusConnection>
#include <QDBusConnectionInterface>
#include <QDebug>
#include "DiscoverNotifier.h"
#include "../DiscoverVersion.h"
#include "NotifierItem.h"
......@@ -36,7 +35,7 @@ int main(int argc, char** argv)
KDBusService::StartupOptions startup = {};
{
KAboutData about(QStringLiteral("DiscoverNotifier"), i18n("Discover Notifier"), version, i18n("System update status notifier"),
KAboutLicense::GPL, i18n("© 2010-2019 Plasma Development Team"));
KAboutLicense::GPL, i18n("© 2010-2020 Plasma Development Team"));
about.addAuthor(QStringLiteral("Aleix Pol Gonzalez"), {}, QStringLiteral("aleixpol@kde.org"));
about.setProductName("discover/discover");
about.setProgramLogo(app.windowIcon());
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment