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