/* * SPDX-FileCopyrightText: 2011 Jonathan Thomas * * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL */ #include "UpdateModel.h" // Qt includes #include "libdiscover_debug.h" #include #include // KDE includes #include #include // Own includes #include "UpdateItem.h" #include #include #include UpdateModel::UpdateModel(QObject *parent) : QAbstractListModel(parent) , m_updateSizeTimer(new QTimer(this)) , m_updates(nullptr) { connect(ResourcesModel::global(), &ResourcesModel::fetchingChanged, this, &UpdateModel::activityChanged); connect(ResourcesModel::global(), &ResourcesModel::updatesCountChanged, this, &UpdateModel::activityChanged); connect(ResourcesModel::global(), &ResourcesModel::resourceDataChanged, this, &UpdateModel::resourceDataChanged); connect(this, &UpdateModel::toUpdateChanged, this, &UpdateModel::updateSizeChanged); m_updateSizeTimer->setInterval(100); m_updateSizeTimer->setSingleShot(true); connect(m_updateSizeTimer, &QTimer::timeout, this, &UpdateModel::updateSizeChanged); } UpdateModel::~UpdateModel() { qDeleteAll(m_updateItems); m_updateItems.clear(); } QHash UpdateModel::roleNames() const { auto ret = QAbstractItemModel::roleNames(); ret.insert(Qt::CheckStateRole, "checked"); ret.insert(ResourceProgressRole, "resourceProgress"); ret.insert(ResourceStateRole, "resourceState"); ret.insert(ResourceStateIsDoneRole, "resourceStateIsDone"); ret.insert(ResourceRole, "resource"); ret.insert(SizeRole, "size"); ret.insert(SectionRole, "section"); ret.insert(ChangelogRole, "changelog"); ret.insert(ExtendedRole, "extended"); return ret; } void UpdateModel::setBackend(ResourcesUpdatesModel *updates) { if (m_updates) { disconnect(m_updates, &ResourcesUpdatesModel::progressingChanged, this, &UpdateModel::activityChanged); disconnect(m_updates, &ResourcesUpdatesModel::resourceProgressed, this, &UpdateModel::resourceHasProgressed); } m_updates = updates; if (m_updates) { connect(m_updates, &ResourcesUpdatesModel::progressingChanged, this, &UpdateModel::activityChanged); connect(m_updates, &ResourcesUpdatesModel::resourceProgressed, this, &UpdateModel::resourceHasProgressed); } activityChanged(); } void UpdateModel::resourceHasProgressed(AbstractResource *res, qreal progress, AbstractBackendUpdater::State state) { UpdateItem *item = itemFromResource(res); if (!item) { return; } item->setProgress(progress); item->setState(state); const QModelIndex idx = indexFromItem(item); Q_EMIT dataChanged(idx, idx, {ResourceProgressRole, ResourceStateRole, ResourceStateIsDoneRole, SectionResourceProgressRole}); } void UpdateModel::activityChanged() { if (m_updates) { if (!m_updates->isProgressing()) { m_updates->prepare(); setResources(m_updates->toUpdate()); for (auto item : std::as_const(m_updateItems)) { item->setProgress(0); } } else { setResources(m_updates->toUpdate()); } } } QVariant UpdateModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } UpdateItem *item = itemFromIndex(index); switch (role) { case Qt::DisplayRole: return item->name(); case Qt::DecorationRole: return item->icon(); case Qt::CheckStateRole: return item->checked(); case SizeRole: return item->size() > 0 ? KFormat().formatByteSize(item->size()) : i18n("Unknown"); case ResourceRole: return QVariant::fromValue(item->resource()); case ResourceProgressRole: return item->progress(); case ResourceStateRole: return item->state(); case ResourceStateIsDoneRole: return item->state() == AbstractBackendUpdater::State::Done; case ChangelogRole: return item->changelog(); case ExtendedRole: return item->isExtended(); case SectionRole: { static const QString appUpdatesSection = i18nc("@item:inlistbox", "Applications"); static const QString applicationSupportSection = i18nc("@item:inlistbox", "Application Support"); static const QString systemUpdateSection = i18nc("@item:inlistbox", "System Software"); static const QString addonsSection = i18nc("@item:inlistbox", "Addons"); switch (item->resource()->type()) { case AbstractResource::Application: return appUpdatesSection; case AbstractResource::ApplicationSupport: return applicationSupportSection; case AbstractResource::System: return systemUpdateSection; case AbstractResource::Addon: return addonsSection; } Q_UNREACHABLE(); } case SectionResourceProgressRole: return (100 - item->progress()) + (101 * item->resource()->type()); default: break; } return QVariant(); } void UpdateModel::checkResources(const QList &resource, bool checked) { if (checked) { m_updates->addResources(resource); } else { m_updates->removeResources(resource); } } Qt::ItemFlags UpdateModel::flags(const QModelIndex &index) const { if (!index.isValid()) { return Qt::NoItemFlags; } return Qt::ItemIsEnabled | Qt::ItemIsSelectable; } int UpdateModel::rowCount(const QModelIndex &parent) const { return !parent.isValid() ? m_updateItems.count() : 0; } bool UpdateModel::setData(const QModelIndex &idx, const QVariant &value, int role) { if (role == Qt::CheckStateRole) { UpdateItem *item = itemFromIndex(idx); const bool newValue = value.toInt() == Qt::Checked; const QList apps = {item->app()}; checkResources(apps, newValue); Q_ASSERT(idx.data(Qt::CheckStateRole) == value); // When un/checking some backends will decide to add or remove a bunch of packages, so refresh it all auto m = idx.model(); Q_EMIT dataChanged(m->index(0, 0), m->index(m->rowCount() - 1, 0), {Qt::CheckStateRole}); Q_EMIT toUpdateChanged(); return true; } else if (role == ExtendedRole) { UpdateItem *item = itemFromIndex(idx); if (item->isExtended() != value.toBool()) { item->setExtended(value.toBool()); Q_EMIT dataChanged(idx, idx, {ExtendedRole}); } } return false; } void UpdateModel::fetchUpdateDetails(int row) { UpdateItem *item = itemFromIndex(index(row, 0)); Q_ASSERT(item); if (!item) { return; } item->app()->fetchUpdateDetails(); } void UpdateModel::integrateChangelog(const QString &changelog) { auto app = qobject_cast(sender()); Q_ASSERT(app); auto item = itemFromResource(app); if (!item) { return; } item->setChangelog(changelog); const QModelIndex idx = indexFromItem(item); Q_ASSERT(idx.isValid()); Q_EMIT dataChanged(idx, idx, {ChangelogRole}); } void UpdateModel::setResources(const QList &resources) { if (resources == m_resources) { return; } m_resources = resources; beginResetModel(); qDeleteAll(m_updateItems); m_updateItems.clear(); QVector appItems, appSupportItems, systemItems, addonItems; for (AbstractResource *resource : resources) { connect(resource, &AbstractResource::changelogFetched, this, &UpdateModel::integrateChangelog, Qt::UniqueConnection); UpdateItem *updateItem = new UpdateItem(resource); switch (resource->type()) { case AbstractResource::Application: appItems += updateItem; break; case AbstractResource::ApplicationSupport: appSupportItems += updateItem; break; case AbstractResource::System: systemItems += updateItem; break; case AbstractResource::Addon: addonItems += updateItem; break; } } const auto sortUpdateItems = [](UpdateItem *a, UpdateItem *b) { return a->name() < b->name(); }; std::sort(appItems.begin(), appItems.end(), sortUpdateItems); std::sort(appSupportItems.begin(), appSupportItems.end(), sortUpdateItems); std::sort(systemItems.begin(), systemItems.end(), sortUpdateItems); std::sort(addonItems.begin(), addonItems.end(), sortUpdateItems); m_updateItems = (QVector() << appItems << addonItems << appSupportItems << systemItems); endResetModel(); Q_EMIT hasUpdatesChanged(!resources.isEmpty()); Q_EMIT toUpdateChanged(); } bool UpdateModel::hasUpdates() const { return rowCount() > 0; } ResourcesUpdatesModel *UpdateModel::backend() const { return m_updates; } int UpdateModel::toUpdateCount() const { int ret = 0; QSet packages; for (UpdateItem *item : std::as_const(m_updateItems)) { const auto packageName = item->resource()->packageName(); if (packages.contains(packageName)) { continue; } packages.insert(packageName); ret += item->checked() != Qt::Unchecked ? 1 : 0; } return ret; } int UpdateModel::totalUpdatesCount() const { int ret = 0; QSet packages; for (UpdateItem *item : std::as_const(m_updateItems)) { const auto packageName = item->resource()->packageName(); if (packages.contains(packageName)) { continue; } packages.insert(packageName); ret += 1; } return ret; } UpdateItem *UpdateModel::itemFromResource(AbstractResource *res) { for (UpdateItem *item : std::as_const(m_updateItems)) { if (item->app() == res) { return item; } } return nullptr; } QString UpdateModel::updateSize() const { if (!m_updates) { return QString(); } if (m_updates->updateSize() != 0) { return KFormat().formatByteSize(m_updates->updateSize()); } return i18n("Unknown"); } QModelIndex UpdateModel::indexFromItem(UpdateItem *item) const { return index(m_updateItems.indexOf(item), 0, {}); } UpdateItem *UpdateModel::itemFromIndex(const QModelIndex &index) const { return m_updateItems[index.row()]; } void UpdateModel::resourceDataChanged(AbstractResource *res, const QVector &properties) { auto item = itemFromResource(res); if (!item) { return; } const auto index = indexFromItem(item); if (properties.contains("icon")) { Q_EMIT dataChanged(index, index, {Qt::DecorationRole}); } if (properties.contains("state")) { Q_EMIT dataChanged(index, index, {SizeRole}); } else if (properties.contains("size")) { Q_EMIT dataChanged(index, index, {SizeRole}); m_updateSizeTimer->start(); } } void UpdateModel::checkAll() { QList updatedItems; for (int i = 0, c = rowCount(); i < c; ++i) { auto idx = index(i); if (idx.data(Qt::CheckStateRole) != Qt::Checked) { updatedItems.append(itemFromIndex(idx)->app()); } } checkResources(updatedItems, true); Q_EMIT dataChanged(index(0), index(rowCount() - 1), {Qt::CheckStateRole}); Q_EMIT toUpdateChanged(); } void UpdateModel::uncheckAll() { QList updatedItems; for (int i = 0, c = rowCount(); i < c; ++i) { auto idx = index(i); if (idx.data(Qt::CheckStateRole) != Qt::Unchecked) { updatedItems.append(itemFromIndex(idx)->app()); } } checkResources(updatedItems, false); Q_EMIT dataChanged(index(0), index(rowCount() - 1), {Qt::CheckStateRole}); Q_EMIT toUpdateChanged(); } #include "moc_UpdateModel.cpp"