File indexing completed on 2024-05-19 04:29:54

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(weight, name, isPersistent);
0130 
0131     {
0132         QMutexLocker l(&d->mutex);
0133         d->subtasks.append(p);
0134     }
0135     connect(p, SIGNAL(sigUpdated()), SLOT(update()));
0136     connect(p, SIGNAL(sigCancelled()), SLOT(cancel()));
0137 
0138     QPointer<KoUpdater> updater = p->connectedUpdater();
0139 
0140     Q_EMIT triggerUpdateAsynchronously();
0141     return updater;
0142 }
0143 
0144 void KoProgressUpdater::removePersistentSubtask(QPointer<KoUpdater> updater)
0145 {
0146     {
0147         QMutexLocker l(&d->mutex);
0148 
0149         for (auto it = d->subtasks.begin(); it != d->subtasks.end();) {
0150             if ((*it)->connectedUpdater() != updater) {
0151                 ++it;
0152             } else {
0153                 KIS_SAFE_ASSERT_RECOVER_NOOP((*it)->isPersistent());
0154                 (*it)->deleteLater();
0155                 it = d->subtasks.erase(it);
0156                 break;
0157             }
0158         }
0159     }
0160 
0161     Q_EMIT triggerUpdateAsynchronously();
0162 }
0163 
0164 void KoProgressUpdater::cancel()
0165 {
0166     KIS_SAFE_ASSERT_RECOVER_RETURN(QThread::currentThread() == this->thread());
0167 
0168     QList<QPointer<KoUpdaterPrivate> > subtasks;
0169 
0170     {
0171         QMutexLocker l(&d->mutex);
0172         subtasks = d->subtasks;
0173     }
0174 
0175     Q_FOREACH (QPointer<KoUpdaterPrivate> updater, subtasks) {
0176         if (!updater) continue;
0177 
0178         updater->setProgress(100);
0179         updater->setInterrupted(true);
0180     }
0181     d->canceled = true;
0182 
0183     Q_EMIT triggerUpdateAsynchronously();
0184 }
0185 
0186 void KoProgressUpdater::update()
0187 {
0188     KIS_SAFE_ASSERT_RECOVER_RETURN(QThread::currentThread() == this->thread());
0189 
0190     if (d->mode == Unthreaded) {
0191         qApp->processEvents();
0192     }
0193 
0194     d->updateCompressor->start();
0195 }
0196 
0197 void KoProgressUpdater::updateUi()
0198 {
0199     KIS_SAFE_ASSERT_RECOVER_RETURN(QThread::currentThread() == this->thread());
0200 
0201     // This function runs in the app main thread. All the progress
0202     // updates arrive at the KoUpdaterPrivate instances through
0203     // queued connections, so until we relinquish control to the
0204     // event loop, the progress values cannot change, and that
0205     // won't happen until we return from this function (which is
0206     // triggered by a timer)
0207 
0208     {
0209         QMutexLocker l(&d->mutex);
0210 
0211         if (!d->subtasks.isEmpty()) {
0212             int totalProgress = 0;
0213             int totalWeight = 0;
0214             d->isUndefinedState = false;
0215 
0216             Q_FOREACH (QPointer<KoUpdaterPrivate> updater, d->subtasks) {
0217                 if (updater->interrupted()) {
0218                     d->currentProgress = -1;
0219                     break;
0220                 }
0221 
0222                 if (!updater->hasValidRange()) {
0223                     totalWeight = 0;
0224                     totalProgress = 0;
0225                     d->isUndefinedState = true;
0226                     break;
0227                 }
0228 
0229                 if (updater->isPersistent() && updater->isCompleted()) {
0230                     continue;
0231                 }
0232 
0233                 const int progress = qBound(0, updater->progress(), 100);
0234                 totalProgress += progress * updater->weight();
0235                 totalWeight += updater->weight();
0236             }
0237 
0238             const int progressPercent = totalWeight > 0 ? totalProgress / totalWeight : -1;
0239 
0240             d->currentProgress =
0241                     d->taskMax == 99 ?
0242                         progressPercent :
0243                         qRound(qreal(progressPercent) * d->taskMax / 99.0);
0244         }
0245 
0246     }
0247 
0248     if (d->progressProxy()) {
0249         if (!d->isUndefinedState) {
0250             d->progressProxy()->setRange(0, d->taskMax);
0251 
0252             if (d->currentProgress == -1) {
0253                 d->currentProgress = d->progressProxy()->maximum();
0254             }
0255 
0256             if (d->currentProgress >= d->progressProxy()->maximum()) {
0257                 {
0258                     QMutexLocker l(&d->mutex);
0259                     d->clearState();
0260                 }
0261                 d->progressProxy()->setRange(0, d->taskMax);
0262                 d->progressProxy()->setValue(d->progressProxy()->maximum());
0263             } else {
0264                 d->progressProxy()->setValue(d->currentProgress);
0265             }
0266         } else {
0267             d->progressProxy()->setRange(0,0);
0268             d->progressProxy()->setValue(0);
0269         }
0270 
0271         d->updateParentText();
0272     }
0273 }
0274 
0275 void KoProgressUpdater::Private::updateParentText()
0276 {
0277     if (!progressProxy()) return;
0278 
0279     QString actionName = taskName;
0280 
0281     if (autoNestNames) {
0282         Q_FOREACH (QPointer<KoUpdaterPrivate> updater, subtasks) {
0283 
0284             if (updater->isPersistent() && updater->isCompleted()) {
0285                 continue;
0286             }
0287 
0288             if (updater->progress() < 100) {
0289                 const QString subTaskName = updater->mergedSubTaskName();
0290 
0291                 if (!subTaskName.isEmpty()) {
0292                     if (actionName.isEmpty()) {
0293                         actionName = subTaskName;
0294                     } else {
0295                         actionName = QString("%1: %2").arg(actionName).arg(subTaskName);
0296                     }
0297                 }
0298                 break;
0299             }
0300         }
0301         progressProxy()->setAutoNestedName(actionName);
0302     } else {
0303         progressProxy()->setFormat(actionName);
0304     }
0305 
0306 }
0307 
0308 void KoProgressUpdater::Private::clearState()
0309 {
0310     for (auto it = subtasks.begin(); it != subtasks.end();) {
0311         if (!(*it)->isPersistent()) {
0312             (*it)->deleteLater();
0313             it = subtasks.erase(it);
0314         } else {
0315             if ((*it)->interrupted()) {
0316                 (*it)->setInterrupted(false);
0317             }
0318             ++it;
0319         }
0320     }
0321 
0322     canceled = false;
0323 }
0324 
0325 bool KoProgressUpdater::interrupted() const
0326 {
0327     return d->canceled;
0328 }
0329 
0330 void KoProgressUpdater::setUpdateInterval(int ms)
0331 {
0332     d->updateCompressor->setDelay(ms);
0333 }
0334 
0335 int KoProgressUpdater::updateInterval() const
0336 {
0337     return d->updateCompressor->delay();
0338 }
0339 
0340 void KoProgressUpdater::setAutoNestNames(bool value)
0341 {
0342     d->autoNestNames = value;
0343 }
0344 
0345 bool KoProgressUpdater::autoNestNames() const
0346 {
0347     return d->autoNestNames;
0348 }