ResourcesModel.cpp 12.7 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
/***************************************************************************
 *   Copyright © 2012 Aleix Pol Gonzalez <aleixpol@blue-systems.com>       *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or         *
 *   modify it under the terms of the GNU General Public License as        *
 *   published by the Free Software Foundation; either version 2 of        *
 *   the License or (at your option) version 3 or any later version        *
 *   accepted by the membership of KDE e.V. (or its successor approved     *
 *   by the membership of KDE e.V.), which shall act as a proxy            *
 *   defined in Section 14 of version 3 of the license.                    *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>. *
 ***************************************************************************/

#include "ResourcesModel.h"

#include "AbstractResource.h"
Jonathan Thomas's avatar
Jonathan Thomas committed
24
#include "resources/AbstractResourcesBackend.h"
25
#include "resources/AbstractBackendUpdater.h"
26 27
#include <ReviewsBackend/Rating.h>
#include <ReviewsBackend/AbstractReviewsBackend.h>
28
#include <Transaction/Transaction.h>
29
#include <DiscoverBackendsFactory.h>
Jonathan Thomas's avatar
Jonathan Thomas committed
30
#include "Transaction/TransactionModel.h"
31
#include "Category/CategoryModel.h"
32
#include "utils.h"
33
#include <QDebug>
34 35
#include <QCoreApplication>
#include <QThread>
36
#include <QAction>
37
#include <QMetaProperty>
38
#include <KLocalizedString>
39 40
#include <KSharedConfig>
#include <KConfigGroup>
41

42
ResourcesModel *ResourcesModel::s_self = nullptr;
43 44 45

ResourcesModel *ResourcesModel::global()
{
46 47
    if(!s_self)
        s_self = new ResourcesModel;
48
    return s_self;
49 50
}

51
ResourcesModel::ResourcesModel(QObject* parent, bool load)
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
52
    : QObject(parent)
53
    , m_initializingBackends(0)
54
    , m_isFetching(false)
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
55
    , m_currentApplicationBackend(nullptr)
56 57
{
    init(load);
58
    connect(this, &ResourcesModel::allInitialized, this, &ResourcesModel::slotFetching);
59
    connect(this, &ResourcesModel::backendsChanged, this, &ResourcesModel::initApplicationsBackend);
60 61 62
}

void ResourcesModel::init(bool load)
63
{
64 65
    Q_ASSERT(!s_self);
    Q_ASSERT(QCoreApplication::instance()->thread()==QThread::currentThread());
66

67 68
    if(load)
        QMetaObject::invokeMethod(this, "registerAllBackends", Qt::QueuedConnection);
69 70 71 72 73 74


    QAction* updateAction = new QAction(this);
    updateAction->setIcon(QIcon::fromTheme(QStringLiteral("system-software-update")));
    updateAction->setText(i18nc("@action Checks the Internet for updates", "Check for Updates"));
    updateAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_R));
75 76
    connect(this, &ResourcesModel::fetchingChanged, updateAction, [updateAction](bool fetching) {
        updateAction->setEnabled(!fetching);
77 78 79 80
    });
    connect(updateAction, &QAction::triggered, this, &ResourcesModel::checkForUpdates);

    m_ownActions += updateAction;
81 82 83
}

ResourcesModel::ResourcesModel(const QString& backendName, QObject* parent)
84
    : ResourcesModel(parent, false)
85
{
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
86
    s_self = this;
87
    registerBackendByName(backendName);
88 89
}

90 91 92 93 94
ResourcesModel::~ResourcesModel()
{
    qDeleteAll(m_backends);
}

Jonathan Thomas's avatar
Jonathan Thomas committed
95
void ResourcesModel::addResourcesBackend(AbstractResourcesBackend* backend)
96
{
Jonathan Thomas's avatar
Jonathan Thomas committed
97
    Q_ASSERT(!m_backends.contains(backend));
98 99
    if(!backend->isValid()) {
        qWarning() << "Discarding invalid backend" << backend->name();
100
        CategoryModel::global()->blacklistPlugin(backend->name());
101
        backend->deleteLater();
102 103 104
        return;
    }

Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
105
    m_backends += backend;
106
    if(!backend->isFetching()) {
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
107 108
        if (backend->updatesCount() > 0)
            emit updatesCountChanged();
109
    } else {
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
110
        m_initializingBackends++;
111
    }
112

Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
113 114
    connect(backend, &AbstractResourcesBackend::fetchingChanged, this, &ResourcesModel::callerFetchingChanged);
    connect(backend, &AbstractResourcesBackend::allDataChanged, this, &ResourcesModel::updateCaller);
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
115
    connect(backend, &AbstractResourcesBackend::resourcesChanged, this, &ResourcesModel::resourceDataChanged);
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
116
    connect(backend, &AbstractResourcesBackend::updatesCountChanged, this, &ResourcesModel::updatesCountChanged);
117
    connect(backend, &AbstractResourcesBackend::resourceRemoved, this, &ResourcesModel::resourceRemoved);
118
    connect(backend, &AbstractResourcesBackend::passiveMessage, this, &ResourcesModel::passiveMessage);
119
    connect(backend->backendUpdater(), &AbstractBackendUpdater::progressingChanged, this, &ResourcesModel::slotFetching);
120

121 122
    if(m_initializingBackends==0)
        emit allInitialized();
123
    else
124
        slotFetching();
125 126
}

