This commit is contained in:
Trial97 2023-12-13 14:48:31 +02:00
commit 763eab6b96
No known key found for this signature in database
GPG Key ID: 55EF5DA53DB36318
46 changed files with 324 additions and 152 deletions

View File

@ -24,7 +24,7 @@ jobs:
with: with:
ref: ${{ github.event.pull_request.head.sha }} ref: ${{ github.event.pull_request.head.sha }}
- name: Create backport PRs - name: Create backport PRs
uses: korthout/backport-action@v2.1.1 uses: korthout/backport-action@v2.2.0
with: with:
# Config README: https://github.com/korthout/backport-action#backport-action # Config README: https://github.com/korthout/backport-action#backport-action
pull_description: |- pull_description: |-

View File

@ -61,7 +61,7 @@ jobs:
qt_ver: 6 qt_ver: 6
qt_host: windows qt_host: windows
qt_arch: '' qt_arch: ''
qt_version: '6.6.0' qt_version: '6.6.1'
qt_modules: 'qt5compat qtimageformats' qt_modules: 'qt5compat qtimageformats'
qt_tools: '' qt_tools: ''
@ -73,7 +73,7 @@ jobs:
qt_ver: 6 qt_ver: 6
qt_host: windows qt_host: windows
qt_arch: 'win64_msvc2019_arm64' qt_arch: 'win64_msvc2019_arm64'
qt_version: '6.6.0' qt_version: '6.6.1'
qt_modules: 'qt5compat qtimageformats' qt_modules: 'qt5compat qtimageformats'
qt_tools: '' qt_tools: ''
@ -83,7 +83,7 @@ jobs:
qt_ver: 6 qt_ver: 6
qt_host: mac qt_host: mac
qt_arch: '' qt_arch: ''
qt_version: '6.6.0' qt_version: '6.6.1'
qt_modules: 'qt5compat qtimageformats' qt_modules: 'qt5compat qtimageformats'
qt_tools: '' qt_tools: ''

View File

@ -17,7 +17,7 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: cachix/install-nix-action@6a9a9e84a173d90b3ffb42c5ddaf9ea033fad011 # v23 - uses: cachix/install-nix-action@7ac1ec25491415c381d9b62f0657c7a028df52a7 # v24
- uses: DeterminateSystems/update-flake-lock@v20 - uses: DeterminateSystems/update-flake-lock@v20
with: with:

30
flake.lock generated
View File

