File indexing completed on 2024-05-05 17:33:20

0001 /*
0002  *   SPDX-FileCopyrightText: 2012 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 "AbstractResource.h"
0008 #include "AbstractResourcesBackend.h"
0009 #include "libdiscover_debug.h"
0010 #include <Category/CategoryModel.h>
0011 #include <KFormat>
0012 #include <KLocalizedString>
0013 #include <KShell>
0014 #include <QList>
0015 #include <QProcess>
0016 #include <QString>
0017 #include <ReviewsBackend/AbstractReviewsBackend.h>
0018 #include <ReviewsBackend/Rating.h>
0019 
0020 AbstractResource::AbstractResource(AbstractResourcesBackend *parent)
0021     : QObject(parent)
0022 {
0023     connect(this, &AbstractResource::stateChanged, this, &AbstractResource::sizeChanged);
0024     connect(this, &AbstractResource::stateChanged, this, &AbstractResource::versionsChanged);
0025     connect(this, &AbstractResource::stateChanged, this, &AbstractResource::reportNewState);
0026 }
0027 
0028 AbstractResource::~AbstractResource() = default;
0029 
0030 QUrl AbstractResource::homepage()
0031 {
0032     return QUrl();
0033 }
0034 
0035 QUrl AbstractResource::helpURL()
0036 {
0037     return QUrl();
0038 }
0039 
0040 QUrl AbstractResource::bugURL()
0041 {
0042     return QUrl();
0043 }
0044 
0045 QUrl AbstractResource::donationURL()
0046 {
0047     return QUrl();
0048 }
0049 
0050 QUrl AbstractResource::contributeURL()
0051 {
0052     return {};
0053 }
0054 
0055 void AbstractResource::addMetadata(const QString &key, const QJsonValue &value)
0056 {
0057     m_metadata.insert(key, value);
0058 }
0059 
0060 QJsonValue AbstractResource::getMetadata(const QString &key)
0061 {
0062     return m_metadata.value(key);
0063 }
0064 
0065 bool AbstractResource::canUpgrade()
0066 {
0067     return state() == Upgradeable;
0068 }
0069 
0070 bool AbstractResource::isInstalled()
0071 {
0072     return state() >= Installed;
0073 }
0074 
0075 void AbstractResource::fetchScreenshots()
0076 {
0077     Q_EMIT screenshotsFetched({});
0078 }
0079 
0080 QStringList AbstractResource::mimetypes() const
0081 {
0082     return QStringList();
0083 }
0084 
0085 AbstractResourcesBackend *AbstractResource::backend() const
0086 {
0087     return static_cast<AbstractResourcesBackend *>(parent());
0088 }
0089 
0090 QObject *AbstractResource::backendObject() const
0091 {
0092     return parent();
0093 }
0094 
0095 QString AbstractResource::status()
0096 {
0097     switch (state()) {
0098     case Broken:
0099         return i18n("Broken");
0100     case None:
0101         return i18n("Available");
0102     case Installed:
0103         return i18n("Installed");
0104     case Upgradeable:
0105         return i18n("Upgradeable");
0106     }
0107     return QString();
0108 }
0109 
0110 QString AbstractResource::sizeDescription()
0111 {
0112     return KFormat().formatByteSize(size());
0113 }
0114 
0115 QCollatorSortKey AbstractResource::nameSortKey()
0116 {
0117     if (!m_collatorKey) {
0118         m_collatorKey.reset(new QCollatorSortKey(QCollator().sortKey(name())));
0119     }
0120     return *m_collatorKey;
0121 }
0122 
0123 Rating *AbstractResource::rating() const
0124 {
0125     AbstractReviewsBackend *ratings = backend()->reviewsBackend();
0126     return ratings ? ratings->ratingForApplication(const_cast<AbstractResource *>(this)) : nullptr;
0127 }
0128 
0129 QVariant AbstractResource::ratingVariant() const
0130 {
0131     auto instance = rating();
0132     return instance ? QVariant::fromValue<Rating>(*instance) : QVariant();
0133 }
0134 
0135 QStringList AbstractResource::extends() const
0136 {
0137     return {};
0138 }
0139 
0140 QString AbstractResource::appstreamId() const
0141 {
0142     return {};
0143 }
0144 
0145 void AbstractResource::reportNewState()
0146 {
0147     if (backend()->isFetching())
0148         return;
0149 
0150     static const QVector<QByteArray> ns = {"state", "status", "canUpgrade", "size", "sizeDescription", "installedVersion", "availableVersion"};
0151     Q_EMIT backend()->resourcesChanged(this, ns);
0152 }
0153 
0154 static bool shouldFilter(AbstractResource *res, const CategoryFilter &filter)
0155 {
0156     bool ret = true;
0157     switch (filter.type) {
0158     case CategoryFilter::CategoryNameFilter:
0159         ret = res->categories().contains(std::get<QString>(filter.value));
0160         break;
0161     case CategoryFilter::PkgSectionFilter:
0162         ret = res->section() == std::get<QString>(filter.value);
0163         break;
0164     case CategoryFilter::PkgWildcardFilter: {
0165         QString wildcard = std::get<QString>(filter.value);
0166         wildcard.remove(QLatin1Char('*'));
0167         ret = res->packageName().contains(wildcard);
0168     } break;
0169     case CategoryFilter::AppstreamIdWildcardFilter: {
0170         QString wildcard = std::get<QString>(filter.value);
0171         wildcard.remove(QLatin1Char('*'));
0172         ret = res->appstreamId().contains(wildcard);
0173     } break;
0174     case CategoryFilter::PkgNameFilter: // Only useful in the not filters
0175         ret = res->packageName() == std::get<QString>(filter.value);
0176         break;
0177     case CategoryFilter::AndFilter: {
0178         const auto filters = std::get<QVector<CategoryFilter>>(filter.value);
0179         ret = std::all_of(filters.begin(), filters.end(), [res](const CategoryFilter &f) {
0180             return shouldFilter(res, f);
0181         });
0182         break;
0183     }
0184     case CategoryFilter::OrFilter: {
0185         const auto filters = std::get<QVector<CategoryFilter>>(filter.value);
0186         ret = std::any_of(filters.begin(), filters.end(), [res](const CategoryFilter &f) {
0187             return shouldFilter(res, f);
0188         });
0189         break;
0190     }
0191     case CategoryFilter::NotFilter: {
0192         const auto filters = std::get<QVector<CategoryFilter>>(filter.value);
0193         ret = !std::any_of(filters.begin(), filters.end(), [res](const CategoryFilter &f) {
0194             return shouldFilter(res, f);
0195         });
0196         break;
0197     }
0198     }
0199     return ret;
0200 }
0201 
0202 bool AbstractResource::categoryMatches(Category *cat)
0203 {
0204     return shouldFilter(this, cat->filter());
0205 }
0206 
0207 static QSet<Category *> walkCategories(AbstractResource *res, const QVector<Category *> &cats)
0208 {
0209     QSet<Category *> ret;
0210     for (Category *cat : cats) {
0211         if (res->categoryMatches(cat)) {
0212             const auto subcats = walkCategories(res, cat->subCategories());
0213             if (subcats.isEmpty()) {
0214                 ret += cat;
0215             } else {
0216                 ret += subcats;
0217             }
0218         }
0219     }
0220 
0221     return ret;
0222 }
0223 
0224 QSet<Category *> AbstractResource::categoryObjects(const QVector<Category *> &cats) const
0225 {
0226     return walkCategories(const_cast<AbstractResource *>(this), cats);
0227 }
0228 
0229 QUrl AbstractResource::url() const
0230 {
0231     const QString asid = appstreamId();
0232     return asid.isEmpty() ? QUrl(backend()->name() + QStringLiteral("://") + packageName()) : QUrl(QStringLiteral("appstream://") + asid);
0233 }
0234 
0235 QString AbstractResource::displayOrigin() const
0236 {
0237     return origin();
0238 }
0239 
0240 QString AbstractResource::executeLabel() const
0241 {
0242     return i18n("Launch");
0243 }
0244 
0245 QString AbstractResource::upgradeText() const
0246 {
0247     QString installed = installedVersion(), available = availableVersion();
0248     if (installed == available) {
0249         // Update of the same version; show when old and new are
0250         // the same (common with Flatpak runtimes)
0251         return i18nc("@info 'Refresh' is used as a noun here, and %1 is an app's version number", "Refresh of version %1", available);
0252     } else if (!installed.isEmpty() && !available.isEmpty()) {
0253         // Old and new version numbers
0254         // This thing with \u009C is a fancy feature in QML text handling:
0255         // when the string will be elided, it shows the string after
0256         // the last \u009C. This allows us to show a smaller string
0257         // when there's now enough room
0258 
0259         // All of this is mostly for the benefit of KDE Neon users,
0260         // since the version strings there are really really long
0261         return i18nc("Do not translate or alter \\u009C", "%1 → %2\u009C%1 → %2\u009C%2", installed, available);
0262     } else {
0263         // Available version only, for when the installed version
0264         // isn't available for some reason
0265         return available;
0266     }
0267 }
0268 
0269 QString AbstractResource::versionString()
0270 {
0271     const QString version = isInstalled() ? installedVersion() : availableVersion();
0272     if (version.isEmpty()) {
0273         return {};
0274     } else {
0275         QLocale l;
0276         const QString releaseString = l.toString(releaseDate(), QLocale::ShortFormat);
0277         if (!releaseString.isEmpty()) {
0278             return i18n("%1, released on %2", version, releaseString);
0279         } else {
0280             return version;
0281         }
0282     }
0283 }
0284 
0285 QString AbstractResource::contentRatingDescription() const
0286 {
0287     return {};
0288 }
0289 
0290 AbstractResource::ContentIntensity AbstractResource::contentRatingIntensity() const
0291 {
0292     return Mild;
0293 }
0294 
0295 QString AbstractResource::contentRatingText() const
0296 {
0297     return {};
0298 }
0299 
0300 uint AbstractResource::contentRatingMinimumAge() const
0301 {
0302     return 0;
0303 }