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

#include "PackageKitBackend.h"
9
#include "PackageKitSourcesBackend.h"
10
#include "PackageKitUpdater.h"
11
#include "AppPackageKitResource.h"
12
#include "PKTransaction.h"
13
#include "LocalFilePKResource.h"
14
#include "PKResolveTransaction.h"
15
#include <resources/AbstractResource.h>
16
#include <resources/StandardBackendUpdater.h>
17
#include <resources/SourcesModel.h>
18
19
#include <appstream/OdrsReviewsBackend.h>
#include <appstream/AppStreamIntegration.h>
20
#include <appstream/AppStreamUtils.h>
21

22
#include <QProcess>
23
#include <QStringList>
24
#include <QDebug>
25
#include <QStandardPaths>
26
#include <QFile>
27
28
29
#include <QAction>
#include <QMimeDatabase>
#include <QFileSystemWatcher>
30
31
#include <QFutureWatcher>
#include <QtConcurrentRun>
32

33
#include <PackageKit/Daemon>
34
#include <PackageKit/Offline>
35
#include <PackageKit/Details>
36

37
#include <KLocalizedString>
38
#include <KProtocolManager>
39

40
#include "utils.h"
41
#include "config-paths.h"
42
#include "libdiscover_backend_debug.h"
43

Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
44
DISCOVER_BACKEND_PLUGIN(PackageKitBackend)
45

46
47
48
49
50
51
52
53
54
55
56
57
template <typename T, typename W>
static void setWhenAvailable(const QDBusPendingReply<T>& pending, W func, QObject* parent)
{
    QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(pending, parent);
    QObject::connect(watcher, &QDBusPendingCallWatcher::finished,
                    parent, [func](QDBusPendingCallWatcher* watcher) {
                        watcher->deleteLater();
                        QDBusPendingReply<T> reply = *watcher;
                        func(reply.value());
                    });
}

58
QString PackageKitBackend::locateService(const QString &filename)
59
60
61
62
{
    return QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("applications/")+filename);
}

63
PackageKitBackend::PackageKitBackend(QObject* parent)
64
    : AbstractResourcesBackend(parent)
65
    , m_appdata(new AppStream::Pool)
66
    , m_updater(new PackageKitUpdater(this))
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
67
    , m_refresher(nullptr)
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
68
    , m_isFetching(0)
69
    , m_reviews(AppStreamIntegration::global()->reviews())
70
{
71
    QTimer* t = new QTimer(this);
72
    connect(t, &QTimer::timeout, this, &PackageKitBackend::checkForUpdates);
73
74
75
    t->setInterval(60 * 60 * 1000);
    t->setSingleShot(false);
    t->start();
76

77
    m_delayedDetailsFetch.setSingleShot(true);
78
    m_delayedDetailsFetch.setInterval(100);
79
80
    connect(&m_delayedDetailsFetch, &QTimer::timeout, this, &PackageKitBackend::performDetailsFetch);

81
    connect(PackageKit::Daemon::global(), &PackageKit::Daemon::restartScheduled, m_updater, &PackageKitUpdater::enableNeedsReboot);
82
    connect(PackageKit::Daemon::global(), &PackageKit::Daemon::isRunningChanged, this, &PackageKitBackend::checkDaemonRunning);
83
84
85
    connect(m_reviews.data(), &OdrsReviewsBackend::ratingsReady, this, [this] {
        m_reviews->emitRatingFetched(this, kTransform<QList<AbstractResource*>>(m_packages.packages.values(), [] (AbstractResource* r) { return r; }));
    });
86

87
88
89
90
91
92
93
    auto proxyWatch = new QFileSystemWatcher(this);
    proxyWatch->addPath(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + QLatin1String("/kioslaverc"));
    connect(proxyWatch, &QFileSystemWatcher::fileChanged, this, [this](){
        KProtocolManager::reparseConfiguration();
        updateProxy();
    });

94
    SourcesModel::global()->addSourcesBackend(new PackageKitSourcesBackend(this));
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
95

96
97
    reloadPackageList();

98
    acquireFetching(true);
99
    setWhenAvailable(PackageKit::Daemon::getTimeSinceAction(PackageKit::Transaction::RoleRefreshCache), [this](uint timeSince) {
100
101
        if (timeSince > 3600)
            checkForUpdates();
102
103
        else
            fetchUpdates();
104
        acquireFetching(false);
105
    }, this);
106
107

    PackageKit::Daemon::global()->setHints(QStringLiteral("locale=%1").arg(qEnvironmentVariable("LANG")));
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
108
109
}

