File indexing completed on 2024-05-05 17:33:21
0001 /* 0002 * SPDX-FileCopyrightText: 2012 Aleix Pol Gonzalez <aleixpol@blue-systems.com> 0003 * 0004 * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0005 */ 0006 0007 #include "ResourcesUpdatesModel.h" 0008 #include "AbstractBackendUpdater.h" 0009 #include "AbstractResource.h" 0010 #include "ResourcesModel.h" 0011 #include "libdiscover_debug.h" 0012 #include "utils.h" 0013 #include <Transaction/Transaction.h> 0014 #include <Transaction/TransactionModel.h> 0015 0016 #include <KConfigGroup> 0017 #include <KConfigWatcher> 0018 #include <KFormat> 0019 #include <KLocalizedString> 0020 #include <KSharedConfig> 0021 0022 class UpdateTransaction : public Transaction 0023 { 0024 Q_OBJECT 0025 public: 0026 UpdateTransaction(ResourcesUpdatesModel * /*parent*/, const QVector<AbstractBackendUpdater *> &updaters) 0027 : Transaction(nullptr, nullptr, Transaction::InstallRole) 0028 , m_allUpdaters(updaters) 0029 { 0030 bool cancelable = false; 0031 for (auto updater : qAsConst(m_allUpdaters)) { 0032 connect(updater, &AbstractBackendUpdater::progressingChanged, this, &UpdateTransaction::slotProgressingChanged); 0033 connect(updater, &AbstractBackendUpdater::downloadSpeedChanged, this, &UpdateTransaction::slotDownloadSpeedChanged); 0034 connect(updater, &AbstractBackendUpdater::progressChanged, this, &UpdateTransaction::slotUpdateProgress); 0035 connect(updater, &AbstractBackendUpdater::proceedRequest, this, &UpdateTransaction::processProceedRequest); 0036 connect(updater, &AbstractBackendUpdater::distroErrorMessage, this, &UpdateTransaction::distroErrorMessage); 0037 connect(updater, &AbstractBackendUpdater::cancelableChanged, this, [this](bool) { 0038 setCancellable(kContains(m_allUpdaters, [](AbstractBackendUpdater *updater) { 0039 return updater->isCancelable() && updater->isProgressing(); 0040 })); 0041 }); 0042 cancelable |= updater->isCancelable(); 0043 } 0044 setCancellable(cancelable); 0045 } 0046 0047 void processProceedRequest(const QString &title, const QString &message) 0048 { 0049 m_updatersWaitingForFeedback += qobject_cast<AbstractBackendUpdater *>(sender()); 0050 Q_EMIT proceedRequest(title, message); 0051 } 0052 0053 void cancel() override 0054 { 0055 const QVector<AbstractBackendUpdater *> toCancel = m_updatersWaitingForFeedback.isEmpty() ? m_allUpdaters : m_updatersWaitingForFeedback; 0056 0057 for (auto updater : toCancel) { 0058 updater->cancel(); 0059 } 0060 } 0061 0062 void proceed() override 0063 { 0064 m_updatersWaitingForFeedback.takeFirst()->proceed(); 0065 } 0066 0067 bool isProgressing() const 0068 { 0069 bool progressing = false; 0070 for (AbstractBackendUpdater *upd : qAsConst(m_allUpdaters)) { 0071 progressing |= upd->isProgressing(); 0072 } 0073 return progressing; 0074 } 0075 0076 void slotProgressingChanged() 0077 { 0078 if (status() > SetupStatus && status() < DoneStatus && !isProgressing()) { 0079 setStatus(Transaction::DoneStatus); 0080 Q_EMIT finished(); 0081 deleteLater(); 0082 } 0083 } 0084 0085 void slotUpdateProgress() 0086 { 0087 qreal total = 0; 0088 for (AbstractBackendUpdater *updater : qAsConst(m_allUpdaters)) { 0089 total += updater->progress(); 0090 } 0091 setProgress(total / m_allUpdaters.count()); 0092 } 0093 0094 void slotDownloadSpeedChanged() 0095 { 0096 quint64 total = 0; 0097 for (AbstractBackendUpdater *updater : qAsConst(m_allUpdaters)) { 0098 total += updater->downloadSpeed(); 0099 } 0100 setDownloadSpeed(total); 0101 } 0102 0103 QVariant icon() const override 0104 { 0105 return QStringLiteral("update-low"); 0106 } 0107 QString name() const override 0108 { 0109 return i18n("Updates"); 0110 } 0111 0112 Q_SIGNALS: 0113 void finished(); 0114 0115 private: 0116 QVector<AbstractBackendUpdater *> m_updatersWaitingForFeedback; 0117 const QVector<AbstractBackendUpdater *> m_allUpdaters; 0118 }; 0119 0120 ResourcesUpdatesModel::ResourcesUpdatesModel(QObject *parent) 0121 : QStandardItemModel(parent) 0122 , m_lastIsProgressing(false) 0123 , m_transaction(nullptr) 0124 { 0125 connect(ResourcesModel::global(), &ResourcesModel::backendsChanged, this, &ResourcesUpdatesModel::init); 0126 0127 init(); 0128 } 0129 0130 void ResourcesUpdatesModel::init() 0131 { 0132 const QVector<AbstractResourcesBackend *> backends = ResourcesModel::global()->backends(); 0133 m_lastIsProgressing = false; 0134 for (AbstractResourcesBackend *b : backends) { 0135 AbstractBackendUpdater *updater = b->backendUpdater(); 0136 if (updater && !m_updaters.contains(updater)) { 0137 connect(updater, &AbstractBackendUpdater::statusMessageChanged, this, &ResourcesUpdatesModel::message); 0138 connect(updater, &AbstractBackendUpdater::statusDetailChanged, this, &ResourcesUpdatesModel::message); 0139 connect(updater, &AbstractBackendUpdater::downloadSpeedChanged, this, &ResourcesUpdatesModel::downloadSpeedChanged); 0140 connect(updater, &AbstractBackendUpdater::resourceProgressed, this, &ResourcesUpdatesModel::resourceProgressed); 0141 connect(updater, &AbstractBackendUpdater::passiveMessage, this, &ResourcesUpdatesModel::passiveMessage); 0142 connect(updater, &AbstractBackendUpdater::needsRebootChanged, this, &ResourcesUpdatesModel::needsRebootChanged); 0143 connect(updater, &AbstractBackendUpdater::destroyed, this, &ResourcesUpdatesModel::updaterDestroyed); 0144 connect(updater, &AbstractBackendUpdater::errorMessageChanged, this, &ResourcesUpdatesModel::errorMessagesChanged); 0145 m_updaters += updater; 0146 0147 m_lastIsProgressing |= updater->isProgressing(); 0148 } 0149 } 0150 0151 // To enable from command line use: 0152 // kwriteconfig5 --file discoverrc --group Software --key UseOfflineUpdates true 0153 auto sharedConfig = KSharedConfig::openConfig(); 0154 KConfigGroup group(sharedConfig, "Software"); 0155 m_offlineUpdates = group.readEntry<bool>("UseOfflineUpdates", false); 0156 0157 KConfigWatcher::Ptr watcher = KConfigWatcher::create(sharedConfig); 0158 connect(watcher.data(), &KConfigWatcher::configChanged, this, [this](const KConfigGroup &group, const QByteArrayList &names) { 0159 // Ensure it is for the right file 0160 if (!names.contains("UseOfflineUpdates") || group.name() != "Software") { 0161 return; 0162 } 0163 0164 if (m_offlineUpdates == group.readEntry<bool>("UseOfflineUpdates", false)) { 0165 return; 0166 } 0167 Q_EMIT useUnattendedUpdatesChanged(); 0168 }); 0169 0170 auto tm = TransactionModel::global(); 0171 const auto transactions = tm->transactions(); 0172 for (auto t : transactions) { 0173 auto updateTransaction = qobject_cast<UpdateTransaction *>(t); 0174 if (updateTransaction) { 0175 setTransaction(updateTransaction); 0176 } 0177 } 0178 0179 Q_EMIT errorMessagesChanged(); 0180 } 0181 0182 void ResourcesUpdatesModel::updaterDestroyed(QObject *obj) 0183 { 0184 for (auto it = m_updaters.begin(); it != m_updaters.end();) { 0185 if (*it == obj) 0186 it = m_updaters.erase(it); 0187 else 0188 ++it; 0189 } 0190 } 0191 0192 void ResourcesUpdatesModel::message(const QString &msg) 0193 { 0194 if (msg.isEmpty()) 0195 return; 0196 0197 appendRow(new QStandardItem(msg)); 0198 } 0199 0200 void ResourcesUpdatesModel::prepare() 0201 { 0202 if (isProgressing()) { 0203 qCWarning(LIBDISCOVER_LOG) << "trying to set up a running instance"; 0204 return; 0205 } 0206 0207 for (AbstractBackendUpdater *upd : qAsConst(m_updaters)) { 0208 upd->setOfflineUpdates(m_offlineUpdates); 0209 upd->prepare(); 0210 } 0211 } 0212 0213 void ResourcesUpdatesModel::updateAll() 0214 { 0215 if (!m_updaters.isEmpty()) { 0216 delete m_transaction; 0217 0218 const auto updaters = kFilter<QVector<AbstractBackendUpdater *>>(m_updaters, [](AbstractBackendUpdater *u) { 0219 return u->hasUpdates(); 0220 }); 0221 if (updaters.isEmpty()) { 0222 return; 0223 } 0224 0225 m_transaction = new UpdateTransaction(this, updaters); 0226 m_transaction->setStatus(Transaction::SetupStatus); 0227 setTransaction(m_transaction); 0228 TransactionModel::global()->addTransaction(m_transaction); 0229 for (AbstractBackendUpdater *upd : updaters) { 0230 QMetaObject::invokeMethod(upd, &AbstractBackendUpdater::start, Qt::QueuedConnection); 0231 } 0232 0233 QMetaObject::invokeMethod( 0234 this, 0235 [this]() { 0236 m_transaction->setStatus(Transaction::CommittingStatus); 0237 m_transaction->slotProgressingChanged(); 0238 }, 0239 Qt::QueuedConnection); 0240 } 0241 } 0242 0243 bool ResourcesUpdatesModel::isProgressing() const 0244 { 0245 return m_transaction && m_transaction->status() < Transaction::DoneStatus; 0246 } 0247 0248 QList<AbstractResource *> ResourcesUpdatesModel::toUpdate() const 0249 { 0250 QList<AbstractResource *> ret; 0251 for (AbstractBackendUpdater *upd : qAsConst(m_updaters)) { 0252 ret += upd->toUpdate(); 0253 } 0254 return ret; 0255 } 0256 0257 void ResourcesUpdatesModel::addResources(const QList<AbstractResource *> &resources) 0258 { 0259 QHash<AbstractResourcesBackend *, QList<AbstractResource *>> sortedResources; 0260 for (AbstractResource *res : resources) { 0261 sortedResources[res->backend()] += res; 0262 } 0263 0264 for (auto it = sortedResources.constBegin(), itEnd = sortedResources.constEnd(); it != itEnd; ++it) { 0265 it.key()->backendUpdater()->addResources(*it); 0266 } 0267 } 0268 0269 void ResourcesUpdatesModel::removeResources(const QList<AbstractResource *> &resources) 0270 { 0271 QHash<AbstractResourcesBackend *, QList<AbstractResource *>> sortedResources; 0272 for (AbstractResource *res : resources) { 0273 sortedResources[res->backend()] += res; 0274 } 0275 0276 for (auto it = sortedResources.constBegin(), itEnd = sortedResources.constEnd(); it != itEnd; ++it) { 0277 it.key()->backendUpdater()->removeResources(*it); 0278 } 0279 } 0280 0281 QDateTime ResourcesUpdatesModel::lastUpdate() const 0282 { 0283 QDateTime ret; 0284 for (AbstractBackendUpdater *upd : qAsConst(m_updaters)) { 0285 QDateTime current = upd->lastUpdate(); 0286 if (!ret.isValid() || (current.isValid() && current > ret)) { 0287 ret = current; 0288 } 0289 } 0290 return ret; 0291 } 0292 0293 double ResourcesUpdatesModel::updateSize() const 0294 { 0295 double ret = 0.; 0296 for (AbstractBackendUpdater *upd : m_updaters) { 0297 ret += std::max(0., upd->updateSize()); 0298 } 0299 return ret; 0300 } 0301 0302 qint64 ResourcesUpdatesModel::secsToLastUpdate() const 0303 { 0304 return lastUpdate().secsTo(QDateTime::currentDateTime()); 0305 } 0306 0307 void ResourcesUpdatesModel::setTransaction(UpdateTransaction *transaction) 0308 { 0309 m_transaction = transaction; 0310 connect(transaction, &UpdateTransaction::finished, this, &ResourcesUpdatesModel::finished); 0311 connect(transaction, &UpdateTransaction::finished, this, &ResourcesUpdatesModel::progressingChanged); 0312 0313 Q_EMIT progressingChanged(); 0314 } 0315 0316 Transaction *ResourcesUpdatesModel::transaction() const 0317 { 0318 return m_transaction.data(); 0319 } 0320 0321 bool ResourcesUpdatesModel::needsReboot() const 0322 { 0323 for (auto upd : m_updaters) { 0324 if (upd->needsReboot()) 0325 return true; 0326 } 0327 return false; 0328 } 0329 0330 bool ResourcesUpdatesModel::readyToReboot() const 0331 { 0332 return kContains(m_updaters, [](AbstractBackendUpdater *updater) { 0333 return !updater->needsReboot() || updater->isReadyToReboot(); 0334 }); 0335 } 0336 0337 bool ResourcesUpdatesModel::useUnattendedUpdates() const 0338 { 0339 return m_offlineUpdates; 0340 } 0341 0342 void ResourcesUpdatesModel::setOfflineUpdates(bool offline) 0343 { 0344 m_offlineUpdates = offline; 0345 } 0346 0347 QStringList ResourcesUpdatesModel::errorMessages() const 0348 { 0349 QStringList ret; 0350 for (auto updater : m_updaters) { 0351 const auto error = updater->errorMessage(); 0352 if (!error.isEmpty()) { 0353 ret << error; 0354 } 0355 } 0356 ret.removeDuplicates(); 0357 return ret; 0358 } 0359 0360 #include "ResourcesUpdatesModel.moc"