127
void ResourcesModel::callerFetchingChanged()
128 129
{
    AbstractResourcesBackend* backend = qobject_cast<AbstractResourcesBackend*>(sender());
130 131 132 133 134 135

    if (!backend->isValid()) {
        qWarning() << "Discarding invalid backend" << backend->name();
        int idx = m_backends.indexOf(backend);
        Q_ASSERT(idx>=0);
        m_backends.removeAt(idx);
136
//         Q_EMIT backendsChanged();
137
        CategoryModel::global()->blacklistPlugin(backend->name());
138
        backend->deleteLater();
139 140 141
        return;
    }

142
    if(backend->isFetching()) {
143
        m_initializingBackends++;
144
        slotFetching();
145
    } else {
146 147 148 149
        m_initializingBackends--;
        if(m_initializingBackends==0)
            emit allInitialized();
        else
150
            slotFetching();
151 152 153
    }
}

154
void ResourcesModel::updateCaller(const QVector<QByteArray>& properties)
155 156 157
{
    AbstractResourcesBackend* backend = qobject_cast<AbstractResourcesBackend*>(sender());
    
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
158
    Q_EMIT backendDataChanged(backend, properties);
159 160
}

161 162 163 164
QVector< AbstractResourcesBackend* > ResourcesModel::backends() const
{
    return m_backends;
}
165

166 167 168
int ResourcesModel::updatesCount() const
{
    int ret = 0;
Jonathan Thomas's avatar
Jonathan Thomas committed
169

170 171 172
    foreach(AbstractResourcesBackend* backend, m_backends) {
        ret += backend->updatesCount();
    }
Jonathan Thomas's avatar
Jonathan Thomas committed
173

174 175
    return ret;
}
176 177 178

void ResourcesModel::installApplication(AbstractResource* app)
{
179
    TransactionModel::global()->addTransaction(app->backend()->installApplication(app));
180 181
}

182
void ResourcesModel::installApplication(AbstractResource* app, const AddonList& addons)
183
{
184
    TransactionModel::global()->addTransaction(app->backend()->installApplication(app, addons));
185 186 187 188
}

void ResourcesModel::removeApplication(AbstractResource* app)
{
189
    TransactionModel::global()->addTransaction(app->backend()->removeApplication(app));
190 191
}

192 193
void ResourcesModel::registerAllBackends()
{
194
    DiscoverBackendsFactory f;
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
195
    const auto backends = f.allBackends();
196
    if(m_initializingBackends==0 && backends.isEmpty()) {
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
197
        qWarning() << "Couldn't find any backends";
198 199 200 201 202
        emit allInitialized();
    } else {
        foreach(AbstractResourcesBackend* b, backends) {
            addResourcesBackend(b);
        }
203
        emit backendsChanged();
204 205 206 207 208
    }
}

void ResourcesModel::registerBackendByName(const QString& name)
{
209
    DiscoverBackendsFactory f;
210 211
    for(auto b : f.backend(name))
        addResourcesBackend(b);
212 213

    emit backendsChanged();
214
}
215

216 217
bool ResourcesModel::isFetching() const
{
218 219 220 221 222 223
    return m_isFetching;
}

void ResourcesModel::slotFetching()
{
    bool newFetching = false;
224
    foreach(AbstractResourcesBackend* b, m_backends) {
225 226 227 228 229
        // 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.
        if(b->isFetching() || (b->backendUpdater() && b->backendUpdater()->isProgressing())) {
230 231
            newFetching = true;
            break;
232
        }
233
    }
234 235 236 237
    if (newFetching != m_isFetching) {
        m_isFetching = newFetching;
        Q_EMIT fetchingChanged(m_isFetching);
    }
238
}
239 240 241

QList<QAction*> ResourcesModel::messageActions() const
{
242
    QList<QAction*> ret = m_ownActions;
243 244 245
    foreach(AbstractResourcesBackend* b, m_backends) {
        ret += b->messageActions();
    }
246
    Q_ASSERT(!ret.contains(nullptr));
247 248
    return ret;
}
249 250 251

bool ResourcesModel::isBusy() const
{
252
    return TransactionModel::global()->rowCount() > 0;
253
}
254 255 256

bool ResourcesModel::isExtended(const QString& id)
{
257 258 259 260 261 262
    bool ret = true;
    foreach (AbstractResourcesBackend* backend, m_backends) {
        ret = backend->extends().contains(id);
        if (ret)
            break;
    }
263 264
    return ret;
}
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
265 266 267 268

