Merge pull request #961 from Ryex/better-tasks

This commit is contained in:
Sefa Eyeoglu 2023-05-12 09:47:19 +02:00 committed by GitHub
commit c5aff7cc1e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
58 changed files with 1441 additions and 247 deletions

View File

@ -345,6 +345,8 @@ elseif(UNIX)
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/${Launcher_SVG} DESTINATION "${KDE_INSTALL_ICONDIR}/hicolor/scalable/apps") install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/${Launcher_SVG} DESTINATION "${KDE_INSTALL_ICONDIR}/hicolor/scalable/apps")
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/${Launcher_mrpack_MIMEInfo} DESTINATION ${KDE_INSTALL_MIMEDIR}) install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/${Launcher_mrpack_MIMEInfo} DESTINATION ${KDE_INSTALL_MIMEDIR})
install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/launcher/qtlogging.ini" DESTINATION "${KDE_INSTALL_DATADIR}/${Launcher_Name}")
if(Launcher_ManPage) if(Launcher_ManPage)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${Launcher_ManPage} DESTINATION "${KDE_INSTALL_MANDIR}/man6") install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${Launcher_ManPage} DESTINATION "${KDE_INSTALL_MANDIR}/man6")
endif() endif()
@ -372,6 +374,8 @@ else()
message(FATAL_ERROR "Platform not supported") message(FATAL_ERROR "Platform not supported")
endif() endif()
################################ Included Libs ################################ ################################ Included Libs ################################
include(ExternalProject) include(ExternalProject)

View File

@ -8,6 +8,7 @@
* Copyright (C) 2022 Lenny McLennington <lenny@sneed.church> * Copyright (C) 2022 Lenny McLennington <lenny@sneed.church>
* Copyright (C) 2022 Tayou <tayou@gmx.net> * Copyright (C) 2022 Tayou <tayou@gmx.net>
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me> * Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
* Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com>
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -46,6 +47,7 @@
#include "net/PasteUpload.h" #include "net/PasteUpload.h"
#include "pathmatcher/MultiMatcher.h" #include "pathmatcher/MultiMatcher.h"
#include "pathmatcher/SimplePrefixMatcher.h" #include "pathmatcher/SimplePrefixMatcher.h"
#include "settings/INIFile.h"
#include "ui/MainWindow.h" #include "ui/MainWindow.h"
#include "ui/InstanceWindow.h" #include "ui/InstanceWindow.h"
@ -286,6 +288,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
if (QFile::exists(FS::PathCombine(m_rootPath, "portable.txt"))) { if (QFile::exists(FS::PathCombine(m_rootPath, "portable.txt"))) {
dataPath = m_rootPath; dataPath = m_rootPath;
adjustedBy = "Portable data path"; adjustedBy = "Portable data path";
m_portable = true;
} }
#endif #endif
} }
@ -411,6 +414,47 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
"%{if-category}[%{category}]: %{endif}" "%{if-category}[%{category}]: %{endif}"
"%{message}"); "%{message}");
bool foundLoggingRules = false;
auto logRulesFile = QStringLiteral("qtlogging.ini");
auto logRulesPath = FS::PathCombine(dataPath, logRulesFile);
qDebug() << "Testing" << logRulesPath << "...";
foundLoggingRules = QFile::exists(logRulesPath);
// search the dataPath()
// seach app data standard path
if(!foundLoggingRules && !isPortable() && dirParam.isEmpty()) {
logRulesPath = QStandardPaths::locate(QStandardPaths::AppDataLocation, FS::PathCombine("..", logRulesFile));
if(!logRulesPath.isEmpty()) {
qDebug() << "Found" << logRulesPath << "...";
foundLoggingRules = true;
}
}
// seach root path
if(!foundLoggingRules) {
logRulesPath = FS::PathCombine(m_rootPath, logRulesFile);
qDebug() << "Testing" << logRulesPath << "...";
foundLoggingRules = QFile::exists(logRulesPath);
}
if(foundLoggingRules) {
// load and set logging rules
qDebug() << "Loading logging rules from:" << logRulesPath;
QSettings loggingRules(logRulesPath, QSettings::IniFormat);
loggingRules.beginGroup("Rules");
QStringList rule_names = loggingRules.childKeys();
QStringList rules;
qDebug() << "Setting log rules:";
for (auto rule_name : rule_names) {
auto rule = QString("%1=%2").arg(rule_name).arg(loggingRules.value(rule_name).toString());
rules.append(rule);
qDebug() << " " << rule;
}
auto rules_str = rules.join("\n");
QLoggingCategory::setFilterRules(rules_str);
}
qDebug() << "<> Log initialized."; qDebug() << "<> Log initialized.";
} }

View File

@ -187,6 +187,10 @@ public:
return m_rootPath; return m_rootPath;
} }
bool isPortable() {
return m_portable;
}
const Capabilities capabilities() { const Capabilities capabilities() {
return m_capabilities; return m_capabilities;
} }
@ -275,6 +279,7 @@ private:
QString m_rootPath; QString m_rootPath;
Status m_status = Application::StartingUp; Status m_status = Application::StartingUp;
Capabilities m_capabilities; Capabilities m_capabilities;
bool m_portable = false;
#ifdef Q_OS_MACOS #ifdef Q_OS_MACOS
Qt::ApplicationState m_prevAppState = Qt::ApplicationInactive; Qt::ApplicationState m_prevAppState = Qt::ApplicationInactive;

View File

@ -124,6 +124,8 @@ set(NET_SOURCES
net/HttpMetaCache.h net/HttpMetaCache.h
net/MetaCacheSink.cpp net/MetaCacheSink.cpp
net/MetaCacheSink.h net/MetaCacheSink.h
net/Logging.h
net/Logging.cpp
net/NetAction.h net/NetAction.h
net/NetJob.cpp net/NetJob.cpp
net/NetJob.h net/NetJob.h
@ -576,6 +578,55 @@ ecm_qt_declare_logging_category(CORE_SOURCES
EXPORT "${Launcher_Name}" EXPORT "${Launcher_Name}"
) )
ecm_qt_export_logging_category(
IDENTIFIER taskLogC
CATEGORY_NAME "launcher.task"
DEFAULT_SEVERITY Debug
DESCRIPTION "Task actions"
EXPORT "${Launcher_Name}"
)
ecm_qt_export_logging_category(
IDENTIFIER taskNetLogC
CATEGORY_NAME "launcher.task.net"
DEFAULT_SEVERITY Debug
DESCRIPTION "task network action"
EXPORT "${Launcher_Name}"
)
ecm_qt_export_logging_category(
IDENTIFIER taskDownloadLogC
CATEGORY_NAME "launcher.task.net.download"
DEFAULT_SEVERITY Debug
DESCRIPTION "task network download actions"
EXPORT "${Launcher_Name}"
)
ecm_qt_export_logging_category(
IDENTIFIER taskUploadLogC
CATEGORY_NAME "launcher.task.net.upload"
DEFAULT_SEVERITY Debug
DESCRIPTION "task network upload actions"
EXPORT "${Launcher_Name}"
)
ecm_qt_export_logging_category(
IDENTIFIER taskMetaCacheLogC
CATEGORY_NAME "launcher.task.net.metacache"
DEFAULT_SEVERITY Debug
DESCRIPTION "task network meta-cache actions"
EXPORT "${Launcher_Name}"
)
ecm_qt_export_logging_category(
IDENTIFIER taskHttpMetaCacheLogC
CATEGORY_NAME "launcher.task.net.metacache.http"
DEFAULT_SEVERITY Debug
DESCRIPTION "task network http meta-cache actions"
EXPORT "${Launcher_Name}"
)
if(KDE_INSTALL_LOGGINGCATEGORIESDIR) # only install if there is a standard path for this if(KDE_INSTALL_LOGGINGCATEGORIESDIR) # only install if there is a standard path for this
ecm_qt_install_logging_categories( ecm_qt_install_logging_categories(
EXPORT "${Launcher_Name}" EXPORT "${Launcher_Name}"
@ -922,6 +973,8 @@ SET(LAUNCHER_SOURCES
ui/widgets/VariableSizedImageObject.cpp ui/widgets/VariableSizedImageObject.cpp
ui/widgets/ProjectItem.h ui/widgets/ProjectItem.h
ui/widgets/ProjectItem.cpp ui/widgets/ProjectItem.cpp
ui/widgets/SubTaskProgressBar.h
ui/widgets/SubTaskProgressBar.cpp
ui/widgets/VersionListView.cpp ui/widgets/VersionListView.cpp
ui/widgets/VersionListView.h ui/widgets/VersionListView.h
ui/widgets/VersionSelectWidget.cpp ui/widgets/VersionSelectWidget.cpp
@ -982,6 +1035,7 @@ qt_wrap_ui(LAUNCHER_UI
ui/widgets/CustomCommands.ui ui/widgets/CustomCommands.ui
ui/widgets/InfoFrame.ui ui/widgets/InfoFrame.ui
ui/widgets/ModFilterWidget.ui ui/widgets/ModFilterWidget.ui
ui/widgets/SubTaskProgressBar.ui
ui/widgets/ThemeCustomizationWidget.ui ui/widgets/ThemeCustomizationWidget.ui
ui/dialogs/CopyInstanceDialog.ui ui/dialogs/CopyInstanceDialog.ui
ui/dialogs/ProfileSetupDialog.ui ui/dialogs/ProfileSetupDialog.ui
@ -1155,6 +1209,12 @@ if(INSTALL_BUNDLE STREQUAL "full")
CODE "file(WRITE \"\${CMAKE_INSTALL_PREFIX}/${RESOURCES_DEST_DIR}/qt.conf\" \" \")" CODE "file(WRITE \"\${CMAKE_INSTALL_PREFIX}/${RESOURCES_DEST_DIR}/qt.conf\" \" \")"
COMPONENT Runtime COMPONENT Runtime
) )
# add qtlogging.ini as a config file
install(
FILES "qtlogging.ini"
DESTINATION ${CMAKE_INSTALL_PREFIX}/${RESOURCES_DEST_DIR}
COMPONENT Runtime
)
# Bundle plugins # Bundle plugins
# Image formats # Image formats
install( install(

View File

@ -98,6 +98,7 @@ void InstanceImportTask::executeTask()
connect(m_filesNetJob.get(), &NetJob::succeeded, this, &InstanceImportTask::downloadSucceeded); connect(m_filesNetJob.get(), &NetJob::succeeded, this, &InstanceImportTask::downloadSucceeded);
connect(m_filesNetJob.get(), &NetJob::progress, this, &InstanceImportTask::downloadProgressChanged); connect(m_filesNetJob.get(), &NetJob::progress, this, &InstanceImportTask::downloadProgressChanged);
connect(m_filesNetJob.get(), &NetJob::stepProgress, this, &InstanceImportTask::propogateStepProgress);
connect(m_filesNetJob.get(), &NetJob::failed, this, &InstanceImportTask::downloadFailed); connect(m_filesNetJob.get(), &NetJob::failed, this, &InstanceImportTask::downloadFailed);
connect(m_filesNetJob.get(), &NetJob::aborted, this, &InstanceImportTask::downloadAborted); connect(m_filesNetJob.get(), &NetJob::aborted, this, &InstanceImportTask::downloadAborted);
@ -291,7 +292,9 @@ void InstanceImportTask::processFlame()
}); });
connect(inst_creation_task, &Task::failed, this, &InstanceImportTask::emitFailed); connect(inst_creation_task, &Task::failed, this, &InstanceImportTask::emitFailed);
connect(inst_creation_task, &Task::progress, this, &InstanceImportTask::setProgress); connect(inst_creation_task, &Task::progress, this, &InstanceImportTask::setProgress);
connect(inst_creation_task, &Task::stepProgress, this, &InstanceImportTask::propogateStepProgress);
connect(inst_creation_task, &Task::status, this, &InstanceImportTask::setStatus); connect(inst_creation_task, &Task::status, this, &InstanceImportTask::setStatus);
connect(inst_creation_task, &Task::details, this, &InstanceImportTask::setDetails);
connect(inst_creation_task, &Task::finished, inst_creation_task, &InstanceCreationTask::deleteLater); connect(inst_creation_task, &Task::finished, inst_creation_task, &InstanceCreationTask::deleteLater);
connect(this, &Task::aborted, inst_creation_task, &InstanceCreationTask::abort); connect(this, &Task::aborted, inst_creation_task, &InstanceCreationTask::abort);
@ -382,7 +385,9 @@ void InstanceImportTask::processModrinth()
}); });
connect(inst_creation_task, &Task::failed, this, &InstanceImportTask::emitFailed); connect(inst_creation_task, &Task::failed, this, &InstanceImportTask::emitFailed);
connect(inst_creation_task, &Task::progress, this, &InstanceImportTask::setProgress); connect(inst_creation_task, &Task::progress, this, &InstanceImportTask::setProgress);
connect(inst_creation_task, &Task::stepProgress, this, &InstanceImportTask::propogateStepProgress);
connect(inst_creation_task, &Task::status, this, &InstanceImportTask::setStatus); connect(inst_creation_task, &Task::status, this, &InstanceImportTask::setStatus);
connect(inst_creation_task, &Task::details, this, &InstanceImportTask::setDetails);
connect(inst_creation_task, &Task::finished, inst_creation_task, &InstanceCreationTask::deleteLater); connect(inst_creation_task, &Task::finished, inst_creation_task, &InstanceCreationTask::deleteLater);
connect(this, &Task::aborted, inst_creation_task, &InstanceCreationTask::abort); connect(this, &Task::aborted, inst_creation_task, &InstanceCreationTask::abort);

View File

