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"