ResourcesModel.cpp 13.5 KB
Newer Older
1
2
3
4
5
/*
 *   SPDX-FileCopyrightText: 2012 Aleix Pol Gonzalez <aleixpol@blue-systems.com>
 *
 *   SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
 */
6
7
8
9

#include "ResourcesModel.h"

#include "AbstractResource.h"
10
#include "Category/CategoryModel.h"
Alexander Lohnau's avatar
Alexander Lohnau committed
11
#include "Transaction/TransactionModel.h"
Laurent Montel's avatar
Laurent Montel committed
12
#include "libdiscover_debug.h"
Alexander Lohnau's avatar
Alexander Lohnau committed
13
14
15
16
17
18
#include "resources/AbstractBackendUpdater.h"
#include "resources/AbstractResourcesBackend.h"
#include "utils.h"
#include <DiscoverBackendsFactory.h>
#include <KConfigGroup>
#include <KLocalizedString>
19
#include <KOSRelease>
Alexander Lohnau's avatar
Alexander Lohnau committed
20
#include <KSharedConfig>
21
#include <QCoreApplication>
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
22
#include <QIcon>
Alexander Lohnau's avatar
Alexander Lohnau committed
23
#include <QMetaProperty>
24
#include <QThread>
Alexander Lohnau's avatar
Alexander Lohnau committed
25
26
27
28
#include <ReviewsBackend/AbstractReviewsBackend.h>
#include <ReviewsBackend/Rating.h>
#include <Transaction/Transaction.h>
#include <functional>
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
29
#include <resources/DiscoverAction.h>
30

31
ResourcesModel *ResourcesModel::s_self = nullptr;
32
33
34

ResourcesModel *ResourcesModel::global()
{
Alexander Lohnau's avatar
Alexander Lohnau committed
35
    if (!s_self)
36
        s_self = new ResourcesModel;
37
    return s_self;
38
39
}

Alexander Lohnau's avatar
Alexander Lohnau committed
40
ResourcesModel::ResourcesModel(QObject *parent, bool load)
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
41
    : QObject(parent)
42
    , m_isFetching(false)
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
43
    , m_initializingBackends(0)
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
44
    , m_currentApplicationBackend(nullptr)
45
    , m_allInitializedEmitter(new QTimer(this))
Alexander Lohnau's avatar
Alexander Lohnau committed
46
47
48
49
50
    , m_updatesCount(
          0,
          [this] {
              {
                  int ret = 0;
Jonah Brüchert's avatar
Jonah Brüchert committed
51
                  for (AbstractResourcesBackend *backend : qAsConst(m_backends)) {
Alexander Lohnau's avatar
Alexander Lohnau committed
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
                      ret += backend->updatesCount();
                  }
                  return ret;
              }
          },
          [this](int count) {
              Q_EMIT updatesCountChanged(count);
          })
    , m_fetchingUpdatesProgress(
          0,
          [this] {
              {
                  if (m_backends.isEmpty())
                      return 0;

                  int sum = 0;
                  for (auto backend : qAsConst(m_backends)) {
                      sum += backend->fetchingUpdatesProgress();
                  }
71
                  return sum / (int)m_backends.count();
Alexander Lohnau's avatar
Alexander Lohnau committed
72
73
74
75
76
              }
          },
          [this](int progress) {
              Q_EMIT fetchingUpdatesProgressChanged(progress);
          })
77
78
{
    init(load);
79
    connect(this, &ResourcesModel::allInitialized, this, &ResourcesModel::slotFetching);
80
    connect(this, &ResourcesModel::backendsChanged, this, &ResourcesModel::initApplicationsBackend);
81
82
83
}