@ -797,7 +797,9 @@ class InstanceStaging : public Task {
connect(child, &Task::aborted, this, &InstanceStaging::childAborted); connect(child, &Task::aborted, this, &InstanceStaging::childAborted);
connect(child, &Task::abortStatusChanged, this, &InstanceStaging::setAbortable); connect(child, &Task::abortStatusChanged, this, &InstanceStaging::setAbortable);
connect(child, &Task::status, this, &InstanceStaging::setStatus); connect(child, &Task::status, this, &InstanceStaging::setStatus);
connect(child, &Task::details, this, &InstanceStaging::setDetails);
connect(child, &Task::progress, this, &InstanceStaging::setProgress); connect(child, &Task::progress, this, &InstanceStaging::setProgress);
connect(child, &Task::stepProgress, this, &InstanceStaging::propogateStepProgress);
connect(&m_backoffTimer, &QTimer::timeout, this, &InstanceStaging::childSucceded); connect(&m_backoffTimer, &QTimer::timeout, this, &InstanceStaging::childSucceded);
} }

View File

@ -18,6 +18,8 @@
#include <MMCTime.h> #include <MMCTime.h>
#include <QObject> #include <QObject>
#include <QDateTime>
#include <QTextStream>
QString Time::prettifyDuration(int64_t duration) { QString Time::prettifyDuration(int64_t duration) {
int seconds = (int) (duration % 60); int seconds = (int) (duration % 60);
@ -36,3 +38,65 @@ QString Time::prettifyDuration(int64_t duration) {
} }
return QObject::tr("%1d %2h %3min").arg(days).arg(hours).arg(minutes); return QObject::tr("%1d %2h %3min").arg(days).arg(hours).arg(minutes);
} }
QString Time::humanReadableDuration(double duration, int precision) {
using days = std::chrono::duration<int, std::ratio<86400>>;
QString outStr;
QTextStream os(&outStr);
bool neg = false;
if (duration < 0) {
neg = true; // flag
duration *= -1; // invert
}
auto std_duration = std::chrono::duration<double>(duration);
auto d = std::chrono::duration_cast<days>(std_duration);
std_duration -= d;
auto h = std::chrono::duration_cast<std::chrono::hours>(std_duration);
std_duration -= h;
auto m = std::chrono::duration_cast<std::chrono::minutes>(std_duration);
std_duration -= m;
auto s = std::chrono::duration_cast<std::chrono::seconds>(std_duration);
std_duration -= s;
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(std_duration);
auto dc = d.count();
auto hc = h.count();
auto mc = m.count();
auto sc = s.count();
auto msc = ms.count();
if (neg) {
os << '-';
}
if (dc) {
os << dc << QObject::tr("days");
}
if (hc) {
if (dc)
os << " ";
os << qSetFieldWidth(2) << hc << QObject::tr("h"); // hours
}
if (mc) {
if (dc || hc)
os << " ";
os << qSetFieldWidth(2) << mc << QObject::tr("m"); // minutes
}
if (dc || hc || mc || sc) {
if (dc || hc || mc)
os << " ";
os << qSetFieldWidth(2) << sc << QObject::tr("s"); // seconds
}
if ((msc && (precision > 0)) || !(dc || hc || mc || sc)) {
if (dc || hc || mc || sc)
os << " ";
os << qSetFieldWidth(0) << qSetRealNumberPrecision(precision) << msc << QObject::tr("ms"); // miliseconds
}
os.flush();
return outStr;
}

View File

@ -22,4 +22,13 @@ namespace Time {
QString prettifyDuration(int64_t duration); QString prettifyDuration(int64_t duration);
/**
* @brief Returns a string with short form time duration ie. `2days 1h3m4s56.0ms`.
* miliseconds are only included if `precision` is greater than 0.
*
* @param duration a number of seconds as floating point
* @param precision number of decmial points to display on fractons of a second, defualts to 0.
* @return QString
*/
QString humanReadableDuration(double duration, int precision = 0);
} }

View File

@ -53,6 +53,7 @@ ResourceDownloadTask::ResourceDownloadTask(ModPlatform::IndexedPack pack,
m_filesNetJob->addNetAction(Net::Download::makeFile(m_pack_version.downloadUrl, dir.absoluteFilePath(getFilename()))); m_filesNetJob->addNetAction(Net::Download::makeFile(m_pack_version.downloadUrl, dir.absoluteFilePath(getFilename())));
connect(m_filesNetJob.get(), &NetJob::succeeded, this, &ResourceDownloadTask::downloadSucceeded); connect(m_filesNetJob.get(), &NetJob::succeeded, this, &ResourceDownloadTask::downloadSucceeded);
connect(m_filesNetJob.get(), &NetJob::progress, this, &ResourceDownloadTask::downloadProgressChanged); connect(m_filesNetJob.get(), &NetJob::progress, this, &ResourceDownloadTask::downloadProgressChanged);
connect(m_filesNetJob.get(), &NetJob::stepProgress, this, &ResourceDownloadTask::propogateStepProgress);
connect(m_filesNetJob.get(), &NetJob::failed, this, &ResourceDownloadTask::downloadFailed); connect(m_filesNetJob.get(), &NetJob::failed, this, &ResourceDownloadTask::downloadFailed);
addTask(m_filesNetJob); addTask(m_filesNetJob);

View File

@ -36,7 +36,9 @@
#include "StringUtils.h" #include "StringUtils.h"
#include <QRegularExpression>
#include <QUuid> #include <QUuid>
#include <cmath>
/// If you're wondering where these came from exactly, then know you're not the only one =D /// If you're wondering where these came from exactly, then know you're not the only one =D
@ -113,6 +115,69 @@ int StringUtils::naturalCompare(const QString& s1, const QString& s2, Qt::CaseSe
return QString::compare(s1, s2, cs); return QString::compare(s1, s2, cs);
} }
QString StringUtils::truncateUrlHumanFriendly(QUrl& url, int max_len, bool hard_limit)
{
auto display_options = QUrl::RemoveUserInfo | QUrl::RemoveFragment | QUrl::NormalizePathSegments;
auto str_url = url.toDisplayString(display_options);
if (str_url.length() <= max_len)
return str_url;
auto url_path_parts = url.path().split('/');
QString last_path_segment = url_path_parts.takeLast();
if (url_path_parts.size() >= 1 && url_path_parts.first().isEmpty())
url_path_parts.removeFirst(); // drop empty first segment (from leading / )
if (url_path_parts.size() >= 1)
url_path_parts.removeLast(); // drop the next to last path segment
auto url_template = QStringLiteral("%1://%2/%3%4");
auto url_compact = url_path_parts.isEmpty()
? url_template.arg(url.scheme(), url.host(), QStringList({ "...", last_path_segment }).join('/'), url.query())
: url_template.arg(url.scheme(), url.host(),
QStringList({ url_path_parts.join('/'), "...", last_path_segment }).join('/'), url.query());
// remove url parts one by one if it's still too long
while (url_compact.length() > max_len && url_path_parts.size() >= 1) {
url_path_parts.removeLast(); // drop the next to last path segment
url_compact = url_path_parts.isEmpty()
? url_template.arg(url.scheme(), url.host(), QStringList({ "...", last_path_segment }).join('/'), url.query())
: url_template.arg(url.scheme(), url.host(),
QStringList({ url_path_parts.join('/'), "...", last_path_segment }).join('/'), url.query());
}
if ((url_compact.length() >= max_len) && hard_limit) {
// still too long, truncate normaly
url_compact = QString(str_url);
auto to_remove = url_compact.length() - max_len + 3;
url_compact.remove(url_compact.length() - to_remove - 1, to_remove);
url_compact.append("...");
}
return url_compact;
}
static const QStringList s_units_si{ "KB", "MB", "GB", "TB" };
static const QStringList s_units_kibi{ "KiB", "MiB", "GiB", "TiB" };
QString StringUtils::humanReadableFileSize(double bytes, bool use_si, int decimal_points)
{
const QStringList units = use_si ? s_units_si : s_units_kibi;
const int scale = use_si ? 1000 : 1024;
int u = -1;
double r = pow(10, decimal_points);
do {
bytes /= scale;
u++;
} while (round(abs(bytes) * r) / r >= scale && u < units.length() - 1);
return QString::number(bytes, 'f', 2) + " " + units[u];
}
QString StringUtils::getRandomAlphaNumeric() QString StringUtils::getRandomAlphaNumeric()
{ {
return QUuid::createUuid().toString(QUuid::Id128); return QUuid::createUuid().toString(QUuid::Id128);

View File

@ -37,6 +37,7 @@
#pragma once #pragma once
#include <QString> #include <QString>
#include <QUrl>
namespace StringUtils { namespace StringUtils {
@ -66,5 +67,16 @@ inline QString fromStdString(string s)
int naturalCompare(const QString& s1, const QString& s2, Qt::CaseSensitivity cs); int naturalCompare(const QString& s1, const QString& s2, Qt::CaseSensitivity cs);
/**
* @brief Truncate a url while keeping its readability py placing the `...` in the middle of the path
* @param url Url to truncate
* @param max_len max lenght of url in charaters
* @param hard_limit if truncating the path can't get the url short enough, truncate it normaly.
*/
QString truncateUrlHumanFriendly(QUrl &url, int max_len, bool hard_limit = false);
QString humanReadableFileSize(double bytes, bool use_si = false, int decimal_points = 1);
QString getRandomAlphaNumeric(); QString getRandomAlphaNumeric();
} // namespace StringUtils } // namespace StringUtils

View File

@ -27,8 +27,10 @@ void Update::executeTask()
if(m_updateTask) if(m_updateTask)
{ {
connect(m_updateTask.get(), SIGNAL(finished()), this, SLOT(updateFinished())); connect(m_updateTask.get(), SIGNAL(finished()), this, SLOT(updateFinished()));
connect(m_updateTask.get(), &Task::progress, this, &Task::setProgress); connect(m_updateTask.get(), &Task::progress, this, &Update::setProgress);
connect(m_updateTask.get(), &Task::status, this, &Task::setStatus); connect(m_updateTask.get(), &Task::stepProgress, this, &Update::propogateStepProgress);
connect(m_updateTask.get(), &Task::status, this, &Update::setStatus);
connect(m_updateTask.get(), &Task::details, this, &Update::setDetails);
emit progressReportingRequest(); emit progressReportingRequest();
return; return;
} }

View File

@ -22,6 +22,7 @@ void MinecraftLoadAndCheck::executeTask()
connect(m_task.get(), &Task::failed, this, &MinecraftLoadAndCheck::subtaskFailed); connect(m_task.get(), &Task::failed, this, &MinecraftLoadAndCheck::subtaskFailed);
connect(m_task.get(), &Task::aborted, this, [this]{ subtaskFailed(tr("Aborted")); }); connect(m_task.get(), &Task::aborted, this, [this]{ subtaskFailed(tr("Aborted")); });
connect(m_task.get(), &Task::progress, this, &MinecraftLoadAndCheck::progress); connect(m_task.get(), &Task::progress, this, &MinecraftLoadAndCheck::progress);
connect(m_task.get(), &Task::stepProgress, this, &MinecraftLoadAndCheck::propogateStepProgress);
connect(m_task.get(), &Task::status, this, &MinecraftLoadAndCheck::setStatus); connect(m_task.get(), &Task::status, this, &MinecraftLoadAndCheck::setStatus);
} }

View File

@ -100,7 +100,9 @@ void MinecraftUpdate::next()
disconnect(task.get(), &Task::failed, this, &MinecraftUpdate::subtaskFailed); disconnect(task.get(), &Task::failed, this, &MinecraftUpdate::subtaskFailed);
disconnect(task.get(), &Task::aborted, this, &Task::abort); disconnect(task.get(), &Task::aborted, this, &Task::abort);
disconnect(task.get(), &Task::progress, this, &MinecraftUpdate::progress); disconnect(task.get(), &Task::progress, this, &MinecraftUpdate::progress);
disconnect(task.get(), &Task::stepProgress, this, &MinecraftUpdate::propogateStepProgress);
disconnect(task.get(), &Task::status, this, &MinecraftUpdate::setStatus); disconnect(task.get(), &Task::status, this, &MinecraftUpdate::setStatus);
disconnect(task.get(), &Task::details, this, &MinecraftUpdate::setDetails);
} }
if(m_currentTask == m_tasks.size()) if(m_currentTask == m_tasks.size())
{ {
@ -118,7 +120,9 @@ void MinecraftUpdate::next()
connect(task.get(), &Task::failed, this, &MinecraftUpdate::subtaskFailed); connect(task.get(), &Task::failed, this, &MinecraftUpdate::subtaskFailed);
connect(task.get(), &Task::aborted, this, &Task::abort); connect(task.get(), &Task::aborted, this, &Task::abort);
connect(task.get(), &Task::progress, this, &MinecraftUpdate::progress); connect(task.get(), &Task::progress, this, &MinecraftUpdate::progress);
connect(task.get(), &Task::stepProgress, this, &MinecraftUpdate::propogateStepProgress);
connect(task.get(), &Task::status, this, &MinecraftUpdate::setStatus); connect(task.get(), &Task::status, this, &MinecraftUpdate::setStatus);
connect(task.get(), &Task::details, this, &MinecraftUpdate::setDetails);
// if the task is already running, do not start it again // if the task is already running, do not start it again
if(!task->isRunning()) if(!task->isRunning())
{ {

View File

@ -45,6 +45,7 @@ void AssetUpdateTask::executeTask()
connect(downloadJob.get(), &NetJob::failed, this, &AssetUpdateTask::assetIndexFailed); connect(downloadJob.get(), &NetJob::failed, this, &AssetUpdateTask::assetIndexFailed);
connect(downloadJob.get(), &NetJob::aborted, this, [this]{ emitFailed(tr("Aborted")); }); connect(downloadJob.get(), &NetJob::aborted, this, [this]{ emitFailed(tr("Aborted")); });
connect(downloadJob.get(), &NetJob::progress, this, &AssetUpdateTask::progress); connect(downloadJob.get(), &NetJob::progress, this, &AssetUpdateTask::progress);
connect(downloadJob.get(), &NetJob::stepProgress, this, &AssetUpdateTask::propogateStepProgress);
qDebug() << m_inst->name() << ": Starting asset index download"; qDebug() << m_inst->name() << ": Starting asset index download";
downloadJob->start(); downloadJob->start();
@ -83,6 +84,7 @@ void AssetUpdateTask::assetIndexFinished()
connect(downloadJob.get(), &NetJob::failed, this, &AssetUpdateTask::assetsFailed); connect(downloadJob.get(), &NetJob::failed, this, &AssetUpdateTask::assetsFailed);
connect(downloadJob.get(), &NetJob::aborted, this, [this]{ emitFailed(tr("Aborted")); }); connect(downloadJob.get(), &NetJob::aborted, this, [this]{ emitFailed(tr("Aborted")); });
connect(downloadJob.get(), &NetJob::progress, this, &AssetUpdateTask::progress); connect(downloadJob.get(), &NetJob::progress, this, &AssetUpdateTask::progress);
connect(downloadJob.get(), &NetJob::stepProgress, this, &AssetUpdateTask::propogateStepProgress);
downloadJob->start(); downloadJob->start();
return; return;
} }

View File

@ -75,6 +75,7 @@ void FMLLibrariesTask::executeTask()
connect(dljob.get(), &NetJob::failed, this, &FMLLibrariesTask::fmllibsFailed); connect(dljob.get(), &NetJob::failed, this, &FMLLibrariesTask::fmllibsFailed);
connect(dljob.get(), &NetJob::aborted, this, [this]{ emitFailed(tr("Aborted")); }); connect(dljob.get(), &NetJob::aborted, this, [this]{ emitFailed(tr("Aborted")); });
connect(dljob.get(), &NetJob::progress, this, &FMLLibrariesTask::progress); connect(dljob.get(), &NetJob::progress, this, &FMLLibrariesTask::progress);
connect(dljob.get(), &NetJob::stepProgress, this, &FMLLibrariesTask::propogateStepProgress);
downloadJob.reset(dljob); downloadJob.reset(dljob);
downloadJob->start(); downloadJob->start();
} }