110
111
112
113
114
PackageKitBackend::~PackageKitBackend()
{
    m_threadPool.waitForDone(200);
    m_threadPool.clear();
}
115

116
117
118
119
120
121
122
123
124
void PackageKitBackend::updateProxy()
{

    if (PackageKit::Daemon::isRunning()) {
        static bool everHad = KProtocolManager::useProxy();
        if (!everHad && !KProtocolManager::useProxy())
            return;

        everHad = KProtocolManager::useProxy();
Laurent Montel's avatar
Laurent Montel committed
125
126
127
128
        PackageKit::Daemon::global()->setProxy(KProtocolManager::proxyFor(QStringLiteral("http")),
                                               KProtocolManager::proxyFor(QStringLiteral("https")),
                                               KProtocolManager::proxyFor(QStringLiteral("ftp")),
                                               KProtocolManager::proxyFor(QStringLiteral("socks")),
129
130
131
132
133
                                               {},
                                               {});
    }
}

134
135
136
137
138
bool PackageKitBackend::isFetching() const
{
    return m_isFetching;
}

139
void PackageKitBackend::acquireFetching(bool f)
140
{
141
142
143
144
145
146
    if (f)
        m_isFetching++;
    else
        m_isFetching--;

    if ((!f && m_isFetching==0) || (f && m_isFetching==1)) {
147
        emit fetchingChanged();
148
149
        if (m_isFetching==0)
            emit available();
150
    }
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
151
    Q_ASSERT(m_isFetching>=0);
152
153
}

154
155
156
157
158
159
160
struct DelayedAppStreamLoad {
    QVector<AppStream::Component> components;
    QHash<QString, AppStream::Component> missingComponents;
    bool correct = true;
};

static DelayedAppStreamLoad loadAppStream(AppStream::Pool* appdata)
161
{
162
    DelayedAppStreamLoad ret;
163

Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
164
    ret.correct = appdata->load();
165
    if (!ret.correct) {
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
166
        qWarning() << "Could not open the AppStream metadata pool" << appdata->lastError();
167
168
    }

169
170
    const auto components = appdata->components();
    ret.components.reserve(components.size());
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
171
    foreach(const AppStream::Component& component, components) {
172
173
174
        if (component.kind() == AppStream::Component::KindFirmware)
            continue;

175
176
        const auto pkgNames = component.packageNames();
        if (pkgNames.isEmpty()) {
177
178
            const auto entries = component.launchable(AppStream::Launchable::KindDesktopId).entries();
            if (component.kind() == AppStream::Component::KindDesktopApp && !entries.isEmpty()) {
179
                const QString file = PackageKitBackend::locateService(entries.first());
180
                if (!file.isEmpty()) {
181
                    ret.missingComponents[file] = component;
182
183
                }
            }
184
185
        } else {
            ret.components << component;
186
        }
187
188
189
    }
    return ret;
}
190

191
192
193
194
195
void PackageKitBackend::reloadPackageList()
{
    acquireFetching(true);
    if (m_refresher) {
        disconnect(m_refresher.data(), &PackageKit::Transaction::finished, this, &PackageKitBackend::reloadPackageList);
196
    }
197

198
199
200
201
202
203
204
205
206
207
208
    m_appdata.reset(new AppStream::Pool);

    auto fw = new QFutureWatcher<DelayedAppStreamLoad>(this);
    connect(fw, &QFutureWatcher<DelayedAppStreamLoad>::finished, this, [this, fw]() {
        const auto data = fw->result();
        fw->deleteLater();

        if (!data.correct && m_packages.packages.isEmpty()) {
            QTimer::singleShot(0, this, [this]() {
                Q_EMIT passiveMessage(i18n("Please make sure that Appstream is properly set up on your system"));
            });
209
        }
210
211
212
213
214
        for (const auto &component: data.components) {
            const auto pkgNames = component.packageNames();
            addComponent(component, pkgNames);
        }

215
        if (data.components.isEmpty()) {
216
217
218
219
220
221
222
223
224
            qCDebug(LIBDISCOVER_BACKEND_LOG) << "empty appstream db";
            if (PackageKit::Daemon::backendName() == QLatin1String("aptcc") || PackageKit::Daemon::backendName().isEmpty()) {
                checkForUpdates();
            }
        }
        if (!m_appstreamInitialized) {
            m_appstreamInitialized = true;
            Q_EMIT loadedAppStream();
        }
225
        acquireFetching(false);
226
227
    });
    fw->setFuture(QtConcurrent::run(&m_threadPool, &loadAppStream, m_appdata.get()));
228
229
}

Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
230
AppPackageKitResource* PackageKitBackend::addComponent(const AppStream::Component& component, const QStringList& pkgNames)
231
232
233
234
{
    Q_ASSERT(isFetching());
    Q_ASSERT(!pkgNames.isEmpty());

235
236
    auto& resPos = m_packages.packages[component.id()];
    AppPackageKitResource* res = qobject_cast<AppPackageKitResource*>(resPos);
237
238
    if (!res) {
        res = new AppPackageKitResource(component, pkgNames.at(0), this);
239
        resPos = res;
240
241
242
    } else {
        res->clearPackageIds();
    }
243
244
245
246
247
248
249
250
251
252
    foreach (const QString& pkg, pkgNames) {
        m_packages.packageToApp[pkg] += component.id();
    }

    foreach (const QString& pkg, component.extends()) {
        m_packages.extendedBy[pkg] += res;
    }
    return res;
}