void ResourcesModel::init(bool load)
84
{
85
    Q_ASSERT(!s_self);
Alexander Lohnau's avatar
Alexander Lohnau committed
86
    Q_ASSERT(QCoreApplication::instance()->thread() == QThread::currentThread());
87

88
89
    m_allInitializedEmitter->setSingleShot(true);
    m_allInitializedEmitter->setInterval(0);
Alexander Lohnau's avatar
Alexander Lohnau committed
90
    connect(m_allInitializedEmitter, &QTimer::timeout, this, [this]() {
91
        if (m_initializingBackends == 0) {
92
            m_isInitializing = false;
93
            Q_EMIT allInitialized();
94
        }
95
96
    });

Alexander Lohnau's avatar
Alexander Lohnau committed
97
    if (load)
98
        QMetaObject::invokeMethod(this, "registerAllBackends", Qt::QueuedConnection);
99

Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
100
    m_updateAction = new DiscoverAction(this);
101
    m_updateAction->setIconName(QStringLiteral("system-software-update"));
102
    m_updateAction->setText(i18n("Refresh"));
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
103
104
    connect(this, &ResourcesModel::fetchingChanged, m_updateAction, [this](bool fetching) {
        m_updateAction->setEnabled(!fetching);
105
        m_fetchingUpdatesProgress.reevaluate();
106
    });
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
107
    connect(m_updateAction, &DiscoverAction::triggered, this, &ResourcesModel::checkForUpdates);
108
109

    connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, &QObject::deleteLater);
110
111
}

Alexander Lohnau's avatar
Alexander Lohnau committed
112
ResourcesModel::ResourcesModel(const QString &backendName, QObject *parent)
113
    : ResourcesModel(parent, false)
114
{
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
115
    s_self = this;
116
    registerBackendByName(backendName);
117
118
}

119
120
ResourcesModel::~ResourcesModel()
{
121
    s_self = nullptr;
122
123
124
    qDeleteAll(m_backends);
}

Alexander Lohnau's avatar
Alexander Lohnau committed
125
void ResourcesModel::addResourcesBackend(AbstractResourcesBackend *backend)
126
{
Jonathan Thomas's avatar
Jonathan Thomas committed
127
    Q_ASSERT(!m_backends.contains(backend));
Alexander Lohnau's avatar
Alexander Lohnau committed
128
    if (!backend->isValid()) {
Laurent Montel's avatar
Laurent Montel committed
129
        qCWarning(LIBDISCOVER_LOG) << "Discarding invalid backend" << backend->name();
130
        CategoryModel::global()->blacklistPlugin(backend->name());
131
        backend->deleteLater();
132
133
134
        return;
    }

Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
135
    m_backends += backend;
Alexander Lohnau's avatar
Alexander Lohnau committed
136
    if (!backend->isFetching()) {
137
        m_updatesCount.reevaluate();
138
    } else {
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
139
        m_initializingBackends++;
140
    }
141

Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
142
143
    connect(backend, &AbstractResourcesBackend::fetchingChanged, this, &ResourcesModel::callerFetchingChanged);
    connect(backend, &AbstractResourcesBackend::allDataChanged, this, &ResourcesModel::updateCaller);
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
144
    connect(backend, &AbstractResourcesBackend::resourcesChanged, this, &ResourcesModel::resourceDataChanged);
Alexander Lohnau's avatar
Alexander Lohnau committed
145
146
147
148
149
150
    connect(backend, &AbstractResourcesBackend::updatesCountChanged, this, [this] {
        m_updatesCount.reevaluate();
    });
    connect(backend, &AbstractResourcesBackend::fetchingUpdatesProgressChanged, this, [this] {
        m_fetchingUpdatesProgress.reevaluate();
    });
151
    connect(backend, &AbstractResourcesBackend::resourceRemoved, this, &ResourcesModel::resourceRemoved);
152
    connect(backend, &AbstractResourcesBackend::passiveMessage, this, &ResourcesModel::passiveMessage);
153
    connect(backend, &AbstractResourcesBackend::inlineMessage, this, &ResourcesModel::inlineMessage);
154
    connect(backend->backendUpdater(), &AbstractBackendUpdater::progressingChanged, this, &ResourcesModel::slotFetching);
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
155
156
157
    if (backend->reviewsBackend()) {
        connect(backend->reviewsBackend(), &AbstractReviewsBackend::error, this, &ResourcesModel::passiveMessage, Qt::UniqueConnection);
    }
158

159
160
161
162
    // In case this is in fact the first backend to be added, and also happens to be
    // pre-filled, we still need for the rest of the backends to be added before trying
    // to send out the initialized signal. To ensure this happens, schedule it for the
    // start of the next run of the event loop.
Alexander Lohnau's avatar
Alexander Lohnau committed
163
    if (m_initializingBackends == 0) {
164
        m_allInitializedEmitter->start();
165
    } else {
166
        slotFetching();
167
    }
168
169
}

