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"