253
254
void PackageKitBackend::resolvePackages(const QStringList &packageNames)
{
255
256
257
258
259
260
261
    if (!m_resolveTransaction) {
        m_resolveTransaction = new PKResolveTransaction(this);
        connect(m_resolveTransaction, &PKResolveTransaction::allFinished, this, &PackageKitBackend::getPackagesFinished);
        connect(m_resolveTransaction, &PKResolveTransaction::started, this, [this] {
            m_resolveTransaction = nullptr;
        });
    }
262

263
    m_resolveTransaction->addPackageNames(packageNames);
264
265
266
267
}

void PackageKitBackend::fetchUpdates()
{
268
269
270
    if (m_updater->isProgressing())
        return;

271
272
273
274
275
    m_getUpdatesTransaction = PackageKit::Daemon::getUpdates();
    connect(m_getUpdatesTransaction, &PackageKit::Transaction::finished, this, &PackageKitBackend::getUpdatesFinished);
    connect(m_getUpdatesTransaction, &PackageKit::Transaction::package, this, &PackageKitBackend::addPackageToUpdate);
    connect(m_getUpdatesTransaction, &PackageKit::Transaction::errorCode, this, &PackageKitBackend::transactionError);
    connect(m_getUpdatesTransaction, &PackageKit::Transaction::percentageChanged, this, &PackageKitBackend::fetchingUpdatesProgressChanged);
276
    m_updatesPackageId.clear();
277
    m_hasSecurityUpdates = false;
278
279

    m_updater->setProgressing(true);
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
280
281

    Q_EMIT fetchingUpdatesProgressChanged();
282
283
}

284
285
286
287
void PackageKitBackend::addPackageArch(PackageKit::Transaction::Info info, const QString& packageId, const QString& summary)
{
    addPackage(info, packageId, summary, true);
}
288

289
290
291
292
293
294
void PackageKitBackend::addPackageNotArch(PackageKit::Transaction::Info info, const QString& packageId, const QString& summary)
{
    addPackage(info, packageId, summary, false);
}

void PackageKitBackend::addPackage(PackageKit::Transaction::Info info, const QString &packageId, const QString &summary, bool arch)
295
{
296
297
298
299
300
301
    if(PackageKit::Daemon::packageArch(packageId) == QLatin1String("source")) {
        // We do not add source packages, they make little sense here. If source is needed,
        // we are going to have to consider that in some other way, some other time
        // If we do not ignore them here, e.g. openSuse entirely fails at installing applications
        return;
    }
302
    const QString packageName = PackageKit::Daemon::packageName(packageId);
303
    QSet<AbstractResource*> r = resourcesByPackageName(packageName);
304
    if (r.isEmpty()) {
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
305
306
        auto pk = new PackageKitResource(packageName, summary, this);
        r = { pk };
307
        m_packagesToAdd.insert(pk);
308
    }
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
309
    foreach(auto res, r)
310
        static_cast<PackageKitResource*>(res)->addPackageId(info, packageId, arch);
311
312
}

313
void PackageKitBackend::getPackagesFinished()
314
{
315
316
    includePackagesToAdd();
}
317