170
void ResourcesModel::callerFetchingChanged()
171
{
Alexander Lohnau's avatar
Alexander Lohnau committed
172
    AbstractResourcesBackend *backend = qobject_cast<AbstractResourcesBackend *>(sender());
173
174

    if (!backend->isValid()) {
Laurent Montel's avatar
Laurent Montel committed
175
        qCWarning(LIBDISCOVER_LOG) << "Discarding invalid backend" << backend->name();
176
        int idx = m_backends.indexOf(backend);
Alexander Lohnau's avatar
Alexander Lohnau committed
177
        Q_ASSERT(idx >= 0);
178
        m_backends.removeAt(idx);
179
        Q_EMIT backendsChanged();
180
        CategoryModel::global()->blacklistPlugin(backend->name());
181
        backend->deleteLater();
182
        slotFetching();
183
184
185
        return;
    }

Alexander Lohnau's avatar
Alexander Lohnau committed
186
    if (backend->isFetching()) {
187
        m_initializingBackends++;
188
        slotFetching();
189
    } else {
190
        m_initializingBackends--;
Alexander Lohnau's avatar
Alexander Lohnau committed
191
        if (m_initializingBackends == 0)
192
            m_allInitializedEmitter->start();
193
        else
194
            slotFetching();
195
196
197
    }
}

Alexander Lohnau's avatar
Alexander Lohnau committed
198
void ResourcesModel::updateCaller(const QVector<QByteArray> &properties)
199
{
Alexander Lohnau's avatar
Alexander Lohnau committed
200
201
    AbstractResourcesBackend *backend = qobject_cast<AbstractResourcesBackend *>(sender());

Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
202
    Q_EMIT backendDataChanged(backend, properties);
203
204
}

Alexander Lohnau's avatar
Alexander Lohnau committed
205
QVector<AbstractResourcesBackend *> ResourcesModel::backends() const
206
207
208
{
    return m_backends;
}
209

210
211
212
213
bool ResourcesModel::hasSecurityUpdates() const
{
    bool ret = false;

Jonah Brüchert's avatar
Jonah Brüchert committed
214
    for (AbstractResourcesBackend *backend : qAsConst(m_backends)) {
215
216
217
218
219
220
        ret |= backend->hasSecurityUpdates();
    }

    return ret;
}

Alexander Lohnau's avatar
Alexander Lohnau committed
221
void ResourcesModel::installApplication(AbstractResource *app)
222
{
223
    TransactionModel::global()->addTransaction(app->backend()->installApplication(app));
224
225
}

Alexander Lohnau's avatar
Alexander Lohnau committed
226
void ResourcesModel::installApplication(AbstractResource *app, const AddonList &addons)
227
{
228
    TransactionModel::global()->addTransaction(app->backend()->installApplication(app, addons));
229
230
}

Alexander Lohnau's avatar
Alexander Lohnau committed
231
void ResourcesModel::removeApplication(AbstractResource *app)
232
{
233
    TransactionModel::global()->addTransaction(app->backend()->removeApplication(app));
234
235
}

236
237
void ResourcesModel::registerAllBackends()
{
238
    DiscoverBackendsFactory f;
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
239
    const auto backends = f.allBackends();
Alexander Lohnau's avatar
Alexander Lohnau committed
240
    if (m_initializingBackends == 0 && backends.isEmpty()) {
Laurent Montel's avatar
Laurent Montel committed
241
        qCWarning(LIBDISCOVER_LOG) << "Couldn't find any backends";
242
        m_allInitializedEmitter->start();
243
    } else {
Jonah Brüchert's avatar
Jonah Brüchert committed
244
        for (AbstractResourcesBackend *b : backends) {
245
246
            addResourcesBackend(b);
        }
247
        Q_EMIT backendsChanged();
248
249
250
    }
}

Alexander Lohnau's avatar
Alexander Lohnau committed
251
void ResourcesModel::registerBackendByName(const QString &name)
252
{
253
    DiscoverBackendsFactory f;
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
254
    const auto backends = f.backend(name);
Alexander Lohnau's avatar
Alexander Lohnau committed
255
    for (auto b : backends)
256
        addResourcesBackend(b);
257

258
    Q_EMIT backendsChanged();
259
}
260