@ -21,11 +21,11 @@
"nixpkgs-lib": "nixpkgs-lib" "nixpkgs-lib": "nixpkgs-lib"
}, },
"locked": { "locked": {
"lastModified": 1698882062, "lastModified": 1701473968,
"narHash": "sha256-HkhafUayIqxXyHH1X8d9RDl1M2CkFgZLjKD3MzabiEo=", "narHash": "sha256-YcVE5emp1qQ8ieHUnxt1wCZCC3ZfAS+SRRWZ2TMda7E=",
"owner": "hercules-ci", "owner": "hercules-ci",
"repo": "flake-parts", "repo": "flake-parts",
"rev": "8c9fa2545007b49a5db5f650ae91f227672c3877", "rev": "34fed993f1674c8d06d58b37ce1e0fe5eebcb9f5",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -91,11 +91,11 @@
}, },
"nix-filter": { "nix-filter": {
"locked": { "locked": {
"lastModified": 1694857738, "lastModified": 1701697642,
"narHash": "sha256-bxxNyLHjhu0N8T3REINXQ2ZkJco0ABFPn6PIe2QUfqo=", "narHash": "sha256-L217WytWZHSY8GW9Gx1A64OnNctbuDbfslaTEofXXRw=",
"owner": "numtide", "owner": "numtide",
"repo": "nix-filter", "repo": "nix-filter",
"rev": "41fd48e00c22b4ced525af521ead8792402de0ea", "rev": "c843418ecfd0344ecb85844b082ff5675e02c443",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -106,11 +106,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1700108881, "lastModified": 1701998057,
"narHash": "sha256-+Lqybl8kj0+nD/IlAWPPG/RDTa47gff9nbei0u7BntE=", "narHash": "sha256-gAJGhcTO9cso7XDfAScXUlPcva427AUT2q02qrmXPdo=",
"owner": "nixos", "owner": "nixos",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "7414e9ee0b3e9903c24d3379f577a417f0aae5f1", "rev": "09dc04054ba2ff1f861357d0e7e76d021b273cd7",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -123,11 +123,11 @@
"nixpkgs-lib": { "nixpkgs-lib": {
"locked": { "locked": {
"dir": "lib", "dir": "lib",
"lastModified": 1698611440, "lastModified": 1701253981,
"narHash": "sha256-jPjHjrerhYDy3q9+s5EAsuhyhuknNfowY6yt6pjn9pc=", "narHash": "sha256-ztaDIyZ7HrTAfEEUt9AtTDNoCYxUdSd6NrRHaYOIxtk=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "0cbe9f69c234a7700596e943bfae7ef27a31b735", "rev": "e92039b55bcd58469325ded85d4f58dd5a4eaf58",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -153,11 +153,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1700064067, "lastModified": 1700922917,
"narHash": "sha256-1ZWNDzhu8UlVCK7+DUN9dVQfiHX1bv6OQP9VxstY/gs=", "narHash": "sha256-ej2fch/T584b5K9sk1UhmZF7W6wEfDHuoUYpFN8dtvM=",
"owner": "cachix", "owner": "cachix",
"repo": "pre-commit-hooks.nix", "repo": "pre-commit-hooks.nix",
"rev": "e558068cba67b23b4fbc5537173dbb43748a17e8", "rev": "e5ee5c5f3844550c01d2131096c7271cec5e9b78",
"type": "github" "type": "github"
}, },
"original": { "original": {

View File

@ -132,6 +132,15 @@
#include "gamemode_client.h" #include "gamemode_client.h"
#endif #endif
#if defined(Q_OS_LINUX)
#include <sys/statvfs.h>
#endif
#if defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
#include <sys/mount.h>
#include <sys/types.h>
#endif
#if defined(Q_OS_MAC) #if defined(Q_OS_MAC)
#if defined(SPARKLE_ENABLED) #if defined(SPARKLE_ENABLED)
#include "updater/MacSparkleUpdater.h" #include "updater/MacSparkleUpdater.h"
@ -659,6 +668,8 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
// The cat // The cat
m_settings->registerSetting("TheCat", false); m_settings->registerSetting("TheCat", false);
m_settings->registerSetting("StatusBarVisible", true);
m_settings->registerSetting("ToolbarsLocked", false); m_settings->registerSetting("ToolbarsLocked", false);
m_settings->registerSetting("InstSortMode", "Name"); m_settings->registerSetting("InstSortMode", "Name");
@ -988,6 +999,37 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
} }
} }
// notify user if /tmp is mounted with `noexec` (#1693)
{
bool is_tmp_noexec = false;
#if defined(Q_OS_LINUX)
struct statvfs tmp_stat;
statvfs("/tmp", &tmp_stat);
is_tmp_noexec = tmp_stat.f_flag & ST_NOEXEC;
#elif defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
struct statfs tmp_stat;
statfs("/tmp", &tmp_stat);
is_tmp_noexec = tmp_stat.f_flags & MNT_NOEXEC;
#endif
if (is_tmp_noexec) {
auto infoMsg =
tr("Your /tmp directory is currently mounted with the 'noexec' flag enabled.\n"
"Some versions of Minecraft may not launch.\n");
auto msgBox = new QMessageBox(QMessageBox::Information, tr("Incompatible system configuration"), infoMsg, QMessageBox::Ok);
msgBox->setDefaultButton(QMessageBox::Ok);
msgBox->setAttribute(Qt::WA_DeleteOnClose);
msgBox->setMinimumWidth(460);
msgBox->adjustSize();
msgBox->open();
}
}
if (createSetupWizard()) { if (createSetupWizard()) {
return; return;
} }
@ -1471,6 +1513,17 @@ InstanceWindow* Application::showInstanceWindow(InstancePtr instance, QString pa
auto& window = extras.window; auto& window = extras.window;
if (window) { if (window) {
// If the window is minimized on macOS or Windows, activate and bring it up
#ifdef Q_OS_MACOS
if (window->isMinimized()) {
window->setWindowState(window->windowState() & ~Qt::WindowMinimized);
}
#elif defined(Q_OS_WIN)
if (window->isMinimized()) {
window->showNormal();
}
#endif
window->raise(); window->raise();
window->activateWindow(); window->activateWindow();
} else { } else {
@ -1478,6 +1531,7 @@ InstanceWindow* Application::showInstanceWindow(InstancePtr instance, QString pa
m_openWindows++; m_openWindows++;
connect(window, &InstanceWindow::isClosing, this, &Application::on_windowClose); connect(window, &InstanceWindow::isClosing, this, &Application::on_windowClose);
} }
if (!page.isEmpty()) { if (!page.isEmpty()) {
window->selectPage(page); window->selectPage(page);
} }

View File

@ -64,6 +64,8 @@ BaseInstance::BaseInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr s
m_settings->registerSetting("lastLaunchTime", 0); m_settings->registerSetting("lastLaunchTime", 0);
m_settings->registerSetting("totalTimePlayed", 0); m_settings->registerSetting("totalTimePlayed", 0);
if (m_settings->get("totalTimePlayed").toLongLong() < 0)
m_settings->reset("totalTimePlayed");
m_settings->registerSetting("lastTimePlayed", 0); m_settings->registerSetting("lastTimePlayed", 0);
m_settings->registerSetting("linkedInstances", "[]"); m_settings->registerSetting("linkedInstances", "[]");

View File

@ -142,9 +142,8 @@ void InstanceCopyTask::copyFinished()
if (!m_keepPlaytime) { if (!m_keepPlaytime) {
inst->resetTimePlayed(); inst->resetTimePlayed();
} }
if (m_useLinks)
inst->addLinkedInstanceId(m_origInstance->id());
if (m_useLinks) { if (m_useLinks) {
inst->addLinkedInstanceId(m_origInstance->id());
auto allowed_symlinks_file = QFileInfo(FS::PathCombine(inst->gameRoot(), "allowed_symlinks.txt")); auto allowed_symlinks_file = QFileInfo(FS::PathCombine(inst->gameRoot(), "allowed_symlinks.txt"));
QByteArray allowed_symlinks; QByteArray allowed_symlinks;

View File

@ -307,7 +307,6 @@ void ResourceFolderModel::applyUpdates(QSet<QString>& current_set, QSet<QString>
auto removed_it = m_resources.begin() + removed_index; auto removed_it = m_resources.begin() + removed_index;
Q_ASSERT(removed_it != m_resources.end()); Q_ASSERT(removed_it != m_resources.end());
Q_ASSERT(removed_set.contains(removed_it->get()->internal_id()));
if ((*removed_it)->isResolving()) { if ((*removed_it)->isResolving()) {
auto ticket = (*removed_it)->resolutionTicket(); auto ticket = (*removed_it)->resolutionTicket();

View File

@ -192,7 +192,9 @@ Task::Ptr GetModDependenciesTask::prepareDependencyTask(const ModPlatform::Depen
ResourceAPI::DependencySearchArgs args = { dep, m_version, m_loaderType }; ResourceAPI::DependencySearchArgs args = { dep, m_version, m_loaderType };
ResourceAPI::DependencySearchCallbacks callbacks; ResourceAPI::DependencySearchCallbacks callbacks;
callbacks.on_fail = [](QString reason, int) {
qCritical() << tr("A network error occurred. Could not load project dependenies:%1").arg(reason);
};
callbacks.on_succeed = [dep, provider, pDep, level, this](auto& doc, [[maybe_unused]] auto& pack) { callbacks.on_succeed = [dep, provider, pDep, level, this](auto& doc, [[maybe_unused]] auto& pack) {
try { try {
QJsonArray arr; QJsonArray arr;

View File

@ -149,6 +149,7 @@ void EnsureMetadataTask::executeTask()
if (m_current_task) if (m_current_task)
m_current_task.reset(); m_current_task.reset();
}); });
connect(project_task.get(), &Task::failed, this, &EnsureMetadataTask::emitFailed);
m_current_task = project_task; m_current_task = project_task;
project_task->start(); project_task->start();

View File

@ -99,6 +99,7 @@ class ResourceAPI {
}; };
struct VersionSearchCallbacks { struct VersionSearchCallbacks {
std::function<void(QJsonDocument&, ModPlatform::IndexedPack)> on_succeed; std::function<void(QJsonDocument&, ModPlatform::IndexedPack)> on_succeed;
std::function<void(QString const& reason, int network_error_code)> on_fail;
}; };
struct ProjectInfoArgs { struct ProjectInfoArgs {
@ -121,6 +122,7 @@ class ResourceAPI {
struct DependencySearchCallbacks { struct DependencySearchCallbacks {
std::function<void(QJsonDocument&, const ModPlatform::Dependency&)> on_succeed; std::function<void(QJsonDocument&, const ModPlatform::Dependency&)> on_succeed;
std::function<void(QString const& reason, int network_error_code)> on_fail;
}; };
public: public:

View File

@ -119,7 +119,6 @@ void Flame::FileResolvingTask::netJobFinished()
connect(m_checkJob.get(), &NetJob::failed, this, [this, step_progress](QString reason) { connect(m_checkJob.get(), &NetJob::failed, this, [this, step_progress](QString reason) {
step_progress->state = TaskStepState::Failed; step_progress->state = TaskStepState::Failed;
stepProgress(*step_progress); stepProgress(*step_progress);
emitFailed(reason);
}); });
connect(m_checkJob.get(), &NetJob::stepProgress, this, &FileResolvingTask::propagateStepProgress); connect(m_checkJob.get(), &NetJob::stepProgress, this, &FileResolvingTask::propagateStepProgress);
connect(m_checkJob.get(), &NetJob::progress, this, [this, step_progress](qint64 current, qint64 total) { connect(m_checkJob.get(), &NetJob::progress, this, [this, step_progress](qint64 current, qint64 total) {

View File

@ -24,7 +24,7 @@ bool FlameCheckUpdate::abort()
return true; return true;
} }
ModPlatform::IndexedPack getProjectInfo(ModPlatform::IndexedVersion& ver_info) ModPlatform::IndexedPack FlameCheckUpdate::getProjectInfo(ModPlatform::IndexedVersion& ver_info)
{ {
ModPlatform::IndexedPack pack; ModPlatform::IndexedPack pack;
@ -57,6 +57,7 @@ ModPlatform::IndexedPack getProjectInfo(ModPlatform::IndexedVersion& ver_info)
} }
}); });
connect(get_project_job, &NetJob::failed, this, &FlameCheckUpdate::emitFailed);
QObject::connect(get_project_job, &NetJob::finished, [&loop, get_project_job] { QObject::connect(get_project_job, &NetJob::finished, [&loop, get_project_job] {
get_project_job->deleteLater(); get_project_job->deleteLater();
loop.quit(); loop.quit();
@ -68,7 +69,7 @@ ModPlatform::IndexedPack getProjectInfo(ModPlatform::IndexedVersion& ver_info)
return pack; return pack;
} }
ModPlatform::IndexedVersion getFileInfo(int addonId, int fileId) ModPlatform::IndexedVersion FlameCheckUpdate::getFileInfo(int addonId, int fileId)
{ {
ModPlatform::IndexedVersion ver; ModPlatform::IndexedVersion ver;
@ -100,7 +101,7 @@ ModPlatform::IndexedVersion getFileInfo(int addonId, int fileId)
qDebug() << doc; qDebug() << doc;
} }
}); });
connect(get_file_info_job, &NetJob::failed, this, &FlameCheckUpdate::emitFailed);
QObject::connect(get_file_info_job, &NetJob::finished, [&loop, get_file_info_job] { QObject::connect(get_file_info_job, &NetJob::finished, [&loop, get_file_info_job] {
get_file_info_job->deleteLater(); get_file_info_job->deleteLater();
loop.quit(); loop.quit();

View File

@ -22,6 +22,9 @@ class FlameCheckUpdate : public CheckUpdateTask {
void executeTask() override; void executeTask() override;
private: private:
ModPlatform::IndexedPack getProjectInfo(ModPlatform::IndexedVersion& ver_info);
ModPlatform::IndexedVersion getFileInfo(int addonId, int fileId);
NetJob* m_net_job = nullptr; NetJob* m_net_job = nullptr;
bool m_was_aborted = false; bool m_was_aborted = false;

View File

@ -227,6 +227,7 @@ bool FlameCreationTask::updateInstance()
m_files_to_remove.append(old_minecraft_dir.absoluteFilePath(relative_path)); m_files_to_remove.append(old_minecraft_dir.absoluteFilePath(relative_path));
} }
}); });
connect(job.get(), &Task::failed, this, [](QString reason) { qCritical() << "Failed to get files: " << reason; });
connect(job.get(), &Task::finished, &loop, &QEventLoop::quit); connect(job.get(), &Task::finished, &loop, &QEventLoop::quit);
m_process_update_file_info_job = job; m_process_update_file_info_job = job;
@ -427,6 +428,9 @@ bool FlameCreationTask::createInstance()
// Don't add managed info to packs without an ID (most likely imported from ZIP) // Don't add managed info to packs without an ID (most likely imported from ZIP)
if (!m_managed_id.isEmpty()) if (!m_managed_id.isEmpty())
instance.setManagedPack("flame", m_managed_id, m_pack.name, m_managed_version_id, m_pack.version); instance.setManagedPack("flame", m_managed_id, m_pack.name, m_managed_version_id, m_pack.version);
else
instance.setManagedPack("flame", "", name(), "", "");
instance.setName(name()); instance.setName(name());
m_mod_id_resolver.reset(new Flame::FileResolvingTask(APPLICATION->network(), m_pack)); m_mod_id_resolver.reset(new Flame::FileResolvingTask(APPLICATION->network(), m_pack));

View File

@ -323,6 +323,7 @@ void FlamePackExportTask::getProjectsInfo()
} }
buildZip(); buildZip();
}); });
connect(projTask.get(), &Task::failed, this, &FlamePackExportTask::emitFailed);
task.reset(projTask); task.reset(projTask);
task->start(); task->start();
} }

View File

@ -102,6 +102,13 @@ Task::Ptr NetworkResourceAPI::getProjectVersions(VersionSearchArgs&& args, Versi
callbacks.on_succeed(doc, args.pack); callbacks.on_succeed(doc, args.pack);
}); });
QObject::connect(netJob.get(), &NetJob::failed, [&netJob, callbacks](QString reason) {
int network_error_code = -1;
if (auto* failed_action = netJob->getFailedActions().at(0); failed_action && failed_action->m_reply)
network_error_code = failed_action->m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
callbacks.on_fail(reason, network_error_code);
});
return netJob; return netJob;
} }
@ -146,6 +153,12 @@ Task::Ptr NetworkResourceAPI::getDependencyVersion(DependencySearchArgs&& args,
callbacks.on_succeed(doc, args.dependency); callbacks.on_succeed(doc, args.dependency);
}); });
QObject::connect(netJob.get(), &NetJob::failed, [&netJob, callbacks](QString reason) {
int network_error_code = -1;
if (auto* failed_action = netJob->getFailedActions().at(0); failed_action && failed_action->m_reply)
network_error_code = failed_action->m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
callbacks.on_fail(reason, network_error_code);
});
return netJob; return netJob;
} }