318
319
void PackageKitBackend::includePackagesToAdd()
{
320
    if (m_packagesToAdd.isEmpty() && m_packagesToDelete.isEmpty())
321
322
323
324
325
326
        return;

    acquireFetching(true);
    foreach(PackageKitResource* res, m_packagesToAdd) {
        m_packages.packages[res->packageName()] = res;
    }
327
    foreach(PackageKitResource* res, m_packagesToDelete) {
328
329
        const auto pkgs = m_packages.packageToApp.value(res->packageName(), {res->packageName()});
        foreach(const auto &pkg, pkgs) {
330
            auto res = m_packages.packages.take(pkg);
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
331
            if (res) {
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
332
                if (AppPackageKitResource* ares = qobject_cast<AppPackageKitResource*>(res)) {
333
334
                    const auto extends = res->extends();
                    for(const auto &ext: extends)
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
335
336
337
                        m_packages.extendedBy[ext].removeAll(ares);
                }

Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
338
339
340
                emit resourceRemoved(res);
                res->deleteLater();
            }
341
        }
342
343
344
    }
    m_packagesToAdd.clear();
    m_packagesToDelete.clear();
345
    acquireFetching(false);
346
347
348
349
350
}

void PackageKitBackend::transactionError(PackageKit::Transaction::Error, const QString& message)
{
    qWarning() << "Transaction error: " << message << sender();
351
    Q_EMIT passiveMessage(message);
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
352
353
}

354
355
void PackageKitBackend::packageDetails(const PackageKit::Details& details)
{
356
    const QSet<AbstractResource*> resources = resourcesByPackageName(PackageKit::Daemon::packageName(details.packageId()));
357
358
359
    if (resources.isEmpty())
        qWarning() << "couldn't find package for" << details.packageId();

360
    foreach(AbstractResource* res, resources) {
361
        qobject_cast<PackageKitResource*>(res)->setDetails(details);
362
    }
363
364
}

365
QSet<AbstractResource*> PackageKitBackend::resourcesByPackageName(const QString& name) const
366
{
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
367
    return resourcesByPackageNames<QSet<AbstractResource*>>({name});
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
368
369
}

370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
template <typename T>
T PackageKitBackend::resourcesByPackageNames(const QStringList &pkgnames) const
{
    T ret;
    ret.reserve(pkgnames.size());
    for(const QString &name : pkgnames) {
        const QStringList names = m_packages.packageToApp.value(name, QStringList(name));
        foreach(const QString& name, names) {
            AbstractResource* res = m_packages.packages.value(name);
            if (res)
                ret += res;
        }
    }
    return ret;
}

386
void PackageKitBackend::checkForUpdates()
387
{
388
    if (PackageKit::Daemon::global()->offline()->updateTriggered()) {
389
        qCDebug(LIBDISCOVER_BACKEND_LOG) << "Won't be checking for updates again, the system needs a reboot to apply the fetched offline updates.";
390
391
392
        return;
    }

393
    if (!m_refresher) {
394
        acquireFetching(true);
395
        m_refresher = PackageKit::Daemon::refreshCache(false);
396

397
        connect(m_refresher.data(), &PackageKit::Transaction::errorCode, this, &PackageKitBackend::transactionError);
398
        connect(m_refresher.data(), &PackageKit::Transaction::finished, this, [this]() {
399
            m_refresher = nullptr;
400
            fetchUpdates();
401
402
            acquireFetching(false);
        });
403
    } else {
404
        qWarning() << "already resetting";
405
    }
406
407
}

408
409
QList<AppStream::Component> PackageKitBackend::componentsById(const QString& id) const
{
410
    Q_ASSERT(m_appstreamInitialized);
411
    return m_appdata->componentsById(id);
412
413
}

414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
static const auto needsResolveFilter = [] (AbstractResource* res) { return res->state() == AbstractResource::Broken; };
static const auto installedFilter = [] (AbstractResource* res) { return res->state() >= AbstractResource::Installed; };

class PKResultsStream : public ResultsStream
{
public:
    PKResultsStream(PackageKitBackend* backend, const QString &name)
        : ResultsStream(name)
        , backend(backend)
    {}

    PKResultsStream(PackageKitBackend* backend, const QString &name, const QVector<AbstractResource*> &resources)
        : ResultsStream(name)
        , backend(backend)
    {
        QTimer::singleShot(0, this, [resources, this] () {
            if (!resources.isEmpty())
                setResources(resources);
            finish();
        });
    }

