File indexing completed on 2024-05-12 16:59:00

0001 /*
0002  *   SPDX-FileCopyrightText: 2011 Jonathan Thomas <echidnaman@kubuntu.org>
0003  *
0004  *   SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0005  */
0006 
0007 #include "UpdateModel.h"
0008 
0009 // Qt includes
0010 #include "libdiscover_debug.h"
0011 #include <QFont>
0012 #include <QTimer>
0013 
0014 // KDE includes
0015 #include <KFormat>
0016 #include <KLocalizedString>
0017 
0018 // Own includes
0019 #include "UpdateItem.h"
0020 #include <resources/AbstractResource.h>
0021 #include <resources/ResourcesModel.h>
0022 #include <resources/ResourcesUpdatesModel.h>
0023 
0024 UpdateModel::UpdateModel(QObject *parent)
0025     : QAbstractListModel(parent)
0026     , m_updateSizeTimer(new QTimer(this))
0027     , m_updates(nullptr)
0028 {
0029     connect(ResourcesModel::global(), &ResourcesModel::fetchingChanged, this, &UpdateModel::activityChanged);
0030     connect(ResourcesModel::global(), &ResourcesModel::updatesCountChanged, this, &UpdateModel::activityChanged);
0031     connect(ResourcesModel::global(), &ResourcesModel::resourceDataChanged, this, &UpdateModel::resourceDataChanged);
0032     connect(this, &UpdateModel::toUpdateChanged, this, &UpdateModel::updateSizeChanged);
0033 
0034     m_updateSizeTimer->setInterval(100);
0035     m_updateSizeTimer->setSingleShot(true);
0036     connect(m_updateSizeTimer, &QTimer::timeout, this, &UpdateModel::updateSizeChanged);
0037 }
0038 
0039 UpdateModel::~UpdateModel()
0040 {
0041     qDeleteAll(m_updateItems);
0042     m_updateItems.clear();
0043 }
0044 
0045 QHash<int, QByteArray> UpdateModel::roleNames() const
0046 {
0047     auto ret = QAbstractItemModel::roleNames();
0048     ret.insert(Qt::CheckStateRole, "checked");
0049     ret.insert(ResourceProgressRole, "resourceProgress");
0050     ret.insert(ResourceStateRole, "resourceState");
0051     ret.insert(ResourceRole, "resource");
0052     ret.insert(SizeRole, "size");
0053     ret.insert(SectionRole, "section");
0054     ret.insert(ChangelogRole, "changelog");
0055     ret.insert(UpgradeTextRole, "upgradeText");
0056     ret.insert(ExtendedRole, "extended");
0057     return ret;
0058 }
0059 
0060 void UpdateModel::setBackend(ResourcesUpdatesModel *updates)
0061 {
0062     if (m_updates) {
0063         disconnect(m_updates, nullptr, this, nullptr);
0064     }
0065 
0066     m_updates = updates;
0067 
0068     connect(m_updates, &ResourcesUpdatesModel::progressingChanged, this, &UpdateModel::activityChanged);
0069     connect(m_updates, &ResourcesUpdatesModel::resourceProgressed, this, &UpdateModel::resourceHasProgressed);
0070 
0071     activityChanged();
0072 }
0073 
0074 void UpdateModel::resourceHasProgressed(AbstractResource *res, qreal progress, AbstractBackendUpdater::State state)
0075 {
0076     UpdateItem *item = itemFromResource(res);
0077     if (!item)
0078         return;
0079     item->setProgress(progress);
0080     item->setState(state);
0081 
0082     const QModelIndex idx = indexFromItem(item);
0083     Q_EMIT dataChanged(idx, idx, {ResourceProgressRole, ResourceStateRole, SectionResourceProgressRole});
0084 }
0085 
0086 void UpdateModel::activityChanged()
0087 {
0088     if (m_updates) {
0089         if (!m_updates->isProgressing()) {
0090             m_updates->prepare();
0091             setResources(m_updates->toUpdate());
0092 
0093             for (auto item : qAsConst(m_updateItems)) {
0094                 item->setProgress(0);
0095             }
0096         } else
0097             setResources(m_updates->toUpdate());
0098     }
0099 }
0100 
0101 QVariant UpdateModel::data(const QModelIndex &index, int role) const
0102 {
0103     if (!index.isValid()) {
0104         return QVariant();
0105     }
0106 
0107     UpdateItem *item = itemFromIndex(index);
0108 
0109     switch (role) {
0110     case Qt::DisplayRole:
0111         return item->name();
0112     case Qt::DecorationRole:
0113         return item->icon();
0114     case Qt::CheckStateRole:
0115         return item->checked();
0116     case SizeRole:
0117         return item->size() > 0 ? KFormat().formatByteSize(item->size()) : i18n("Unknown");
0118     case ResourceRole:
0119         return QVariant::fromValue<QObject *>(item->resource());
0120     case ResourceProgressRole:
0121         return item->progress();
0122     case ResourceStateRole:
0123         return item->state();
0124     case ChangelogRole:
0125         return item->changelog();
0126     case ExtendedRole:
0127         return item->isExtended();
0128     case SectionRole: {
0129         static const QString appUpdatesSection = i18nc("@item:inlistbox", "Applications");
0130         static const QString systemUpdateSection = i18nc("@item:inlistbox", "System Software");
0131         static const QString addonsSection = i18nc("@item:inlistbox", "Addons");
0132         switch (item->resource()->type()) {
0133         case AbstractResource::Application:
0134             return appUpdatesSection;
0135         case AbstractResource::Technical:
0136             return systemUpdateSection;
0137         case AbstractResource::Addon:
0138             return addonsSection;
0139         }
0140         Q_UNREACHABLE();
0141     }
0142     case SectionResourceProgressRole:
0143         return (100 - item->progress()) + (101 * item->resource()->type());
0144     default:
0145         break;
0146     }
0147 
0148     return QVariant();
0149 }
0150 
0151 void UpdateModel::checkResources(const QList<AbstractResource *> &resource, bool checked)
0152 {
0153     if (checked)
0154         m_updates->addResources(resource);
0155     else
0156         m_updates->removeResources(resource);
0157 }
0158 
0159 Qt::ItemFlags UpdateModel::flags(const QModelIndex &index) const
0160 {
0161     if (!index.isValid())
0162         return Qt::NoItemFlags;
0163 
0164     return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
0165 }
0166 
0167 int UpdateModel::rowCount(const QModelIndex &parent) const
0168 {
0169     return !parent.isValid() ? m_updateItems.count() : 0;
0170 }
0171 
0172 bool UpdateModel::setData(const QModelIndex &idx, const QVariant &value, int role)
0173 {
0174     if (role == Qt::CheckStateRole) {
0175         UpdateItem *item = itemFromIndex(idx);
0176         const bool newValue = value.toInt() == Qt::Checked;
0177         const QList<AbstractResource *> apps = {item->app()};
0178 
0179         checkResources(apps, newValue);
0180         Q_ASSERT(idx.data(Qt::CheckStateRole) == value);
0181 
0182         // When un/checking some backends will decide to add or remove a bunch of packages, so refresh it all
0183         auto m = idx.model();
0184         Q_EMIT dataChanged(m->index(0, 0), m->index(m->rowCount() - 1, 0), {Qt::CheckStateRole});
0185         Q_EMIT toUpdateChanged();
0186 
0187         return true;
0188     } else if (role == ExtendedRole) {
0189         UpdateItem *item = itemFromIndex(idx);
0190         if (item->isExtended() != value.toBool()) {
0191             item->setExtended(value.toBool());
0192             Q_EMIT dataChanged(idx, idx, {ExtendedRole});
0193         }
0194     }
0195 
0196     return false;
0197 }
0198 
0199 void UpdateModel::fetchUpdateDetails(int row)
0200 {
0201     UpdateItem *item = itemFromIndex(index(row, 0));
0202     Q_ASSERT(item);
0203     if (!item)
0204         return;
0205 
0206     item->app()->fetchUpdateDetails();
0207 }
0208 
0209 void UpdateModel::integrateChangelog(const QString &changelog)
0210 {
0211     auto app = qobject_cast<AbstractResource *>(sender());
0212     Q_ASSERT(app);
0213     auto item = itemFromResource(app);
0214     if (!item)
0215         return;
0216 
0217     item->setChangelog(changelog);
0218 
0219     const QModelIndex idx = indexFromItem(item);
0220     Q_ASSERT(idx.isValid());
0221     Q_EMIT dataChanged(idx, idx, {ChangelogRole});
0222 }
0223 
0224 void UpdateModel::setResources(const QList<AbstractResource *> &resources)
0225 {
0226     if (resources == m_resources) {
0227         return;
0228     }
0229     m_resources = resources;
0230 
0231     beginResetModel();
0232     qDeleteAll(m_updateItems);
0233     m_updateItems.clear();
0234 
0235     QVector<UpdateItem *> appItems, systemItems, addonItems;
0236     for (AbstractResource *res : resources) {
0237         connect(res, &AbstractResource::changelogFetched, this, &UpdateModel::integrateChangelog, Qt::UniqueConnection);
0238 
0239         UpdateItem *updateItem = new UpdateItem(res);
0240 
0241         switch (res->type()) {
0242         case AbstractResource::Technical:
0243             systemItems += updateItem;
0244             break;
0245         case AbstractResource::Application:
0246             appItems += updateItem;
0247             break;
0248         case AbstractResource::Addon:
0249             addonItems += updateItem;
0250             break;
0251         }
0252     }
0253     const auto sortUpdateItems = [](UpdateItem *a, UpdateItem *b) {
0254         return a->name() < b->name();
0255     };
0256     std::sort(appItems.begin(), appItems.end(), sortUpdateItems);
0257     std::sort(systemItems.begin(), systemItems.end(), sortUpdateItems);
0258     std::sort(addonItems.begin(), addonItems.end(), sortUpdateItems);
0259     m_updateItems = (QVector<UpdateItem *>() << appItems << addonItems << systemItems);
0260     endResetModel();
0261 
0262     Q_EMIT hasUpdatesChanged(!resources.isEmpty());
0263     Q_EMIT toUpdateChanged();
0264 }
0265 
0266 bool UpdateModel::hasUpdates() const
0267 {
0268     return rowCount() > 0;
0269 }
0270 
0271 ResourcesUpdatesModel *UpdateModel::backend() const
0272 {
0273     return m_updates;
0274 }
0275 
0276 int UpdateModel::toUpdateCount() const
0277 {
0278     int ret = 0;
0279     QSet<QString> packages;
0280     for (UpdateItem *item : qAsConst(m_updateItems)) {
0281         const auto packageName = item->resource()->packageName();
0282         if (packages.contains(packageName)) {
0283             continue;
0284         }
0285         packages.insert(packageName);
0286         ret += item->checked() != Qt::Unchecked ? 1 : 0;
0287     }
0288     return ret;
0289 }
0290 
0291 int UpdateModel::totalUpdatesCount() const
0292 {
0293     int ret = 0;
0294     QSet<QString> packages;
0295     for (UpdateItem *item : qAsConst(m_updateItems)) {
0296         const auto packageName = item->resource()->packageName();
0297         if (packages.contains(packageName)) {
0298             continue;
0299         }
0300         packages.insert(packageName);
0301         ret += 1;
0302     }
0303     return ret;
0304 }
0305 
0306 UpdateItem *UpdateModel::itemFromResource(AbstractResource *res)
0307 {
0308     for (UpdateItem *item : qAsConst(m_updateItems)) {
0309         if (item->app() == res)
0310             return item;
0311     }
0312     return nullptr;
0313 }
0314 
0315 QString UpdateModel::updateSize() const
0316 {
0317     if (!m_updates) {
0318         return QString();
0319     }
0320     if (m_updates->updateSize() != 0) {
0321         return KFormat().formatByteSize(m_updates->updateSize());
0322     }
0323     return i18n("Unknown");
0324 }
0325 
0326 QModelIndex UpdateModel::indexFromItem(UpdateItem *item) const
0327 {
0328     return index(m_updateItems.indexOf(item), 0, {});
0329 }
0330 
0331 UpdateItem *UpdateModel::itemFromIndex(const QModelIndex &index) const
0332 {
0333     return m_updateItems[index.row()];
0334 }
0335 
0336 void UpdateModel::resourceDataChanged(AbstractResource *res, const QVector<QByteArray> &properties)
0337 {
0338     auto item = itemFromResource(res);
0339     if (!item)
0340         return;
0341 
0342     const auto index = indexFromItem(item);
0343     if (properties.contains("state"))
0344         Q_EMIT dataChanged(index, index, {SizeRole, UpgradeTextRole});
0345     else if (properties.contains("size")) {
0346         Q_EMIT dataChanged(index, index, {SizeRole});
0347         m_updateSizeTimer->start();
0348     }
0349 }
0350 
0351 void UpdateModel::checkAll()
0352 {
0353     QList<AbstractResource *> updatedItems;
0354 
0355     for (int i = 0, c = rowCount(); i < c; ++i) {
0356         auto idx = index(i);
0357         if (idx.data(Qt::CheckStateRole) != Qt::Checked) {
0358             updatedItems.append(itemFromIndex(idx)->app());
0359         }
0360     }
0361 
0362     checkResources(updatedItems, true);
0363 
0364     Q_EMIT dataChanged(index(0), index(rowCount() - 1), {Qt::CheckStateRole});
0365     Q_EMIT toUpdateChanged();
0366 }
0367 
0368 void UpdateModel::uncheckAll()
0369 {
0370     QList<AbstractResource *> updatedItems;
0371 
0372     for (int i = 0, c = rowCount(); i < c; ++i) {
0373         auto idx = index(i);
0374         if (idx.data(Qt::CheckStateRole) != Qt::Unchecked) {
0375             updatedItems.append(itemFromIndex(idx)->app());
0376         }
0377     }
0378 
0379     checkResources(updatedItems, false);
0380 
0381     Q_EMIT dataChanged(index(0), index(rowCount() - 1), {Qt::CheckStateRole});
0382     Q_EMIT toUpdateChanged();
0383 }
0384 
0385 #include "moc_UpdateModel.cpp"