View File

@ -72,9 +72,7 @@ void ModrinthCheckUpdate::executeTask()
auto response = std::make_shared<QByteArray>(); auto response = std::make_shared<QByteArray>();
auto job = api.latestVersions(hashes, best_hash_type, m_game_versions, m_loaders, response); auto job = api.latestVersions(hashes, best_hash_type, m_game_versions, m_loaders, response);
QEventLoop lock; connect(job.get(), &Task::succeeded, this, [this, response, mappings, best_hash_type, job] {
connect(job.get(), &Task::succeeded, this, [this, response, &mappings, best_hash_type, job] {
QJsonParseError parse_error{}; QJsonParseError parse_error{};
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
if (parse_error.error != QJsonParseError::NoError) { if (parse_error.error != QJsonParseError::NoError) {
@ -82,7 +80,7 @@ void ModrinthCheckUpdate::executeTask()
<< " reason: " << parse_error.errorString(); << " reason: " << parse_error.errorString();
qWarning() << *response; qWarning() << *response;
failed(parse_error.errorString()); emitFailed(parse_error.errorString());
return; return;
} }
@ -167,19 +165,17 @@ void ModrinthCheckUpdate::executeTask()
m_deps.append(std::make_shared<GetModDependenciesTask::PackDependency>(pack, project_ver)); m_deps.append(std::make_shared<GetModDependenciesTask::PackDependency>(pack, project_ver));
} }
} catch (Json::JsonException& e) { } catch (Json::JsonException& e) {
failed(e.cause() + " : " + e.what()); emitFailed(e.cause() + " : " + e.what());
return;
} }
emitSucceeded();
}); });
connect(job.get(), &Task::finished, &lock, &QEventLoop::quit); connect(job.get(), &Task::failed, this, &ModrinthCheckUpdate::emitFailed);
setStatus(tr("Waiting for the API response from Modrinth...")); setStatus(tr("Waiting for the API response from Modrinth..."));
setProgress(1, 3); setProgress(1, 3);
m_net_job = qSharedPointerObjectCast<NetJob, Task>(job); m_net_job = qSharedPointerObjectCast<NetJob, Task>(job);
job->start(); job->start();
lock.exec();
emitSucceeded();
} }

View File

@ -226,6 +226,9 @@ bool ModrinthCreationTask::createInstance()
// Don't add managed info to packs without an ID (most likely imported from ZIP) // Don't add managed info to packs without an ID (most likely imported from ZIP)
if (!m_managed_id.isEmpty()) if (!m_managed_id.isEmpty())
instance.setManagedPack("modrinth", m_managed_id, m_managed_name, m_managed_version_id, version()); instance.setManagedPack("modrinth", m_managed_id, m_managed_name, m_managed_version_id, version());
else
instance.setManagedPack("modrinth", "", name(), "", "");
instance.setName(name()); instance.setName(name());
instance.saveNow(); instance.saveNow();
@ -289,7 +292,7 @@ bool ModrinthCreationTask::createInstance()
// Only change the name if it didn't use a custom name, so that the previous custom name // Only change the name if it didn't use a custom name, so that the previous custom name
// is preserved, but if we're using the original one, we update the version string. // is preserved, but if we're using the original one, we update the version string.
// NOTE: This needs to come before the copyManagedPack call! // NOTE: This needs to come before the copyManagedPack call!
if (inst->name().contains(inst->getManagedPackVersionName())) { if (inst->name().contains(inst->getManagedPackVersionName()) && inst->name() != instance.name()) {
if (askForChangingInstanceName(m_parent, inst->name(), instance.name()) == InstanceNameChange::ShouldChange) if (askForChangingInstanceName(m_parent, inst->name(), instance.name()) == InstanceNameChange::ShouldChange)
inst->setName(instance.name()); inst->setName(instance.name());
} }