    void setResources(const QVector<AbstractResource*> &res)
    {
        const auto toResolve = kFilter<QVector<AbstractResource*>>(res, needsResolveFilter);
        if (!toResolve.isEmpty())
            backend->resolvePackages(kTransform<QStringList>(toResolve, [] (AbstractResource* res) { return res->packageName(); }));
        Q_EMIT resourcesFound(res);
    }
private:
    PackageKitBackend* const backend;
};

Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
447
ResultsStream* PackageKitBackend::search(const AbstractResourcesBackend::Filters& filter)
448
{
449
450
    if (!filter.resourceUrl.isEmpty()) {
        return findResourceByPackageName(filter.resourceUrl);
451
    } else if (!filter.extends.isEmpty()) {
452
453
454
455
        auto stream = new PKResultsStream(this, QStringLiteral("PackageKitStream-extends"));
        auto f = [this, filter, stream] {
            const auto resources = kTransform<QVector<AbstractResource*>>(m_packages.extendedBy.value(filter.extends), [](AppPackageKitResource* a){ return a; });
            if (!resources.isEmpty()) {
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
456
                stream->setResources(resources);
457
458
459
460
            }
        };
        runWhenInitialized(f, stream);
        return stream;
461
462
463
464
    } else if (filter.state == AbstractResource::Upgradeable) {
        return new ResultsStream(QStringLiteral("PackageKitStream-upgradeable"), kTransform<QVector<AbstractResource*>>(upgradeablePackages())); //No need for it to be a PKResultsStream
    } else if (filter.state == AbstractResource::Installed) {
        auto stream = new PKResultsStream(this, QStringLiteral("PackageKitStream-installed"));
465
        auto f = [this, stream, filter] {
466
467
            const auto toResolve = kFilter<QVector<AbstractResource*>>(m_packages.packages, needsResolveFilter);

468
469
470
            auto installedAndNameFilter = [filter] (AbstractResource *res) {
                return res->state() >= AbstractResource::Installed && (res->name().contains(filter.search) || res->packageName() == filter.search);
            };
471
472
            if (!toResolve.isEmpty()) {
                resolvePackages(kTransform<QStringList>(toResolve, [] (AbstractResource* res) { return res->packageName(); }));
473
474
                connect(m_resolveTransaction, &PKResolveTransaction::allFinished, this, [stream, toResolve, installedAndNameFilter] {
                    const auto resolved = kFilter<QVector<AbstractResource*>>(toResolve, installedAndNameFilter);
475
476
477
478
479
480
                    if (!resolved.isEmpty())
                        Q_EMIT stream->resourcesFound(resolved);
                    stream->finish();
                });
            }

481
            const auto resolved = kFilter<QVector<AbstractResource*>>(m_packages.packages, installedAndNameFilter);
482
483
484
485
486
487
488
489
490
491
            if (!resolved.isEmpty()) {
                QTimer::singleShot(0, this, [resolved, toResolve, stream] () {
                    if (!resolved.isEmpty())
                        Q_EMIT stream->resourcesFound(resolved);

                    if (toResolve.isEmpty())
                        stream->finish();
            });
            }
        };
492
        runWhenInitialized(f, stream);
493
        return stream;
494
    } else if (filter.search.isEmpty()) {
495
496
497
498
        auto stream = new PKResultsStream(this, QStringLiteral("PackageKitStream-all"));
        auto f = [this, filter, stream] {
            auto resources = kFilter<QVector<AbstractResource*>>(m_packages.packages, [](AbstractResource* res) { return res->type() != AbstractResource::Technical && !qobject_cast<PackageKitResource*>(res)->extendsItself(); });
            if (!resources.isEmpty()) {
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
499
                stream->setResources(resources);
500
501
            }
        };
502
        runWhenInitialized(f, stream);
503
        return stream;
504
    } else {
505
        auto stream = new PKResultsStream(this, QStringLiteral("PackageKitStream-search"));
506
507
508
509
510
        const auto f = [this, stream, filter] () {
            const QList<AppStream::Component> components = m_appdata->search(filter.search);
            const QStringList ids = kTransform<QStringList>(components, [](const AppStream::Component& comp) { return comp.id(); });
            if (!ids.isEmpty()) {
                const auto resources = kFilter<QVector<AbstractResource*>>(resourcesByPackageNames<QVector<AbstractResource*>>(ids), [](AbstractResource* res){ return !qobject_cast<PackageKitResource*>(res)->extendsItself(); });
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
511
                stream->setResources(resources);
512
            }
513

514
515
516
517
518
519
520
521
522
523
524
            PackageKit::Transaction * tArch = PackageKit::Daemon::resolve(filter.search, PackageKit::Transaction::FilterArch);
            connect(tArch, &PackageKit::Transaction::package, this, &PackageKitBackend::addPackageArch);
            connect(tArch, &PackageKit::Transaction::package, stream, [stream](PackageKit::Transaction::Info /*info*/, const QString &packageId){
                stream->setProperty("packageId", packageId);
            });
            connect(tArch, &PackageKit::Transaction::finished, stream, [stream, ids, this](PackageKit::Transaction::Exit status) {
                getPackagesFinished();
                if (status == PackageKit::Transaction::Exit::ExitSuccess) {
                    const auto packageId = stream->property("packageId");
                    if (!packageId.isNull()) {
                        const auto res = resourcesByPackageNames<QVector<AbstractResource*>>({PackageKit::Daemon::packageName(packageId.toString())});
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
525
                        stream->setResources(kFilter<QVector<AbstractResource*>>(res, [ids](AbstractResource* res){ return !ids.contains(res->appstreamId()); }));
526
                    }
527
                }
528
529
530
                stream->finish();
            }, Qt::QueuedConnection);
        };
531
        runWhenInitialized(f, stream);
532
        return stream;
533
    }
534
535
}