View File

@ -70,6 +70,8 @@ void LibrariesTask::executeTask()
connect(downloadJob.get(), &NetJob::failed, this, &LibrariesTask::jarlibFailed); connect(downloadJob.get(), &NetJob::failed, this, &LibrariesTask::jarlibFailed);
connect(downloadJob.get(), &NetJob::aborted, this, [this]{ emitFailed(tr("Aborted")); }); connect(downloadJob.get(), &NetJob::aborted, this, [this]{ emitFailed(tr("Aborted")); });
connect(downloadJob.get(), &NetJob::progress, this, &LibrariesTask::progress); connect(downloadJob.get(), &NetJob::progress, this, &LibrariesTask::progress);
connect(downloadJob.get(), &NetJob::stepProgress, this, &LibrariesTask::propogateStepProgress);
downloadJob->start(); downloadJob->start();
} }

View File

@ -683,6 +683,7 @@ void PackInstallTask::installConfigs()
abortable = true; abortable = true;
setProgress(current, total); setProgress(current, total);
}); });
connect(jobPtr.get(), &NetJob::stepProgress, this, &PackInstallTask::propogateStepProgress);
connect(jobPtr.get(), &NetJob::aborted, [&]{ connect(jobPtr.get(), &NetJob::aborted, [&]{
abortable = false; abortable = false;
jobPtr.reset(); jobPtr.reset();
@ -846,9 +847,11 @@ void PackInstallTask::downloadMods()
}); });
connect(jobPtr.get(), &NetJob::progress, [&](qint64 current, qint64 total) connect(jobPtr.get(), &NetJob::progress, [&](qint64 current, qint64 total)
{ {
setDetails(tr("%1 out of %2 complete").arg(current).arg(total));
abortable = true; abortable = true;
setProgress(current, total); setProgress(current, total);
}); });
connect(jobPtr.get(), &NetJob::stepProgress, this, &PackInstallTask::propogateStepProgress);
connect(jobPtr.get(), &NetJob::aborted, [&] connect(jobPtr.get(), &NetJob::aborted, [&]
{ {
abortable = false; abortable = false;

View File

@ -35,6 +35,7 @@
#include "FlameInstanceCreationTask.h" #include "FlameInstanceCreationTask.h"
#include "modplatform/flame/FileResolvingTask.h"
#include "modplatform/flame/FlameAPI.h" #include "modplatform/flame/FlameAPI.h"
#include "modplatform/flame/PackManifest.h" #include "modplatform/flame/PackManifest.h"
@ -382,7 +383,7 @@ bool FlameCreationTask::createInstance()
}); });
connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::progress, this, &FlameCreationTask::setProgress); connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::progress, this, &FlameCreationTask::setProgress);
connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::status, this, &FlameCreationTask::setStatus); connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::status, this, &FlameCreationTask::setStatus);
connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::stepProgress, this, &FlameCreationTask::propogateStepProgress);
m_mod_id_resolver->start(); m_mod_id_resolver->start();
loop.exec(); loop.exec();
@ -452,7 +453,7 @@ void FlameCreationTask::idResolverSucceeded(QEventLoop& loop)
void FlameCreationTask::setupDownloadJob(QEventLoop& loop) void FlameCreationTask::setupDownloadJob(QEventLoop& loop)
{ {
m_files_job.reset(new NetJob(tr("Mod download"), APPLICATION->network())); m_files_job.reset(new NetJob(tr("Mod Download Flame"), APPLICATION->network()));
for (const auto& result : m_mod_id_resolver->getResults().files) { for (const auto& result : m_mod_id_resolver->getResults().files) {
QString filename = result.fileName; QString filename = result.fileName;
if (!result.required) { if (!result.required) {
@ -496,7 +497,11 @@ void FlameCreationTask::setupDownloadJob(QEventLoop& loop)
m_files_job.reset(); m_files_job.reset();
setError(reason); setError(reason);
}); });
connect(m_files_job.get(), &NetJob::progress, this, &FlameCreationTask::setProgress); connect(m_files_job.get(), &NetJob::progress, this, [this](qint64 current, qint64 total){
setDetails(tr("%1 out of %2 complete").arg(current).arg(total));
setProgress(current, total);
});
connect(m_files_job.get(), &NetJob::stepProgress, this, &FlameCreationTask::propogateStepProgress);
connect(m_files_job.get(), &NetJob::finished, &loop, &QEventLoop::quit); connect(m_files_job.get(), &NetJob::finished, &loop, &QEventLoop::quit);
setStatus(tr("Downloading mods...")); setStatus(tr("Downloading mods..."));

View File

@ -81,6 +81,7 @@ void PackInstallTask::downloadPack()
connect(netJobContainer.get(), &NetJob::succeeded, this, &PackInstallTask::onDownloadSucceeded); connect(netJobContainer.get(), &NetJob::succeeded, this, &PackInstallTask::onDownloadSucceeded);
connect(netJobContainer.get(), &NetJob::failed, this, &PackInstallTask::onDownloadFailed); connect(netJobContainer.get(), &NetJob::failed, this, &PackInstallTask::onDownloadFailed);
connect(netJobContainer.get(), &NetJob::progress, this, &PackInstallTask::onDownloadProgress); connect(netJobContainer.get(), &NetJob::progress, this, &PackInstallTask::onDownloadProgress);
connect(netJobContainer.get(), &NetJob::stepProgress, this, &PackInstallTask::propogateStepProgress);
connect(netJobContainer.get(), &NetJob::aborted, this, &PackInstallTask::onDownloadAborted); connect(netJobContainer.get(), &NetJob::aborted, this, &PackInstallTask::onDownloadAborted);
netJobContainer->start(); netJobContainer->start();

View File

@ -11,6 +11,7 @@
#include "net/ChecksumValidator.h" #include "net/ChecksumValidator.h"
#include "net/NetJob.h"
#include "settings/INISettingsObject.h" #include "settings/INISettingsObject.h"
#include "ui/dialogs/CustomMessageBox.h" #include "ui/dialogs/CustomMessageBox.h"
@ -223,7 +224,7 @@ bool ModrinthCreationTask::createInstance()
instance.setName(name()); instance.setName(name());
instance.saveNow(); instance.saveNow();
m_files_job.reset(new NetJob(tr("Mod download"), APPLICATION->network())); m_files_job.reset(new NetJob(tr("Mod Download Modrinth"), APPLICATION->network()));
auto root_modpack_path = FS::PathCombine(m_stagingPath, ".minecraft"); auto root_modpack_path = FS::PathCombine(m_stagingPath, ".minecraft");
auto root_modpack_url = QUrl::fromLocalFile(root_modpack_path); auto root_modpack_url = QUrl::fromLocalFile(root_modpack_path);
@ -262,7 +263,11 @@ bool ModrinthCreationTask::createInstance()
setError(reason); setError(reason);
}); });
connect(m_files_job.get(), &NetJob::finished, &loop, &QEventLoop::quit); connect(m_files_job.get(), &NetJob::finished, &loop, &QEventLoop::quit);
connect(m_files_job.get(), &NetJob::progress, [&](qint64 current, qint64 total) { setProgress(current, total); }); connect(m_files_job.get(), &NetJob::progress, [&](qint64 current, qint64 total) {
setDetails(tr("%1 out of %2 complete").arg(current).arg(total));
setProgress(current, total);
});
connect(m_files_job.get(), &NetJob::stepProgress, this, &ModrinthCreationTask::propogateStepProgress);
setStatus(tr("Downloading mods...")); setStatus(tr("Downloading mods..."));
m_files_job->start(); m_files_job->start();

View File

@ -50,6 +50,7 @@ void Technic::SingleZipPackInstallTask::executeTask()
auto job = m_filesNetJob.get(); auto job = m_filesNetJob.get();
connect(job, &NetJob::succeeded, this, &Technic::SingleZipPackInstallTask::downloadSucceeded); connect(job, &NetJob::succeeded, this, &Technic::SingleZipPackInstallTask::downloadSucceeded);
connect(job, &NetJob::progress, this, &Technic::SingleZipPackInstallTask::downloadProgressChanged); connect(job, &NetJob::progress, this, &Technic::SingleZipPackInstallTask::downloadProgressChanged);
connect(job, &NetJob::stepProgress, this, &Technic::SingleZipPackInstallTask::propogateStepProgress);
connect(job, &NetJob::failed, this, &Technic::SingleZipPackInstallTask::downloadFailed); connect(job, &NetJob::failed, this, &Technic::SingleZipPackInstallTask::downloadFailed);
m_filesNetJob->start(); m_filesNetJob->start();
} }

View File

@ -127,6 +127,7 @@ void Technic::SolderPackInstallTask::fileListSucceeded()
connect(m_filesNetJob.get(), &NetJob::succeeded, this, &Technic::SolderPackInstallTask::downloadSucceeded); connect(m_filesNetJob.get(), &NetJob::succeeded, this, &Technic::SolderPackInstallTask::downloadSucceeded);
connect(m_filesNetJob.get(), &NetJob::progress, this, &Technic::SolderPackInstallTask::downloadProgressChanged); connect(m_filesNetJob.get(), &NetJob::progress, this, &Technic::SolderPackInstallTask::downloadProgressChanged);
connect(m_filesNetJob.get(), &NetJob::stepProgress, this, &Technic::SolderPackInstallTask::propogateStepProgress);
connect(m_filesNetJob.get(), &NetJob::failed, this, &Technic::SolderPackInstallTask::downloadFailed); connect(m_filesNetJob.get(), &NetJob::failed, this, &Technic::SolderPackInstallTask::downloadFailed);
connect(m_filesNetJob.get(), &NetJob::aborted, this, &Technic::SolderPackInstallTask::downloadAborted); connect(m_filesNetJob.get(), &NetJob::aborted, this, &Technic::SolderPackInstallTask::downloadAborted);
m_filesNetJob->start(); m_filesNetJob->start();

View File

