File indexing completed on 2024-05-05 05:29:11

0001 /*
0002  *   SPDX-FileCopyrightText: 2016 Aleix Pol Gonzalez <aleixpol@blue-systems.com>
0003  *
0004  *   SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0005  */
0006 
0007 #include "AbstractAppsModel.h"
0008 
0009 #include "discover_debug.h"
0010 #include <KConfigGroup>
0011 #include <KIO/StoredTransferJob>
0012 #include <KSharedConfig>
0013 #include <QDir>
0014 #include <QFile>
0015 #include <QJsonArray>
0016 #include <QJsonDocument>
0017 #include <QStandardPaths>
0018 #include <QtGlobal>
0019 
0020 #include <libdiscover_debug.h>
0021 #include <resources/ResourcesModel.h>
0022 #include <resources/StoredResultsStream.h>
0023 #include <utils.h>
0024 
0025 class BestInResultsStream : public QObject
0026 {
0027     Q_OBJECT
0028 public:
0029     BestInResultsStream(const QSet<ResultsStream *> &streams)
0030         : QObject()
0031     {
0032         connect(this, &BestInResultsStream::finished, this, &QObject::deleteLater);
0033         Q_ASSERT(!streams.contains(nullptr));
0034         if (streams.isEmpty()) {
0035             QTimer::singleShot(0, this, &BestInResultsStream::clear);
0036         }
0037 
0038         for (auto stream : streams) {
0039             m_streams.insert(stream);
0040             connect(stream, &ResultsStream::resourcesFound, this, [this](const QVector<StreamResult> &resources) {
0041                 m_resources.append(resources.constFirst());
0042             });
0043             connect(stream, &QObject::destroyed, this, &BestInResultsStream::streamDestruction);
0044         }
0045     }
0046 
0047     void streamDestruction(QObject *obj)
0048     {
0049         m_streams.remove(obj);
0050         clear();
0051     }
0052 
0053     void clear()
0054     {
0055         if (m_streams.isEmpty()) {
0056             Q_EMIT finished(m_resources);
0057         }
0058     }
0059 
0060 Q_SIGNALS:
0061     void finished(QVector<StreamResult> resources);
0062 
0063 private:
0064     QVector<StreamResult> m_resources;
0065     QSet<QObject *> m_streams;
0066 };
0067 
0068 AbstractAppsModel::AbstractAppsModel()
0069 {
0070     connect(ResourcesModel::global(), &ResourcesModel::currentApplicationBackendChanged, this, &AbstractAppsModel::refreshCurrentApplicationBackend);
0071     refreshCurrentApplicationBackend();
0072 }
0073 
0074 void AbstractAppsModel::refreshCurrentApplicationBackend()
0075 {
0076     auto backend = ResourcesModel::global()->currentApplicationBackend();
0077     if (m_backend == backend)
0078         return;
0079 
0080     if (m_backend) {
0081         disconnect(m_backend, &AbstractResourcesBackend::fetchingChanged, this, &AbstractAppsModel::refresh);
0082         disconnect(m_backend, &AbstractResourcesBackend::resourceRemoved, this, &AbstractAppsModel::removeResource);
0083     }
0084 
0085     m_backend = backend;
0086 
0087     if (backend) {
0088         connect(backend, &AbstractResourcesBackend::fetchingChanged, this, &AbstractAppsModel::refresh);
0089         connect(backend, &AbstractResourcesBackend::resourceRemoved, this, &AbstractAppsModel::removeResource);
0090     }
0091 
0092     Q_EMIT currentApplicationBackendChanged(m_backend);
0093 }
0094 
0095 void AbstractAppsModel::setUris(const QVector<QUrl> &uris)
0096 {
0097     if (!m_backend)
0098         return;
0099 
0100     if (m_uris == uris) {
0101         return;
0102     }
0103     m_uris = uris;
0104 
0105     QSet<ResultsStream *> streams;
0106     for (const auto &uri : uris) {
0107         AbstractResourcesBackend::Filters filter;
0108         filter.resourceUrl = uri;
0109         streams << m_backend->search(filter);
0110     }
0111     if (!streams.isEmpty()) {
0112         auto stream = new BestInResultsStream(streams);
0113         acquireFetching(true);
0114         connect(stream, &BestInResultsStream::finished, this, &AbstractAppsModel::setResources);
0115     }
0116 }
0117 
0118 static void filterDupes(QVector<StreamResult> &resources)
0119 {
0120     QSet<QString> found;
0121     for (auto it = resources.begin(); it != resources.end();) {
0122         auto id = it->resource->appstreamId();
0123         if (found.contains(id)) {
0124             it = resources.erase(it);
0125         } else {
0126             found.insert(id);
0127             ++it;
0128         }
0129     }
0130 }
0131 
0132 void AbstractAppsModel::acquireFetching(bool f)
0133 {
0134     if (f)
0135         m_isFetching++;
0136     else
0137         m_isFetching--;
0138 
0139     if ((!f && m_isFetching == 0) || (f && m_isFetching == 1)) {
0140         Q_EMIT isFetchingChanged();
0141     }
0142     Q_ASSERT(m_isFetching >= 0);
0143 }
0144 
0145 void AbstractAppsModel::setResources(const QVector<StreamResult> &_resources)
0146 {
0147     auto resources = _resources;
0148     filterDupes(resources);
0149 
0150     if (m_resources != resources) {
0151         // TODO: sort like in the json files
0152         beginResetModel();
0153         m_resources = resources;
0154         endResetModel();
0155         Q_EMIT appsCountChanged();
0156     }
0157 
0158     acquireFetching(false);
0159 }
0160 
0161 void AbstractAppsModel::removeResource(AbstractResource *resource)
0162 {
0163     int index = m_resources.indexOf(resource);
0164     if (index < 0)
0165         return;
0166 
0167     beginRemoveRows({}, index, index);
0168     m_resources.removeAt(index);
0169     endRemoveRows();
0170 }
0171 
0172 QVariant AbstractAppsModel::data(const QModelIndex &index, int role) const
0173 {
0174     if (!index.isValid() || role != Qt::UserRole)
0175         return {};
0176 
0177     auto res = m_resources.value(index.row()).resource;
0178     if (!res)
0179         return {};
0180 
0181     return QVariant::fromValue<QObject *>(res);
0182 }
0183 
0184 int AbstractAppsModel::rowCount(const QModelIndex &parent) const
0185 {
0186     return parent.isValid() ? 0 : m_resources.count();
0187 }
0188 
0189 QHash<int, QByteArray> AbstractAppsModel::roleNames() const
0190 {
0191     return {{Qt::UserRole, "application"}};
0192 }
0193 
0194 #include "AbstractAppsModel.moc"