PackageKitNotifier.cpp 12.5 KB
Newer Older
1
2
3
4
5
6
/*
 *   SPDX-FileCopyrightText: 2013 Lukas Appelhans <l.appelhans@gmx.de>
 *   SPDX-FileCopyrightText: 2015 Aleix Pol Gonzalez <aleixpol@blue-systems.com>
 *
 *   SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
 */
7

8
9
#include "PackageKitNotifier.h"

Alexander Lohnau's avatar
Alexander Lohnau committed
10
11
12
#include <KConfigGroup>
#include <KDesktopFile>
#include <KLocalizedString>
13
#include <KNotification>
14
#include <PackageKit/Daemon>
15
#include <PackageKit/Offline>
16
#include <QDBusInterface>
Alexander Lohnau's avatar
Alexander Lohnau committed
17
#include <QDebug>
18
#include <QFile>
19
#include <QFileSystemWatcher>
Alexander Lohnau's avatar
Alexander Lohnau committed
20
21
22
23
24
#include <QProcess>
#include <QRegularExpression>
#include <QStandardPaths>
#include <QTextStream>
#include <QTimer>
25

26
#include "libdiscover_backend_debug.h"
Alexander Lohnau's avatar
Alexander Lohnau committed
27
#include "pk-offline-private.h"
28

Alexander Lohnau's avatar
Alexander Lohnau committed
29
PackageKitNotifier::PackageKitNotifier(QObject *parent)
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
30
    : BackendNotifierModule(parent)
31
32
    , m_securityUpdates(0)
    , m_normalUpdates(0)
33
{
34
    connect(PackageKit::Daemon::global(), &PackageKit::Daemon::updatesChanged, this, &PackageKitNotifier::recheckSystemUpdateNeeded);
35
    connect(PackageKit::Daemon::global(), &PackageKit::Daemon::transactionListChanged, this, &PackageKitNotifier::transactionListChanged);
36
    connect(PackageKit::Daemon::global(), &PackageKit::Daemon::restartScheduled, this, &PackageKitNotifier::nowNeedsReboot);
Alexander Lohnau's avatar
Alexander Lohnau committed
37
    connect(PackageKit::Daemon::global()->offline(), &PackageKit::Offline::changed, this, [this] {
38
39
40
        if (PackageKit::Daemon::global()->offline()->updateTriggered())
            nowNeedsReboot();
    });
41

Alexander Lohnau's avatar
Alexander Lohnau committed
42
    // Check if there's packages after 5'
43
44
    QTimer::singleShot(5 * 60 * 1000, this, &PackageKitNotifier::refreshDatabase);

45
46
47
48
49
    QTimer *regularCheck = new QTimer(this);
    connect(regularCheck, &QTimer::timeout, this, &PackageKitNotifier::refreshDatabase);

    const QString aptconfig = QStandardPaths::findExecutable(QStringLiteral("apt-config"));
    if (!aptconfig.isEmpty()) {
Alexander Lohnau's avatar
Alexander Lohnau committed
50
        checkAptVariable(aptconfig, QLatin1String("Apt::Periodic::Update-Package-Lists"), [regularCheck](const QStringRef &value) {
51
            bool ok;
52
53
            const int days = value.toInt(&ok);
            if (!ok || days == 0) {
Alexander Lohnau's avatar
Alexander Lohnau committed
54
                regularCheck->setInterval(24 * 60 * 60 * 1000); // refresh at least once every day
55
                regularCheck->start();
56
57
                if (!value.isEmpty())
                    qWarning() << "couldn't understand value for timer:" << value;
58
            }
59

Alexander Lohnau's avatar
Alexander Lohnau committed
60
61
            // if the setting is not empty, refresh will be carried out by unattended-upgrade
            // https://wiki.debian.org/UnattendedUpgrades
62
        });
63
    } else {
Alexander Lohnau's avatar
Alexander Lohnau committed
64
        regularCheck->setInterval(24 * 60 * 60 * 1000); // refresh at least once every day
65
        regularCheck->start();
66
    }
67

Harald Sitter's avatar
style++    
Harald Sitter committed
68
    QTimer::singleShot(3000, this, &PackageKitNotifier::checkOfflineUpdates);
69
70
71
72
73

    m_recheckTimer = new QTimer(this);
    m_recheckTimer->setInterval(200);
    m_recheckTimer->setSingleShot(true);
    connect(m_recheckTimer, &QTimer::timeout, this, &PackageKitNotifier::recheckSystemUpdate);
74

Alexander Lohnau's avatar
Alexander Lohnau committed
75
    QFileSystemWatcher *watcher = new QFileSystemWatcher(this);
76
77
    watcher->addPath(QStringLiteral(PK_OFFLINE_ACTION_FILENAME));
    connect(watcher, &QFileSystemWatcher::fileChanged, this, &PackageKitNotifier::nowNeedsReboot);
78

Alexander Lohnau's avatar
Alexander Lohnau committed
79
80
81
    QTimer::singleShot(100, this, [this]() {
        if (QFile::exists(QStringLiteral(PK_OFFLINE_ACTION_FILENAME)))
            nowNeedsReboot();
82
    });
83
84
85
86
87
88
}

