Merge branch 'develop' of https://github.com/PrismLauncher/PrismLauncher into clean_net

Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
This commit is contained in:
Trial97 2024-07-22 22:06:09 +03:00
commit 63a380a039
No known key found for this signature in database
GPG Key ID: 55EF5DA53DB36318
46 changed files with 368 additions and 214 deletions

12
flake.lock generated
View File

@ -75,11 +75,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1720031269, "lastModified": 1720768451,
"narHash": "sha256-rwz8NJZV+387rnWpTYcXaRNvzUSnnF9aHONoJIYmiUQ=", "narHash": "sha256-EYekUHJE2gxeo2pM/zM9Wlqw1Uw2XTJXOSAO79ksc4Y=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "9f4128e00b0ae8ec65918efeba59db998750ead6", "rev": "7e7c39ea35c5cdd002cd4588b03a3fb9ece6fad9",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -103,11 +103,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1719259945, "lastModified": 1720524665,
"narHash": "sha256-F1h+XIsGKT9TkGO3omxDLEb/9jOOsI6NnzsXFsZhry4=", "narHash": "sha256-ni/87oHPZm6Gv0ECYxr1f6uxB0UKBWJ6HvS7lwLU6oY=",
"owner": "cachix", "owner": "cachix",
"repo": "pre-commit-hooks.nix", "repo": "pre-commit-hooks.nix",
"rev": "0ff4381bbb8f7a52ca4a851660fc7a437a4c6e07", "rev": "8d6a17d0cdf411c55f12602624df6368ad86fac1",
"type": "github" "type": "github"
}, },
"original": { "original": {

View File

@ -559,6 +559,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
m_settings->registerSetting("NumberOfConcurrentTasks", 10); m_settings->registerSetting("NumberOfConcurrentTasks", 10);
m_settings->registerSetting("NumberOfConcurrentDownloads", 6); m_settings->registerSetting("NumberOfConcurrentDownloads", 6);
m_settings->registerSetting("NumberOfManualRetries", 1);
m_settings->registerSetting("RequestTimeout", 60); m_settings->registerSetting("RequestTimeout", 60);
QString defaultMonospace; QString defaultMonospace;
@ -817,7 +818,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
m_icons.reset(new IconList(instFolders, setting->get().toString())); m_icons.reset(new IconList(instFolders, setting->get().toString()));
connect(setting.get(), &Setting::SettingChanged, connect(setting.get(), &Setting::SettingChanged,
[&](const Setting&, QVariant value) { m_icons->directoryChanged(value.toString()); }); [&](const Setting&, QVariant value) { m_icons->directoryChanged(value.toString()); });
qDebug() << "<> Instance icons intialized."; qDebug() << "<> Instance icons initialized.";
} }
// Themes // Themes

View File

@ -394,7 +394,7 @@ QList<QString> JavaUtils::FindJavaPaths()
return javas; return javas;
} }
#elif defined(Q_OS_LINUX) #elif defined(Q_OS_LINUX) || defined(Q_OS_OPENBSD) || defined(Q_OS_FREEBSD)
QList<QString> JavaUtils::FindJavaPaths() QList<QString> JavaUtils::FindJavaPaths()
{ {
QList<QString> javas; QList<QString> javas;
@ -419,6 +419,7 @@ QList<QString> JavaUtils::FindJavaPaths()
scanJavaDir(snap + dirPath); scanJavaDir(snap + dirPath);
} }
}; };
#if defined(Q_OS_LINUX)
// oracle RPMs // oracle RPMs
scanJavaDirs("/usr/java"); scanJavaDirs("/usr/java");
// general locations used by distro packaging // general locations used by distro packaging
@ -437,7 +438,10 @@ QList<QString> JavaUtils::FindJavaPaths()
scanJavaDirs("/opt/ibm"); // IBM Semeru Certified Edition scanJavaDirs("/opt/ibm"); // IBM Semeru Certified Edition
// flatpak // flatpak
scanJavaDirs("/app/jdk"); scanJavaDirs("/app/jdk");
#elif defined(Q_OS_OPENBSD) || defined(Q_OS_FREEBSD)
// ports install to /usr/local on OpenBSD & FreeBSD
scanJavaDirs("/usr/local");
#endif
auto home = qEnvironmentVariable("HOME"); auto home = qEnvironmentVariable("HOME");
// javas downloaded by IntelliJ // javas downloaded by IntelliJ

View File

@ -30,13 +30,21 @@ class ParsingValidator : public Net::Validator {
virtual ~ParsingValidator() {}; virtual ~ParsingValidator() {};
public: /* methods */ public: /* methods */
bool init(QNetworkRequest&) override { return true; } bool init(QNetworkRequest&) override
{
m_data.clear();
return true;
}
bool write(QByteArray& data) override bool write(QByteArray& data) override
{ {
this->m_data.append(data); this->m_data.append(data);
return true; return true;
} }
bool abort() override { return true; } bool abort() override
{
m_data.clear();
return true;
}
bool validate(QNetworkReply&) override bool validate(QNetworkReply&) override
{ {
auto fname = m_entity->localFilename(); auto fname = m_entity->localFilename();

View File

@ -58,7 +58,6 @@
#include "ComponentUpdateTask.h" #include "ComponentUpdateTask.h"
#include "PackProfile.h" #include "PackProfile.h"
#include "PackProfile_p.h" #include "PackProfile_p.h"
#include "minecraft/mod/Mod.h"
#include "modplatform/ModIndex.h" #include "modplatform/ModIndex.h"
static const QMap<QString, ModPlatform::ModLoaderType> modloaderMapping{ { "net.neoforged", ModPlatform::NeoForge }, static const QMap<QString, ModPlatform::ModLoaderType> modloaderMapping{ { "net.neoforged", ModPlatform::NeoForge },
@ -181,7 +180,7 @@ static bool loadPackProfile(PackProfile* parent,
} }
if (!componentsFile.open(QFile::ReadOnly)) { if (!componentsFile.open(QFile::ReadOnly)) {
qCritical() << "Couldn't open" << componentsFile.fileName() << " for reading:" << componentsFile.errorString(); qCritical() << "Couldn't open" << componentsFile.fileName() << " for reading:" << componentsFile.errorString();
qWarning() << "Ignoring overriden order"; qWarning() << "Ignoring overridden order";
return false; return false;
} }
@ -190,7 +189,7 @@ static bool loadPackProfile(PackProfile* parent,
QJsonDocument doc = QJsonDocument::fromJson(componentsFile.readAll(), &error); QJsonDocument doc = QJsonDocument::fromJson(componentsFile.readAll(), &error);
if (error.error != QJsonParseError::NoError) { if (error.error != QJsonParseError::NoError) {
qCritical() << "Couldn't parse" << componentsFile.fileName() << ":" << error.errorString(); qCritical() << "Couldn't parse" << componentsFile.fileName() << ":" << error.errorString();
qWarning() << "Ignoring overriden order"; qWarning() << "Ignoring overridden order";
return false; return false;
} }
@ -1022,3 +1021,23 @@ std::optional<ModPlatform::ModLoaderTypes> PackProfile::getSupportedModLoaders()
loaders |= ModPlatform::Forge; loaders |= ModPlatform::Forge;
return loaders; return loaders;
} }
QList<ModPlatform::ModLoaderType> PackProfile::getModLoadersList()
{
QList<ModPlatform::ModLoaderType> result;
for (auto c : d->components) {
if (c->isEnabled() && modloaderMapping.contains(c->getID())) {
result.append(modloaderMapping[c->getID()]);
}
}
// TODO: remove this or add version condition once Quilt drops official Fabric support
if (result.contains(ModPlatform::Quilt) && !result.contains(ModPlatform::Fabric)) {
result.append(ModPlatform::Fabric);
}
if (getComponentVersion("net.minecraft") == "1.20.1" && result.contains(ModPlatform::NeoForge) &&
!result.contains(ModPlatform::Forge)) {
result.append(ModPlatform::Forge);
}
return result;
}

View File

@ -146,6 +146,7 @@ class PackProfile : public QAbstractListModel {
std::optional<ModPlatform::ModLoaderTypes> getModLoaders(); std::optional<ModPlatform::ModLoaderTypes> getModLoaders();
// this returns aditional loaders(Quilt supports fabric and NeoForge supports Forge) // this returns aditional loaders(Quilt supports fabric and NeoForge supports Forge)
std::optional<ModPlatform::ModLoaderTypes> getSupportedModLoaders(); std::optional<ModPlatform::ModLoaderTypes> getSupportedModLoaders();
QList<ModPlatform::ModLoaderType> getModLoadersList();
private: private:
void scheduleSave(); void scheduleSave();

View File

@ -57,7 +57,7 @@ bool readOverrideOrders(QString path, PatchOrder& order)
} }
if (!orderFile.open(QFile::ReadOnly)) { if (!orderFile.open(QFile::ReadOnly)) {
qCritical() << "Couldn't open" << orderFile.fileName() << " for reading:" << orderFile.errorString(); qCritical() << "Couldn't open" << orderFile.fileName() << " for reading:" << orderFile.errorString();
qWarning() << "Ignoring overriden order"; qWarning() << "Ignoring overridden order";
return false; return false;
} }
@ -66,7 +66,7 @@ bool readOverrideOrders(QString path, PatchOrder& order)
QJsonDocument doc = QJsonDocument::fromJson(orderFile.readAll(), &error); QJsonDocument doc = QJsonDocument::fromJson(orderFile.readAll(), &error);
if (error.error != QJsonParseError::NoError) { if (error.error != QJsonParseError::NoError) {
qCritical() << "Couldn't parse" << orderFile.fileName() << ":" << error.errorString(); qCritical() << "Couldn't parse" << orderFile.fileName() << ":" << error.errorString();
qWarning() << "Ignoring overriden order"; qWarning() << "Ignoring overridden order";
return false; return false;
} }
@ -84,7 +84,7 @@ bool readOverrideOrders(QString path, PatchOrder& order)
} }
} catch ([[maybe_unused]] const JSONValidationError& err) { } catch ([[maybe_unused]] const JSONValidationError& err) {
qCritical() << "Couldn't parse" << orderFile.fileName() << ": bad file format"; qCritical() << "Couldn't parse" << orderFile.fileName() << ": bad file format";
qWarning() << "Ignoring overriden order"; qWarning() << "Ignoring overridden order";
order.clear(); order.clear();
return false; return false;
} }

