File indexing completed on 2024-05-19 09:21:26

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 "ReviewsBackend/ReviewsModel.h"
0008 #include "libdiscover_debug.h"
0009 #include <KConfigGroup>
0010 #include <KSharedConfig>
0011 #include <QtQml/qqmllist.h>
0012 #include <ReviewsBackend/AbstractReviewsBackend.h>
0013 #include <ReviewsBackend/Review.h>
0014 #include <ksharedconfig.h>
0015 #include <resources/AbstractResource.h>
0016 #include <resources/AbstractResourcesBackend.h>
0017 #include <resources/ResourcesModel.h>
0018 
0019 using namespace Qt::StringLiterals;
0020 
0021 int StarsCount::one() const
0022 {
0023     return m_one;
0024 }
0025 
0026 int StarsCount::two() const
0027 {
0028     return m_two;
0029 }
0030 
0031 int StarsCount::three() const
0032 {
0033     return m_three;
0034 }
0035 
0036 int StarsCount::four() const
0037 {
0038     return m_four;
0039 }
0040 
0041 int StarsCount::five() const
0042 {
0043     return m_five;
0044 }
0045 
0046 void StarsCount::addRating(int rating)
0047 {
0048     // Ratings are 1-10, but we show only 5 stars
0049     switch (int(std::ceil(qreal(rating) / 2.0))) {
0050     case 1:
0051         ++m_one;
0052         break;
0053     case 2:
0054         ++m_two;
0055         break;
0056     case 3:
0057         ++m_three;
0058         break;
0059     case 4:
0060         ++m_four;
0061         break;
0062     case 5:
0063         ++m_five;
0064         break;
0065     default:
0066         break;
0067     }
0068 }
0069 
0070 void StarsCount::clear()
0071 {
0072     m_one = 0;
0073     m_two = 0;
0074     m_three = 0;
0075     m_four = 0;
0076     m_five = 0;
0077 }
0078 
0079 ReviewsModel::ReviewsModel(QObject *parent)
0080     : QAbstractListModel(parent)
0081     , m_lastPage(0)
0082 {
0083     KSharedConfigPtr config = KSharedConfig::openConfig();
0084     KConfigGroup configGroup(config, u"Reviews"_s);
0085     const QString role = configGroup.readEntry("PreferredSortRole");
0086     if (QStringList({QStringLiteral("wilsonScore"), QStringLiteral("date"), QStringLiteral("rating")}).contains(role)) {
0087         m_preferredSortRole = role;
0088     } else {
0089         m_preferredSortRole = QStringLiteral("wilsonScore");
0090     }
0091 }
0092 
0093 ReviewsModel::~ReviewsModel() = default;
0094 
0095 QHash<int, QByteArray> ReviewsModel::roleNames() const
0096 {
0097     QHash<int, QByteArray> roles = QAbstractItemModel::roleNames();
0098     roles.insert(ShouldShow, "shouldShow");
0099     roles.insert(Reviewer, "reviewer");
0100     roles.insert(CreationDate, "date");
0101     roles.insert(UsefulnessTotal, "usefulnessTotal");
0102     roles.insert(UsefulnessFavorable, "usefulnessFavorable");
0103     roles.insert(WilsonScore, "wilsonScore");
0104     roles.insert(UsefulChoice, "usefulChoice");
0105     roles.insert(Rating, "rating");
0106     roles.insert(Summary, "summary");
0107     roles.insert(Depth, "depth");
0108     roles.insert(PackageVersion, "packageVersion");
0109     return roles;
0110 }
0111 
0112 QVariant ReviewsModel::data(const QModelIndex &index, int role) const
0113 {
0114     if (!index.isValid())
0115         return QVariant();
0116     switch (role) {
0117     case Qt::DisplayRole:
0118         return m_reviews.at(index.row())->reviewText();
0119     case ShouldShow:
0120         return m_reviews.at(index.row())->shouldShow();
0121     case Reviewer:
0122         return m_reviews.at(index.row())->reviewer();
0123     case CreationDate:
0124         return m_reviews.at(index.row())->creationDate();
0125     case UsefulnessTotal:
0126         return m_reviews.at(index.row())->usefulnessTotal();
0127     case UsefulnessFavorable:
0128         return m_reviews.at(index.row())->usefulnessFavorable();
0129     case WilsonScore:
0130         return m_reviews.at(index.row())->wilsonScore();
0131     case UsefulChoice:
0132         return m_reviews.at(index.row())->usefulChoice();
0133     case Rating:
0134         return m_reviews.at(index.row())->rating();
0135     case Summary:
0136         return m_reviews.at(index.row())->summary();
0137     case PackageVersion:
0138         return m_reviews.at(index.row())->packageVersion();
0139     case Depth:
0140         return m_reviews.at(index.row())->getMetadata(QStringLiteral("NumberOfParents")).toInt();
0141     }
0142     return QVariant();
0143 }
0144 
0145 int ReviewsModel::rowCount(const QModelIndex &parent) const
0146 {
0147     if (parent.isValid())
0148         return 0;
0149     return m_reviews.count();
0150 }
0151 
0152 QString ReviewsModel::preferredSortRole() const
0153 {
0154     return m_preferredSortRole;
0155 }
0156 
0157 void ReviewsModel::setPreferredSortRole(const QString &sorting)
0158 {
0159     if (m_preferredSortRole == sorting || !QStringList({QStringLiteral("wilsonScore"), QStringLiteral("date"), QStringLiteral("rating")}).contains(sorting)) {
0160         return;
0161     }
0162 
0163     m_preferredSortRole = sorting;
0164     KSharedConfigPtr config = KSharedConfig::openConfig();
0165     KConfigGroup configGroup(config, u"Reviews"_s);
0166     configGroup.writeEntry("PreferredSortRole", sorting);
0167 
0168     Q_EMIT preferredSortRoleChanged();
0169 }
0170 
0171 AbstractResource *ReviewsModel::resource() const
0172 {
0173     return m_app;
0174 }
0175 
0176 AbstractReviewsBackend *ReviewsModel::backend() const
0177 {
0178     return m_backend;
0179 }
0180 
0181 void ReviewsModel::setResource(AbstractResource *app)
0182 {
0183     if (m_app != app) {
0184         beginResetModel();
0185         m_starsCount.clear();
0186         m_reviews.clear();
0187         m_lastPage = 0;
0188 
0189         if (m_backend) {
0190             disconnect(m_backend, &AbstractReviewsBackend::errorMessageChanged, this, &ReviewsModel::restartFetching);
0191             disconnect(m_backend, &AbstractReviewsBackend::reviewsReady, this, &ReviewsModel::addReviews);
0192             disconnect(m_backend, &AbstractReviewsBackend::fetchingChanged, this, &ReviewsModel::fetchingChanged);
0193             disconnect(m_app, &AbstractResource::versionsChanged, this, &ReviewsModel::restartFetching);
0194         }
0195         m_app = app;
0196         m_backend = app ? app->backend()->reviewsBackend() : nullptr;
0197         if (m_backend) {
0198             connect(m_backend, &AbstractReviewsBackend::errorMessageChanged, this, &ReviewsModel::restartFetching);
0199             connect(m_backend, &AbstractReviewsBackend::reviewsReady, this, &ReviewsModel::addReviews);
0200             connect(m_backend, &AbstractReviewsBackend::fetchingChanged, this, &ReviewsModel::fetchingChanged);
0201             connect(m_app, &AbstractResource::versionsChanged, this, &ReviewsModel::restartFetching);
0202 
0203             QMetaObject::invokeMethod(this, &ReviewsModel::restartFetching, Qt::QueuedConnection);
0204         }
0205         endResetModel();
0206         Q_EMIT rowsChanged();
0207         Q_EMIT resourceChanged();
0208     }
0209 }
0210 
0211 void ReviewsModel::restartFetching()
0212 {
0213     if (!m_app || !m_backend)
0214         return;
0215 
0216     m_canFetchMore = true;
0217     m_lastPage = 0;
0218     fetchMore();
0219     Q_EMIT rowsChanged();
0220 }
0221 
0222 void ReviewsModel::fetchMore(const QModelIndex &parent)
0223 {
0224     if (!m_backend || !m_app || parent.isValid() || m_backend->isFetching() || !m_canFetchMore)
0225         return;
0226 
0227     m_lastPage++;
0228     m_backend->fetchReviews(m_app, m_lastPage);
0229     // qCDebug(LIBDISCOVER_LOG) << "fetching reviews... " << m_lastPage;
0230 }
0231 
0232 void ReviewsModel::addReviews(AbstractResource *app, const QVector<ReviewPtr> &reviews, bool canFetchMore)
0233 {
0234     if (app != m_app)
0235         return;
0236 
0237     m_canFetchMore = canFetchMore;
0238     qCDebug(LIBDISCOVER_LOG) << "reviews arrived..." << m_lastPage << reviews.size();
0239 
0240     if (!reviews.isEmpty()) {
0241         for (ReviewPtr r : reviews) {
0242             m_starsCount.addRating(r->rating());
0243         }
0244         beginInsertRows(QModelIndex(), rowCount(), rowCount() + reviews.size() - 1);
0245         m_reviews += reviews;
0246         endInsertRows();
0247         Q_EMIT rowsChanged();
0248     }
0249 }
0250 
0251 bool ReviewsModel::canFetchMore(const QModelIndex & /*parent*/) const
0252 {
0253     return m_canFetchMore;
0254 }
0255 
0256 void ReviewsModel::markUseful(int row, bool useful)
0257 {
0258     Review *r = m_reviews[row].data();
0259     r->setUsefulChoice(useful ? Yes : No);
0260     // qCDebug(LIBDISCOVER_LOG) << "submitting usefulness" << r->applicationName() << r->id() << useful;
0261     m_backend->submitUsefulness(r, useful);
0262     const QModelIndex ind = index(row, 0, QModelIndex());
0263     Q_EMIT dataChanged(ind, ind, {UsefulnessTotal, UsefulnessFavorable, UsefulChoice});
0264 }
0265 
0266 void ReviewsModel::deleteReview(int row)
0267 {
0268     Review *r = m_reviews[row].data();
0269     m_backend->deleteReview(r);
0270 }
0271 
0272 void ReviewsModel::flagReview(int row, const QString &reason, const QString &text)
0273 {
0274     Review *r = m_reviews[row].data();
0275     m_backend->flagReview(r, reason, text);
0276 }
0277 
0278 StarsCount ReviewsModel::starsCount() const
0279 {
0280     return m_starsCount;
0281 }
0282 
0283 bool ReviewsModel::isFetching() const
0284 {
0285     return m_backend && m_backend->isFetching();
0286 }
0287 
0288 #include "moc_ReviewsModel.cpp"