PackageKitNotifier::~PackageKitNotifier()
{
}

89
90
91
92
93
void PackageKitNotifier::checkOfflineUpdates()
{
    if (!QFile::exists(QStringLiteral(PK_OFFLINE_RESULTS_FILENAME))) {
        return;
    }
94
    qCDebug(LIBDISCOVER_BACKEND_LOG) << "found offline update results at " << PK_OFFLINE_RESULTS_FILENAME;
95
96
97
98
99
100
101
102
103
104

    KDesktopFile file(QStringLiteral(PK_OFFLINE_RESULTS_FILENAME));
    KConfigGroup group(&file, PK_OFFLINE_RESULTS_GROUP);

    const bool success = group.readEntry("Success", false);
    const QString packagesJoined = group.readEntry("Packages");
    const auto packages = packagesJoined.splitRef(QLatin1Char(','));
    if (!success) {
        const QString errorDetails = group.readEntry("ErrorDetails");

105
        KNotification *notification = new KNotification(QStringLiteral("OfflineUpdateFailed"), KNotification::Persistent | KNotification::DefaultEvent);
106
107
        notification->setIconName(QStringLiteral("error"));
        notification->setText(i18n("Offline Updates"));
Alexander Lohnau's avatar
Alexander Lohnau committed
108
        notification->setText(i18np("Failed to update %1 package\n%2", "Failed to update %1 packages\n%2", packages.count(), errorDetails));
109
        notification->setActions(QStringList{i18nc("@action:button", "Open Discover"), i18nc("@action:button", "Repair System")});
110

Alexander Lohnau's avatar
Alexander Lohnau committed
111
        connect(notification, &KNotification::action1Activated, this, []() {
112
            QProcess::startDetached(QStringLiteral("plasma-discover"), QStringList());
113
        });
Alexander Lohnau's avatar
Alexander Lohnau committed
114
        connect(notification, &KNotification::action2Activated, this, [this]() {
115
            auto trans = PackageKit::Daemon::global()->repairSystem();
Alexander Lohnau's avatar
Alexander Lohnau committed
116
117
118
            connect(trans, &PackageKit::Transaction::errorCode, this, [](PackageKit::Transaction::Error /*error*/, const QString &details) {
                KNotification::event(QStringLiteral("OfflineUpdateRepairFailed"),
                                     i18n("Repair Failed"),
119
                                     xi18nc("@info", "%1<nl/>Please report this error to your distribution.", details),
Alexander Lohnau's avatar
Alexander Lohnau committed
120
121
122
                                     {},
                                     KNotification::Persistent,
                                     QStringLiteral("org.kde.discovernotifier"));
123
124
            });
        });
125
126
127

        notification->sendEvent();
    } else {
128
        KNotification *notification = new KNotification(QStringLiteral("OfflineUpdateSuccessful"));
129
130
        notification->setIconName(QStringLiteral("system-software-update"));
        notification->setTitle(i18n("Offline Updates"));
131
        notification->setText(i18np("Successfully updated %1 package", "Successfully updated %1 packages", packages.count()));
132
        notification->setActions(QStringList{i18nc("@action:button", "Open Discover")});
133
        notification->setComponentName(QStringLiteral("discoverabstractnotifier"));
134

Alexander Lohnau's avatar
Alexander Lohnau committed
135
        connect(notification, &KNotification::action1Activated, this, []() {
136
            QProcess::startDetached(QStringLiteral("plasma-discover"), QStringList());
137
138
139
140
        });

        notification->sendEvent();
    }
141
142

    PackageKit::Daemon::global()->offline()->clearResults();
143
144
}

