File indexing completed on 2024-04-14 03:54:10

0001 /*
0002     SPDX-FileCopyrightText: 2016 Dan Leinir Turthra Jensen <admin@leinir.dk>
0003 
0004     SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0005 */
0006 
0007 #include "quickitemsmodel.h"
0008 #include "core/commentsmodel.h"
0009 #include "downloadlinkinfo.h"
0010 #include "itemsmodel.h"
0011 #include "quickengine.h"
0012 
0013 #include <KShell>
0014 #include <QProcess>
0015 
0016 class ItemsModelPrivate
0017 {
0018 public:
0019     ItemsModelPrivate(ItemsModel *qq)
0020         : q(qq)
0021         , model(nullptr)
0022         , engine(nullptr)
0023     {
0024     }
0025     ItemsModel *q;
0026     KNSCore::ItemsModel *model;
0027     Engine *engine;
0028 
0029     QHash<QString, KNSCore::CommentsModel *> commentsModels;
0030 
0031     bool initModel()
0032     {
0033         if (model) {
0034             return true;
0035         }
0036         if (!engine) {
0037             return false;
0038         }
0039         model = new KNSCore::ItemsModel(engine, q);
0040 
0041         q->connect(engine, &Engine::signalProvidersLoaded, engine, &Engine::reloadEntries);
0042         // Entries have been fetched and should be shown:
0043         q->connect(engine, &Engine::signalEntriesLoaded, model, [this](const KNSCore::Entry::List &entries) {
0044             model->slotEntriesLoaded(entries);
0045         });
0046         q->connect(engine, &Engine::entryEvent, model, [this](const KNSCore::Entry &entry, KNSCore::Entry::EntryEvent event) {
0047             if (event == KNSCore::Entry::DetailsLoadedEvent && engine->filter() != KNSCore::Provider::Updates) {
0048                 model->slotEntriesLoaded(KNSCore::Entry::List{entry});
0049             }
0050         });
0051 
0052         // Check if we need intermediate states
0053         q->connect(engine, &Engine::entryEvent, q, [this](const KNSCore::Entry &entry, KNSCore::Entry::EntryEvent event) {
0054             onEntryEvent(entry, event);
0055         });
0056         q->connect(engine, &Engine::signalResetView, model, &KNSCore::ItemsModel::clearEntries);
0057 
0058         q->connect(model, &KNSCore::ItemsModel::loadPreview, engine, &Engine::loadPreview);
0059         q->connect(engine, &Engine::entryPreviewLoaded, model, &KNSCore::ItemsModel::slotEntryPreviewLoaded);
0060 
0061         q->connect(model, &KNSCore::ItemsModel::rowsInserted, q, &ItemsModel::rowsInserted);
0062         q->connect(model, &KNSCore::ItemsModel::rowsRemoved, q, &ItemsModel::rowsRemoved);
0063         q->connect(model, &KNSCore::ItemsModel::dataChanged, q, &ItemsModel::dataChanged);
0064         q->connect(model, &KNSCore::ItemsModel::modelAboutToBeReset, q, &ItemsModel::modelAboutToBeReset);
0065         q->connect(model, &KNSCore::ItemsModel::modelReset, q, &ItemsModel::modelReset);
0066         return true;
0067     }
0068 
0069     void onEntryEvent(const KNSCore::Entry &entry, KNSCore::Entry::EntryEvent event)
0070     {
0071         if (event == KNSCore::Entry::StatusChangedEvent) {
0072             model->slotEntryChanged(entry);
0073             Q_EMIT q->entryChanged(entry);
0074 
0075             // If we update/uninstall an entry we have to update the UI, see BUG: 425135
0076             if (engine->filter() == KNSCore::Provider::Updates && entry.status() != KNSCore::Entry::Updateable && entry.status() != KNSCore::Entry::Updating) {
0077                 model->removeEntry(entry);
0078             } else if (engine->filter() == KNSCore::Provider::Installed && entry.status() == KNSCore::Entry::Deleted) {
0079                 model->removeEntry(entry);
0080             }
0081         }
0082 
0083         if (event == KNSCore::Entry::DetailsLoadedEvent) {
0084             model->slotEntryChanged(entry);
0085             Q_EMIT q->entryChanged(entry);
0086         }
0087     }
0088 };
0089 
0090 ItemsModel::ItemsModel(QObject *parent)
0091     : QAbstractListModel(parent)
0092     , d(new ItemsModelPrivate(this))
0093 {
0094 }
0095 
0096 ItemsModel::~ItemsModel() = default;
0097 
0098 QHash<int, QByteArray> ItemsModel::roleNames() const
0099 {
0100     static const QHash<int, QByteArray> roles = QHash<int, QByteArray>{
0101         {Qt::DisplayRole, "display"},
0102         {NameRole, "name"},
0103         {UniqueIdRole, "uniqueId"},
0104         {CategoryRole, "category"},
0105         {HomepageRole, "homepage"},
0106         {AuthorRole, "author"},
0107         {LicenseRole, "license"},
0108         {ShortSummaryRole, "shortSummary"},
0109         {SummaryRole, "summary"},
0110         {ChangelogRole, "changelog"},
0111         {VersionRole, "version"},
0112         {ReleaseDateRole, "releaseDate"},
0113         {UpdateVersionRole, "updateVersion"},
0114         {UpdateReleaseDateRole, "updateReleaseDate"},
0115         {PayloadRole, "payload"},
0116         {Qt::DecorationRole, "decoration"},
0117         {PreviewsSmallRole, "previewsSmall"},
0118         {PreviewsRole, "previews"},
0119         {InstalledFilesRole, "installedFiles"},
0120         {UnInstalledFilesRole, "uninstalledFiles"},
0121         {RatingRole, "rating"},
0122         {NumberOfCommentsRole, "numberOfComments"},
0123         {DownloadCountRole, "downloadCount"},
0124         {NumberFansRole, "numberFans"},
0125         {NumberKnowledgebaseEntriesRole, "numberKnowledgebaseEntries"},
0126         {KnowledgebaseLinkRole, "knowledgebaseLink"},
0127         {DownloadLinksRole, "downloadLinks"},
0128         {DonationLinkRole, "donationLink"},
0129         {ProviderIdRole, "providerId"},
0130         {SourceRole, "source"},
0131         {EntryRole, "entry"},
0132     };
0133     return roles;
0134 }
0135 
0136 int ItemsModel::rowCount(const QModelIndex &parent) const
0137 {
0138     if (parent.isValid()) {
0139         return 0;
0140     }
0141     if (d->initModel()) {
0142         return d->model->rowCount(QModelIndex());
0143     }
0144     return 0;
0145 }
0146 
0147 QVariant ItemsModel::data(const QModelIndex &index, int role) const
0148 {
0149     if (index.isValid() && d->initModel()) {
0150         KNSCore::Entry entry = d->model->data(d->model->index(index.row()), Qt::UserRole).value<KNSCore::Entry>();
0151         switch (role) {
0152         case NameRole:
0153         case Qt::DisplayRole:
0154             return entry.name();
0155         case EntryRole:
0156             return QVariant::fromValue(entry);
0157         case UniqueIdRole:
0158             return entry.uniqueId();
0159         case CategoryRole:
0160             return entry.category();
0161         case HomepageRole:
0162             return entry.homepage();
0163             break;
0164         case AuthorRole: {
0165             KNSCore::Author author = entry.author();
0166             QVariantMap returnAuthor;
0167             returnAuthor[QStringLiteral("id")] = author.id();
0168             returnAuthor[QStringLiteral("name")] = author.name();
0169             returnAuthor[QStringLiteral("email")] = author.email();
0170             returnAuthor[QStringLiteral("homepage")] = author.homepage();
0171             returnAuthor[QStringLiteral("jabber")] = author.jabber();
0172             returnAuthor[QStringLiteral("avatarUrl")] = author.avatarUrl();
0173             returnAuthor[QStringLiteral("description")] = author.description();
0174             return returnAuthor;
0175         } break;
0176         case LicenseRole:
0177             return entry.license();
0178         case ShortSummaryRole:
0179             return entry.shortSummary();
0180         case SummaryRole:
0181             return entry.summary();
0182         case ChangelogRole:
0183             return entry.changelog();
0184         case VersionRole:
0185             return entry.version();
0186         case ReleaseDateRole:
0187             return entry.releaseDate();
0188         case UpdateVersionRole:
0189             return entry.updateVersion();
0190         case UpdateReleaseDateRole:
0191             return entry.updateReleaseDate();
0192         case PayloadRole:
0193             return entry.payload();
0194         case Qt::DecorationRole:
0195             return entry.previewUrl(KNSCore::Entry::PreviewSmall1);
0196         case PreviewsSmallRole: {
0197             QStringList previews;
0198             previews << entry.previewUrl(KNSCore::Entry::PreviewSmall1);
0199             previews << entry.previewUrl(KNSCore::Entry::PreviewSmall2);
0200             previews << entry.previewUrl(KNSCore::Entry::PreviewSmall3);
0201             while (!previews.isEmpty() && previews.last().isEmpty()) {
0202                 previews.takeLast();
0203             }
0204             return previews;
0205         }
0206         case PreviewsRole: {
0207             QStringList previews;
0208             previews << entry.previewUrl(KNSCore::Entry::PreviewBig1);
0209             previews << entry.previewUrl(KNSCore::Entry::PreviewBig2);
0210             previews << entry.previewUrl(KNSCore::Entry::PreviewBig3);
0211             while (!previews.isEmpty() && previews.last().isEmpty()) {
0212                 previews.takeLast();
0213             }
0214             return previews;
0215         }
0216         case InstalledFilesRole:
0217             return entry.installedFiles();
0218         case UnInstalledFilesRole:
0219             return entry.uninstalledFiles();
0220         case RatingRole:
0221             return entry.rating();
0222         case NumberOfCommentsRole:
0223             return entry.numberOfComments();
0224         case DownloadCountRole:
0225             return entry.downloadCount();
0226         case NumberFansRole:
0227             return entry.numberFans();
0228         case NumberKnowledgebaseEntriesRole:
0229             return entry.numberKnowledgebaseEntries();
0230         case KnowledgebaseLinkRole:
0231             return entry.knowledgebaseLink();
0232         case DownloadLinksRole: {
0233             // This would be good to cache... but it also needs marking as dirty, somehow...
0234             const QList<KNSCore::Entry::DownloadLinkInformation> dllinks = entry.downloadLinkInformationList();
0235             QVariantList list;
0236             for (const KNSCore::Entry::DownloadLinkInformation &link : dllinks) {
0237                 list.append(QVariant::fromValue(DownloadLinkInfo(link)));
0238             }
0239             if (list.isEmpty() && !entry.payload().isEmpty()) {
0240                 KNSCore::Entry::DownloadLinkInformation data;
0241                 data.descriptionLink = entry.payload();
0242                 list.append(QVariant::fromValue(DownloadLinkInfo(data)));
0243             }
0244             return QVariant::fromValue(list);
0245         }
0246         case DonationLinkRole:
0247             return entry.donationLink();
0248         case ProviderIdRole:
0249             return entry.providerId();
0250         case SourceRole: {
0251             KNSCore::Entry::Source src = entry.source();
0252             switch (src) {
0253             case KNSCore::Entry::Cache:
0254                 return QStringLiteral("Cache");
0255             case KNSCore::Entry::Online:
0256                 return QStringLiteral("Online");
0257             case KNSCore::Entry::Registry:
0258                 return QStringLiteral("Registry");
0259             default:
0260                 return QStringLiteral("Unknown source - shouldn't be possible");
0261             }
0262         }
0263         case CommentsModelRole: {
0264             KNSCore::CommentsModel *commentsModel{nullptr};
0265             if (!d->commentsModels.contains(entry.uniqueId())) {
0266                 commentsModel = new KNSCore::CommentsModel(d->engine);
0267                 commentsModel->setEntry(entry);
0268                 d->commentsModels[entry.uniqueId()] = commentsModel;
0269             } else {
0270                 commentsModel = d->commentsModels[entry.uniqueId()];
0271             }
0272             return QVariant::fromValue(commentsModel);
0273         }
0274         default:
0275             return QStringLiteral("Unknown role");
0276         }
0277     }
0278     return QVariant();
0279 }
0280 
0281 bool ItemsModel::canFetchMore(const QModelIndex &parent) const
0282 {
0283     return !parent.isValid() && d->engine && d->engine->categoriesMetadata().count() > 0;
0284 }
0285 
0286 void ItemsModel::fetchMore(const QModelIndex &parent)
0287 {
0288     if (parent.isValid() || !d->engine) {
0289         return;
0290     }
0291     d->engine->requestMoreData();
0292 }
0293 
0294 QObject *ItemsModel::engine() const
0295 {
0296     return d->engine;
0297 }
0298 
0299 void ItemsModel::setEngine(QObject *newEngine)
0300 {
0301     if (d->engine != newEngine) {
0302         beginResetModel();
0303         d->engine = qobject_cast<Engine *>(newEngine);
0304         if (d->model) {
0305             d->model->deleteLater();
0306             d->model = nullptr;
0307         }
0308         Q_EMIT engineChanged();
0309         endResetModel();
0310     }
0311 }
0312 
0313 int ItemsModel::indexOfEntryId(const QString &providerId, const QString &entryId)
0314 {
0315     int idx{-1};
0316     if (d->engine && d->model) {
0317         for (int i = 0; i < rowCount(); ++i) {
0318             KNSCore::Entry testEntry = d->model->data(d->model->index(i), Qt::UserRole).value<KNSCore::Entry>();
0319             if (providerId == testEntry.providerId() && entryId == testEntry.uniqueId()) {
0320                 idx = i;
0321                 break;
0322             }
0323         }
0324     }
0325     return idx;
0326 }
0327 
0328 #include "moc_quickitemsmodel.cpp"