View File

@ -29,7 +29,6 @@
#include "modplatform/ResourceAPI.h" #include "modplatform/ResourceAPI.h"
#include "modplatform/flame/FlameAPI.h" #include "modplatform/flame/FlameAPI.h"
#include "modplatform/modrinth/ModrinthAPI.h" #include "modplatform/modrinth/ModrinthAPI.h"
#include "tasks/ConcurrentTask.h"
#include "tasks/SequentialTask.h" #include "tasks/SequentialTask.h"
#include "ui/pages/modplatform/ModModel.h" #include "ui/pages/modplatform/ModModel.h"
#include "ui/pages/modplatform/flame/FlameResourceModels.h" #include "ui/pages/modplatform/flame/FlameResourceModels.h"

View File

@ -3,7 +3,6 @@
#include "minecraft/mod/Mod.h" #include "minecraft/mod/Mod.h"
#include "minecraft/mod/tasks/GetModDependenciesTask.h" #include "minecraft/mod/tasks/GetModDependenciesTask.h"
#include "modplatform/ModIndex.h" #include "modplatform/ModIndex.h"
#include "modplatform/ResourceAPI.h"
#include "tasks/Task.h" #include "tasks/Task.h"
class ResourceDownloadTask; class ResourceDownloadTask;
@ -15,9 +14,9 @@ class CheckUpdateTask : public Task {
public: public:
CheckUpdateTask(QList<Mod*>& mods, CheckUpdateTask(QList<Mod*>& mods,
std::list<Version>& mcVersions, std::list<Version>& mcVersions,
std::optional<ModPlatform::ModLoaderTypes> loaders, QList<ModPlatform::ModLoaderType> loadersList,
std::shared_ptr<ModFolderModel> mods_folder) std::shared_ptr<ModFolderModel> mods_folder)
: Task(nullptr), m_mods(mods), m_game_versions(mcVersions), m_loaders(loaders), m_mods_folder(mods_folder) {}; : Task(nullptr), m_mods(mods), m_game_versions(mcVersions), m_loaders_list(loadersList), m_mods_folder(mods_folder) {};
struct UpdatableMod { struct UpdatableMod {
QString name; QString name;
@ -67,7 +66,7 @@ class CheckUpdateTask : public Task {
protected: protected:
QList<Mod*>& m_mods; QList<Mod*>& m_mods;
std::list<Version>& m_game_versions; std::list<Version>& m_game_versions;
std::optional<ModPlatform::ModLoaderTypes> m_loaders; QList<ModPlatform::ModLoaderType> m_loaders_list;
std::shared_ptr<ModFolderModel> m_mods_folder; std::shared_ptr<ModFolderModel> m_mods_folder;
std::vector<UpdatableMod> m_updatable; std::vector<UpdatableMod> m_updatable;

View File

@ -25,7 +25,6 @@
#include <QVariant> #include <QVariant>
#include <QVector> #include <QVector>
#include <memory> #include <memory>
#include <optional>
class QIODevice; class QIODevice;
@ -44,7 +43,7 @@ namespace ProviderCapabilities {
const char* name(ResourceProvider); const char* name(ResourceProvider);
QString readableName(ResourceProvider); QString readableName(ResourceProvider);
QStringList hashType(ResourceProvider); QStringList hashType(ResourceProvider);
}; // namespace ProviderCapabilities } // namespace ProviderCapabilities
struct ModpackAuthor { struct ModpackAuthor {
QString name; QString name;

View File

@ -74,6 +74,7 @@ void Flame::FileResolvingTask::netJobFinished()
setProgress(1, 3); setProgress(1, 3);
// job to check modrinth for blocked projects // job to check modrinth for blocked projects
m_checkJob.reset(new NetJob("Modrinth check", m_network)); m_checkJob.reset(new NetJob("Modrinth check", m_network));
m_checkJob->setAskRetry(false);
blockedProjects = QMap<File*, std::shared_ptr<QByteArray>>(); blockedProjects = QMap<File*, std::shared_ptr<QByteArray>>();
QJsonDocument doc; QJsonDocument doc;

View File

@ -4,6 +4,7 @@
#include "FlameAPI.h" #include "FlameAPI.h"
#include <memory> #include <memory>
#include <optional>
#include "FlameModIndex.h" #include "FlameModIndex.h"
#include "Application.h" #include "Application.h"
@ -12,7 +13,6 @@
#include "net/ApiDownload.h" #include "net/ApiDownload.h"
#include "net/ApiUpload.h" #include "net/ApiUpload.h"
#include "net/NetJob.h" #include "net/NetJob.h"
#include "net/Upload.h"
Task::Ptr FlameAPI::matchFingerprints(const QList<uint>& fingerprints, std::shared_ptr<QByteArray> response) Task::Ptr FlameAPI::matchFingerprints(const QList<uint>& fingerprints, std::shared_ptr<QByteArray> response)
{ {
@ -34,7 +34,7 @@ Task::Ptr FlameAPI::matchFingerprints(const QList<uint>& fingerprints, std::shar
return netJob; return netJob;
} }
auto FlameAPI::getModFileChangelog(int modId, int fileId) -> QString QString FlameAPI::getModFileChangelog(int modId, int fileId)
{ {
QEventLoop lock; QEventLoop lock;
QString changelog; QString changelog;
@ -69,7 +69,7 @@ auto FlameAPI::getModFileChangelog(int modId, int fileId) -> QString
return changelog; return changelog;
} }
auto FlameAPI::getModDescription(int modId) -> QString QString FlameAPI::getModDescription(int modId)
{ {
QEventLoop lock; QEventLoop lock;
QString description; QString description;
@ -102,7 +102,7 @@ auto FlameAPI::getModDescription(int modId) -> QString
return description; return description;
} }
auto FlameAPI::getLatestVersion(VersionSearchArgs&& args) -> ModPlatform::IndexedVersion QList<ModPlatform::IndexedVersion> FlameAPI::getLatestVersions(VersionSearchArgs&& args)
{ {
auto versions_url_optional = getVersionsURL(args); auto versions_url_optional = getVersionsURL(args);
if (!versions_url_optional.has_value()) if (!versions_url_optional.has_value())
@ -114,7 +114,7 @@ auto FlameAPI::getLatestVersion(VersionSearchArgs&& args) -> ModPlatform::Indexe
auto netJob = makeShared<NetJob>(QString("Flame::GetLatestVersion(%1)").arg(args.pack.name), APPLICATION->network()); auto netJob = makeShared<NetJob>(QString("Flame::GetLatestVersion(%1)").arg(args.pack.name), APPLICATION->network());
auto response = std::make_shared<QByteArray>(); auto response = std::make_shared<QByteArray>();
ModPlatform::IndexedVersion ver; QList<ModPlatform::IndexedVersion> ver;
netJob->addNetAction(Net::ApiDownload::makeByteArray(versions_url, response)); netJob->addNetAction(Net::ApiDownload::makeByteArray(versions_url, response));
@ -134,9 +134,7 @@ auto FlameAPI::getLatestVersion(VersionSearchArgs&& args) -> ModPlatform::Indexe
for (auto file : arr) { for (auto file : arr) {
auto file_obj = Json::requireObject(file); auto file_obj = Json::requireObject(file);
auto file_tmp = FlameMod::loadIndexedPackVersion(file_obj); ver.append(FlameMod::loadIndexedPackVersion(file_obj));
if (file_tmp.date > ver.date && (!args.loaders.has_value() || !file_tmp.loaders || args.loaders.value() & file_tmp.loaders))
ver = file_tmp;
} }
} catch (Json::JsonException& e) { } catch (Json::JsonException& e) {
@ -146,7 +144,7 @@ auto FlameAPI::getLatestVersion(VersionSearchArgs&& args) -> ModPlatform::Indexe
} }
}); });
QObject::connect(netJob.get(), &NetJob::finished, [&loop] { loop.quit(); }); QObject::connect(netJob.get(), &NetJob::finished, &loop, &QEventLoop::quit);
netJob->start(); netJob->start();
@ -260,4 +258,27 @@ QList<ModPlatform::Category> FlameAPI::loadModCategories(std::shared_ptr<QByteAr
qDebug() << doc; qDebug() << doc;
} }
return categories; return categories;
}; };
std::optional<ModPlatform::IndexedVersion> FlameAPI::getLatestVersion(QList<ModPlatform::IndexedVersion> versions,
QList<ModPlatform::ModLoaderType> instanceLoaders,
ModPlatform::ModLoaderTypes modLoaders)
{
// edge case: mod has installed for forge but the instance is fabric => fabric version will be prioritizated on update
auto bestVersion = [&versions](ModPlatform::ModLoaderTypes loader) {
std::optional<ModPlatform::IndexedVersion> ver;
for (auto file_tmp : versions) {
if (file_tmp.loaders & loader && (!ver.has_value() || file_tmp.date > ver->date)) {
ver = file_tmp;
}
}
return ver;
};
for (auto l : instanceLoaders) {
auto ver = bestVersion(l);
if (ver.has_value()) {
return ver;
}
}
return bestVersion(modLoaders);
}