261
262
bool ResourcesModel::isFetching() const
{
263
264
265
    return m_isFetching;
}

266
267
268
269
270
bool ResourcesModel::isInitializing() const
{
    return m_isInitializing;
}

271
272
273
void ResourcesModel::slotFetching()
{
    bool newFetching = false;
Jonah Brüchert's avatar
Jonah Brüchert committed
274
    for (AbstractResourcesBackend *b : qAsConst(m_backends)) {
275
276
277
278
        // isFetching should sort of be enough. However, sometimes the backend itself
        // will still be operating on things, which from a model point of view would
        // still mean something going on. So, interpret that as fetching as well, for
        // the purposes of this property.
Alexander Lohnau's avatar
Alexander Lohnau committed
279
        if (b->isFetching() || (b->backendUpdater() && b->backendUpdater()->isProgressing())) {
280
281
            newFetching = true;
            break;
282
        }
283
    }
284
285
286
287
    if (newFetching != m_isFetching) {
        m_isFetching = newFetching;
        Q_EMIT fetchingChanged(m_isFetching);
    }
288
}
289

290
291
bool ResourcesModel::isBusy() const
{
292
    return TransactionModel::global()->rowCount() > 0;
293
}
294

Alexander Lohnau's avatar
Alexander Lohnau committed
295
bool ResourcesModel::isExtended(const QString &id)
296
{
297
    bool ret = true;
Jonah Brüchert's avatar
Jonah Brüchert committed
298
    for (AbstractResourcesBackend *backend : qAsConst(m_backends)) {
299
300
301
302
        ret = backend->extends().contains(id);
        if (ret)
            break;
    }
303
304
    return ret;
}
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
305

Alexander Lohnau's avatar
Alexander Lohnau committed
306
AggregatedResultsStream::AggregatedResultsStream(const QSet<ResultsStream *> &streams)
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
307
308
    : ResultsStream(QStringLiteral("AggregatedResultsStream"))
{
309
    Q_ASSERT(!streams.contains(nullptr));
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
310
    if (streams.isEmpty()) {
Laurent Montel's avatar
Laurent Montel committed
311
        qCWarning(LIBDISCOVER_LOG) << "no streams to aggregate!!";
312
        QTimer::singleShot(0, this, &AggregatedResultsStream::clear);
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
313
314
    }

Alexander Lohnau's avatar
Alexander Lohnau committed
315
    for (auto stream : streams) {
316
        connect(stream, &ResultsStream::resourcesFound, this, &AggregatedResultsStream::addResults);
Aleix Pol Gonzalez's avatar
safety    
Aleix Pol Gonzalez committed
317
        connect(stream, &QObject::destroyed, this, &AggregatedResultsStream::streamDestruction);
318
        connect(this, &ResultsStream::fetchMore, stream, &ResultsStream::fetchMore);
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
319
320
        m_streams << stream;
    }
321
322
323
324
325

    m_delayedEmission.setInterval(0);
    connect(&m_delayedEmission, &QTimer::timeout, this, &AggregatedResultsStream::emitResults);
}

326
327
AggregatedResultsStream::~AggregatedResultsStream() = default;

Alexander Lohnau's avatar
Alexander Lohnau committed
328
void AggregatedResultsStream::addResults(const QVector<AbstractResource *> &res)
329
{
Alexander Lohnau's avatar
Alexander Lohnau committed
330
    for (auto r : res)
Aleix Pol Gonzalez's avatar
safety    
Aleix Pol Gonzalez committed
331
        connect(r, &QObject::destroyed, this, &AggregatedResultsStream::resourceDestruction);
332

333
    m_results += res;
334

335
    m_delayedEmission.start();
336
337
338
339
340
341
342
343
}

void AggregatedResultsStream::emitResults()
{
    if (!m_results.isEmpty()) {
        Q_EMIT resourcesFound(m_results);
        m_results.clear();
    }
344
    m_delayedEmission.setInterval(m_delayedEmission.interval() + 100);
345
    m_delayedEmission.stop();
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
346
347
}