View File

@ -36,6 +36,7 @@
*/ */
#include "NetJob.h" #include "NetJob.h"
#include "tasks/ConcurrentTask.h"
#if defined(LAUNCHER_APPLICATION) #if defined(LAUNCHER_APPLICATION)
#include "Application.h" #include "Application.h"
#endif #endif
@ -56,18 +57,15 @@ auto NetJob::addNetAction(NetAction::Ptr action) -> bool
return true; return true;
} }
void NetJob::startNext() void NetJob::executeNextSubTask()
{ {
if (m_queue.isEmpty() && m_doing.isEmpty()) {
// We're finished, check for failures and retry if we can (up to 3 times) // We're finished, check for failures and retry if we can (up to 3 times)
if (!m_failed.isEmpty() && m_try < 3) { if (isRunning() && m_queue.isEmpty() && m_doing.isEmpty() && !m_failed.isEmpty() && m_try < 3) {
m_try += 1; m_try += 1;
while (!m_failed.isEmpty()) while (!m_failed.isEmpty())
m_queue.enqueue(m_failed.take(*m_failed.keyBegin())); m_queue.enqueue(m_failed.take(*m_failed.keyBegin()));
} }
} ConcurrentTask::executeNextSubTask();
ConcurrentTask::startNext();
} }
auto NetJob::size() const -> int auto NetJob::size() const -> int

View File