View File

@ -5,7 +5,6 @@
#pragma once #pragma once
#include <QList> #include <QList>
#include <algorithm>
#include <memory> #include <memory>
#include "modplatform/ModIndex.h" #include "modplatform/ModIndex.h"
#include "modplatform/ResourceAPI.h" #include "modplatform/ResourceAPI.h"
@ -13,10 +12,13 @@
class FlameAPI : public NetworkResourceAPI { class FlameAPI : public NetworkResourceAPI {
public: public:
auto getModFileChangelog(int modId, int fileId) -> QString; QString getModFileChangelog(int modId, int fileId);
auto getModDescription(int modId) -> QString; QString getModDescription(int modId);
auto getLatestVersion(VersionSearchArgs&& args) -> ModPlatform::IndexedVersion; QList<ModPlatform::IndexedVersion> getLatestVersions(VersionSearchArgs&& args);
std::optional<ModPlatform::IndexedVersion> getLatestVersion(QList<ModPlatform::IndexedVersion> versions,
QList<ModPlatform::ModLoaderType> instanceLoaders,
ModPlatform::ModLoaderTypes fallback);
Task::Ptr getProjects(QStringList addonIds, std::shared_ptr<QByteArray> response) const override; Task::Ptr getProjects(QStringList addonIds, std::shared_ptr<QByteArray> response) const override;
Task::Ptr matchFingerprints(const QList<uint>& fingerprints, std::shared_ptr<QByteArray> response); Task::Ptr matchFingerprints(const QList<uint>& fingerprints, std::shared_ptr<QByteArray> response);
@ -26,9 +28,9 @@ class FlameAPI : public NetworkResourceAPI {
static Task::Ptr getModCategories(std::shared_ptr<QByteArray> response); static Task::Ptr getModCategories(std::shared_ptr<QByteArray> response);
static QList<ModPlatform::Category> loadModCategories(std::shared_ptr<QByteArray> response); static QList<ModPlatform::Category> loadModCategories(std::shared_ptr<QByteArray> response);
[[nodiscard]] auto getSortingMethods() const -> QList<ResourceAPI::SortingMethod> override; [[nodiscard]] QList<ResourceAPI::SortingMethod> getSortingMethods() const override;
static inline auto validateModLoaders(ModPlatform::ModLoaderTypes loaders) -> bool static inline bool validateModLoaders(ModPlatform::ModLoaderTypes loaders)
{ {
return loaders & (ModPlatform::NeoForge | ModPlatform::Forge | ModPlatform::Fabric | ModPlatform::Quilt); return loaders & (ModPlatform::NeoForge | ModPlatform::Forge | ModPlatform::Fabric | ModPlatform::Quilt);
} }
@ -67,7 +69,7 @@ class FlameAPI : public NetworkResourceAPI {
return 0; return 0;
} }
static auto getModLoaderStrings(const ModPlatform::ModLoaderTypes types) -> const QStringList static const QStringList getModLoaderStrings(const ModPlatform::ModLoaderTypes types)
{ {
QStringList l; QStringList l;
for (auto loader : { ModPlatform::NeoForge, ModPlatform::Forge, ModPlatform::Fabric, ModPlatform::Quilt }) { for (auto loader : { ModPlatform::NeoForge, ModPlatform::Forge, ModPlatform::Fabric, ModPlatform::Quilt }) {
@ -78,10 +80,7 @@ class FlameAPI : public NetworkResourceAPI {
return l; return l;
} }
static auto getModLoaderFilters(ModPlatform::ModLoaderTypes types) -> const QString static const QString getModLoaderFilters(ModPlatform::ModLoaderTypes types) { return "[" + getModLoaderStrings(types).join(',') + "]"; }
{
return "[" + getModLoaderStrings(types).join(',') + "]";
}
private: private:
[[nodiscard]] std::optional<QString> getSearchURL(SearchArgs const& args) const override [[nodiscard]] std::optional<QString> getSearchURL(SearchArgs const& args) const override

View File

@ -1,4 +1,5 @@
#include "FlameCheckUpdate.h" #include "FlameCheckUpdate.h"
#include "Application.h"
#include "FlameAPI.h" #include "FlameAPI.h"
#include "FlameModIndex.h" #include "FlameModIndex.h"
@ -132,25 +133,26 @@ void FlameCheckUpdate::executeTask()
setStatus(tr("Getting API response from CurseForge for '%1'...").arg(mod->name())); setStatus(tr("Getting API response from CurseForge for '%1'...").arg(mod->name()));
setProgress(i++, m_mods.size()); setProgress(i++, m_mods.size());
auto latest_ver = api.getLatestVersion({ { mod->metadata()->project_id.toString() }, m_game_versions, m_loaders }); auto latest_vers = api.getLatestVersions({ { mod->metadata()->project_id.toString() }, m_game_versions });
// Check if we were aborted while getting the latest version // Check if we were aborted while getting the latest version
if (m_was_aborted) { if (m_was_aborted) {
aborted(); aborted();
return; return;
} }
auto latest_ver = api.getLatestVersion(latest_vers, m_loaders_list, mod->loaders());
setStatus(tr("Parsing the API response from CurseForge for '%1'...").arg(mod->name())); setStatus(tr("Parsing the API response from CurseForge for '%1'...").arg(mod->name()));
if (!latest_ver.addonId.isValid()) { if (!latest_ver.has_value() || !latest_ver->addonId.isValid()) {
emit checkFailed(mod, tr("No valid version found for this mod. It's probably unavailable for the current game " emit checkFailed(mod, tr("No valid version found for this mod. It's probably unavailable for the current game "
"version / mod loader.")); "version / mod loader."));
continue; continue;
} }
if (latest_ver.downloadUrl.isEmpty() && latest_ver.fileId != mod->metadata()->file_id) { if (latest_ver->downloadUrl.isEmpty() && latest_ver->fileId != mod->metadata()->file_id) {
auto pack = getProjectInfo(latest_ver); auto pack = getProjectInfo(latest_ver.value());
auto recover_url = QString("%1/download/%2").arg(pack.websiteUrl, latest_ver.fileId.toString()); auto recover_url = QString("%1/download/%2").arg(pack.websiteUrl, latest_ver->fileId.toString());
emit checkFailed(mod, tr("Mod has a new update available, but is not downloadable using CurseForge."), recover_url); emit checkFailed(mod, tr("Mod has a new update available, but is not downloadable using CurseForge."), recover_url);
continue; continue;
@ -166,19 +168,19 @@ void FlameCheckUpdate::executeTask()
pack->authors.append({ author }); pack->authors.append({ author });
pack->description = mod->description(); pack->description = mod->description();
pack->provider = ModPlatform::ResourceProvider::FLAME; pack->provider = ModPlatform::ResourceProvider::FLAME;
if (!latest_ver.hash.isEmpty() && (mod->metadata()->hash != latest_ver.hash || mod->status() == ModStatus::NotInstalled)) { if (!latest_ver->hash.isEmpty() && (mod->metadata()->hash != latest_ver->hash || mod->status() == ModStatus::NotInstalled)) {
auto old_version = mod->version(); auto old_version = mod->version();
if (old_version.isEmpty() && mod->status() != ModStatus::NotInstalled) { if (old_version.isEmpty() && mod->status() != ModStatus::NotInstalled) {
auto current_ver = getFileInfo(latest_ver.addonId.toInt(), mod->metadata()->file_id.toInt()); auto current_ver = getFileInfo(latest_ver->addonId.toInt(), mod->metadata()->file_id.toInt());
old_version = current_ver.version; old_version = current_ver.version;
} }
auto download_task = makeShared<ResourceDownloadTask>(pack, latest_ver, m_mods_folder); auto download_task = makeShared<ResourceDownloadTask>(pack, latest_ver.value(), m_mods_folder);
m_updatable.emplace_back(pack->name, mod->metadata()->hash, old_version, latest_ver.version, latest_ver.version_type, m_updatable.emplace_back(pack->name, mod->metadata()->hash, old_version, latest_ver->version, latest_ver->version_type,
api.getModFileChangelog(latest_ver.addonId.toInt(), latest_ver.fileId.toInt()), api.getModFileChangelog(latest_ver->addonId.toInt(), latest_ver->fileId.toInt()),
ModPlatform::ResourceProvider::FLAME, download_task); ModPlatform::ResourceProvider::FLAME, download_task);
} }
m_deps.append(std::make_shared<GetModDependenciesTask::PackDependency>(pack, latest_ver)); m_deps.append(std::make_shared<GetModDependenciesTask::PackDependency>(pack, latest_ver.value()));
} }
emitSucceeded(); emitSucceeded();

View File

@ -1,6 +1,5 @@
#pragma once #pragma once
#include "Application.h"
#include "modplatform/CheckUpdateTask.h" #include "modplatform/CheckUpdateTask.h"
#include "net/NetJob.h" #include "net/NetJob.h"
@ -10,9 +9,9 @@ class FlameCheckUpdate : public CheckUpdateTask {
public: public:
FlameCheckUpdate(QList<Mod*>& mods, FlameCheckUpdate(QList<Mod*>& mods,
std::list<Version>& mcVersions, std::list<Version>& mcVersions,
std::optional<ModPlatform::ModLoaderTypes> loaders, QList<ModPlatform::ModLoaderType> loadersList,
std::shared_ptr<ModFolderModel> mods_folder) std::shared_ptr<ModFolderModel> mods_folder)
: CheckUpdateTask(mods, mcVersions, loaders, mods_folder) : CheckUpdateTask(mods, mcVersions, loadersList, mods_folder)
{} {}
public slots: public slots:

View File

@ -1,23 +1,31 @@
#include "ModrinthCheckUpdate.h" #include "ModrinthCheckUpdate.h"
#include "Application.h"
#include "ModrinthAPI.h" #include "ModrinthAPI.h"
#include "ModrinthPackIndex.h" #include "ModrinthPackIndex.h"
#include "Json.h" #include "Json.h"
#include "QObjectPtr.h"
#include "ResourceDownloadTask.h" #include "ResourceDownloadTask.h"
#include "modplatform/helpers/HashUtils.h" #include "modplatform/helpers/HashUtils.h"
#include "tasks/ConcurrentTask.h" #include "tasks/ConcurrentTask.h"
#include "minecraft/mod/ModFolderModel.h"
static ModrinthAPI api; static ModrinthAPI api;
ModrinthCheckUpdate::ModrinthCheckUpdate(QList<Mod*>& mods,
std::list<Version>& mcVersions,
QList<ModPlatform::ModLoaderType> loadersList,
std::shared_ptr<ModFolderModel> mods_folder)
: CheckUpdateTask(mods, mcVersions, loadersList, mods_folder)
, m_hash_type(ModPlatform::ProviderCapabilities::hashType(ModPlatform::ResourceProvider::MODRINTH).first())
{}
bool ModrinthCheckUpdate::abort() bool ModrinthCheckUpdate::abort()
{ {
if (m_net_job) if (m_job)
return m_net_job->abort(); return m_job->abort();
return true; return true;
} }
@ -29,15 +37,10 @@ bool ModrinthCheckUpdate::abort()
void ModrinthCheckUpdate::executeTask() void ModrinthCheckUpdate::executeTask()
{ {
setStatus(tr("Preparing mods for Modrinth...")); setStatus(tr("Preparing mods for Modrinth..."));
setProgress(0, 3); setProgress(0, 9);
QHash<QString, Mod*> mappings; auto hashing_task =
makeShared<ConcurrentTask>(this, "MakeModrinthHashesTask", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt());
// Create all hashes
QStringList hashes;
auto best_hash_type = ModPlatform::ProviderCapabilities::hashType(ModPlatform::ResourceProvider::MODRINTH).first();
ConcurrentTask hashing_task(this, "MakeModrinthHashesTask", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt());
for (auto* mod : m_mods) { for (auto* mod : m_mods) {
if (!mod->enabled()) { if (!mod->enabled()) {
emit checkFailed(mod, tr("Disabled mods won't be updated, to prevent mod duplication issues!")); emit checkFailed(mod, tr("Disabled mods won't be updated, to prevent mod duplication issues!"));
@ -49,132 +52,176 @@ void ModrinthCheckUpdate::executeTask()
// Sadly the API can only handle one hash type per call, se we // Sadly the API can only handle one hash type per call, se we
// need to generate a new hash if the current one is innadequate // need to generate a new hash if the current one is innadequate
// (though it will rarely happen, if at all) // (though it will rarely happen, if at all)
if (mod->metadata()->hash_format != best_hash_type) { if (mod->metadata()->hash_format != m_hash_type) {
auto hash_task = Hashing::createHasher(mod->fileinfo().absoluteFilePath(), ModPlatform::ResourceProvider::MODRINTH); auto hash_task = Hashing::createHasher(mod->fileinfo().absoluteFilePath(), ModPlatform::ResourceProvider::MODRINTH);
connect(hash_task.get(), &Hashing::Hasher::resultsReady, [&hashes, &mappings, mod](QString hash) { connect(hash_task.get(), &Hashing::Hasher::resultsReady, [this, mod](QString hash) { m_mappings.insert(hash, mod); });
hashes.append(hash);
mappings.insert(hash, mod);
});
connect(hash_task.get(), &Task::failed, [this] { failed("Failed to generate hash"); }); connect(hash_task.get(), &Task::failed, [this] { failed("Failed to generate hash"); });
hashing_task.addTask(hash_task); hashing_task->addTask(hash_task);
} else { } else {
hashes.append(hash); m_mappings.insert(hash, mod);
mappings.insert(hash, mod);
} }
} }
QEventLoop loop; connect(hashing_task.get(), &Task::finished, this, &ModrinthCheckUpdate::checkNextLoader);
connect(&hashing_task, &Task::finished, [&loop] { loop.quit(); }); m_job = hashing_task;
hashing_task.start(); hashing_task->start();
loop.exec(); }
auto response = std::make_shared<QByteArray>(); void ModrinthCheckUpdate::checkVersionsResponse(std::shared_ptr<QByteArray> response,
auto job = api.latestVersions(hashes, best_hash_type, m_game_versions, m_loaders, response); ModPlatform::ModLoaderTypes loader,
bool forceModLoaderCheck)
{
QJsonParseError parse_error{};
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
if (parse_error.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response from ModrinthCheckUpdate at " << parse_error.offset
<< " reason: " << parse_error.errorString();
qWarning() << *response;
connect(job.get(), &Task::succeeded, this, [this, response, mappings, best_hash_type, job] { emitFailed(parse_error.errorString());
QJsonParseError parse_error{}; return;
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); }
if (parse_error.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response from ModrinthCheckUpdate at " << parse_error.offset
<< " reason: " << parse_error.errorString();
qWarning() << *response;
emitFailed(parse_error.errorString()); setStatus(tr("Parsing the API response from Modrinth..."));
return; setProgress(m_next_loader_idx * 2, 9);
}
setStatus(tr("Parsing the API response from Modrinth...")); try {
setProgress(2, 3); for (auto hash : m_mappings.keys()) {
if (forceModLoaderCheck && !(m_mappings[hash]->loaders() & loader)) {
try { continue;
for (auto hash : mappings.keys()) {
auto project_obj = doc[hash].toObject();
// If the returned project is empty, but we have Modrinth metadata,
// it means this specific version is not available
if (project_obj.isEmpty()) {
qDebug() << "Mod " << mappings.find(hash).value()->name() << " got an empty response.";
qDebug() << "Hash: " << hash;
emit checkFailed(
mappings.find(hash).value(),
tr("No valid version found for this mod. It's probably unavailable for the current game version / mod loader."));
continue;
}
// Sometimes a version may have multiple files, one with "forge" and one with "fabric",
// so we may want to filter it
QString loader_filter;
if (m_loaders.has_value()) {
static auto flags = { ModPlatform::ModLoaderType::NeoForge, ModPlatform::ModLoaderType::Forge,
ModPlatform::ModLoaderType::Fabric, ModPlatform::ModLoaderType::Quilt };
for (auto flag : flags) {
if (m_loaders.value().testFlag(flag)) {
loader_filter = ModPlatform::getModLoaderAsString(flag);
break;
}
}
}
// Currently, we rely on a couple heuristics to determine whether an update is actually available or not:
// - The file needs to be preferred: It is either the primary file, or the one found via (explicit) usage of the
// loader_filter
// - The version reported by the JAR is different from the version reported by the indexed version (it's usually the case)
// Such is the pain of having arbitrary files for a given version .-.
auto project_ver = Modrinth::loadIndexedPackVersion(project_obj, best_hash_type, loader_filter);
if (project_ver.downloadUrl.isEmpty()) {
qCritical() << "Modrinth mod without download url!";
qCritical() << project_ver.fileName;
emit checkFailed(mappings.find(hash).value(), tr("Mod has an empty download URL"));
continue;
}
auto mod_iter = mappings.find(hash);
if (mod_iter == mappings.end()) {
qCritical() << "Failed to remap mod from Modrinth!";
continue;
}
auto mod = *mod_iter;
auto key = project_ver.hash;
// Fake pack with the necessary info to pass to the download task :)
auto pack = std::make_shared<ModPlatform::IndexedPack>();
pack->name = mod->name();
pack->slug = mod->metadata()->slug;
pack->addonId = mod->metadata()->project_id;
pack->websiteUrl = mod->homeurl();
for (auto& author : mod->authors())
pack->authors.append({ author });
pack->description = mod->description();
pack->provider = ModPlatform::ResourceProvider::MODRINTH;
if ((key != hash && project_ver.is_preferred) || (mod->status() == ModStatus::NotInstalled)) {
if (mod->version() == project_ver.version_number)
continue;
auto download_task = makeShared<ResourceDownloadTask>(pack, project_ver, m_mods_folder);
m_updatable.emplace_back(pack->name, hash, mod->version(), project_ver.version_number, project_ver.version_type,
project_ver.changelog, ModPlatform::ResourceProvider::MODRINTH, download_task);
}
m_deps.append(std::make_shared<GetModDependenciesTask::PackDependency>(pack, project_ver));
} }
} catch (Json::JsonException& e) { auto project_obj = doc[hash].toObject();
emitFailed(e.cause() + " : " + e.what());
return;
}
emitSucceeded();
});
connect(job.get(), &Task::failed, this, &ModrinthCheckUpdate::emitFailed); // If the returned project is empty, but we have Modrinth metadata,
// it means this specific version is not available
if (project_obj.isEmpty()) {
qDebug() << "Mod " << m_mappings.find(hash).value()->name() << " got an empty response."
<< "Hash: " << hash;
continue;
}
// Sometimes a version may have multiple files, one with "forge" and one with "fabric",
// so we may want to filter it
QString loader_filter;
static auto flags = { ModPlatform::ModLoaderType::NeoForge, ModPlatform::ModLoaderType::Forge,
ModPlatform::ModLoaderType::Quilt, ModPlatform::ModLoaderType::Fabric };
for (auto flag : flags) {
if (loader.testFlag(flag)) {
loader_filter = ModPlatform::getModLoaderAsString(flag);
break;
}
}
// Currently, we rely on a couple heuristics to determine whether an update is actually available or not:
// - The file needs to be preferred: It is either the primary file, or the one found via (explicit) usage of the
// loader_filter
// - The version reported by the JAR is different from the version reported by the indexed version (it's usually the case)
// Such is the pain of having arbitrary files for a given version .-.
auto project_ver = Modrinth::loadIndexedPackVersion(project_obj, m_hash_type, loader_filter);
if (project_ver.downloadUrl.isEmpty()) {
qCritical() << "Modrinth mod without download url!" << project_ver.fileName;
continue;
}
auto mod_iter = m_mappings.find(hash);
if (mod_iter == m_mappings.end()) {
qCritical() << "Failed to remap mod from Modrinth!";
continue;
}
auto mod = *mod_iter;
m_mappings.remove(hash);
auto key = project_ver.hash;
// Fake pack with the necessary info to pass to the download task :)
auto pack = std::make_shared<ModPlatform::IndexedPack>();
pack->name = mod->name();
pack->slug = mod->metadata()->slug;
pack->addonId = mod->metadata()->project_id;
pack->websiteUrl = mod->homeurl();
for (auto& author : mod->authors())
pack->authors.append({ author });
pack->description = mod->description();
pack->provider = ModPlatform::ResourceProvider::MODRINTH;
if ((key != hash && project_ver.is_preferred) || (mod->status() == ModStatus::NotInstalled)) {
if (mod->version() == project_ver.version_number)
continue;
auto download_task = makeShared<ResourceDownloadTask>(pack, project_ver, m_mods_folder);
m_updatable.emplace_back(pack->name, hash, mod->version(), project_ver.version_number, project_ver.version_type,
project_ver.changelog, ModPlatform::ResourceProvider::MODRINTH, download_task);
}
m_deps.append(std::make_shared<GetModDependenciesTask::PackDependency>(pack, project_ver));
}
} catch (Json::JsonException& e) {
emitFailed(e.cause() + " : " + e.what());
return;
}
checkNextLoader();
}
void ModrinthCheckUpdate::getUpdateModsForLoader(ModPlatform::ModLoaderTypes loader, bool forceModLoaderCheck)
{
auto response = std::make_shared<QByteArray>();
QStringList hashes;
if (forceModLoaderCheck) {
for (auto hash : m_mappings.keys()) {
if (m_mappings[hash]->loaders() & loader) {
hashes.append(hash);
}
}
} else {
hashes = m_mappings.keys();
}
auto job = api.latestVersions(hashes, m_hash_type, m_game_versions, loader, response);
connect(job.get(), &Task::succeeded, this,
[this, response, loader, forceModLoaderCheck] { checkVersionsResponse(response, loader, forceModLoaderCheck); });
connect(job.get(), &Task::failed, this, &ModrinthCheckUpdate::checkNextLoader);
setStatus(tr("Waiting for the API response from Modrinth...")); setStatus(tr("Waiting for the API response from Modrinth..."));
setProgress(1, 3); setProgress(m_next_loader_idx * 2 - 1, 9);
m_net_job = qSharedPointerObjectCast<NetJob, Task>(job); m_job = job;
job->start(); job->start();
} }
void ModrinthCheckUpdate::checkNextLoader()
{
if (m_mappings.isEmpty()) {
emitSucceeded();
return;
}
if (m_next_loader_idx < m_loaders_list.size()) {
getUpdateModsForLoader(m_loaders_list.at(m_next_loader_idx));
m_next_loader_idx++;
return;
}
static auto flags = { ModPlatform::ModLoaderType::NeoForge, ModPlatform::ModLoaderType::Forge, ModPlatform::ModLoaderType::Quilt,
ModPlatform::ModLoaderType::Fabric };
for (auto flag : flags) {
if (!m_loaders_list.contains(flag)) {
m_loaders_list.append(flag);
m_next_loader_idx++;
setProgress(m_next_loader_idx * 2 - 1, 9);
for (auto m : m_mappings) {
if (m->loaders() & flag) {
getUpdateModsForLoader(flag, true);
return;
}
}
setProgress(m_next_loader_idx * 2, 9);
}
}
for (auto m : m_mappings) {
emit checkFailed(m,
tr("No valid version found for this mod. It's probably unavailable for the current game version / mod loader."));
}
emitSucceeded();
return;
}

View File

@ -1,8 +1,6 @@
#pragma once #pragma once
#include "Application.h"
#include "modplatform/CheckUpdateTask.h" #include "modplatform/CheckUpdateTask.h"
#include "net/NetJob.h"
class ModrinthCheckUpdate : public CheckUpdateTask { class ModrinthCheckUpdate : public CheckUpdateTask {
Q_OBJECT Q_OBJECT
@ -10,17 +8,21 @@ class ModrinthCheckUpdate : public CheckUpdateTask {
public: public:
ModrinthCheckUpdate(QList<Mod*>& mods, ModrinthCheckUpdate(QList<Mod*>& mods,
std::list<Version>& mcVersions, std::list<Version>& mcVersions,
std::optional<ModPlatform::ModLoaderTypes> loaders, QList<ModPlatform::ModLoaderType> loadersList,
std::shared_ptr<ModFolderModel> mods_folder) std::shared_ptr<ModFolderModel> mods_folder);
: CheckUpdateTask(mods, mcVersions, loaders, mods_folder)
{}
public slots: public slots:
bool abort() override; bool abort() override;
protected slots: protected slots:
void executeTask() override; void executeTask() override;
void getUpdateModsForLoader(ModPlatform::ModLoaderTypes loader, bool forceModLoaderCheck = false);
void checkVersionsResponse(std::shared_ptr<QByteArray> response, ModPlatform::ModLoaderTypes loader, bool forceModLoaderCheck = false);
void checkNextLoader();
private: private:
NetJob::Ptr m_net_job = nullptr; Task::Ptr m_job = nullptr;
QHash<QString, Mod*> m_mappings;
QString m_hash_type;
int m_next_loader_idx = 0;
}; };

View File

@ -60,7 +60,11 @@ class ChecksumValidator : public Validator {
return true; return true;
} }
auto abort() -> bool override { return true; } auto abort() -> bool override
{
m_checksum.reset();
return true;
}
auto validate(QNetworkReply&) -> bool override auto validate(QNetworkReply&) -> bool override
{ {

View File

@ -36,6 +36,7 @@
*/ */
#include "NetJob.h" #include "NetJob.h"
#include <QNetworkReply>
#include "net/NetRequest.h" #include "net/NetRequest.h"
#include "tasks/ConcurrentTask.h" #include "tasks/ConcurrentTask.h"
#if defined(LAUNCHER_APPLICATION) #if defined(LAUNCHER_APPLICATION)
@ -145,10 +146,23 @@ void NetJob::updateState()
.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())));
} }
bool NetJob::isOnline()
{
// check some errors that are ussually associated with the lack of internet
for (auto job : getFailedActions()) {
auto err = job->error();
if (err != QNetworkReply::HostNotFoundError && err != QNetworkReply::NetworkSessionFailedError) {
return true;
}
}
return false;
};
void NetJob::emitFailed(QString reason) void NetJob::emitFailed(QString reason)
{ {
#if defined(LAUNCHER_APPLICATION) #if defined(LAUNCHER_APPLICATION)
if (m_ask_retry) { if (m_ask_retry && m_manual_try < APPLICATION->settings()->get("NumberOfManualRetries").toInt() && isOnline()) {
m_manual_try++;
auto response = CustomMessageBox::selectable(nullptr, "Confirm retry", auto response = CustomMessageBox::selectable(nullptr, "Confirm retry",
"The tasks failed.\n" "The tasks failed.\n"
"Failed urls\n" + "Failed urls\n" +

View File

@ -74,10 +74,12 @@ class NetJob : public ConcurrentTask {
protected: protected:
void updateState() override; void updateState() override;
bool isOnline();
private: private:
shared_qobject_ptr<QNetworkAccessManager> m_network; shared_qobject_ptr<QNetworkAccessManager> m_network;
int m_try = 1; int m_try = 1;
bool m_ask_retry = true; bool m_ask_retry = true;
int m_manual_try = 0;
}; };

View File

@ -84,7 +84,7 @@ void NetRequest::executeTask()
break; break;
case State::Inactive: case State::Inactive:
case State::Failed: case State::Failed:
emit failed("Failed to initilize sink"); emit failed("Failed to initialize sink");
emit finished(); emit finished();
return; return;
case State::AbortedByUser: case State::AbortedByUser:

View File

@ -58,6 +58,7 @@ void NewsChecker::reloadNews()
NetJob::Ptr job{ new NetJob("News RSS Feed", m_network) }; NetJob::Ptr job{ new NetJob("News RSS Feed", m_network) };
job->addNetAction(Net::Download::makeByteArray(m_feedUrl, newsData)); job->addNetAction(Net::Download::makeByteArray(m_feedUrl, newsData));
job->setAskRetry(false);
QObject::connect(job.get(), &NetJob::succeeded, this, &NewsChecker::rssDownloadFinished); QObject::connect(job.get(), &NetJob::succeeded, this, &NewsChecker::rssDownloadFinished);
QObject::connect(job.get(), &NetJob::failed, this, &NewsChecker::rssDownloadFailed); QObject::connect(job.get(), &NetJob::failed, this, &NewsChecker::rssDownloadFailed);
m_newsNetJob.reset(job); m_newsNetJob.reset(job);

View File

@ -69,13 +69,13 @@ QNetworkReply* ImgurAlbumCreation::getReply(QNetworkRequest& request)
} }
const QByteArray data = "deletehashes=" + hashes.join(',').toUtf8() + "&title=Minecraft%20Screenshots&privacy=hidden"; const QByteArray data = "deletehashes=" + hashes.join(',').toUtf8() + "&title=Minecraft%20Screenshots&privacy=hidden";
return m_network->post(request, data); return m_network->post(request, data);
}; }
auto ImgurAlbumCreation::Sink::init(QNetworkRequest& request) -> Task::State auto ImgurAlbumCreation::Sink::init(QNetworkRequest& request) -> Task::State
{ {
m_output.clear(); m_output.clear();
return Task::State::Running; return Task::State::Running;
}; }
auto ImgurAlbumCreation::Sink::write(QByteArray& data) -> Task::State auto ImgurAlbumCreation::Sink::write(QByteArray& data) -> Task::State
{ {

View File

@ -73,13 +73,13 @@ QNetworkReply* ImgurUpload::getReply(QNetworkRequest& request)
multipart->append(namePart); multipart->append(namePart);
return m_network->post(request, multipart); return m_network->post(request, multipart);
}; }
auto ImgurUpload::Sink::init(QNetworkRequest& request) -> Task::State auto ImgurUpload::Sink::init(QNetworkRequest& request) -> Task::State
{ {
m_output.clear(); m_output.clear();
return Task::State::Running; return Task::State::Running;
}; }
auto ImgurUpload::Sink::write(QByteArray& data) -> Task::State auto ImgurUpload::Sink::write(QByteArray& data) -> Task::State
{ {

View File

@ -29,7 +29,7 @@
class OverrideSetting : public Setting { class OverrideSetting : public Setting {
Q_OBJECT Q_OBJECT
public: public:
explicit OverrideSetting(std::shared_ptr<Setting> overriden, std::shared_ptr<Setting> gate); explicit OverrideSetting(std::shared_ptr<Setting> overridden, std::shared_ptr<Setting> gate);
virtual QVariant defValue() const; virtual QVariant defValue() const;
virtual QVariant get() const; virtual QVariant get() const;

View File

@ -28,7 +28,7 @@
class PassthroughSetting : public Setting { class PassthroughSetting : public Setting {
Q_OBJECT Q_OBJECT
public: public:
explicit PassthroughSetting(std::shared_ptr<Setting> overriden, std::shared_ptr<Setting> gate); explicit PassthroughSetting(std::shared_ptr<Setting> overridden, std::shared_ptr<Setting> gate);
virtual QVariant defValue() const; virtual QVariant defValue() const;
virtual QVariant get() const; virtual QVariant get() const;

View File

@ -553,6 +553,7 @@ void TranslationsModel::downloadIndex()
auto task = Net::Download::makeCached(QUrl(BuildConfig.TRANSLATIONS_BASE_URL + "index_v2.json"), entry); auto task = Net::Download::makeCached(QUrl(BuildConfig.TRANSLATIONS_BASE_URL + "index_v2.json"), entry);
d->m_index_task = task.get(); d->m_index_task = task.get();
d->m_index_job->addNetAction(task); d->m_index_job->addNetAction(task);
d->m_index_job->setAskRetry(false);
connect(d->m_index_job.get(), &NetJob::failed, this, &TranslationsModel::indexFailed); connect(d->m_index_job.get(), &NetJob::failed, this, &TranslationsModel::indexFailed);
connect(d->m_index_job.get(), &NetJob::succeeded, this, &TranslationsModel::indexReceived); connect(d->m_index_job.get(), &NetJob::succeeded, this, &TranslationsModel::indexReceived);
d->m_index_job->start(); d->m_index_job->start();

View File

@ -1,4 +1,5 @@
#include "ModUpdateDialog.h" #include "ModUpdateDialog.h"
#include "Application.h"
#include "ChooseProviderDialog.h" #include "ChooseProviderDialog.h"
#include "CustomMessageBox.h" #include "CustomMessageBox.h"
#include "ProgressDialog.h" #include "ProgressDialog.h"
@ -30,9 +31,9 @@ static std::list<Version> mcVersions(BaseInstance* inst)
return { static_cast<MinecraftInstance*>(inst)->getPackProfile()->getComponent("net.minecraft")->getVersion() }; return { static_cast<MinecraftInstance*>(inst)->getPackProfile()->getComponent("net.minecraft")->getVersion() };
} }
static std::optional<ModPlatform::ModLoaderTypes> mcLoaders(BaseInstance* inst) static QList<ModPlatform::ModLoaderType> mcLoadersList(BaseInstance* inst)
{ {
return { static_cast<MinecraftInstance*>(inst)->getPackProfile()->getSupportedModLoaders() }; return static_cast<MinecraftInstance*>(inst)->getPackProfile()->getModLoadersList();
} }
ModUpdateDialog::ModUpdateDialog(QWidget* parent, ModUpdateDialog::ModUpdateDialog(QWidget* parent,
@ -86,19 +87,19 @@ void ModUpdateDialog::checkCandidates()
} }
auto versions = mcVersions(m_instance); auto versions = mcVersions(m_instance);
auto loaders = mcLoaders(m_instance); auto loadersList = mcLoadersList(m_instance);
SequentialTask check_task(m_parent, tr("Checking for updates")); SequentialTask check_task(m_parent, tr("Checking for updates"));
if (!m_modrinth_to_update.empty()) { if (!m_modrinth_to_update.empty()) {
m_modrinth_check_task.reset(new ModrinthCheckUpdate(m_modrinth_to_update, versions, loaders, m_mod_model)); m_modrinth_check_task.reset(new ModrinthCheckUpdate(m_modrinth_to_update, versions, loadersList, m_mod_model));
connect(m_modrinth_check_task.get(), &CheckUpdateTask::checkFailed, this, connect(m_modrinth_check_task.get(), &CheckUpdateTask::checkFailed, this,
[this](Mod* mod, QString reason, QUrl recover_url) { m_failed_check_update.append({ mod, reason, recover_url }); }); [this](Mod* mod, QString reason, QUrl recover_url) { m_failed_check_update.append({ mod, reason, recover_url }); });
check_task.addTask(m_modrinth_check_task); check_task.addTask(m_modrinth_check_task);
} }
if (!m_flame_to_update.empty()) { if (!m_flame_to_update.empty()) {
m_flame_check_task.reset(new FlameCheckUpdate(m_flame_to_update, versions, loaders, m_mod_model)); m_flame_check_task.reset(new FlameCheckUpdate(m_flame_to_update, versions, loadersList, m_mod_model));
connect(m_flame_check_task.get(), &CheckUpdateTask::checkFailed, this, connect(m_flame_check_task.get(), &CheckUpdateTask::checkFailed, this,
[this](Mod* mod, QString reason, QUrl recover_url) { m_failed_check_update.append({ mod, reason, recover_url }); }); [this](Mod* mod, QString reason, QUrl recover_url) { m_failed_check_update.append({ mod, reason, recover_url }); });
check_task.addTask(m_flame_check_task); check_task.addTask(m_flame_check_task);

View File

@ -207,7 +207,7 @@
<item row="0" column="0"> <item row="0" column="0">
<widget class="QLabel" name="label_8"> <widget class="QLabel" name="label_8">
<property name="text"> <property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Note: you only need to set this to access private data. Read the &lt;a href=&quot;https://docs.modrinth.com/api-spec/#section/Authentication&quot;&gt;documentation&lt;/a&gt; for more information.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Note: you only need to set this to access private data. Read the &lt;a href=&quot;https://docs.modrinth.com/#section/Authentication&quot;&gt;documentation&lt;/a&gt; for more information.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="openExternalLinks"> <property name="openExternalLinks">
<bool>true</bool> <bool>true</bool>

View File

@ -66,7 +66,7 @@ class AccountListPage : public QMainWindow, public BasePage {
return icon; return icon;
} }
QString id() const override { return "accounts"; } QString id() const override { return "accounts"; }
QString helpPage() const override { return "Getting-Started#adding-an-account"; } QString helpPage() const override { return "/getting-started/adding-an-account"; }
void retranslate() override; void retranslate() override;
public slots: public slots:

View File

@ -203,6 +203,7 @@ void LauncherPage::applySettings()
s->set("NumberOfConcurrentTasks", ui->numberOfConcurrentTasksSpinBox->value()); s->set("NumberOfConcurrentTasks", ui->numberOfConcurrentTasksSpinBox->value());
s->set("NumberOfConcurrentDownloads", ui->numberOfConcurrentDownloadsSpinBox->value()); s->set("NumberOfConcurrentDownloads", ui->numberOfConcurrentDownloadsSpinBox->value());
s->set("NumberOfManualRetries", ui->numberOfManualRetriesSpinBox->value());
s->set("RequestTimeout", ui->timeoutSecondsSpinBox->value()); s->set("RequestTimeout", ui->timeoutSecondsSpinBox->value());
// Console settings // Console settings
@ -261,6 +262,7 @@ void LauncherPage::loadSettings()
ui->numberOfConcurrentTasksSpinBox->setValue(s->get("NumberOfConcurrentTasks").toInt()); ui->numberOfConcurrentTasksSpinBox->setValue(s->get("NumberOfConcurrentTasks").toInt());
ui->numberOfConcurrentDownloadsSpinBox->setValue(s->get("NumberOfConcurrentDownloads").toInt()); ui->numberOfConcurrentDownloadsSpinBox->setValue(s->get("NumberOfConcurrentDownloads").toInt());
ui->numberOfManualRetriesSpinBox->setValue(s->get("NumberOfManualRetries").toInt());
ui->timeoutSecondsSpinBox->setValue(s->get("RequestTimeout").toInt()); ui->timeoutSecondsSpinBox->setValue(s->get("RequestTimeout").toInt());
// Console settings // Console settings

View File

@ -291,6 +291,20 @@
</widget> </widget>
</item> </item>
<item row="2" column="0"> <item row="2" column="0">
<widget class="QLabel" name="numberOfManualRetriesLabel">
<property name="text">
<string>Number of manual retries</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QSpinBox" name="numberOfManualRetriesSpinBox">
<property name="minimum">
<number>0</number>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="timeoutSecondsLabel"> <widget class="QLabel" name="timeoutSecondsLabel">
<property name="toolTip"> <property name="toolTip">
<string>Seconds to wait until the requests are terminated</string> <string>Seconds to wait until the requests are terminated</string>
@ -300,7 +314,7 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="1"> <item row="3" column="1">
<widget class="QSpinBox" name="timeoutSecondsSpinBox"> <widget class="QSpinBox" name="timeoutSecondsSpinBox">
<property name="suffix"> <property name="suffix">
<string>s</string> <string>s</string>

View File

@ -119,6 +119,7 @@ class ModrinthManagedPackPage final : public ManagedPackPage {
void parseManagedPack() override; void parseManagedPack() override;
[[nodiscard]] QString url() const override; [[nodiscard]] QString url() const override;
[[nodiscard]] QString helpPage() const override { return "modrinth-managed-pack"; }
public slots: public slots:
void suggestVersion() override; void suggestVersion() override;
@ -142,6 +143,7 @@ class FlameManagedPackPage final : public ManagedPackPage {
void parseManagedPack() override; void parseManagedPack() override;
[[nodiscard]] QString url() const override; [[nodiscard]] QString url() const override;
[[nodiscard]] QString helpPage() const override { return "curseforge-managed-pack"; }
public slots: public slots:
void suggestVersion() override; void suggestVersion() override;

View File

@ -57,7 +57,7 @@ class OtherLogsPage : public QWidget, public BasePage {
QString id() const override { return "logs"; } QString id() const override { return "logs"; }
QString displayName() const override { return tr("Other logs"); } QString displayName() const override { return tr("Other logs"); }
QIcon icon() const override { return APPLICATION->getThemedIcon("log"); } QIcon icon() const override { return APPLICATION->getThemedIcon("log"); }
QString helpPage() const override { return "Minecraft-Logs"; } QString helpPage() const override { return "other-Logs"; }
void retranslate() override; void retranslate() override;
void openedImpl() override; void openedImpl() override;

View File

@ -48,7 +48,7 @@ class ShaderPackPage : public ExternalResourcesPage {
QString displayName() const override { return tr("Shader packs"); } QString displayName() const override { return tr("Shader packs"); }
QIcon icon() const override { return APPLICATION->getThemedIcon("shaderpacks"); } QIcon icon() const override { return APPLICATION->getThemedIcon("shaderpacks"); }
QString id() const override { return "shaderpacks"; } QString id() const override { return "shaderpacks"; }
QString helpPage() const override { return "Resource-packs"; } QString helpPage() const override { return "shader-packs"; }
bool shouldDisplay() const override { return true; } bool shouldDisplay() const override { return true; }

View File

@ -317,8 +317,10 @@ std::optional<QIcon> ResourceModel::getIcon(QModelIndex& index, const QUrl& url)
if (QPixmapCache::find(url.toString(), &pixmap)) if (QPixmapCache::find(url.toString(), &pixmap))
return { pixmap }; return { pixmap };
if (!m_current_icon_job) if (!m_current_icon_job) {
m_current_icon_job.reset(new NetJob("IconJob", APPLICATION->network())); m_current_icon_job.reset(new NetJob("IconJob", APPLICATION->network()));
m_current_icon_job->setAskRetry(false);
}
if (m_currently_running_icon_actions.contains(url)) if (m_currently_running_icon_actions.contains(url))
return {}; return {};

View File

@ -40,6 +40,8 @@ class ResourcePackResourcePage : public ResourcePage {
[[nodiscard]] QMap<QString, QString> urlHandlers() const override; [[nodiscard]] QMap<QString, QString> urlHandlers() const override;
[[nodiscard]] inline auto helpPage() const -> QString override { return "resourcepack-platform"; }
protected: protected:
ResourcePackResourcePage(ResourceDownloadDialog* dialog, BaseInstance& instance); ResourcePackResourcePage(ResourceDownloadDialog* dialog, BaseInstance& instance);

View File

@ -42,6 +42,8 @@ class ShaderPackResourcePage : public ResourcePage {
[[nodiscard]] QMap<QString, QString> urlHandlers() const override; [[nodiscard]] QMap<QString, QString> urlHandlers() const override;
[[nodiscard]] inline auto helpPage() const -> QString override { return "shaderpack-platform"; }
protected: protected:
ShaderPackResourcePage(ShaderPackDownloadDialog* dialog, BaseInstance& instance); ShaderPackResourcePage(ShaderPackDownloadDialog* dialog, BaseInstance& instance);

View File

@ -195,6 +195,7 @@ void ListModel::requestLogo(QString file, QString url)
MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("ATLauncherPacks", QString("logos/%1").arg(file)); MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("ATLauncherPacks", QString("logos/%1").arg(file));
auto job = new NetJob(QString("ATLauncher Icon Download %1").arg(file), APPLICATION->network()); auto job = new NetJob(QString("ATLauncher Icon Download %1").arg(file), APPLICATION->network());
job->setAskRetry(false);
job->addNetAction(Net::ApiDownload::makeCached(QUrl(url), entry)); job->addNetAction(Net::ApiDownload::makeCached(QUrl(url), entry));
auto fullPath = entry->getFullPath(); auto fullPath = entry->getFullPath();

View File

@ -110,6 +110,7 @@ void ListModel::requestLogo(QString logo, QString url)
MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("FlamePacks", QString("logos/%1").arg(logo)); MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("FlamePacks", QString("logos/%1").arg(logo));
auto job = new NetJob(QString("Flame Icon Download %1").arg(logo), APPLICATION->network()); auto job = new NetJob(QString("Flame Icon Download %1").arg(logo), APPLICATION->network());
job->setAskRetry(false);
job->addNetAction(Net::ApiDownload::makeCached(QUrl(url), entry)); job->addNetAction(Net::ApiDownload::makeCached(QUrl(url), entry));
auto fullPath = entry->getFullPath(); auto fullPath = entry->getFullPath();
@ -172,7 +173,7 @@ void ListModel::performPaginatedSearch()
callbacks.on_succeed = [this](auto& doc, auto& pack) { searchRequestForOneSucceeded(doc); }; callbacks.on_succeed = [this](auto& doc, auto& pack) { searchRequestForOneSucceeded(doc); };
callbacks.on_abort = [this] { callbacks.on_abort = [this] {
qCritical() << "Search task aborted by an unknown reason!"; qCritical() << "Search task aborted by an unknown reason!";
searchRequestFailed("Abborted"); searchRequestFailed("Aborted");
}; };
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) {

View File

@ -44,7 +44,7 @@ class ImportFTBPage : public QWidget, public BasePage {
QString displayName() const override { return tr("FTB App Import"); } QString displayName() const override { return tr("FTB App Import"); }
QIcon icon() const override { return APPLICATION->getThemedIcon("ftb_logo"); } QIcon icon() const override { return APPLICATION->getThemedIcon("ftb_logo"); }
QString id() const override { return "import_ftb"; } QString id() const override { return "import_ftb"; }
QString helpPage() const override { return "FTB-platform"; } QString helpPage() const override { return "FTB-import"; }
bool shouldDisplay() const override { return true; } bool shouldDisplay() const override { return true; }
void openedImpl() override; void openedImpl() override;
void retranslate() override; void retranslate() override;

View File

@ -264,6 +264,7 @@ void ListModel::requestLogo(QString file)
MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("FTBPacks", QString("logos/%1").arg(file)); MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("FTBPacks", QString("logos/%1").arg(file));
NetJob* job = new NetJob(QString("FTB Icon Download for %1").arg(file), APPLICATION->network()); NetJob* job = new NetJob(QString("FTB Icon Download for %1").arg(file), APPLICATION->network());
job->setAskRetry(false);
job->addNetAction(Net::ApiDownload::makeCached(QUrl(QString(BuildConfig.LEGACY_FTB_CDN_BASE_URL + "static/%1").arg(file)), entry)); job->addNetAction(Net::ApiDownload::makeCached(QUrl(QString(BuildConfig.LEGACY_FTB_CDN_BASE_URL + "static/%1").arg(file)), entry));
auto fullPath = entry->getFullPath(); auto fullPath = entry->getFullPath();

View File

@ -66,7 +66,7 @@ class Page : public QWidget, public BasePage {
QString displayName() const override { return "FTB Legacy"; } QString displayName() const override { return "FTB Legacy"; }
QIcon icon() const override { return APPLICATION->getThemedIcon("ftb_logo"); } QIcon icon() const override { return APPLICATION->getThemedIcon("ftb_logo"); }
QString id() const override { return "legacy_ftb"; } QString id() const override { return "legacy_ftb"; }
QString helpPage() const override { return "FTB-platform"; } QString helpPage() const override { return "FTB-legacy"; }
bool shouldDisplay() const override; bool shouldDisplay() const override;
void openedImpl() override; void openedImpl() override;
void retranslate() override; void retranslate() override;

View File

@ -254,6 +254,7 @@ void ModpackListModel::requestLogo(QString logo, QString url)
MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry(m_parent->metaEntryBase(), QString("logos/%1").arg(logo)); MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry(m_parent->metaEntryBase(), QString("logos/%1").arg(logo));
auto job = new NetJob(QString("%1 Icon Download %2").arg(m_parent->debugName()).arg(logo), APPLICATION->network()); auto job = new NetJob(QString("%1 Icon Download %2").arg(m_parent->debugName()).arg(logo), APPLICATION->network());
job->setAskRetry(false);
job->addNetAction(Net::ApiDownload::makeCached(QUrl(url), entry)); job->addNetAction(Net::ApiDownload::makeCached(QUrl(url), entry));
auto fullPath = entry->getFullPath(); auto fullPath = entry->getFullPath();

View File

@ -292,6 +292,7 @@ void Technic::ListModel::requestLogo(QString logo, QString url)
MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("TechnicPacks", QString("logos/%1").arg(logo)); MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("TechnicPacks", QString("logos/%1").arg(logo));
auto job = new NetJob(QString("Technic Icon Download %1").arg(logo), APPLICATION->network()); auto job = new NetJob(QString("Technic Icon Download %1").arg(logo), APPLICATION->network());
job->setAskRetry(false);
job->addNetAction(Net::ApiDownload::makeCached(QUrl(url), entry)); job->addNetAction(Net::ApiDownload::makeCached(QUrl(url), entry));
auto fullPath = entry->getFullPath(); auto fullPath = entry->getFullPath();

View File

@ -80,7 +80,7 @@ void VariableSizedImageObject::drawObject(QPainter* painter,
{ {
if (!format.hasProperty(ImageData)) { if (!format.hasProperty(ImageData)) {
QUrl image_url{ qvariant_cast<QString>(format.property(QTextFormat::ImageName)) }; QUrl image_url{ qvariant_cast<QString>(format.property(QTextFormat::ImageName)) };
if (m_fetching_images.contains(image_url)) if (m_fetching_images.contains(image_url) || image_url.isEmpty())
return; return;
auto meta = std::make_shared<ImageMetadata>(); auto meta = std::make_shared<ImageMetadata>();
@ -140,6 +140,7 @@ void VariableSizedImageObject::loadImage(QTextDocument* doc, std::shared_ptr<Ima
QString("images/%1").arg(QString(QCryptographicHash::hash(meta->url.toEncoded(), QCryptographicHash::Algorithm::Sha1).toHex()))); QString("images/%1").arg(QString(QCryptographicHash::hash(meta->url.toEncoded(), QCryptographicHash::Algorithm::Sha1).toHex())));
auto job = new NetJob(QString("Load Image: %1").arg(meta->url.fileName()), APPLICATION->network()); auto job = new NetJob(QString("Load Image: %1").arg(meta->url.fileName()), APPLICATION->network());
job->setAskRetry(false);
job->addNetAction(Net::ApiDownload::makeCached(meta->url, entry)); job->addNetAction(Net::ApiDownload::makeCached(meta->url, entry));
auto full_entry_path = entry->getFullPath(); auto full_entry_path = entry->getFullPath();