AggregatedResultsStream::AggregatedResultsStream(const QSet<ResultsStream*>& streams)
    : ResultsStream(QStringLiteral("AggregatedResultsStream"))
{
269
    Q_ASSERT(!streams.contains(nullptr));
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
270 271
    if (streams.isEmpty()) {
        qWarning() << "no streams to aggregate!!";
272
        QTimer::singleShot(0, this, &AggregatedResultsStream::clear);
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
273 274 275
    }

    for (auto stream: streams) {
276
        connect(stream, &ResultsStream::resourcesFound, this, &AggregatedResultsStream::addResults);
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
277 278 279
        connect(stream, &QObject::destroyed, this, &AggregatedResultsStream::destruction);
        m_streams << stream;
    }
280 281 282 283 284 285 286

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

void AggregatedResultsStream::addResults(const QVector<AbstractResource *>& res)
{
287
    m_results += res;
288

289
    m_delayedEmission.start();
290 291 292 293 294 295 296 297
}

void AggregatedResultsStream::emitResults()
{
    if (!m_results.isEmpty()) {
        Q_EMIT resourcesFound(m_results);
        m_results.clear();
    }
298
    m_delayedEmission.setInterval(m_delayedEmission.interval() + 100);
299
    m_delayedEmission.stop();
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
300 301 302 303 304
}

void AggregatedResultsStream::destruction(QObject* obj)
{
    m_streams.remove(obj);
305 306 307 308 309
    clear();
}

void AggregatedResultsStream::clear()
{
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
310
    if (m_streams.isEmpty()) {
311
        emitResults();
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
312 313 314 315 316
        Q_EMIT finished();
        deleteLater();
    }
}

317
AggregatedResultsStream * ResourcesModel::findResourceByPackageName(const QUrl& search)
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
318 319 320 321 322 323 324 325 326 327 328 329
{
    QSet<ResultsStream*> streams;
    foreach(auto backend, m_backends) {
        streams << backend->findResourceByPackageName(search);
    }
    return new AggregatedResultsStream(streams);
}

AggregatedResultsStream* ResourcesModel::search(const AbstractResourcesBackend::Filters& search)
{
    QSet<ResultsStream*> streams;
    foreach(auto backend, m_backends) {
330
        streams << backend->search(search);
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
331 332 333
    }
    return new AggregatedResultsStream(streams);
}
334 335 336 337 338 339 340 341 342 343 344

AbstractResource* ResourcesModel::resourceForFile(const QUrl& file)
{
    AbstractResource* ret = nullptr;
    foreach(auto backend, m_backends) {
        ret = backend->resourceForFile(file);
        if (ret)
            break;
    }
    return ret;
}
345 346 347 348 349 350

void ResourcesModel::checkForUpdates()
{
    for(auto backend: qAsConst(m_backends))
        backend->checkForUpdates();
}
351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366

QVector<AbstractResourcesBackend *> ResourcesModel::applicationBackends() const
{
    return kFilter<QVector<AbstractResourcesBackend*>>(m_backends, [](AbstractResourcesBackend* b){ return b->hasApplications(); });
}

QVariantList ResourcesModel::applicationBackendsVariant() const
{
    return kTransform<QVariantList>(applicationBackends(), [](AbstractResourcesBackend* b) {return QVariant::fromValue<QObject*>(b);});
}

AbstractResourcesBackend* ResourcesModel::currentApplicationBackend() const
{
    return m_currentApplicationBackend;
}

367
void ResourcesModel::setCurrentApplicationBackend(AbstractResourcesBackend* backend, bool write)
368 369
{
    if (backend != m_currentApplicationBackend) {
370 371 372 373 374 375 376
        if (write) {
            KConfigGroup settings(KSharedConfig::openConfig(), "ResourcesModel");
            if (backend)
                settings.writeEntry("currentApplicationBackend", backend->name());
            else
                settings.deleteEntry("currentApplicationBackend");
        }
377 378 379 380 381 382 383 384 385 386 387 388 389 390

        qDebug() << "setting currentApplicationBackend" << backend;
        m_currentApplicationBackend = backend;
        Q_EMIT currentApplicationBackendChanged(backend);
    }
}

void ResourcesModel::initApplicationsBackend()
{
    KConfigGroup settings(KSharedConfig::openConfig(), "ResourcesModel");
    const QString name = settings.readEntry<QString>("currentApplicationBackend", QStringLiteral("packagekit-backend"));

    const auto backends = applicationBackends();
    auto idx = kIndexOf(backends, [name](AbstractResourcesBackend* b) { return b->name() == name; });
391 392 393 394 395
    if (idx<0) {
        idx = kIndexOf(backends, [](AbstractResourcesBackend* b) { return b->hasApplications(); });
        qDebug() << "falling back applications backend to" << idx;
    }
    setCurrentApplicationBackend(backends.value(idx, nullptr), false);
396
}