File indexing completed on 2024-05-12 16:02:30

0001 /* This file is part of the KDE project
0002  * SPDX-FileCopyrightText: 2006-2007 Thomas Zander <zander@kde.org>
0003  * SPDX-FileCopyrightText: 2009 Boudewijn Rempt <boud@valdyas.org>
0004  *
0005  * SPDX-License-Identifier: LGPL-2.0-or-later
0006  */
0007 #include "KoProgressUpdater.h"
0008 
0009 #include <QApplication>
0010 #include <QString>
0011 #include <QTimer>
0012 #include <QThread>
0013 #include <QMutex>
0014 #include <QMutexLocker>
0015 
0016 #include "KoUpdaterPrivate_p.h"
0017 #include "KoUpdater.h"
0018 #include "KoProgressProxy.h"
0019 
0020 #include "kis_signal_compressor.h"
0021 
0022 #include <kis_debug.h>
0023 
0024 class Q_DECL_HIDDEN KoProgressUpdater::Private
0025 {
0026 public:
0027 
0028     Private(KoProgressUpdater *_q, KoProgressProxy *proxy, QPointer<KoUpdater> parentUpdater, Mode _mode)
0029         : q(_q)
0030         , parentProgressProxy(proxy)
0031         , parentUpdater(parentUpdater)
0032         , mode(_mode)
0033         , updateCompressor(new KisSignalCompressor(250, KisSignalCompressor::FIRST_ACTIVE, q))
0034         , canceled(false)
0035     {
0036     }
0037 
0038     KoProgressUpdater *q;
0039 
0040 private:
0041     KoProgressProxy *parentProgressProxy;
0042     QPointer<KoUpdater> parentUpdater;
0043 
0044 public:
0045     Mode mode;
0046     int currentProgress = 0;
0047     bool isUndefinedState = false;
0048     KisSignalCompressor *updateCompressor;
0049     QList<QPointer<KoUpdaterPrivate> > subtasks;
0050     bool canceled;
0051     int updateInterval = 250; // ms, 4 updates per second should be enough
0052     bool autoNestNames = false;
0053     QString taskName;
0054     int taskMax = 99;
0055     bool isStarted = false;
0056 
0057     QMutex mutex;
0058 
0059     void updateParentText();
0060     void clearState();
0061 
0062     KoProgressProxy* progressProxy() {
0063         return parentUpdater ? parentUpdater : parentProgressProxy;
0064     }
0065 };
0066 
0067 // NOTE: do not make the KoProgressUpdater object part of the QObject
0068 // hierarchy. Do not make KoProgressProxy its parent (note that KoProgressProxy
0069 // is not necessarily castable to QObject ). This prevents proper functioning
0070 // of progress reporting in multi-threaded environments.
0071 KoProgressUpdater::KoProgressUpdater(KoProgressProxy *progressProxy, Mode mode)
0072     : d (new Private(this, progressProxy, 0, mode))
0073 {
0074     KIS_ASSERT_RECOVER_RETURN(progressProxy);
0075     connect(d->updateCompressor, SIGNAL(timeout()), SLOT(updateUi()));
0076     connect(this, SIGNAL(triggerUpdateAsynchronously()), d->updateCompressor, SLOT(start()));
0077     Q_EMIT triggerUpdateAsynchronously();
0078 }
0079 
0080 KoProgressUpdater::KoProgressUpdater(QPointer<KoUpdater> updater)
0081     : d (new Private(this, 0, updater, Unthreaded))
0082 {
0083     KIS_ASSERT_RECOVER_RETURN(updater);
0084     connect(d->updateCompressor, SIGNAL(timeout()), SLOT(updateUi()));
0085     connect(this, SIGNAL(triggerUpdateAsynchronously()), d->updateCompressor, SLOT(start()));
0086     Q_EMIT triggerUpdateAsynchronously();
0087 }
0088 
0089 KoProgressUpdater::~KoProgressUpdater()
0090 {
0091     if (d->progressProxy()) {
0092         d->progressProxy()->setRange(0, d->taskMax);
0093         d->progressProxy()->setValue(d->progressProxy()->maximum());
0094     }
0095 
0096     // make sure to stop the timer to avoid accessing
0097     // the data we are going to delete right now
0098     d->updateCompressor->stop();
0099 
0100     qDeleteAll(d->subtasks);
0101     d->subtasks.clear();
0102 
0103     delete d;
0104 }
0105 
0106 void KoProgressUpdater::start(int range, const QString &text)
0107 {
0108     {
0109         QMutexLocker l(&d->mutex);
0110         d->clearState();
0111         d->taskName = text;
0112         d->taskMax = range - 1;
0113         d->isStarted = true;
0114         d->currentProgress = 0;
0115     }
0116 
0117     Q_EMIT triggerUpdateAsynchronously();
0118 }
0119 
0120 QPointer<KoUpdater> KoProgressUpdater::startSubtask(int weight,
0121                                                     const QString &name,
0122                                                     bool isPersistent)
0123 {
0124     if (!d->isStarted) {
0125         // lazy initialization for intermediate proxies
0126         start();
0127     }
0128 
0129     KoUpdaterPrivate *p = new KoUpdaterPrivate(this, weight, name, isPersistent);
0130 
0131     {
0132         QMutexLocker l(&d->mutex);
0133         d->subtasks.append(p);
0134     }
0135     connect(p, SIGNAL(sigUpdated()), SLOT(update()));
0136 
0137     QPointer<KoUpdater> updater = p->connectedUpdater();
0138 
0139     Q_EMIT triggerUpdateAsynchronously();
0140     return updater;
0141 }
0142 
0143 void KoProgressUpdater::removePersistentSubtask(QPointer<KoUpdater> updater)
0144 {
0145     {
0146         QMutexLocker l(&d->mutex);
0147 
0148         for (auto it = d->subtasks.begin(); it != d->subtasks.end();) {
0149             if ((*it)->connectedUpdater() != updater) {
0150                 ++it;
0151             } else {
0152                 KIS_SAFE_ASSERT_RECOVER_NOOP((*it)->isPersistent());
0153                 (*it)->deleteLater();
0154                 it = d->subtasks.erase(it);
0155                 break;
0156             }
0157         }
0158     }
0159 
0160     Q_EMIT triggerUpdateAsynchronously();
0161 }
0162 
0163 void KoProgressUpdater::cancel()
0164 {
0165     KIS_SAFE_ASSERT_RECOVER_RETURN(QThread::currentThread() == this->thread());
0166 
0167     QList<QPointer<KoUpdaterPrivate> > subtasks;
0168 
0169     {
0170         QMutexLocker l(&d->mutex);
0171         subtasks = d->subtasks;
0172     }
0173 
0174     Q_FOREACH (QPointer<KoUpdaterPrivate> updater, subtasks) {
0175         if (!updater) continue;
0176 
0177         updater->setProgress(100);
0178         updater->setInterrupted(true);
0179     }
0180     d->canceled = true;
0181 
0182     Q_EMIT triggerUpdateAsynchronously();
0183 }
0184 
0185 void KoProgressUpdater::update()
0186 {
0187     KIS_SAFE_ASSERT_RECOVER_RETURN(QThread::currentThread() == this->thread());
0188 
0189     if (d->mode == Unthreaded) {
0190         qApp->processEvents();
0191     }
0192 
0193     d->updateCompressor->start();
0194 }
0195 
0196 void KoProgressUpdater::updateUi()
0197 {
0198     KIS_SAFE_ASSERT_RECOVER_RETURN(QThread::currentThread() == this->thread());
0199 
0200     // This function runs in the app main thread. All the progress
0201     // updates arrive at the KoUpdaterPrivate instances through
0202     // queued connections, so until we relinquish control to the
0203     // event loop, the progress values cannot change, and that
0204     // won't happen until we return from this function (which is
0205     // triggered by a timer)
0206 
0207     {
0208         QMutexLocker l(&d->mutex);
0209 
0210         if (!d->subtasks.isEmpty()) {
0211             int totalProgress = 0;
0212             int totalWeight = 0;
0213             d->isUndefinedState = false;
0214 
0215             Q_FOREACH (QPointer<KoUpdaterPrivate> updater, d->subtasks) {
0216                 if (updater->interrupted()) {
0217                     d->currentProgress = -1;
0218                     break;
0219                 }
0220 
0221                 if (!updater->hasValidRange()) {
0222                     totalWeight = 0;
0223                     totalProgress = 0;
0224                     d->isUndefinedState = true;
0225                     break;
0226                 }
0227 
0228                 if (updater->isPersistent() && updater->isCompleted()) {
0229                     continue;
0230                 }
0231 
0232                 const int progress = qBound(0, updater->progress(), 100);
0233                 totalProgress += progress * updater->weight();
0234                 totalWeight += updater->weight();
0235             }
0236 
0237             const int progressPercent = totalWeight > 0 ? totalProgress / totalWeight : -1;
0238 
0239             d->currentProgress =
0240                     d->taskMax == 99 ?
0241                         progressPercent :
0242                         qRound(qreal(progressPercent) * d->taskMax / 99.0);
0243         }
0244 
0245     }
0246 
0247     if (d->progressProxy()) {
0248         if (!d->isUndefinedState) {
0249             d->progressProxy()->setRange(0, d->taskMax);
0250 
0251             if (d->currentProgress == -1) {
0252                 d->currentProgress = d->progressProxy()->maximum();
0253             }
0254 
0255             if (d->currentProgress >= d->progressProxy()->maximum()) {
0256                 {
0257                     QMutexLocker l(&d->mutex);
0258                     d->clearState();
0259                 }
0260                 d->progressProxy()->setRange(0, d->taskMax);
0261                 d->progressProxy()->setValue(d->progressProxy()->maximum());
0262             } else {
0263                 d->progressProxy()->setValue(d->currentProgress);
0264             }
0265         } else {
0266             d->progressProxy()->setRange(0,0);
0267             d->progressProxy()->setValue(0);
0268         }
0269 
0270         d->updateParentText();
0271     }
0272 }
0273 
0274 void KoProgressUpdater::Private::updateParentText()
0275 {
0276     if (!progressProxy()) return;
0277 
0278     QString actionName = taskName;
0279 
0280     if (autoNestNames) {
0281         Q_FOREACH (QPointer<KoUpdaterPrivate> updater, subtasks) {
0282 
0283             if (updater->isPersistent() && updater->isCompleted()) {
0284                 continue;
0285             }
0286 
0287             if (updater->progress() < 100) {
0288                 const QString subTaskName = updater->mergedSubTaskName();
0289 
0290                 if (!subTaskName.isEmpty()) {
0291                     if (actionName.isEmpty()) {
0292                         actionName = subTaskName;
0293                     } else {
0294                         actionName = QString("%1: %2").arg(actionName).arg(subTaskName);
0295                     }
0296                 }
0297                 break;
0298             }
0299         }
0300         progressProxy()->setAutoNestedName(actionName);
0301     } else {
0302         progressProxy()->setFormat(actionName);
0303     }
0304 
0305 }
0306 
0307 void KoProgressUpdater::Private::clearState()
0308 {
0309     for (auto it = subtasks.begin(); it != subtasks.end();) {
0310         if (!(*it)->isPersistent()) {
0311             (*it)->deleteLater();
0312             it = subtasks.erase(it);
0313         } else {
0314             if ((*it)->interrupted()) {
0315                 (*it)->setInterrupted(false);
0316             }
0317             ++it;
0318         }
0319     }
0320 
0321     canceled = false;
0322 }
0323 
0324 bool KoProgressUpdater::interrupted() const
0325 {
0326     return d->canceled;
0327 }
0328 
0329 void KoProgressUpdater::setUpdateInterval(int ms)
0330 {
0331     d->updateCompressor->setDelay(ms);
0332 }
0333 
0334 int KoProgressUpdater::updateInterval() const
0335 {
0336     return d->updateCompressor->delay();
0337 }
0338 
0339 void KoProgressUpdater::setAutoNestNames(bool value)
0340 {
0341     d->autoNestNames = value;
0342 }
0343 
0344 bool KoProgressUpdater::autoNestNames() const
0345 {
0346     return d->autoNestNames;
0347 }