@ -55,8 +55,6 @@ class NetJob : public ConcurrentTask {
explicit NetJob(QString job_name, shared_qobject_ptr<QNetworkAccessManager> network); explicit NetJob(QString job_name, shared_qobject_ptr<QNetworkAccessManager> network);
~NetJob() override = default; ~NetJob() override = default;
void startNext() override;
auto size() const -> int; auto size() const -> int;
auto canAbort() const -> bool override; auto canAbort() const -> bool override;
@ -69,6 +67,9 @@ class NetJob : public ConcurrentTask {
// Qt can't handle auto at the start for some reason? // Qt can't handle auto at the start for some reason?
bool abort() override; bool abort() override;
protected slots:
void executeNextSubTask() override;
protected: protected:
void updateState() override; void updateState() override;

View File

@ -58,14 +58,14 @@ void ImgurUpload::init()
QNetworkReply* ImgurUpload::getReply(QNetworkRequest& request) QNetworkReply* ImgurUpload::getReply(QNetworkRequest& request)
{ {
auto file = new QFile(m_fileInfo.absoluteFilePath()); auto file = new QFile(m_fileInfo.absoluteFilePath(), this);
if (!file->open(QFile::ReadOnly)) { if (!file->open(QFile::ReadOnly)) {
emitFailed(); emitFailed();
return nullptr; return nullptr;
} }
QHttpMultiPart* multipart = new QHttpMultiPart(QHttpMultiPart::FormDataType); QHttpMultiPart* multipart = new QHttpMultiPart(QHttpMultiPart::FormDataType, this);
file->setParent(multipart); file->setParent(multipart);
QHttpPart filePart; QHttpPart filePart;
filePart.setBodyDevice(file); filePart.setBodyDevice(file);

View File

@ -54,6 +54,7 @@ bool INIFile::saveFile(QString fileName)
insert("ConfigVersion", "1.2"); insert("ConfigVersion", "1.2");
QSettings _settings_obj{ fileName, QSettings::Format::IniFormat }; QSettings _settings_obj{ fileName, QSettings::Format::IniFormat };
_settings_obj.setFallbacksEnabled(false); _settings_obj.setFallbacksEnabled(false);
_settings_obj.clear();
for (Iterator iter = begin(); iter != end(); iter++) for (Iterator iter = begin(); iter != end(); iter++)
_settings_obj.setValue(iter.key(), iter.value()); _settings_obj.setValue(iter.key(), iter.value());

View File

@ -35,7 +35,6 @@
*/ */
#include "ConcurrentTask.h" #include "ConcurrentTask.h"
#include <QCoreApplication>
#include <QDebug> #include <QDebug>
#include "tasks/Task.h" #include "tasks/Task.h"
@ -47,9 +46,9 @@ ConcurrentTask::ConcurrentTask(QObject* parent, QString task_name, int max_concu
ConcurrentTask::~ConcurrentTask() ConcurrentTask::~ConcurrentTask()
{ {
for (auto task : m_queue) { for (auto task : m_doing) {
if (task) if (task)
task->deleteLater(); task->disconnect(this);
} }
} }
@ -65,15 +64,13 @@ void ConcurrentTask::addTask(Task::Ptr task)
void ConcurrentTask::executeTask() void ConcurrentTask::executeTask()
{ {
// Start one task, startNext handles starting the up to the m_total_max_size for (auto i = 0; i < m_total_max_size; i++)
// while tracking the number currently being done QMetaObject::invokeMethod(this, &ConcurrentTask::executeNextSubTask, Qt::QueuedConnection);
QMetaObject::invokeMethod(this, &ConcurrentTask::startNext, Qt::QueuedConnection);
} }
bool ConcurrentTask::abort() bool ConcurrentTask::abort()
{ {
m_queue.clear(); m_queue.clear();
m_aborted = true;
if (m_doing.isEmpty()) { if (m_doing.isEmpty()) {
// Don't call emitAborted() here, we want to bypass the 'is the task running' check // Don't call emitAborted() here, we want to bypass the 'is the task running' check
@ -108,29 +105,36 @@ void ConcurrentTask::clear()
m_failed.clear(); m_failed.clear();
m_queue.clear(); m_queue.clear();
m_aborted = false;
m_progress = 0; m_progress = 0;
m_stepProgress = 0; m_stepProgress = 0;
} }
void ConcurrentTask::startNext() void ConcurrentTask::executeNextSubTask()
{ {
if (m_aborted || m_doing.count() > m_total_max_size) if (!isRunning()) {
return; return;
}
if (m_queue.isEmpty() && m_doing.isEmpty() && !wasSuccessful()) { if (m_doing.count() >= m_total_max_size) {
return;
}
if (m_queue.isEmpty()) {
if (m_doing.isEmpty()) {
if (m_failed.isEmpty())
emitSucceeded(); emitSucceeded();
else
emitFailed(tr("One or more subtasks failed"));
}
return; return;
} }
if (m_queue.isEmpty()) startSubTask(m_queue.dequeue());
return; }
Task::Ptr next = m_queue.dequeue();
void ConcurrentTask::startSubTask(Task::Ptr next)
{
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); });
// this should never happen but if it does, it's better to fail the task than get stuck
connect(next.get(), &Task::aborted, this, [this, next] { subTaskFailed(next, "Aborted"); }); connect(next.get(), &Task::aborted, this, [this, next] { subTaskFailed(next, "Aborted"); });
connect(next.get(), &Task::status, this, [this, next](QString msg) { subTaskStatus(next, msg); }); connect(next.get(), &Task::status, this, [this, next](QString msg) { subTaskStatus(next, msg); });
@ -140,55 +144,42 @@ void ConcurrentTask::startNext()
connect(next.get(), &Task::progress, this, [this, next](qint64 current, qint64 total) { subTaskProgress(next, current, total); }); 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);
qsizetype num_starts = qMin(m_queue.size(), m_total_max_size - m_doing.size());
auto task_progress = std::make_shared<TaskStepProgress>(next->getUid()); auto task_progress = std::make_shared<TaskStepProgress>(next->getUid());
m_task_progress.insert(next->getUid(), task_progress); m_task_progress.insert(next->getUid(), task_progress);
updateState(); updateState();
updateStepProgress(*task_progress.get(), Operation::ADDED); updateStepProgress(*task_progress.get(), Operation::ADDED);
QCoreApplication::processEvents();
QMetaObject::invokeMethod(next.get(), &Task::start, Qt::QueuedConnection); QMetaObject::invokeMethod(next.get(), &Task::start, Qt::QueuedConnection);
}
// Allow going up the number of concurrent tasks in case of tasks being added in the middle of a running task. void ConcurrentTask::subTaskFinished(Task::Ptr task, TaskStepState state)
for (int i = 0; i < num_starts; i++) {
QMetaObject::invokeMethod(this, &ConcurrentTask::startNext, Qt::QueuedConnection); m_done.insert(task.get(), task);
(state == TaskStepState::Succeeded ? m_succeeded : m_failed).insert(task.get(), task);
m_doing.remove(task.get());
auto task_progress = m_task_progress.value(task->getUid());
task_progress->state = state;
disconnect(task.get(), 0, this, 0);
emit stepProgress(*task_progress);
updateState();
updateStepProgress(*task_progress, Operation::REMOVED);
QMetaObject::invokeMethod(this, &ConcurrentTask::executeNextSubTask, Qt::QueuedConnection);
} }
void ConcurrentTask::subTaskSucceeded(Task::Ptr task) void ConcurrentTask::subTaskSucceeded(Task::Ptr task)
{ {
m_done.insert(task.get(), task); subTaskFinished(task, TaskStepState::Succeeded);
m_succeeded.insert(task.get(), task);
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);
emit stepProgress(*task_progress);
updateState();
updateStepProgress(*task_progress, Operation::REMOVED);
startNext();
} }
void ConcurrentTask::subTaskFailed(Task::Ptr task, [[maybe_unused]] const QString& msg) void ConcurrentTask::subTaskFailed(Task::Ptr task, [[maybe_unused]] const QString& msg)
{ {
m_done.insert(task.get(), task); subTaskFinished(task, TaskStepState::Failed);
m_failed.insert(task.get(), task);
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);
emit stepProgress(*task_progress);
updateState();
updateStepProgress(*task_progress, Operation::REMOVED);
startNext();
} }
void ConcurrentTask::subTaskStatus(Task::Ptr task, const QString& msg) void ConcurrentTask::subTaskStatus(Task::Ptr task, const QString& msg)

View File

@ -72,10 +72,11 @@ class ConcurrentTask : public Task {
protected slots: protected slots:
void executeTask() override; void executeTask() override;
virtual void startNext(); virtual void executeNextSubTask();
void subTaskSucceeded(Task::Ptr); void subTaskSucceeded(Task::Ptr);
void subTaskFailed(Task::Ptr, const QString& msg); virtual void subTaskFailed(Task::Ptr, const QString& msg);
void subTaskFinished(Task::Ptr, TaskStepState);
void subTaskStatus(Task::Ptr task, const QString& msg); void subTaskStatus(Task::Ptr task, const QString& msg);
void subTaskDetails(Task::Ptr task, const QString& msg); void subTaskDetails(Task::Ptr task, const QString& msg);
void subTaskProgress(Task::Ptr task, qint64 current, qint64 total); void subTaskProgress(Task::Ptr task, qint64 current, qint64 total);
@ -90,6 +91,8 @@ class ConcurrentTask : public Task {
virtual void updateState(); virtual void updateState();
void startSubTask(Task::Ptr task);
protected: protected:
QString m_name; QString m_name;
QString m_step_status; QString m_step_status;
@ -107,6 +110,4 @@ class ConcurrentTask : public Task {
qint64 m_stepProgress = 0; qint64 m_stepProgress = 0;
qint64 m_stepTotalProgress = 100; qint64 m_stepTotalProgress = 100;
bool m_aborted = false;
}; };

View File

@ -36,9 +36,9 @@
#include <QDebug> #include <QDebug>
MultipleOptionsTask::MultipleOptionsTask(QObject* parent, const QString& task_name) : SequentialTask(parent, task_name) {} MultipleOptionsTask::MultipleOptionsTask(QObject* parent, const QString& task_name) : ConcurrentTask(parent, task_name, 1) {}
void MultipleOptionsTask::startNext() void MultipleOptionsTask::executeNextSubTask()
{ {
if (m_done.size() != m_failed.size()) { if (m_done.size() != m_failed.size()) {
emitSucceeded(); emitSucceeded();
@ -51,7 +51,7 @@ void MultipleOptionsTask::startNext()
return; return;
} }
ConcurrentTask::startNext(); ConcurrentTask::executeNextSubTask();
} }
void MultipleOptionsTask::updateState() void MultipleOptionsTask::updateState()

View File

@ -34,18 +34,18 @@
*/ */
#pragma once #pragma once
#include "SequentialTask.h" #include "ConcurrentTask.h"
/* This task type will attempt to do run each of it's subtasks in sequence, /* This task type will attempt to do run each of it's subtasks in sequence,
* until one of them succeeds. When that happens, the remaining tasks will not run. * until one of them succeeds. When that happens, the remaining tasks will not run.
* */ * */
class MultipleOptionsTask : public SequentialTask { class MultipleOptionsTask : public ConcurrentTask {
Q_OBJECT Q_OBJECT
public: public:
explicit MultipleOptionsTask(QObject* parent = nullptr, const QString& task_name = ""); explicit MultipleOptionsTask(QObject* parent = nullptr, const QString& task_name = "");
~MultipleOptionsTask() override = default; ~MultipleOptionsTask() override = default;
private slots: private slots:
void startNext() override; void executeNextSubTask() override;
void updateState() override; void updateState() override;
}; };

View File

@ -36,18 +36,15 @@
#include "SequentialTask.h" #include "SequentialTask.h"
#include <QDebug> #include <QDebug>
#include "tasks/ConcurrentTask.h"
SequentialTask::SequentialTask(QObject* parent, QString task_name) : ConcurrentTask(parent, task_name, 1) {} SequentialTask::SequentialTask(QObject* parent, QString task_name) : ConcurrentTask(parent, task_name, 1) {}
void SequentialTask::startNext() void SequentialTask::subTaskFailed(Task::Ptr task, const QString& msg)
{ {
if (m_failed.size() > 0) { emitFailed(msg);
emitFailed(tr("One of the tasks failed!")); qWarning() << msg;
qWarning() << m_failed.constBegin()->get()->failReason(); ConcurrentTask::subTaskFailed(task, msg);
return;
}
ConcurrentTask::startNext();
} }
void SequentialTask::updateState() void SequentialTask::updateState()

View File

@ -50,7 +50,9 @@ class SequentialTask : public ConcurrentTask {
explicit SequentialTask(QObject* parent = nullptr, QString task_name = ""); explicit SequentialTask(QObject* parent = nullptr, QString task_name = "");
~SequentialTask() override = default; ~SequentialTask() override = default;
protected slots:
virtual void subTaskFailed(Task::Ptr, const QString& msg) override;
protected: protected:
void startNext() override;
void updateState() override; void updateState() override;
}; };

View File

@ -186,6 +186,7 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi
ui->instanceToolBar->addContextMenuAction(ui->newsToolBar->toggleViewAction()); ui->instanceToolBar->addContextMenuAction(ui->newsToolBar->toggleViewAction());
ui->instanceToolBar->addContextMenuAction(ui->instanceToolBar->toggleViewAction()); ui->instanceToolBar->addContextMenuAction(ui->instanceToolBar->toggleViewAction());
ui->instanceToolBar->addContextMenuAction(ui->actionToggleStatusBar);
ui->instanceToolBar->addContextMenuAction(ui->actionLockToolbars); ui->instanceToolBar->addContextMenuAction(ui->actionLockToolbars);
} }
@ -319,6 +320,14 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi
setCatBackground(cat_enable); setCatBackground(cat_enable);
} }
// Togglable status bar
{
bool statusBarVisible = APPLICATION->settings()->get("StatusBarVisible").toBool();
ui->actionToggleStatusBar->setChecked(statusBarVisible);
connect(ui->actionToggleStatusBar, &QAction::toggled, this, &MainWindow::setStatusBarVisibility);
setStatusBarVisibility(statusBarVisible);
}
// Lock toolbars // Lock toolbars
{ {
bool toolbarsLocked = APPLICATION->settings()->get("ToolbarsLocked").toBool(); bool toolbarsLocked = APPLICATION->settings()->get("ToolbarsLocked").toBool();
@ -451,10 +460,16 @@ QMenu* MainWindow::createPopupMenu()
QMenu* filteredMenu = QMainWindow::createPopupMenu(); QMenu* filteredMenu = QMainWindow::createPopupMenu();
filteredMenu->removeAction(ui->mainToolBar->toggleViewAction()); filteredMenu->removeAction(ui->mainToolBar->toggleViewAction());
filteredMenu->addAction(ui->actionToggleStatusBar);
filteredMenu->addAction(ui->actionLockToolbars); filteredMenu->addAction(ui->actionLockToolbars);
return filteredMenu; return filteredMenu;
} }
void MainWindow::setStatusBarVisibility(bool state)
{
statusBar()->setVisible(state);
APPLICATION->settings()->set("StatusBarVisible", state);
}
void MainWindow::lockToolbars(bool state) void MainWindow::lockToolbars(bool state)
{ {
ui->mainToolBar->setMovable(!state); ui->mainToolBar->setMovable(!state);

View File

@ -205,6 +205,8 @@ class MainWindow : public QMainWindow {
void globalSettingsClosed(); void globalSettingsClosed();
void setStatusBarVisibility(bool);
void lockToolbars(bool); void lockToolbars(bool);
#ifndef Q_OS_MAC #ifndef Q_OS_MAC

View File

@ -176,6 +176,7 @@
<addaction name="actionChangeTheme"/> <addaction name="actionChangeTheme"/>
<addaction name="separator"/> <addaction name="separator"/>
<addaction name="actionCAT"/> <addaction name="actionCAT"/>
<addaction name="actionToggleStatusBar"/>
<addaction name="actionLockToolbars"/> <addaction name="actionLockToolbars"/>
<addaction name="separator"/> <addaction name="separator"/>
</widget> </widget>
@ -257,6 +258,14 @@
<string>It's a fluffy kitty :3</string> <string>It's a fluffy kitty :3</string>
</property> </property>
</action> </action>
<action name="actionToggleStatusBar">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Status Bar</string>
</property>
</action>
<action name="actionLockToolbars"> <action name="actionLockToolbars">
<property name="checkable"> <property name="checkable">
<bool>true</bool> <bool>true</bool>

View File

@ -15,7 +15,6 @@
#pragma once #pragma once
#include <net/NetJob.h>
#include <QDialog> #include <QDialog>
namespace Ui { namespace Ui {
@ -31,7 +30,4 @@ class AboutDialog : public QDialog {
private: private:
Ui::AboutDialog* ui; Ui::AboutDialog* ui;
NetJob::Ptr netJob;
QByteArray dataSink;
}; };

View File

@ -328,6 +328,8 @@ auto ModUpdateDialog::ensureMetadata() -> bool
connect(modrinth_task.get(), &EnsureMetadataTask::metadataFailed, [this, &should_try_others](Mod* candidate) { connect(modrinth_task.get(), &EnsureMetadataTask::metadataFailed, [this, &should_try_others](Mod* candidate) {
onMetadataFailed(candidate, should_try_others.find(candidate->internal_id()).value(), ModPlatform::ResourceProvider::MODRINTH); onMetadataFailed(candidate, should_try_others.find(candidate->internal_id()).value(), ModPlatform::ResourceProvider::MODRINTH);
}); });
connect(modrinth_task.get(), &EnsureMetadataTask::failed,
[this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); });
if (modrinth_task->getHashingTask()) if (modrinth_task->getHashingTask())
seq.addTask(modrinth_task->getHashingTask()); seq.addTask(modrinth_task->getHashingTask());
@ -341,6 +343,8 @@ auto ModUpdateDialog::ensureMetadata() -> bool
connect(flame_task.get(), &EnsureMetadataTask::metadataFailed, [this, &should_try_others](Mod* candidate) { connect(flame_task.get(), &EnsureMetadataTask::metadataFailed, [this, &should_try_others](Mod* candidate) {
onMetadataFailed(candidate, should_try_others.find(candidate->internal_id()).value(), ModPlatform::ResourceProvider::FLAME); onMetadataFailed(candidate, should_try_others.find(candidate->internal_id()).value(), ModPlatform::ResourceProvider::FLAME);
}); });
connect(flame_task.get(), &EnsureMetadataTask::failed,
[this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); });
if (flame_task->getHashingTask()) if (flame_task->getHashingTask())
seq.addTask(flame_task->getHashingTask()); seq.addTask(flame_task->getHashingTask());
@ -394,6 +398,8 @@ void ModUpdateDialog::onMetadataFailed(Mod* mod, bool try_others, ModPlatform::R
auto task = makeShared<EnsureMetadataTask>(mod, index_dir, next(first_choice)); auto task = makeShared<EnsureMetadataTask>(mod, index_dir, next(first_choice));
connect(task.get(), &EnsureMetadataTask::metadataReady, [this](Mod* candidate) { onMetadataEnsured(candidate); }); connect(task.get(), &EnsureMetadataTask::metadataReady, [this](Mod* candidate) { onMetadataEnsured(candidate); });
connect(task.get(), &EnsureMetadataTask::metadataFailed, [this](Mod* candidate) { onMetadataFailed(candidate, false); }); connect(task.get(), &EnsureMetadataTask::metadataFailed, [this](Mod* candidate) { onMetadataFailed(candidate, false); });
connect(task.get(), &EnsureMetadataTask::failed,
[this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); });
m_second_try_metadata->addTask(task); m_second_try_metadata->addTask(task);
} else { } else {

View File

@ -158,13 +158,14 @@ void VisualGroup::drawHeader(QPainter* painter, const QStyleOptionViewItem& opti
painter->setRenderHint(QPainter::Antialiasing); painter->setRenderHint(QPainter::Antialiasing);
// sizes and offsets, to keep things consistent below // sizes and offsets, to keep things consistent below
int arrowOffsetLeft = fontMetrics.height() / 2 + 7; const int arrowOffsetLeft = fontMetrics.height() / 2 + 7;
int textOffsetLeft = arrowOffsetLeft * 2; const int textOffsetLeft = arrowOffsetLeft * 2;
int arrowSize = 6; const int centerHeight = optRect.top() + fontMetrics.height() / 2;
int centerHeight = optRect.top() + fontMetrics.height() / 2; const QString& textToDraw = text.isEmpty() ? QObject::tr("Ungrouped") : text;
// BEGIN: arrow // BEGIN: arrow
{ {
constexpr int arrowSize = 6;
QPolygon arrowPolygon; QPolygon arrowPolygon;
if (collapsed) { if (collapsed) {
arrowPolygon << QPoint(arrowOffsetLeft - arrowSize / 2, centerHeight - arrowSize) arrowPolygon << QPoint(arrowOffsetLeft - arrowSize / 2, centerHeight - arrowSize)
@ -188,9 +189,26 @@ void VisualGroup::drawHeader(QPainter* painter, const QStyleOptionViewItem& opti
textRect.setHeight(fontMetrics.height()); textRect.setHeight(fontMetrics.height());
textRect.setRight(textRect.right() - 7); textRect.setRight(textRect.right() - 7);
painter->drawText(textRect, Qt::AlignLeft | Qt::AlignVCenter, !text.isEmpty() ? text : QObject::tr("Ungrouped")); painter->drawText(textRect, Qt::AlignLeft | Qt::AlignVCenter, textToDraw);
} }
// END: text // END: text
// BEGIN: horizontal line
{
penColor.setAlphaF(0.05);
pen.setColor(penColor);
painter->setPen(pen);
// startPoint is left + arrow + text + space
const int startPoint =
optRect.left() + fontMetrics.height() + fontMetrics.size(Qt::AlignLeft | Qt::AlignVCenter, textToDraw).width() + 20;
painter->setRenderHint(QPainter::Antialiasing, false);
QPolygon polygon;
// for some reason the height (yPos) doesn't look centered, so we are adding 1 to the center height
const int lineHeight = centerHeight + 1;
polygon << QPoint(startPoint, lineHeight) << QPoint(optRect.right() - 3, lineHeight);
painter->drawPolyline(polygon);
}
// END: horizontal line
} }
int VisualGroup::totalHeight() const int VisualGroup::totalHeight() const

View File

@ -131,6 +131,22 @@ ManagedPackPage::~ManagedPackPage()
void ManagedPackPage::openedImpl() void ManagedPackPage::openedImpl()
{ {
if (m_inst->getManagedPackID().isEmpty()) {
ui->packVersion->hide();
ui->packVersionLabel->hide();
ui->packOrigin->hide();
ui->packOriginLabel->hide();
ui->versionsComboBox->hide();
ui->updateButton->hide();
ui->updateToVersionLabel->hide();
ui->updateFromFileButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
ui->packName->setText(m_inst->name());
ui->changelogTextBrowser->setText(tr("This is a local modpack.\n"
"This can be updated only using a file in %1 format\n")
.arg(displayName()));
return;
}
ui->packName->setText(m_inst->getManagedPackName()); ui->packName->setText(m_inst->getManagedPackName());
ui->packVersion->setText(m_inst->getManagedPackVersionName()); ui->packVersion->setText(m_inst->getManagedPackVersionName());
ui->packOrigin->setText(tr("Website: <a href=%1>%2</a> | Pack ID: %3 | Version ID: %4") ui->packOrigin->setText(tr("Website: <a href=%1>%2</a> | Pack ID: %3 | Version ID: %4")
@ -355,6 +371,8 @@ void ModrinthManagedPackPage::update()
void ModrinthManagedPackPage::updateFromFile() void ModrinthManagedPackPage::updateFromFile()
{ {
auto output = QFileDialog::getOpenFileUrl(this, tr("Choose update file"), QDir::homePath(), "Modrinth pack (*.mrpack *.zip)"); auto output = QFileDialog::getOpenFileUrl(this, tr("Choose update file"), QDir::homePath(), "Modrinth pack (*.mrpack *.zip)");
if (output.isEmpty())
return;
QMap<QString, QString> extra_info; QMap<QString, QString> extra_info;
extra_info.insert("pack_id", m_inst->getManagedPackID()); extra_info.insert("pack_id", m_inst->getManagedPackID());
extra_info.insert("pack_version_id", QString()); extra_info.insert("pack_version_id", QString());
@ -472,7 +490,7 @@ void FlameManagedPackPage::parseManagedPack()
QString FlameManagedPackPage::url() const QString FlameManagedPackPage::url() const
{ {
// FIXME: We should display the websiteUrl field, but this requires doing the API request first :( // FIXME: We should display the websiteUrl field, but this requires doing the API request first :(
return {}; return "https://www.curseforge.com/projects/" + m_inst->getManagedPackID();
} }
void FlameManagedPackPage::suggestVersion() void FlameManagedPackPage::suggestVersion()
@ -519,6 +537,8 @@ void FlameManagedPackPage::update()
void FlameManagedPackPage::updateFromFile() void FlameManagedPackPage::updateFromFile()
{ {
auto output = QFileDialog::getOpenFileUrl(this, tr("Choose update file"), QDir::homePath(), "CurseForge pack (*.zip)"); auto output = QFileDialog::getOpenFileUrl(this, tr("Choose update file"), QDir::homePath(), "CurseForge pack (*.zip)");
if (output.isEmpty())
return;
QMap<QString, QString> extra_info; QMap<QString, QString> extra_info;
extra_info.insert("pack_id", m_inst->getManagedPackID()); extra_info.insert("pack_id", m_inst->getManagedPackID());

View File

@ -295,13 +295,6 @@ void VersionPage::on_actionRemove_triggered()
m_container->refreshContainer(); m_container->refreshContainer();
} }
void VersionPage::on_actionInstall_mods_triggered()
{
if (m_container) {
m_container->selectPage("mods");
}
}
void VersionPage::on_actionAdd_to_Minecraft_jar_triggered() void VersionPage::on_actionAdd_to_Minecraft_jar_triggered()
{ {
auto list = GuiUtil::BrowseForFiles("jarmod", tr("Select jar mods"), tr("Minecraft.jar mods (*.zip *.jar)"), auto list = GuiUtil::BrowseForFiles("jarmod", tr("Select jar mods"), tr("Minecraft.jar mods (*.zip *.jar)"),

View File

@ -80,7 +80,6 @@ class VersionPage : public QMainWindow, public BasePage {
void on_actionAdd_Agents_triggered(); void on_actionAdd_Agents_triggered();
void on_actionRevert_triggered(); void on_actionRevert_triggered();
void on_actionEdit_triggered(); void on_actionEdit_triggered();
void on_actionInstall_mods_triggered();
void on_actionCustomize_triggered(); void on_actionCustomize_triggered();
void on_actionDownload_All_triggered(); void on_actionDownload_All_triggered();

View File

@ -207,6 +207,10 @@ void ResourceModel::loadEntry(QModelIndex& entry)
return; return;
versionRequestSucceeded(doc, pack, entry); versionRequestSucceeded(doc, pack, entry);
}; };
if (!callbacks.on_fail)
callbacks.on_fail = [](QString reason, int) {
QMessageBox::critical(nullptr, tr("Error"), tr("A network error occurred. Could not load project versions:%1").arg(reason));
};
if (auto job = m_api->getProjectVersions(std::move(args), std::move(callbacks)); job) if (auto job = m_api->getProjectVersions(std::move(args), std::move(callbacks)); job)
runInfoJob(job); runInfoJob(job);
@ -230,6 +234,12 @@ void ResourceModel::loadEntry(QModelIndex& entry)
return; return;
QMessageBox::critical(nullptr, tr("Error"), tr("A network error occurred. Could not load project info:%1").arg(reason)); QMessageBox::critical(nullptr, tr("Error"), tr("A network error occurred. Could not load project info:%1").arg(reason));
}; };
if (!callbacks.on_abort)
callbacks.on_abort = [this] {
if (!s_running_models.constFind(this).value())
return;
qCritical() << tr("The request was abborted for an unknown reason");
};
if (auto job = m_api->getProjectInfo(std::move(args), std::move(callbacks)); job) if (auto job = m_api->getProjectInfo(std::move(args), std::move(callbacks)); job)
runInfoJob(job); runInfoJob(job);

View File

@ -170,6 +170,10 @@ void ListModel::performPaginatedSearch()
callbacks.on_fail = [this](QString reason) { searchRequestFailed(reason); }; callbacks.on_fail = [this](QString reason) { searchRequestFailed(reason); };
callbacks.on_succeed = [this](auto& doc, auto& pack) { searchRequestForOneSucceeded(doc); }; callbacks.on_succeed = [this](auto& doc, auto& pack) { searchRequestForOneSucceeded(doc); };
callbacks.on_abort = [this] {
qCritical() << "Search task aborted by an unknown reason!";
searchRequestFailed("Abborted");
};
static const FlameAPI api; static const FlameAPI api;
if (auto job = api.getProjectInfo({ projectId }, std::move(callbacks)); job) { if (auto job = api.getProjectInfo({ projectId }, std::move(callbacks)); job) {
jobPtr = job; jobPtr = job;

View File

@ -34,6 +34,7 @@
*/ */
#include "FlamePage.h" #include "FlamePage.h"
#include "ui/dialogs/CustomMessageBox.h"
#include "ui_FlamePage.h" #include "ui_FlamePage.h"
#include <QKeyEvent> #include <QKeyEvent>
@ -193,6 +194,8 @@ void FlamePage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelInde
suggestCurrent(); suggestCurrent();
}); });
QObject::connect(netJob, &NetJob::finished, this, [response, netJob] { netJob->deleteLater(); }); QObject::connect(netJob, &NetJob::finished, this, [response, netJob] { netJob->deleteLater(); });
connect(netJob, &NetJob::failed,
[this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); });
netJob->start(); netJob->start();
} else { } else {
for (auto version : current.versions) { for (auto version : current.versions) {

View File

@ -140,6 +140,10 @@ void ModpackListModel::performPaginatedSearch()
callbacks.on_fail = [this](QString reason) { searchRequestFailed(reason); }; callbacks.on_fail = [this](QString reason) { searchRequestFailed(reason); };
callbacks.on_succeed = [this](auto& doc, auto& pack) { searchRequestForOneSucceeded(doc); }; callbacks.on_succeed = [this](auto& doc, auto& pack) { searchRequestForOneSucceeded(doc); };
callbacks.on_abort = [this] {
qCritical() << "Search task aborted by an unknown reason!";
searchRequestFailed("Abborted");
};
static const ModrinthAPI api; static const ModrinthAPI api;
if (auto job = api.getProjectInfo({ projectId }, std::move(callbacks)); job) { if (auto job = api.getProjectInfo({ projectId }, std::move(callbacks)); job) {
jobPtr = job; jobPtr = job;

View File

@ -35,6 +35,7 @@
*/ */
#include "ModrinthPage.h" #include "ModrinthPage.h"
#include "ui/dialogs/CustomMessageBox.h"
#include "ui_ModrinthPage.h" #include "ui_ModrinthPage.h"
#include "ModrinthModel.h" #include "ModrinthModel.h"
@ -182,6 +183,8 @@ void ModrinthPage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelI
suggestCurrent(); suggestCurrent();
}); });
QObject::connect(netJob, &NetJob::finished, this, [response, netJob] { netJob->deleteLater(); }); QObject::connect(netJob, &NetJob::finished, this, [response, netJob] { netJob->deleteLater(); });
connect(netJob, &NetJob::failed,
[this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); });
netJob->start(); netJob->start();
} else } else
updateUI(); updateUI();
@ -235,6 +238,8 @@ void ModrinthPage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelI
suggestCurrent(); suggestCurrent();
}); });
QObject::connect(netJob, &NetJob::finished, this, [response, netJob] { netJob->deleteLater(); }); QObject::connect(netJob, &NetJob::finished, this, [response, netJob] { netJob->deleteLater(); });
connect(netJob, &NetJob::failed,
[this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); });
netJob->start(); netJob->start();
} else { } else {

View File

@ -34,6 +34,7 @@
*/ */
#include "TechnicPage.h" #include "TechnicPage.h"
#include "ui/dialogs/CustomMessageBox.h"
#include "ui/widgets/ProjectItem.h" #include "ui/widgets/ProjectItem.h"
#include "ui_TechnicPage.h" #include "ui_TechnicPage.h"
@ -208,6 +209,8 @@ void TechnicPage::suggestCurrent()
metadataLoaded(); metadataLoaded();
}); });
connect(jobPtr.get(), &NetJob::failed,
[this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); });
jobPtr = netJob; jobPtr = netJob;
jobPtr->start(); jobPtr->start();
@ -258,6 +261,8 @@ void TechnicPage::metadataLoaded()
netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(url), response)); netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(url), response));
QObject::connect(netJob.get(), &NetJob::succeeded, this, &TechnicPage::onSolderLoaded); QObject::connect(netJob.get(), &NetJob::succeeded, this, &TechnicPage::onSolderLoaded);
connect(jobPtr.get(), &NetJob::failed,
[this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); });
jobPtr = netJob; jobPtr = netJob;
jobPtr->start(); jobPtr->start();