145
void PackageKitNotifier::recheckSystemUpdateNeeded()
146
{
147
148
    static bool first = true;
    if (first) {
Alexander Lohnau's avatar
Alexander Lohnau committed
149
        // PKQt will emit these signals when it starts (bug?) and would trigger the system recheck before we've ever checked at all
150
151
152
153
154
        connect(PackageKit::Daemon::global(), &PackageKit::Daemon::networkStateChanged, this, &PackageKitNotifier::recheckSystemUpdateNeeded);
        connect(PackageKit::Daemon::global(), &PackageKit::Daemon::isRunningChanged, this, &PackageKitNotifier::recheckSystemUpdateNeeded);
        first = false;
    }

155
156
157
    if (PackageKit::Daemon::global()->offline()->updateTriggered())
        return;

158
159
160
161
    m_recheckTimer->start();
}

void PackageKitNotifier::recheckSystemUpdate()
162
{
163
    if (PackageKit::Daemon::global()->isRunning()) {
164
        PackageKit::Daemon::getUpdates();
165
    }
166
167
}

Alexander Lohnau's avatar
Alexander Lohnau committed
168
void PackageKitNotifier::setupGetUpdatesTransaction(PackageKit::Transaction *trans)
169
{
170
    qCDebug(LIBDISCOVER_BACKEND_LOG) << "using..." << trans << trans->tid().path();
171
172
173
174
175
176
177

    trans->setProperty("normalUpdates", 0);
    trans->setProperty("securityUpdates", 0);
    connect(trans, &PackageKit::Transaction::package, this, &PackageKitNotifier::package);
    connect(trans, &PackageKit::Transaction::finished, this, &PackageKitNotifier::finished);
}

Alexander Lohnau's avatar
Alexander Lohnau committed
178
void PackageKitNotifier::package(PackageKit::Transaction::Info info, const QString & /*packageID*/, const QString & /*summary*/)
179
{
Alexander Lohnau's avatar
Alexander Lohnau committed
180
    PackageKit::Transaction *trans = qobject_cast<PackageKit::Transaction *>(sender());
181

182
    switch (info) {
Alexander Lohnau's avatar
Alexander Lohnau committed
183
184
185
186
187
188
189
190
    case PackageKit::Transaction::InfoBlocked:
        break; // skip, we ignore blocked updates
    case PackageKit::Transaction::InfoSecurity:
        trans->setProperty("securityUpdates", trans->property("securityUpdates").toInt() + 1);
        break;
    default:
        trans->setProperty("normalUpdates", trans->property("normalUpdates").toInt() + 1);
        break;
191
192
193
    }
}

Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
194
void PackageKitNotifier::finished(PackageKit::Transaction::Exit /*exit*/, uint)
195
{
Alexander Lohnau's avatar
Alexander Lohnau committed
196
    const PackageKit::Transaction *trans = qobject_cast<PackageKit::Transaction *>(sender());
197
198
199

    const uint normalUpdates = trans->property("normalUpdates").toInt();
    const uint securityUpdates = trans->property("securityUpdates").toInt();
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
200
    const bool changed = normalUpdates != m_normalUpdates || securityUpdates != m_securityUpdates;
201
202
203
204
205
206
207

    m_normalUpdates = normalUpdates;
    m_securityUpdates = securityUpdates;

    if (changed) {
        Q_EMIT foundUpdates();
    }
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
208
209
}

210
bool PackageKitNotifier::hasUpdates()
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
211
{
212
    return m_normalUpdates > 0;
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
213
214
}

215
bool PackageKitNotifier::hasSecurityUpdates()
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
216
{
217
    return m_securityUpdates > 0;
218
}
219

Alexander Lohnau's avatar
Alexander Lohnau committed
220
void PackageKitNotifier::onDistroUpgrade(PackageKit::Transaction::DistroUpgrade /*type*/, const QString &name, const QString &description)
221
{
222
    auto a = new UpgradeAction(name, description, this);
Alexander Lohnau's avatar
Alexander Lohnau committed
223
    connect(a, &UpgradeAction::triggered, this, [](const QString &name) {
224
225
        PackageKit::Daemon::upgradeSystem(name, PackageKit::Transaction::UpgradeKindDefault);
    });
226
    Q_EMIT foundUpgradeAction(a);
227
228
}

