File indexing completed on 2024-05-12 05:29:06

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