View File

@ -102,14 +102,7 @@ void VariableSizedImageObject::loadImage(QTextDocument* doc, const QUrl& source,
auto full_entry_path = entry->getFullPath(); auto full_entry_path = entry->getFullPath();
auto source_url = source; auto source_url = source;
connect(job, &NetJob::succeeded, this, [this, doc, full_entry_path, source_url, posInDocument] { auto loadImage = [this, doc, full_entry_path, source_url, posInDocument](const QImage& image) {
qDebug() << "Loaded resource at" << full_entry_path;
// If we flushed, don't proceed.
if (!m_fetching_images.contains(source_url))
return;
QImage image(full_entry_path);
doc->addResource(QTextDocument::ImageResource, source_url, image); doc->addResource(QTextDocument::ImageResource, source_url, image);
parseImage(doc, image, posInDocument); parseImage(doc, image, posInDocument);
@ -121,6 +114,23 @@ void VariableSizedImageObject::loadImage(QTextDocument* doc, const QUrl& source,
doc->setPageSize(size); doc->setPageSize(size);
m_fetching_images.remove(source_url); m_fetching_images.remove(source_url);
};
connect(job, &NetJob::succeeded, this, [this, full_entry_path, source_url, loadImage] {
qDebug() << "Loaded resource at:" << full_entry_path;
// If we flushed, don't proceed.
if (!m_fetching_images.contains(source_url))
return;
QImage image(full_entry_path);
loadImage(image);
});
connect(job, &NetJob::failed, this, [this, full_entry_path, source_url, loadImage](QString reason) {
qWarning() << "Failed resource at:" << full_entry_path << " because:" << reason;
// If we flushed, don't proceed.
if (!m_fetching_images.contains(source_url))
return;
loadImage(QImage());
}); });
connect(job, &NetJob::finished, job, &NetJob::deleteLater); connect(job, &NetJob::finished, job, &NetJob::deleteLater);

View File

@ -37,13 +37,13 @@ class BasicTask_MultiStep : public Task {
class BigConcurrentTask : public ConcurrentTask { class BigConcurrentTask : public ConcurrentTask {
Q_OBJECT Q_OBJECT
void startNext() override void executeNextSubTask() override
{ {
// This is here only to help fill the stack a bit more quickly (if there's an issue, of course :^)) // This is here only to help fill the stack a bit more quickly (if there's an issue, of course :^))
// Each tasks thus adds 1024 * 4 bytes to the stack, at the very least. // Each tasks thus adds 1024 * 4 bytes to the stack, at the very least.
[[maybe_unused]] volatile std::array<uint32_t, 1024> some_data_on_the_stack{}; [[maybe_unused]] volatile std::array<uint32_t, 1024> some_data_on_the_stack{};
ConcurrentTask::startNext(); ConcurrentTask::executeNextSubTask();
} }
}; };
@ -71,11 +71,14 @@ class BigConcurrentTaskThread : public QThread {
quit(); quit();
}); });
m_deadline.start(); if (thread() != QThread::currentThread()) {
QMetaObject::invokeMethod(this, &BigConcurrentTaskThread::start_timer, Qt::QueuedConnection);
}
big_task.run(); big_task.run();
exec(); exec();
} }
void start_timer() { m_deadline.start(); }
public: public:
bool passed_the_deadline = false; bool passed_the_deadline = false;