229
230
231
232
void PackageKitNotifier::refreshDatabase()
{
    if (!m_refresher) {
        m_refresher = PackageKit::Daemon::refreshCache(false);
233
        connect(m_refresher.data(), &PackageKit::Transaction::finished, this, &PackageKitNotifier::recheckSystemUpdateNeeded);
234
    }
235

236
    if (!m_distUpgrades && (PackageKit::Daemon::roles() & PackageKit::Transaction::RoleUpgradeSystem)) {
237
238
239
        m_distUpgrades = PackageKit::Daemon::getDistroUpgrades();
        connect(m_distUpgrades, &PackageKit::Transaction::distroUpgrade, this, &PackageKitNotifier::onDistroUpgrade);
    }
240
}
241

Alexander Lohnau's avatar
Alexander Lohnau committed
242
QProcess *PackageKitNotifier::checkAptVariable(const QString &aptconfig, const QLatin1String &varname, const std::function<void(const QStringRef &val)> &func)
243
{
Alexander Lohnau's avatar
Alexander Lohnau committed
244
    QProcess *process = new QProcess;
245
    process->start(aptconfig, {QStringLiteral("dump")});
Alexander Lohnau's avatar
Alexander Lohnau committed
246
    connect(process, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), this, [func, process, varname](int code) {
247
248
249
        if (code != 0)
            return;

250
        QRegularExpression rx(QLatin1Char('^') + varname + QStringLiteral(" \"(.*?)\";?$"), QRegularExpression::CaseInsensitiveOption);
251
252
253
254
255
256
        QTextStream stream(process);
        QString line;
        while (stream.readLineInto(&line)) {
            const auto match = rx.match(line);
            if (match.hasMatch()) {
                func(match.capturedRef(1));
257
                return;
258
259
            }
        }
260
        func({});
261
    });
Alexander Lohnau's avatar
Alexander Lohnau committed
262
    connect(process, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), process, &QObject::deleteLater);
263
264
    return process;
}
265

Alexander Lohnau's avatar
Alexander Lohnau committed
266
void PackageKitNotifier::transactionListChanged(const QStringList &tids)
267
{
268
269
270
    if (PackageKit::Daemon::global()->offline()->updateTriggered())
        return;

Alexander Lohnau's avatar
Alexander Lohnau committed
271
    for (const auto &tid : tids) {
272
273
274
275
        if (m_transactions.contains(tid))
            continue;

        auto t = new PackageKit::Transaction(QDBusObjectPath(tid));
276
277
278
279
280
281

        connect(t, &PackageKit::Transaction::roleChanged, this, [this, t]() {
            if (t->role() == PackageKit::Transaction::RoleGetUpdates) {
                setupGetUpdatesTransaction(t);
            }
        });
282
        connect(t, &PackageKit::Transaction::requireRestart, this, &PackageKitNotifier::onRequireRestart);
Alexander Lohnau's avatar
Alexander Lohnau committed
283
        connect(t, &PackageKit::Transaction::finished, this, [this, t]() {
284
            auto restart = t->property("requireRestart");
285
286
287
            if (!restart.isNull()) {
                auto restartEvent = PackageKit::Transaction::Restart(restart.toInt());
                if (restartEvent >= PackageKit::Transaction::RestartSession) {
288
                    nowNeedsReboot();
289
290
                }
            }
291
292
293
294
295
296
297
            m_transactions.remove(t->tid().path());
            t->deleteLater();
        });
        m_transactions.insert(tid, t);
    }
}

298
299
300
301
302
303
304
305
void PackageKitNotifier::nowNeedsReboot()
{
    if (!m_needsReboot) {
        m_needsReboot = true;
        Q_EMIT needsRebootChanged();
    }
}

306
307
void PackageKitNotifier::onRequireRestart(PackageKit::Transaction::Restart type, const QString &packageID)
{
Alexander Lohnau's avatar
Alexander Lohnau committed
308
    PackageKit::Transaction *t = qobject_cast<PackageKit::Transaction *>(sender());
309
    t->setProperty("requireRestart", qMax<int>(t->property("requireRestart").toInt(), type));
310
    qCDebug(LIBDISCOVER_BACKEND_LOG) << "RESTART" << type << "is required for package" << packageID;
311
}