536
537
538
539
540
541
542
543
544
void PackageKitBackend::runWhenInitialized(const std::function<void ()>& f, QObject* stream)
{
    if (!m_appstreamInitialized) {
        connect(this, &PackageKitBackend::loadedAppStream, stream, f);
    } else {
        QTimer::singleShot(0, this, f);
    }
}

545
PKResultsStream * PackageKitBackend::findResourceByPackageName(const QUrl& url)
546
{
547
548
549
    if (url.isLocalFile()) {
        QMimeDatabase db;
        const auto mime = db.mimeTypeForUrl(url);
Laurent Montel's avatar
Laurent Montel committed
550
551
552
        if (    mime.inherits(QStringLiteral("application/vnd.debian.binary-package"))
            || mime.inherits(QStringLiteral("application/x-rpm"))
            || mime.inherits(QStringLiteral("application/x-tar"))
553
            || mime.inherits(QStringLiteral("application/x-zstd-compressed-tar"))
Laurent Montel's avatar
Laurent Montel committed
554
            || mime.inherits(QStringLiteral("application/x-xz-compressed-tar"))
555
        ) {
556
            return new PKResultsStream(this, QStringLiteral("PackageKitStream-localpkg"), { new LocalFilePKResource(url, this)});
557
        }
558
    } else if (url.scheme() == QLatin1String("appstream")) {
559
560
561
562
563
564
565
566
567
568
        static const QMap<QString, QString> deprecatedAppstreamIds = {
            { QStringLiteral("org.kde.krita.desktop"), QStringLiteral("krita.desktop") },
            { QStringLiteral("org.kde.digikam.desktop"), QStringLiteral("digikam.desktop") },
            { QStringLiteral("org.kde.ktorrent.desktop"), QStringLiteral("ktorrent.desktop") },
            { QStringLiteral("org.kde.gcompris.desktop"), QStringLiteral("gcompris.desktop") },
            { QStringLiteral("org.kde.kmymoney.desktop"), QStringLiteral("kmymoney.desktop") },
            { QStringLiteral("org.kde.kolourpaint.desktop"), QStringLiteral("kolourpaint.desktop") },
            { QStringLiteral("org.blender.blender.desktop"), QStringLiteral("blender.desktop") },
        };
        
569
570
        const auto appstreamIds = AppStreamUtils::appstreamIds(url);
        if (appstreamIds.isEmpty())
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
571
            Q_EMIT passiveMessage(i18n("Malformed appstream url '%1'", url.toDisplayString()));
572
        else {
573
            auto stream = new PKResultsStream(this, QStringLiteral("PackageKitStream-appstream-url"));
574
            const auto f = [this, appstreamIds, stream] () {
575
                AbstractResource* pkg = nullptr;
576

577
578
579
580
581
582
583
584
                QStringList allAppStreamIds = appstreamIds;
                {
                    auto it = deprecatedAppstreamIds.constFind(appstreamIds.first());
                    if (it != deprecatedAppstreamIds.constEnd()) {
                        allAppStreamIds << *it;
                    }
                }

585
                for (auto it = m_packages.packages.constBegin(), itEnd = m_packages.packages.constEnd(); it != itEnd; ++it) {
586
                    const bool matches = kContains(allAppStreamIds, [&it] (const QString& id) {
587
                        static const QLatin1String desktopPostfix(".desktop");
588
                        return it.key().compare(id, Qt::CaseInsensitive) == 0 ||
589
590
                              //doing (id == id.key()+".desktop") without allocating
                              (id.size() == (desktopPostfix.size() + it.key().size()) && id.endsWith(desktopPostfix) && id.startsWith(it.key(), Qt::CaseInsensitive));
591
592
                    });
                    if (matches) {
593
594
595
                        pkg = it.value();
                        break;
                    }
596
                }
597
                if (pkg)
598
                    stream->setResources({pkg});
599
600
601
602
                stream->finish();
    //             if (!pkg)
    //                 qCDebug(LIBDISCOVER_BACKEND_LOG) << "could not find" << host << deprecatedHost;
            };
603
            runWhenInitialized(f, stream);
604
            return stream;
605
        }
606
    }
607
    return new PKResultsStream(this, QStringLiteral("PackageKitStream-unknown-url"), {});
608
609
}

