chore(clang-tidy): modernize the code

Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
This commit is contained in:
Trial97 2026-05-10 20:51:56 +03:00
parent 2e45d135c5
commit effa8bedb1
No known key found for this signature in database
GPG key ID: 55EF5DA53DB36318
42 changed files with 854 additions and 786 deletions

View file

@ -77,8 +77,8 @@ ResourceDownloadTask::ResourceDownloadTask(ModPlatform::IndexedPack::Ptr pack,
auto action = Net::ApiDownload::makeFile(m_pack_version.downloadUrl, m_pack_model->dir().absoluteFilePath(getFilename()),
Net::Download::Option::NoOptions,
createModrinthMeta(m_pack_model->instance(), std::move(downloadReason)));
if (!m_pack_version.hash_type.isEmpty() && !m_pack_version.hash.isEmpty()) {
switch (Hashing::algorithmFromString(m_pack_version.hash_type)) {
if (!m_pack_version.hashType.isEmpty() && !m_pack_version.hash.isEmpty()) {
switch (Hashing::algorithmFromString(m_pack_version.hashType)) {
case Hashing::Algorithm::Md4:
action->addValidator(new Net::ChecksumValidator(QCryptographicHash::Algorithm::Md4, m_pack_version.hash));
break;

View file

@ -193,7 +193,7 @@ QList<ModPlatform::Dependency> GetModDependenciesTask::getDependenciesForVersion
return cDependencies;
}
Task::Ptr GetModDependenciesTask::getProjectInfoTask(std::shared_ptr<PackDependency> pDep)
Task::Ptr GetModDependenciesTask::getProjectInfoTask(const std::shared_ptr<PackDependency>& pDep)
{
auto provider = pDep->pack->provider;
auto [info, responseInfo] = getAPI(provider)->getProject(pDep->pack->addonId.toString());
@ -249,10 +249,10 @@ Task::Ptr GetModDependenciesTask::prepareDependencyTask(const ModPlatform::Depen
.dependency = dep, .mcVersion = m_version, .loader = m_loaderType, .includeChangelog = true
};
ResourceAPI::Callback<ModPlatform::IndexedVersion> callbacks;
callbacks.on_fail = [](const QString& reason, int) {
callbacks.onFail = [](const QString& reason, int) {
qCritical() << tr("A network error occurred. Could not load project dependencies:%1").arg(reason);
};
callbacks.on_succeed = [dep, provider, pDep, level, this](auto& pack) {
callbacks.onSucceed = [dep, provider, pDep, level, this](auto& pack) {
pDep->version = pack;
if (!pDep->version.addonId.isValid()) {
if (m_loaderType & ModPlatform::Quilt) { // falback for quilt
@ -268,7 +268,7 @@ Task::Ptr GetModDependenciesTask::prepareDependencyTask(const ModPlatform::Depen
removePack(dep.addonId);
return;
}
pDep->version.is_currently_selected = true;
pDep->version.isCurrentlySelected = true;
pDep->pack->versions = { pDep->version };
pDep->pack->versionsLoaded = true;
@ -350,7 +350,7 @@ auto GetModDependenciesTask::getExtraInfo() -> QHash<QString, PackDependencyExtr
return rby;
}
bool GetModDependenciesTask::isLocalyInstalled(std::shared_ptr<PackDependency> pDep)
bool GetModDependenciesTask::isLocalyInstalled(const std::shared_ptr<PackDependency>& pDep)
{
return pDep->version.fileName.isEmpty() ||
std::ranges::find_if(m_selected,
@ -362,13 +362,13 @@ bool GetModDependenciesTask::isLocalyInstalled(std::shared_ptr<PackDependency> p
[pDep](const QString& i) { return !i.isEmpty() && laxCompare(i, pDep->version.fileName); }) !=
m_modsFileNames.end() || // check the existing mods
std::ranges::find_if(m_packDependencies, [pDep](std::shared_ptr<PackDependency> i) {
std::ranges::find_if(m_packDependencies, [pDep](const std::shared_ptr<PackDependency>& i) {
return pDep->pack->addonId != i->pack->addonId && !i->version.fileName.isEmpty() &&
laxCompare(pDep->version.fileName, i->version.fileName);
}) != m_packDependencies.end(); // check loaded dependencies
}
bool GetModDependenciesTask::maybeInstalled(std::shared_ptr<PackDependency> pDep)
bool GetModDependenciesTask::maybeInstalled(const std::shared_ptr<PackDependency>& pDep)
{
return std::ranges::find_if(m_modsFileNames, [pDep](const QString& i) {
return !i.isEmpty() && laxCompare(i, pDep->version.fileName, true);

View file

@ -71,12 +71,12 @@ class GetModDependenciesTask : public SequentialTask {
QList<ModPlatform::Dependency> getDependenciesForVersion(const ModPlatform::IndexedVersion&,
ModPlatform::ResourceProvider providerName);
void prepare();
Task::Ptr getProjectInfoTask(std::shared_ptr<PackDependency> pDep);
Task::Ptr getProjectInfoTask(const std::shared_ptr<PackDependency>& pDep);
ModPlatform::Dependency getOverride(const ModPlatform::Dependency&, ModPlatform::ResourceProvider providerName);
void removePack(const QVariant& addonId);
bool isLocalyInstalled(const std::shared_ptr<PackDependency>& pDep);
bool isLocalyInstalled(std::shared_ptr<PackDependency> pDep);
bool maybeInstalled(std::shared_ptr<PackDependency> pDep);
bool maybeInstalled(const std::shared_ptr<PackDependency>& pDep);
private:
QList<std::shared_ptr<PackDependency>> m_packDependencies;

View file

@ -7,7 +7,6 @@
#include "Json.h"
#include "QObjectPtr.h"
#include "minecraft/mod/Mod.h"
#include "minecraft/mod/tasks/LocalResourceUpdateTask.h"
#include "modplatform/flame/FlameAPI.h"
@ -15,44 +14,47 @@
#include "modplatform/helpers/HashUtils.h"
#include "modplatform/modrinth/ModrinthAPI.h"
#include "modplatform/modrinth/ModrinthPackIndex.h"
#include "settings/SettingsObject.h"
#include "tasks/ConcurrentTask.h"
static ModrinthAPI modrinth_api;
static FlameAPI flame_api;
EnsureMetadataTask::EnsureMetadataTask(Resource* resource, QDir dir, ModPlatform::ResourceProvider prov)
: Task(), m_indexDir(dir), m_provider(prov), m_hashingTask(nullptr), m_currentTask(nullptr)
EnsureMetadataTask::EnsureMetadataTask(Resource* resource, const QDir& dir, ModPlatform::ResourceProvider prov)
: m_indexDir(dir), m_provider(prov), m_hashingTask(nullptr), m_currentTask(nullptr)
{
auto hashTask = createNewHash(resource);
if (!hashTask)
if (!hashTask) {
return;
connect(hashTask.get(), &Hashing::Hasher::resultsReady, [this, resource](QString hash) { m_resources.insert(hash, resource); });
}
connect(hashTask.get(), &Hashing::Hasher::resultsReady, [this, resource](const QString& hash) { m_resources.insert(hash, resource); });
connect(hashTask.get(), &Task::failed, [this, resource] { emitFail(resource, "", RemoveFromList::No); });
m_hashingTask = hashTask;
}
EnsureMetadataTask::EnsureMetadataTask(QList<Resource*>& resources, QDir dir, ModPlatform::ResourceProvider prov)
: Task(), m_indexDir(dir), m_provider(prov), m_currentTask(nullptr)
EnsureMetadataTask::EnsureMetadataTask(QList<Resource*>& resources, const QDir& dir, ModPlatform::ResourceProvider prov)
: m_indexDir(dir), m_provider(prov), m_currentTask(nullptr)
{
auto hashTask = makeShared<ConcurrentTask>("MakeHashesTask", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt());
m_hashingTask = hashTask;
auto cHashTask = makeShared<ConcurrentTask>("MakeHashesTask", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt());
m_hashingTask = cHashTask;
for (auto* resource : resources) {
auto hash_task = createNewHash(resource);
if (!hash_task)
auto hashTask = createNewHash(resource);
if (!hashTask) {
continue;
connect(hash_task.get(), &Hashing::Hasher::resultsReady, [this, resource](QString hash) { m_resources.insert(hash, resource); });
connect(hash_task.get(), &Task::failed, [this, resource] { emitFail(resource, "", RemoveFromList::No); });
hashTask->addTask(hash_task);
}
connect(hashTask.get(), &Hashing::Hasher::resultsReady,
[this, resource](const QString& hash) { m_resources.insert(hash, resource); });
connect(hashTask.get(), &Task::failed, [this, resource] { emitFail(resource, "", RemoveFromList::No); });
cHashTask->addTask(hashTask);
}
}
EnsureMetadataTask::EnsureMetadataTask(QHash<QString, Resource*>& resources, QDir dir, ModPlatform::ResourceProvider prov)
: Task(), m_resources(resources), m_indexDir(dir), m_provider(prov), m_currentTask(nullptr)
EnsureMetadataTask::EnsureMetadataTask(QHash<QString, Resource*>& resources, const QDir& dir, ModPlatform::ResourceProvider prov)
: m_resources(resources), m_indexDir(dir), m_provider(prov), m_currentTask(nullptr)
{}
Hashing::Hasher::Ptr EnsureMetadataTask::createNewHash(Resource* resource)
{
if (!resource || !resource->valid() || resource->type() == ResourceType::FOLDER)
if (!resource || !resource->valid() || resource->type() == ResourceType::FOLDER) {
return nullptr;
}
return Hashing::createHasher(resource->fileinfo().absoluteFilePath(), m_provider);
}
@ -63,8 +65,9 @@ QString EnsureMetadataTask::getExistingHash(Resource* resource)
// (linear on the number of mods vs. linear on the size of the mod's JAR)
auto it = m_resources.keyValueBegin();
while (it != m_resources.keyValueEnd()) {
if ((*it).second == resource)
if ((*it).second == resource) {
break;
}
it++;
}
@ -80,10 +83,11 @@ QString EnsureMetadataTask::getExistingHash(Resource* resource)
bool EnsureMetadataTask::abort()
{
// Prevent sending signals to a dead object
disconnect(this, 0, 0, 0);
QObject::disconnect(this, nullptr, nullptr, nullptr);
if (m_currentTask)
if (m_currentTask) {
return m_currentTask->abort();
}
return true;
}
@ -111,70 +115,74 @@ void EnsureMetadataTask::executeTask()
}
}
Task::Ptr version_task;
Task::Ptr versionTask;
switch (m_provider) {
case (ModPlatform::ResourceProvider::MODRINTH):
version_task = modrinthVersionsTask();
versionTask = modrinthVersionsTask();
break;
case (ModPlatform::ResourceProvider::FLAME):
version_task = flameVersionsTask();
versionTask = flameVersionsTask();
break;
}
auto invalidade_leftover = [this] {
for (auto resource = m_resources.constBegin(); resource != m_resources.constEnd(); resource++)
auto invalidadeLeftover = [this] {
for (auto resource = m_resources.constBegin(); resource != m_resources.constEnd(); resource++) {
emitFail(resource.value(), resource.key(), RemoveFromList::No);
}
m_resources.clear();
emitSucceeded();
};
connect(version_task.get(), &Task::finished, this, [this, invalidade_leftover] {
Task::Ptr project_task;
connect(versionTask.get(), &Task::finished, this, [this, invalidadeLeftover] {
Task::Ptr projectTask;
switch (m_provider) {
case (ModPlatform::ResourceProvider::MODRINTH):
project_task = modrinthProjectsTask();
projectTask = modrinthProjectsTask();
break;
case (ModPlatform::ResourceProvider::FLAME):
project_task = flameProjectsTask();
projectTask = flameProjectsTask();
break;
}
if (!project_task) {
invalidade_leftover();
if (!projectTask) {
invalidadeLeftover();
return;
}
connect(project_task.get(), &Task::finished, this, [this, invalidade_leftover, project_task] {
invalidade_leftover();
project_task->deleteLater();
if (m_currentTask)
connect(projectTask.get(), &Task::finished, this, [this, invalidadeLeftover, projectTask] {
invalidadeLeftover();
projectTask->deleteLater();
if (m_currentTask) {
m_currentTask.reset();
}
});
connect(project_task.get(), &Task::failed, this, &EnsureMetadataTask::emitFailed);
connect(projectTask.get(), &Task::failed, this, &EnsureMetadataTask::emitFailed);
m_currentTask = project_task;
project_task->start();
m_currentTask = projectTask;
projectTask->start();
});
if (m_resources.size() > 1)
if (m_resources.size() > 1) {
setStatus(tr("Requesting metadata information from %1...").arg(ModPlatform::ProviderCapabilities::readableName(m_provider)));
else if (!m_resources.empty())
} else if (!m_resources.empty()) {
setStatus(tr("Requesting metadata information from %1 for '%2'...")
.arg(ModPlatform::ProviderCapabilities::readableName(m_provider), m_resources.begin().value()->name()));
}
m_currentTask = version_task;
version_task->start();
m_currentTask = versionTask;
versionTask->start();
}
void EnsureMetadataTask::emitReady(Resource* resource, QString key, RemoveFromList remove)
{
if (!resource) {
qCritical() << "Tried to mark a null resource as ready.";
if (!key.isEmpty())
if (!key.isEmpty()) {
m_resources.remove(key);
}
return;
}
@ -183,8 +191,9 @@ void EnsureMetadataTask::emitReady(Resource* resource, QString key, RemoveFromLi
emit metadataReady(resource);
if (remove == RemoveFromList::Yes) {
if (key.isEmpty())
if (key.isEmpty()) {
key = getExistingHash(resource);
}
m_resources.remove(key);
}
}
@ -193,8 +202,9 @@ void EnsureMetadataTask::emitFail(Resource* resource, QString key, RemoveFromLis
{
if (!resource) {
qCritical() << "Tried to mark a null resource as failed.";
if (!key.isEmpty())
if (!key.isEmpty()) {
m_resources.remove(key);
}
return;
}
@ -203,8 +213,9 @@ void EnsureMetadataTask::emitFail(Resource* resource, QString key, RemoveFromLis
emit metadataFailed(resource);
if (remove == RemoveFromList::Yes) {
if (key.isEmpty())
if (key.isEmpty()) {
key = getExistingHash(resource);
}
m_resources.remove(key);
}
}
@ -213,30 +224,31 @@ void EnsureMetadataTask::emitFail(Resource* resource, QString key, RemoveFromLis
Task::Ptr EnsureMetadataTask::modrinthVersionsTask()
{
auto hash_type = ModPlatform::ProviderCapabilities::hashType(ModPlatform::ResourceProvider::MODRINTH).first();
auto hashType = ModPlatform::ProviderCapabilities::hashType(ModPlatform::ResourceProvider::MODRINTH).first();
auto [ver_task, response] = modrinth_api.currentVersions(m_resources.keys(), hash_type);
auto [verTask, response] = ModrinthAPI::currentVersions(m_resources.keys(), hashType);
// Prevents unfortunate timings when aborting the task
if (!ver_task)
if (!verTask) {
return Task::Ptr{ nullptr };
}
connect(ver_task.get(), &Task::succeeded, this, [this, response] {
QJsonParseError parse_error{};
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
if (parse_error.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response from Modrinth::CurrentVersions at" << parse_error.offset
<< "reason:" << parse_error.errorString();
connect(verTask.get(), &Task::succeeded, this, [this, response] {
QJsonParseError parseError{};
const QJsonDocument doc = QJsonDocument::fromJson(*response, &parseError);
if (parseError.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response from Modrinth::CurrentVersions at" << parseError.offset
<< "reason:" << parseError.errorString();
qWarning() << *response;
failed(parse_error.errorString());
failed(parseError.errorString());
return;
}
try {
auto entries = Json::requireObject(doc);
for (auto& hash : m_resources.keys()) {
auto resource = m_resources.find(hash).value();
auto* resource = m_resources.find(hash).value();
try {
auto entry = Json::requireObject(entries, hash);
@ -257,36 +269,38 @@ Task::Ptr EnsureMetadataTask::modrinthVersionsTask()
}
});
return ver_task;
return verTask;
}
Task::Ptr EnsureMetadataTask::modrinthProjectsTask()
{
QHash<QString, QString> addonIds;
for (const auto& data : m_tempVersions)
for (const auto& data : m_tempVersions) {
addonIds.insert(data.addonId.toString(), data.hash);
}
Task::Ptr proj_task;
QByteArray* response;
Task::Ptr projTask;
QByteArray* response = nullptr;
if (addonIds.isEmpty()) {
qWarning() << "No addonId found!";
} else if (addonIds.size() == 1) {
std::tie(proj_task, response) = modrinth_api.getProject(*addonIds.keyBegin());
std::tie(projTask, response) = ModrinthAPI().getProject(*addonIds.keyBegin());
} else {
std::tie(proj_task, response) = modrinth_api.getProjects(addonIds.keys());
std::tie(projTask, response) = ModrinthAPI().getProjects(addonIds.keys());
}
// Prevents unfortunate timings when aborting the task
if (!proj_task)
if (!projTask) {
return Task::Ptr{ nullptr };
}
connect(proj_task.get(), &Task::succeeded, this, [this, response, addonIds] {
QJsonParseError parse_error{};
auto doc = QJsonDocument::fromJson(*response, &parse_error);
if (parse_error.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response from Modrinth projects task at" << parse_error.offset
<< "reason:" << parse_error.errorString();
connect(projTask.get(), &Task::succeeded, this, [this, response, addonIds] {
QJsonParseError parseError{};
auto doc = QJsonDocument::fromJson(*response, &parseError);
if (parseError.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response from Modrinth projects task at" << parseError.offset
<< "reason:" << parseError.errorString();
qWarning() << *response;
return;
}
@ -294,10 +308,11 @@ Task::Ptr EnsureMetadataTask::modrinthProjectsTask()
QJsonArray entries;
try {
if (addonIds.size() == 1)
if (addonIds.size() == 1) {
entries = { doc.object() };
else
} else {
entries = Json::requireArray(doc);
}
} catch (Json::JsonException& e) {
qDebug() << e.cause();
qDebug() << doc;
@ -307,9 +322,9 @@ Task::Ptr EnsureMetadataTask::modrinthProjectsTask()
ModPlatform::IndexedPack pack;
try {
auto entry_obj = Json::requireObject(entry);
auto entryObj = Json::requireObject(entry);
Modrinth::loadIndexedPack(pack, entry_obj);
Modrinth::loadIndexedPack(pack, entryObj);
} catch (Json::JsonException& e) {
qDebug() << e.cause();
qDebug() << doc;
@ -320,13 +335,13 @@ Task::Ptr EnsureMetadataTask::modrinthProjectsTask()
auto hash = addonIds.find(pack.addonId.toString()).value();
auto resource_iter = m_resources.find(hash);
if (resource_iter == m_resources.end()) {
auto resourceIter = m_resources.find(hash);
if (resourceIter == m_resources.end()) {
qWarning() << "Invalid project id from the API response.";
continue;
}
auto* resource = resource_iter.value();
auto* resource = resourceIter.value();
setStatus(tr("Parsing API response from Modrinth for '%1'...").arg(resource->name()));
@ -334,7 +349,7 @@ Task::Ptr EnsureMetadataTask::modrinthProjectsTask()
}
});
return proj_task;
return projTask;
}
// Flame
@ -345,42 +360,42 @@ Task::Ptr EnsureMetadataTask::flameVersionsTask()
fingerprints.push_back(murmur.toUInt());
}
auto [ver_task, response] = flame_api.matchFingerprints(fingerprints);
auto [verTask, response] = FlameAPI::matchFingerprints(fingerprints);
connect(ver_task.get(), &Task::succeeded, this, [this, response] {
QJsonParseError parse_error{};
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
if (parse_error.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response from Flame::CurrentVersions at" << parse_error.offset
<< "reason:" << parse_error.errorString();
connect(verTask.get(), &Task::succeeded, this, [this, response] {
QJsonParseError parseError{};
const QJsonDocument doc = QJsonDocument::fromJson(*response, &parseError);
if (parseError.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response from Flame::CurrentVersions at" << parseError.offset
<< "reason:" << parseError.errorString();
qWarning() << *response;
failed(parse_error.errorString());
failed(parseError.errorString());
return;
}
try {
auto doc_obj = Json::requireObject(doc);
auto data_obj = Json::requireObject(doc_obj, "data");
auto data_arr = Json::requireArray(data_obj, "exactMatches");
auto docObj = Json::requireObject(doc);
auto dataObj = Json::requireObject(docObj, "data");
auto dataArr = Json::requireArray(dataObj, "exactMatches");
if (data_arr.isEmpty()) {
if (dataArr.isEmpty()) {
qWarning() << "No matches found for fingerprint search!";
return;
}
for (auto match : data_arr) {
auto match_obj = match.toObject();
auto file_obj = match_obj["file"].toObject();
for (auto match : dataArr) {
auto matchObj = match.toObject();
auto fileObj = matchObj["file"].toObject();
if (match_obj.isEmpty() || file_obj.isEmpty()) {
if (matchObj.isEmpty() || fileObj.isEmpty()) {
qWarning() << "Fingerprint match is empty!";
return;
}
auto fingerprint = QString::number(file_obj["fileFingerprint"].toInteger());
auto fingerprint = QString::number(fileObj["fileFingerprint"].toInteger());
auto resource = m_resources.find(fingerprint);
if (resource == m_resources.end()) {
qWarning() << "Invalid fingerprint from the API response.";
@ -389,7 +404,7 @@ Task::Ptr EnsureMetadataTask::flameVersionsTask()
setStatus(tr("Parsing API response from CurseForge for '%1'...").arg((*resource)->name()));
m_tempVersions.insert(fingerprint, FlameMod::loadIndexedPackVersion(file_obj));
m_tempVersions.insert(fingerprint, FlameMod::loadIndexedPackVersion(fileObj));
}
} catch (Json::JsonException& e) {
@ -398,7 +413,7 @@ Task::Ptr EnsureMetadataTask::flameVersionsTask()
}
});
return ver_task;
return verTask;
}
Task::Ptr EnsureMetadataTask::flameProjectsTask()
@ -408,56 +423,59 @@ Task::Ptr EnsureMetadataTask::flameProjectsTask()
if (m_tempVersions.contains(hash)) {
auto data = m_tempVersions.find(hash).value();
auto id_str = data.addonId.toString();
if (!id_str.isEmpty())
auto idStr = data.addonId.toString();
if (!idStr.isEmpty()) {
addonIds.insert(data.addonId.toString(), hash);
}
}
}
Task::Ptr proj_task;
QByteArray* response;
Task::Ptr projTask;
QByteArray* response = nullptr;
if (addonIds.isEmpty()) {
qWarning() << "No addonId found!";
} else if (addonIds.size() == 1) {
std::tie(proj_task, response) = flame_api.getProject(*addonIds.keyBegin());
std::tie(projTask, response) = FlameAPI().getProject(*addonIds.keyBegin());
} else {
std::tie(proj_task, response) = flame_api.getProjects(addonIds.keys());
std::tie(projTask, response) = FlameAPI().getProjects(addonIds.keys());
}
// Prevents unfortunate timings when aborting the task
if (!proj_task)
if (!projTask) {
return Task::Ptr{ nullptr };
}
connect(proj_task.get(), &Task::succeeded, this, [this, response, addonIds] {
QJsonParseError parse_error{};
auto doc = QJsonDocument::fromJson(*response, &parse_error);
if (parse_error.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response from Flame projects task at" << parse_error.offset
<< "reason:" << parse_error.errorString();
connect(projTask.get(), &Task::succeeded, this, [this, response, addonIds] {
QJsonParseError parseError{};
auto doc = QJsonDocument::fromJson(*response, &parseError);
if (parseError.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response from Flame projects task at" << parseError.offset
<< "reason:" << parseError.errorString();
qWarning() << *response;
return;
}
try {
QJsonArray entries;
if (addonIds.size() == 1)
if (addonIds.size() == 1) {
entries = { Json::requireObject(Json::requireObject(doc), "data") };
else
} else {
entries = Json::requireArray(Json::requireObject(doc), "data");
}
for (auto entry : entries) {
auto entry_obj = Json::requireObject(entry);
auto entryObj = Json::requireObject(entry);
auto id = QString::number(Json::requireInteger(entry_obj, "id"));
auto id = QString::number(Json::requireInteger(entryObj, "id"));
auto hash = addonIds.find(id).value();
auto resource = m_resources.find(hash).value();
auto* resource = m_resources.find(hash).value();
ModPlatform::IndexedPack pack;
try {
setStatus(tr("Parsing API response from CurseForge for '%1'...").arg(resource->name()));
FlameMod::loadIndexedPack(pack, entry_obj);
FlameMod::loadIndexedPack(pack, entryObj);
} catch (Json::JsonException& e) {
qDebug() << e.cause();
@ -473,7 +491,7 @@ Task::Ptr EnsureMetadataTask::flameProjectsTask()
}
});
return proj_task;
return projTask;
}
void EnsureMetadataTask::updateMetadata(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& ver, Resource* resource)
@ -481,8 +499,9 @@ void EnsureMetadataTask::updateMetadata(ModPlatform::IndexedPack& pack, ModPlatf
try {
// Prevent file name mismatch
ver.fileName = resource->fileinfo().fileName();
if (ver.fileName.endsWith(".disabled"))
if (ver.fileName.endsWith(".disabled")) {
ver.fileName.chop(9);
}
auto task = makeShared<LocalResourceUpdateTask>(m_indexDir, pack, ver);

View file

@ -1,25 +1,22 @@
#pragma once
#include "ModIndex.h"
#include "net/NetJob.h"
#include "modplatform/helpers/HashUtils.h"
#include "minecraft/mod/Resource.h"
#include "tasks/ConcurrentTask.h"
#include <QDir>
class Mod;
class QDir;
#include "minecraft/mod/Resource.h"
class EnsureMetadataTask : public Task {
Q_OBJECT
public:
EnsureMetadataTask(Resource*, QDir, ModPlatform::ResourceProvider = ModPlatform::ResourceProvider::MODRINTH);
EnsureMetadataTask(QList<Resource*>&, QDir, ModPlatform::ResourceProvider = ModPlatform::ResourceProvider::MODRINTH);
EnsureMetadataTask(QHash<QString, Resource*>&, QDir, ModPlatform::ResourceProvider = ModPlatform::ResourceProvider::MODRINTH);
EnsureMetadataTask(Resource*, const QDir&, ModPlatform::ResourceProvider = ModPlatform::ResourceProvider::MODRINTH);
EnsureMetadataTask(QList<Resource*>&, const QDir&, ModPlatform::ResourceProvider = ModPlatform::ResourceProvider::MODRINTH);
EnsureMetadataTask(QHash<QString, Resource*>&, const QDir&, ModPlatform::ResourceProvider = ModPlatform::ResourceProvider::MODRINTH);
~EnsureMetadataTask() = default;
~EnsureMetadataTask() override = default;
Task::Ptr getHashingTask() { return m_hashingTask; }
@ -37,7 +34,7 @@ class EnsureMetadataTask : public Task {
Task::Ptr flameProjectsTask();
// Helpers
enum class RemoveFromList { Yes, No };
enum class RemoveFromList : std::uint8_t { Yes, No };
void emitReady(Resource*, QString key = {}, RemoveFromList = RemoveFromList::Yes);
void emitFail(Resource*, QString key = {}, RemoveFromList = RemoveFromList::Yes);

View file

@ -113,27 +113,27 @@ struct IndexedVersion {
QVariant addonId;
QVariant fileId;
QString version;
QString version_number;
IndexedVersionType version_type;
QString versionNumber;
IndexedVersionType versionType;
QStringList mcVersion;
QString downloadUrl;
QString date;
QString fileName;
ModLoaderTypes loaders;
QString hash_type;
QString hashType;
QString hash;
bool is_preferred = true;
bool isPreferred = true;
QString changelog;
QList<Dependency> dependencies;
Side side = Side::NoSide; // this is for flame API
// For internal use, not provided by APIs
bool is_currently_selected = false;
bool isCurrentlySelected = false;
QString getVersionDisplayString() const
{
auto release_type = version_type.isValid() ? QString(" [%1]").arg(version_type.toString()) : "";
auto versionStr = !version.contains(version_number) ? version_number : "";
auto releaseType = versionType.isValid() ? QString(" [%1]").arg(versionType.toString()) : "";
auto versionStr = !version.contains(versionNumber) ? versionNumber : "";
QString gameVersion = "";
for (const auto& v : mcVersion) {
if (version.contains(v)) {
@ -144,7 +144,7 @@ struct IndexedVersion {
gameVersion = QObject::tr(" for %1").arg(v);
}
}
return QString("%1%2 — %3%4").arg(version, gameVersion, versionStr, release_type);
return QString("%1%2 — %3%4").arg(version, gameVersion, versionStr, releaseType);
}
};
@ -191,7 +191,7 @@ struct IndexedPack {
return false;
}
return versions.at(index).is_currently_selected;
return versions.at(index).isCurrentlySelected;
}
bool isAnyVersionSelected() const
{
@ -199,7 +199,7 @@ struct IndexedPack {
return false;
}
return std::any_of(versions.constBegin(), versions.constEnd(), [](const auto& v) { return v.is_currently_selected; });
return std::any_of(versions.constBegin(), versions.constEnd(), [](const auto& v) { return v.isCurrentlySelected; });
}
};

View file

@ -1,5 +1,7 @@
#include "modplatform/ResourceAPI.h"
#include <algorithm>
#include "Application.h"
#include "Json.h"
#include "net/NetJob.h"
@ -8,30 +10,30 @@
#include "net/ApiDownload.h"
Task::Ptr ResourceAPI::searchProjects(SearchArgs&& args, Callback<QList<ModPlatform::IndexedPack::Ptr>>&& callbacks) const
Task::Ptr ResourceAPI::searchProjects(const SearchArgs& args, const Callback<QList<ModPlatform::IndexedPack::Ptr>>& callbacks) const
{
auto search_url_optional = getSearchURL(args);
if (!search_url_optional.has_value()) {
callbacks.on_fail("Failed to create search URL", -1);
auto searchUrlOptional = getSearchURL(args);
if (!searchUrlOptional.has_value()) {
callbacks.onFail("Failed to create search URL", -1);
return nullptr;
}
auto search_url = search_url_optional.value();
const auto& searchUrl = searchUrlOptional.value();
auto netJob = makeShared<NetJob>(QString("%1::Search").arg(debugName()), APPLICATION->network());
auto [action, response] = Net::ApiDownload::makeByteArray(QUrl(search_url));
auto [action, response] = Net::ApiDownload::makeByteArray(QUrl(searchUrl));
netJob->addNetAction(action);
QObject::connect(netJob.get(), &NetJob::succeeded, [this, response, callbacks] {
QJsonParseError parse_error{};
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
if (parse_error.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response from" << debugName() << "at" << parse_error.offset
<< "reason:" << parse_error.errorString();
QJsonParseError parseError{};
QJsonDocument doc = QJsonDocument::fromJson(*response, &parseError);
if (parseError.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response from" << debugName() << "at" << parseError.offset
<< "reason:" << parseError.errorString();
qWarning() << *response;
callbacks.on_fail(parse_error.errorString(), -1);
callbacks.onFail(parseError.errorString(), -1);
return;
}
@ -52,7 +54,7 @@ Task::Ptr ResourceAPI::searchProjects(SearchArgs&& args, Callback<QList<ModPlatf
}
}
callbacks.on_succeed(newList);
callbacks.onSucceed(newList);
});
// Capture a weak_ptr instead of a shared_ptr to avoid circular dependency issues.
@ -60,40 +62,44 @@ Task::Ptr ResourceAPI::searchProjects(SearchArgs&& args, Callback<QList<ModPlatf
// as it only temporarily locks the resource when needed.
auto weak = netJob.toWeakRef();
QObject::connect(netJob.get(), &NetJob::failed, [weak, callbacks](const QString& reason) {
int network_error_code = -1;
int networkErrorCode = -1;
if (auto netJob = weak.lock()) {
if (auto* failed_action = netJob->getFailedActions().at(0); failed_action)
network_error_code = failed_action->replyStatusCode();
if (auto* failedAction = netJob->getFailedActions().at(0); failedAction) {
networkErrorCode = failedAction->replyStatusCode();
}
}
callbacks.on_fail(reason, network_error_code);
callbacks.onFail(reason, networkErrorCode);
});
QObject::connect(netJob.get(), &NetJob::aborted, [callbacks] {
if (callbacks.on_abort != nullptr)
callbacks.on_abort();
if (callbacks.onAbort != nullptr) {
callbacks.onAbort();
}
});
return netJob;
}
Task::Ptr ResourceAPI::getProjectVersions(VersionSearchArgs&& args, Callback<QVector<ModPlatform::IndexedVersion>>&& callbacks) const
Task::Ptr ResourceAPI::getProjectVersions(const VersionSearchArgs& args,
const Callback<QVector<ModPlatform::IndexedVersion>>& callbacks) const
{
auto versions_url_optional = getVersionsURL(args);
if (!versions_url_optional.has_value())
auto versionsUrlOptional = getVersionsURL(args);
if (!versionsUrlOptional.has_value()) {
return nullptr;
}
auto versions_url = versions_url_optional.value();
const auto& versionsUrl = versionsUrlOptional.value();
auto netJob = makeShared<NetJob>(QString("%1::Versions").arg(args.pack->name), APPLICATION->network());
auto [action, response] = Net::ApiDownload::makeByteArray(versions_url);
auto [action, response] = Net::ApiDownload::makeByteArray(versionsUrl);
netJob->addNetAction(action);
QObject::connect(netJob.get(), &NetJob::succeeded, [this, response, callbacks, args] {
QJsonParseError parse_error{};
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
if (parse_error.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response for getting versions at" << parse_error.offset
<< "reason:" << parse_error.errorString();
QJsonParseError parseError{};
QJsonDocument doc = QJsonDocument::fromJson(*response, &parseError);
if (parseError.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response for getting versions at" << parseError.offset
<< "reason:" << parseError.errorString();
qWarning() << *response;
return;
}
@ -119,13 +125,13 @@ Task::Ptr ResourceAPI::getProjectVersions(VersionSearchArgs&& args, Callback<QVe
// dates are in RFC 3339 format
return a.date > b.date;
};
std::sort(unsortedVersions.begin(), unsortedVersions.end(), orderSortPredicate);
std::ranges::sort(unsortedVersions, orderSortPredicate);
} catch (const JSONValidationError& e) {
qDebug() << doc;
qWarning() << "Error while reading" << debugName() << "resource version:" << e.cause();
}
callbacks.on_succeed(unsortedVersions);
callbacks.onSucceed(unsortedVersions);
});
// Capture a weak_ptr instead of a shared_ptr to avoid circular dependency issues.
@ -133,87 +139,93 @@ Task::Ptr ResourceAPI::getProjectVersions(VersionSearchArgs&& args, Callback<QVe
// as it only temporarily locks the resource when needed.
auto weak = netJob.toWeakRef();
QObject::connect(netJob.get(), &NetJob::failed, [weak, callbacks](const QString& reason) {
int network_error_code = -1;
int networkErrorCode = -1;
if (auto netJob = weak.lock()) {
if (auto* failed_action = netJob->getFailedActions().at(0); failed_action)
network_error_code = failed_action->replyStatusCode();
if (auto* failedAction = netJob->getFailedActions().at(0); failedAction) {
networkErrorCode = failedAction->replyStatusCode();
}
}
callbacks.on_fail(reason, network_error_code);
callbacks.onFail(reason, networkErrorCode);
});
QObject::connect(netJob.get(), &NetJob::aborted, [callbacks] {
if (callbacks.on_abort != nullptr)
callbacks.on_abort();
if (callbacks.onAbort != nullptr) {
callbacks.onAbort();
}
});
return netJob;
}
Task::Ptr ResourceAPI::getProjectInfo(ProjectInfoArgs&& args, Callback<ModPlatform::IndexedPack::Ptr>&& callbacks, bool askRetry) const
Task::Ptr ResourceAPI::getProjectInfo(const ProjectInfoArgs& args,
const Callback<ModPlatform::IndexedPack::Ptr>& callbacks,
bool askRetry) const
{
auto [job, response] = getProject(args.pack->addonId.toString(), askRetry);
QObject::connect(job.get(), &NetJob::succeeded, [this, response, callbacks, args] {
auto pack = args.pack;
QJsonParseError parse_error{};
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
if (parse_error.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response for mod info at" << parse_error.offset
<< "reason:" << parse_error.errorString();
QJsonParseError parseError{};
QJsonDocument doc = QJsonDocument::fromJson(*response, &parseError);
if (parseError.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response for mod info at" << parseError.offset << "reason:" << parseError.errorString();
qWarning() << *response;
return;
}
try {
auto obj = Json::requireObject(doc);
if (obj.contains("data"))
if (obj.contains("data")) {
obj = Json::requireObject(obj, "data");
}
loadIndexedPack(*pack, obj);
loadExtraPackInfo(*pack, obj);
} catch (const JSONValidationError& e) {
qDebug() << doc;
qWarning() << "Error while reading" << debugName() << "resource info:" << e.cause();
}
callbacks.on_succeed(pack);
callbacks.onSucceed(pack);
});
// Capture a weak_ptr instead of a shared_ptr to avoid circular dependency issues.
// This prevents the lambda from extending the lifetime of the shared resource,
// as it only temporarily locks the resource when needed.
auto weak = job.toWeakRef();
QObject::connect(job.get(), &NetJob::failed, [weak, callbacks](const QString& reason) {
int network_error_code = -1;
int networkErrorCode = -1;
if (auto job = weak.lock()) {
if (auto netJob = qSharedPointerDynamicCast<NetJob>(job)) {
if (auto* failed_action = netJob->getFailedActions().at(0); failed_action) {
network_error_code = failed_action->replyStatusCode();
if (auto* failedAction = netJob->getFailedActions().at(0); failedAction) {
networkErrorCode = failedAction->replyStatusCode();
}
}
}
callbacks.on_fail(reason, network_error_code);
callbacks.onFail(reason, networkErrorCode);
});
QObject::connect(job.get(), &NetJob::aborted, [callbacks] {
if (callbacks.on_abort != nullptr)
callbacks.on_abort();
if (callbacks.onAbort != nullptr) {
callbacks.onAbort();
}
});
return job;
}
Task::Ptr ResourceAPI::getDependencyVersion(DependencySearchArgs&& args, Callback<ModPlatform::IndexedVersion>&& callbacks) const
Task::Ptr ResourceAPI::getDependencyVersion(const DependencySearchArgs& args, const Callback<ModPlatform::IndexedVersion>& callbacks) const
{
auto versions_url_optional = getDependencyURL(args);
if (!versions_url_optional.has_value())
auto versionsUrlOptional = getDependencyURL(args);
if (!versionsUrlOptional.has_value()) {
return nullptr;
}
auto versions_url = versions_url_optional.value();
const auto& versionsUrl = versionsUrlOptional.value();
auto netJob = makeShared<NetJob>(QString("%1::Dependency").arg(args.dependency.addonId.toString()), APPLICATION->network());
auto [action, response] = Net::ApiDownload::makeByteArray(versions_url);
auto [action, response] = Net::ApiDownload::makeByteArray(versionsUrl);
netJob->addNetAction(action);
QObject::connect(netJob.get(), &NetJob::succeeded, [this, response, callbacks, args] {
QJsonParseError parse_error{};
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
if (parse_error.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response for getting dependency version at" << parse_error.offset
<< "reason:" << parse_error.errorString();
QJsonParseError parseError{};
QJsonDocument doc = QJsonDocument::fromJson(*response, &parseError);
if (parseError.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response for getting dependency version at" << parseError.offset
<< "reason:" << parseError.errorString();
qWarning() << *response;
return;
}
@ -230,21 +242,23 @@ Task::Ptr ResourceAPI::getDependencyVersion(DependencySearchArgs&& args, Callbac
auto obj = versionIter.toObject();
auto file = loadIndexedPackVersion(obj, ModPlatform::ResourceType::Mod);
if (!file.addonId.isValid())
if (!file.addonId.isValid()) {
file.addonId = args.dependency.addonId;
}
if (file.fileId.isValid() &&
(!file.loaders || args.loader & file.loaders)) // Heuristic to check if the returned value is valid
(!file.loaders || args.loader & file.loaders)) { // Heuristic to check if the returned value is valid
versions.append(file);
}
}
auto orderSortPredicate = [](const ModPlatform::IndexedVersion& a, const ModPlatform::IndexedVersion& b) -> bool {
// dates are in RFC 3339 format
return a.date > b.date;
};
std::sort(versions.begin(), versions.end(), orderSortPredicate);
std::ranges::sort(versions, orderSortPredicate);
auto bestMatch = versions.size() != 0 ? versions.front() : ModPlatform::IndexedVersion();
callbacks.on_succeed(bestMatch);
callbacks.onSucceed(bestMatch);
});
// Capture a weak_ptr instead of a shared_ptr to avoid circular dependency issues.
@ -252,50 +266,52 @@ Task::Ptr ResourceAPI::getDependencyVersion(DependencySearchArgs&& args, Callbac
// as it only temporarily locks the resource when needed.
auto weak = netJob.toWeakRef();
QObject::connect(netJob.get(), &NetJob::failed, [weak, callbacks](const QString& reason) {
int network_error_code = -1;
int networkErrorCode = -1;
if (auto netJob = weak.lock()) {
if (auto* failed_action = netJob->getFailedActions().at(0); failed_action)
network_error_code = failed_action->replyStatusCode();
if (auto* failedAction = netJob->getFailedActions().at(0); failedAction) {
networkErrorCode = failedAction->replyStatusCode();
}
}
callbacks.on_fail(reason, network_error_code);
callbacks.onFail(reason, networkErrorCode);
});
return netJob;
}
QString ResourceAPI::getGameVersionsString(std::vector<Version> mcVersions) const
QString ResourceAPI::getGameVersionsString(const std::vector<Version>& mcVersions)
{
QString s;
for (auto& ver : mcVersions) {
for (const auto& ver : mcVersions) {
s += QString("\"%1\",").arg(mapMCVersionToModrinth(ver));
}
s.remove(s.length() - 1, 1); // remove last comma
return s;
}
QString ResourceAPI::mapMCVersionToModrinth(Version v) const
QString ResourceAPI::mapMCVersionToModrinth(const Version& v)
{
static const QString preString = " Pre-Release ";
static const QString s_preString = " Pre-Release ";
auto verStr = v.toString();
if (verStr.contains(preString)) {
verStr.replace(preString, "-pre");
if (verStr.contains(s_preString)) {
verStr.replace(s_preString, "-pre");
}
verStr.replace(" ", "-");
return verStr;
}
std::pair<Task::Ptr, QByteArray*> ResourceAPI::getProject(QString addonId, bool askRetry) const
std::pair<Task::Ptr, QByteArray*> ResourceAPI::getProject(const QString& addonId, bool askRetry) const
{
auto project_url_optional = getInfoURL(addonId);
if (!project_url_optional.has_value())
auto projectUrlOptional = getInfoURL(addonId);
if (!projectUrlOptional.has_value()) {
return { nullptr, nullptr };
}
auto project_url = project_url_optional.value();
const auto& projectUrl = projectUrlOptional.value();
auto netJob = makeShared<NetJob>(QString("%1::GetProject").arg(addonId), APPLICATION->network());
netJob->setAskRetry(askRetry);
auto [action, response] = Net::ApiDownload::makeByteArray(QUrl(project_url));
auto [action, response] = Net::ApiDownload::makeByteArray(QUrl(projectUrl));
netJob->addNetAction(action);
return { netJob, response };

View file

@ -42,7 +42,6 @@
#include <QList>
#include <QString>
#include <list>
#include <optional>
#include <utility>
@ -65,14 +64,14 @@ class ResourceAPI {
// Used by Modrinth in the API request.
QString name;
// The human-readable name of the sorting, used for display in the UI.
QString readable_name;
QString readableName;
};
template <typename T>
struct Callback {
std::function<void(T&)> on_succeed;
std::function<void(const QString& reason, int network_error_code)> on_fail;
std::function<void()> on_abort;
std::function<void(T&)> onSucceed;
std::function<void(const QString& reason, int networkErrorCode)> onFail;
std::function<void()> onAbort;
};
struct SearchArgs {
@ -113,21 +112,21 @@ class ResourceAPI {
virtual auto getSortingMethods() const -> QList<SortingMethod> = 0;
public slots:
virtual Task::Ptr searchProjects(SearchArgs&&, Callback<QList<ModPlatform::IndexedPack::Ptr>>&&) const;
virtual Task::Ptr searchProjects(const SearchArgs&, const Callback<QList<ModPlatform::IndexedPack::Ptr>>&) const;
virtual std::pair<Task::Ptr, QByteArray*> getProject(QString addonId, bool askRetry = true) const;
virtual std::pair<Task::Ptr, QByteArray*> getProject(const QString& addonId, bool askRetry = true) const;
virtual std::pair<Task::Ptr, QByteArray*> getProjects(QStringList addonIds) const = 0;
virtual Task::Ptr getProjectInfo(ProjectInfoArgs&&, Callback<ModPlatform::IndexedPack::Ptr>&&, bool askRetry = true) const;
Task::Ptr getProjectVersions(VersionSearchArgs&& args, Callback<QVector<ModPlatform::IndexedVersion>>&& callbacks) const;
virtual Task::Ptr getDependencyVersion(DependencySearchArgs&&, Callback<ModPlatform::IndexedVersion>&&) const;
virtual Task::Ptr getProjectInfo(const ProjectInfoArgs&, const Callback<ModPlatform::IndexedPack::Ptr>&, bool askRetry = true) const;
Task::Ptr getProjectVersions(const VersionSearchArgs& args, const Callback<QVector<ModPlatform::IndexedVersion>>& callbacks) const;
virtual Task::Ptr getDependencyVersion(const DependencySearchArgs&, const Callback<ModPlatform::IndexedVersion>&) const;
protected:
inline QString debugName() const { return "External resource API"; }
virtual QString debugName() const { return "External resource API"; }
QString mapMCVersionToModrinth(Version v) const;
static QString mapMCVersionToModrinth(const Version& v);
QString getGameVersionsString(std::vector<Version> mcVersions) const;
static QString getGameVersionsString(const std::vector<Version>& mcVersions);
public:
virtual auto getSearchURL(const SearchArgs& args) const -> std::optional<QString> = 0;

View file

@ -22,7 +22,7 @@
#include "ResourceType.h"
namespace ModPlatform {
static const QMap<ResourceType, QString> s_packedTypeNames = { { ResourceType::ResourcePack, QObject::tr("resource pack") },
static const QMap<ResourceType, QString> g_packedTypeNames = { { ResourceType::ResourcePack, QObject::tr("resource pack") },
{ ResourceType::TexturePack, QObject::tr("texture pack") },
{ ResourceType::DataPack, QObject::tr("data pack") },
{ ResourceType::ShaderPack, QObject::tr("shader pack") },
@ -34,7 +34,7 @@ namespace ResourceTypeUtils {
QString getName(ResourceType type)
{
return s_packedTypeNames.constFind(type).value();
return g_packedTypeNames.constFind(type).value();
}
} // namespace ResourceTypeUtils

View file

@ -32,8 +32,8 @@ namespace ModPlatform {
enum class ResourceType : std::uint8_t { Mod, ResourcePack, ShaderPack, Modpack, DataPack, World, Screenshots, TexturePack, Unknown };
namespace ResourceTypeUtils {
static const std::set<ResourceType> VALID_RESOURCES = { ResourceType::DataPack, ResourceType::ResourcePack, ResourceType::TexturePack,
ResourceType::ShaderPack, ResourceType::World, ResourceType::Mod };
static const std::set<ResourceType> g_VALID_RESOURCES = { ResourceType::DataPack, ResourceType::ResourcePack, ResourceType::TexturePack,
ResourceType::ShaderPack, ResourceType::World, ResourceType::Mod };
QString getName(ResourceType type);
} // namespace ResourceTypeUtils
} // namespace ModPlatform

View file

@ -18,6 +18,7 @@
#include "FileResolvingTask.h"
#include <algorithm>
#include <utility>
#include "Json.h"
#include "modplatform/ModIndex.h"
@ -27,13 +28,11 @@
#include "modplatform/modrinth/ModrinthPackIndex.h"
#include "net/NetJob.h"
#include "settings/SettingsObject.h"
#include "tasks/Task.h"
#include "Application.h"
static const FlameAPI flameAPI;
static ModrinthAPI modrinthAPI;
Flame::FileResolvingTask::FileResolvingTask(Flame::Manifest& toProcess) : m_manifest(toProcess) {}
bool Flame::FileResolvingTask::abort()
@ -55,32 +54,32 @@ void Flame::FileResolvingTask::executeTask()
setProgress(0, 3);
QStringList fileIds;
for (auto file : m_manifest.files) {
for (const auto& file : m_manifest.files) {
fileIds.push_back(QString::number(file.fileId));
}
auto [task, response] = flameAPI.getFiles(fileIds);
auto [task, response] = FlameAPI::getFiles(fileIds);
m_task = task;
auto step_progress = std::make_shared<TaskStepProgress>();
connect(m_task.get(), &Task::succeeded, this, [this, response, step_progress]() {
step_progress->state = TaskStepState::Succeeded;
stepProgress(*step_progress);
auto stepProgress2 = std::make_shared<TaskStepProgress>();
connect(m_task.get(), &Task::succeeded, this, [this, response, stepProgress2]() {
stepProgress2->state = TaskStepState::Succeeded;
stepProgress(*stepProgress2);
netJobFinished(response);
});
connect(m_task.get(), &Task::failed, this, [this, step_progress](QString reason) {
step_progress->state = TaskStepState::Failed;
stepProgress(*step_progress);
emitFailed(reason);
connect(m_task.get(), &Task::failed, this, [this, stepProgress2](QString reason) {
stepProgress2->state = TaskStepState::Failed;
stepProgress(*stepProgress2);
emitFailed(std::move(reason));
});
connect(m_task.get(), &Task::stepProgress, this, &FileResolvingTask::propagateStepProgress);
connect(m_task.get(), &Task::progress, this, [this, step_progress](qint64 current, qint64 total) {
connect(m_task.get(), &Task::progress, this, [this, stepProgress2](qint64 current, qint64 total) {
qDebug() << "Resolve slug progress" << current << total;
step_progress->update(current, total);
stepProgress(*step_progress);
stepProgress2->update(current, total);
stepProgress(*stepProgress2);
});
connect(m_task.get(), &Task::status, this, [this, step_progress](QString status) {
step_progress->status = status;
stepProgress(*step_progress);
connect(m_task.get(), &Task::status, this, [this, stepProgress2](QString status) {
stepProgress2->status = std::move(status);
stepProgress(*stepProgress2);
});
m_task->start();
@ -115,7 +114,7 @@ void Flame::FileResolvingTask::netJobFinished(QByteArray* response)
Q_ASSERT(m_manifest.files.contains(fileid));
m_manifest.files[fileid].version = version;
auto url = QUrl(version.downloadUrl, QUrl::TolerantMode);
if (!url.isValid() && "sha1" == version.hash_type && !version.hash.isEmpty()) {
if (!url.isValid() && "sha1" == version.hashType && !version.hash.isEmpty()) {
hashes.push_back(version.hash);
}
} catch (Json::JsonException& e) {
@ -131,18 +130,18 @@ void Flame::FileResolvingTask::netJobFinished(QByteArray* response)
getFlameProjects();
return;
}
auto [modrinthTask, modrinthResponse] = modrinthAPI.currentVersions(hashes, "sha1");
auto [modrinthTask, modrinthResponse] = ModrinthAPI::currentVersions(hashes, "sha1");
m_task = modrinthTask;
(dynamic_cast<NetJob*>(m_task.get()))->setAskRetry(false);
auto step_progress = std::make_shared<TaskStepProgress>();
connect(m_task.get(), &Task::succeeded, this, [this, modrinthResponse, step_progress]() {
step_progress->state = TaskStepState::Succeeded;
stepProgress(*step_progress);
QJsonParseError parse_error{};
QJsonDocument doc = QJsonDocument::fromJson(*modrinthResponse, &parse_error);
if (parse_error.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response from Modrinth::CurrentVersions at" << parse_error.offset
<< "reason:" << parse_error.errorString();
auto stepProgress2 = std::make_shared<TaskStepProgress>();
connect(m_task.get(), &Task::succeeded, this, [this, modrinthResponse, stepProgress2]() {
stepProgress2->state = TaskStepState::Succeeded;
stepProgress(*stepProgress2);
QJsonParseError parseError{};
QJsonDocument doc = QJsonDocument::fromJson(*modrinthResponse, &parseError);
if (parseError.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response from Modrinth::CurrentVersions at" << parseError.offset
<< "reason:" << parseError.errorString();
qWarning() << *modrinthResponse;
getFlameProjects();
@ -153,7 +152,7 @@ void Flame::FileResolvingTask::netJobFinished(QByteArray* response)
auto entries = Json::requireObject(doc);
for (auto& out : m_manifest.files) {
auto url = QUrl(out.version.downloadUrl, QUrl::TolerantMode);
if (!url.isValid() && "sha1" == out.version.hash_type && !out.version.hash.isEmpty()) {
if (!url.isValid() && "sha1" == out.version.hashType && !out.version.hash.isEmpty()) {
try {
auto entry = Json::requireObject(entries, out.version.hash);
@ -174,20 +173,20 @@ void Flame::FileResolvingTask::netJobFinished(QByteArray* response)
}
getFlameProjects();
});
connect(m_task.get(), &Task::failed, this, [this, step_progress](QString reason) {
step_progress->state = TaskStepState::Failed;
stepProgress(*step_progress);
connect(m_task.get(), &Task::failed, this, [this, stepProgress2](const QString& /*reason*/) {
stepProgress2->state = TaskStepState::Failed;
stepProgress(*stepProgress2);
getFlameProjects();
});
connect(m_task.get(), &Task::stepProgress, this, &FileResolvingTask::propagateStepProgress);
connect(m_task.get(), &Task::progress, this, [this, step_progress](qint64 current, qint64 total) {
connect(m_task.get(), &Task::progress, this, [this, stepProgress2](qint64 current, qint64 total) {
qDebug() << "Resolve slug progress" << current << total;
step_progress->update(current, total);
stepProgress(*step_progress);
stepProgress2->update(current, total);
stepProgress(*stepProgress2);
});
connect(m_task.get(), &Task::status, this, [this, step_progress](QString status) {
step_progress->status = status;
stepProgress(*step_progress);
connect(m_task.get(), &Task::status, this, [this, stepProgress2](QString status) {
stepProgress2->status = std::move(status);
stepProgress(*stepProgress2);
});
m_task->start();
}
@ -196,20 +195,20 @@ void Flame::FileResolvingTask::getFlameProjects()
{
setProgress(2, 3);
QStringList addonIds;
for (auto file : m_manifest.files) {
for (const auto& file : m_manifest.files) {
addonIds.push_back(QString::number(file.projectId));
}
auto [task, response] = flameAPI.getProjects(addonIds);
auto [task, response] = FlameAPI().getProjects(addonIds);
m_task = task;
auto step_progress = std::make_shared<TaskStepProgress>();
connect(m_task.get(), &Task::succeeded, this, [this, response, step_progress] {
QJsonParseError parse_error{};
auto doc = QJsonDocument::fromJson(*response, &parse_error);
if (parse_error.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response from Modrinth projects task at" << parse_error.offset
<< "reason:" << parse_error.errorString();
auto stepProgress2 = std::make_shared<TaskStepProgress>();
connect(m_task.get(), &Task::succeeded, this, [this, response, stepProgress2] {
QJsonParseError parseError{};
auto doc = QJsonDocument::fromJson(*response, &parseError);
if (parseError.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response from Modrinth projects task at" << parseError.offset
<< "reason:" << parseError.errorString();
qWarning() << *response;
return;
}
@ -219,8 +218,8 @@ void Flame::FileResolvingTask::getFlameProjects()
entries = Json::requireArray(Json::requireObject(doc), "data");
for (auto entry : entries) {
auto entry_obj = Json::requireObject(entry);
auto id = Json::requireInteger(entry_obj, "id");
auto entryObj = Json::requireObject(entry);
auto id = Json::requireInteger(entryObj, "id");
auto file = std::find_if(m_manifest.files.begin(), m_manifest.files.end(),
[id](const Flame::File& file) { return file.projectId == id; });
if (file == m_manifest.files.end()) {
@ -228,7 +227,7 @@ void Flame::FileResolvingTask::getFlameProjects()
}
setStatus(tr("Parsing API response from CurseForge for '%1'...").arg(file->version.fileName));
FlameMod::loadIndexedPack(file->pack, entry_obj);
FlameMod::loadIndexedPack(file->pack, entryObj);
if (file->pack.resourceType == ModPlatform::ResourceType::World) {
file->targetFolder = "saves";
}
@ -237,25 +236,25 @@ void Flame::FileResolvingTask::getFlameProjects()
qDebug() << e.cause();
qDebug() << doc;
}
step_progress->state = TaskStepState::Succeeded;
stepProgress(*step_progress);
stepProgress2->state = TaskStepState::Succeeded;
stepProgress(*stepProgress2);
emitSucceeded();
});
connect(m_task.get(), &Task::failed, this, [this, step_progress](QString reason) {
step_progress->state = TaskStepState::Failed;
stepProgress(*step_progress);
emitFailed(reason);
connect(m_task.get(), &Task::failed, this, [this, stepProgress2](QString reason) {
stepProgress2->state = TaskStepState::Failed;
stepProgress(*stepProgress2);
emitFailed(std::move(reason));
});
connect(m_task.get(), &Task::stepProgress, this, &FileResolvingTask::propagateStepProgress);
connect(m_task.get(), &Task::progress, this, [this, step_progress](qint64 current, qint64 total) {
connect(m_task.get(), &Task::progress, this, [this, stepProgress2](qint64 current, qint64 total) {
qDebug() << "Resolve slug progress" << current << total;
step_progress->update(current, total);
stepProgress(*step_progress);
stepProgress2->update(current, total);
stepProgress(*stepProgress2);
});
connect(m_task.get(), &Task::status, this, [this, step_progress](QString status) {
step_progress->status = status;
stepProgress(*step_progress);
connect(m_task.get(), &Task::status, this, [this, stepProgress2](QString status) {
stepProgress2->status = std::move(status);
stepProgress(*stepProgress2);
});
m_task->start();

View file

@ -25,7 +25,7 @@ class FileResolvingTask : public Task {
Q_OBJECT
public:
explicit FileResolvingTask(Flame::Manifest& toProcess);
virtual ~FileResolvingTask() = default;
~FileResolvingTask() override = default;
bool canAbort() const override { return true; }
bool abort() override;
@ -33,7 +33,7 @@ class FileResolvingTask : public Task {
const Flame::Manifest& getResults() const { return m_manifest; }
protected:
virtual void executeTask() override;
void executeTask() override;
protected slots:
void netJobFinished(QByteArray* response);

View file

@ -3,10 +3,8 @@
// SPDX-License-Identifier: GPL-3.0-only
#include "FlameAPI.h"
#include <memory>
#include <optional>
#include "BuildConfig.h"
#include "FlameModIndex.h"
#include "Application.h"
#include "Json.h"
@ -19,17 +17,17 @@ std::pair<Task::Ptr, QByteArray*> FlameAPI::matchFingerprints(const QList<uint>&
{
auto netJob = makeShared<NetJob>(QString("Flame::MatchFingerprints"), APPLICATION->network());
QJsonObject body_obj;
QJsonArray fingerprints_arr;
for (auto& fp : fingerprints) {
fingerprints_arr.append(QString("%1").arg(fp));
QJsonObject bodyObj;
QJsonArray fingerprintsArr;
for (const auto& fp : fingerprints) {
fingerprintsArr.append(QString("%1").arg(fp));
}
body_obj["fingerprints"] = fingerprints_arr;
bodyObj["fingerprints"] = fingerprintsArr;
QJsonDocument body(body_obj);
auto body_raw = body.toJson();
auto [action, response] = Net::ApiUpload::makeByteArray(QString(BuildConfig.FLAME_BASE_URL + "/fingerprints"), body_raw);
QJsonDocument body(bodyObj);
auto bodyRaw = body.toJson();
auto [action, response] = Net::ApiUpload::makeByteArray(QString(BuildConfig.FLAME_BASE_URL + "/fingerprints"), bodyRaw);
netJob->addNetAction(action);
return { netJob, response };
@ -47,14 +45,14 @@ QString FlameAPI::getModFileChangelog(int modId, int fileId)
netJob->addNetAction(action);
QObject::connect(netJob.get(), &NetJob::succeeded, [&netJob, response, &changelog] {
QJsonParseError parse_error{};
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
if (parse_error.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response from Flame::FileChangelog at" << parse_error.offset
<< "reason:" << parse_error.errorString();
QJsonParseError parseError{};
QJsonDocument doc = QJsonDocument::fromJson(*response, &parseError);
if (parseError.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response from Flame::FileChangelog at" << parseError.offset
<< "reason:" << parseError.errorString();
qWarning() << *response;
netJob->failed(parse_error.errorString());
netJob->failed(parseError.errorString());
return;
}
@ -80,14 +78,14 @@ QString FlameAPI::getModDescription(int modId)
netJob->addNetAction(action);
QObject::connect(netJob.get(), &NetJob::succeeded, [&netJob, response, &description] {
QJsonParseError parse_error{};
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
if (parse_error.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response from Flame::ModDescription at" << parse_error.offset
<< "reason:" << parse_error.errorString();
QJsonParseError parseError{};
QJsonDocument doc = QJsonDocument::fromJson(*response, &parseError);
if (parseError.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response from Flame::ModDescription at" << parseError.offset
<< "reason:" << parseError.errorString();
qWarning() << *response;
netJob->failed(parse_error.errorString());
netJob->failed(parseError.errorString());
return;
}
@ -106,48 +104,48 @@ std::pair<Task::Ptr, QByteArray*> FlameAPI::getProjects(QStringList addonIds) co
{
auto netJob = makeShared<NetJob>(QString("Flame::GetProjects"), APPLICATION->network());
QJsonObject body_obj;
QJsonArray addons_arr;
QJsonObject bodyObj;
QJsonArray addonsArr;
for (auto& addonId : addonIds) {
addons_arr.append(addonId);
addonsArr.append(addonId);
}
body_obj["modIds"] = addons_arr;
bodyObj["modIds"] = addonsArr;
QJsonDocument body(body_obj);
auto body_raw = body.toJson();
auto [action, response] = Net::ApiUpload::makeByteArray(QString(BuildConfig.FLAME_BASE_URL + "/mods"), body_raw);
QJsonDocument body(bodyObj);
auto bodyRaw = body.toJson();
auto [action, response] = Net::ApiUpload::makeByteArray(QString(BuildConfig.FLAME_BASE_URL + "/mods"), bodyRaw);
netJob->addNetAction(action);
QObject::connect(netJob.get(), &NetJob::failed, [body_raw] { qDebug() << body_raw; });
QObject::connect(netJob.get(), &NetJob::failed, [bodyRaw] { qDebug() << bodyRaw; });
return { netJob, response };
}
std::pair<Task::Ptr, QByteArray*> FlameAPI::getFiles(const QStringList& fileIds) const
std::pair<Task::Ptr, QByteArray*> FlameAPI::getFiles(const QStringList& fileIds)
{
auto netJob = makeShared<NetJob>(QString("Flame::GetFiles"), APPLICATION->network());
QJsonObject body_obj;
QJsonArray files_arr;
for (auto& fileId : fileIds) {
files_arr.append(fileId);
QJsonObject bodyObj;
QJsonArray filesArr;
for (const auto& fileId : fileIds) {
filesArr.append(fileId);
}
body_obj["fileIds"] = files_arr;
bodyObj["fileIds"] = filesArr;
QJsonDocument body(body_obj);
auto body_raw = body.toJson();
QJsonDocument body(bodyObj);
auto bodyRaw = body.toJson();
auto [action, response] = Net::ApiUpload::makeByteArray(QString(BuildConfig.FLAME_BASE_URL + "/mods/files"), body_raw);
auto [action, response] = Net::ApiUpload::makeByteArray(QString(BuildConfig.FLAME_BASE_URL + "/mods/files"), bodyRaw);
netJob->addNetAction(action);
QObject::connect(netJob.get(), &NetJob::failed, [body_raw] { qDebug() << body_raw; });
QObject::connect(netJob.get(), &NetJob::failed, [bodyRaw] { qDebug() << bodyRaw; });
return { netJob, response };
}
std::pair<Task::Ptr, QByteArray*> FlameAPI::getFile(const QString& addonId, const QString& fileId) const
std::pair<Task::Ptr, QByteArray*> FlameAPI::getFile(const QString& addonId, const QString& fileId)
{
auto netJob = makeShared<NetJob>(QString("Flame::GetFile"), APPLICATION->network());
auto [action, response] =
@ -162,14 +160,14 @@ std::pair<Task::Ptr, QByteArray*> FlameAPI::getFile(const QString& addonId, cons
QList<ResourceAPI::SortingMethod> FlameAPI::getSortingMethods() const
{
// https://docs.curseforge.com/?python#tocS_ModsSearchSortField
return { { .index = 1, .name = "Featured", .readable_name = QObject::tr("Sort by Featured") },
{ .index = 2, .name = "Popularity", .readable_name = QObject::tr("Sort by Popularity") },
{ .index = 3, .name = "LastUpdated", .readable_name = QObject::tr("Sort by Last Updated") },
{ .index = 4, .name = "Name", .readable_name = QObject::tr("Sort by Name") },
{ .index = 5, .name = "Author", .readable_name = QObject::tr("Sort by Author") },
{ .index = 6, .name = "TotalDownloads", .readable_name = QObject::tr("Sort by Downloads") },
{ .index = 7, .name = "Category", .readable_name = QObject::tr("Sort by Category") },
{ .index = 8, .name = "GameVersion", .readable_name = QObject::tr("Sort by Game Version") } };
return { { .index = 1, .name = "Featured", .readableName = QObject::tr("Sort by Featured") },
{ .index = 2, .name = "Popularity", .readableName = QObject::tr("Sort by Popularity") },
{ .index = 3, .name = "LastUpdated", .readableName = QObject::tr("Sort by Last Updated") },
{ .index = 4, .name = "Name", .readableName = QObject::tr("Sort by Name") },
{ .index = 5, .name = "Author", .readableName = QObject::tr("Sort by Author") },
{ .index = 6, .name = "TotalDownloads", .readableName = QObject::tr("Sort by Downloads") },
{ .index = 7, .name = "Category", .readableName = QObject::tr("Sort by Category") },
{ .index = 8, .name = "GameVersion", .readableName = QObject::tr("Sort by Game Version") } };
}
namespace {
@ -205,7 +203,7 @@ std::pair<Task::Ptr, QByteArray*> FlameAPI::getCategories(ModPlatform::ResourceT
auto [action, response] = Net::ApiDownload::makeByteArray(
QUrl(QString(BuildConfig.FLAME_BASE_URL + "/categories?gameId=432&classId=%1").arg(getClassId(type))));
netJob->addNetAction(action);
QObject::connect(netJob.get(), &Task::failed, [](QString msg) { qDebug() << "Flame failed to get categories:" << msg; });
QObject::connect(netJob.get(), &Task::failed, [](const QString& msg) { qDebug() << "Flame failed to get categories:" << msg; });
return { netJob, response };
}
@ -217,11 +215,10 @@ std::pair<Task::Ptr, QByteArray*> FlameAPI::getModCategories()
QList<ModPlatform::Category> FlameAPI::loadModCategories(const QByteArray& response)
{
QList<ModPlatform::Category> categories;
QJsonParseError parse_error{};
QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error);
if (parse_error.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response from categories at" << parse_error.offset
<< "reason:" << parse_error.errorString();
QJsonParseError parseError{};
QJsonDocument doc = QJsonDocument::fromJson(response, &parseError);
if (parseError.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response from categories at" << parseError.offset << "reason:" << parseError.errorString();
qWarning() << *response;
return categories;
}
@ -245,17 +242,17 @@ QList<ModPlatform::Category> FlameAPI::loadModCategories(const QByteArray& respo
return categories;
};
std::optional<ModPlatform::IndexedVersion> FlameAPI::getLatestVersion(QList<ModPlatform::IndexedVersion> versions,
QList<ModPlatform::ModLoaderType> instanceLoaders,
ModPlatform::ModLoaderTypes modLoaders,
std::optional<ModPlatform::IndexedVersion> FlameAPI::getLatestVersion(const QList<ModPlatform::IndexedVersion>& versions,
const QList<ModPlatform::ModLoaderType>& instanceLoaders,
ModPlatform::ModLoaderTypes fallback,
bool checkLoaders)
{
static const auto noLoader = ModPlatform::ModLoaderType(0);
static const auto s_noLoader = ModPlatform::ModLoaderType(0);
if (!checkLoaders) {
std::optional<ModPlatform::IndexedVersion> ver;
for (auto file_tmp : versions) {
if (!ver.has_value() || file_tmp.date > ver->date) {
ver = file_tmp;
for (const auto& fileTmp : versions) {
if (!ver.has_value() || fileTmp.date > ver->date) {
ver = fileTmp;
}
}
return ver;
@ -271,26 +268,26 @@ std::optional<ModPlatform::IndexedVersion> FlameAPI::getLatestVersion(QList<ModP
bestMatch[loader] = version;
}
};
for (auto file_tmp : versions) {
auto loaders = ModPlatform::modLoaderTypesToList(file_tmp.loaders);
for (const auto& fileTmp : versions) {
auto loaders = ModPlatform::modLoaderTypesToList(fileTmp.loaders);
if (loaders.isEmpty()) {
checkVersion(file_tmp, noLoader);
checkVersion(fileTmp, s_noLoader);
} else {
for (auto loader : loaders) {
checkVersion(file_tmp, loader);
checkVersion(fileTmp, loader);
}
}
}
// edge case: mod has installed for forge but the instance is fabric => fabric version will be prioritizated on update
auto currentLoaders = instanceLoaders + ModPlatform::modLoaderTypesToList(modLoaders);
currentLoaders.append(noLoader); // add a fallback in case the versions do not define a loader
auto currentLoaders = instanceLoaders + ModPlatform::modLoaderTypesToList(fallback);
currentLoaders.append(s_noLoader); // add a fallback in case the versions do not define a loader
for (auto loader : currentLoaders) {
if (bestMatch.contains(loader)) {
auto bestForLoader = bestMatch.value(loader);
// awkward case where the mod has only two loaders and one of them is not specified
if (loader != noLoader && bestMatch.contains(noLoader) && bestMatch.size() == 2) {
auto bestForNoLoader = bestMatch.value(noLoader);
if (loader != s_noLoader && bestMatch.contains(s_noLoader) && bestMatch.size() == 2) {
auto bestForNoLoader = bestMatch.value(s_noLoader);
if (bestForNoLoader.date > bestForLoader.date) {
return bestForNoLoader;
}

View file

@ -4,10 +4,12 @@
#pragma once
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QList>
#include <cstdint>
#include "BuildConfig.h"
#include "Json.h"
#include "Version.h"
#include "modplatform/ModIndex.h"
#include "modplatform/ResourceAPI.h"
@ -15,18 +17,18 @@
class FlameAPI : public ResourceAPI {
public:
QString getModFileChangelog(int modId, int fileId);
QString getModDescription(int modId);
static QString getModFileChangelog(int modId, int fileId);
static QString getModDescription(int modId);
std::optional<ModPlatform::IndexedVersion> getLatestVersion(QList<ModPlatform::IndexedVersion> versions,
QList<ModPlatform::ModLoaderType> instanceLoaders,
ModPlatform::ModLoaderTypes fallback,
bool checkLoaders);
static std::optional<ModPlatform::IndexedVersion> getLatestVersion(const QList<ModPlatform::IndexedVersion>& versions,
const QList<ModPlatform::ModLoaderType>& instanceLoaders,
ModPlatform::ModLoaderTypes fallback,
bool checkLoaders);
std::pair<Task::Ptr, QByteArray*> getProjects(QStringList addonIds) const override;
std::pair<Task::Ptr, QByteArray*> matchFingerprints(const QList<uint>& fingerprints);
std::pair<Task::Ptr, QByteArray*> getFiles(const QStringList& fileIds) const;
std::pair<Task::Ptr, QByteArray*> getFile(const QString& addonId, const QString& fileId) const;
static std::pair<Task::Ptr, QByteArray*> matchFingerprints(const QList<uint>& fingerprints);
static std::pair<Task::Ptr, QByteArray*> getFiles(const QStringList& fileIds);
static std::pair<Task::Ptr, QByteArray*> getFile(const QString& addonId, const QString& fileId);
static std::pair<Task::Ptr, QByteArray*> getCategories(ModPlatform::ResourceType type);
static std::pair<Task::Ptr, QByteArray*> getModCategories();
@ -34,9 +36,9 @@ class FlameAPI : public ResourceAPI {
QList<ResourceAPI::SortingMethod> getSortingMethods() const override;
static inline bool validateModLoaders(ModPlatform::ModLoaderTypes loaders)
static 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)) != 0;
}
static ModPlatform::ResourceType getResourceType(int classId);
@ -71,44 +73,49 @@ class FlameAPI : public ResourceAPI {
return 0;
}
static const QStringList getModLoaderStrings(const ModPlatform::ModLoaderTypes types)
static QStringList getModLoaderStrings(const ModPlatform::ModLoaderTypes types)
{
QStringList l;
for (auto loader : { ModPlatform::NeoForge, ModPlatform::Forge, ModPlatform::Fabric, ModPlatform::Quilt }) {
if (types & loader) {
if ((types & loader) != 0) {
l << QString::number(getMappedModLoader(loader));
}
}
return l;
}
static const QString getModLoaderFilters(ModPlatform::ModLoaderTypes types) { return "[" + getModLoaderStrings(types).join(',') + "]"; }
static QString getModLoaderFilters(ModPlatform::ModLoaderTypes types) { return "[" + getModLoaderStrings(types).join(',') + "]"; }
public:
std::optional<QString> getSearchURL(const SearchArgs& args) const override
{
QStringList get_arguments;
get_arguments.append(QString("classId=%1").arg(getClassId(args.type)));
get_arguments.append(QString("index=%1").arg(args.offset));
get_arguments.append("pageSize=25");
if (args.search.has_value())
get_arguments.append(QString("searchFilter=%1").arg(args.search.value()));
if (args.sorting.has_value())
get_arguments.append(QString("sortField=%1").arg(args.sorting.value().index));
get_arguments.append("sortOrder=desc");
QStringList getArguments;
getArguments.append(QString("classId=%1").arg(getClassId(args.type)));
getArguments.append(QString("index=%1").arg(args.offset));
getArguments.append("pageSize=25");
if (args.search.has_value()) {
getArguments.append(QString("searchFilter=%1").arg(args.search.value()));
}
if (args.sorting.has_value()) {
getArguments.append(QString("sortField=%1").arg(args.sorting.value().index));
}
getArguments.append("sortOrder=desc");
if (args.loaders.has_value()) {
ModPlatform::ModLoaderTypes loaders = args.loaders.value();
loaders &= ~static_cast<std::uint16_t>(ModPlatform::ModLoaderType::DataPack);
if (loaders != 0)
get_arguments.append(QString("modLoaderTypes=%1").arg(getModLoaderFilters(loaders)));
if (loaders != 0) {
getArguments.append(QString("modLoaderTypes=%1").arg(getModLoaderFilters(loaders)));
}
}
if (args.categoryIds.has_value() && !args.categoryIds->empty()) {
getArguments.append(QString("categoryIds=[%1]").arg(args.categoryIds->join(",")));
}
if (args.categoryIds.has_value() && !args.categoryIds->empty())
get_arguments.append(QString("categoryIds=[%1]").arg(args.categoryIds->join(",")));
if (args.versions.has_value() && !args.versions.value().empty())
get_arguments.append(QString("gameVersion=%1").arg(args.versions.value().front().toString()));
if (args.versions.has_value() && !args.versions.value().empty()) {
getArguments.append(QString("gameVersion=%1").arg(args.versions.value().front().toString()));
}
return BuildConfig.FLAME_BASE_URL + "/mods/search?gameId=432&" + get_arguments.join('&');
return BuildConfig.FLAME_BASE_URL + "/mods/search?gameId=432&" + getArguments.join('&');
}
std::optional<QString> getVersionsURL(const VersionSearchArgs& args) const override
@ -116,8 +123,9 @@ class FlameAPI : public ResourceAPI {
auto addonId = args.pack->addonId.toString();
QString url = QString(BuildConfig.FLAME_BASE_URL + "/mods/%1/files?pageSize=10000").arg(addonId);
if (args.mcVersions.has_value())
if (args.mcVersions.has_value()) {
url += QString("&gameVersion=%1").arg(args.mcVersions.value().front().toString());
}
if (args.loaders.has_value() && args.loaders.value() != ModPlatform::ModLoaderType::DataPack &&
ModPlatform::hasSingleModLoaderSelected(args.loaders.value())) {
@ -136,15 +144,15 @@ class FlameAPI : public ResourceAPI {
return arr;
}
// FIXME: Client-side version filtering. This won't take into account any user-selected filtering.
const auto& mc_versions = arr.mcVersion;
const auto& mcVersions = arr.mcVersion;
if (std::any_of(mc_versions.constBegin(), mc_versions.constEnd(),
[](const auto& mc_version) { return Version(mc_version) <= Version("1.6"); })) {
if (std::any_of(mcVersions.constBegin(), mcVersions.constEnd(),
[](const auto& mcVersion) { return Version(mcVersion) <= Version("1.6"); })) {
return arr;
}
return {};
};
void loadExtraPackInfo(ModPlatform::IndexedPack& m, [[maybe_unused]] QJsonObject&) const override { FlameMod::loadBody(m); }
void loadExtraPackInfo(ModPlatform::IndexedPack& m, [[maybe_unused]] QJsonObject& /*unused*/) const override { FlameMod::loadBody(m); }
private:
std::optional<QString> getInfoURL(const QString& id) const override { return QString(BuildConfig.FLAME_BASE_URL + "/mods/%1").arg(id); }
@ -153,7 +161,7 @@ class FlameAPI : public ResourceAPI {
auto addonId = args.dependency.addonId.toString();
auto url =
QString(BuildConfig.FLAME_BASE_URL + "/mods/%1/files?pageSize=10000&gameVersion=%2").arg(addonId, args.mcVersion.toString());
if (args.loader && ModPlatform::hasSingleModLoaderSelected(args.loader)) {
if ((args.loader != 0U) && ModPlatform::hasSingleModLoaderSelected(args.loader)) {
int mappedModLoader = getMappedModLoader(static_cast<ModPlatform::ModLoaderType>(static_cast<int>(args.loader)));
url += QString("&modLoaderType=%1").arg(mappedModLoader);
}

View file

@ -122,7 +122,7 @@ void FlameCheckUpdate::getLatestVersionCallback(Resource* resource, QByteArray*
}
auto downloadTask = makeShared<ResourceDownloadTask>(pack, latestVer.value(), m_resourceModel, true, "update");
m_updates.emplace_back(pack->name, resource->metadata()->hash, oldVersion, latestVer->version, latestVer->version_type,
m_updates.emplace_back(pack->name, resource->metadata()->hash, oldVersion, latestVer->version, latestVer->versionType,
FlameAPI().getModFileChangelog(latestVer->addonId.toInt(), latestVer->fileId.toInt()),
ModPlatform::ResourceProvider::FLAME, downloadTask, resource->enabled());
}

View file

@ -55,13 +55,13 @@
#include "settings/INISettingsObject.h"
#include "SysInfo.h"
#include "tasks/ConcurrentTask.h"
#include "ui/dialogs/BlockedModsDialog.h"
#include "ui/dialogs/CustomMessageBox.h"
#include <QDebug>
#include <QFileInfo>
#include <algorithm>
#include "HardwareInfo.h"
#include "meta/Index.h"
@ -70,163 +70,170 @@
#include "net/ApiDownload.h"
#include "ui/pages/modplatform/OptionalModDialog.h"
static const FlameAPI api;
bool FlameCreationTask::abort()
{
if (!canAbort())
if (!canAbort()) {
return false;
}
if (m_processUpdateFileInfoJob)
if (m_processUpdateFileInfoJob) {
m_processUpdateFileInfoJob->abort();
if (m_filesJob)
}
if (m_filesJob) {
m_filesJob->abort();
if (m_modIdResolver)
}
if (m_modIdResolver) {
m_modIdResolver->abort();
}
return InstanceCreationTask::abort();
}
bool FlameCreationTask::updateInstance()
{
auto instance_list = APPLICATION->instances();
auto* instanceList = APPLICATION->instances();
// FIXME: How to handle situations when there's more than one install already for a given modpack?
BaseInstance* inst;
if (auto original_id = originalInstanceID(); !original_id.isEmpty()) {
inst = instance_list->getInstanceById(original_id);
BaseInstance* inst = nullptr;
if (auto originalId = originalInstanceID(); !originalId.isEmpty()) {
inst = instanceList->getInstanceById(originalId);
Q_ASSERT(inst);
} else {
inst = instance_list->getInstanceByManagedName(originalName());
inst = instanceList->getInstanceByManagedName(originalName());
if (!inst) {
inst = instance_list->getInstanceById(originalName());
inst = instanceList->getInstanceById(originalName());
if (!inst)
if (!inst) {
return false;
}
}
}
QString index_path(FS::PathCombine(m_stagingPath, "manifest.json"));
QString indexPath(FS::PathCombine(m_stagingPath, "manifest.json"));
try {
Flame::loadManifest(m_pack, index_path);
Flame::loadManifest(m_pack, indexPath);
} catch (const JSONValidationError& e) {
setError(tr("Could not understand pack manifest:\n") + e.cause());
return false;
}
auto version_id = inst->getManagedPackVersionName();
auto version_str = !version_id.isEmpty() ? tr(" (version %1)").arg(version_id) : "";
auto versionId = inst->getManagedPackVersionName();
auto versionStr = !versionId.isEmpty() ? tr(" (version %1)").arg(versionId) : "";
if (shouldConfirmUpdate()) {
auto should_update = askIfShouldUpdate(m_parent, version_str);
if (should_update == ShouldUpdate::SkipUpdating)
auto shouldUpdate = askIfShouldUpdate(m_parent, versionStr);
if (shouldUpdate == ShouldUpdate::SkipUpdating) {
return false;
if (should_update == ShouldUpdate::Cancel) {
}
if (shouldUpdate == ShouldUpdate::Cancel) {
m_abort = true;
return false;
}
}
QDir old_inst_dir(inst->instanceRoot());
QDir oldInstDir(inst->instanceRoot());
QString old_index_folder(FS::PathCombine(old_inst_dir.absolutePath(), "flame"));
QString old_index_path(FS::PathCombine(old_index_folder, "manifest.json"));
QString oldIndexFolder(FS::PathCombine(oldInstDir.absolutePath(), "flame"));
QString oldIndexPath(FS::PathCombine(oldIndexFolder, "manifest.json"));
QFileInfo old_index_file(old_index_path);
if (old_index_file.exists()) {
Flame::Manifest old_pack;
Flame::loadManifest(old_pack, old_index_path);
QFileInfo oldIndexFile(oldIndexPath);
if (oldIndexFile.exists()) {
Flame::Manifest oldPack;
Flame::loadManifest(oldPack, oldIndexPath);
auto& old_files = old_pack.files;
auto& oldFiles = oldPack.files;
auto& files = m_pack.files;
// Remove repeated files, we don't need to download them!
auto files_iterator = files.begin();
while (files_iterator != files.end()) {
const auto& file = files_iterator;
auto filesIterator = files.begin();
while (filesIterator != files.end()) {
const auto& file = filesIterator;
auto old_file = old_files.find(file.key());
if (old_file != old_files.end()) {
auto oldFile = oldFiles.find(file.key());
if (oldFile != oldFiles.end()) {
// We found a match, but is it a different version?
if (old_file->fileId == file->fileId) {
if (oldFile->fileId == file->fileId) {
qDebug() << "Removed file at" << file->targetFolder << "with id" << file->fileId << "from list of downloads";
old_files.remove(file.key());
files_iterator = files.erase(files_iterator);
oldFiles.remove(file.key());
filesIterator = files.erase(filesIterator);
if (files_iterator != files.begin())
files_iterator--;
if (filesIterator != files.begin()) {
filesIterator--;
}
}
}
files_iterator++;
filesIterator++;
}
QDir old_minecraft_dir(inst->gameRoot());
QDir oldMinecraftDir(inst->gameRoot());
// We will remove all the previous overrides, to prevent duplicate files!
// TODO: Currently 'overrides' will always override the stuff on update. How do we preserve unchanged overrides?
// FIXME: We may want to do something about disabled mods.
auto old_overrides = Override::readOverrides("overrides", old_index_folder);
for (const auto& entry : old_overrides) {
scheduleToDelete(m_parent, old_minecraft_dir, entry);
auto oldOverrides = Override::readOverrides("overrides", oldIndexFolder);
for (const auto& entry : oldOverrides) {
scheduleToDelete(m_parent, oldMinecraftDir, entry);
}
// Remove remaining old files (we need to do an API request to know which ids are which files...)
QStringList fileIds;
for (auto& file : old_files) {
for (auto& file : oldFiles) {
fileIds.append(QString::number(file.fileId));
}
auto [job, raw_response] = api.getFiles(fileIds);
auto [job, rawResponse] = FlameAPI::getFiles(fileIds);
QEventLoop loop;
connect(job.get(), &Task::succeeded, this, [this, raw_response, fileIds, old_inst_dir, &old_files, old_minecraft_dir] {
connect(job.get(), &Task::succeeded, this, [this, rawResponse, fileIds, oldInstDir, &oldFiles, oldMinecraftDir] {
// Parse the API response
QJsonParseError parse_error{};
auto doc = QJsonDocument::fromJson(*raw_response, &parse_error);
if (parse_error.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response from Flame files task at" << parse_error.offset
<< "reason:" << parse_error.errorString();
qWarning() << *raw_response;
QJsonParseError parseError{};
auto doc = QJsonDocument::fromJson(*rawResponse, &parseError);
if (parseError.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response from Flame files task at" << parseError.offset
<< "reason:" << parseError.errorString();
qWarning() << *rawResponse;
return;
}
try {
QJsonArray entries;
if (fileIds.size() == 1)
if (fileIds.size() == 1) {
entries = { Json::requireObject(Json::requireObject(doc), "data") };
else
} else {
entries = Json::requireArray(Json::requireObject(doc), "data");
}
for (auto entry : entries) {
auto entry_obj = Json::requireObject(entry);
auto entryObj = Json::requireObject(entry);
Flame::File file;
// We don't care about blocked mods, we just need local data to delete the file
file.version = FlameMod::loadIndexedPackVersion(entry_obj);
auto id = Json::requireInteger(entry_obj, "id");
old_files.insert(id, file);
file.version = FlameMod::loadIndexedPackVersion(entryObj);
auto id = Json::requireInteger(entryObj, "id");
oldFiles.insert(id, file);
}
} catch (Json::JsonException& e) {
qCritical() << e.cause() << e.what();
}
// Delete the files
for (auto& file : old_files) {
if (file.version.fileName.isEmpty() || file.targetFolder.isEmpty())
for (auto& file : oldFiles) {
if (file.version.fileName.isEmpty() || file.targetFolder.isEmpty()) {
continue;
}
QString relative_path(FS::PathCombine(file.targetFolder, file.version.fileName));
scheduleToDelete(m_parent, old_minecraft_dir, relative_path, true);
QString relativePath(FS::PathCombine(file.targetFolder, file.version.fileName));
scheduleToDelete(m_parent, oldMinecraftDir, relativePath, true);
}
});
connect(job.get(), &Task::failed, this, [](QString reason) { qCritical() << "Failed to get files:" << reason; });
connect(job.get(), &Task::failed, this, [](const QString& reason) { qCritical() << "Failed to get files:" << reason; });
connect(job.get(), &Task::finished, &loop, &QEventLoop::quit);
m_processUpdateFileInfoJob = job;
@ -237,10 +244,10 @@ bool FlameCreationTask::updateInstance()
m_processUpdateFileInfoJob = nullptr;
} else {
// We don't have an old index file, so we may duplicate stuff!
auto dialog = CustomMessageBox::selectable(m_parent, tr("No index file."),
tr("We couldn't find a suitable index file for the older version. This may cause some "
"of the files to be duplicated. Do you want to continue?"),
QMessageBox::Warning, QMessageBox::Ok | QMessageBox::Cancel);
auto* dialog = CustomMessageBox::selectable(m_parent, tr("No index file."),
tr("We couldn't find a suitable index file for the older version. This may cause some "
"of the files to be duplicated. Do you want to continue?"),
QMessageBox::Warning, QMessageBox::Ok | QMessageBox::Cancel);
if (dialog->exec() == QDialog::DialogCode::Rejected) {
m_abort = true;
@ -257,7 +264,10 @@ bool FlameCreationTask::updateInstance()
return false;
}
QString FlameCreationTask::getVersionForLoader(QString uid, QString loaderType, QString loaderVersion, QString mcVersion)
QString FlameCreationTask::getVersionForLoader(const QString& uid,
const QString& loaderType,
QString loaderVersion,
const QString& mcVersion)
{
if (loaderVersion == "recommended") {
auto vlist = APPLICATION->metadataIndex()->get(uid);
@ -270,27 +280,29 @@ QString FlameCreationTask::getVersionForLoader(QString uid, QString loaderType,
QEventLoop loadVersionLoop;
auto task = vlist->getLoadTask();
connect(task.get(), &Task::finished, &loadVersionLoop, &QEventLoop::quit);
if (!task->isRunning())
if (!task->isRunning()) {
task->start();
}
loadVersionLoop.exec();
}
for (auto version : vlist->versions()) {
for (const auto& version : vlist->versions()) {
// first recommended build we find, we use.
if (!version->isRecommended())
if (!version->isRecommended()) {
continue;
}
auto reqs = version->requiredSet();
// filter by minecraft version, if the loader depends on a certain version.
// not all mod loaders depend on a given Minecraft version, so we won't do this
// filtering for those loaders.
if (loaderType == "forge" || loaderType == "neoforge") {
auto iter = std::find_if(reqs.begin(), reqs.end(), [mcVersion](const Meta::Require& req) {
return req.uid == "net.minecraft" && req.equalsVersion == mcVersion;
});
if (iter == reqs.end())
auto iter = std::ranges::find_if(
reqs, [mcVersion](const Meta::Require& req) { return req.uid == "net.minecraft" && req.equalsVersion == mcVersion; });
if (iter == reqs.end()) {
continue;
}
}
return version->descriptor();
}
@ -311,17 +323,18 @@ std::unique_ptr<MinecraftInstance> FlameCreationTask::createInstance()
{
QEventLoop loop;
QString parent_folder(FS::PathCombine(m_stagingPath, "flame"));
QString parentFolder(FS::PathCombine(m_stagingPath, "flame"));
try {
QString index_path(FS::PathCombine(m_stagingPath, "manifest.json"));
if (!m_pack.is_loaded)
Flame::loadManifest(m_pack, index_path);
QString indexPath(FS::PathCombine(m_stagingPath, "manifest.json"));
if (!m_pack.isLoaded) {
Flame::loadManifest(m_pack, indexPath);
}
// Keep index file in case we need it some other time (like when changing versions)
QString new_index_place(FS::PathCombine(parent_folder, "manifest.json"));
FS::ensureFilePathExists(new_index_place);
FS::move(index_path, new_index_place);
QString newIndexPlace(FS::PathCombine(parentFolder, "manifest.json"));
FS::ensureFilePathExists(newIndexPlace);
FS::move(indexPath, newIndexPlace);
} catch (const JSONValidationError& e) {
setError(tr("Could not understand pack manifest:\n") + e.cause());
@ -332,7 +345,7 @@ std::unique_ptr<MinecraftInstance> FlameCreationTask::createInstance()
QString overridePath = FS::PathCombine(m_stagingPath, m_pack.overrides);
if (QFile::exists(overridePath)) {
// Create a list of overrides in "overrides.txt" inside flame/
Override::createOverrides("overrides", parent_folder, overridePath);
Override::createOverrides("overrides", parentFolder, overridePath);
QString mcPath = FS::PathCombine(m_stagingPath, "minecraft");
if (!FS::move(overridePath, mcPath)) {
@ -353,8 +366,9 @@ std::unique_ptr<MinecraftInstance> FlameCreationTask::createInstance()
auto id = loader.id;
if (id.startsWith("neoforge-")) {
id.remove("neoforge-");
if (id.startsWith("1.20.1-"))
if (id.startsWith("1.20.1-")) {
id.remove("1.20.1-"); // this is a mess for curseforge
}
loaderType = "neoforge";
loaderUid = "net.neoforged";
} else if (id.startsWith("forge-")) {
@ -388,13 +402,14 @@ std::unique_ptr<MinecraftInstance> FlameCreationTask::createInstance()
logWarning(tr("Mysterious trailing dots removed from Minecraft version while importing pack."));
}
auto components = instance->getPackProfile();
auto* components = instance->getPackProfile();
components->buildingFromScratch();
components->setComponentVersion("net.minecraft", mcVersion, true);
if (!loaderType.isEmpty()) {
auto version = getVersionForLoader(loaderUid, loaderType, loaderVersion, mcVersion);
if (version.isEmpty())
if (version.isEmpty()) {
return nullptr;
}
components->setComponentVersion(loaderUid, version);
}
@ -415,13 +430,13 @@ std::unique_ptr<MinecraftInstance> FlameCreationTask::createInstance()
// only set memory if this is a fresh instance
if (!m_instance && recommendedRAM > 0) {
const uint64_t sysMiB = HardwareInfo::totalRamMiB();
const uint64_t max = sysMiB * 0.9;
const auto max = sysMiB * 9 / 10;
if (static_cast<uint64_t>(recommendedRAM) > max) {
logWarning(tr("The recommended memory of the modpack exceeds 90% of your system RAM—reducing it from %1 MiB to %2 MiB!")
.arg(recommendedRAM)
.arg(max));
recommendedRAM = max;
recommendedRAM = static_cast<int>(max);
}
instance->settings()->set("OverrideMemory", true);
@ -439,23 +454,24 @@ std::unique_ptr<MinecraftInstance> FlameCreationTask::createInstance()
qDebug() << info.fileName();
jarMods.push_back(info.absoluteFilePath());
}
auto profile = instance->getPackProfile();
auto* profile = instance->getPackProfile();
profile->installJarMods(jarMods);
// nuke the original files
FS::deletePath(jarmodsPath);
}
// Don't add managed info to packs without an ID (most likely imported from ZIP)
if (!m_managedId.isEmpty())
if (!m_managedId.isEmpty()) {
instance->setManagedPack("flame", m_managedId, m_pack.name, m_managedVersionId, m_pack.version);
else
} else {
instance->setManagedPack("flame", "", name(), "", "");
}
instance->setName(name());
m_modIdResolver.reset(new Flame::FileResolvingTask(m_pack));
connect(m_modIdResolver.get(), &Flame::FileResolvingTask::succeeded, this, [this, &loop] { idResolverSucceeded(loop); });
connect(m_modIdResolver.get(), &Flame::FileResolvingTask::failed, [this, &loop](QString reason) {
connect(m_modIdResolver.get(), &Flame::FileResolvingTask::failed, [this, &loop](const QString& reason) {
m_modIdResolver.reset();
setError(tr("Unable to resolve mod IDs:\n") + reason);
loop.quit();
@ -469,17 +485,17 @@ std::unique_ptr<MinecraftInstance> FlameCreationTask::createInstance()
loop.exec();
bool did_succeed = getError().isEmpty();
bool didSucceed = getError().isEmpty();
// Update information of the already installed instance, if any.
if (m_instance && did_succeed) {
if (m_instance && didSucceed) {
setAbortable(false);
auto inst = m_instance.value();
auto* inst = *m_instance;
inst->copyManagedPack(*instance);
}
if (did_succeed) {
if (didSucceed) {
return instance;
}
return nullptr;
@ -508,7 +524,7 @@ void FlameCreationTask::idResolverSucceeded(QEventLoop& loop)
}
// first check for blocked mods
QList<BlockedMod> blocked_mods;
QList<BlockedMod> blockedMods;
auto anyBlocked = false;
for (const auto& result : results.values()) {
if (result.pack.resourceType != ModPlatform::ResourceType::Mod) {
@ -517,19 +533,19 @@ void FlameCreationTask::idResolverSucceeded(QEventLoop& loop)
// skip optional mods that were not selected
if (result.version.downloadUrl.isEmpty()) {
BlockedMod blocked_mod;
blocked_mod.name = result.version.fileName;
blocked_mod.websiteUrl = QString("%1/download/%2").arg(result.pack.websiteUrl, QString::number(result.fileId));
blocked_mod.hash = result.version.hash;
blocked_mod.matched = false;
blocked_mod.localPath = "";
blocked_mod.targetFolder = result.targetFolder;
BlockedMod blockedMod;
blockedMod.name = result.version.fileName;
blockedMod.websiteUrl = QString("%1/download/%2").arg(result.pack.websiteUrl, QString::number(result.fileId));
blockedMod.hash = result.version.hash;
blockedMod.matched = false;
blockedMod.localPath = "";
blockedMod.targetFolder = result.targetFolder;
auto fileName = result.version.fileName;
fileName = FS::RemoveInvalidPathChars(fileName);
auto relpath = FS::PathCombine(result.targetFolder, fileName);
blocked_mod.disabled = !result.required && !m_selectedOptionalMods.contains(relpath);
blockedMod.disabled = !result.required && !m_selectedOptionalMods.contains(relpath);
blocked_mods.append(blocked_mod);
blockedMods.append(blockedMod);
anyBlocked = true;
}
@ -537,16 +553,16 @@ void FlameCreationTask::idResolverSucceeded(QEventLoop& loop)
if (anyBlocked) {
qWarning() << "Blocked mods found, displaying mod list";
BlockedModsDialog message_dialog(m_parent, tr("Blocked mods found"),
tr("The following files are not available for download in third party launchers.<br/>"
"You will need to manually download them and add them to the instance."),
blocked_mods);
BlockedModsDialog messageDialog(m_parent, tr("Blocked mods found"),
tr("The following files are not available for download in third party launchers.<br/>"
"You will need to manually download them and add them to the instance."),
blockedMods);
message_dialog.setModal(true);
messageDialog.setModal(true);
if (message_dialog.exec()) {
qDebug() << "Post dialog blocked mods list:" << blocked_mods;
copyBlockedMods(blocked_mods);
if (messageDialog.exec() != 0) {
qDebug() << "Post dialog blocked mods list:" << blockedMods;
copyBlockedMods(blockedMods);
setupDownloadJob(loop);
} else {
m_modIdResolver.reset();
@ -586,7 +602,7 @@ void FlameCreationTask::setupDownloadJob(QEventLoop& loop)
m_filesJob.reset();
validateOtherResources(loop);
});
connect(m_filesJob.get(), &NetJob::failed, [this](QString reason) {
connect(m_filesJob.get(), &NetJob::failed, [this](const QString& reason) {
m_filesJob.reset();
setError(reason);
});
@ -602,22 +618,23 @@ void FlameCreationTask::setupDownloadJob(QEventLoop& loop)
/// @brief copy the matched blocked mods to the instance staging area
/// @param blocked_mods list of the blocked mods and their matched paths
void FlameCreationTask::copyBlockedMods(const QList<BlockedMod>& blocked_mods)
void FlameCreationTask::copyBlockedMods(const QList<BlockedMod>& blockedMods)
{
setStatus(tr("Copying Blocked Mods..."));
setAbortable(false);
int i = 0;
int total = blocked_mods.length();
auto total = blockedMods.length();
setProgress(i, total);
for (const auto& mod : blocked_mods) {
for (const auto& mod : blockedMods) {
if (!mod.matched) {
qDebug() << mod.name << "was not matched to a local file, skipping copy";
continue;
}
auto destPath = FS::PathCombine(m_stagingPath, "minecraft", mod.targetFolder, mod.name);
if (mod.disabled)
if (mod.disabled) {
destPath += ".disabled";
}
setStatus(tr("Copying Blocked Mods (%1 out of %2 are done)").arg(QString::number(i), QString::number(total)));
@ -644,13 +661,13 @@ void FlameCreationTask::validateOtherResources(QEventLoop& loop)
{
qDebug() << "Validating whether other resources are in the right place";
QStringList zipMods;
for (auto [fileName, targetFolder] : m_otherResources) {
for (const auto& [fileName, targetFolder] : m_otherResources) {
qDebug() << "Checking" << fileName << "...";
auto localPath = FS::PathCombine(m_stagingPath, "minecraft", targetFolder, fileName);
/// @brief check the target and move the the file
/// @return path where file can now be found
auto validatePath = [&localPath, this](QString fileName, QString targetFolder, QString realTarget) {
auto validatePath = [&localPath, this](const QString& fileName, const QString& targetFolder, const QString& realTarget) {
if (targetFolder != realTarget) {
qDebug() << "Target folder of" << fileName << "is incorrect, it belongs in" << realTarget;
auto destPath = FS::PathCombine(m_stagingPath, "minecraft", realTarget, fileName);
@ -664,7 +681,7 @@ void FlameCreationTask::validateOtherResources(QEventLoop& loop)
return localPath;
};
auto installWorld = [this](QString worldPath) {
auto installWorld = [this](const QString& worldPath) {
qDebug() << "Installing World from" << worldPath;
QFileInfo worldFileInfo(worldPath);
World w(worldFileInfo);
@ -714,7 +731,7 @@ void FlameCreationTask::validateOtherResources(QEventLoop& loop)
auto task = makeShared<ConcurrentTask>("CreateModMetadata", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt());
auto results = m_modIdResolver->getResults().files;
auto folder = FS::PathCombine(m_stagingPath, "minecraft", "mods", ".index");
for (auto file : results) {
for (const auto& file : results) {
if (file.targetFolder != "mods" || (file.version.fileName.endsWith(".zip") && !zipMods.contains(file.version.fileName))) {
continue;
}

View file

@ -51,18 +51,18 @@ class FlameCreationTask final : public InstanceCreationTask {
Q_OBJECT
public:
FlameCreationTask(const QString& staging_path,
SettingsObject* global_settings,
FlameCreationTask(const QString& stagingPath,
SettingsObject* globalSettings,
QWidget* parent,
QString id,
QString version_id,
QString original_instance_id = {})
: InstanceCreationTask(), m_parent(parent), m_managedId(std::move(id)), m_managedVersionId(std::move(version_id))
QString versionId,
QString originalInstanceId = {})
: m_parent(parent), m_managedId(std::move(id)), m_managedVersionId(std::move(versionId))
{
setStagingPath(staging_path);
setParentSettings(global_settings);
setStagingPath(stagingPath);
setParentSettings(globalSettings);
m_original_instance_id = std::move(original_instance_id);
m_original_instance_id = std::move(originalInstanceId);
}
bool abort() override;
@ -73,9 +73,9 @@ class FlameCreationTask final : public InstanceCreationTask {
private slots:
void idResolverSucceeded(QEventLoop&);
void setupDownloadJob(QEventLoop&);
void copyBlockedMods(QList<BlockedMod> const& blocked_mods);
void copyBlockedMods(const QList<BlockedMod>& blockedMods);
void validateOtherResources(QEventLoop& loop);
QString getVersionForLoader(QString uid, QString loaderType, QString version, QString mcVersion);
QString getVersionForLoader(const QString& uid, const QString& loaderType, QString version, const QString& mcVersion);
private:
QWidget* m_parent = nullptr;

View file

@ -1,14 +1,12 @@
#include "FlameModIndex.h"
#include <algorithm>
#include "FileSystem.h"
#include "Json.h"
#include "minecraft/MinecraftInstance.h"
#include "minecraft/PackProfile.h"
#include "modplatform/ModIndex.h"
#include "modplatform/flame/FlameAPI.h"
static FlameAPI api;
void FlameMod::loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj)
{
pack.addonId = Json::requireInteger(obj, "id");
@ -44,35 +42,41 @@ void FlameMod::loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj)
void FlameMod::loadURLs(ModPlatform::IndexedPack& pack, QJsonObject& obj)
{
auto links_obj = obj["links"].toObject();
auto linksObj = obj["links"].toObject();
pack.extraData.issuesUrl = links_obj["issuesUrl"].toString();
if (pack.extraData.issuesUrl.endsWith('/'))
pack.extraData.issuesUrl = linksObj["issuesUrl"].toString();
if (pack.extraData.issuesUrl.endsWith('/')) {
pack.extraData.issuesUrl.chop(1);
}
pack.extraData.sourceUrl = links_obj["sourceUrl"].toString();
if (pack.extraData.sourceUrl.endsWith('/'))
pack.extraData.sourceUrl = linksObj["sourceUrl"].toString();
if (pack.extraData.sourceUrl.endsWith('/')) {
pack.extraData.sourceUrl.chop(1);
}
pack.extraData.wikiUrl = links_obj["wikiUrl"].toString();
if (pack.extraData.wikiUrl.endsWith('/'))
pack.extraData.wikiUrl = linksObj["wikiUrl"].toString();
if (pack.extraData.wikiUrl.endsWith('/')) {
pack.extraData.wikiUrl.chop(1);
}
if (!pack.extraData.body.isEmpty())
if (!pack.extraData.body.isEmpty()) {
pack.extraDataLoaded = true;
}
}
void FlameMod::loadBody(ModPlatform::IndexedPack& pack)
{
pack.extraData.body = api.getModDescription(pack.addonId.toInt());
pack.extraData.body = FlameAPI::getModDescription(pack.addonId.toInt());
if (!pack.extraData.issuesUrl.isEmpty() || !pack.extraData.sourceUrl.isEmpty() || !pack.extraData.wikiUrl.isEmpty())
if (!pack.extraData.issuesUrl.isEmpty() || !pack.extraData.sourceUrl.isEmpty() || !pack.extraData.wikiUrl.isEmpty()) {
pack.extraDataLoaded = true;
}
}
static QString enumToString(int hash_algorithm)
namespace {
QString enumToString(int hashAlgorithm)
{
switch (hash_algorithm) {
switch (hashAlgorithm) {
default:
case 1:
return "sha1";
@ -80,6 +84,7 @@ static QString enumToString(int hash_algorithm)
return "md5";
}
}
} // namespace
void FlameMod::loadIndexedPackVersions(ModPlatform::IndexedPack& pack, QJsonArray& arr)
{
@ -88,23 +93,25 @@ void FlameMod::loadIndexedPackVersions(ModPlatform::IndexedPack& pack, QJsonArra
auto obj = versionIter.toObject();
auto file = loadIndexedPackVersion(obj);
if (!file.addonId.isValid())
if (!file.addonId.isValid()) {
file.addonId = pack.addonId;
}
if (file.fileId.isValid()) // Heuristic to check if the returned value is valid
if (file.fileId.isValid()) { // Heuristic to check if the returned value is valid
unsortedVersions.append(file);
}
}
auto orderSortPredicate = [](const ModPlatform::IndexedVersion& a, const ModPlatform::IndexedVersion& b) -> bool {
// dates are in RFC 3339 format
return a.date > b.date;
};
std::sort(unsortedVersions.begin(), unsortedVersions.end(), orderSortPredicate);
std::ranges::sort(unsortedVersions, orderSortPredicate);
pack.versions = unsortedVersions;
pack.versionsLoaded = true;
}
auto FlameMod::loadIndexedPackVersion(QJsonObject& obj, bool load_changelog) -> ModPlatform::IndexedVersion
auto FlameMod::loadIndexedPackVersion(QJsonObject& obj, bool loadChangelog) -> ModPlatform::IndexedVersion
{
auto versionArray = Json::requireArray(obj, "gameVersions");
@ -112,27 +119,29 @@ auto FlameMod::loadIndexedPackVersion(QJsonObject& obj, bool load_changelog) ->
for (auto mcVer : versionArray) {
auto str = mcVer.toString();
if (str.contains('.'))
if (str.contains('.')) {
file.mcVersion.append(str);
}
file.side = ModPlatform::Side::NoSide;
if (auto loader = str.toLower(); loader == "neoforge")
if (auto loader = str.toLower(); loader == "neoforge") {
file.loaders |= ModPlatform::NeoForge;
else if (loader == "forge")
} else if (loader == "forge") {
file.loaders |= ModPlatform::Forge;
else if (loader == "cauldron")
} else if (loader == "cauldron") {
file.loaders |= ModPlatform::Cauldron;
else if (loader == "liteloader")
} else if (loader == "liteloader") {
file.loaders |= ModPlatform::LiteLoader;
else if (loader == "fabric")
} else if (loader == "fabric") {
file.loaders |= ModPlatform::Fabric;
else if (loader == "quilt")
} else if (loader == "quilt") {
file.loaders |= ModPlatform::Quilt;
else if (loader == "server" || loader == "client") {
if (file.side == ModPlatform::Side::NoSide)
} else if (loader == "server" || loader == "client") {
if (file.side == ModPlatform::Side::NoSide) {
file.side = ModPlatform::SideUtils::fromString(loader);
else if (file.side != ModPlatform::SideUtils::fromString(loader))
} else if (file.side != ModPlatform::SideUtils::fromString(loader)) {
file.side = ModPlatform::Side::UniversalSide;
}
}
}
@ -144,31 +153,31 @@ auto FlameMod::loadIndexedPackVersion(QJsonObject& obj, bool load_changelog) ->
file.fileName = Json::requireString(obj, "fileName");
file.fileName = FS::RemoveInvalidPathChars(file.fileName);
ModPlatform::IndexedVersionType ver_type;
ModPlatform::IndexedVersionType verType;
switch (Json::requireInteger(obj, "releaseType")) {
case 1:
ver_type = ModPlatform::IndexedVersionType::Release;
verType = ModPlatform::IndexedVersionType::Release;
break;
case 2:
ver_type = ModPlatform::IndexedVersionType::Beta;
verType = ModPlatform::IndexedVersionType::Beta;
break;
case 3:
ver_type = ModPlatform::IndexedVersionType::Alpha;
verType = ModPlatform::IndexedVersionType::Alpha;
break;
default:
ver_type = ModPlatform::IndexedVersionType::Unknown;
verType = ModPlatform::IndexedVersionType::Unknown;
break;
}
file.version_type = ver_type;
file.versionType = verType;
auto hash_list = obj["hashes"].toArray();
for (auto h : hash_list) {
auto hash_entry = h.toObject();
auto hash_types = ModPlatform::ProviderCapabilities::hashType(ModPlatform::ResourceProvider::FLAME);
auto hash_algo = enumToString(hash_entry["algo"].toInt(1));
if (hash_types.contains(hash_algo)) {
file.hash = Json::requireString(hash_entry, "value");
file.hash_type = hash_algo;
auto hashList = obj["hashes"].toArray();
for (auto h : hashList) {
auto hashEntry = h.toObject();
auto hashTypes = ModPlatform::ProviderCapabilities::hashType(ModPlatform::ResourceProvider::FLAME);
auto hashAlgo = enumToString(hashEntry["algo"].toInt(1));
if (hashTypes.contains(hashAlgo)) {
file.hash = Json::requireString(hashEntry, "value");
file.hashType = hashAlgo;
break;
}
}
@ -204,8 +213,9 @@ auto FlameMod::loadIndexedPackVersion(QJsonObject& obj, bool load_changelog) ->
file.dependencies.append(dependency);
}
if (load_changelog)
file.changelog = api.getModFileChangelog(file.addonId.toInt(), file.fileId.toInt());
if (loadChangelog) {
file.changelog = FlameAPI::getModFileChangelog(file.addonId.toInt(), file.fileId.toInt());
}
return file;
}

View file

@ -6,13 +6,11 @@
#include "modplatform/ModIndex.h"
#include "BaseInstance.h"
namespace FlameMod {
void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj);
void loadURLs(ModPlatform::IndexedPack& m, QJsonObject& obj);
void loadBody(ModPlatform::IndexedPack& m);
void loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj);
void loadURLs(ModPlatform::IndexedPack& pack, QJsonObject& obj);
void loadBody(ModPlatform::IndexedPack& pack);
void loadIndexedPackVersions(ModPlatform::IndexedPack& pack, QJsonArray& arr);
ModPlatform::IndexedVersion loadIndexedPackVersion(QJsonObject& obj, bool load_changelog = false);
ModPlatform::IndexedVersion loadIndexedPackVersion(QJsonObject& obj, bool loadChangelog = false);
} // namespace FlameMod

View file

@ -1,20 +1,21 @@
#include "PackManifest.h"
#include "Json.h"
static void loadFileV1(Flame::File& f, QJsonObject& file)
namespace {
void loadFileV1(Flame::File& f, QJsonObject& file)
{
f.projectId = Json::requireInteger(file, "projectID");
f.fileId = Json::requireInteger(file, "fileID");
f.required = file["required"].toBool(true);
}
static void loadModloaderV1(Flame::Modloader& m, QJsonObject& modLoader)
void loadModloaderV1(Flame::Modloader& m, QJsonObject& modLoader)
{
m.id = Json::requireString(modLoader, "id");
m.primary = modLoader["primary"].toBool();
}
static void loadMinecraftV1(Flame::Minecraft& m, QJsonObject& minecraft)
void loadMinecraftV1(Flame::Minecraft& m, QJsonObject& minecraft)
{
m.version = Json::requireString(minecraft, "version");
// extra libraries... apparently only used for a custom Minecraft launcher in the 1.2.5 FTB retro pack
@ -30,7 +31,7 @@ static void loadMinecraftV1(Flame::Minecraft& m, QJsonObject& minecraft)
m.recommendedRAM = minecraft["recommendedRam"].toInt();
}
static void loadManifestV1(Flame::Manifest& pack, QJsonObject& manifest)
void loadManifestV1(Flame::Manifest& pack, QJsonObject& manifest)
{
auto mc = Json::requireObject(manifest, "minecraft");
@ -52,8 +53,9 @@ static void loadManifestV1(Flame::Manifest& pack, QJsonObject& manifest)
pack.overrides = manifest["overrides"].toString("overrides");
pack.is_loaded = true;
pack.isLoaded = true;
}
} // namespace
void Flame::loadManifest(Flame::Manifest& m, const QString& filepath)
{

View file

@ -79,7 +79,7 @@ struct Manifest {
QMap<int, Flame::File> files;
QString overrides;
bool is_loaded = false;
bool isLoaded = false;
};
void loadManifest(Flame::Manifest& m, const QString& filepath);

View file

@ -12,93 +12,93 @@
#include "net/ApiUpload.h"
#include "net/NetJob.h"
std::pair<Task::Ptr, QByteArray*> ModrinthAPI::currentVersion(const QString& hash, const QString& hash_format) const
std::pair<Task::Ptr, QByteArray*> ModrinthAPI::currentVersion(const QString& hash, const QString& hashFormat)
{
auto netJob = makeShared<NetJob>(QString("Modrinth::GetCurrentVersion"), APPLICATION->network());
auto [action, response] =
Net::ApiDownload::makeByteArray(QString(BuildConfig.MODRINTH_PROD_URL + "/version_file/%1?algorithm=%2").arg(hash, hash_format));
Net::ApiDownload::makeByteArray(QString(BuildConfig.MODRINTH_PROD_URL + "/version_file/%1?algorithm=%2").arg(hash, hashFormat));
netJob->addNetAction(action);
return { netJob, response };
}
std::pair<Task::Ptr, QByteArray*> ModrinthAPI::currentVersions(const QStringList& hashes, QString hash_format) const
std::pair<Task::Ptr, QByteArray*> ModrinthAPI::currentVersions(const QStringList& hashes, const QString& hashFormat)
{
auto netJob = makeShared<NetJob>(QString("Modrinth::GetCurrentVersions"), APPLICATION->network());
QJsonObject body_obj;
QJsonObject bodyObj;
Json::writeStringList(body_obj, "hashes", hashes);
Json::writeString(body_obj, "algorithm", hash_format);
Json::writeStringList(bodyObj, "hashes", hashes);
Json::writeString(bodyObj, "algorithm", hashFormat);
QJsonDocument body(body_obj);
auto body_raw = body.toJson();
QJsonDocument body(bodyObj);
auto bodyRaw = body.toJson();
auto [action, response] = Net::ApiUpload::makeByteArray(QString(BuildConfig.MODRINTH_PROD_URL + "/version_files"), body_raw);
auto [action, response] = Net::ApiUpload::makeByteArray(QString(BuildConfig.MODRINTH_PROD_URL + "/version_files"), bodyRaw);
netJob->addNetAction(action);
netJob->setAskRetry(false);
return { netJob, response };
}
std::pair<Task::Ptr, QByteArray*> ModrinthAPI::latestVersion(const QString& hash,
const QString& hash_format,
const QString& hashFormat,
std::optional<std::vector<Version>> mcVersions,
std::optional<ModPlatform::ModLoaderTypes> loaders) const
{
auto netJob = makeShared<NetJob>(QString("Modrinth::GetLatestVersion"), APPLICATION->network());
QJsonObject body_obj;
QJsonObject bodyObj;
if (loaders.has_value()) {
Json::writeStringList(body_obj, "loaders", getModLoaderStrings(loaders.value()));
Json::writeStringList(bodyObj, "loaders", getModLoaderStrings(loaders.value()));
}
if (mcVersions.has_value()) {
QStringList game_versions;
QStringList gameVersions;
for (auto& ver : mcVersions.value()) {
game_versions.append(mapMCVersionToModrinth(ver));
gameVersions.append(mapMCVersionToModrinth(ver));
}
Json::writeStringList(body_obj, "game_versions", game_versions);
Json::writeStringList(bodyObj, "game_versions", gameVersions);
}
QJsonDocument body(body_obj);
auto body_raw = body.toJson();
QJsonDocument body(bodyObj);
auto bodyRaw = body.toJson();
auto [action, response] = Net::ApiUpload::makeByteArray(
QString(BuildConfig.MODRINTH_PROD_URL + "/version_file/%1/update?algorithm=%2").arg(hash, hash_format), body_raw);
QString(BuildConfig.MODRINTH_PROD_URL + "/version_file/%1/update?algorithm=%2").arg(hash, hashFormat), bodyRaw);
netJob->addNetAction(action);
return { netJob, response };
}
std::pair<Task::Ptr, QByteArray*> ModrinthAPI::latestVersions(const QStringList& hashes,
const QString& hash_format,
const QString& hashFormat,
std::optional<std::vector<Version>> mcVersions,
std::optional<ModPlatform::ModLoaderTypes> loaders) const
{
auto netJob = makeShared<NetJob>(QString("Modrinth::GetLatestVersions"), APPLICATION->network());
QJsonObject body_obj;
QJsonObject bodyObj;
Json::writeStringList(body_obj, "hashes", hashes);
Json::writeString(body_obj, "algorithm", hash_format);
Json::writeStringList(bodyObj, "hashes", hashes);
Json::writeString(bodyObj, "algorithm", hashFormat);
if (loaders.has_value()) {
Json::writeStringList(body_obj, "loaders", getModLoaderStrings(loaders.value()));
Json::writeStringList(bodyObj, "loaders", getModLoaderStrings(loaders.value()));
}
if (mcVersions.has_value()) {
QStringList game_versions;
QStringList gameVersions;
for (auto& ver : mcVersions.value()) {
game_versions.append(mapMCVersionToModrinth(ver));
gameVersions.append(mapMCVersionToModrinth(ver));
}
Json::writeStringList(body_obj, "game_versions", game_versions);
Json::writeStringList(bodyObj, "game_versions", gameVersions);
}
QJsonDocument body(body_obj);
auto body_raw = body.toJson();
auto [action, response] = Net::ApiUpload::makeByteArray(QString(BuildConfig.MODRINTH_PROD_URL + "/version_files/update"), body_raw);
QJsonDocument body(bodyObj);
auto bodyRaw = body.toJson();
auto [action, response] = Net::ApiUpload::makeByteArray(QString(BuildConfig.MODRINTH_PROD_URL + "/version_files/update"), bodyRaw);
netJob->addNetAction(action);
return { netJob, response };
@ -118,14 +118,14 @@ std::pair<Task::Ptr, QByteArray*> ModrinthAPI::getProjects(QStringList addonIds)
QList<ResourceAPI::SortingMethod> ModrinthAPI::getSortingMethods() const
{
// https://docs.modrinth.com/api-spec/#tag/projects/operation/searchProjects
return { { .index = 1, .name = "relevance", .readable_name = QObject::tr("Sort by Relevance") },
{ .index = 2, .name = "downloads", .readable_name = QObject::tr("Sort by Downloads") },
{ .index = 3, .name = "follows", .readable_name = QObject::tr("Sort by Follows") },
{ .index = 4, .name = "newest", .readable_name = QObject::tr("Sort by Newest") },
{ .index = 5, .name = "updated", .readable_name = QObject::tr("Sort by Last Updated") } };
return { { .index = 1, .name = "relevance", .readableName = QObject::tr("Sort by Relevance") },
{ .index = 2, .name = "downloads", .readableName = QObject::tr("Sort by Downloads") },
{ .index = 3, .name = "follows", .readableName = QObject::tr("Sort by Follows") },
{ .index = 4, .name = "newest", .readableName = QObject::tr("Sort by Newest") },
{ .index = 5, .name = "updated", .readableName = QObject::tr("Sort by Last Updated") } };
}
namespace {
const auto resourceTypeMap = std::array{
const auto g_resourceTypeMap = std::array{
std::pair{ ModPlatform::ResourceType::Mod, "mod" }, std::pair{ ModPlatform::ResourceType::ResourcePack, "resourcepack" },
std::pair{ ModPlatform::ResourceType::ShaderPack, "shader" }, std::pair{ ModPlatform::ResourceType::DataPack, "datapack" },
std::pair{ ModPlatform::ResourceType::Modpack, "modpack" },
@ -134,7 +134,7 @@ const auto resourceTypeMap = std::array{
ModPlatform::ResourceType ModrinthAPI::getResourceType(const QString& param)
{
for (const auto& [key, value] : resourceTypeMap) {
for (const auto& [key, value] : g_resourceTypeMap) {
if (value == param) {
return key;
}
@ -146,7 +146,7 @@ ModPlatform::ResourceType ModrinthAPI::getResourceType(const QString& param)
QString ModrinthAPI::resourceTypeParameter(ModPlatform::ResourceType type)
{
for (const auto& [key, value] : resourceTypeMap) {
for (const auto& [key, value] : g_resourceTypeMap) {
if (key == type) {
return value;
}
@ -169,11 +169,10 @@ std::pair<Task::Ptr, QByteArray*> ModrinthAPI::getModCategories()
QList<ModPlatform::Category> ModrinthAPI::loadCategories(const QByteArray& response, const QString& projectType)
{
QList<ModPlatform::Category> categories;
QJsonParseError parse_error{};
QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error);
if (parse_error.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response from categories at" << parse_error.offset
<< "reason:" << parse_error.errorString();
QJsonParseError parseError{};
QJsonDocument doc = QJsonDocument::fromJson(response, &parseError);
if (parseError.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response from categories at" << parseError.offset << "reason:" << parseError.errorString();
qWarning() << *response;
return categories;
}

View file

@ -10,21 +10,24 @@
#include "modplatform/modrinth/ModrinthPackIndex.h"
#include <QDebug>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <utility>
class ModrinthAPI : public ResourceAPI {
public:
std::pair<Task::Ptr, QByteArray*> currentVersion(const QString& hash, const QString& hash_format) const;
static std::pair<Task::Ptr, QByteArray*> currentVersion(const QString& hash, const QString& hashFormat);
std::pair<Task::Ptr, QByteArray*> currentVersions(const QStringList& hashes, QString hash_format) const;
static std::pair<Task::Ptr, QByteArray*> currentVersions(const QStringList& hashes, const QString& hashFormat);
std::pair<Task::Ptr, QByteArray*> latestVersion(const QString& hash,
const QString& hash_format,
const QString& hashFormat,
std::optional<std::vector<Version>> mcVersions,
std::optional<ModPlatform::ModLoaderTypes> loaders) const;
std::pair<Task::Ptr, QByteArray*> latestVersions(const QStringList& hashes,
const QString& hash_format,
const QString& hashFormat,
std::optional<std::vector<Version>> mcVersions,
std::optional<ModPlatform::ModLoaderTypes> loaders) const;
@ -107,30 +110,30 @@ class ModrinthAPI : public ResourceAPI {
QString createFacets(const SearchArgs& args) const
{
QStringList facets_list;
QStringList facetsList;
if (args.loaders.has_value() && args.loaders.value() != 0) {
facets_list.append(QString("[%1]").arg(getModLoaderFilters(args.loaders.value())));
facetsList.append(QString("[%1]").arg(getModLoaderFilters(args.loaders.value())));
}
if (args.versions.has_value() && !args.versions.value().empty()) {
facets_list.append(QString("[%1]").arg(getGameVersionsArray(args.versions.value())));
facetsList.append(QString("[%1]").arg(getGameVersionsArray(args.versions.value())));
}
if (args.side.has_value()) {
auto side = getSideFilters(args.side.value());
if (!side.isEmpty()) {
facets_list.append(QString("[%1]").arg(side));
facetsList.append(QString("[%1]").arg(side));
}
}
if (args.categoryIds.has_value() && !args.categoryIds->empty()) {
facets_list.append(QString("[%1]").arg(getCategoriesFilters(args.categoryIds.value())));
facetsList.append(QString("[%1]").arg(getCategoriesFilters(args.categoryIds.value())));
}
if (args.openSource) {
facets_list.append("[\"open_source:true\"]");
facetsList.append("[\"open_source:true\"]");
}
facets_list.append(QString("[\"project_type:%1\"]").arg(resourceTypeParameter(args.type)));
facetsList.append(QString("[\"project_type:%1\"]").arg(resourceTypeParameter(args.type)));
return QString("[%1]").arg(facets_list.join(','));
return QString("[%1]").arg(facetsList.join(','));
}
public:
@ -143,18 +146,18 @@ class ModrinthAPI : public ResourceAPI {
}
}
QStringList get_arguments;
get_arguments.append(QString("offset=%1").arg(args.offset));
get_arguments.append(QString("limit=25"));
QStringList getArguments;
getArguments.append(QString("offset=%1").arg(args.offset));
getArguments.append(QString("limit=25"));
if (args.search.has_value()) {
get_arguments.append(QString("query=%1").arg(args.search.value()));
getArguments.append(QString("query=%1").arg(args.search.value()));
}
if (args.sorting.has_value()) {
get_arguments.append(QString("index=%1").arg(args.sorting.value().name));
getArguments.append(QString("index=%1").arg(args.sorting.value().name));
}
get_arguments.append(QString("facets=%1").arg(createFacets(args)));
getArguments.append(QString("facets=%1").arg(createFacets(args)));
return BuildConfig.MODRINTH_PROD_URL + "/search?" + get_arguments.join('&');
return BuildConfig.MODRINTH_PROD_URL + "/search?" + getArguments.join('&');
};
auto getInfoURL(const QString& id) const -> std::optional<QString> override
@ -162,24 +165,24 @@ class ModrinthAPI : public ResourceAPI {
return BuildConfig.MODRINTH_PROD_URL + "/project/" + id;
};
auto getMultipleModInfoURL(const QStringList& ids) const -> QString
static auto getMultipleModInfoURL(const QStringList& ids) -> QString
{
return BuildConfig.MODRINTH_PROD_URL + QString("/projects?ids=[\"%1\"]").arg(ids.join("\",\""));
};
auto getVersionsURL(const VersionSearchArgs& args) const -> std::optional<QString> override
{
QStringList get_arguments;
QStringList getArguments;
if (args.mcVersions.has_value()) {
get_arguments.append(QString("game_versions=[%1]").arg(getGameVersionsString(args.mcVersions.value())));
getArguments.append(QString("game_versions=[%1]").arg(getGameVersionsString(args.mcVersions.value())));
}
if (args.loaders.has_value()) {
get_arguments.append(QString("loaders=[\"%1\"]").arg(getModLoaderStrings(args.loaders.value()).join("\",\"")));
getArguments.append(QString("loaders=[\"%1\"]").arg(getModLoaderStrings(args.loaders.value()).join("\",\"")));
}
get_arguments.append(QString("include_changelog=%1").arg(args.includeChangelog ? "true" : "false"));
getArguments.append(QString("include_changelog=%1").arg(args.includeChangelog ? "true" : "false"));
return QString("%1/project/%2/version%3%4")
.arg(BuildConfig.MODRINTH_PROD_URL, args.pack->addonId.toString(), get_arguments.isEmpty() ? "" : "?", get_arguments.join('&'));
.arg(BuildConfig.MODRINTH_PROD_URL, args.pack->addonId.toString(), getArguments.isEmpty() ? "" : "?", getArguments.join('&'));
};
QString getGameVersionsArray(const std::vector<Version>& mcVersions) const

View file

@ -178,7 +178,7 @@ void ModrinthCheckUpdate::checkVersionsResponse(QByteArray* response, std::optio
pack->slug = resource->metadata()->slug;
pack->addonId = resource->metadata()->project_id;
pack->provider = ModPlatform::ResourceProvider::MODRINTH;
if ((projectVer.hash != hash && projectVer.is_preferred) || (resource->status() == ResourceStatus::NotInstalled)) {
if ((projectVer.hash != hash && projectVer.isPreferred) || (resource->status() == ResourceStatus::NotInstalled)) {
auto downloadTask = makeShared<ResourceDownloadTask>(pack, projectVer, m_resourceModel, true, "update");
QString oldVersion = resource->metadata()->version_number;
@ -190,8 +190,8 @@ void ModrinthCheckUpdate::checkVersionsResponse(QByteArray* response, std::optio
}
}
m_updates.emplace_back(pack->name, hash, oldVersion, projectVer.version_number, projectVer.version_type,
projectVer.changelog, ModPlatform::ResourceProvider::MODRINTH, downloadTask, resource->enabled());
m_updates.emplace_back(pack->name, hash, oldVersion, projectVer.versionNumber, projectVer.versionType, projectVer.changelog,
ModPlatform::ResourceProvider::MODRINTH, downloadTask, resource->enabled());
}
m_deps.append(std::make_shared<GetModDependenciesTask::PackDependency>(pack, projectVer));

View file

@ -22,14 +22,14 @@
#include "ModrinthAPI.h"
#include "Json.h"
#include "minecraft/MinecraftInstance.h"
#include "minecraft/PackProfile.h"
#include "modplatform/ModIndex.h"
namespace {
bool shouldDownloadOnSide(const QString& side)
{
return side == "required" || side == "optional";
}
} // namespace
// https://docs.modrinth.com/api/operations/getproject/
void Modrinth::loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj)
@ -80,31 +80,34 @@ void Modrinth::loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj)
void Modrinth::loadExtraPackData(ModPlatform::IndexedPack& pack, QJsonObject& obj)
{
pack.extraData.issuesUrl = obj["issues_url"].toString();
if (pack.extraData.issuesUrl.endsWith('/'))
if (pack.extraData.issuesUrl.endsWith('/')) {
pack.extraData.issuesUrl.chop(1);
}
pack.extraData.sourceUrl = obj["source_url"].toString();
if (pack.extraData.sourceUrl.endsWith('/'))
if (pack.extraData.sourceUrl.endsWith('/')) {
pack.extraData.sourceUrl.chop(1);
}
pack.extraData.wikiUrl = obj["wiki_url"].toString();
if (pack.extraData.wikiUrl.endsWith('/'))
if (pack.extraData.wikiUrl.endsWith('/')) {
pack.extraData.wikiUrl.chop(1);
}
pack.extraData.discordUrl = obj["discord_url"].toString();
if (pack.extraData.discordUrl.endsWith('/')) {
pack.extraData.discordUrl.chop(1);
}
auto donate_arr = obj["donation_urls"].toArray();
for (auto d : donate_arr) {
auto d_obj = Json::requireObject(d);
auto donateArr = obj["donation_urls"].toArray();
for (auto d : donateArr) {
auto dObj = Json::requireObject(d);
ModPlatform::DonationData donate;
donate.id = d_obj["id"].toString();
donate.platform = d_obj["platform"].toString();
donate.url = d_obj["url"].toString();
donate.id = dObj["id"].toString();
donate.platform = dObj["platform"].toString();
donate.url = dObj["url"].toString();
pack.extraData.donate.append(donate);
}
@ -117,8 +120,8 @@ void Modrinth::loadExtraPackData(ModPlatform::IndexedPack& pack, QJsonObject& ob
}
ModPlatform::IndexedVersion Modrinth::loadIndexedPackVersion(QJsonObject& obj,
const QString& preferred_hash_type,
const QString& preferred_file_name)
const QString& preferredHashType,
const QString& preferredFileName)
{
ModPlatform::IndexedVersion file;
@ -150,8 +153,8 @@ ModPlatform::IndexedVersion Modrinth::loadIndexedPackVersion(QJsonObject& obj,
}
}
file.version = Json::requireString(obj, "name");
file.version_number = Json::requireString(obj, "version_number");
file.version_type = ModPlatform::IndexedVersionType::fromString(Json::requireString(obj, "version_type"));
file.versionNumber = Json::requireString(obj, "version_number");
file.versionType = ModPlatform::IndexedVersionType::fromString(Json::requireString(obj, "version_type"));
if (obj.contains("changelog")) {
file.changelog = Json::requireString(obj, "changelog");
@ -197,8 +200,8 @@ ModPlatform::IndexedVersion Modrinth::loadIndexedPackVersion(QJsonObject& obj,
auto parent = files[i].toObject();
auto fileName = Json::requireString(parent, "filename");
if (!preferred_file_name.isEmpty() && fileName.contains(preferred_file_name)) {
file.is_preferred = true;
if (!preferredFileName.isEmpty() && fileName.contains(preferredFileName)) {
file.isPreferred = true;
break;
}
@ -215,18 +218,18 @@ ModPlatform::IndexedVersion Modrinth::loadIndexedPackVersion(QJsonObject& obj,
file.downloadUrl = Json::requireString(parent, "url");
file.fileName = Json::requireString(parent, "filename");
file.fileName = FS::RemoveInvalidPathChars(file.fileName);
file.is_preferred = Json::requireBoolean(parent, "primary") || (files.count() == 1);
auto hash_list = Json::requireObject(parent, "hashes");
file.isPreferred = Json::requireBoolean(parent, "primary") || (files.count() == 1);
auto hashList = Json::requireObject(parent, "hashes");
if (hash_list.contains(preferred_hash_type)) {
file.hash = Json::requireString(hash_list, preferred_hash_type);
file.hash_type = preferred_hash_type;
if (hashList.contains(preferredHashType)) {
file.hash = Json::requireString(hashList, preferredHashType);
file.hashType = preferredHashType;
} else {
auto hash_types = ModPlatform::ProviderCapabilities::hashType(ModPlatform::ResourceProvider::MODRINTH);
for (auto& hash_type : hash_types) {
if (hash_list.contains(hash_type)) {
file.hash = Json::requireString(hash_list, hash_type);
file.hash_type = hash_type;
auto hashTypes = ModPlatform::ProviderCapabilities::hashType(ModPlatform::ResourceProvider::MODRINTH);
for (auto& hashType : hashTypes) {
if (hashList.contains(hashType)) {
file.hash = Json::requireString(hashList, hashType);
file.hashType = hashType;
break;
}
}

View file

@ -19,13 +19,11 @@
#include "modplatform/ModIndex.h"
#include "BaseInstance.h"
namespace Modrinth {
void loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj);
void loadExtraPackData(ModPlatform::IndexedPack& pack, QJsonObject& obj);
auto loadIndexedPackVersion(QJsonObject& obj, const QString& preferred_hash_type = "sha512", const QString& preferred_file_name = "")
auto loadIndexedPackVersion(QJsonObject& obj, const QString& preferredHashType = "sha512", const QString& preferredFileName = "")
-> ModPlatform::IndexedVersion;
} // namespace Modrinth

View file

@ -122,7 +122,7 @@ auto V1::createModFormat([[maybe_unused]] const QDir& index_dir,
mod.url = mod_version.downloadUrl;
}
mod.hash_format = mod_version.hash_type;
mod.hash_format = mod_version.hashType;
mod.hash = mod_version.hash;
mod.provider = mod_pack.provider;
@ -133,9 +133,9 @@ auto V1::createModFormat([[maybe_unused]] const QDir& index_dir,
mod.mcVersions = mod_version.mcVersion;
mod.mcVersions.removeDuplicates();
std::ranges::sort(mod.mcVersions, sortMCVersions);
mod.releaseType = mod_version.version_type;
mod.releaseType = mod_version.versionType;
mod.version_number = mod_version.version_number;
mod.version_number = mod_version.versionNumber;
if (mod.version_number.isNull()) // on CurseForge, there is only a version name - not a version number
mod.version_number = mod_version.version;

View file

@ -1126,7 +1126,7 @@ void MainWindow::processURLs(QList<QUrl> urls)
auto type = ResourceUtils::identify(localFileInfo);
if (ModPlatform::ResourceTypeUtils::VALID_RESOURCES.count(type) == 0) { // probably instance/modpack
if (ModPlatform::ResourceTypeUtils::g_VALID_RESOURCES.count(type) == 0) { // probably instance/modpack
addInstance(localFileName, extra_info);
continue;
}

View file

@ -56,9 +56,9 @@ ResourceDownloadDialog::ResourceDownloadDialog(QWidget* parent,
BaseInstance* inst,
bool suppressInitialSearch)
: QDialog(parent)
, m_base_model(baseModel)
, m_baseModel(baseModel)
, m_buttons(QDialogButtonBox::Help | QDialogButtonBox::Ok | QDialogButtonBox::Cancel)
, m_vertical_layout(this)
, m_verticalLayout(this)
, m_suppressInitialSearch(suppressInitialSearch)
, m_instance(inst)
{
@ -135,7 +135,7 @@ void ResourceDownloadDialog::initializeContainer()
m_container = new PageContainer(this, {}, this);
m_container->setSizePolicy(QSizePolicy::Policy::Preferred, QSizePolicy::Policy::Expanding);
m_container->layout()->setContentsMargins(0, 0, 0, 0);
m_vertical_layout.addWidget(m_container);
m_verticalLayout.addWidget(m_container);
m_container->addButtons(&m_buttons);
@ -206,7 +206,7 @@ void ResourceDownloadDialog::confirm()
.filename = task->getFilename(),
.provider = ModPlatform::ProviderCapabilities::name(task->getProvider()),
.required_by = extraInfo.requiredBy,
.version_type = task->getVersion().version_type.toString(),
.version_type = task->getVersion().versionType.toString(),
.enabled = !extraInfo.maybeInstalled });
}

View file

@ -68,7 +68,7 @@ class ResourceDownloadDialog : public QDialog, public BasePageProvider {
void removeResource(const QString&);
QList<DownloadTaskPtr> getTasks();
ResourceFolderModel* getBaseModel() const { return m_base_model; }
ResourceFolderModel* getBaseModel() const { return m_baseModel; }
void setResourceMetadata(const std::shared_ptr<Metadata::ModStruct>& meta);
@ -88,12 +88,12 @@ class ResourceDownloadDialog : public QDialog, public BasePageProvider {
virtual GetModDependenciesTask::Ptr getModDependenciesTask() { return nullptr; }
protected:
ResourceFolderModel* m_base_model;
ResourceFolderModel* m_baseModel;
PageContainer* m_container = nullptr;
QDialogButtonBox m_buttons;
QVBoxLayout m_vertical_layout;
QVBoxLayout m_verticalLayout;
bool m_suppressInitialSearch = false;
BaseInstance* m_instance = nullptr;

View file

@ -243,7 +243,7 @@ void ResourceUpdateDialog::checkCandidates()
auto downloadTask = makeShared<ResourceDownloadTask>(dep->pack, dep->version, m_resourceModel, true, "dependency");
auto extraInfo = dependencyExtraInfo.value(dep->version.addonId.toString());
CheckUpdateTask::Update updatable = {
dep->pack->name, dep->version.hash, tr("Not installed"), dep->version.version, dep->version.version_type,
dep->pack->name, dep->version.hash, tr("Not installed"), dep->version.version, dep->version.versionType,
changelog, dep->pack->provider, downloadTask, !extraInfo.maybeInstalled,
};
@ -412,6 +412,7 @@ void ResourceUpdateDialog::onMetadataEnsured(Resource* resource)
}
}
namespace {
ModPlatform::ResourceProvider next(ModPlatform::ResourceProvider p)
{
switch (p) {
@ -423,6 +424,7 @@ ModPlatform::ResourceProvider next(ModPlatform::ResourceProvider p)
return ModPlatform::ResourceProvider::FLAME;
}
} // namespace
void ResourceUpdateDialog::onMetadataFailed(Resource* resource, bool tryOthers, ModPlatform::ResourceProvider firstChoice)
{

View file

@ -128,7 +128,8 @@ ManagedPackPage::ManagedPackPage(BaseInstance* inst, InstanceWindow* instance_wi
QDesktopServices::openUrl(url);
});
connect(ui->urlLine, &QLineEdit::textChanged, this, [this](QString text) { m_inst->settings()->set("ManagedPackURL", text.trimmed()); });
connect(ui->urlLine, &QLineEdit::textChanged, this,
[this](QString text) { m_inst->settings()->set("ManagedPackURL", text.trimmed()); });
}
ManagedPackPage::~ManagedPackPage()
@ -273,7 +274,7 @@ void ModrinthManagedPackPage::parseManagedPack()
m_pack = { .addonId = m_inst->getManagedPackID() };
// Use default if no callbacks are set
callbacks.on_succeed = [this](auto& doc) {
callbacks.onSucceed = [this](auto& doc) {
m_pack.versions = doc;
m_pack.versionsLoaded = true;
@ -298,8 +299,8 @@ void ModrinthManagedPackPage::parseManagedPack()
m_loaded = true;
};
callbacks.on_fail = [this](const QString& /*reason*/, int) { setFailState(); };
callbacks.on_abort = [this]() { setFailState(); };
callbacks.onFail = [this](const QString& /*reason*/, int) { setFailState(); };
callbacks.onAbort = [this]() { setFailState(); };
m_fetch_job = m_api.getProjectVersions({ .pack = std::make_shared<ModPlatform::IndexedPack>(m_pack),
.mcVersions = {},
.loaders = {},
@ -428,7 +429,7 @@ void FlameManagedPackPage::parseManagedPack()
ResourceAPI::Callback<QVector<ModPlatform::IndexedVersion>> callbacks{};
// Use default if no callbacks are set
callbacks.on_succeed = [this](auto& doc) {
callbacks.onSucceed = [this](auto& doc) {
m_pack.versions = doc;
m_pack.versionsLoaded = true;
@ -451,8 +452,8 @@ void FlameManagedPackPage::parseManagedPack()
m_loaded = true;
};
callbacks.on_fail = [this](const QString& /*reason*/, int) { setFailState(); };
callbacks.on_abort = [this]() { setFailState(); };
callbacks.onFail = [this](const QString& /*reason*/, int) { setFailState(); };
callbacks.onAbort = [this]() { setFailState(); };
m_fetch_job = m_api.getProjectVersions({ .pack = std::make_shared<ModPlatform::IndexedPack>(m_pack),
.mcVersions = {},
.loaders = {},

View file

@ -22,7 +22,7 @@ ModModel::ModModel(BaseInstance& base_inst, ResourceAPI* api, QString debugName,
ResourceAPI::SearchArgs ModModel::createSearchArguments()
{
auto profile = static_cast<MinecraftInstance const&>(m_base_instance).getPackProfile();
auto profile = static_cast<const MinecraftInstance&>(m_base_instance).getPackProfile();
Q_ASSERT(profile);
Q_ASSERT(m_filter);
@ -50,7 +50,7 @@ ResourceAPI::SearchArgs ModModel::createSearchArguments()
ResourceAPI::VersionSearchArgs ModModel::createVersionsArguments(const QModelIndex& entry)
{
auto pack = m_packs[entry.row()];
auto profile = static_cast<MinecraftInstance const&>(m_base_instance).getPackProfile();
auto profile = static_cast<const MinecraftInstance&>(m_base_instance).getPackProfile();
Q_ASSERT(profile);
Q_ASSERT(m_filter);
@ -128,7 +128,7 @@ bool ModModel::checkVersionFilters(const ModPlatform::IndexedVersion& v)
(!loaders.has_value() || !v.loaders || loaders.value() & v.loaders) && // loaders
checkSide(m_filter->side, v.side) && // side
(m_filter->releases.empty() || // releases
std::find(m_filter->releases.cbegin(), m_filter->releases.cend(), v.version_type) != m_filter->releases.cend()) &&
std::find(m_filter->releases.cbegin(), m_filter->releases.cend(), v.versionType) != m_filter->releases.cend()) &&
m_filter->checkMcVersions(v.mcVersion)); // mcVersions
}

View file

@ -148,7 +148,7 @@ void ResourceModel::search()
if (!projectId.isEmpty()) {
ResourceAPI::Callback<ModPlatform::IndexedPack::Ptr> callbacks;
callbacks.on_fail = [this](QString reason, int networkErrorCode) {
callbacks.onFail = [this](QString reason, int networkErrorCode) {
if (!s_running_models.constFind(this).value()) {
return;
}
@ -157,14 +157,14 @@ void ResourceModel::search()
}
searchRequestFailed(std::move(reason), networkErrorCode);
};
callbacks.on_abort = [this] {
callbacks.onAbort = [this] {
if (!s_running_models.constFind(this).value()) {
return;
}
searchRequestAborted();
};
callbacks.on_succeed = [this](auto& pack) {
callbacks.onSucceed = [this](auto& pack) {
if (!s_running_models.constFind(this).value()) {
return;
}
@ -182,19 +182,19 @@ void ResourceModel::search()
ResourceAPI::Callback<QList<ModPlatform::IndexedPack::Ptr>> callbacks{};
callbacks.on_succeed = [this](auto& doc) {
callbacks.onSucceed = [this](auto& doc) {
if (!s_running_models.constFind(this).value()) {
return;
}
searchRequestSucceeded(doc);
};
callbacks.on_fail = [this](QString reason, int networkErrorCode) {
callbacks.onFail = [this](QString reason, int networkErrorCode) {
if (!s_running_models.constFind(this).value()) {
return;
}
searchRequestFailed(std::move(reason), networkErrorCode);
};
callbacks.on_abort = [this] {
callbacks.onAbort = [this] {
if (!s_running_models.constFind(this).value()) {
return;
}
@ -220,16 +220,16 @@ void ResourceModel::loadEntry(const QModelIndex& entry)
auto addonId = pack->addonId;
// Use default if no callbacks are set
if (!callbacks.on_succeed) {
callbacks.on_succeed = [this, entry, addonId](auto& doc) {
if (!callbacks.onSucceed) {
callbacks.onSucceed = [this, entry, addonId](auto& doc) {
if (!s_running_models.constFind(this).value()) {
return;
}
versionRequestSucceeded(doc, addonId, entry);
};
}
if (!callbacks.on_fail) {
callbacks.on_fail = [](const QString& reason, int) {
if (!callbacks.onFail) {
callbacks.onFail = [](const QString& reason, int) {
QMessageBox::critical(nullptr, tr("Error"),
tr("A network error occurred. Could not load project versions: %1").arg(reason));
};
@ -244,19 +244,19 @@ void ResourceModel::loadEntry(const QModelIndex& entry)
auto args{ createInfoArguments(entry) };
ResourceAPI::Callback<ModPlatform::IndexedPack::Ptr> callbacks{};
callbacks.on_succeed = [this, entry](auto& newpack) {
callbacks.onSucceed = [this, entry](auto& newpack) {
if (!s_running_models.constFind(this).value()) {
return;
}
infoRequestSucceeded(newpack, entry);
};
callbacks.on_fail = [this](const QString& reason, int) {
callbacks.onFail = [this](const QString& reason, int) {
if (!s_running_models.constFind(this).value()) {
return;
}
QMessageBox::critical(nullptr, tr("Error"), tr("A network error occurred. Could not load project info: %1").arg(reason));
};
callbacks.on_abort = [this] {
callbacks.onAbort = [this] {
if (!s_running_models.constFind(this).value()) {
return;
}
@ -521,7 +521,7 @@ void ResourceModel::addPack(ModPlatform::IndexedPack::Ptr pack,
bool isIndexed,
QString downloadReason)
{
version.is_currently_selected = true;
version.isCurrentlySelected = true;
m_selected.append(makeShared<ResourceDownloadTask>(std::move(pack), version, packs, isIndexed, std::move(downloadReason)));
}
@ -547,7 +547,7 @@ void ResourceModel::removePack(const QString& rem)
return;
}
for (auto& ver : pack->get()->versions) {
ver.is_currently_selected = false;
ver.isCurrentlySelected = false;
}
}

View file

@ -139,7 +139,7 @@ class ResourceModel : public QAbstractListModel {
/* Default search request callbacks */
void searchRequestSucceeded(QList<ModPlatform::IndexedPack::Ptr>&);
void searchRequestForOneSucceeded(ModPlatform::IndexedPack::Ptr);
void searchRequestFailed(QString reason, int network_error_code);
void searchRequestFailed(QString reason, int networkErrorCode);
void searchRequestAborted();
void versionRequestSucceeded(QVector<ModPlatform::IndexedVersion>&, QVariant, const QModelIndex&);

View file

@ -182,7 +182,7 @@ void ResourcePage::addSortings()
std::ranges::sort(sorts, [](const auto& l, const auto& r) { return l.index < r.index; });
for (auto&& sorting : sorts) {
m_ui->sortByBox->addItem(sorting.readable_name, QVariant(sorting.index));
m_ui->sortByBox->addItem(sorting.readableName, QVariant(sorting.index));
}
}
@ -318,8 +318,8 @@ void ResourcePage::versionListUpdated(const QModelIndex& index)
}
auto versionText = version.version;
if (version.version_type.isValid()) {
versionText += QString(" [%1]").arg(version.version_type.toString());
if (version.versionType.isValid()) {
versionText += QString(" [%1]").arg(version.versionType.toString());
}
if (version.fileId == installedVersion) {
versionText += tr(" [installed]", "Mod version select");
@ -427,7 +427,7 @@ void ResourcePage::onResourceSelected()
auto& version = currentPack->versions[m_selectedVersionIndex];
Q_ASSERT(!version.downloadUrl.isNull());
if (version.is_currently_selected) {
if (version.isCurrentlySelected) {
removeResourceFromDialog(currentPack->name);
} else {
addResourceToDialog(currentPack, version);

View file

@ -173,14 +173,14 @@ void ListModel::performPaginatedSearch()
if (!projectId.isEmpty()) {
ResourceAPI::Callback<ModPlatform::IndexedPack::Ptr> callbacks;
callbacks.on_fail = [this](QString reason, int network_error_code) {
if (network_error_code == 404) {
callbacks.onFail = [this](QString reason, int networkErrorCode) {
if (networkErrorCode == 404) {
m_searchState = ResetRequested;
}
searchRequestFailed(reason);
};
callbacks.on_succeed = [this](auto& pack) { searchRequestForOneSucceeded(pack); };
callbacks.on_abort = [this] {
callbacks.onSucceed = [this](auto& pack) { searchRequestForOneSucceeded(pack); };
callbacks.onAbort = [this] {
qCritical() << "Search task aborted by an unknown reason!";
searchRequestFailed("Aborted");
};
@ -198,9 +198,9 @@ void ListModel::performPaginatedSearch()
ResourceAPI::Callback<QList<ModPlatform::IndexedPack::Ptr>> callbacks{};
callbacks.on_succeed = [this](auto& doc) { searchRequestFinished(doc); };
callbacks.on_fail = [this](QString reason, int) { searchRequestFailed(reason); };
callbacks.on_abort = [this] {
callbacks.onSucceed = [this](auto& doc) { searchRequestFinished(doc); };
callbacks.onFail = [this](QString reason, int) { searchRequestFailed(reason); };
callbacks.onAbort = [this] {
qCritical() << "Search task aborted by an unknown reason!";
searchRequestFailed("Aborted");
};

View file

@ -163,7 +163,7 @@ void FlamePage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelInde
auto addonId = m_current->addonId;
// Use default if no callbacks are set
callbacks.on_succeed = [this, curr, addonId](auto& doc) {
callbacks.onSucceed = [this, curr, addonId](auto& doc) {
if (addonId != m_current->addonId) {
return; // wrong request
}
@ -200,7 +200,7 @@ void FlamePage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelInde
}
suggestCurrent();
};
callbacks.on_fail = [this](QString reason, int) {
callbacks.onFail = [this](QString reason, int) {
CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec();
};

View file

@ -141,14 +141,14 @@ void ModpackListModel::performPaginatedSearch()
if (!projectId.isEmpty()) {
ResourceAPI::Callback<ModPlatform::IndexedPack::Ptr> callbacks;
callbacks.on_fail = [this](QString reason, int network_error_code) {
if (network_error_code == 404) {
callbacks.onFail = [this](QString reason, int networkErrorCode) {
if (networkErrorCode == 404) {
m_searchState = ResetRequested;
}
searchRequestFailed(reason, network_error_code);
searchRequestFailed(reason, networkErrorCode);
};
callbacks.on_succeed = [this](auto& pack) { searchRequestForOneSucceeded(pack); };
callbacks.on_abort = [this] {
callbacks.onSucceed = [this](auto& pack) { searchRequestForOneSucceeded(pack); };
callbacks.onAbort = [this] {
qCritical() << "Search task aborted by an unknown reason!";
searchRequestFailed("Aborted", 0);
};
@ -166,9 +166,9 @@ void ModpackListModel::performPaginatedSearch()
ResourceAPI::Callback<QList<ModPlatform::IndexedPack::Ptr>> callbacks{};
callbacks.on_succeed = [this](auto& doc) { searchRequestFinished(doc); };
callbacks.on_fail = [this](QString reason, int network_error_code) { searchRequestFailed(reason, network_error_code); };
callbacks.on_abort = [this] {
callbacks.onSucceed = [this](auto& doc) { searchRequestFinished(doc); };
callbacks.onFail = [this](QString reason, int networkErrorCode) { searchRequestFailed(reason, networkErrorCode); };
callbacks.onAbort = [this] {
qCritical() << "Search task aborted by an unknown reason!";
searchRequestFailed("Aborted", 0);
};
@ -322,12 +322,12 @@ void ModpackListModel::searchRequestForOneSucceeded(ModPlatform::IndexedPack::Pt
endInsertRows();
}
void ModpackListModel::searchRequestFailed(QString reason, int network_error_code)
void ModpackListModel::searchRequestFailed(QString reason, int networkErrorCode)
{
if (network_error_code == -1) {
if (networkErrorCode == -1) {
// Unknown error in network stack
QMessageBox::critical(nullptr, tr("Error"), tr("A network error occurred. Could not load modpacks."));
} else if (network_error_code == 409) {
} else if (networkErrorCode == 409) {
// 409 Gone, notify user to update
QMessageBox::critical(nullptr, tr("Error"),
//: %1 refers to the launcher itself

View file

@ -85,7 +85,7 @@ class ModpackListModel : public QAbstractListModel {
public slots:
void searchRequestFinished(QList<ModPlatform::IndexedPack::Ptr>& doc_all);
void searchRequestFailed(QString reason, int network_error_code);
void searchRequestFailed(QString reason, int networkErrorCode);
void searchRequestForOneSucceeded(ModPlatform::IndexedPack::Ptr);
protected slots:

View file

@ -150,10 +150,10 @@ void ModrinthPage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelI
ResourceAPI::Callback<ModPlatform::IndexedPack::Ptr> callbacks;
auto id = m_current->addonId;
callbacks.on_fail = [this](QString reason, int) {
callbacks.onFail = [this](QString reason, int) {
CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec();
};
callbacks.on_succeed = [this, id, curr](auto& pack) {
callbacks.onSucceed = [this, id, curr](auto& pack) {
if (id != m_current->addonId) {
return; // wrong request?
}
@ -182,7 +182,7 @@ void ModrinthPage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelI
auto addonId = m_current->addonId;
// Use default if no callbacks are set
callbacks.on_succeed = [this, curr, addonId](auto& doc) {
callbacks.onSucceed = [this, curr, addonId](auto& doc) {
if (addonId != m_current->addonId) {
return; // wrong request
}
@ -215,7 +215,7 @@ void ModrinthPage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelI
suggestCurrent();
};
callbacks.on_fail = [this](QString reason, int) {
callbacks.onFail = [this](QString reason, int) {
CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec();
};
@ -227,7 +227,7 @@ void ModrinthPage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelI
} else {
for (auto version : m_current->versions) {
if (!version.version.contains(version.version))
m_ui->versionSelectionBox->addItem(QString("%1 - %2").arg(version.version, version.version_number),
m_ui->versionSelectionBox->addItem(QString("%1 - %2").arg(version.version, version.versionNumber),
QVariant(version.fileId));
else
m_ui->versionSelectionBox->addItem(version.version, QVariant(version.fileId));

View file

@ -86,7 +86,7 @@ class ModFilterWidget : public QTabWidget {
{
return ((!loaders || !v.loaders || loaders & v.loaders) && // loaders
(releases.empty() || // releases
std::find(releases.cbegin(), releases.cend(), v.version_type) != releases.cend()) &&
std::find(releases.cbegin(), releases.cend(), v.versionType) != releases.cend()) &&
checkMcVersions({ v.mcVersion })); // gameVersion}
}
};