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

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