feat: better task tracking
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
This commit is contained in:
		| @@ -909,6 +909,8 @@ SET(LAUNCHER_SOURCES | ||||
|     ui/widgets/VariableSizedImageObject.cpp | ||||
|     ui/widgets/ProjectItem.h | ||||
|     ui/widgets/ProjectItem.cpp | ||||
|     ui/widgets/SubTaskProgressBar.h | ||||
|     ui/widgets/SubTaskProgressBar.cpp | ||||
|     ui/widgets/VersionListView.cpp | ||||
|     ui/widgets/VersionListView.h | ||||
|     ui/widgets/VersionSelectWidget.cpp | ||||
| @@ -969,6 +971,7 @@ qt_wrap_ui(LAUNCHER_UI | ||||
|     ui/widgets/CustomCommands.ui | ||||
|     ui/widgets/InfoFrame.ui | ||||
|     ui/widgets/ModFilterWidget.ui | ||||
|     ui/widgets/SubTaskProgressBar.ui | ||||
|     ui/widgets/ThemeCustomizationWidget.ui | ||||
|     ui/dialogs/CopyInstanceDialog.ui | ||||
|     ui/dialogs/ProfileSetupDialog.ui | ||||
|   | ||||
| @@ -48,12 +48,15 @@ | ||||
| #include "BuildConfig.h" | ||||
| #include "Application.h" | ||||
|  | ||||
| Q_LOGGING_CATEGORY(DownloadLogC, "Task.Net.Download") | ||||
|  | ||||
| namespace Net { | ||||
|  | ||||
| auto Download::makeCached(QUrl url, MetaEntryPtr entry, Options options) -> Download::Ptr | ||||
| { | ||||
|     auto dl = makeShared<Download>(); | ||||
|     dl->m_url = url; | ||||
|     dl->setObjectName(QString("CACHE:") + url.toString()); | ||||
|     dl->m_options = options; | ||||
|     auto md5Node = new ChecksumValidator(QCryptographicHash::Md5); | ||||
|     auto cachedNode = new MetaCacheSink(entry, md5Node, options.testFlag(Option::MakeEternal)); | ||||
| @@ -65,6 +68,7 @@ auto Download::makeByteArray(QUrl url, QByteArray* output, Options options) -> D | ||||
| { | ||||
|     auto dl = makeShared<Download>(); | ||||
|     dl->m_url = url; | ||||
|     dl->setObjectName(QString("BYTES:") + url.toString()); | ||||
|     dl->m_options = options; | ||||
|     dl->m_sink.reset(new ByteArraySink(output)); | ||||
|     return dl; | ||||
| @@ -74,6 +78,7 @@ auto Download::makeFile(QUrl url, QString path, Options options) -> Download::Pt | ||||
| { | ||||
|     auto dl = makeShared<Download>(); | ||||
|     dl->m_url = url; | ||||
|     dl->setObjectName(QString("FILE:") + url.toString()); | ||||
|     dl->m_options = options; | ||||
|     dl->m_sink.reset(new FileSink(path)); | ||||
|     return dl; | ||||
| @@ -89,7 +94,7 @@ void Download::executeTask() | ||||
|     setStatus(tr("Downloading %1").arg(m_url.toString())); | ||||
|  | ||||
|     if (getState() == Task::State::AbortedByUser) { | ||||
|         qWarning() << "Attempt to start an aborted Download:" << m_url.toString(); | ||||
|         qCWarning(DownloadLogC) << getUid().toString() << "Attempt to start an aborted Download:" << m_url.toString(); | ||||
|         emitAborted(); | ||||
|         return; | ||||
|     } | ||||
| @@ -99,10 +104,10 @@ void Download::executeTask() | ||||
|     switch (m_state) { | ||||
|         case State::Succeeded: | ||||
|             emit succeeded(); | ||||
|             qDebug() << "Download cache hit " << m_url.toString(); | ||||
|             qCDebug(DownloadLogC) << getUid().toString() << "Download cache hit " << m_url.toString(); | ||||
|             return; | ||||
|         case State::Running: | ||||
|             qDebug() << "Downloading " << m_url.toString(); | ||||
|             qCDebug(DownloadLogC) << getUid().toString() << "Downloading " << m_url.toString(); | ||||
|             break; | ||||
|         case State::Inactive: | ||||
|         case State::Failed: | ||||
| @@ -124,6 +129,9 @@ void Download::executeTask() | ||||
|             request.setRawHeader("Authorization", token.toUtf8()); | ||||
|     } | ||||
|      | ||||
|     m_last_progress_time = m_clock.now(); | ||||
|     m_last_progress_bytes = 0; | ||||
|  | ||||
|     QNetworkReply* rep = m_network->get(request); | ||||
|      | ||||
|     m_reply.reset(rep); | ||||
| @@ -140,13 +148,21 @@ void Download::executeTask() | ||||
|  | ||||
| void Download::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) | ||||
| { | ||||
|     auto now = m_clock.now(); | ||||
|     auto elapsed = now - m_last_progress_time; | ||||
|     auto elapsed_ms = std::chrono::duration_cast<std::chrono::milliseconds>(elapsed).count(); | ||||
|     auto bytes_recived_since = bytesReceived - m_last_progress_bytes; | ||||
|  | ||||
|     auto speed = humanReadableFileSize(bytes_recived_since / elapsed_ms * 1000) + "/s"; | ||||
|     m_details = speed;     | ||||
|  | ||||
|     setProgress(bytesReceived, bytesTotal); | ||||
| } | ||||
|  | ||||
| void Download::downloadError(QNetworkReply::NetworkError error) | ||||
| { | ||||
|     if (error == QNetworkReply::OperationCanceledError) { | ||||
|         qCritical() << "Aborted " << m_url.toString(); | ||||
|         qCCritical(DownloadLogC) << getUid().toString() << "Aborted " << m_url.toString(); | ||||
|         m_state = State::AbortedByUser; | ||||
|     } else { | ||||
|         if (m_options & Option::AcceptLocalFiles) { | ||||
| @@ -156,7 +172,7 @@ void Download::downloadError(QNetworkReply::NetworkError error) | ||||
|             } | ||||
|         } | ||||
|         // error happened during download. | ||||
|         qCritical() << "Failed " << m_url.toString() << " with reason " << error; | ||||
|         qCCritical(DownloadLogC) << getUid().toString() << "Failed " << m_url.toString() << " with reason " << error; | ||||
|         m_state = State::Failed; | ||||
|     } | ||||
| } | ||||
| @@ -165,9 +181,9 @@ void Download::sslErrors(const QList<QSslError>& errors) | ||||
| { | ||||
|     int i = 1; | ||||
|     for (auto error : errors) { | ||||
|         qCritical() << "Download" << m_url.toString() << "SSL Error #" << i << " : " << error.errorString(); | ||||
|         qCCritical(DownloadLogC) << getUid().toString() << "Download" << m_url.toString() << "SSL Error #" << i << " : " << error.errorString(); | ||||
|         auto cert = error.certificate(); | ||||
|         qCritical() << "Certificate in question:\n" << cert.toText(); | ||||
|         qCCritical(DownloadLogC) << getUid().toString() << "Certificate in question:\n" << cert.toText(); | ||||
|         i++; | ||||
|     } | ||||
| } | ||||
| @@ -210,17 +226,17 @@ auto Download::handleRedirect() -> bool | ||||
|          */ | ||||
|         redirect = QUrl(redirectStr, QUrl::TolerantMode); | ||||
|         if (!redirect.isValid()) { | ||||
|             qWarning() << "Failed to parse redirect URL:" << redirectStr; | ||||
|             qCWarning(DownloadLogC) << getUid().toString() << "Failed to parse redirect URL:" << redirectStr; | ||||
|             downloadError(QNetworkReply::ProtocolFailure); | ||||
|             return false; | ||||
|         } | ||||
|         qDebug() << "Fixed location header:" << redirect; | ||||
|         qCDebug(DownloadLogC) << getUid().toString() << "Fixed location header:" << redirect; | ||||
|     } else { | ||||
|         qDebug() << "Location header:" << redirect; | ||||
|         qCDebug(DownloadLogC) << getUid().toString() << "Location header:" << redirect; | ||||
|     } | ||||
|  | ||||
|     m_url = QUrl(redirect.toString()); | ||||
|     qDebug() << "Following redirect to " << m_url.toString(); | ||||
|     qCDebug(DownloadLogC) << getUid().toString() << "Following redirect to " << m_url.toString(); | ||||
|     startAction(m_network); | ||||
|  | ||||
|     return true; | ||||
| @@ -230,26 +246,26 @@ void Download::downloadFinished() | ||||
| { | ||||
|     // handle HTTP redirection first | ||||
|     if (handleRedirect()) { | ||||
|         qDebug() << "Download redirected:" << m_url.toString(); | ||||
|         qCDebug(DownloadLogC) << getUid().toString() << "Download redirected:" << m_url.toString(); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     // if the download failed before this point ... | ||||
|     if (m_state == State::Succeeded)  // pretend to succeed so we continue processing :) | ||||
|     { | ||||
|         qDebug() << "Download failed but we are allowed to proceed:" << m_url.toString(); | ||||
|         qCDebug(DownloadLogC) << getUid().toString() << "Download failed but we are allowed to proceed:" << m_url.toString(); | ||||
|         m_sink->abort(); | ||||
|         m_reply.reset(); | ||||
|         emit succeeded(); | ||||
|         return; | ||||
|     } else if (m_state == State::Failed) { | ||||
|         qDebug() << "Download failed in previous step:" << m_url.toString(); | ||||
|         qCDebug(DownloadLogC) << getUid().toString() << "Download failed in previous step:" << m_url.toString(); | ||||
|         m_sink->abort(); | ||||
|         m_reply.reset(); | ||||
|         emit failed(""); | ||||
|         return; | ||||
|     } else if (m_state == State::AbortedByUser) { | ||||
|         qDebug() << "Download aborted in previous step:" << m_url.toString(); | ||||
|         qCDebug(DownloadLogC) << getUid().toString() << "Download aborted in previous step:" << m_url.toString(); | ||||
|         m_sink->abort(); | ||||
|         m_reply.reset(); | ||||
|         emit aborted(); | ||||
| @@ -259,14 +275,14 @@ void Download::downloadFinished() | ||||
|     // make sure we got all the remaining data, if any | ||||
|     auto data = m_reply->readAll(); | ||||
|     if (data.size()) { | ||||
|         qDebug() << "Writing extra" << data.size() << "bytes"; | ||||
|         qCDebug(DownloadLogC) << getUid().toString() << "Writing extra" << data.size() << "bytes"; | ||||
|         m_state = m_sink->write(data); | ||||
|     } | ||||
|  | ||||
|     // otherwise, finalize the whole graph | ||||
|     m_state = m_sink->finalize(*m_reply.get()); | ||||
|     if (m_state != State::Succeeded) { | ||||
|         qDebug() << "Download failed to finalize:" << m_url.toString(); | ||||
|         qCDebug(DownloadLogC) << getUid().toString() << "Download failed to finalize:" << m_url.toString(); | ||||
|         m_sink->abort(); | ||||
|         m_reply.reset(); | ||||
|         emit failed(""); | ||||
| @@ -274,7 +290,7 @@ void Download::downloadFinished() | ||||
|     } | ||||
|  | ||||
|     m_reply.reset(); | ||||
|     qDebug() << "Download succeeded:" << m_url.toString(); | ||||
|     qCDebug(DownloadLogC) << getUid().toString() << "Download succeeded:" << m_url.toString(); | ||||
|     emit succeeded(); | ||||
| } | ||||
|  | ||||
| @@ -284,11 +300,11 @@ void Download::downloadReadyRead() | ||||
|         auto data = m_reply->readAll(); | ||||
|         m_state = m_sink->write(data); | ||||
|         if (m_state == State::Failed) { | ||||
|             qCritical() << "Failed to process response chunk"; | ||||
|             qCCritical(DownloadLogC) << getUid().toString() << "Failed to process response chunk"; | ||||
|         } | ||||
|         // qDebug() << "Download" << m_url.toString() << "gained" << data.size() << "bytes"; | ||||
|     } else { | ||||
|         qCritical() << "Cannot write download data! illegal status " << m_status; | ||||
|         qCCritical(DownloadLogC) << getUid().toString() << "Cannot write download data! illegal status " << m_status; | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -22,6 +22,7 @@ | ||||
|  *      Copyright 2013-2021 MultiMC Contributors | ||||
|  * | ||||
|  *      Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  | ||||
|  *      you may not use this file except in compliance with the License. | ||||
|  *      You may obtain a copy of the License at | ||||
|  * | ||||
| @@ -36,6 +37,8 @@ | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <chrono> | ||||
|  | ||||
| #include "HttpMetaCache.h" | ||||
| #include "NetAction.h" | ||||
| #include "Sink.h" | ||||
| @@ -63,6 +66,7 @@ class Download : public NetAction { | ||||
|     void addValidator(Validator* v); | ||||
|     auto abort() -> bool override; | ||||
|     auto canAbort() const -> bool override { return true; }; | ||||
|     auto getDetails() const -> QString override {return m_details; }; | ||||
|  | ||||
|    private: | ||||
|     auto handleRedirect() -> bool; | ||||
| @@ -80,6 +84,12 @@ class Download : public NetAction { | ||||
|    private: | ||||
|     std::unique_ptr<Sink> m_sink; | ||||
|     Options m_options; | ||||
|  | ||||
|     std::chrono::steady_clock m_clock; | ||||
|     std::chrono::time_point<std::chrono::steady_clock> m_last_progress_time; | ||||
|     qint64 m_last_progress_bytes; | ||||
|  | ||||
|     QString m_details; | ||||
| }; | ||||
| }  // namespace Net | ||||
|  | ||||
|   | ||||
| @@ -35,12 +35,33 @@ | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <cmath> | ||||
|  | ||||
| #include <QNetworkReply> | ||||
| #include <QUrl> | ||||
|  | ||||
| #include "QObjectPtr.h" | ||||
| #include "tasks/Task.h" | ||||
|  | ||||
| static const QStringList s_units_si  {"kb", "MB", "GB", "TB"}; | ||||
| static const QStringList s_units_kibi {"kiB", "MiB", "Gib", "TiB"}; | ||||
|  | ||||
| inline QString humanReadableFileSize(qint64 bytes, bool use_si = false, int decimal_points = 1) { | ||||
|     const QStringList units = use_si ? s_units_si : s_units_kibi; | ||||
|     const int scale = use_si ? 1000 : 1024; | ||||
|     double size = bytes; | ||||
|  | ||||
|     int u = -1; | ||||
|     double r = pow(10,  decimal_points); | ||||
|  | ||||
|     do { | ||||
|         size /= scale; | ||||
|         u++; | ||||
|     } while (round(abs(size) * r) / r >= scale && u < units.length() - 1); | ||||
|  | ||||
|     return QString::number(size, 'f', 2) + " " + units[u]; | ||||
| } | ||||
|  | ||||
| class NetAction : public Task { | ||||
|     Q_OBJECT | ||||
| protected: | ||||
|   | ||||
| @@ -15,14 +15,13 @@ ConcurrentTask::~ConcurrentTask() | ||||
|     } | ||||
| } | ||||
|  | ||||
| auto ConcurrentTask::getStepProgress() const -> qint64 | ||||
| auto ConcurrentTask::getStepProgress() const -> QList<TaskStepProgress> | ||||
| { | ||||
|     return m_stepProgress; | ||||
|     QList<TaskStepProgress> task_progress; | ||||
|     for (auto progress : task_progress) { | ||||
|         task_progress.append(task_progress); | ||||
|     } | ||||
|  | ||||
| auto ConcurrentTask::getStepTotalProgress() const -> qint64 | ||||
| { | ||||
|     return m_stepTotalProgress; | ||||
|     return task_progress; | ||||
| } | ||||
|  | ||||
| void ConcurrentTask::addTask(Task::Ptr task) | ||||
| @@ -33,11 +32,14 @@ void ConcurrentTask::addTask(Task::Ptr task) | ||||
| void ConcurrentTask::executeTask() | ||||
| { | ||||
|     // Start the least amount of tasks needed, but at least one | ||||
|     int num_starts = qMax(1, qMin(m_total_max_size, m_queue.size())); | ||||
|     for (int i = 0; i < num_starts; i++) { | ||||
|     // int num_starts = qMax(1, qMin(m_total_max_size, m_queue.size())); | ||||
|     // for (int i = 0; i < num_starts; i++) { | ||||
|     //     QMetaObject::invokeMethod(this, &ConcurrentTask::startNext, Qt::QueuedConnection); | ||||
|     // } | ||||
|     // Start One task, startNext hadles starting the up to the m_total_max_size | ||||
|     // while tracking the number currently being done | ||||
|     QMetaObject::invokeMethod(this, &ConcurrentTask::startNext, Qt::QueuedConnection); | ||||
| } | ||||
| } | ||||
|  | ||||
| bool ConcurrentTask::abort() | ||||
| { | ||||
| @@ -97,17 +99,18 @@ void ConcurrentTask::startNext() | ||||
|  | ||||
|     Task::Ptr next = m_queue.dequeue(); | ||||
|  | ||||
|     connect(next.get(), &Task::succeeded, this, [this, next] { subTaskSucceeded(next); }); | ||||
|     connect(next.get(), &Task::succeeded, this, [this, next](){ subTaskSucceeded(next); }); | ||||
|     connect(next.get(), &Task::failed, this, [this, next](QString msg) { subTaskFailed(next, msg); }); | ||||
|  | ||||
|     connect(next.get(), &Task::status, this, &ConcurrentTask::subTaskStatus); | ||||
|     connect(next.get(), &Task::stepStatus, this, &ConcurrentTask::subTaskStatus); | ||||
|     connect(next.get(), &Task::status, this, [this, next](QString msg){ subTaskStatus(next, msg); }); | ||||
|     connect(next.get(), &Task::stepProgress, this, [this, next](QList<TaskStepProgress> tp){ subTaskStepProgress(next, tp); }); | ||||
|  | ||||
|     connect(next.get(), &Task::progress, this, &ConcurrentTask::subTaskProgress); | ||||
|     connect(next.get(), &Task::progress, this, [this, next](qint64 current, qint64 total){ subTaskProgress(next, current, total); }); | ||||
|  | ||||
|     m_doing.insert(next.get(), next); | ||||
|     m_task_progress.insert(next->getUid(), std::make_shared<TaskStepProgress>(TaskStepProgress({next->getUid()})));  | ||||
|  | ||||
|  | ||||
|     setStepStatus(next->isMultiStep() ? next->getStepStatus() : next->getStatus()); | ||||
|     updateState(); | ||||
|  | ||||
|     QCoreApplication::processEvents(); | ||||
| @@ -123,7 +126,10 @@ void ConcurrentTask::startNext() | ||||
| void ConcurrentTask::subTaskSucceeded(Task::Ptr task) | ||||
| { | ||||
|     m_done.insert(task.get(), task); | ||||
|     m_succeeded.insert(task.get(), task); | ||||
|  | ||||
|     m_doing.remove(task.get()); | ||||
|     m_task_progress.value(task->getUid())->state = TaskState::Succeeded; | ||||
|  | ||||
|     disconnect(task.get(), 0, this, 0); | ||||
|  | ||||
| @@ -138,6 +144,7 @@ void ConcurrentTask::subTaskFailed(Task::Ptr task, const QString& msg) | ||||
|     m_failed.insert(task.get(), task); | ||||
|  | ||||
|     m_doing.remove(task.get()); | ||||
|     m_task_progress.value(task->getUid())->state = TaskState::Failed; | ||||
|  | ||||
|     disconnect(task.get(), 0, this, 0); | ||||
|  | ||||
| @@ -146,20 +153,64 @@ void ConcurrentTask::subTaskFailed(Task::Ptr task, const QString& msg) | ||||
|     startNext(); | ||||
| } | ||||
|  | ||||
| void ConcurrentTask::subTaskStatus(const QString& msg) | ||||
| void ConcurrentTask::subTaskStatus(Task::Ptr task, const QString& msg) | ||||
| { | ||||
|     setStepStatus(msg); | ||||
|     auto taskProgress = m_task_progress.value(task->getUid()); | ||||
|     taskProgress->status = msg; | ||||
|     updateState(); | ||||
| } | ||||
|  | ||||
| void ConcurrentTask::subTaskProgress(qint64 current, qint64 total) | ||||
| void ConcurrentTask::subTaskProgress(Task::Ptr task, qint64 current, qint64 total) | ||||
| { | ||||
|     auto taskProgress = m_task_progress.value(task->getUid()); | ||||
|      | ||||
|     taskProgress->current = current; | ||||
|     taskProgress->total = total; | ||||
|  | ||||
|     taskProgress->details = task->getDetails();  | ||||
|  | ||||
|     updateStepProgress(); | ||||
|     updateState(); | ||||
| } | ||||
|  | ||||
| void ConcurrentTask::subTaskStepProgress(Task::Ptr task, QList<TaskStepProgress> task_step_progress) | ||||
| { | ||||
|     for (auto progress : task_step_progress) { | ||||
|         if (!m_task_progress.contains(progress.uid)) | ||||
|             m_task_progress.insert(progress.uid, std::make_shared<TaskStepProgress>(progress)); | ||||
|  | ||||
|  | ||||
|     } | ||||
|      | ||||
| } | ||||
|  | ||||
| void ConcurrentTask::updateStepProgress() | ||||
| { | ||||
|    qint64 current = 0, total = 0; | ||||
|    for ( auto taskProgress : m_task_progress ) { | ||||
|        current += taskProgress->current; | ||||
|        total += taskProgress->total; | ||||
|    } | ||||
|  | ||||
|    m_stepProgress = current; | ||||
|    m_stepTotalProgress = total; | ||||
| } | ||||
|  | ||||
| void ConcurrentTask::updateState() | ||||
| { | ||||
|     if (totalSize() > 1) { | ||||
|         setProgress(m_done.count(), totalSize()); | ||||
|     setStatus(tr("Executing %1 task(s) (%2 out of %3 are done)") | ||||
|                   .arg(QString::number(m_doing.count()), QString::number(m_done.count()), QString::number(totalSize()))); | ||||
|         setStatus(tr("Executing %1 task(s) (%2 out of %3 are done)").arg(QString::number(m_doing.count()), QString::number(m_done.count()), QString::number(totalSize()))); | ||||
|     } else { | ||||
|         setProgress(m_stepProgress, m_stepTotalProgress); | ||||
|         QString status = tr("Please wait ..."); | ||||
|         if (m_queue.size() > 0) { | ||||
|             status = tr("Waiting for 1 task to start ..."); | ||||
|         } else if (m_doing.size() > 0) { | ||||
|             status = tr("Executing 1 task:"); | ||||
|         } else if (m_done.size() > 0) { | ||||
|             status = tr("Task finished."); | ||||
|         } | ||||
|         setStatus(status); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,7 +1,10 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include <QUuid> | ||||
| #include <QHash> | ||||
| #include <QQueue> | ||||
| #include <QSet> | ||||
| #include <memory> | ||||
|  | ||||
| #include "tasks/Task.h" | ||||
|  | ||||
| @@ -16,10 +19,7 @@ public: | ||||
|     bool canAbort() const override { return true; } | ||||
|  | ||||
|     inline auto isMultiStep() const -> bool override { return m_queue.size() > 1; }; | ||||
|     auto getStepProgress() const -> qint64 override; | ||||
|     auto getStepTotalProgress() const -> qint64 override; | ||||
|  | ||||
|     inline auto getStepStatus() const -> QString override { return m_step_status; } | ||||
|     auto getStepProgress() const -> QList<TaskStepProgress> override; | ||||
|  | ||||
|     void addTask(Task::Ptr task); | ||||
|  | ||||
| @@ -39,14 +39,15 @@ slots: | ||||
|  | ||||
|     void subTaskSucceeded(Task::Ptr); | ||||
|     void subTaskFailed(Task::Ptr, const QString &msg); | ||||
|     void subTaskStatus(const QString &msg); | ||||
|     void subTaskProgress(qint64 current, qint64 total); | ||||
|     void subTaskStatus(Task::Ptr task, const QString &msg); | ||||
|     void subTaskProgress(Task::Ptr task, qint64 current, qint64 total); | ||||
|     void subTaskStepProgress(Task::Ptr task, QList<TaskStepProgress> task_step_progress); | ||||
|  | ||||
| protected: | ||||
|     // NOTE: This is not thread-safe. | ||||
|     [[nodiscard]] unsigned int totalSize() const { return m_queue.size() + m_doing.size() + m_done.size(); } | ||||
|  | ||||
|     void setStepStatus(QString status) { m_step_status = status; emit stepStatus(status); }; | ||||
|     void updateStepProgress(); | ||||
|  | ||||
|     virtual void updateState(); | ||||
|  | ||||
| @@ -59,6 +60,9 @@ protected: | ||||
|     QHash<Task*, Task::Ptr> m_doing;  | ||||
|     QHash<Task*, Task::Ptr> m_done; | ||||
|     QHash<Task*, Task::Ptr> m_failed; | ||||
|     QHash<Task*, Task::Ptr> m_succeeded; | ||||
|  | ||||
|     QHash<QUuid, std::shared_ptr<TaskStepProgress>> m_task_progress; | ||||
|  | ||||
|     int m_total_max_size; | ||||
|  | ||||
|   | ||||
| @@ -37,8 +37,11 @@ | ||||
|  | ||||
| #include <QDebug> | ||||
|  | ||||
| Q_LOGGING_CATEGORY(TaskLogC, "Task") | ||||
|  | ||||
| Task::Task(QObject *parent, bool show_debug) : QObject(parent), m_show_debug(show_debug) | ||||
| { | ||||
|     m_uid = QUuid::createUuid(); | ||||
|     setAutoDelete(false); | ||||
| } | ||||
|  | ||||
| @@ -65,31 +68,31 @@ void Task::start() | ||||
|         case State::Inactive: | ||||
|         { | ||||
|             if (m_show_debug) | ||||
|                 qDebug() << "Task" << describe() << "starting for the first time"; | ||||
|                 qCDebug(TaskLogC) << "Task" << describe() << "starting for the first time"; | ||||
|             break; | ||||
|         } | ||||
|         case State::AbortedByUser: | ||||
|         { | ||||
|             if (m_show_debug) | ||||
|                 qDebug() << "Task" << describe() << "restarting for after being aborted by user"; | ||||
|                 qCDebug(TaskLogC) << "Task" << describe() << "restarting for after being aborted by user"; | ||||
|             break; | ||||
|         } | ||||
|         case State::Failed: | ||||
|         { | ||||
|             if (m_show_debug) | ||||
|                 qDebug() << "Task" << describe() << "restarting for after failing at first"; | ||||
|                 qCDebug(TaskLogC) << "Task" << describe() << "restarting for after failing at first"; | ||||
|             break; | ||||
|         } | ||||
|         case State::Succeeded: | ||||
|         { | ||||
|             if (m_show_debug) | ||||
|                 qDebug() << "Task" << describe() << "restarting for after succeeding at first"; | ||||
|                 qCDebug(TaskLogC) << "Task" << describe() << "restarting for after succeeding at first"; | ||||
|             break; | ||||
|         } | ||||
|         case State::Running: | ||||
|         { | ||||
|             if (m_show_debug) | ||||
|                 qWarning() << "The launcher tried to start task" << describe() << "while it was already running!"; | ||||
|                 qCWarning(TaskLogC) << "The launcher tried to start task" << describe() << "while it was already running!"; | ||||
|             return; | ||||
|         } | ||||
|     } | ||||
| @@ -104,12 +107,12 @@ void Task::emitFailed(QString reason) | ||||
|     // Don't fail twice. | ||||
|     if (!isRunning()) | ||||
|     { | ||||
|         qCritical() << "Task" << describe() << "failed while not running!!!!: " << reason; | ||||
|         qCCritical(TaskLogC) << "Task" << describe() << "failed while not running!!!!: " << reason; | ||||
|         return; | ||||
|     } | ||||
|     m_state = State::Failed; | ||||
|     m_failReason = reason; | ||||
|     qCritical() << "Task" << describe() << "failed: " << reason; | ||||
|     qCCritical(TaskLogC) << "Task" << describe() << "failed: " << reason; | ||||
|     emit failed(reason); | ||||
|     emit finished(); | ||||
| } | ||||
| @@ -119,13 +122,13 @@ void Task::emitAborted() | ||||
|     // Don't abort twice. | ||||
|     if (!isRunning()) | ||||
|     { | ||||
|         qCritical() << "Task" << describe() << "aborted while not running!!!!"; | ||||
|         qCCritical(TaskLogC) << "Task" << describe() << "aborted while not running!!!!"; | ||||
|         return; | ||||
|     } | ||||
|     m_state = State::AbortedByUser; | ||||
|     m_failReason = "Aborted."; | ||||
|     if (m_show_debug) | ||||
|         qDebug() << "Task" << describe() << "aborted."; | ||||
|         qCDebug(TaskLogC) << "Task" << describe() << "aborted."; | ||||
|     emit aborted(); | ||||
|     emit finished(); | ||||
| } | ||||
| @@ -135,12 +138,12 @@ void Task::emitSucceeded() | ||||
|     // Don't succeed twice. | ||||
|     if (!isRunning()) | ||||
|     { | ||||
|         qCritical() << "Task" << describe() << "succeeded while not running!!!!"; | ||||
|         qCCritical(TaskLogC) << "Task" << describe() << "succeeded while not running!!!!"; | ||||
|         return; | ||||
|     } | ||||
|     m_state = State::Succeeded; | ||||
|     if (m_show_debug) | ||||
|         qDebug() << "Task" << describe() << "succeeded"; | ||||
|         qCDebug(TaskLogC) << "Task" << describe() << "succeeded"; | ||||
|     emit succeeded(); | ||||
|     emit finished(); | ||||
| } | ||||
| @@ -159,6 +162,7 @@ QString Task::describe() | ||||
|     { | ||||
|         out << name; | ||||
|     } | ||||
|     out << " ID: " << m_uid.toString(QUuid::WithoutBraces); | ||||
|     out << QChar(')'); | ||||
|     out.flush(); | ||||
|     return outStr; | ||||
|   | ||||
| @@ -1,7 +1,8 @@ | ||||
| // SPDX-License-Identifier: GPL-3.0-only | ||||
| /* | ||||
|  *  PolyMC - Minecraft Launcher | ||||
|  *  PrismLauncher - Minecraft Launcher | ||||
|  *  Copyright (c) 2022 flowln <flowlnlnln@gmail.com> | ||||
|  *  Copyright (c) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com> | ||||
|  * | ||||
|  *  This program is free software: you can redistribute it and/or modify | ||||
|  *  it under the terms of the GNU General Public License as published by | ||||
| @@ -36,9 +37,29 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include <QRunnable> | ||||
| #include <QUuid> | ||||
| #include <QLoggingCategory> | ||||
|  | ||||
| #include "QObjectPtr.h" | ||||
|  | ||||
| enum class TaskState { | ||||
|     Waiting, | ||||
|     Running, | ||||
|     Failed, | ||||
|     Succeeded, | ||||
|     Finished | ||||
| }; | ||||
|  | ||||
| struct TaskStepProgress { | ||||
|     QUuid uid;  | ||||
|     qint64 current; | ||||
|     qint64 total; | ||||
|     QString status; | ||||
|     QString details; | ||||
|     TaskState state = TaskState::Waiting; | ||||
|     bool isDone() { return (state == TaskState::Failed) || (state == TaskState::Succeeded) || (state == TaskState::Finished); } | ||||
| }; | ||||
|  | ||||
| class Task : public QObject, public QRunnable { | ||||
|     Q_OBJECT | ||||
|    public: | ||||
| @@ -73,12 +94,14 @@ class Task : public QObject, public QRunnable { | ||||
|     auto getState() const -> State { return m_state; } | ||||
|  | ||||
|     QString getStatus() { return m_status; } | ||||
|     virtual auto getStepStatus() const -> QString { return m_status; } | ||||
|  | ||||
|     qint64 getProgress() { return m_progress; } | ||||
|     qint64 getTotalProgress() { return m_progressTotal; } | ||||
|     virtual auto getStepProgress() const -> qint64 { return 0; } | ||||
|     virtual auto getStepTotalProgress() const -> qint64 { return 100; } | ||||
|     virtual auto getStepProgress() const -> QList<TaskStepProgress> { return {}; } | ||||
|  | ||||
|     virtual auto getDetails() const -> QString { return ""; }  | ||||
|  | ||||
|     QUuid getUid() { return m_uid; } | ||||
|  | ||||
|    protected: | ||||
|     void logWarning(const QString& line); | ||||
| @@ -94,7 +117,7 @@ class Task : public QObject, public QRunnable { | ||||
|     void aborted(); | ||||
|     void failed(QString reason); | ||||
|     void status(QString status); | ||||
|     void stepStatus(QString status); | ||||
|     void stepProgress(QList<TaskStepProgress> task_progress); //  | ||||
|  | ||||
|     /** Emitted when the canAbort() status has changed. | ||||
|      */ | ||||
| @@ -135,4 +158,6 @@ class Task : public QObject, public QRunnable { | ||||
|    private: | ||||
|     // Change using setAbortStatus | ||||
|     bool m_can_abort = false; | ||||
|     QUuid m_uid; | ||||
|  | ||||
| }; | ||||
|   | ||||
| @@ -1,4 +1,24 @@ | ||||
| /* Copyright 2013-2021 MultiMC Contributors | ||||
| /// SPDX-License-Identifier: GPL-3.0-only | ||||
| /* | ||||
|  *  PrismLaucher - Minecraft Launcher | ||||
|  *  Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com> | ||||
|  * | ||||
|  *  This program is free software: you can redistribute it and/or modify | ||||
|  *  it under the terms of the GNU General Public License as published by | ||||
|  *  the Free Software Foundation, version 3. | ||||
|  * | ||||
|  *  This program is distributed in the hope that it will be useful, | ||||
|  *  but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|  *  GNU General Public License for more details. | ||||
|  * | ||||
|  *  You should have received a copy of the GNU General Public License | ||||
|  *  along with this program.  If not, see <https://www.gnu.org/licenses/>. | ||||
|  * | ||||
|  * This file incorporates work covered by the following copyright and | ||||
|  * permission notice: | ||||
|  * | ||||
|  *      Copyright 2013-2021 MultiMC Contributors | ||||
|  * | ||||
|  *      Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  *      you may not use this file except in compliance with the License. | ||||
| @@ -16,11 +36,31 @@ | ||||
| #include "ProgressDialog.h" | ||||
| #include "ui_ProgressDialog.h" | ||||
|  | ||||
| #include <limits> | ||||
| #include <QDebug> | ||||
| #include <QKeyEvent> | ||||
|  | ||||
| #include "tasks/Task.h" | ||||
|  | ||||
| #include "ui/widgets/SubTaskProgressBar.h" | ||||
|  | ||||
|  | ||||
| template<typename T>  | ||||
| int map_int_range(T value) | ||||
| { | ||||
|     auto type_min = std::numeric_limits<T>::min(); | ||||
|     auto type_max = std::numeric_limits<T>::max(); | ||||
|  | ||||
|     auto int_min = std::numeric_limits<int>::min(); | ||||
|     auto int_max = std::numeric_limits<int>::max(); | ||||
|  | ||||
|     auto type_range = type_max - type_min; | ||||
|     auto int_range = int_max - int_min; | ||||
|  | ||||
|     return static_cast<int>((value - type_min) * int_range / type_range + int_min); | ||||
| } | ||||
|  | ||||
|  | ||||
| ProgressDialog::ProgressDialog(QWidget* parent) : QDialog(parent), ui(new Ui::ProgressDialog) | ||||
| { | ||||
|     ui->setupUi(this); | ||||
| @@ -79,7 +119,7 @@ int ProgressDialog::execWithTask(Task* task) | ||||
|     connect(task, &Task::failed, this, &ProgressDialog::onTaskFailed); | ||||
|     connect(task, &Task::succeeded, this, &ProgressDialog::onTaskSucceeded); | ||||
|     connect(task, &Task::status, this, &ProgressDialog::changeStatus); | ||||
|     connect(task, &Task::stepStatus, this, &ProgressDialog::changeStatus); | ||||
|     connect(task, &Task::stepProgress, this, &ProgressDialog::changeStepProgress); | ||||
|     connect(task, &Task::progress, this, &ProgressDialog::changeProgress); | ||||
|  | ||||
|     connect(task, &Task::aborted, this, &ProgressDialog::hide); | ||||
| @@ -149,23 +189,54 @@ void ProgressDialog::onTaskSucceeded() | ||||
| void ProgressDialog::changeStatus(const QString& status) | ||||
| { | ||||
|     ui->globalStatusLabel->setText(task->getStatus()); | ||||
|     ui->statusLabel->setText(task->getStepStatus()); | ||||
|     // ui->statusLabel->setText(task->getStepStatus()); | ||||
|  | ||||
|     updateSize(); | ||||
| } | ||||
|  | ||||
| void ProgressDialog::addTaskProgress(TaskStepProgress progress) | ||||
| { | ||||
|     SubTaskProgressBar* task_bar = new SubTaskProgressBar(this); | ||||
|     taskProgress.insert(progress.uid, task_bar); | ||||
|     ui->taskProgressLayout->addWidget(task_bar); | ||||
| } | ||||
|  | ||||
| void ProgressDialog::changeStepProgress(QList<TaskStepProgress> task_progress) | ||||
| { | ||||
|     for (auto tp : task_progress) { | ||||
|         if (!taskProgress.contains(tp.uid)) | ||||
|             addTaskProgress(tp); | ||||
|         auto task_bar = taskProgress.value(tp.uid); | ||||
|  | ||||
|         if (tp.total < 0) { | ||||
|             task_bar->setRange(0, 0); | ||||
|         } else { | ||||
|             task_bar->setRange(0, map_int_range<qint64>(tp.total)); | ||||
|         } | ||||
|  | ||||
|         task_bar->setValue(map_int_range<qint64>(tp.current)); | ||||
|         task_bar->setStatus(tp.status); | ||||
|         task_bar->setDetails(tp.details); | ||||
|  | ||||
|         if (tp.isDone()) { | ||||
|             task_bar->setVisible(false); | ||||
|         } | ||||
|  | ||||
|     } | ||||
| } | ||||
|  | ||||
| void ProgressDialog::changeProgress(qint64 current, qint64 total) | ||||
| { | ||||
|     ui->globalProgressBar->setMaximum(total); | ||||
|     ui->globalProgressBar->setValue(current); | ||||
|  | ||||
|     if (!m_is_multi_step) { | ||||
|         ui->taskProgressBar->setMaximum(total); | ||||
|         ui->taskProgressBar->setValue(current); | ||||
|     } else { | ||||
|         ui->taskProgressBar->setMaximum(task->getStepProgress()); | ||||
|         ui->taskProgressBar->setValue(task->getStepTotalProgress()); | ||||
|     } | ||||
|     // if (!m_is_multi_step) { | ||||
|     //     ui->taskProgressBar->setMaximum(total); | ||||
|     //     ui->taskProgressBar->setValue(current); | ||||
|     // } else { | ||||
|     //     ui->taskProgressBar->setMaximum(task->getStepProgress()); | ||||
|     //     ui->taskProgressBar->setValue(task->getStepTotalProgress()); | ||||
|     // } | ||||
| } | ||||
|  | ||||
| void ProgressDialog::keyPressEvent(QKeyEvent* e) | ||||
|   | ||||
| @@ -1,4 +1,24 @@ | ||||
| /* Copyright 2013-2021 MultiMC Contributors | ||||
| /// SPDX-License-Identifier: GPL-3.0-only | ||||
| /* | ||||
|  *  PrismLaucher - Minecraft Launcher | ||||
|  *  Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com> | ||||
|  * | ||||
|  *  This program is free software: you can redistribute it and/or modify | ||||
|  *  it under the terms of the GNU General Public License as published by | ||||
|  *  the Free Software Foundation, version 3. | ||||
|  * | ||||
|  *  This program is distributed in the hope that it will be useful, | ||||
|  *  but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|  *  GNU General Public License for more details. | ||||
|  * | ||||
|  *  You should have received a copy of the GNU General Public License | ||||
|  *  along with this program.  If not, see <https://www.gnu.org/licenses/>. | ||||
|  * | ||||
|  * This file incorporates work covered by the following copyright and | ||||
|  * permission notice: | ||||
|  * | ||||
|  *      Copyright 2013-2021 MultiMC Contributors | ||||
|  * | ||||
|  *      Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  *      you may not use this file except in compliance with the License. | ||||
| @@ -13,10 +33,18 @@ | ||||
|  *      limitations under the License. | ||||
|  */ | ||||
|  | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <QDialog> | ||||
| #include <memory> | ||||
| #include <QHash> | ||||
| #include <QUuid> | ||||
|  | ||||
| #include "QObjectPtr.h" | ||||
| #include "tasks/Task.h" | ||||
|  | ||||
| #include "ui/widgets/SubTaskProgressBar.h" | ||||
|  | ||||
| class Task; | ||||
| class SequentialTask; | ||||
| @@ -52,6 +80,7 @@ slots: | ||||
|  | ||||
|     void changeStatus(const QString &status); | ||||
|     void changeProgress(qint64 current, qint64 total); | ||||
|     void changeStepProgress(QList<TaskStepProgress> task_progress); | ||||
|  | ||||
|  | ||||
| private | ||||
| @@ -64,6 +93,7 @@ protected: | ||||
|  | ||||
| private: | ||||
|     bool handleImmediateResult(QDialog::DialogCode &result); | ||||
|     void addTaskProgress(TaskStepProgress progress); | ||||
|  | ||||
| private: | ||||
|     Ui::ProgressDialog *ui; | ||||
| @@ -71,4 +101,8 @@ private: | ||||
|     Task *task; | ||||
|  | ||||
|     bool m_is_multi_step = false; | ||||
|     QHash<QUuid, SubTaskProgressBar*> taskProgress; | ||||
|  | ||||
|  | ||||
| }; | ||||
|  | ||||
|   | ||||
| @@ -2,6 +2,20 @@ | ||||
| <ui version="4.0"> | ||||
|  <class>ProgressDialog</class> | ||||
|  <widget class="QDialog" name="ProgressDialog"> | ||||
|   <property name="geometry"> | ||||
|    <rect> | ||||
|     <x>0</x> | ||||
|     <y>0</y> | ||||
|     <width>400</width> | ||||
|     <height>109</height> | ||||
|    </rect> | ||||
|   </property> | ||||
|   <property name="sizePolicy"> | ||||
|    <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding"> | ||||
|     <horstretch>0</horstretch> | ||||
|     <verstretch>0</verstretch> | ||||
|    </sizepolicy> | ||||
|   </property> | ||||
|   <property name="minimumSize"> | ||||
|    <size> | ||||
|     <width>400</width> | ||||
| @@ -18,6 +32,16 @@ | ||||
|    <string>Please wait...</string> | ||||
|   </property> | ||||
|   <layout class="QGridLayout" name="gridLayout"> | ||||
|    <item row="2" column="0"> | ||||
|     <widget class="QProgressBar" name="globalProgressBar"> | ||||
|      <property name="enabled"> | ||||
|       <bool>true</bool> | ||||
|      </property> | ||||
|      <property name="value"> | ||||
|       <number>24</number> | ||||
|      </property> | ||||
|     </widget> | ||||
|    </item> | ||||
|    <item row="4" column="0"> | ||||
|     <widget class="QPushButton" name="skipButton"> | ||||
|      <property name="sizePolicy"> | ||||
| @@ -31,15 +55,11 @@ | ||||
|      </property> | ||||
|     </widget> | ||||
|    </item> | ||||
|    <item row="0" column="0"> | ||||
|     <widget class="QLabel" name="globalStatusLabel"> | ||||
|      <property name="text"> | ||||
|       <string>Global Task Status...</string> | ||||
|      </property> | ||||
|     </widget> | ||||
|    <item row="3" column="0"> | ||||
|     <layout class="QVBoxLayout" name="taskProgressLayout"/> | ||||
|    </item> | ||||
|    <item row="2" column="0"> | ||||
|     <widget class="QLabel" name="statusLabel"> | ||||
|    <item row="1" column="0"> | ||||
|     <widget class="QLabel" name="globalStatusLabel"> | ||||
|      <property name="sizePolicy"> | ||||
|       <sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding"> | ||||
|        <horstretch>0</horstretch> | ||||
| @@ -47,30 +67,7 @@ | ||||
|       </sizepolicy> | ||||
|      </property> | ||||
|      <property name="text"> | ||||
|       <string>Task Status...</string> | ||||
|      </property> | ||||
|      <property name="wordWrap"> | ||||
|       <bool>true</bool> | ||||
|      </property> | ||||
|     </widget> | ||||
|    </item> | ||||
|    <item row="3" column="0"> | ||||
|     <widget class="QProgressBar" name="taskProgressBar"> | ||||
|      <property name="value"> | ||||
|       <number>24</number> | ||||
|      </property> | ||||
|      <property name="textVisible"> | ||||
|       <bool>false</bool> | ||||
|      </property> | ||||
|     </widget> | ||||
|    </item> | ||||
|    <item row="1" column="0"> | ||||
|     <widget class="QProgressBar" name="globalProgressBar"> | ||||
|      <property name="enabled"> | ||||
|       <bool>true</bool> | ||||
|      </property> | ||||
|      <property name="value"> | ||||
|       <number>24</number> | ||||
|       <string>Global Task Status...</string> | ||||
|      </property> | ||||
|     </widget> | ||||
|    </item> | ||||
|   | ||||
							
								
								
									
										58
									
								
								launcher/ui/widgets/SubTaskProgressBar.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								launcher/ui/widgets/SubTaskProgressBar.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,58 @@ | ||||
| // SPDX-License-Identifier: GPL-3.0-only | ||||
| /* | ||||
|  *  PrismLaucher - Minecraft Launcher | ||||
|  *  Copyright (C) 2022 Rachel Powers <508861+Ryex@users.noreply.github.com> | ||||
|  * | ||||
|  *  This program is free software: you can redistribute it and/or modify | ||||
|  *  it under the terms of the GNU General Public License as published by | ||||
|  *  the Free Software Foundation, version 3. | ||||
|  * | ||||
|  *  This program is distributed in the hope that it will be useful, | ||||
|  *  but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|  *  GNU General Public License for more details. | ||||
|  * | ||||
|  *  You should have received a copy of the GNU General Public License | ||||
|  *  along with this program.  If not, see <https://www.gnu.org/licenses/>. | ||||
|  * | ||||
|  */ | ||||
|  | ||||
| #include "SubTaskProgressBar.h" | ||||
| #include "ui_SubTaskProgressBar.h" | ||||
|  | ||||
| unique_qobject_ptr<SubTaskProgressBar> SubTaskProgressBar::create(QWidget* parent) | ||||
| { | ||||
|     auto progress_bar = new SubTaskProgressBar(parent); | ||||
|     return unique_qobject_ptr<SubTaskProgressBar>(progress_bar); | ||||
| } | ||||
|  | ||||
| SubTaskProgressBar::SubTaskProgressBar(QWidget* parent) | ||||
|     : ui(new Ui::SubTaskProgressBar) | ||||
| { | ||||
|     ui->setupUi(this); | ||||
| } | ||||
| SubTaskProgressBar::~SubTaskProgressBar()  | ||||
| { | ||||
|     delete ui; | ||||
| } | ||||
|  | ||||
| void SubTaskProgressBar::setRange(int min, int max) | ||||
| { | ||||
|     ui->progressBar->setRange(min, max); | ||||
| } | ||||
|  | ||||
| void SubTaskProgressBar::setValue(int value) | ||||
| { | ||||
|     ui->progressBar->setValue(value); | ||||
| } | ||||
|  | ||||
| void SubTaskProgressBar::setStatus(QString status) | ||||
| { | ||||
|     ui->statusLabel->setText(status); | ||||
| } | ||||
|  | ||||
| void SubTaskProgressBar::setDetails(QString details) | ||||
| { | ||||
|     ui->statusDetailsLabel->setText(details); | ||||
| } | ||||
|  | ||||
							
								
								
									
										50
									
								
								launcher/ui/widgets/SubTaskProgressBar.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								launcher/ui/widgets/SubTaskProgressBar.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,50 @@ | ||||
| // SPDX-License-Identifier: GPL-3.0-only | ||||
| /* | ||||
|  *  PrismLaucher - Minecraft Launcher | ||||
|  *  Copyright (C) 2022 Rachel Powers <508861+Ryex@users.noreply.github.com> | ||||
|  * | ||||
|  *  This program is free software: you can redistribute it and/or modify | ||||
|  *  it under the terms of the GNU General Public License as published by | ||||
|  *  the Free Software Foundation, version 3. | ||||
|  * | ||||
|  *  This program is distributed in the hope that it will be useful, | ||||
|  *  but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|  *  GNU General Public License for more details. | ||||
|  * | ||||
|  *  You should have received a copy of the GNU General Public License | ||||
|  *  along with this program.  If not, see <https://www.gnu.org/licenses/>. | ||||
|  * | ||||
|  */ | ||||
| #pragma once | ||||
|  | ||||
| #include <qobjectdefs.h> | ||||
| #include <qwidget.h> | ||||
| #include <QWidget> | ||||
| #include "QObjectPtr.h" | ||||
|  | ||||
| namespace Ui { | ||||
| class SubTaskProgressBar; | ||||
| } | ||||
|  | ||||
| class SubTaskProgressBar : public QWidget  | ||||
| { | ||||
|     Q_OBJECT | ||||
|  | ||||
| public: | ||||
|     static unique_qobject_ptr<SubTaskProgressBar> create(QWidget* parent = nullptr); | ||||
|  | ||||
|     SubTaskProgressBar(QWidget* parent = nullptr); | ||||
|     ~SubTaskProgressBar(); | ||||
|  | ||||
|     void setRange(int min, int max); | ||||
|     void setValue(int value); | ||||
|     void setStatus(QString status); | ||||
|     void setDetails(QString details); | ||||
|  | ||||
|  | ||||
|  | ||||
| private: | ||||
|     Ui::SubTaskProgressBar* ui; | ||||
|    | ||||
| }; | ||||
							
								
								
									
										70
									
								
								launcher/ui/widgets/SubTaskProgressBar.ui
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								launcher/ui/widgets/SubTaskProgressBar.ui
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,70 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <ui version="4.0"> | ||||
|  <class>SubTaskProgressBar</class> | ||||
|  <widget class="QWidget" name="SubTaskProgressBar"> | ||||
|   <property name="geometry"> | ||||
|    <rect> | ||||
|     <x>0</x> | ||||
|     <y>0</y> | ||||
|     <width>265</width> | ||||
|     <height>65</height> | ||||
|    </rect> | ||||
|   </property> | ||||
|   <property name="sizePolicy"> | ||||
|    <sizepolicy hsizetype="MinimumExpanding" vsizetype="Ignored"> | ||||
|     <horstretch>0</horstretch> | ||||
|     <verstretch>0</verstretch> | ||||
|    </sizepolicy> | ||||
|   </property> | ||||
|   <property name="windowTitle"> | ||||
|    <string>Form</string> | ||||
|   </property> | ||||
|   <layout class="QVBoxLayout" name="verticalLayout" stretch="0,0"> | ||||
|    <item> | ||||
|     <layout class="QHBoxLayout" name="horizontalLayout" stretch="1,0"> | ||||
|      <item> | ||||
|       <widget class="QLabel" name="statusLabel"> | ||||
|        <property name="sizePolicy"> | ||||
|         <sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding"> | ||||
|          <horstretch>0</horstretch> | ||||
|          <verstretch>0</verstretch> | ||||
|         </sizepolicy> | ||||
|        </property> | ||||
|        <property name="text"> | ||||
|         <string>Sub Task Status...</string> | ||||
|        </property> | ||||
|       </widget> | ||||
|      </item> | ||||
|      <item> | ||||
|       <widget class="QLabel" name="statusDetailsLabel"> | ||||
|        <property name="sizePolicy"> | ||||
|         <sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding"> | ||||
|          <horstretch>0</horstretch> | ||||
|          <verstretch>0</verstretch> | ||||
|         </sizepolicy> | ||||
|        </property> | ||||
|        <property name="text"> | ||||
|         <string>Status Details</string> | ||||
|        </property> | ||||
|        <property name="alignment"> | ||||
|         <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> | ||||
|        </property> | ||||
|       </widget> | ||||
|      </item> | ||||
|     </layout> | ||||
|    </item> | ||||
|    <item> | ||||
|     <widget class="QProgressBar" name="progressBar"> | ||||
|      <property name="value"> | ||||
|       <number>24</number> | ||||
|      </property> | ||||
|      <property name="textVisible"> | ||||
|       <bool>true</bool> | ||||
|      </property> | ||||
|     </widget> | ||||
|    </item> | ||||
|   </layout> | ||||
|  </widget> | ||||
|  <resources/> | ||||
|  <connections/> | ||||
| </ui> | ||||
| @@ -99,7 +99,7 @@ class TaskTest : public QObject { | ||||
|         t.setStatus(status); | ||||
|  | ||||
|         QCOMPARE(t.getStatus(), status); | ||||
|         QCOMPARE(t.getStepStatus(), status); | ||||
|         QCOMPARE(t.getStepProgress().isEmpty(), QList<TaskStepProgress>{}.isEmpty()); | ||||
|     } | ||||
|  | ||||
|     void test_SetStatus_MultiStep(){ | ||||
| @@ -111,7 +111,7 @@ class TaskTest : public QObject { | ||||
|         QCOMPARE(t.getStatus(), status); | ||||
|         // Even though it is multi step, it does not override the getStepStatus method, | ||||
|         // so it should remain the same. | ||||
|         QCOMPARE(t.getStepStatus(), status); | ||||
|         QCOMPARE(t.getStepProgress().isEmpty(), QList<TaskStepProgress>{}.isEmpty()); | ||||
|     } | ||||
|  | ||||
|     void test_SetProgress(){ | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Rachel Powers
					Rachel Powers