610
611
612
613
614
bool PackageKitBackend::hasSecurityUpdates() const
{
    return m_hasSecurityUpdates;
}

615
int PackageKitBackend::updatesCount() const
616
{
617
618
619
    if (PackageKit::Daemon::global()->offline()->updateTriggered())
        return 0;

620
621
    int ret = 0;
    QSet<QString> packages;
622
623
    const auto toUpgrade = upgradeablePackages();
    for(auto res: toUpgrade) {
624
625
626
627
628
629
630
631
        const auto packageName = res->packageName();
        if (packages.contains(packageName)) {
            continue;
        }
        packages.insert(packageName);
        ret += 1;
    }
    return ret;
632
633
}

634
Transaction* PackageKitBackend::installApplication(AbstractResource* app, const AddonList& addons)
635
{
636
    Transaction* t = nullptr;
637
638
    if(!addons.addonsToInstall().isEmpty()) {
        QVector<AbstractResource*> appsToInstall = resourcesByPackageNames<QVector<AbstractResource*>>(addons.addonsToInstall());
639
640
        if(!app->isInstalled())
            appsToInstall << app;
641
        t = new PKTransaction(appsToInstall, Transaction::ChangeAddonsRole);
642
643
    } else if (!app->isInstalled())
        t = installApplication(app);
644
645

    if (!addons.addonsToRemove().isEmpty()) {
646
        const auto appsToRemove = resourcesByPackageNames<QVector<AbstractResource*>>(addons.addonsToRemove());
647
        t = new PKTransaction(appsToRemove, Transaction::RemoveRole);
648
    }
649

650
    return t;
651
652
}

653
Transaction* PackageKitBackend::installApplication(AbstractResource* app)
654
{
655
    return new PKTransaction({app}, Transaction::InstallRole);
656
657
}

658
Transaction* PackageKitBackend::removeApplication(AbstractResource* app)
659
{
660
    Q_ASSERT(!isFetching());
661
662
663
664
    if (!qobject_cast<PackageKitResource*>(app)) {
        Q_EMIT passiveMessage(i18n("Cannot remove '%1'", app->name()));
        return nullptr;
    }
665
    return new PKTransaction({app}, Transaction::RemoveRole);
666
667
}

Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
668
QSet<AbstractResource*> PackageKitBackend::upgradeablePackages() const
669
{
670
671
672
673
    if (isFetching() || !m_packagesToAdd.isEmpty()) {
        return {};
    }

Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
674
    QSet<AbstractResource*> ret;
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
675
    ret.reserve(m_updatesPackageId.size());
676
    Q_FOREACH (const QString& pkgid, m_updatesPackageId) {
677
        const QString pkgname = PackageKit::Daemon::packageName(pkgid);
678
        const auto pkgs = resourcesByPackageName(pkgname);
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
679
        if (pkgs.isEmpty()) {
680
681
            qWarning() << "couldn't find resource for" << pkgid;
        }
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
682
        ret.unite(pkgs);
683
    }
684
    return kFilter<QSet<AbstractResource*>>(ret, [] (AbstractResource* res) { return !static_cast<PackageKitResource*>(res)->extendsItself(); });
685
686
}

687
688
void PackageKitBackend::addPackageToUpdate(PackageKit::Transaction::Info info, const QString& packageId, const QString& summary)
{
689
690
    if (info == PackageKit::Transaction::InfoBlocked) {
        return;
691
    }
692

693
694
695
696
697
    if (info == PackageKit::Transaction::InfoRemoving || info == PackageKit::Transaction::InfoObsoleting) {
        // Don't try updating packages which need to be removed
        return;
    }

698
699
700
701
702
    if (info == PackageKit::Transaction::InfoSecurity)
        m_hasSecurityUpdates = true;

    m_updatesPackageId += packageId;
    addPackage(info, packageId, summary, true);
703
704
705
706
}