@ -4,6 +4,7 @@
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com> * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me> * Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
* Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com>
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -36,17 +37,23 @@
*/ */
#include "Download.h" #include "Download.h"
#include <QUrl>
#include <QDateTime> #include <QDateTime>
#include <QFileInfo> #include <QFileInfo>
#include "ByteArraySink.h" #include "ByteArraySink.h"
#include "ChecksumValidator.h" #include "ChecksumValidator.h"
#include "FileSystem.h"
#include "MetaCacheSink.h" #include "MetaCacheSink.h"
#include "BuildConfig.h"
#include "Application.h" #include "Application.h"
#include "BuildConfig.h"
#include "net/Logging.h"
#include "net/NetAction.h"
#include "MMCTime.h"
#include "StringUtils.h"
namespace Net { namespace Net {
@ -54,6 +61,7 @@ auto Download::makeCached(QUrl url, MetaEntryPtr entry, Options options) -> Down
{ {
auto dl = makeShared<Download>(); auto dl = makeShared<Download>();
dl->m_url = url; dl->m_url = url;
dl->setObjectName(QString("CACHE:") + url.toString());
dl->m_options = options; dl->m_options = options;
auto md5Node = new ChecksumValidator(QCryptographicHash::Md5); auto md5Node = new ChecksumValidator(QCryptographicHash::Md5);
auto cachedNode = new MetaCacheSink(entry, md5Node, options.testFlag(Option::MakeEternal)); auto cachedNode = new MetaCacheSink(entry, md5Node, options.testFlag(Option::MakeEternal));
@ -65,6 +73,7 @@ auto Download::makeByteArray(QUrl url, QByteArray* output, Options options) -> D
{ {
auto dl = makeShared<Download>(); auto dl = makeShared<Download>();
dl->m_url = url; dl->m_url = url;
dl->setObjectName(QString("BYTES:") + url.toString());
dl->m_options = options; dl->m_options = options;
dl->m_sink.reset(new ByteArraySink(output)); dl->m_sink.reset(new ByteArraySink(output));
return dl; return dl;
@ -74,6 +83,7 @@ auto Download::makeFile(QUrl url, QString path, Options options) -> Download::Pt
{ {
auto dl = makeShared<Download>(); auto dl = makeShared<Download>();
dl->m_url = url; dl->m_url = url;
dl->setObjectName(QString("FILE:") + url.toString());
dl->m_options = options; dl->m_options = options;
dl->m_sink.reset(new FileSink(path)); dl->m_sink.reset(new FileSink(path));
return dl; return dl;
@ -86,10 +96,10 @@ void Download::addValidator(Validator* v)
void Download::executeTask() void Download::executeTask()
{ {
setStatus(tr("Downloading %1").arg(m_url.toString())); setStatus(tr("Downloading %1").arg(StringUtils::truncateUrlHumanFriendly(m_url, 80)));
if (getState() == Task::State::AbortedByUser) { if (getState() == Task::State::AbortedByUser) {
qWarning() << "Attempt to start an aborted Download:" << m_url.toString(); qCWarning(taskDownloadLogC) << getUid().toString() << "Attempt to start an aborted Download:" << m_url.toString();
emitAborted(); emitAborted();
return; return;
} }
@ -99,10 +109,10 @@ void Download::executeTask()
switch (m_state) { switch (m_state) {
case State::Succeeded: case State::Succeeded:
emit succeeded(); emit succeeded();
qDebug() << "Download cache hit " << m_url.toString(); qCDebug(taskDownloadLogC) << getUid().toString() << "Download cache hit " << m_url.toString();
return; return;
case State::Running: case State::Running:
qDebug() << "Downloading " << m_url.toString(); qCDebug(taskDownloadLogC) << getUid().toString() << "Downloading " << m_url.toString();
break; break;
case State::Inactive: case State::Inactive:
case State::Failed: case State::Failed:
@ -124,6 +134,9 @@ void Download::executeTask()
request.setRawHeader("Authorization", token.toUtf8()); request.setRawHeader("Authorization", token.toUtf8());
} }
m_last_progress_time = m_clock.now();
m_last_progress_bytes = 0;
QNetworkReply* rep = m_network->get(request); QNetworkReply* rep = m_network->get(request);
m_reply.reset(rep); m_reply.reset(rep);
@ -140,13 +153,39 @@ void Download::executeTask()
void Download::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) void Download::downloadProgress(qint64 bytesReceived, qint64 bytesTotal)
{ {
auto now = m_clock.now();
auto elapsed = now - m_last_progress_time;
// use milliseconds for speed precision
auto elapsed_ms = std::chrono::duration_cast<std::chrono::milliseconds>(elapsed);
auto bytes_received_since = bytesReceived - m_last_progress_bytes;
auto dl_speed_bps = (double)bytes_received_since / elapsed_ms.count() * 1000;
auto remaing_time_s = (bytesTotal - bytesReceived) / dl_speed_bps;
//: Current amount of bytes downloaded, out of the total amount of bytes in the download
QString dl_progress =
tr("%1 / %2").arg(StringUtils::humanReadableFileSize(bytesReceived)).arg(StringUtils::humanReadableFileSize(bytesTotal));
QString dl_speed_str;
if (elapsed_ms.count() > 0) {
auto str_eta = bytesTotal > 0 ? Time::humanReadableDuration(remaing_time_s) : tr("unknown");
//: Download speed, in bytes per second (remaining download time in parenthesis)
dl_speed_str =
tr("%1 /s (%2)").arg(StringUtils::humanReadableFileSize(dl_speed_bps)).arg(str_eta);
} else {
//: Download speed at 0 bytes per second
dl_speed_str = tr("0 B/s");
}
setDetails(dl_progress + "\n" + dl_speed_str);
setProgress(bytesReceived, bytesTotal); setProgress(bytesReceived, bytesTotal);
} }
void Download::downloadError(QNetworkReply::NetworkError error) void Download::downloadError(QNetworkReply::NetworkError error)
{ {
if (error == QNetworkReply::OperationCanceledError) { if (error == QNetworkReply::OperationCanceledError) {
qCritical() << "Aborted " << m_url.toString(); qCCritical(taskDownloadLogC) << getUid().toString() << "Aborted " << m_url.toString();
m_state = State::AbortedByUser; m_state = State::AbortedByUser;
} else { } else {
if (m_options & Option::AcceptLocalFiles) { if (m_options & Option::AcceptLocalFiles) {
@ -156,7 +195,7 @@ void Download::downloadError(QNetworkReply::NetworkError error)
} }
} }
// error happened during download. // error happened during download.
qCritical() << "Failed " << m_url.toString() << " with reason " << error; qCCritical(taskDownloadLogC) << getUid().toString() << "Failed " << m_url.toString() << " with reason " << error;
m_state = State::Failed; m_state = State::Failed;
} }
} }
@ -165,9 +204,10 @@ void Download::sslErrors(const QList<QSslError>& errors)
{ {
int i = 1; int i = 1;
for (auto error : errors) { for (auto error : errors) {
qCritical() << "Download" << m_url.toString() << "SSL Error #" << i << " : " << error.errorString(); qCCritical(taskDownloadLogC) << getUid().toString() << "Download" << m_url.toString() << "SSL Error #" << i << " : "
<< error.errorString();
auto cert = error.certificate(); auto cert = error.certificate();
qCritical() << "Certificate in question:\n" << cert.toText(); qCCritical(taskDownloadLogC) << getUid().toString() << "Certificate in question:\n" << cert.toText();
i++; i++;
} }
} }
@ -210,17 +250,17 @@ auto Download::handleRedirect() -> bool
*/ */
redirect = QUrl(redirectStr, QUrl::TolerantMode); redirect = QUrl(redirectStr, QUrl::TolerantMode);
if (!redirect.isValid()) { if (!redirect.isValid()) {
qWarning() << "Failed to parse redirect URL:" << redirectStr; qCWarning(taskDownloadLogC) << getUid().toString() << "Failed to parse redirect URL:" << redirectStr;
downloadError(QNetworkReply::ProtocolFailure); downloadError(QNetworkReply::ProtocolFailure);
return false; return false;
} }
qDebug() << "Fixed location header:" << redirect; qCDebug(taskDownloadLogC) << getUid().toString() << "Fixed location header:" << redirect;
} else { } else {
qDebug() << "Location header:" << redirect; qCDebug(taskDownloadLogC) << getUid().toString() << "Location header:" << redirect;
} }
m_url = QUrl(redirect.toString()); m_url = QUrl(redirect.toString());
qDebug() << "Following redirect to " << m_url.toString(); qCDebug(taskDownloadLogC) << getUid().toString() << "Following redirect to " << m_url.toString();
startAction(m_network); startAction(m_network);
return true; return true;
@ -230,26 +270,26 @@ void Download::downloadFinished()
{ {
// handle HTTP redirection first // handle HTTP redirection first
if (handleRedirect()) { if (handleRedirect()) {
qDebug() << "Download redirected:" << m_url.toString(); qCDebug(taskDownloadLogC) << getUid().toString() << "Download redirected:" << m_url.toString();
return; return;
} }
// if the download failed before this point ... // if the download failed before this point ...
if (m_state == State::Succeeded) // pretend to succeed so we continue processing :) 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(taskDownloadLogC) << getUid().toString() << "Download failed but we are allowed to proceed:" << m_url.toString();
m_sink->abort(); m_sink->abort();
m_reply.reset(); m_reply.reset();
emit succeeded(); emit succeeded();
return; return;
} else if (m_state == State::Failed) { } else if (m_state == State::Failed) {
qDebug() << "Download failed in previous step:" << m_url.toString(); qCDebug(taskDownloadLogC) << getUid().toString() << "Download failed in previous step:" << m_url.toString();
m_sink->abort(); m_sink->abort();
m_reply.reset(); m_reply.reset();
emit failed(""); emit failed("");
return; return;
} else if (m_state == State::AbortedByUser) { } else if (m_state == State::AbortedByUser) {
qDebug() << "Download aborted in previous step:" << m_url.toString(); qCDebug(taskDownloadLogC) << getUid().toString() << "Download aborted in previous step:" << m_url.toString();
m_sink->abort(); m_sink->abort();
m_reply.reset(); m_reply.reset();
emit aborted(); emit aborted();
@ -259,14 +299,14 @@ void Download::downloadFinished()
// make sure we got all the remaining data, if any // make sure we got all the remaining data, if any
auto data = m_reply->readAll(); auto data = m_reply->readAll();
if (data.size()) { if (data.size()) {
qDebug() << "Writing extra" << data.size() << "bytes"; qCDebug(taskDownloadLogC) << getUid().toString() << "Writing extra" << data.size() << "bytes";
m_state = m_sink->write(data); m_state = m_sink->write(data);
} }
// otherwise, finalize the whole graph // otherwise, finalize the whole graph
m_state = m_sink->finalize(*m_reply.get()); m_state = m_sink->finalize(*m_reply.get());
if (m_state != State::Succeeded) { if (m_state != State::Succeeded) {
qDebug() << "Download failed to finalize:" << m_url.toString(); qCDebug(taskDownloadLogC) << getUid().toString() << "Download failed to finalize:" << m_url.toString();
m_sink->abort(); m_sink->abort();
m_reply.reset(); m_reply.reset();
emit failed(""); emit failed("");
@ -274,7 +314,7 @@ void Download::downloadFinished()
} }
m_reply.reset(); m_reply.reset();
qDebug() << "Download succeeded:" << m_url.toString(); qCDebug(taskDownloadLogC) << getUid().toString() << "Download succeeded:" << m_url.toString();
emit succeeded(); emit succeeded();
} }
@ -284,11 +324,11 @@ void Download::downloadReadyRead()
auto data = m_reply->readAll(); auto data = m_reply->readAll();
m_state = m_sink->write(data); m_state = m_sink->write(data);
if (m_state == State::Failed) { if (m_state == State::Failed) {
qCritical() << "Failed to process response chunk"; qCCritical(taskDownloadLogC) << getUid().toString() << "Failed to process response chunk";
} }
// qDebug() << "Download" << m_url.toString() << "gained" << data.size() << "bytes"; // qDebug() << "Download" << m_url.toString() << "gained" << data.size() << "bytes";
} else { } else {
qCritical() << "Cannot write download data! illegal status " << m_status; qCCritical(taskDownloadLogC) << getUid().toString() << "Cannot write download data! illegal status " << m_status;
} }
} }

View File

@ -1,8 +1,9 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
/* /*
* PolyMC - Minecraft Launcher * Prism Launcher - Minecraft Launcher
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com> * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com>
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -22,6 +23,7 @@
* Copyright 2013-2021 MultiMC Contributors * Copyright 2013-2021 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
@ -36,6 +38,8 @@
#pragma once #pragma once
#include <chrono>
#include "HttpMetaCache.h" #include "HttpMetaCache.h"
#include "NetAction.h" #include "NetAction.h"
#include "Sink.h" #include "Sink.h"
@ -80,6 +84,10 @@ class Download : public NetAction {
private: private:
std::unique_ptr<Sink> m_sink; std::unique_ptr<Sink> m_sink;
Options m_options; 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;
}; };
} // namespace Net } // namespace Net

View File

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
/* /*
* PolyMC - Minecraft Launcher * Prism Launcher - Minecraft Launcher
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com> * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
@ -37,6 +37,8 @@
#include "FileSystem.h" #include "FileSystem.h"
#include "net/Logging.h"
namespace Net { namespace Net {
Task::State FileSink::init(QNetworkRequest& request) Task::State FileSink::init(QNetworkRequest& request)
@ -48,14 +50,14 @@ Task::State FileSink::init(QNetworkRequest& request)
// create a new save file and open it for writing // create a new save file and open it for writing
if (!FS::ensureFilePathExists(m_filename)) { if (!FS::ensureFilePathExists(m_filename)) {
qCritical() << "Could not create folder for " + m_filename; qCCritical(taskNetLogC) << "Could not create folder for " + m_filename;
return Task::State::Failed; return Task::State::Failed;
} }
wroteAnyData = false; wroteAnyData = false;
m_output_file.reset(new QSaveFile(m_filename)); m_output_file.reset(new QSaveFile(m_filename));
if (!m_output_file->open(QIODevice::WriteOnly)) { if (!m_output_file->open(QIODevice::WriteOnly)) {
qCritical() << "Could not open " + m_filename + " for writing"; qCCritical(taskNetLogC) << "Could not open " + m_filename + " for writing";
return Task::State::Failed; return Task::State::Failed;
} }
@ -67,7 +69,7 @@ Task::State FileSink::init(QNetworkRequest& request)
Task::State FileSink::write(QByteArray& data) Task::State FileSink::write(QByteArray& data)
{ {
if (!writeAllValidators(data) || m_output_file->write(data) != data.size()) { if (!writeAllValidators(data) || m_output_file->write(data) != data.size()) {
qCritical() << "Failed writing into " + m_filename; qCCritical(taskNetLogC) << "Failed writing into " + m_filename;
m_output_file->cancelWriting(); m_output_file->cancelWriting();
m_output_file.reset(); m_output_file.reset();
wroteAnyData = false; wroteAnyData = false;
@ -106,7 +108,7 @@ Task::State FileSink::finalize(QNetworkReply& reply)
// nothing went wrong... // nothing went wrong...
if (!m_output_file->commit()) { if (!m_output_file->commit()) {
qCritical() << "Failed to commit changes to " << m_filename; qCCritical(taskNetLogC) << "Failed to commit changes to " << m_filename;
m_output_file->cancelWriting(); m_output_file->cancelWriting();
return Task::State::Failed; return Task::State::Failed;
} }

View File

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
/* /*
* PolyMC - Minecraft Launcher * Prism Launcher - Minecraft Launcher
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com> * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify

View File

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
/* /*
* PolyMC - Minecraft Launcher * Prism Launcher - Minecraft Launcher
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com> * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
@ -44,6 +44,8 @@
#include <QDebug> #include <QDebug>
#include "net/Logging.h"
auto MetaEntry::getFullPath() -> QString auto MetaEntry::getFullPath() -> QString
{ {
// FIXME: make local? // FIXME: make local?
@ -124,7 +126,7 @@ auto HttpMetaCache::resolveEntry(QString base, QString resource_path, QString ex
// Get rid of old entries, to prevent cache problems // Get rid of old entries, to prevent cache problems
auto current_time = QDateTime::currentSecsSinceEpoch(); auto current_time = QDateTime::currentSecsSinceEpoch();
if (entry->isExpired(current_time - ( file_last_changed / 1000 ))) { if (entry->isExpired(current_time - ( file_last_changed / 1000 ))) {
qWarning() << "Removing cache entry because of old age!"; qCWarning(taskNetLogC) << "[HttpMetaCache]" << "Removing cache entry because of old age!";
selected_base.entry_list.remove(resource_path); selected_base.entry_list.remove(resource_path);
return staleEntry(base, resource_path); return staleEntry(base, resource_path);
} }
@ -137,12 +139,12 @@ auto HttpMetaCache::resolveEntry(QString base, QString resource_path, QString ex
auto HttpMetaCache::updateEntry(MetaEntryPtr stale_entry) -> bool auto HttpMetaCache::updateEntry(MetaEntryPtr stale_entry) -> bool
{ {
if (!m_entries.contains(stale_entry->m_baseId)) { if (!m_entries.contains(stale_entry->m_baseId)) {
qCritical() << "Cannot add entry with unknown base: " << stale_entry->m_baseId.toLocal8Bit(); qCCritical(taskHttpMetaCacheLogC) << "Cannot add entry with unknown base: " << stale_entry->m_baseId.toLocal8Bit();
return false; return false;
} }
if (stale_entry->m_stale) { if (stale_entry->m_stale) {
qCritical() << "Cannot add stale entry: " << stale_entry->getFullPath().toLocal8Bit(); qCCritical(taskHttpMetaCacheLogC) << "Cannot add stale entry: " << stale_entry->getFullPath().toLocal8Bit();
return false; return false;
} }
@ -166,10 +168,10 @@ void HttpMetaCache::evictAll()
{ {
for (QString& base : m_entries.keys()) { for (QString& base : m_entries.keys()) {
EntryMap& map = m_entries[base]; EntryMap& map = m_entries[base];
qDebug() << "Evicting base" << base; qCDebug(taskHttpMetaCacheLogC) << "Evicting base" << base;
for (MetaEntryPtr entry : map.entry_list) { for (MetaEntryPtr entry : map.entry_list) {
if (!evictEntry(entry)) if (!evictEntry(entry))
qWarning() << "Unexpected missing cache entry" << entry->m_basePath; qCWarning(taskHttpMetaCacheLogC) << "Unexpected missing cache entry" << entry->m_basePath;
} }
} }
} }
@ -267,7 +269,7 @@ void HttpMetaCache::SaveNow()
if (m_index_file.isNull()) if (m_index_file.isNull())
return; return;
qDebug() << "[HttpMetaCache]" << "Saving metacache with" << m_entries.size() << "entries"; qCDebug(taskHttpMetaCacheLogC) << "Saving metacache with" << m_entries.size() << "entries";
QJsonObject toplevel; QJsonObject toplevel;
Json::writeString(toplevel, "version", "1"); Json::writeString(toplevel, "version", "1");
@ -302,6 +304,6 @@ void HttpMetaCache::SaveNow()
try { try {
Json::write(toplevel, m_index_file); Json::write(toplevel, m_index_file);
} catch (const Exception& e) { } catch (const Exception& e) {
qWarning() << e.what(); qCWarning(taskHttpMetaCacheLogC) << "Error writing cache:" << e.what();
} }
} }

View File

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
/* /*
* PolyMC - Minecraft Launcher * Prism Launcher - Minecraft Launcher
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com> * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify

26
launcher/net/Logging.cpp Normal file
View File

@ -0,0 +1,26 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - 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/>.
*
*/
#include "net/Logging.h"
Q_LOGGING_CATEGORY(taskNetLogC, "launcher.task.net")
Q_LOGGING_CATEGORY(taskDownloadLogC, "launcher.task.net.download")
Q_LOGGING_CATEGORY(taskUploadLogC, "launcher.task.net.upload")
Q_LOGGING_CATEGORY(taskMetaCacheLogC, "launcher.task.net.metacache")
Q_LOGGING_CATEGORY(taskHttpMetaCacheLogC, "launcher.task.net.metacache.http")

28
launcher/net/Logging.h Normal file
View File

@ -0,0 +1,28 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - 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/>.
*
*/
#pragma once
#include <QLoggingCategory>
Q_DECLARE_LOGGING_CATEGORY(taskNetLogC)
Q_DECLARE_LOGGING_CATEGORY(taskDownloadLogC)
Q_DECLARE_LOGGING_CATEGORY(taskUploadLogC)
Q_DECLARE_LOGGING_CATEGORY(taskMetaCacheLogC)
Q_DECLARE_LOGGING_CATEGORY(taskHttpMetaCacheLogC)

View File

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
/* /*
* PolyMC - Minecraft Launcher * Prism Launcher - Minecraft Launcher
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com> * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
@ -39,6 +39,8 @@
#include <QRegularExpression> #include <QRegularExpression>
#include "Application.h" #include "Application.h"
#include "net/Logging.h"
namespace Net { namespace Net {
/** Maximum time to hold a cache entry /** Maximum time to hold a cache entry
@ -97,11 +99,11 @@ Task::State MetaCacheSink::finalizeCache(QNetworkReply & reply)
{ // Cache lifetime { // Cache lifetime
if (m_is_eternal) { if (m_is_eternal) {
qDebug() << "[MetaCache] Adding eternal cache entry:" << m_entry->getFullPath(); qCDebug(taskMetaCacheLogC) << "Adding eternal cache entry:" << m_entry->getFullPath();
m_entry->makeEternal(true); m_entry->makeEternal(true);
} else if (reply.hasRawHeader("Cache-Control")) { } else if (reply.hasRawHeader("Cache-Control")) {
auto cache_control_header = reply.rawHeader("Cache-Control"); auto cache_control_header = reply.rawHeader("Cache-Control");
// qDebug() << "[MetaCache] Parsing 'Cache-Control' header with" << cache_control_header; qCDebug(taskMetaCacheLogC) << "Parsing 'Cache-Control' header with" << cache_control_header;
QRegularExpression max_age_expr("max-age=([0-9]+)"); QRegularExpression max_age_expr("max-age=([0-9]+)");
qint64 max_age = max_age_expr.match(cache_control_header).captured(1).toLongLong(); qint64 max_age = max_age_expr.match(cache_control_header).captured(1).toLongLong();
@ -109,7 +111,7 @@ Task::State MetaCacheSink::finalizeCache(QNetworkReply & reply)
} else if (reply.hasRawHeader("Expires")) { } else if (reply.hasRawHeader("Expires")) {
auto expires_header = reply.rawHeader("Expires"); auto expires_header = reply.rawHeader("Expires");
// qDebug() << "[MetaCache] Parsing 'Expires' header with" << expires_header; qCDebug(taskMetaCacheLogC) << "Parsing 'Expires' header with" << expires_header;
qint64 max_age = QDateTime::fromString(expires_header).toSecsSinceEpoch() - QDateTime::currentSecsSinceEpoch(); qint64 max_age = QDateTime::fromString(expires_header).toSecsSinceEpoch() - QDateTime::currentSecsSinceEpoch();
m_entry->setMaximumAge(max_age); m_entry->setMaximumAge(max_age);
@ -119,7 +121,7 @@ Task::State MetaCacheSink::finalizeCache(QNetworkReply & reply)
if (reply.hasRawHeader("Age")) { if (reply.hasRawHeader("Age")) {
auto age_header = reply.rawHeader("Age"); auto age_header = reply.rawHeader("Age");
// qDebug() << "[MetaCache] Parsing 'Age' header with" << age_header; qCDebug(taskMetaCacheLogC) << "Parsing 'Age' header with" << age_header;
qint64 current_age = age_header.toLongLong(); qint64 current_age = age_header.toLongLong();
m_entry->setCurrentAge(current_age); m_entry->setCurrentAge(current_age);

View File

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
/* /*
* PolyMC - Minecraft Launcher * Prism Launcher - Minecraft Launcher
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com> * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify

View File

@ -1,7 +1,8 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
/* /*
* PolyMC - Minecraft Launcher * Prism Launcher - Minecraft Launcher
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com> * 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 * 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 * it under the terms of the GNU General Public License as published by
@ -44,7 +45,7 @@
class NetAction : public Task { class NetAction : public Task {
Q_OBJECT Q_OBJECT
protected: protected:
explicit NetAction() : Task() {}; explicit NetAction() : Task(){};
public: public:
using Ptr = shared_qobject_ptr<NetAction>; using Ptr = shared_qobject_ptr<NetAction>;
@ -69,7 +70,7 @@ class NetAction : public Task {
} }
protected: protected:
void executeTask() override {}; void executeTask() override{};
public: public:
shared_qobject_ptr<QNetworkAccessManager> m_network; shared_qobject_ptr<QNetworkAccessManager> m_network;

View File

@ -1,8 +1,9 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
/* /*
* PolyMC - Minecraft Launcher * Prism Launcher - Minecraft Launcher
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com> * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com>
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by

View File

@ -1,7 +1,8 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
/* /*
* PolyMC - Minecraft Launcher * Prism Launcher - Minecraft Launcher
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com> * 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 * 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 * it under the terms of the GNU General Public License as published by

View File

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
/* /*
* PolyMC - Minecraft Launcher * Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Lenny McLennington <lenny@sneed.church> * Copyright (C) 2022 Lenny McLennington <lenny@sneed.church>
* Copyright (C) 2022 Swirl <swurl@swurl.xyz> * Copyright (C) 2022 Swirl <swurl@swurl.xyz>
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
@ -47,6 +47,8 @@
#include <QFile> #include <QFile>
#include <QUrlQuery> #include <QUrlQuery>
#include "net/Logging.h"
std::array<PasteUpload::PasteTypeInfo, 4> PasteUpload::PasteTypes = { std::array<PasteUpload::PasteTypeInfo, 4> PasteUpload::PasteTypes = {
{{"0x0.st", "https://0x0.st", ""}, {{"0x0.st", "https://0x0.st", ""},
{"hastebin", "https://hst.sh", "/documents"}, {"hastebin", "https://hst.sh", "/documents"},
@ -147,7 +149,7 @@ void PasteUpload::executeTask()
void PasteUpload::downloadError(QNetworkReply::NetworkError error) void PasteUpload::downloadError(QNetworkReply::NetworkError error)
{ {
// error happened during download. // error happened during download.
qCritical() << "Network error: " << error; qCCritical(taskUploadLogC) << getUid().toString() << "Network error: " << error;
emitFailed(m_reply->errorString()); emitFailed(m_reply->errorString());
} }
@ -166,7 +168,7 @@ void PasteUpload::downloadFinished()
{ {
QString reasonPhrase = m_reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString(); QString reasonPhrase = m_reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString();
emitFailed(tr("Error: %1 returned unexpected status code %2 %3").arg(m_uploadUrl).arg(statusCode).arg(reasonPhrase)); emitFailed(tr("Error: %1 returned unexpected status code %2 %3").arg(m_uploadUrl).arg(statusCode).arg(reasonPhrase));
qCritical() << m_uploadUrl << " returned unexpected status code " << statusCode << " with body: " << data; qCCritical(taskUploadLogC) << getUid().toString() << m_uploadUrl << " returned unexpected status code " << statusCode << " with body: " << data;
m_reply.reset(); m_reply.reset();
return; return;
} }
@ -187,7 +189,7 @@ void PasteUpload::downloadFinished()
else else
{ {
emitFailed(tr("Error: %1 returned a malformed response body").arg(m_uploadUrl)); emitFailed(tr("Error: %1 returned a malformed response body").arg(m_uploadUrl));
qCritical() << m_uploadUrl << " returned malformed response body: " << data; qCCritical(taskUploadLogC) << getUid().toString() << getUid().toString() << m_uploadUrl << " returned malformed response body: " << data;
return; return;
} }
break; break;
@ -206,15 +208,15 @@ void PasteUpload::downloadFinished()
{ {
QString error = jsonObj["error"].toString(); QString error = jsonObj["error"].toString();
emitFailed(tr("Error: %1 returned an error: %2").arg(m_uploadUrl, error)); emitFailed(tr("Error: %1 returned an error: %2").arg(m_uploadUrl, error));
qCritical() << m_uploadUrl << " returned error: " << error; qCCritical(taskUploadLogC) << getUid().toString() << m_uploadUrl << " returned error: " << error;
qCritical() << "Response body: " << data; qCCritical(taskUploadLogC) << getUid().toString() << "Response body: " << data;
return; return;
} }
} }
else else
{ {
emitFailed(tr("Error: %1 returned a malformed response body").arg(m_uploadUrl)); emitFailed(tr("Error: %1 returned a malformed response body").arg(m_uploadUrl));
qCritical() << m_uploadUrl << " returned malformed response body: " << data; qCCritical(taskUploadLogC) << getUid().toString() << m_uploadUrl << " returned malformed response body: " << data;
return; return;
} }
break; break;
@ -234,16 +236,16 @@ void PasteUpload::downloadFinished()
QString error = jsonObj["error"].toString(); QString error = jsonObj["error"].toString();
QString message = (jsonObj.contains("message") && jsonObj["message"].isString()) ? jsonObj["message"].toString() : "none"; QString message = (jsonObj.contains("message") && jsonObj["message"].isString()) ? jsonObj["message"].toString() : "none";
emitFailed(tr("Error: %1 returned an error code: %2\nError message: %3").arg(m_uploadUrl, error, message)); emitFailed(tr("Error: %1 returned an error code: %2\nError message: %3").arg(m_uploadUrl, error, message));
qCritical() << m_uploadUrl << " returned error: " << error; qCCritical(taskUploadLogC) << getUid().toString() << m_uploadUrl << " returned error: " << error;
qCritical() << "Error message: " << message; qCCritical(taskUploadLogC) << getUid().toString() << "Error message: " << message;
qCritical() << "Response body: " << data; qCCritical(taskUploadLogC) << getUid().toString() << "Response body: " << data;
return; return;
} }
} }
else else
{ {
emitFailed(tr("Error: %1 returned a malformed response body").arg(m_uploadUrl)); emitFailed(tr("Error: %1 returned a malformed response body").arg(m_uploadUrl));
qCritical() << m_uploadUrl << " returned malformed response body: " << data; qCCritical(taskUploadLogC) << getUid().toString() << m_uploadUrl << " returned malformed response body: " << data;
return; return;
} }
break; break;

View File

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
/* /*
* PolyMC - Minecraft Launcher * Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Lenny McLennington <lenny@sneed.church> * Copyright (C) 2022 Lenny McLennington <lenny@sneed.church>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify

View File

@ -4,6 +4,7 @@
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com> * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me> * Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
* Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com>
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -42,6 +43,8 @@
#include "BuildConfig.h" #include "BuildConfig.h"
#include "Application.h" #include "Application.h"
#include "net/Logging.h"
namespace Net { namespace Net {
bool Upload::abort() bool Upload::abort()
@ -60,11 +63,11 @@ namespace Net {
void Upload::downloadError(QNetworkReply::NetworkError error) { void Upload::downloadError(QNetworkReply::NetworkError error) {
if (error == QNetworkReply::OperationCanceledError) { if (error == QNetworkReply::OperationCanceledError) {
qCritical() << "Aborted " << m_url.toString(); qCCritical(taskUploadLogC) << getUid().toString() << "Aborted " << m_url.toString();
m_state = State::AbortedByUser; m_state = State::AbortedByUser;
} else { } else {
// error happened during download. // error happened during download.
qCritical() << "Failed " << m_url.toString() << " with reason " << error; qCCritical(taskUploadLogC) << getUid().toString() << "Failed " << m_url.toString() << " with reason " << error;
m_state = State::Failed; m_state = State::Failed;
} }
} }
@ -72,9 +75,9 @@ namespace Net {
void Upload::sslErrors(const QList<QSslError> &errors) { void Upload::sslErrors(const QList<QSslError> &errors) {
int i = 1; int i = 1;
for (const auto& error : errors) { for (const auto& error : errors) {
qCritical() << "Upload" << m_url.toString() << "SSL Error #" << i << " : " << error.errorString(); qCCritical(taskUploadLogC) << getUid().toString() << "Upload" << m_url.toString() << "SSL Error #" << i << " : " << error.errorString();
auto cert = error.certificate(); auto cert = error.certificate();
qCritical() << "Certificate in question:\n" << cert.toText(); qCCritical(taskUploadLogC) << getUid().toString() << "Certificate in question:\n" << cert.toText();
i++; i++;
} }
} }
@ -117,17 +120,17 @@ namespace Net {
*/ */
redirect = QUrl(redirectStr, QUrl::TolerantMode); redirect = QUrl(redirectStr, QUrl::TolerantMode);
if (!redirect.isValid()) { if (!redirect.isValid()) {
qWarning() << "Failed to parse redirect URL:" << redirectStr; qCWarning(taskUploadLogC) << getUid().toString() << "Failed to parse redirect URL:" << redirectStr;
downloadError(QNetworkReply::ProtocolFailure); downloadError(QNetworkReply::ProtocolFailure);
return false; return false;
} }
qDebug() << "Fixed location header:" << redirect; qCDebug(taskUploadLogC) << getUid().toString() << "Fixed location header:" << redirect;
} else { } else {
qDebug() << "Location header:" << redirect; qCDebug(taskUploadLogC) << getUid().toString() << "Location header:" << redirect;
} }
m_url = QUrl(redirect.toString()); m_url = QUrl(redirect.toString());
qDebug() << "Following redirect to " << m_url.toString(); qCDebug(taskUploadLogC) << getUid().toString() << "Following redirect to " << m_url.toString();
startAction(m_network); startAction(m_network);
return true; return true;
} }
@ -136,25 +139,25 @@ namespace Net {
// handle HTTP redirection first // handle HTTP redirection first
// very unlikely for post requests, still can happen // very unlikely for post requests, still can happen
if (handleRedirect()) { if (handleRedirect()) {
qDebug() << "Upload redirected:" << m_url.toString(); qCDebug(taskUploadLogC) << getUid().toString() << "Upload redirected:" << m_url.toString();
return; return;
} }
// if the download failed before this point ... // if the download failed before this point ...
if (m_state == State::Succeeded) { if (m_state == State::Succeeded) {
qDebug() << "Upload failed but we are allowed to proceed:" << m_url.toString(); qCDebug(taskUploadLogC) << getUid().toString() << "Upload failed but we are allowed to proceed:" << m_url.toString();
m_sink->abort(); m_sink->abort();
m_reply.reset(); m_reply.reset();
emit succeeded(); emit succeeded();
return; return;
} else if (m_state == State::Failed) { } else if (m_state == State::Failed) {
qDebug() << "Upload failed in previous step:" << m_url.toString(); qCDebug(taskUploadLogC) << getUid().toString() << "Upload failed in previous step:" << m_url.toString();
m_sink->abort(); m_sink->abort();
m_reply.reset(); m_reply.reset();
emit failed(""); emit failed("");
return; return;
} else if (m_state == State::AbortedByUser) { } else if (m_state == State::AbortedByUser) {
qDebug() << "Upload aborted in previous step:" << m_url.toString(); qCDebug(taskUploadLogC) << getUid().toString() << "Upload aborted in previous step:" << m_url.toString();
m_sink->abort(); m_sink->abort();
m_reply.reset(); m_reply.reset();
emit aborted(); emit aborted();
@ -164,21 +167,21 @@ namespace Net {
// make sure we got all the remaining data, if any // make sure we got all the remaining data, if any
auto data = m_reply->readAll(); auto data = m_reply->readAll();
if (data.size()) { if (data.size()) {
qDebug() << "Writing extra" << data.size() << "bytes"; qCDebug(taskUploadLogC) << getUid().toString() << "Writing extra" << data.size() << "bytes";
m_state = m_sink->write(data); m_state = m_sink->write(data);
} }
// otherwise, finalize the whole graph // otherwise, finalize the whole graph
m_state = m_sink->finalize(*m_reply.get()); m_state = m_sink->finalize(*m_reply.get());
if (m_state != State::Succeeded) { if (m_state != State::Succeeded) {
qDebug() << "Upload failed to finalize:" << m_url.toString(); qCDebug(taskUploadLogC) << getUid().toString() << "Upload failed to finalize:" << m_url.toString();
m_sink->abort(); m_sink->abort();
m_reply.reset(); m_reply.reset();
emit failed(""); emit failed("");
return; return;
} }
m_reply.reset(); m_reply.reset();
qDebug() << "Upload succeeded:" << m_url.toString(); qCDebug(taskUploadLogC) << getUid().toString() << "Upload succeeded:" << m_url.toString();
emit succeeded(); emit succeeded();
} }
@ -193,7 +196,7 @@ namespace Net {
setStatus(tr("Uploading %1").arg(m_url.toString())); setStatus(tr("Uploading %1").arg(m_url.toString()));
if (m_state == State::AbortedByUser) { if (m_state == State::AbortedByUser) {
qWarning() << "Attempt to start an aborted Upload:" << m_url.toString(); qCWarning(taskUploadLogC) << getUid().toString() << "Attempt to start an aborted Upload:" << m_url.toString();
emit aborted(); emit aborted();
return; return;
} }
@ -202,10 +205,10 @@ namespace Net {
switch (m_state) { switch (m_state) {
case State::Succeeded: case State::Succeeded:
emitSucceeded(); emitSucceeded();
qDebug() << "Upload cache hit " << m_url.toString(); qCDebug(taskUploadLogC) << getUid().toString() << "Upload cache hit " << m_url.toString();
return; return;
case State::Running: case State::Running:
qDebug() << "Uploading " << m_url.toString(); qCDebug(taskUploadLogC) << getUid().toString() << "Uploading " << m_url.toString();
break; break;
case State::Inactive: case State::Inactive:
case State::Failed: case State::Failed:

View File

@ -1,8 +1,9 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
/* /*
* PolyMC - Minecraft Launcher * Prism Launcher - Minecraft Launcher
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com> * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com>
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by

16
launcher/qtlogging.ini Normal file
View File

@ -0,0 +1,16 @@
[Rules]
*.debug=true
# prevent log spam and strange bugs
# qt.qpa.drawing in particular causes theme artifacts on MacOS
qt.*.debug=false
# don't log credentials by default
launcher.auth.credentials.debug=false
# remove the debug lines, other log levels still get through
launcher.task.net.download.debug=false
# enable or disable whole catageries
launcher.task.net=true
launcher.task=false
launcher.task.net.upload=true
launcher.task.net.metacache=false
launcher.task.net.metacache.http=true

View File

@ -1,11 +1,49 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - 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
* 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "ConcurrentTask.h" #include "ConcurrentTask.h"
#include <QDebug>
#include <QCoreApplication> #include <QCoreApplication>
#include <QDebug>
#include "tasks/Task.h"
ConcurrentTask::ConcurrentTask(QObject* parent, QString task_name, int max_concurrent) ConcurrentTask::ConcurrentTask(QObject* parent, QString task_name, int max_concurrent)
: Task(parent), m_name(task_name), m_total_max_size(max_concurrent) : Task(parent), m_name(task_name), m_total_max_size(max_concurrent)
{ setObjectName(task_name); } {
setObjectName(task_name);
}
ConcurrentTask::~ConcurrentTask() ConcurrentTask::~ConcurrentTask()
{ {
@ -15,14 +53,9 @@ ConcurrentTask::~ConcurrentTask()
} }
} }
auto ConcurrentTask::getStepProgress() const -> qint64 auto ConcurrentTask::getStepProgress() const -> TaskStepProgressList
{ {
return m_stepProgress; return m_task_progress.values();
}
auto ConcurrentTask::getStepTotalProgress() const -> qint64
{
return m_stepTotalProgress;
} }
void ConcurrentTask::addTask(Task::Ptr task) void ConcurrentTask::addTask(Task::Ptr task)
@ -32,11 +65,9 @@ void ConcurrentTask::addTask(Task::Ptr task)
void ConcurrentTask::executeTask() void ConcurrentTask::executeTask()
{ {
// Start the least amount of tasks needed, but at least one // Start one task, startNext handles starting the up to the m_total_max_size
int num_starts = qMax(1, qMin(m_total_max_size, m_queue.size())); // while tracking the number currently being done
for (int i = 0; i < num_starts; i++) {
QMetaObject::invokeMethod(this, &ConcurrentTask::startNext, Qt::QueuedConnection); QMetaObject::invokeMethod(this, &ConcurrentTask::startNext, Qt::QueuedConnection);
}
} }
bool ConcurrentTask::abort() bool ConcurrentTask::abort()
@ -97,18 +128,22 @@ void ConcurrentTask::startNext()
Task::Ptr next = m_queue.dequeue(); 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::failed, this, [this, next](QString msg) { subTaskFailed(next, msg); });
connect(next.get(), &Task::status, this, &ConcurrentTask::subTaskStatus); connect(next.get(), &Task::status, this, [this, next](QString msg) { subTaskStatus(next, msg); });
connect(next.get(), &Task::stepStatus, this, &ConcurrentTask::subTaskStatus); connect(next.get(), &Task::details, this, [this, next](QString msg) { subTaskDetails(next, msg); });
connect(next.get(), &Task::stepProgress, this, [this, next](TaskStepProgress const& 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_doing.insert(next.get(), next);
auto task_progress = std::make_shared<TaskStepProgress>(TaskStepProgress({ next->getUid() }));
m_task_progress.insert(next->getUid(), task_progress);
setStepStatus(next->isMultiStep() ? next->getStepStatus() : next->getStatus());
updateState(); updateState();
updateStepProgress(*task_progress.get(), Operation::ADDED);
QCoreApplication::processEvents(); QCoreApplication::processEvents();
@ -123,12 +158,17 @@ void ConcurrentTask::startNext()
void ConcurrentTask::subTaskSucceeded(Task::Ptr task) void ConcurrentTask::subTaskSucceeded(Task::Ptr task)
{ {
m_done.insert(task.get(), task); m_done.insert(task.get(), task);
m_succeeded.insert(task.get(), task);
m_doing.remove(task.get()); m_doing.remove(task.get());
auto task_progress = m_task_progress.value(task->getUid());
task_progress->state = TaskStepState::Succeeded;
disconnect(task.get(), 0, this, 0); disconnect(task.get(), 0, this, 0);
emit stepProgress(*task_progress.get());
updateState(); updateState();
updateStepProgress(*task_progress.get(), Operation::REMOVED);
startNext(); startNext();
} }
@ -139,27 +179,128 @@ void ConcurrentTask::subTaskFailed(Task::Ptr task, const QString& msg)
m_doing.remove(task.get()); m_doing.remove(task.get());
auto task_progress = m_task_progress.value(task->getUid());
task_progress->state = TaskStepState::Failed;
disconnect(task.get(), 0, this, 0); disconnect(task.get(), 0, this, 0);
emit stepProgress(*task_progress.get());
updateState(); updateState();
updateStepProgress(*task_progress.get(), Operation::REMOVED);
startNext(); startNext();
} }
void ConcurrentTask::subTaskStatus(const QString& msg) void ConcurrentTask::subTaskStatus(Task::Ptr task, const QString& msg)
{ {
setStepStatus(msg); auto task_progress = m_task_progress.value(task->getUid());
task_progress->status = msg;
task_progress->state = TaskStepState::Running;
emit stepProgress(*task_progress.get());
if (totalSize() == 1) {
setStatus(msg);
}
} }
void ConcurrentTask::subTaskProgress(qint64 current, qint64 total) void ConcurrentTask::subTaskDetails(Task::Ptr task, const QString& msg)
{ {
m_stepProgress = current; auto task_progress = m_task_progress.value(task->getUid());
m_stepTotalProgress = total; task_progress->details = msg;
task_progress->state = TaskStepState::Running;
emit stepProgress(*task_progress.get());
if (totalSize() == 1) {
setDetails(msg);
}
}
void ConcurrentTask::subTaskProgress(Task::Ptr task, qint64 current, qint64 total)
{
auto task_progress = m_task_progress.value(task->getUid());
task_progress->old_current = task_progress->current;
task_progress->old_total = task_progress->old_total;
task_progress->current = current;
task_progress->total = total;
task_progress->state = TaskStepState::Running;
emit stepProgress(*task_progress.get());
updateStepProgress(*task_progress.get(), Operation::CHANGED);
updateState();
if (totalSize() == 1) {
setProgress(task_progress->current, task_progress->total);
}
}
void ConcurrentTask::subTaskStepProgress(Task::Ptr task, TaskStepProgress const& task_progress)
{
Operation op = Operation::ADDED;
if (!m_task_progress.contains(task_progress.uid)) {
m_task_progress.insert(task_progress.uid, std::make_shared<TaskStepProgress>(task_progress));
op = Operation::ADDED;
emit stepProgress(task_progress);
updateStepProgress(task_progress, op);
} else {
auto tp = m_task_progress.value(task_progress.uid);
tp->old_current = tp->current;
tp->old_total = tp->total;
tp->current = task_progress.current;
tp->total = task_progress.total;
tp->status = task_progress.status;
tp->details = task_progress.details;
op = Operation::CHANGED;
emit stepProgress(*tp.get());
updateStepProgress(*tp.get(), op);
}
}
void ConcurrentTask::updateStepProgress(TaskStepProgress const& changed_progress, Operation op)
{
switch (op) {
case Operation::ADDED:
m_stepProgress += changed_progress.current;
m_stepTotalProgress += changed_progress.total;
break;
case Operation::REMOVED:
m_stepProgress -= changed_progress.current;
m_stepTotalProgress -= changed_progress.total;
break;
case Operation::CHANGED:
m_stepProgress -= changed_progress.old_current;
m_stepTotalProgress -= changed_progress.old_total;
m_stepProgress += changed_progress.current;
m_stepTotalProgress += changed_progress.total;
break;
}
} }
void ConcurrentTask::updateState() void ConcurrentTask::updateState()
{ {
if (totalSize() > 1) {
setProgress(m_done.count(), totalSize()); setProgress(m_done.count(), totalSize());
setStatus(tr("Executing %1 task(s) (%2 out of %3 are done)") 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()))); .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 a 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);
}
} }

View File

@ -1,13 +1,51 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - 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
* 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once #pragma once
#include <QHash>
#include <QQueue> #include <QQueue>
#include <QSet> #include <QSet>
#include <QUuid>
#include <memory>
#include "tasks/Task.h" #include "tasks/Task.h"
class ConcurrentTask : public Task { class ConcurrentTask : public Task {
Q_OBJECT Q_OBJECT
public: public:
using Ptr = shared_qobject_ptr<ConcurrentTask>; using Ptr = shared_qobject_ptr<ConcurrentTask>;
explicit ConcurrentTask(QObject* parent = nullptr, QString task_name = "", int max_concurrent = 6); explicit ConcurrentTask(QObject* parent = nullptr, QString task_name = "", int max_concurrent = 6);
@ -15,15 +53,12 @@ public:
bool canAbort() const override { return true; } bool canAbort() const override { return true; }
inline auto isMultiStep() const -> bool override { return m_queue.size() > 1; }; inline auto isMultiStep() const -> bool override { return totalSize() > 1; };
auto getStepProgress() const -> qint64 override; auto getStepProgress() const -> TaskStepProgressList override;
auto getStepTotalProgress() const -> qint64 override;
inline auto getStepStatus() const -> QString override { return m_step_status; }
void addTask(Task::Ptr task); void addTask(Task::Ptr task);
public slots: public slots:
bool abort() override; bool abort() override;
/** Resets the internal state of the task. /** Resets the internal state of the task.
@ -31,26 +66,28 @@ public slots:
*/ */
void clear(); void clear();
protected protected slots:
slots:
void executeTask() override; void executeTask() override;
virtual void startNext(); virtual void startNext();
void subTaskSucceeded(Task::Ptr); void subTaskSucceeded(Task::Ptr);
void subTaskFailed(Task::Ptr, const QString &msg); void subTaskFailed(Task::Ptr, const QString& msg);
void subTaskStatus(const QString &msg); void subTaskStatus(Task::Ptr task, const QString& msg);
void subTaskProgress(qint64 current, qint64 total); void subTaskDetails(Task::Ptr task, const QString& msg);
void subTaskProgress(Task::Ptr task, qint64 current, qint64 total);
void subTaskStepProgress(Task::Ptr task, TaskStepProgress const& task_step_progress);
protected: protected:
// NOTE: This is not thread-safe. // NOTE: This is not thread-safe.
[[nodiscard]] unsigned int totalSize() const { return m_queue.size() + m_doing.size() + m_done.size(); } [[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); }; enum class Operation { ADDED, REMOVED, CHANGED };
void updateStepProgress(TaskStepProgress const& changed_progress, Operation);
virtual void updateState(); virtual void updateState();
protected: protected:
QString m_name; QString m_name;
QString m_step_status; QString m_step_status;
@ -59,6 +96,9 @@ protected:
QHash<Task*, Task::Ptr> m_doing; QHash<Task*, Task::Ptr> m_doing;
QHash<Task*, Task::Ptr> m_done; QHash<Task*, Task::Ptr> m_done;
QHash<Task*, Task::Ptr> m_failed; 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; int m_total_max_size;

View File

@ -1,3 +1,37 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2022 flowln <flowlnlnln@gmail.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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "MultipleOptionsTask.h" #include "MultipleOptionsTask.h"
#include <QDebug> #include <QDebug>

View File

@ -1,3 +1,37 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2022 flowln <flowlnlnln@gmail.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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once #pragma once
#include "SequentialTask.h" #include "SequentialTask.h"

View File

@ -1,3 +1,38 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - 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
* 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "SequentialTask.h" #include "SequentialTask.h"
#include <QDebug> #include <QDebug>

View File

@ -1,3 +1,38 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - 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
* 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once #pragma once
#include "ConcurrentTask.h" #include "ConcurrentTask.h"

View File

@ -2,6 +2,7 @@
/* /*
* PolyMC - Minecraft Launcher * PolyMC - Minecraft Launcher
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com> * 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 * 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 * it under the terms of the GNU General Public License as published by
@ -37,8 +38,11 @@
#include <QDebug> #include <QDebug>
Q_LOGGING_CATEGORY(taskLogC, "launcher.task")
Task::Task(QObject *parent, bool show_debug) : QObject(parent), m_show_debug(show_debug) Task::Task(QObject *parent, bool show_debug) : QObject(parent), m_show_debug(show_debug)
{ {
m_uid = QUuid::createUuid();
setAutoDelete(false); setAutoDelete(false);
} }
@ -51,11 +55,23 @@ void Task::setStatus(const QString &new_status)
} }
} }
void Task::setDetails(const QString& new_details)
{
if (m_details != new_details)
{
m_details = new_details;
emit details(m_details);
}
}
void Task::setProgress(qint64 current, qint64 total) void Task::setProgress(qint64 current, qint64 total)
{ {
if ((m_progress != current) || (m_progressTotal != total)) {
m_progress = current; m_progress = current;
m_progressTotal = total; m_progressTotal = total;
emit progress(m_progress, m_progressTotal); emit progress(m_progress, m_progressTotal);
}
} }
void Task::start() void Task::start()
@ -65,31 +81,31 @@ void Task::start()
case State::Inactive: case State::Inactive:
{ {
if (m_show_debug) if (m_show_debug)
qDebug() << "Task" << describe() << "starting for the first time"; qCDebug(taskLogC) << "Task" << describe() << "starting for the first time";
break; break;
} }
case State::AbortedByUser: case State::AbortedByUser:
{ {
if (m_show_debug) 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; break;
} }
case State::Failed: case State::Failed:
{ {
if (m_show_debug) if (m_show_debug)
qDebug() << "Task" << describe() << "restarting for after failing at first"; qCDebug(taskLogC) << "Task" << describe() << "restarting for after failing at first";
break; break;
} }
case State::Succeeded: case State::Succeeded:
{ {
if (m_show_debug) if (m_show_debug)
qDebug() << "Task" << describe() << "restarting for after succeeding at first"; qCDebug(taskLogC) << "Task" << describe() << "restarting for after succeeding at first";
break; break;
} }
case State::Running: case State::Running:
{ {
if (m_show_debug) 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; return;
} }
} }
@ -104,12 +120,12 @@ void Task::emitFailed(QString reason)
// Don't fail twice. // Don't fail twice.
if (!isRunning()) if (!isRunning())
{ {
qCritical() << "Task" << describe() << "failed while not running!!!!: " << reason; qCCritical(taskLogC) << "Task" << describe() << "failed while not running!!!!: " << reason;
return; return;
} }
m_state = State::Failed; m_state = State::Failed;
m_failReason = reason; m_failReason = reason;
qCritical() << "Task" << describe() << "failed: " << reason; qCCritical(taskLogC) << "Task" << describe() << "failed: " << reason;
emit failed(reason); emit failed(reason);
emit finished(); emit finished();
} }
@ -119,13 +135,13 @@ void Task::emitAborted()
// Don't abort twice. // Don't abort twice.
if (!isRunning()) if (!isRunning())
{ {
qCritical() << "Task" << describe() << "aborted while not running!!!!"; qCCritical(taskLogC) << "Task" << describe() << "aborted while not running!!!!";
return; return;
} }
m_state = State::AbortedByUser; m_state = State::AbortedByUser;
m_failReason = "Aborted."; m_failReason = "Aborted.";
if (m_show_debug) if (m_show_debug)
qDebug() << "Task" << describe() << "aborted."; qCDebug(taskLogC) << "Task" << describe() << "aborted.";
emit aborted(); emit aborted();
emit finished(); emit finished();
} }
@ -135,16 +151,21 @@ void Task::emitSucceeded()
// Don't succeed twice. // Don't succeed twice.
if (!isRunning()) if (!isRunning())
{ {
qCritical() << "Task" << describe() << "succeeded while not running!!!!"; qCCritical(taskLogC) << "Task" << describe() << "succeeded while not running!!!!";
return; return;
} }
m_state = State::Succeeded; m_state = State::Succeeded;
if (m_show_debug) if (m_show_debug)
qDebug() << "Task" << describe() << "succeeded"; qCDebug(taskLogC) << "Task" << describe() << "succeeded";
emit succeeded(); emit succeeded();
emit finished(); emit finished();
} }
void Task::propogateStepProgress(TaskStepProgress const& task_progress)
{
emit stepProgress(task_progress);
}
QString Task::describe() QString Task::describe()
{ {
QString outStr; QString outStr;
@ -159,6 +180,7 @@ QString Task::describe()
{ {
out << name; out << name;
} }
out << " ID: " << m_uid.toString(QUuid::WithoutBraces);
out << QChar(')'); out << QChar(')');
out.flush(); out.flush();
return outStr; return outStr;

View File

@ -1,7 +1,8 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
/* /*
* PolyMC - Minecraft Launcher * PrismLauncher - Minecraft Launcher
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com> * 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 * 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 * it under the terms of the GNU General Public License as published by
@ -36,9 +37,40 @@
#pragma once #pragma once
#include <QRunnable> #include <QRunnable>
#include <QUuid>
#include <QLoggingCategory>
#include "QObjectPtr.h" #include "QObjectPtr.h"
Q_DECLARE_LOGGING_CATEGORY(taskLogC)
enum class TaskStepState {
Waiting,
Running,
Failed,
Succeeded
};
Q_DECLARE_METATYPE(TaskStepState)
struct TaskStepProgress {
QUuid uid;
qint64 current = 0;
qint64 total = -1;
qint64 old_current = 0;
qint64 old_total = -1;
QString status = "";
QString details = "";
TaskStepState state = TaskStepState::Waiting;
bool isDone() const { return (state == TaskStepState::Failed) || (state == TaskStepState::Succeeded); }
};
Q_DECLARE_METATYPE(TaskStepProgress)
typedef QList<std::shared_ptr<TaskStepProgress>> TaskStepProgressList;
class Task : public QObject, public QRunnable { class Task : public QObject, public QRunnable {
Q_OBJECT Q_OBJECT
public: public:
@ -73,12 +105,15 @@ class Task : public QObject, public QRunnable {
auto getState() const -> State { return m_state; } auto getState() const -> State { return m_state; }
QString getStatus() { return m_status; } QString getStatus() { return m_status; }
virtual auto getStepStatus() const -> QString { return m_status; } QString getDetails() { return m_details; }
qint64 getProgress() { return m_progress; } qint64 getProgress() { return m_progress; }
qint64 getTotalProgress() { return m_progressTotal; } qint64 getTotalProgress() { return m_progressTotal; }
virtual auto getStepProgress() const -> qint64 { return 0; } virtual auto getStepProgress() const -> TaskStepProgressList { return {}; }
virtual auto getStepTotalProgress() const -> qint64 { return 100; }
QUuid getUid() { return m_uid; }
protected: protected:
void logWarning(const QString& line); void logWarning(const QString& line);
@ -94,7 +129,8 @@ class Task : public QObject, public QRunnable {
void aborted(); void aborted();
void failed(QString reason); void failed(QString reason);
void status(QString status); void status(QString status);
void stepStatus(QString status); void details(QString details);
void stepProgress(TaskStepProgress const& task_progress);
/** Emitted when the canAbort() status has changed. /** Emitted when the canAbort() status has changed.
*/ */
@ -117,8 +153,11 @@ class Task : public QObject, public QRunnable {
virtual void emitAborted(); virtual void emitAborted();
virtual void emitFailed(QString reason = ""); virtual void emitFailed(QString reason = "");
virtual void propogateStepProgress(TaskStepProgress const& task_progress);
public slots: public slots:
void setStatus(const QString& status); void setStatus(const QString& status);
void setDetails(const QString& details);
void setProgress(qint64 current, qint64 total); void setProgress(qint64 current, qint64 total);
protected: protected:
@ -126,6 +165,7 @@ class Task : public QObject, public QRunnable {
QStringList m_Warnings; QStringList m_Warnings;
QString m_failReason = ""; QString m_failReason = "";
QString m_status; QString m_status;
QString m_details;
int m_progress = 0; int m_progress = 0;
int m_progressTotal = 100; int m_progressTotal = 100;
@ -135,4 +175,6 @@ class Task : public QObject, public QRunnable {
private: private:
// Change using setAbortStatus // Change using setAbortStatus
bool m_can_abort = false; bool m_can_abort = false;
QUuid m_uid;
}; };

View File

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,14 +36,34 @@
#include "ProgressDialog.h" #include "ProgressDialog.h"
#include "ui_ProgressDialog.h" #include "ui_ProgressDialog.h"
#include <limits>
#include <QDebug> #include <QDebug>
#include <QKeyEvent> #include <QKeyEvent>
#include "tasks/Task.h" #include "tasks/Task.h"
#include "ui/widgets/SubTaskProgressBar.h"
// map a value in a numeric range of an arbitrary type to between 0 and INT_MAX
// for getting the best precision out of the qt progress bar
template<typename T, std::enable_if_t<std::is_arithmetic_v<T>, bool> = true>
std::tuple<int, int> map_int_zero_max(T current, T range_max, T range_min)
{
int int_max = std::numeric_limits<int>::max();
auto type_range = range_max - range_min;
double percentage = static_cast<double>(current - range_min) / static_cast<double>(type_range);
int mapped_current = percentage * int_max;
return {mapped_current, int_max};
}
ProgressDialog::ProgressDialog(QWidget* parent) : QDialog(parent), ui(new Ui::ProgressDialog) ProgressDialog::ProgressDialog(QWidget* parent) : QDialog(parent), ui(new Ui::ProgressDialog)
{ {
ui->setupUi(this); ui->setupUi(this);
ui->taskProgressScrollArea->setHidden(true);
this->setWindowFlags(this->windowFlags() & ~Qt::WindowContextHelpButtonHint); this->setWindowFlags(this->windowFlags() & ~Qt::WindowContextHelpButtonHint);
setAttribute(Qt::WidgetAttribute::WA_QuitOnClose, true); setAttribute(Qt::WidgetAttribute::WA_QuitOnClose, true);
setSkipButton(false); setSkipButton(false);
@ -55,9 +95,23 @@ ProgressDialog::~ProgressDialog()
void ProgressDialog::updateSize() void ProgressDialog::updateSize()
{ {
QSize lastSize = this->size();
QSize qSize = QSize(480, minimumSizeHint().height()); QSize qSize = QSize(480, minimumSizeHint().height());
// if the current window is too small
if ((lastSize != qSize) && (lastSize.height() < qSize.height()))
{
resize(qSize); resize(qSize);
setFixedSize(qSize);
// keep the dialog in the center after a resize
this->move(
this->parentWidget()->x() + (this->parentWidget()->width() - this->width()) / 2,
this->parentWidget()->y() + (this->parentWidget()->height() - this->height()) / 2
);
}
setMinimumSize(qSize);
} }
int ProgressDialog::execWithTask(Task* task) int ProgressDialog::execWithTask(Task* task)
@ -79,17 +133,15 @@ int ProgressDialog::execWithTask(Task* task)
connect(task, &Task::failed, this, &ProgressDialog::onTaskFailed); connect(task, &Task::failed, this, &ProgressDialog::onTaskFailed);
connect(task, &Task::succeeded, this, &ProgressDialog::onTaskSucceeded); connect(task, &Task::succeeded, this, &ProgressDialog::onTaskSucceeded);
connect(task, &Task::status, this, &ProgressDialog::changeStatus); connect(task, &Task::status, this, &ProgressDialog::changeStatus);
connect(task, &Task::stepStatus, this, &ProgressDialog::changeStatus); connect(task, &Task::details, this, &ProgressDialog::changeStatus);
connect(task, &Task::stepProgress, this, &ProgressDialog::changeStepProgress);
connect(task, &Task::progress, this, &ProgressDialog::changeProgress); connect(task, &Task::progress, this, &ProgressDialog::changeProgress);
connect(task, &Task::aborted, this, &ProgressDialog::hide); connect(task, &Task::aborted, this, &ProgressDialog::hide);
connect(task, &Task::abortStatusChanged, ui->skipButton, &QPushButton::setEnabled); connect(task, &Task::abortStatusChanged, ui->skipButton, &QPushButton::setEnabled);
m_is_multi_step = task->isMultiStep(); m_is_multi_step = task->isMultiStep();
if (!m_is_multi_step) { ui->taskProgressScrollArea->setHidden(!m_is_multi_step);
ui->globalStatusLabel->setHidden(true); updateSize();
ui->globalProgressBar->setHidden(true);
}
// It's a good idea to start the task after we entered the dialog's event loop :^) // It's a good idea to start the task after we entered the dialog's event loop :^)
if (!task->isRunning()) { if (!task->isRunning()) {
@ -149,23 +201,53 @@ void ProgressDialog::onTaskSucceeded()
void ProgressDialog::changeStatus(const QString& status) void ProgressDialog::changeStatus(const QString& status)
{ {
ui->globalStatusLabel->setText(task->getStatus()); ui->globalStatusLabel->setText(task->getStatus());
ui->statusLabel->setText(task->getStepStatus()); ui->globalStatusDetailsLabel->setText(task->getDetails());
updateSize(); updateSize();
} }
void ProgressDialog::addTaskProgress(TaskStepProgress const& progress)
{
SubTaskProgressBar* task_bar = new SubTaskProgressBar(this);
taskProgress.insert(progress.uid, task_bar);
ui->taskProgressLayout->addWidget(task_bar);
}
void ProgressDialog::changeStepProgress(TaskStepProgress const& task_progress)
{
m_is_multi_step = true;
if(ui->taskProgressScrollArea->isHidden()) {
ui->taskProgressScrollArea->setHidden(false);
updateSize();
}
if (!taskProgress.contains(task_progress.uid))
addTaskProgress(task_progress);
auto task_bar = taskProgress.value(task_progress.uid);
auto const [mapped_current, mapped_total] = map_int_zero_max<qint64>(task_progress.current, task_progress.total, 0);
if (task_progress.total <= 0) {
task_bar->setRange(0, 0);
} else {
task_bar->setRange(0, mapped_total);
}
task_bar->setValue(mapped_current);
task_bar->setStatus(task_progress.status);
task_bar->setDetails(task_progress.details);
if (task_progress.isDone()) {
task_bar->setVisible(false);
}
}
void ProgressDialog::changeProgress(qint64 current, qint64 total) void ProgressDialog::changeProgress(qint64 current, qint64 total)
{ {
ui->globalProgressBar->setMaximum(total); ui->globalProgressBar->setMaximum(total);
ui->globalProgressBar->setValue(current); 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());
}
} }
void ProgressDialog::keyPressEvent(QKeyEvent* e) void ProgressDialog::keyPressEvent(QKeyEvent* e)

View File

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -13,10 +33,18 @@
* limitations under the License. * limitations under the License.
*/ */
#pragma once #pragma once
#include <QDialog> #include <QDialog>
#include <memory> #include <memory>
#include <QHash>
#include <QUuid>
#include "QObjectPtr.h"
#include "tasks/Task.h"
#include "ui/widgets/SubTaskProgressBar.h"
class Task; class Task;
class SequentialTask; class SequentialTask;
@ -52,6 +80,7 @@ slots:
void changeStatus(const QString &status); void changeStatus(const QString &status);
void changeProgress(qint64 current, qint64 total); void changeProgress(qint64 current, qint64 total);
void changeStepProgress(TaskStepProgress const& task_progress);
private private
@ -64,6 +93,7 @@ protected:
private: private:
bool handleImmediateResult(QDialog::DialogCode &result); bool handleImmediateResult(QDialog::DialogCode &result);
void addTaskProgress(TaskStepProgress const& progress);
private: private:
Ui::ProgressDialog *ui; Ui::ProgressDialog *ui;
@ -71,4 +101,8 @@ private:
Task *task; Task *task;
bool m_is_multi_step = false; bool m_is_multi_step = false;
QHash<QUuid, SubTaskProgressBar*> taskProgress;
}; };

View File

@ -2,26 +2,129 @@
<ui version="4.0"> <ui version="4.0">
<class>ProgressDialog</class> <class>ProgressDialog</class>
<widget class="QDialog" name="ProgressDialog"> <widget class="QDialog" name="ProgressDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>480</width>
<height>210</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>1</horstretch>
<verstretch>1</verstretch>
</sizepolicy>
</property>
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>400</width> <width>480</width>
<height>0</height> <height>210</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>600</width>
<height>16777215</height>
</size> </size>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
<string>Please wait...</string> <string>Please wait...</string>
</property> </property>
<layout class="QGridLayout" name="gridLayout"> <property name="sizeGripEnabled">
<item row="4" column="0"> <bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout" stretch="0,0,0,0">
<item>
<layout class="QHBoxLayout" name="horizontalLayout" stretch="1,0">
<item>
<widget class="QLabel" name="globalStatusLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>15</height>
</size>
</property>
<property name="text">
<string>Global Task Status...</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="globalStatusDetailsLabel">
<property name="text">
<string>Global 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="globalProgressBar">
<property name="enabled">
<bool>true</bool>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>24</height>
</size>
</property>
<property name="value">
<number>24</number>
</property>
</widget>
</item>
<item>
<widget class="QScrollArea" name="taskProgressScrollArea">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>100</height>
</size>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAsNeeded</enum>
</property>
<property name="sizeAdjustPolicy">
<enum>QAbstractScrollArea::AdjustToContents</enum>
</property>
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="taskProgressContainer">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>464</width>
<height>96</height>
</rect>
</property>
<layout class="QVBoxLayout" name="taskProgressLayout">
<property name="spacing">
<number>2</number>
</property>
</layout>
</widget>
</widget>
</item>
<item>
<widget class="QPushButton" name="skipButton"> <widget class="QPushButton" name="skipButton">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding"> <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch> <horstretch>0</horstretch>
<verstretch>0</verstretch> <verstretch>0</verstretch>
</sizepolicy> </sizepolicy>
@ -31,49 +134,6 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="0">
<widget class="QLabel" name="globalStatusLabel">
<property name="text">
<string>Global Task Status...</string>
</property>
</widget>
</item>
<item row="2" column="0">
<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>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>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
<resources/> <resources/>

View File

@ -51,6 +51,7 @@ void ProgressWidget::watch(const Task* task)
connect(m_task, &Task::finished, this, &ProgressWidget::handleTaskFinish); connect(m_task, &Task::finished, this, &ProgressWidget::handleTaskFinish);
connect(m_task, &Task::status, this, &ProgressWidget::handleTaskStatus); connect(m_task, &Task::status, this, &ProgressWidget::handleTaskStatus);
// TODO: should we connect &Task::details
connect(m_task, &Task::progress, this, &ProgressWidget::handleTaskProgress); connect(m_task, &Task::progress, this, &ProgressWidget::handleTaskProgress);
connect(m_task, &Task::destroyed, this, &ProgressWidget::taskDestroyed); connect(m_task, &Task::destroyed, this, &ProgressWidget::taskDestroyed);

View 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);
}

View File

@ -0,0 +1,48 @@
// 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 <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;
};

View File

@ -0,0 +1,94 @@
<?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>312</width>
<height>86</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout" stretch="0,0">
<property name="spacing">
<number>0</number>
</property>
<item>
<layout class="QHBoxLayout" name="horizontalLayout" stretch="1,0">
<property name="spacing">
<number>8</number>
</property>
<item>
<widget class="QLabel" name="statusLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<pointsize>8</pointsize>
</font>
</property>
<property name="text">
<string>Sub Task Status...</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="statusDetailsLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<pointsize>8</pointsize>
</font>
</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="font">
<font>
<pointsize>8</pointsize>
</font>
</property>
<property name="value">
<number>24</number>
</property>
<property name="textVisible">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -283,6 +283,7 @@ Section "@Launcher_DisplayName@"
File "@Launcher_APP_BINARY_NAME@.exe" File "@Launcher_APP_BINARY_NAME@.exe"
File "@Launcher_APP_BINARY_NAME@_filelink.exe" File "@Launcher_APP_BINARY_NAME@_filelink.exe"
File "qt.conf" File "qt.conf"
File "qtlogging.ini"
File *.dll File *.dll
File /r "iconengines" File /r "iconengines"
File /r "imageformats" File /r "imageformats"

View File

@ -69,8 +69,9 @@ class BigConcurrentTaskThread : public QThread {
auto sub_tasks = new BasicTask::Ptr[s_num_tasks]; auto sub_tasks = new BasicTask::Ptr[s_num_tasks];
for (unsigned i = 0; i < s_num_tasks; i++) { for (unsigned i = 0; i < s_num_tasks; i++) {
sub_tasks[i] = makeShared<BasicTask>(false); auto sub_task = makeShared<BasicTask>(false);
big_task.addTask(sub_tasks[i]); sub_tasks[i] = sub_task;
big_task.addTask(sub_task);
} }
big_task.run(); big_task.run();
@ -99,7 +100,7 @@ class TaskTest : public QObject {
t.setStatus(status); t.setStatus(status);
QCOMPARE(t.getStatus(), status); QCOMPARE(t.getStatus(), status);
QCOMPARE(t.getStepStatus(), status); QCOMPARE(t.getStepProgress().isEmpty(), TaskStepProgressList{}.isEmpty());
} }
void test_SetStatus_MultiStep(){ void test_SetStatus_MultiStep(){
@ -111,7 +112,7 @@ class TaskTest : public QObject {
QCOMPARE(t.getStatus(), status); QCOMPARE(t.getStatus(), status);
// Even though it is multi step, it does not override the getStepStatus method, // Even though it is multi step, it does not override the getStepStatus method,
// so it should remain the same. // so it should remain the same.
QCOMPARE(t.getStepStatus(), status); QCOMPARE(t.getStepProgress().isEmpty(), TaskStepProgressList{}.isEmpty());
} }
void test_SetProgress(){ void test_SetProgress(){