Alexander Lohnau's avatar
Alexander Lohnau committed
348
void AggregatedResultsStream::resourceDestruction(QObject *obj)
Aleix Pol Gonzalez's avatar
safety    
Aleix Pol Gonzalez committed
349
{
Alexander Lohnau's avatar
Alexander Lohnau committed
350
    m_results.removeAll(qobject_cast<AbstractResource *>(obj));
Aleix Pol Gonzalez's avatar
safety    
Aleix Pol Gonzalez committed
351
352
}

Alexander Lohnau's avatar
Alexander Lohnau committed
353
void AggregatedResultsStream::streamDestruction(QObject *obj)
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
354
355
{
    m_streams.remove(obj);
356
357
358
359
360
    clear();
}

void AggregatedResultsStream::clear()
{
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
361
    if (m_streams.isEmpty()) {
362
        emitResults();
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
363
364
365
366
367
        Q_EMIT finished();
        deleteLater();
    }
}

Alexander Lohnau's avatar
Alexander Lohnau committed
368
AggregatedResultsStream *ResourcesModel::search(const AbstractResourcesBackend::Filters &search)
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
369
{
370
    if (search.isEmpty()) {
Alexander Lohnau's avatar
Alexander Lohnau committed
371
        return new AggregatedResultsStream({new ResultsStream(QStringLiteral("emptysearch"), {})});
372
    }
373

Alexander Lohnau's avatar
Alexander Lohnau committed
374
375
376
    auto streams = kTransform<QSet<ResultsStream *>>(m_backends, [search](AbstractResourcesBackend *backend) {
        return backend->search(search);
    });
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
377
378
    return new AggregatedResultsStream(streams);
}
379

380
381
void ResourcesModel::checkForUpdates()
{
Alexander Lohnau's avatar
Alexander Lohnau committed
382
    for (auto backend : qAsConst(m_backends))
383
384
        backend->checkForUpdates();
}
385

Alexander Lohnau's avatar
Alexander Lohnau committed
386
AbstractResourcesBackend *ResourcesModel::currentApplicationBackend() const
387
388
389
390
{
    return m_currentApplicationBackend;
}

Alexander Lohnau's avatar
Alexander Lohnau committed
391
void ResourcesModel::setCurrentApplicationBackend(AbstractResourcesBackend *backend, bool write)
392
393
{
    if (backend != m_currentApplicationBackend) {
394
395
396
397
398
399
400
        if (write) {
            KConfigGroup settings(KSharedConfig::openConfig(), "ResourcesModel");
            if (backend)
                settings.writeEntry("currentApplicationBackend", backend->name());
            else
                settings.deleteEntry("currentApplicationBackend");
        }
401

Laurent Montel's avatar
Laurent Montel committed
402
        qCDebug(LIBDISCOVER_LOG) << "setting currentApplicationBackend" << backend;
403
404
405
406
407
408
409
        m_currentApplicationBackend = backend;
        Q_EMIT currentApplicationBackendChanged(backend);
    }
}

void ResourcesModel::initApplicationsBackend()
{
410
    const auto name = applicationSourceName();
411

Alexander Lohnau's avatar
Alexander Lohnau committed
412
413
414
415
416
417
418
    auto idx = kIndexOf(m_backends, [name](AbstractResourcesBackend *b) {
        return b->hasApplications() && b->name() == name;
    });
    if (idx < 0) {
        idx = kIndexOf(m_backends, [](AbstractResourcesBackend *b) {
            return b->hasApplications();
        });
Laurent Montel's avatar
Laurent Montel committed
419
        qCDebug(LIBDISCOVER_LOG) << "falling back applications backend to" << idx;
420
    }
421
    setCurrentApplicationBackend(m_backends.value(idx, nullptr), false);
422
}
423

424
425
426
427
428
QString ResourcesModel::applicationSourceName() const
{
    KConfigGroup settings(KSharedConfig::openConfig(), "ResourcesModel");
    return settings.readEntry<QString>("currentApplicationBackend", QStringLiteral("packagekit-backend"));
}
429
430
431

QUrl ResourcesModel::distroBugReportUrl()
{
432
    return QUrl(KOSRelease().bugReportUrl());
433
}