void PackageKitBackend::getUpdatesFinished(PackageKit::Transaction::Exit, uint)
{
707
    if (!m_updatesPackageId.isEmpty()) {
708
        resolvePackages(kTransform<QStringList>(m_updatesPackageId, [](const QString &pkgid) { return PackageKit::Daemon::packageName(pkgid); }));
709
    }
710

711
712
    m_updater->setProgressing(false);

713
    includePackagesToAdd();
714
715
716
717
718
719
720
    if (isFetching()) {
        auto a = new OneTimeAction([this] {
            emit updatesCountChanged();
        }, this);
        connect(this, &PackageKitBackend::available, a, &OneTimeAction::trigger);
    } else
        emit updatesCountChanged();
721
722
}

723
bool PackageKitBackend::isPackageNameUpgradeable(const PackageKitResource* res) const
724
{
725
726
727
    return !upgradeablePackageId(res).isEmpty();
}

Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
728
729
730
731
732
733
734
735
736
737
738
// Copy of Transaction::packageName that doesn't create a copy but just pass a reference
// It's an optimisation as there's a bunch of allocations that happen from packageName
// Having packageName return a QStringRef or a QStringView would fix this issue.
// TODO Qt 6: Have packageName and similars return a QStringView
static QStringRef TransactionpackageName(const QString &packageID)
{
    QStringRef ret;
    ret = packageID.leftRef(packageID.indexOf(QLatin1Char(';')));
    return ret;
}

739
QString PackageKitBackend::upgradeablePackageId(const PackageKitResource* res) const
740
{
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
741
742
743
    const QString name = res->packageName();
    for (const QString& pkgid : m_updatesPackageId) {
        if (TransactionpackageName(pkgid) == name)
744
            return pkgid;
745
    }
746
    return QString();
747
748
}

749
void PackageKitBackend::fetchDetails(const QSet<QString>& pkgid)
750
{
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
751
    if (!m_delayedDetailsFetch.isActive()) {
752
        m_delayedDetailsFetch.start();
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
753
    }
754
755
756
757
758
759

    m_packageNamesToFetchDetails += pkgid;
}

void PackageKitBackend::performDetailsFetch()
{
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
760
    Q_ASSERT(!m_packageNamesToFetchDetails.isEmpty());
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
761
    const auto ids = m_packageNamesToFetchDetails.values();
762
763

    PackageKit::Transaction* transaction = PackageKit::Daemon::getDetails(ids);
764
765
    connect(transaction, &PackageKit::Transaction::details, this, &PackageKitBackend::packageDetails);
    connect(transaction, &PackageKit::Transaction::errorCode, this, &PackageKitBackend::transactionError);
766
    m_packageNamesToFetchDetails.clear();
767
768
}

769
770
771
772
void PackageKitBackend::checkDaemonRunning()
{
    if (!PackageKit::Daemon::isRunning()) {
        qWarning() << "PackageKit stopped running!";
773
774
    } else
        updateProxy();
775
776
}

777
AbstractBackendUpdater* PackageKitBackend::backendUpdater() const
778
{
779
    return m_updater;
780
781
}

782
783
784
785
QVector<AppPackageKitResource*> PackageKitBackend::extendedBy(const QString& id) const
{
    return m_packages.extendedBy[id];
}
786

787
788
AbstractReviewsBackend* PackageKitBackend::reviewsBackend() const
{
789
    return m_reviews.data();
790
}
791

792
793
QString PackageKitBackend::displayName() const
{
794
    return AppStreamIntegration::global()->osRelease()->prettyName();
795
}
796

797
798
799
800
801
802
803
804
805
806
807
808
809
int PackageKitBackend::fetchingUpdatesProgress() const
{
    if (!m_getUpdatesTransaction)
        return 0;
    
    if (m_getUpdatesTransaction->status() == PackageKit::Transaction::StatusWait || m_getUpdatesTransaction->status() == PackageKit::Transaction::StatusUnknown) {
        return m_getUpdatesTransaction->property("lastPercentage").toInt();
    }
    int percentage = percentageWithStatus(m_getUpdatesTransaction->status(), m_getUpdatesTransaction->percentage());
    m_getUpdatesTransaction->setProperty("lastPercentage", percentage);
    return percentage;
}

810
#include "PackageKitBackend.moc"