Add some weird modrinth headers (#5506)

This commit is contained in:
Alexandru Ionut Tripon 2026-05-09 19:52:55 +00:00 committed by GitHub
commit 7fcadcd7a2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 695 additions and 511 deletions

View file

@ -19,23 +19,52 @@
#include "ResourceDownloadTask.h"
#include <utility>
#include "Application.h"
#include "FileSystem.h"
#include "minecraft/MinecraftInstance.h"
#include "minecraft/PackProfile.h"
#include "minecraft/mod/ResourceFolderModel.h"
#include "minecraft/mod/ShaderPackFolderModel.h"
#include "modplatform/ModIndex.h"
#include "modplatform/helpers/HashUtils.h"
#include "net/ApiDownload.h"
#include "net/ChecksumValidator.h"
namespace {
Net::ModrinthDownloadMeta createModrinthMeta(BaseInstance* instance, QString reason)
{
auto* mcInstance = dynamic_cast<MinecraftInstance*>(instance);
if (!mcInstance) {
return {};
}
auto* profile = mcInstance->getPackProfile();
if (!profile) {
return {};
}
auto loaders = profile->getModLoadersList();
return {
.reason = std::move(reason),
.gameVersion = profile->getComponentVersion("net.minecraft"),
.loader = !loaders.isEmpty() ? ModPlatform::getModLoaderAsString(loaders.first()) : "",
};
}
} // namespace
ResourceDownloadTask::ResourceDownloadTask(ModPlatform::IndexedPack::Ptr pack,
ModPlatform::IndexedVersion version,
ResourceFolderModel* packs,
bool is_indexed)
bool isIndexed,
QString downloadReason)
: m_pack(std::move(pack)), m_pack_version(std::move(version)), m_pack_model(packs)
{
if (is_indexed) {
if (isIndexed) {
m_update_task.reset(new LocalResourceUpdateTask(m_pack_model->indexDir(), *m_pack, m_pack_version));
connect(m_update_task.get(), &LocalResourceUpdateTask::hasOldResource, this, &ResourceDownloadTask::hasOldResource);
@ -45,7 +74,9 @@ ResourceDownloadTask::ResourceDownloadTask(ModPlatform::IndexedPack::Ptr pack,
m_filesNetJob.reset(new NetJob(tr("Resource download"), APPLICATION->network()));
m_filesNetJob->setStatus(tr("Downloading resource:\n%1").arg(m_pack_version.downloadUrl));
auto action = Net::ApiDownload::makeFile(m_pack_version.downloadUrl, m_pack_model->dir().absoluteFilePath(getFilename()));
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)) {
case Hashing::Algorithm::Md4:
@ -82,8 +113,9 @@ void ResourceDownloadTask::downloadSucceeded()
auto oldName = std::get<0>(to_delete);
auto oldFilename = std::get<1>(to_delete);
if (oldName.isEmpty() || oldFilename == m_pack_version.fileName)
if (oldName.isEmpty() || oldFilename == m_pack_version.fileName) {
return;
}
m_pack_model->uninstallResource(oldFilename, true);
@ -95,8 +127,9 @@ void ResourceDownloadTask::downloadSucceeded()
if (oldConfig.exists() && !newConfig.exists()) {
bool success = FS::move(oldConfig.filePath(), newConfig.filePath());
if (!success)
if (!success) {
emit logWarning(tr("Failed to rename shader config from '%1' to '%2'").arg(oldConfig.fileName(), newConfig.fileName()));
}
}
}
}
@ -104,7 +137,7 @@ void ResourceDownloadTask::downloadSucceeded()
void ResourceDownloadTask::downloadFailed(QString reason)
{
m_filesNetJob.reset();
emitFailed(reason);
emitFailed(std::move(reason));
}
void ResourceDownloadTask::downloadProgressChanged(qint64 current, qint64 total)
@ -114,7 +147,7 @@ void ResourceDownloadTask::downloadProgressChanged(qint64 current, qint64 total)
// This indirection is done so that we don't delete a mod before being sure it was
// downloaded successfully!
void ResourceDownloadTask::hasOldResource(QString name, QString filename)
void ResourceDownloadTask::hasOldResource(const QString& name, const QString& filename)
{
to_delete = { name, filename };
}

View file

@ -33,7 +33,8 @@ class ResourceDownloadTask : public SequentialTask {
explicit ResourceDownloadTask(ModPlatform::IndexedPack::Ptr pack,
ModPlatform::IndexedVersion version,
ResourceFolderModel* packs,
bool is_indexed = true);
bool isIndexed = true,
QString downloadReason = "standalone");
const QString& getFilename() const { return m_pack_version.fileName; }
const QVariant& getVersionID() const { return m_pack_version.fileId; }
const ModPlatform::IndexedVersion& getVersion() const { return m_pack_version; }
@ -56,5 +57,5 @@ class ResourceDownloadTask : public SequentialTask {
std::tuple<QString, QString> to_delete{ "", "" };
private slots:
void hasOldResource(QString name, QString filename);
void hasOldResource(const QString& name, const QString& filename);
};

View file

@ -183,6 +183,7 @@ class ResourceFolderModel : public QAbstractListModel {
};
QString instDirPath() const;
BaseInstance* instance() const { return m_instance; }
signals:
void updateFinished();

View file

@ -15,15 +15,15 @@ class CheckUpdateTask : public Task {
std::vector<Version>& mcVersions,
QList<ModPlatform::ModLoaderType> loadersList,
ResourceFolderModel* resourceModel)
: Task(), m_resources(resources), m_gameVersions(mcVersions), m_loadersList(std::move(loadersList)), m_resourceModel(resourceModel)
: m_resources(resources), m_gameVersions(mcVersions), m_loadersList(std::move(loadersList)), m_resourceModel(resourceModel)
{}
struct Update {
QString name;
QString old_hash;
QString old_version;
QString new_version;
std::optional<ModPlatform::IndexedVersionType> new_version_type;
QString oldHash;
QString oldVersion;
QString newVersion;
std::optional<ModPlatform::IndexedVersionType> newVersionType;
QString changelog;
ModPlatform::ResourceProvider provider;
shared_qobject_ptr<ResourceDownloadTask> download;
@ -31,19 +31,19 @@ class CheckUpdateTask : public Task {
public:
Update(QString name,
QString old_h,
QString old_v,
QString new_v,
std::optional<ModPlatform::IndexedVersionType> new_v_type,
QString oldH,
QString oldV,
QString newV,
std::optional<ModPlatform::IndexedVersionType> newVType,
QString changelog,
ModPlatform::ResourceProvider p,
shared_qobject_ptr<ResourceDownloadTask> t,
bool enabled = true)
: name(std::move(name))
, old_hash(std::move(old_h))
, old_version(std::move(old_v))
, new_version(std::move(new_v))
, new_version_type(std::move(new_v_type))
, oldHash(std::move(oldH))
, oldVersion(std::move(oldV))
, newVersion(std::move(newV))
, newVersionType(newVType)
, changelog(std::move(changelog))
, provider(p)
, download(std::move(t))
@ -54,14 +54,11 @@ class CheckUpdateTask : public Task {
auto getUpdates() -> std::vector<Update>&& { return std::move(m_updates); }
auto getDependencies() -> QList<std::shared_ptr<GetModDependenciesTask::PackDependency>>&& { return std::move(m_deps); }
public slots:
bool abort() override = 0;
protected slots:
void executeTask() override = 0;
signals:
void checkFailed(Resource* failed, QString reason, QUrl recover_url = {});
void checkFailed(Resource* failed, QString reason, QUrl recoverUrl = {});
protected:
QList<Resource*>& m_resources;

View file

@ -18,8 +18,6 @@
#include "net/NetJob.h"
#include "tasks/Task.h"
static FlameAPI api;
bool FlameCheckUpdate::abort()
{
bool result = false;
@ -39,7 +37,7 @@ void FlameCheckUpdate::executeTask()
{
setStatus(tr("Preparing resources for CurseForge..."));
auto netJob = new NetJob("Get latest versions", APPLICATION->network());
auto* netJob = new NetJob("Get latest versions", APPLICATION->network());
connect(netJob, &Task::finished, this, &FlameCheckUpdate::collectBlockedMods);
connect(netJob, &Task::progress, this, &FlameCheckUpdate::setProgress);
@ -48,9 +46,10 @@ void FlameCheckUpdate::executeTask()
for (auto* resource : m_resources) {
auto project = std::make_shared<ModPlatform::IndexedPack>();
project->addonId = resource->metadata()->project_id.toString();
auto versionsUrlOptional = api.getVersionsURL({ project, m_gameVersions });
if (!versionsUrlOptional.has_value())
auto versionsUrlOptional = FlameAPI().getVersionsURL({ .pack = project, .mcVersions = m_gameVersions });
if (!versionsUrlOptional.has_value()) {
continue;
}
auto [task, response] = Net::ApiDownload::makeByteArray(versionsUrlOptional.value());
@ -63,11 +62,11 @@ void FlameCheckUpdate::executeTask()
void FlameCheckUpdate::getLatestVersionCallback(Resource* resource, QByteArray* response)
{
QJsonParseError parse_error{};
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
if (parse_error.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response from latest mod 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 from latest mod version at" << parseError.offset
<< "reason:" << parseError.errorString();
qWarning() << *response;
return;
}
@ -88,100 +87,104 @@ void FlameCheckUpdate::getLatestVersionCallback(Resource* resource, QByteArray*
qCritical() << e.what();
qDebug() << doc;
}
auto latest_ver = api.getLatestVersion(pack->versions, m_loadersList, resource->metadata()->loaders, !m_loadersList.isEmpty());
auto latestVer = FlameAPI().getLatestVersion(pack->versions, m_loadersList, resource->metadata()->loaders, !m_loadersList.isEmpty());
setStatus(tr("Parsing the API response from CurseForge for '%1'...").arg(resource->name()));
if (!latest_ver.has_value() || !latest_ver->addonId.isValid()) {
if (!latestVer.has_value() || !latestVer->addonId.isValid()) {
QString reason;
if (dynamic_cast<Mod*>(resource) != nullptr)
if (dynamic_cast<Mod*>(resource) != nullptr) {
reason =
tr("No valid version found for this resource. It's probably unavailable for the current game "
"version / mod loader.");
else
} else {
reason = tr("No valid version found for this resource. It's probably unavailable for the current game version.");
}
emit checkFailed(resource, reason);
return;
}
if (latest_ver->downloadUrl.isEmpty() && latest_ver->fileId != resource->metadata()->file_id) {
m_blocked[resource] = latest_ver->fileId.toString();
if (latestVer->downloadUrl.isEmpty() && latestVer->fileId != resource->metadata()->file_id) {
m_blocked[resource] = latestVer->fileId.toString();
return;
}
if (!latest_ver->hash.isEmpty() &&
(resource->metadata()->hash != latest_ver->hash || resource->status() == ResourceStatus::NOT_INSTALLED)) {
auto old_version = resource->metadata()->version_number;
if (old_version.isEmpty()) {
if (resource->status() == ResourceStatus::NOT_INSTALLED)
old_version = tr("Not installed");
else
old_version = tr("Unknown");
if (!latestVer->hash.isEmpty() &&
(resource->metadata()->hash != latestVer->hash || resource->status() == ResourceStatus::NOT_INSTALLED)) {
auto oldVersion = resource->metadata()->version_number;
if (oldVersion.isEmpty()) {
if (resource->status() == ResourceStatus::NOT_INSTALLED) {
oldVersion = tr("Not installed");
} else {
oldVersion = tr("Unknown");
}
}
auto download_task = makeShared<ResourceDownloadTask>(pack, latest_ver.value(), m_resourceModel);
m_updates.emplace_back(pack->name, resource->metadata()->hash, old_version, latest_ver->version, latest_ver->version_type,
api.getModFileChangelog(latest_ver->addonId.toInt(), latest_ver->fileId.toInt()),
ModPlatform::ResourceProvider::FLAME, download_task, resource->enabled());
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,
FlameAPI().getModFileChangelog(latestVer->addonId.toInt(), latestVer->fileId.toInt()),
ModPlatform::ResourceProvider::FLAME, downloadTask, resource->enabled());
}
m_deps.append(std::make_shared<GetModDependenciesTask::PackDependency>(pack, latest_ver.value()));
m_deps.append(std::make_shared<GetModDependenciesTask::PackDependency>(pack, latestVer.value()));
}
void FlameCheckUpdate::collectBlockedMods()
{
QStringList addonIds;
QHash<QString, Resource*> quickSearch;
for (auto const& resource : m_blocked.keys()) {
for (const auto& resource : m_blocked.keys()) {
auto addonId = resource->metadata()->project_id.toString();
addonIds.append(addonId);
quickSearch[addonId] = resource;
}
Task::Ptr projTask;
QByteArray* response;
QByteArray* response = nullptr;
if (addonIds.isEmpty()) {
emitSucceeded();
return;
} else if (addonIds.size() == 1) {
std::tie(projTask, response) = api.getProject(*addonIds.begin());
}
if (addonIds.size() == 1) {
std::tie(projTask, response) = FlameAPI().getProject(*addonIds.begin());
} else {
std::tie(projTask, response) = api.getProjects(addonIds);
std::tie(projTask, response) = FlameAPI().getProjects(addonIds);
}
connect(projTask.get(), &Task::succeeded, this, [this, response, addonIds, quickSearch] {
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();
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 resource = quickSearch.find(id).value();
auto* resource = quickSearch.find(id).value();
ModPlatform::IndexedPack pack;
try {
setStatus(tr("Parsing API response from CurseForge for '%1'...").arg(resource->name()));
FlameMod::loadIndexedPack(pack, entry_obj);
auto recover_url = QString("%1/download/%2").arg(pack.websiteUrl, m_blocked[resource]);
FlameMod::loadIndexedPack(pack, entryObj);
auto recoverUrl = QString("%1/download/%2").arg(pack.websiteUrl, m_blocked[resource]);
emit checkFailed(resource, tr("Resource has a new update available, but is not downloadable using CurseForge."),
recover_url);
recoverUrl);
} catch (Json::JsonException& e) {
qDebug() << e.cause();
qDebug() << entries;

View file

@ -53,7 +53,7 @@ void ModrinthCheckUpdate::executeTask()
setStatus(tr("Preparing resources for Modrinth..."));
setProgress(0, ((m_loadersList.isEmpty() ? 1 : m_loadersList.length()) * 2) + 1);
auto hashing_task =
auto hashingTask =
makeShared<ConcurrentTask>("MakeModrinthHashesTask", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt());
bool startHasing = false;
for (auto* resource : m_resources) {
@ -63,10 +63,11 @@ void ModrinthCheckUpdate::executeTask()
// need to generate a new hash if the current one is innadequate
// (though it will rarely happen, if at all)
if (resource->metadata()->hash_format != m_hashType) {
auto hash_task = Hashing::createHasher(resource->fileinfo().absoluteFilePath(), ModPlatform::ResourceProvider::MODRINTH);
connect(hash_task.get(), &Hashing::Hasher::resultsReady, [this, resource](QString hash) { m_mappings.insert(hash, resource); });
connect(hash_task.get(), &Task::failed, [this] { failed("Failed to generate hash"); });
hashing_task->addTask(hash_task);
auto hashTask = Hashing::createHasher(resource->fileinfo().absoluteFilePath(), ModPlatform::ResourceProvider::MODRINTH);
connect(hashTask.get(), &Hashing::Hasher::resultsReady,
[this, resource](const QString& hash) { m_mappings.insert(hash, resource); });
connect(hashTask.get(), &Task::failed, [this] { failed("Failed to generate hash"); });
hashingTask->addTask(hashTask);
startHasing = true;
} else {
m_mappings.insert(hash, resource);
@ -74,9 +75,9 @@ void ModrinthCheckUpdate::executeTask()
}
if (startHasing) {
connect(hashing_task.get(), &Task::finished, this, &ModrinthCheckUpdate::checkNextLoader);
m_job = hashing_task;
hashing_task->start();
connect(hashingTask.get(), &Task::finished, this, &ModrinthCheckUpdate::checkNextLoader);
m_job = hashingTask;
hashingTask->start();
} else {
checkNextLoader();
}
@ -120,14 +121,14 @@ void ModrinthCheckUpdate::checkVersionsResponse(QByteArray* response, std::optio
setStatus(tr("Parsing the API response from Modrinth..."));
setProgress(m_progress + 1, m_progressTotal);
QJsonParseError parse_error{};
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
if (parse_error.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response from ModrinthCheckUpdate at" << parse_error.offset
<< "reason:" << parse_error.errorString();
QJsonParseError parseError{};
QJsonDocument doc = QJsonDocument::fromJson(*response, &parseError);
if (parseError.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response from ModrinthCheckUpdate at" << parseError.offset
<< "reason:" << parseError.errorString();
qWarning() << *response;
emitFailed(parse_error.errorString());
emitFailed(parseError.errorString());
return;
}
@ -138,11 +139,11 @@ void ModrinthCheckUpdate::checkVersionsResponse(QByteArray* response, std::optio
const QString hash = iter.key();
Resource* resource = iter.value();
auto project_obj = doc[hash].toObject();
auto projectObj = doc[hash].toObject();
// If the returned project is empty, but we have Modrinth metadata,
// it means this specific version is not available
if (project_obj.isEmpty()) {
if (projectObj.isEmpty()) {
qDebug() << "Mod" << m_mappings.find(hash).value()->name() << "got an empty response. Hash:" << hash;
++iter;
continue;
@ -150,11 +151,11 @@ void ModrinthCheckUpdate::checkVersionsResponse(QByteArray* response, std::optio
// Sometimes a version may have multiple files, one with "forge" and one with "fabric",
// so we may want to filter it
QString loader_filter;
QString loaderFilter;
if (loader.has_value() && loader != 0) {
auto modLoaders = ModPlatform::modLoaderTypesToList(*loader);
if (!modLoaders.isEmpty()) {
loader_filter = ModPlatform::getModLoaderAsString(modLoaders.first());
loaderFilter = ModPlatform::getModLoaderAsString(modLoaders.first());
}
}
@ -164,9 +165,9 @@ void ModrinthCheckUpdate::checkVersionsResponse(QByteArray* response, std::optio
// - The version reported by the JAR is different from the version reported by the indexed version (it's usually the case)
// Such is the pain of having arbitrary files for a given version .-.
auto project_ver = Modrinth::loadIndexedPackVersion(project_obj, m_hashType, loader_filter);
if (project_ver.downloadUrl.isEmpty()) {
qCritical() << "Modrinth mod without download url!" << project_ver.fileName;
auto projectVer = Modrinth::loadIndexedPackVersion(projectObj, m_hashType, loaderFilter);
if (projectVer.downloadUrl.isEmpty()) {
qCritical() << "Modrinth mod without download url!" << projectVer.fileName;
++iter;
continue;
}
@ -177,21 +178,22 @@ void ModrinthCheckUpdate::checkVersionsResponse(QByteArray* response, std::optio
pack->slug = resource->metadata()->slug;
pack->addonId = resource->metadata()->project_id;
pack->provider = ModPlatform::ResourceProvider::MODRINTH;
if ((project_ver.hash != hash && project_ver.is_preferred) || (resource->status() == ResourceStatus::NOT_INSTALLED)) {
auto download_task = makeShared<ResourceDownloadTask>(pack, project_ver, m_resourceModel);
if ((projectVer.hash != hash && projectVer.is_preferred) || (resource->status() == ResourceStatus::NOT_INSTALLED)) {
auto downloadTask = makeShared<ResourceDownloadTask>(pack, projectVer, m_resourceModel, true, "update");
QString old_version = resource->metadata()->version_number;
if (old_version.isEmpty()) {
if (resource->status() == ResourceStatus::NOT_INSTALLED)
old_version = tr("Not installed");
else
old_version = tr("Unknown");
QString oldVersion = resource->metadata()->version_number;
if (oldVersion.isEmpty()) {
if (resource->status() == ResourceStatus::NOT_INSTALLED) {
oldVersion = tr("Not installed");
} else {
oldVersion = tr("Unknown");
}
}
m_updates.emplace_back(pack->name, hash, old_version, project_ver.version_number, project_ver.version_type,
project_ver.changelog, ModPlatform::ResourceProvider::MODRINTH, download_task, resource->enabled());
m_updates.emplace_back(pack->name, hash, oldVersion, projectVer.version_number, projectVer.version_type,
projectVer.changelog, ModPlatform::ResourceProvider::MODRINTH, downloadTask, resource->enabled());
}
m_deps.append(std::make_shared<GetModDependenciesTask::PackDependency>(pack, project_ver));
m_deps.append(std::make_shared<GetModDependenciesTask::PackDependency>(pack, projectVer));
iter = m_mappings.erase(iter);
}
@ -211,20 +213,22 @@ void ModrinthCheckUpdate::checkNextLoader()
if (m_loaderIdx < m_loadersList.size()) { // this are mods so check with loades
getUpdateModsForLoader(m_loadersList.at(m_loaderIdx), m_loaderIdx > m_initialSize);
return;
} else if (m_loadersList.isEmpty() && m_loaderIdx == 0) { // this are other resources no need to check more than once with empty loader
}
if (m_loadersList.isEmpty() && m_loaderIdx == 0) { // this are other resources no need to check more than once with empty loader
getUpdateModsForLoader();
return;
}
for (auto resource : m_mappings) {
for (auto* resource : m_mappings) {
QString reason;
if (dynamic_cast<Mod*>(resource) != nullptr)
if (dynamic_cast<Mod*>(resource) != nullptr) {
reason =
tr("No valid version found for this resource. It's probably unavailable for the current game "
"version / mod loader.");
else
} else {
reason = tr("No valid version found for this resource. It's probably unavailable for the current game version.");
}
emit checkFailed(resource, reason);
}

View file

@ -16,7 +16,10 @@
#include "net/ChecksumValidator.h"
#include "net/ApiDownload.h"
#include "net/ApiHeaderProxy.h"
#include "net/NetJob.h"
#include "modplatform/ModIndex.h"
#include "settings/INISettingsObject.h"
#include "ui/dialogs/CustomMessageBox.h"
@ -29,114 +32,119 @@
bool ModrinthCreationTask::abort()
{
if (!canAbort())
if (!canAbort()) {
return false;
}
if (m_task)
if (m_task) {
m_task->abort();
}
return InstanceCreationTask::abort();
}
bool ModrinthCreationTask::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, "modrinth.index.json");
if (!parseManifest(index_path, m_files, true, false))
QString indexPath = FS::PathCombine(m_stagingPath, "modrinth.index.json");
if (!parseManifest(indexPath, m_files, true, false)) {
return false;
}
auto version_name = inst->getManagedPackVersionName();
auto versionName = inst->getManagedPackVersionName();
m_root_path = QFileInfo(inst->gameRoot()).fileName();
auto version_str = !version_name.isEmpty() ? tr(" (version %1)").arg(version_name) : "";
auto versionStr = !versionName.isEmpty() ? tr(" (version %1)").arg(versionName) : "";
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;
}
}
// Remove repeated files, we don't need to download them!
QDir old_inst_dir(inst->instanceRoot());
QDir oldInstDir(inst->instanceRoot());
QString old_index_folder(FS::PathCombine(old_inst_dir.absolutePath(), "mrpack"));
QString oldIndexFolder(FS::PathCombine(oldInstDir.absolutePath(), "mrpack"));
QString old_index_path(FS::PathCombine(old_index_folder, "modrinth.index.json"));
QFileInfo old_index_file(old_index_path);
if (old_index_file.exists()) {
std::vector<File> old_files;
parseManifest(old_index_path, old_files, false, false);
QString oldIndexPath(FS::PathCombine(oldIndexFolder, "modrinth.index.json"));
QFileInfo oldIndexFile(oldIndexPath);
if (oldIndexFile.exists()) {
std::vector<File> oldFiles;
parseManifest(oldIndexPath, oldFiles, false, false);
// Let's remove all duplicated, identical resources!
auto files_iterator = m_files.begin();
auto filesIterator = m_files.begin();
begin:
while (files_iterator != m_files.end()) {
auto const& file = *files_iterator;
while (filesIterator != m_files.end()) {
const auto& file = *filesIterator;
auto old_files_iterator = old_files.begin();
while (old_files_iterator != old_files.end()) {
auto const& old_file = *old_files_iterator;
auto oldFilesIterator = oldFiles.begin();
while (oldFilesIterator != oldFiles.end()) {
const auto& oldFile = *oldFilesIterator;
if (old_file.hash == file.hash) {
if (oldFile.hash == file.hash) {
qDebug() << "Removed file at" << file.path << "from list of downloads";
files_iterator = m_files.erase(files_iterator);
old_files_iterator = old_files.erase(old_files_iterator);
filesIterator = m_files.erase(filesIterator);
oldFilesIterator = oldFiles.erase(oldFilesIterator);
goto begin; // Sorry :c
}
old_files_iterator++;
oldFilesIterator++;
}
files_iterator++;
filesIterator++;
}
QDir old_minecraft_dir(inst->gameRoot());
QDir oldMinecraftDir(inst->gameRoot());
// Some files were removed from the old version, and some will be downloaded in an updated version,
// so we're fine removing them!
if (!old_files.empty()) {
for (auto const& file : old_files) {
scheduleToDelete(m_parent, old_minecraft_dir, file.path, true);
if (!oldFiles.empty()) {
for (const auto& file : oldFiles) {
scheduleToDelete(m_parent, oldMinecraftDir, file.path, true);
}
}
// 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);
}
auto old_client_overrides = Override::readOverrides("client-overrides", old_index_folder);
for (const auto& entry : old_client_overrides) {
scheduleToDelete(m_parent, old_minecraft_dir, entry);
auto oldClientOverrides = Override::readOverrides("client-overrides", oldIndexFolder);
for (const auto& entry : oldClientOverrides) {
scheduleToDelete(m_parent, oldMinecraftDir, entry);
}
} 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;
@ -158,39 +166,40 @@ std::unique_ptr<MinecraftInstance> ModrinthCreationTask::createInstance()
{
QEventLoop loop;
QString parent_folder(FS::PathCombine(m_stagingPath, "mrpack"));
QString parentFolder(FS::PathCombine(m_stagingPath, "mrpack"));
QString index_path = FS::PathCombine(m_stagingPath, "modrinth.index.json");
if (m_files.empty() && !parseManifest(index_path, m_files, true, true))
QString indexPath = FS::PathCombine(m_stagingPath, "modrinth.index.json");
if (m_files.empty() && !parseManifest(indexPath, m_files, true, true)) {
return nullptr;
}
// Keep index file in case we need it some other time (like when changing versions)
QString new_index_place(FS::PathCombine(parent_folder, "modrinth.index.json"));
FS::ensureFilePathExists(new_index_place);
FS::move(index_path, new_index_place);
QString newIndexPlace(FS::PathCombine(parentFolder, "modrinth.index.json"));
FS::ensureFilePathExists(newIndexPlace);
FS::move(indexPath, newIndexPlace);
auto mcPath = FS::PathCombine(m_stagingPath, m_root_path);
auto override_path = FS::PathCombine(m_stagingPath, "overrides");
if (QFile::exists(override_path)) {
auto overridePath = FS::PathCombine(m_stagingPath, "overrides");
if (QFile::exists(overridePath)) {
// Create a list of overrides in "overrides.txt" inside mrpack/
Override::createOverrides("overrides", parent_folder, override_path);
Override::createOverrides("overrides", parentFolder, overridePath);
// Apply the overrides
if (!FS::move(override_path, mcPath)) {
if (!FS::move(overridePath, mcPath)) {
setError(tr("Could not rename the overrides folder:\n") + "overrides");
return nullptr;
}
}
// Do client overrides
auto client_override_path = FS::PathCombine(m_stagingPath, "client-overrides");
if (QFile::exists(client_override_path)) {
auto clientOverridePath = FS::PathCombine(m_stagingPath, "client-overrides");
if (QFile::exists(clientOverridePath)) {
// Create a list of overrides in "client-overrides.txt" inside mrpack/
Override::createOverrides("client-overrides", parent_folder, client_override_path);
Override::createOverrides("client-overrides", parentFolder, clientOverridePath);
// Apply the overrides
if (!FS::overrideFolder(mcPath, client_override_path)) {
if (!FS::overrideFolder(mcPath, clientOverridePath)) {
setError(tr("Could not rename the client overrides folder:\n") + "client overrides");
return nullptr;
}
@ -200,18 +209,27 @@ std::unique_ptr<MinecraftInstance> ModrinthCreationTask::createInstance()
auto instanceSettings = std::make_unique<INISettingsObject>(configPath);
auto instance = std::make_unique<MinecraftInstance>(m_globalSettings, std::move(instanceSettings), m_stagingPath);
auto components = instance->getPackProfile();
auto* components = instance->getPackProfile();
components->buildingFromScratch();
components->setComponentVersion("net.minecraft", m_minecraft_version, true);
if (!m_fabric_version.isEmpty())
QString loader;
if (!m_fabric_version.isEmpty()) {
components->setComponentVersion("net.fabricmc.fabric-loader", m_fabric_version);
if (!m_quilt_version.isEmpty())
loader = ModPlatform::getModLoaderAsString(ModPlatform::ModLoaderType::Fabric);
}
if (!m_quilt_version.isEmpty()) {
components->setComponentVersion("org.quiltmc.quilt-loader", m_quilt_version);
if (!m_forge_version.isEmpty())
loader = ModPlatform::getModLoaderAsString(ModPlatform::ModLoaderType::Quilt);
}
if (!m_forge_version.isEmpty()) {
components->setComponentVersion("net.minecraftforge", m_forge_version);
if (!m_neoForge_version.isEmpty())
loader = ModPlatform::getModLoaderAsString(ModPlatform::ModLoaderType::Forge);
}
if (!m_neoForge_version.isEmpty()) {
components->setComponentVersion("net.neoforged", m_neoForge_version);
loader = ModPlatform::getModLoaderAsString(ModPlatform::ModLoaderType::NeoForge);
}
if (m_instIcon != "default") {
instance->setIconKey(m_instIcon);
@ -220,34 +238,35 @@ std::unique_ptr<MinecraftInstance> ModrinthCreationTask::createInstance()
}
// Don't add managed info to packs without an ID (most likely imported from ZIP)
if (!m_managed_id.isEmpty())
if (!m_managed_id.isEmpty()) {
instance->setManagedPack("modrinth", m_managed_id, m_managed_name, m_managed_version_id, version());
else
} else {
instance->setManagedPack("modrinth", "", name(), "", "");
}
instance->setName(name());
instance->saveNow();
auto downloadMods = makeShared<NetJob>(tr("Mod Download Modrinth"), APPLICATION->network());
auto root_modpack_path = FS::PathCombine(m_stagingPath, m_root_path);
auto root_modpack_url = QUrl::fromLocalFile(root_modpack_path);
auto rootModpackPath = FS::PathCombine(m_stagingPath, m_root_path);
auto rootModpackUrl = QUrl::fromLocalFile(rootModpackPath);
// TODO make this work with other sorts of resource
QHash<QString, Resource*> resources;
for (auto& file : m_files) {
auto fileName = file.path;
fileName = FS::RemoveInvalidPathChars(fileName);
auto file_path = FS::PathCombine(root_modpack_path, fileName);
if (!root_modpack_url.isParentOf(QUrl::fromLocalFile(file_path))) {
auto filePath = FS::PathCombine(rootModpackPath, fileName);
if (!rootModpackUrl.isParentOf(QUrl::fromLocalFile(filePath))) {
// This means we somehow got out of the root folder, so abort here to prevent exploits
setError(tr("One of the files has a path that leads to an arbitrary location (%1). This is a security risk and isn't allowed.")
.arg(fileName));
return nullptr;
}
if (fileName.startsWith("mods/")) {
auto mod = new Mod(file_path);
auto* mod = new Mod(filePath);
ModDetails d;
d.mod_id = file_path;
d.mod_id = filePath;
mod->setDetails(d);
resources[file.hash.toHex()] = mod;
}
@ -255,29 +274,39 @@ std::unique_ptr<MinecraftInstance> ModrinthCreationTask::createInstance()
setError(tr("The file '%1' is missing a download link. This is invalid in the pack format.").arg(fileName));
return nullptr;
}
qDebug() << "Will try to download" << file.downloads.front() << "to" << file_path;
auto dl = Net::ApiDownload::makeFile(file.downloads.dequeue(), file_path);
qDebug() << "Will try to download" << file.downloads.front() << "to" << filePath;
Net::ModrinthDownloadMeta meta{
.reason = m_instance.has_value() ? "update" : "modpack",
.gameVersion = m_minecraft_version,
.loader = loader,
};
QUrl downloadUrl = file.downloads.dequeue();
auto dl = Net::ApiDownload::makeFile(downloadUrl, filePath, Net::Download::Option::NoOptions, meta);
dl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash));
downloadMods->addNetAction(dl);
if (!file.downloads.empty()) {
// FIXME: This really needs to be put into a ConcurrentTask of
// MultipleOptionsTask's , once those exist :)
auto param = dl.toWeakRef();
connect(dl.get(), &Task::failed, [&file, file_path, param, downloadMods] {
auto ndl = Net::ApiDownload::makeFile(file.downloads.dequeue(), file_path);
connect(dl.get(), &Task::failed, [&file, filePath, param, downloadMods, meta] {
QUrl fallbackUrl = file.downloads.dequeue();
auto ndl = Net::ApiDownload::makeFile(fallbackUrl, filePath, Net::Download::Option::NoOptions, meta);
ndl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash));
downloadMods->addNetAction(ndl);
if (auto shared = param.lock())
if (auto shared = param.lock()) {
shared->succeeded();
}
});
}
}
bool ended_well = false;
bool endedWell = false;
connect(downloadMods.get(), &NetJob::succeeded, this, [&ended_well]() { ended_well = true; });
connect(downloadMods.get(), &NetJob::failed, [this, &ended_well](const QString& reason) {
ended_well = false;
connect(downloadMods.get(), &NetJob::succeeded, this, [&endedWell]() { endedWell = true; });
connect(downloadMods.get(), &NetJob::failed, [this, &endedWell](const QString& reason) {
endedWell = false;
setError(reason);
});
connect(downloadMods.get(), &NetJob::finished, &loop, &QEventLoop::quit);
@ -293,8 +322,8 @@ std::unique_ptr<MinecraftInstance> ModrinthCreationTask::createInstance()
loop.exec();
if (!ended_well) {
for (auto resource : resources) {
if (!endedWell) {
for (auto* resource : resources) {
delete resource;
}
return nullptr;
@ -303,7 +332,7 @@ std::unique_ptr<MinecraftInstance> ModrinthCreationTask::createInstance()
QEventLoop ensureMetaLoop;
QDir folder = FS::PathCombine(instance->modsRoot(), ".index");
auto ensureMetadataTask = makeShared<EnsureMetadataTask>(resources, folder, ModPlatform::ResourceProvider::MODRINTH);
connect(ensureMetadataTask.get(), &Task::succeeded, this, [&ended_well]() { ended_well = true; });
connect(ensureMetadataTask.get(), &Task::succeeded, this, [&endedWell]() { endedWell = true; });
connect(ensureMetadataTask.get(), &Task::finished, &ensureMetaLoop, &QEventLoop::quit);
connect(ensureMetadataTask.get(), &Task::progress, [this](qint64 current, qint64 total) {
setDetails(tr("%1 out of %2 complete").arg(current).arg(total));
@ -315,40 +344,38 @@ std::unique_ptr<MinecraftInstance> ModrinthCreationTask::createInstance()
m_task = ensureMetadataTask;
ensureMetaLoop.exec();
for (auto resource : resources) {
for (auto* resource : resources) {
delete resource;
}
resources.clear();
// Update information of the already installed instance, if any.
if (m_instance && ended_well) {
if (m_instance && endedWell) {
setAbortable(false);
auto inst = m_instance.value();
auto* inst = m_instance.value();
// Only change the name if it didn't use a custom name, so that the previous custom name
// is preserved, but if we're using the original one, we update the version string.
// NOTE: This needs to come before the copyManagedPack call!
if (inst->name().contains(inst->getManagedPackVersionName()) && inst->name() != instance->name()) {
if (askForChangingInstanceName(m_parent, inst->name(), instance->name()) == InstanceNameChange::ShouldChange)
if (askForChangingInstanceName(m_parent, inst->name(), instance->name()) == InstanceNameChange::ShouldChange) {
inst->setName(instance->name());
}
}
inst->copyManagedPack(*instance);
}
if (ended_well) {
if (endedWell) {
return instance;
}
return nullptr;
}
bool ModrinthCreationTask::parseManifest(const QString& index_path,
std::vector<File>& files,
bool set_internal_data,
bool show_optional_dialog)
bool ModrinthCreationTask::parseManifest(const QString& indexPath, std::vector<File>& files, bool setInternalData, bool showOptionalDialog)
{
try {
auto doc = Json::requireDocument(index_path);
auto doc = Json::requireDocument(indexPath);
auto obj = Json::requireObject(doc, "modrinth.index.json");
int formatVersion = Json::requireInteger(obj, "formatVersion", "modrinth.index.json");
if (formatVersion == 1) {
@ -357,9 +384,10 @@ bool ModrinthCreationTask::parseManifest(const QString& index_path,
throw JSONValidationError("Unknown game: " + game);
}
if (set_internal_data) {
if (m_managed_version_id.isEmpty())
if (setInternalData) {
if (m_managed_version_id.isEmpty()) {
m_managed_version_id = obj["versionId"].toString();
}
m_managed_name = obj["name"].toString();
}
@ -375,7 +403,8 @@ bool ModrinthCreationTask::parseManifest(const QString& index_path,
QString support = env["client"].toString("unsupported");
if (support == "unsupported") {
continue;
} else if (support == "optional") {
}
if (support == "optional") {
file.required = false;
}
}
@ -387,20 +416,21 @@ bool ModrinthCreationTask::parseManifest(const QString& index_path,
// Do not use requireUrl, which uses StrictMode, instead use QUrl's default TolerantMode
// (as Modrinth seems to incorrectly handle spaces)
auto download_arr = modInfo["downloads"].toArray();
for (auto download : download_arr) {
auto downloadArr = modInfo["downloads"].toArray();
for (auto download : downloadArr) {
qWarning() << download.toString();
bool is_last = download.toString() == download_arr.last().toString();
bool isLast = download.toString() == downloadArr.last().toString();
auto download_url = QUrl(download.toString());
auto downloadUrl = QUrl(download.toString());
if (!download_url.isValid()) {
if (!downloadUrl.isValid()) {
qDebug()
<< QString("Download URL (%1) for %2 is not a correctly formatted URL").arg(download_url.toString(), file.path);
if (is_last && file.downloads.isEmpty())
<< QString("Download URL (%1) for %2 is not a correctly formatted URL").arg(downloadUrl.toString(), file.path);
if (isLast && file.downloads.isEmpty()) {
throw JSONValidationError(tr("Download URL for %1 is not a correctly formatted URL").arg(file.path));
}
} else {
file.downloads.push_back(download_url);
file.downloads.push_back(downloadUrl);
}
}
@ -408,10 +438,11 @@ bool ModrinthCreationTask::parseManifest(const QString& index_path,
}
if (!optionalFiles.empty()) {
if (show_optional_dialog) {
if (showOptionalDialog) {
QStringList oFiles;
for (auto file : optionalFiles)
for (const auto& file : optionalFiles) {
oFiles.push_back(file.path);
}
OptionalModDialog optionalModDialog(m_parent, oFiles);
if (optionalModDialog.exec() == QDialog::Rejected) {
emitAborted();
@ -434,7 +465,7 @@ bool ModrinthCreationTask::parseManifest(const QString& index_path,
}
}
}
if (set_internal_data) {
if (setInternalData) {
auto dependencies = Json::requireObject(obj, "dependencies", "modrinth.index.json");
for (auto it = dependencies.begin(), end = dependencies.end(); it != end; ++it) {
QString name = it.key();

View file

@ -24,18 +24,18 @@ class ModrinthCreationTask final : public InstanceCreationTask {
};
public:
ModrinthCreationTask(QString staging_path,
SettingsObject* global_settings,
ModrinthCreationTask(const QString& stagingPath,
SettingsObject* globalSettings,
QWidget* parent,
QString id,
QString version_id = {},
QString original_instance_id = {})
: InstanceCreationTask(), m_parent(parent), m_managed_id(std::move(id)), m_managed_version_id(std::move(version_id))
QString versionId = {},
QString originalInstanceId = {})
: m_parent(parent), m_managed_id(std::move(id)), m_managed_version_id(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;
@ -44,7 +44,7 @@ class ModrinthCreationTask final : public InstanceCreationTask {
std::unique_ptr<MinecraftInstance> createInstance() override;
private:
bool parseManifest(const QString&, std::vector<File>&, bool set_internal_data = true, bool show_optional_dialog = true);
bool parseManifest(const QString&, std::vector<File>&, bool setInternalData = true, bool showOptionalDialog = true);
private:
QWidget* m_parent = nullptr;

View file

@ -18,28 +18,30 @@
*/
#include "net/ApiDownload.h"
#include <utility>
#include "net/ApiHeaderProxy.h"
namespace Net {
Download::Ptr ApiDownload::makeCached(QUrl url, MetaEntryPtr entry, Download::Options options)
{
auto dl = Download::makeCached(url, entry, options);
auto dl = Download::makeCached(std::move(url), std::move(entry), options);
dl->addHeaderProxy(std::make_unique<ApiHeaderProxy>());
return dl;
}
std::pair<Download::Ptr, QByteArray*> ApiDownload::makeByteArray(QUrl url, Download::Options options)
{
auto [dl, response] = Download::makeByteArray(url, options);
auto [dl, response] = Download::makeByteArray(std::move(url), options);
dl->addHeaderProxy(std::make_unique<ApiHeaderProxy>());
return { dl, response };
}
Download::Ptr ApiDownload::makeFile(QUrl url, QString path, Download::Options options)
Download::Ptr ApiDownload::makeFile(QUrl url, QString path, Download::Options options, ModrinthDownloadMeta meta)
{
auto dl = Download::makeFile(url, path, options);
dl->addHeaderProxy(std::make_unique<ApiHeaderProxy>());
auto dl = Download::makeFile(std::move(url), std::move(path), options);
dl->addHeaderProxy(std::make_unique<ApiHeaderProxy>(std::move(meta)));
return dl;
}

View file

@ -20,13 +20,17 @@
#pragma once
#include "Download.h"
#include "net/ApiHeaderProxy.h"
namespace Net {
namespace ApiDownload {
Download::Ptr makeCached(QUrl url, MetaEntryPtr entry, Download::Options options = Download::Option::NoOptions);
std::pair<Download::Ptr, QByteArray*> makeByteArray(QUrl url, Download::Options options = Download::Option::NoOptions);
Download::Ptr makeFile(QUrl url, QString path, Download::Options options = Download::Option::NoOptions);
Download::Ptr makeFile(QUrl url,
QString path,
Download::Options options = Download::Option::NoOptions,
ModrinthDownloadMeta meta = ModrinthDownloadMeta());
}; // namespace ApiDownload
} // namespace Net

View file

@ -23,27 +23,62 @@
#include "BuildConfig.h"
#include "net/HeaderProxy.h"
#include <QJsonDocument>
#include <QJsonObject>
namespace Net {
struct ModrinthDownloadMeta {
QString reason;
QString gameVersion;
QString loader;
bool isEmpty() const { return reason.isEmpty(); }
QByteArray toJson() const
{
QJsonObject obj;
if (!reason.isEmpty()) {
obj["reason"] = reason;
}
if (!gameVersion.isEmpty()) {
obj["game_version"] = gameVersion;
}
if (!loader.isEmpty()) {
obj["loader"] = loader;
}
return QJsonDocument(obj).toJson(QJsonDocument::Compact);
}
};
class ApiHeaderProxy : public HeaderProxy {
public:
ApiHeaderProxy() : HeaderProxy() {}
virtual ~ApiHeaderProxy() = default;
ApiHeaderProxy() = default;
explicit ApiHeaderProxy(ModrinthDownloadMeta meta) : m_meta(std::move(meta)) {}
~ApiHeaderProxy() override = default;
public:
virtual QList<HeaderPair> headers(const QNetworkRequest& request) const override
QList<HeaderPair> headers(const QNetworkRequest& request) const override
{
QList<HeaderPair> hdrs;
if (APPLICATION->capabilities() & Application::SupportsFlame && request.url().host() == QUrl(BuildConfig.FLAME_BASE_URL).host()) {
hdrs.append({ "x-api-key", APPLICATION->getFlameAPIKey().toUtf8() });
hdrs.append({ .headerName = "x-api-key", .headerValue = APPLICATION->getFlameAPIKey().toUtf8() });
} else if (request.url().host() == QUrl(BuildConfig.MODRINTH_PROD_URL).host() ||
request.url().host() == QUrl(BuildConfig.MODRINTH_STAGING_URL).host()) {
QString token = APPLICATION->getModrinthAPIToken();
if (!token.isNull())
hdrs.append({ "Authorization", token.toUtf8() });
if (!token.isNull()) {
hdrs.append({ .headerName = "Authorization", .headerValue = token.toUtf8() });
}
}
if (request.url().host() == "cdn.modrinth.com" && !m_meta.isEmpty()) {
hdrs.append({ .headerName = "modrinth-download-meta", .headerValue = m_meta.toJson() });
}
return hdrs;
};
private:
ModrinthDownloadMeta m_meta;
};
} // namespace Net

View file

@ -185,7 +185,7 @@ void ResourceDownloadDialog::confirm()
return;
}
for (const auto& dep : task->getDependecies()) {
addResource(dep->pack, dep->version);
addResource(dep->pack, dep->version, "dependency");
depNames << dep->pack->name;
}
dependencyExtraInfo = task->getExtraInfo();
@ -234,10 +234,10 @@ ResourcePage* ResourceDownloadDialog::selectedPage()
return result;
}
void ResourceDownloadDialog::addResource(ModPlatform::IndexedPack::Ptr pack, ModPlatform::IndexedVersion& ver)
void ResourceDownloadDialog::addResource(ModPlatform::IndexedPack::Ptr pack, ModPlatform::IndexedVersion& ver, QString downloadReason)
{
removeResource(pack->name);
selectedPage()->addResourceToPage(pack, ver, getBaseModel());
selectedPage()->addResourceToPage(pack, ver, getBaseModel(), std::move(downloadReason));
setButtonStatus();
}

View file

@ -64,7 +64,7 @@ class ResourceDownloadDialog : public QDialog, public BasePageProvider {
bool selectPage(QString pageId);
ResourcePage* selectedPage();
void addResource(ModPlatform::IndexedPack::Ptr, ModPlatform::IndexedVersion&);
void addResource(ModPlatform::IndexedPack::Ptr, ModPlatform::IndexedVersion&, QString downloadReason = "standalone");
void removeResource(const QString&);
QList<DownloadTaskPtr> getTasks();
@ -121,7 +121,10 @@ class ResourcePackDownloadDialog final : public ResourceDownloadDialog {
Q_OBJECT
public:
explicit ResourcePackDownloadDialog(QWidget* parent, ResourcePackFolderModel* resourcePacks, BaseInstance* instance, bool suppressInitialSearch = false);
explicit ResourcePackDownloadDialog(QWidget* parent,
ResourcePackFolderModel* resourcePacks,
BaseInstance* instance,
bool suppressInitialSearch = false);
~ResourcePackDownloadDialog() override = default;
//: String that gets appended to the resource pack download dialog title ("Download " + resourcesString())
@ -138,7 +141,10 @@ class TexturePackDownloadDialog final : public ResourceDownloadDialog {
Q_OBJECT
public:
explicit TexturePackDownloadDialog(QWidget* parent, TexturePackFolderModel* resourcePacks, BaseInstance* instance, bool suppressInitialSearch = false);
explicit TexturePackDownloadDialog(QWidget* parent,
TexturePackFolderModel* resourcePacks,
BaseInstance* instance,
bool suppressInitialSearch = false);
~TexturePackDownloadDialog() override = default;
//: String that gets appended to the texture pack download dialog title ("Download " + resourcesString())
@ -155,7 +161,10 @@ class ShaderPackDownloadDialog final : public ResourceDownloadDialog {
Q_OBJECT
public:
explicit ShaderPackDownloadDialog(QWidget* parent, ShaderPackFolderModel* shaders, BaseInstance* instance, bool suppressInitialSearch = false);
explicit ShaderPackDownloadDialog(QWidget* parent,
ShaderPackFolderModel* shaders,
BaseInstance* instance,
bool suppressInitialSearch = false);
~ShaderPackDownloadDialog() override = default;
//: String that gets appended to the shader pack download dialog title ("Download " + resourcesString())
@ -172,7 +181,10 @@ class DataPackDownloadDialog final : public ResourceDownloadDialog {
Q_OBJECT
public:
explicit DataPackDownloadDialog(QWidget* parent, DataPackFolderModel* dataPacks, BaseInstance* instance, bool suppressInitialSearch = false);
explicit DataPackDownloadDialog(QWidget* parent,
DataPackFolderModel* dataPacks,
BaseInstance* instance,
bool suppressInitialSearch = false);
~DataPackDownloadDialog() override = default;
//: String that gets appended to the data pack download dialog title ("Download " + resourcesString())

View file

@ -29,10 +29,12 @@
#include <optional>
static std::vector<Version> mcVersions(BaseInstance* inst)
namespace {
std::vector<Version> mcVersions(BaseInstance* inst)
{
return { static_cast<MinecraftInstance*>(inst)->getPackProfile()->getComponent("net.minecraft")->getVersion() };
}
} // namespace
ResourceUpdateDialog::ResourceUpdateDialog(QWidget* parent,
BaseInstance* instance,
@ -58,8 +60,8 @@ ResourceUpdateDialog::ResourceUpdateDialog(QWidget* parent,
void ResourceUpdateDialog::checkCandidates()
{
// Ensure mods have valid metadata
auto went_well = ensureMetadata();
if (!went_well) {
auto wentWell = ensureMetadata();
if (!wentWell) {
m_aborted = true;
return;
}
@ -73,12 +75,12 @@ void ResourceUpdateDialog::checkCandidates()
text += tr("Mod name: %1<br>File name: %2<br>Reason: %3<br><br>").arg(mod->name(), mod->fileinfo().fileName(), reason);
}
ScrollMessageBox message_dialog(m_parent, tr("Metadata generation failed"),
tr("Could not generate metadata for the following resources:<br>"
"Do you wish to proceed without those resources?"),
text);
message_dialog.setModal(true);
if (message_dialog.exec() == QDialog::Rejected) {
ScrollMessageBox messageDialog(m_parent, tr("Metadata generation failed"),
tr("Could not generate metadata for the following resources:<br>"
"Do you wish to proceed without those resources?"),
text);
messageDialog.setModal(true);
if (messageDialog.exec() == QDialog::Rejected) {
m_aborted = true;
QMetaObject::invokeMethod(this, "reject", Qt::QueuedConnection);
return;
@ -87,40 +89,41 @@ void ResourceUpdateDialog::checkCandidates()
auto versions = mcVersions(m_instance);
SequentialTask check_task(tr("Checking for updates"));
SequentialTask checkTask(tr("Checking for updates"));
if (!m_modrinthToUpdate.empty()) {
m_modrinthCheckTask.reset(new ModrinthCheckUpdate(m_modrinthToUpdate, versions, m_loadersList, m_resourceModel));
connect(m_modrinthCheckTask.get(), &CheckUpdateTask::checkFailed, this,
[this](Resource* resource, QString reason, QUrl recover_url) {
m_failedCheckUpdate.append({ resource, reason, recover_url });
[this](Resource* resource, const QString& reason, const QUrl& recoverUrl) {
m_failedCheckUpdate.append({ resource, reason, recoverUrl });
});
check_task.addTask(m_modrinthCheckTask);
checkTask.addTask(m_modrinthCheckTask);
}
if (!m_flameToUpdate.empty()) {
m_flameCheckTask.reset(new FlameCheckUpdate(m_flameToUpdate, versions, m_loadersList, m_resourceModel));
connect(m_flameCheckTask.get(), &CheckUpdateTask::checkFailed, this, [this](Resource* resource, QString reason, QUrl recover_url) {
m_failedCheckUpdate.append({ resource, reason, recover_url });
});
check_task.addTask(m_flameCheckTask);
connect(m_flameCheckTask.get(), &CheckUpdateTask::checkFailed, this,
[this](Resource* resource, const QString& reason, const QUrl& recoverUrl) {
m_failedCheckUpdate.append({ resource, reason, recoverUrl });
});
checkTask.addTask(m_flameCheckTask);
}
connect(&check_task, &Task::failed, this,
[this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); });
connect(&checkTask, &Task::failed, this,
[this](const QString& reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); });
connect(&check_task, &Task::succeeded, this, [this, &check_task]() {
QStringList warnings = check_task.warnings();
connect(&checkTask, &Task::succeeded, this, [this, &checkTask]() {
QStringList warnings = checkTask.warnings();
if (warnings.count()) {
CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->exec();
}
});
// Check for updates
ProgressDialog progress_dialog(m_parent);
progress_dialog.setSkipButton(true, tr("Abort"));
progress_dialog.setWindowTitle(tr("Checking for updates..."));
auto ret = progress_dialog.execWithTask(&check_task);
ProgressDialog progressDialog(m_parent);
progressDialog.setSkipButton(true, tr("Abort"));
progressDialog.setWindowTitle(tr("Checking for updates..."));
auto ret = progressDialog.execWithTask(&checkTask);
// If the dialog was skipped / some download error happened
if (ret == QDialog::DialogCode::Rejected) {
@ -133,8 +136,8 @@ void ResourceUpdateDialog::checkCandidates()
// Add found updates for Modrinth
if (m_modrinthCheckTask) {
auto modrinth_updates = m_modrinthCheckTask->getUpdates();
for (auto& updatable : modrinth_updates) {
auto modrinthUpdates = m_modrinthCheckTask->getUpdates();
for (auto& updatable : modrinthUpdates) {
qDebug() << QString("Mod %1 has an update available!").arg(updatable.name);
appendResource(updatable);
@ -145,8 +148,8 @@ void ResourceUpdateDialog::checkCandidates()
// Add found updated for Flame
if (m_flameCheckTask) {
auto flame_updates = m_flameCheckTask->getUpdates();
for (auto& updatable : flame_updates) {
auto flameUpdates = m_flameCheckTask->getUpdates();
for (auto& updatable : flameUpdates) {
qDebug() << QString("Mod %1 has an update available!").arg(updatable.name);
appendResource(updatable);
@ -161,33 +164,35 @@ void ResourceUpdateDialog::checkCandidates()
for (const auto& failed : m_failedCheckUpdate) {
const auto& mod = std::get<0>(failed);
const auto& reason = std::get<1>(failed);
const auto& recover_url = std::get<2>(failed);
const auto& recoverUrl = std::get<2>(failed);
qDebug() << mod->name() << "failed to check for updates!";
text += tr("Mod name: %1").arg(mod->name()) + "<br>";
if (!reason.isEmpty())
if (!reason.isEmpty()) {
text += tr("Reason: %1").arg(reason) + "<br>";
if (!recover_url.isEmpty())
}
if (!recoverUrl.isEmpty()) {
//: %1 is the link to download it manually
text += tr("Possible solution: Getting the latest version manually:<br>%1<br>")
.arg(QString("<a href='%1'>%1</a>").arg(recover_url.toString()));
.arg(QString("<a href='%1'>%1</a>").arg(recoverUrl.toString()));
}
text += "<br>";
}
ScrollMessageBox message_dialog(m_parent, tr("Failed to check for updates"),
tr("Could not check or get the following resources for updates:<br>"
"Do you wish to proceed without those resources?"),
text, "Disable unavailable mods");
message_dialog.setModal(true);
if (message_dialog.exec() == QDialog::Rejected) {
ScrollMessageBox messageDialog(m_parent, tr("Failed to check for updates"),
tr("Could not check or get the following resources for updates:<br>"
"Do you wish to proceed without those resources?"),
text, "Disable unavailable mods");
messageDialog.setModal(true);
if (messageDialog.exec() == QDialog::Rejected) {
m_aborted = true;
QMetaObject::invokeMethod(this, "reject", Qt::QueuedConnection);
return;
}
// Disable unavailable mods
if (message_dialog.isOptionChecked()) {
if (messageDialog.isOptionChecked()) {
for (const auto& failed : m_failedCheckUpdate) {
const auto& mod = std::get<0>(failed);
mod->enable(EnableAction::DISABLE);
@ -196,10 +201,10 @@ void ResourceUpdateDialog::checkCandidates()
}
if (m_includeDeps && !APPLICATION->settings()->get("ModDependenciesDisabled").toBool()) { // dependencies
auto* mod_model = dynamic_cast<ModFolderModel*>(m_resourceModel);
auto* modModel = dynamic_cast<ModFolderModel*>(m_resourceModel);
if (mod_model != nullptr) {
auto depTask = makeShared<GetModDependenciesTask>(m_instance, mod_model, selectedVers);
if (modModel != nullptr) {
auto depTask = makeShared<GetModDependenciesTask>(m_instance, modModel, selectedVers);
connect(depTask.get(), &Task::failed, this, [this](const QString& reason) {
CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec();
@ -215,10 +220,10 @@ void ResourceUpdateDialog::checkCandidates()
}
});
ProgressDialog progress_dialog_deps(m_parent);
progress_dialog_deps.setSkipButton(true, tr("Abort"));
progress_dialog_deps.setWindowTitle(tr("Checking for dependencies..."));
auto dret = progress_dialog_deps.execWithTask(depTask.get());
ProgressDialog progressDialogDeps(m_parent);
progressDialogDeps.setSkipButton(true, tr("Abort"));
progressDialogDeps.setWindowTitle(tr("Checking for dependencies..."));
auto dret = progressDialogDeps.execWithTask(depTask.get());
// If the dialog was skipped / some download error happened
if (dret == QDialog::DialogCode::Rejected) {
@ -226,19 +231,20 @@ void ResourceUpdateDialog::checkCandidates()
QMetaObject::invokeMethod(this, "reject", Qt::QueuedConnection);
return;
}
static FlameAPI api;
static FlameAPI s_api;
auto dependencyExtraInfo = depTask->getExtraInfo();
for (const auto& dep : depTask->getDependecies()) {
auto changelog = dep->version.changelog;
if (dep->pack->provider == ModPlatform::ResourceProvider::FLAME)
changelog = api.getModFileChangelog(dep->version.addonId.toInt(), dep->version.fileId.toInt());
auto download_task = makeShared<ResourceDownloadTask>(dep->pack, dep->version, m_resourceModel);
if (dep->pack->provider == ModPlatform::ResourceProvider::FLAME) {
changelog = s_api.getModFileChangelog(dep->version.addonId.toInt(), dep->version.fileId.toInt());
}
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,
changelog, dep->pack->provider, download_task, !extraInfo.maybe_installed
changelog, dep->pack->provider, downloadTask, !extraInfo.maybe_installed
};
appendResource(updatable, extraInfo.required_by);
@ -264,56 +270,58 @@ void ResourceUpdateDialog::checkCandidates()
}
}
if (m_aborted || m_noUpdates)
if (m_aborted || m_noUpdates) {
QMetaObject::invokeMethod(this, "reject", Qt::QueuedConnection);
}
}
// Part 1: Ensure we have a valid metadata
auto ResourceUpdateDialog::ensureMetadata() -> bool
{
auto index_dir = indexDir();
auto indexDir2 = indexDir();
SequentialTask seq(tr("Looking for metadata"));
// A better use of data structures here could remove the need for this QHash
QHash<QString, bool> should_try_others;
QList<Resource*> modrinth_tmp;
QList<Resource*> flame_tmp;
QHash<QString, bool> shouldTryOthers;
QList<Resource*> modrinthTmp;
QList<Resource*> flameTmp;
bool confirm_rest = false;
bool try_others_rest = false;
bool skip_rest = false;
ModPlatform::ResourceProvider provider_rest = ModPlatform::ResourceProvider::MODRINTH;
bool confirmRest = false;
bool tryOthersRest = false;
bool skipRest = false;
ModPlatform::ResourceProvider providerRest = ModPlatform::ResourceProvider::MODRINTH;
// adds resource to list based on provider
auto addToTmp = [&modrinth_tmp, &flame_tmp](Resource* resource, ModPlatform::ResourceProvider p) {
auto addToTmp = [&modrinthTmp, &flameTmp](Resource* resource, ModPlatform::ResourceProvider p) {
switch (p) {
case ModPlatform::ResourceProvider::MODRINTH:
modrinth_tmp.push_back(resource);
modrinthTmp.push_back(resource);
break;
case ModPlatform::ResourceProvider::FLAME:
flame_tmp.push_back(resource);
flameTmp.push_back(resource);
break;
}
};
// ask the user on what provider to seach for the mod first
for (auto candidate : m_candidates) {
for (auto* candidate : m_candidates) {
if (candidate->status() != ResourceStatus::NO_METADATA) {
onMetadataEnsured(candidate);
continue;
}
if (skip_rest)
if (skipRest) {
continue;
}
if (candidate->type() == ResourceType::FOLDER) {
continue;
}
if (confirm_rest) {
addToTmp(candidate, provider_rest);
should_try_others.insert(candidate->internal_id(), try_others_rest);
if (confirmRest) {
addToTmp(candidate, providerRest);
shouldTryOthers.insert(candidate->internal_id(), tryOthersRest);
continue;
}
@ -326,68 +334,73 @@ auto ResourceUpdateDialog::ensureMetadata() -> bool
auto response = chooser.getResponse();
if (response.skip_all)
skip_rest = true;
if (response.skip_all) {
skipRest = true;
}
if (response.confirm_all) {
confirm_rest = true;
provider_rest = response.chosen;
try_others_rest = response.try_others;
confirmRest = true;
providerRest = response.chosen;
tryOthersRest = response.try_others;
}
should_try_others.insert(candidate->internal_id(), response.try_others);
shouldTryOthers.insert(candidate->internal_id(), response.try_others);
if (confirmed)
if (confirmed) {
addToTmp(candidate, response.chosen);
}
}
// prepare task for the modrinth mods
if (!modrinth_tmp.empty()) {
auto modrinth_task = makeShared<EnsureMetadataTask>(modrinth_tmp, index_dir, ModPlatform::ResourceProvider::MODRINTH);
connect(modrinth_task.get(), &EnsureMetadataTask::metadataReady, [this](Resource* candidate) { onMetadataEnsured(candidate); });
connect(modrinth_task.get(), &EnsureMetadataTask::metadataFailed, [this, &should_try_others](Resource* candidate) {
onMetadataFailed(candidate, should_try_others.find(candidate->internal_id()).value(), ModPlatform::ResourceProvider::MODRINTH);
if (!modrinthTmp.empty()) {
auto modrinthTask = makeShared<EnsureMetadataTask>(modrinthTmp, indexDir2, ModPlatform::ResourceProvider::MODRINTH);
connect(modrinthTask.get(), &EnsureMetadataTask::metadataReady, [this](Resource* candidate) { onMetadataEnsured(candidate); });
connect(modrinthTask.get(), &EnsureMetadataTask::metadataFailed, [this, &shouldTryOthers](Resource* candidate) {
onMetadataFailed(candidate, shouldTryOthers.find(candidate->internal_id()).value(), ModPlatform::ResourceProvider::MODRINTH);
});
connect(modrinth_task.get(), &EnsureMetadataTask::failed,
[this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); });
connect(modrinthTask.get(), &EnsureMetadataTask::failed,
[this](const QString& reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); });
if (modrinth_task->getHashingTask())
seq.addTask(modrinth_task->getHashingTask());
if (modrinthTask->getHashingTask()) {
seq.addTask(modrinthTask->getHashingTask());
}
seq.addTask(modrinth_task);
seq.addTask(modrinthTask);
}
// prepare task for the flame mods
if (!flame_tmp.empty()) {
auto flame_task = makeShared<EnsureMetadataTask>(flame_tmp, index_dir, ModPlatform::ResourceProvider::FLAME);
connect(flame_task.get(), &EnsureMetadataTask::metadataReady, [this](Resource* candidate) { onMetadataEnsured(candidate); });
connect(flame_task.get(), &EnsureMetadataTask::metadataFailed, [this, &should_try_others](Resource* candidate) {
onMetadataFailed(candidate, should_try_others.find(candidate->internal_id()).value(), ModPlatform::ResourceProvider::FLAME);
if (!flameTmp.empty()) {
auto flameTask = makeShared<EnsureMetadataTask>(flameTmp, indexDir2, ModPlatform::ResourceProvider::FLAME);
connect(flameTask.get(), &EnsureMetadataTask::metadataReady, [this](Resource* candidate) { onMetadataEnsured(candidate); });
connect(flameTask.get(), &EnsureMetadataTask::metadataFailed, [this, &shouldTryOthers](Resource* candidate) {
onMetadataFailed(candidate, shouldTryOthers.find(candidate->internal_id()).value(), ModPlatform::ResourceProvider::FLAME);
});
connect(flame_task.get(), &EnsureMetadataTask::failed,
[this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); });
connect(flameTask.get(), &EnsureMetadataTask::failed,
[this](const QString& reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); });
if (flame_task->getHashingTask())
seq.addTask(flame_task->getHashingTask());
if (flameTask->getHashingTask()) {
seq.addTask(flameTask->getHashingTask());
}
seq.addTask(flame_task);
seq.addTask(flameTask);
}
seq.addTask(m_secondTryMetadata);
// execute all the tasks
ProgressDialog checking_dialog(m_parent);
checking_dialog.setSkipButton(true, tr("Abort"));
checking_dialog.setWindowTitle(tr("Generating metadata..."));
auto ret_metadata = checking_dialog.execWithTask(&seq);
ProgressDialog checkingDialog(m_parent);
checkingDialog.setSkipButton(true, tr("Abort"));
checkingDialog.setWindowTitle(tr("Generating metadata..."));
auto retMetadata = checkingDialog.execWithTask(&seq);
return (ret_metadata != QDialog::DialogCode::Rejected);
return (retMetadata != QDialog::DialogCode::Rejected);
}
void ResourceUpdateDialog::onMetadataEnsured(Resource* resource)
{
// When the mod is a folder, for instance
if (!resource->metadata())
if (!resource->metadata()) {
return;
}
switch (resource->metadata()->provider) {
case ModPlatform::ResourceProvider::MODRINTH:
@ -411,12 +424,12 @@ ModPlatform::ResourceProvider next(ModPlatform::ResourceProvider p)
return ModPlatform::ResourceProvider::FLAME;
}
void ResourceUpdateDialog::onMetadataFailed(Resource* resource, bool try_others, ModPlatform::ResourceProvider first_choice)
void ResourceUpdateDialog::onMetadataFailed(Resource* resource, bool tryOthers, ModPlatform::ResourceProvider firstChoice)
{
if (try_others) {
auto index_dir = indexDir();
if (tryOthers) {
auto indexDir2 = indexDir();
auto task = makeShared<EnsureMetadataTask>(resource, index_dir, next(first_choice));
auto task = makeShared<EnsureMetadataTask>(resource, indexDir2, next(firstChoice));
connect(task.get(), &EnsureMetadataTask::metadataReady, [this](Resource* candidate) { onMetadataEnsured(candidate); });
connect(task.get(), &EnsureMetadataTask::metadataFailed, [this](Resource* candidate) { onMetadataFailed(candidate, false); });
connect(task.get(), &EnsureMetadataTask::failed,
@ -436,57 +449,57 @@ void ResourceUpdateDialog::onMetadataFailed(Resource* resource, bool try_others,
}
}
void ResourceUpdateDialog::appendResource(CheckUpdateTask::Update const& info, QStringList requiredBy)
void ResourceUpdateDialog::appendResource(const CheckUpdateTask::Update& info, QStringList requiredBy)
{
auto item_top = new QTreeWidgetItem(ui->modTreeWidget);
item_top->setCheckState(0, info.enabled ? Qt::CheckState::Checked : Qt::CheckState::Unchecked);
auto* itemTop = new QTreeWidgetItem(ui->modTreeWidget);
itemTop->setCheckState(0, info.enabled ? Qt::CheckState::Checked : Qt::CheckState::Unchecked);
if (!info.enabled) {
item_top->setToolTip(0, tr("Mod was disabled as it may be already installed."));
itemTop->setToolTip(0, tr("Mod was disabled as it may be already installed."));
}
item_top->setText(0, info.name);
item_top->setExpanded(true);
itemTop->setText(0, info.name);
itemTop->setExpanded(true);
auto provider_item = new QTreeWidgetItem(item_top);
QString provider_name = ModPlatform::ProviderCapabilities::readableName(info.provider);
provider_item->setText(0, tr("Provider: %1").arg(provider_name));
provider_item->setData(0, Qt::UserRole, provider_name);
auto* providerItem = new QTreeWidgetItem(itemTop);
QString providerName = ModPlatform::ProviderCapabilities::readableName(info.provider);
providerItem->setText(0, tr("Provider: %1").arg(providerName));
providerItem->setData(0, Qt::UserRole, providerName);
auto old_version_item = new QTreeWidgetItem(item_top);
old_version_item->setText(0, tr("Old version: %1").arg(info.old_version));
old_version_item->setData(0, Qt::UserRole, info.old_version);
auto* oldVersionItem = new QTreeWidgetItem(itemTop);
oldVersionItem->setText(0, tr("Old version: %1").arg(info.oldVersion));
oldVersionItem->setData(0, Qt::UserRole, info.oldVersion);
auto new_version_item = new QTreeWidgetItem(item_top);
new_version_item->setText(0, tr("New version: %1").arg(info.new_version));
new_version_item->setData(0, Qt::UserRole, info.new_version);
auto* newVersionItem = new QTreeWidgetItem(itemTop);
newVersionItem->setText(0, tr("New version: %1").arg(info.newVersion));
newVersionItem->setData(0, Qt::UserRole, info.newVersion);
if (info.new_version_type.has_value()) {
auto new_version_type_item = new QTreeWidgetItem(item_top);
new_version_type_item->setText(0, tr("New Version Type: %1").arg(info.new_version_type.value().toString()));
new_version_type_item->setData(0, Qt::UserRole, info.new_version_type.value().toString());
if (info.newVersionType.has_value()) {
auto* newVersionTypeItem = new QTreeWidgetItem(itemTop);
newVersionTypeItem->setText(0, tr("New Version Type: %1").arg(info.newVersionType.value().toString()));
newVersionTypeItem->setData(0, Qt::UserRole, info.newVersionType.value().toString());
}
if (!requiredBy.isEmpty()) {
auto requiredByItem = new QTreeWidgetItem(item_top);
auto* requiredByItem = new QTreeWidgetItem(itemTop);
if (requiredBy.length() == 1) {
requiredByItem->setText(0, tr("Required by: %1").arg(requiredBy.back()));
requiredByItem->setData(0, Qt::UserRole, requiredBy.back());
} else {
requiredByItem->setText(0, tr("Required by:"));
for (auto req : requiredBy) {
auto reqItem = new QTreeWidgetItem(requiredByItem);
for (const auto& req : requiredBy) {
auto* reqItem = new QTreeWidgetItem(requiredByItem);
reqItem->setText(0, req);
}
}
ui->toggleDepsButton->show();
m_deps << item_top;
m_deps << itemTop;
}
auto changelog_item = new QTreeWidgetItem(item_top);
changelog_item->setText(0, tr("Changelog of the latest version"));
auto* changelogItem = new QTreeWidgetItem(itemTop);
changelogItem->setText(0, tr("Changelog of the latest version"));
auto changelog = new QTreeWidgetItem(changelog_item);
auto changelog_area = new QTextBrowser();
auto* changelog = new QTreeWidgetItem(changelogItem);
auto* changelogArea = new QTextBrowser();
QString text = info.changelog;
changelog->setData(0, Qt::UserRole, text);
@ -494,14 +507,14 @@ void ResourceUpdateDialog::appendResource(CheckUpdateTask::Update const& info, Q
text = markdownToHTML(info.changelog.toUtf8());
}
changelog_area->setHtml(StringUtils::htmlListPatch(text));
changelog_area->setOpenExternalLinks(true);
changelog_area->setLineWrapMode(QTextBrowser::LineWrapMode::WidgetWidth);
changelog_area->setVerticalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAsNeeded);
changelogArea->setHtml(StringUtils::htmlListPatch(text));
changelogArea->setOpenExternalLinks(true);
changelogArea->setLineWrapMode(QTextBrowser::LineWrapMode::WidgetWidth);
changelogArea->setVerticalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAsNeeded);
ui->modTreeWidget->setItemWidget(changelog, 0, changelog_area);
ui->modTreeWidget->setItemWidget(changelog, 0, changelogArea);
ui->modTreeWidget->addTopLevelItem(item_top);
ui->modTreeWidget->addTopLevelItem(itemTop);
}
auto ResourceUpdateDialog::getTasks() -> const QList<ResourceDownloadTask::Ptr>

View file

@ -39,7 +39,7 @@ class ResourceUpdateDialog final : public ReviewMessageBox {
private slots:
void onMetadataEnsured(Resource* resource);
void onMetadataFailed(Resource* resource,
bool try_others = false,
bool tryOthers = false,
ModPlatform::ResourceProvider firstChoice = ModPlatform::ResourceProvider::MODRINTH);
private:

View file

@ -50,7 +50,6 @@
#include "ResourceDownloadTask.h"
#include "minecraft/MinecraftInstance.h"
#include "minecraft/PackProfile.h"
#include "ui/dialogs/ResourceDownloadDialog.h"
@ -63,15 +62,15 @@ ModPage::ModPage(ModDownloadDialog* dialog, BaseInstance& instance) : ResourcePa
void ModPage::setFilterWidget(std::unique_ptr<ModFilterWidget>& widget)
{
if (m_filter_widget)
if (m_filter_widget) {
disconnect(m_filter_widget.get(), nullptr, nullptr, nullptr);
auto old = m_ui->splitter->replaceWidget(0, widget.get());
// because we replaced the widget we also need to delete it
if (old) {
delete old;
}
auto* old = m_ui->splitter->replaceWidget(0, widget.get());
// because we replaced the widget we also need to delete it
delete old;
m_filter_widget.swap(widget);
m_filter = m_filter_widget->getFilter();
@ -112,10 +111,13 @@ QMap<QString, QString> ModPage::urlHandlers() const
/******** Make changes to the UI ********/
void ModPage::addResourceToPage(ModPlatform::IndexedPack::Ptr pack, ModPlatform::IndexedVersion& version, ResourceFolderModel* base_model)
void ModPage::addResourceToPage(ModPlatform::IndexedPack::Ptr pack,
ModPlatform::IndexedVersion& version,
ResourceFolderModel* baseModel,
QString downloadReason)
{
bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool();
m_model->addPack(pack, version, base_model, is_indexed);
bool isIndexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool();
m_model->addPack(pack, version, baseModel, isIndexed, downloadReason);
}
} // namespace ResourceDownload

View file

@ -29,10 +29,10 @@ class ModPage : public ResourcePage {
static T* create(ModDownloadDialog* dialog, BaseInstance& instance)
{
auto page = new T(dialog, instance);
auto model = static_cast<ModModel*>(page->getModel());
auto* model = static_cast<ModModel*>(page->getModel());
auto filter_widget = page->createFilterWidget();
page->setFilterWidget(filter_widget);
auto filterWidget = page->createFilterWidget();
page->setFilterWidget(filterWidget);
model->setFilter(page->getFilter());
connect(model, &ResourceModel::versionListUpdated, page, &ResourcePage::versionListUpdated);
@ -43,18 +43,21 @@ class ModPage : public ResourcePage {
}
//: The plural version of 'mod'
inline QString resourcesString() const override { return tr("mods"); }
QString resourcesString() const override { return tr("mods"); }
//: The singular version of 'mods'
inline QString resourceString() const override { return tr("mod"); }
QString resourceString() const override { return tr("mod"); }
QMap<QString, QString> urlHandlers() const override;
void addResourceToPage(ModPlatform::IndexedPack::Ptr, ModPlatform::IndexedVersion&, ResourceFolderModel*) override;
void addResourceToPage(ModPlatform::IndexedPack::Ptr /*unused*/,
ModPlatform::IndexedVersion& /*unused*/,
ResourceFolderModel* /*unused*/,
QString downloadReason = "standalone") override;
virtual std::unique_ptr<ModFilterWidget> createFilterWidget() = 0;
bool supportsFiltering() const override { return true; };
auto getFilter() const -> const std::shared_ptr<ModFilterWidget::Filter> { return m_filter; }
auto getFilter() const -> std::shared_ptr<ModFilterWidget::Filter> { return m_filter; }
void setFilterWidget(std::unique_ptr<ModFilterWidget>&);
protected:

View file

@ -12,10 +12,11 @@
#include <QUrl>
#include <algorithm>
#include <memory>
#include <utility>
#include "Application.h"
#include "settings/SettingsObject.h"
#include "BuildConfig.h"
#include "settings/SettingsObject.h"
#include "modplatform/ResourceAPI.h"
#include "net/ApiDownload.h"
@ -29,7 +30,7 @@ namespace ResourceDownload {
QHash<ResourceModel*, bool> ResourceModel::s_running_models;
ResourceModel::ResourceModel(ResourceAPI* api) : QAbstractListModel(), m_api(api)
ResourceModel::ResourceModel(ResourceAPI* api) : m_api(api)
{
s_running_models.insert(this, true);
if (APPLICATION_DYN) {
@ -62,14 +63,14 @@ auto ResourceModel::data(const QModelIndex& index, int role) const -> QVariant
}
case Qt::DecorationRole: {
if (APPLICATION_DYN) {
if (auto icon_or_none = const_cast<ResourceModel*>(this)->getIcon(const_cast<QModelIndex&>(index), pack->logoUrl);
icon_or_none.has_value())
return icon_or_none.value();
if (auto iconOrNone = const_cast<ResourceModel*>(this)->getIcon(const_cast<QModelIndex&>(index), pack->logoUrl);
iconOrNone.has_value()) {
return iconOrNone.value();
}
return QIcon::fromTheme("screenshot-placeholder");
} else {
return {};
}
return {};
}
case Qt::SizeHintRole:
return QSize(0, 58);
@ -112,8 +113,9 @@ QHash<int, QByteArray> ResourceModel::roleNames() const
bool ResourceModel::setData(const QModelIndex& index, const QVariant& value, [[maybe_unused]] int role)
{
int pos = index.row();
if (pos >= m_packs.size() || pos < 0 || !index.isValid())
if (pos >= m_packs.size() || pos < 0 || !index.isValid()) {
return false;
}
m_packs[pos] = value.value<ModPlatform::IndexedPack::Ptr>();
emit dataChanged(index, index);
@ -128,45 +130,51 @@ QString ResourceModel::debugName() const
void ResourceModel::fetchMore(const QModelIndex& parent)
{
if (parent.isValid() || m_search_state == SearchState::Finished)
if (parent.isValid() || m_search_state == SearchState::Finished) {
return;
}
search();
}
void ResourceModel::search()
{
if (hasActiveSearchJob())
if (hasActiveSearchJob()) {
return;
}
if (m_search_state != SearchState::ResetRequested && m_search_term.startsWith("#")) {
auto projectId = m_search_term.mid(1);
if (!projectId.isEmpty()) {
ResourceAPI::Callback<ModPlatform::IndexedPack::Ptr> callbacks;
callbacks.on_fail = [this](QString reason, int network_error_code) {
if (!s_running_models.constFind(this).value())
callbacks.on_fail = [this](QString reason, int networkErrorCode) {
if (!s_running_models.constFind(this).value()) {
return;
if (network_error_code == 404) {
}
if (networkErrorCode == 404) {
m_search_state = SearchState::ResetRequested;
}
searchRequestFailed(reason, network_error_code);
searchRequestFailed(std::move(reason), networkErrorCode);
};
callbacks.on_abort = [this] {
if (!s_running_models.constFind(this).value())
if (!s_running_models.constFind(this).value()) {
return;
}
searchRequestAborted();
};
callbacks.on_succeed = [this](auto& pack) {
if (!s_running_models.constFind(this).value())
if (!s_running_models.constFind(this).value()) {
return;
}
searchRequestForOneSucceeded(pack);
};
auto project = std::make_shared<ModPlatform::IndexedPack>();
project->addonId = projectId;
if (auto job = m_api->getProjectInfo({ project }, std::move(callbacks), false); job)
if (auto job = m_api->getProjectInfo({ project }, std::move(callbacks), false); job) {
runSearchJob(job);
}
return;
}
}
@ -175,31 +183,36 @@ void ResourceModel::search()
ResourceAPI::Callback<QList<ModPlatform::IndexedPack::Ptr>> callbacks{};
callbacks.on_succeed = [this](auto& doc) {
if (!s_running_models.constFind(this).value())
if (!s_running_models.constFind(this).value()) {
return;
}
searchRequestSucceeded(doc);
};
callbacks.on_fail = [this](QString reason, int network_error_code) {
if (!s_running_models.constFind(this).value())
callbacks.on_fail = [this](QString reason, int networkErrorCode) {
if (!s_running_models.constFind(this).value()) {
return;
searchRequestFailed(reason, network_error_code);
}
searchRequestFailed(std::move(reason), networkErrorCode);
};
callbacks.on_abort = [this] {
if (!s_running_models.constFind(this).value())
if (!s_running_models.constFind(this).value()) {
return;
}
searchRequestAborted();
};
if (auto job = m_api->searchProjects(std::move(args), std::move(callbacks)); job)
if (auto job = m_api->searchProjects(std::move(args), std::move(callbacks)); job) {
runSearchJob(job);
}
}
void ResourceModel::loadEntry(const QModelIndex& entry)
{
auto const& pack = m_packs[entry.row()];
const auto& pack = m_packs[entry.row()];
if (!hasActiveInfoJob())
if (!hasActiveInfoJob()) {
m_current_info_job.clear();
}
if (!pack->versionsLoaded) {
auto args{ createVersionsArguments(entry) };
@ -207,20 +220,24 @@ void ResourceModel::loadEntry(const QModelIndex& entry)
auto addonId = pack->addonId;
// Use default if no callbacks are set
if (!callbacks.on_succeed)
if (!callbacks.on_succeed) {
callbacks.on_succeed = [this, entry, addonId](auto& doc) {
if (!s_running_models.constFind(this).value())
if (!s_running_models.constFind(this).value()) {
return;
}
versionRequestSucceeded(doc, addonId, entry);
};
if (!callbacks.on_fail)
callbacks.on_fail = [](QString reason, int) {
}
if (!callbacks.on_fail) {
callbacks.on_fail = [](const QString& reason, int) {
QMessageBox::critical(nullptr, tr("Error"),
tr("A network error occurred. Could not load project versions: %1").arg(reason));
};
}
if (auto job = m_api->getProjectVersions(std::move(args), std::move(callbacks)); job)
if (auto job = m_api->getProjectVersions(std::move(args), std::move(callbacks)); job) {
runInfoJob(job);
}
}
if (!pack->extraDataLoaded) {
@ -228,41 +245,45 @@ void ResourceModel::loadEntry(const QModelIndex& entry)
ResourceAPI::Callback<ModPlatform::IndexedPack::Ptr> callbacks{};
callbacks.on_succeed = [this, entry](auto& newpack) {
if (!s_running_models.constFind(this).value())
if (!s_running_models.constFind(this).value()) {
return;
}
infoRequestSucceeded(newpack, entry);
};
callbacks.on_fail = [this](QString reason, int) {
if (!s_running_models.constFind(this).value())
callbacks.on_fail = [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] {
if (!s_running_models.constFind(this).value())
if (!s_running_models.constFind(this).value()) {
return;
}
qCritical() << tr("The request was aborted for an unknown reason");
};
if (auto job = m_api->getProjectInfo(std::move(args), std::move(callbacks)); job)
if (auto job = m_api->getProjectInfo(std::move(args), std::move(callbacks)); job) {
runInfoJob(job);
}
}
}
void ResourceModel::refresh()
{
bool reset_requested = false;
bool resetRequested = false;
if (hasActiveInfoJob()) {
m_current_info_job.abort();
reset_requested = true;
resetRequested = true;
}
if (hasActiveSearchJob()) {
m_current_search_job->abort();
reset_requested = true;
resetRequested = true;
}
if (reset_requested) {
if (resetRequested) {
m_search_state = SearchState::ResetRequested;
return;
}
@ -288,13 +309,15 @@ void ResourceModel::runSearchJob(Task::Ptr ptr)
}
void ResourceModel::runInfoJob(Task::Ptr ptr)
{
if (!m_current_info_job.isRunning())
if (!m_current_info_job.isRunning()) {
m_current_info_job.clear();
}
m_current_info_job.addTask(ptr);
m_current_info_job.addTask(std::move(ptr));
if (!m_current_info_job.isRunning())
if (!m_current_info_job.isRunning()) {
m_current_info_job.run();
}
}
std::optional<ResourceAPI::SortingMethod> ResourceModel::getCurrentSortingMethodByIndex() const
@ -302,11 +325,12 @@ std::optional<ResourceAPI::SortingMethod> ResourceModel::getCurrentSortingMethod
std::optional<ResourceAPI::SortingMethod> sort{};
{ // Find sorting method by ID
auto sorting_methods = getSortingMethods();
auto method = std::find_if(sorting_methods.constBegin(), sorting_methods.constEnd(),
[this](auto const& e) { return m_current_sort_index == e.index; });
if (method != sorting_methods.constEnd())
auto sortingMethods = getSortingMethods();
auto method = std::find_if(sortingMethods.constBegin(), sortingMethods.constEnd(),
[this](const auto& e) { return m_current_sort_index == e.index; });
if (method != sortingMethods.constEnd()) {
sort = *method;
}
}
return sort;
@ -315,43 +339,47 @@ std::optional<ResourceAPI::SortingMethod> ResourceModel::getCurrentSortingMethod
std::optional<QIcon> ResourceModel::getIcon(QModelIndex& index, const QUrl& url)
{
QPixmap pixmap;
if (QPixmapCache::find(url.toString(), &pixmap))
if (QPixmapCache::find(url.toString(), &pixmap)) {
return { pixmap };
}
if (!m_current_icon_job) {
m_current_icon_job.reset(new NetJob("IconJob", APPLICATION->network()));
m_current_icon_job->setAskRetry(false);
}
if (m_currently_running_icon_actions.contains(url))
if (m_currently_running_icon_actions.contains(url)) {
return {};
if (m_failed_icon_actions.contains(url))
}
if (m_failed_icon_actions.contains(url)) {
return {};
}
auto cache_entry = APPLICATION->metacache()->resolveEntry(
auto cacheEntry = APPLICATION->metacache()->resolveEntry(
metaEntryBase(),
QString("logos/%1").arg(QString(QCryptographicHash::hash(url.toEncoded(), QCryptographicHash::Algorithm::Sha1).toHex())));
auto icon_fetch_action = Net::ApiDownload::makeCached(url, cache_entry);
auto iconFetchAction = Net::ApiDownload::makeCached(url, cacheEntry);
auto full_file_path = cache_entry->getFullPath();
connect(icon_fetch_action.get(), &Task::succeeded, this, [this, url, full_file_path, index] {
auto icon = QIcon(full_file_path);
auto fullFilePath = cacheEntry->getFullPath();
connect(iconFetchAction.get(), &Task::succeeded, this, [this, url, fullFilePath, index] {
auto icon = QIcon(fullFilePath);
QPixmapCache::insert(url.toString(), icon.pixmap(icon.actualSize({ 64, 64 })));
m_currently_running_icon_actions.remove(url);
emit dataChanged(index, index, { Qt::DecorationRole });
});
connect(icon_fetch_action.get(), &Task::failed, this, [this, url] {
connect(iconFetchAction.get(), &Task::failed, this, [this, url] {
m_currently_running_icon_actions.remove(url);
m_failed_icon_actions.insert(url);
});
m_currently_running_icon_actions.insert(url);
m_current_icon_job->addNetAction(icon_fetch_action);
if (!m_current_icon_job->isRunning())
m_current_icon_job->addNetAction(iconFetchAction);
if (!m_current_icon_job->isRunning()) {
QMetaObject::invokeMethod(m_current_icon_job.get(), &NetJob::start);
}
return {};
}
@ -363,11 +391,11 @@ void ResourceModel::searchRequestSucceeded(QList<ModPlatform::IndexedPack::Ptr>&
QList<ModPlatform::IndexedPack::Ptr> filteredNewList;
for (auto pack : newList) {
ModPlatform::IndexedPack::Ptr p;
if (auto sel = std::find_if(m_selected.begin(), m_selected.end(),
[&pack](const DownloadTaskPtr i) {
const auto ipack = i->getPack();
return ipack->provider == pack->provider && ipack->addonId == pack->addonId;
});
if (auto sel = std::ranges::find_if(m_selected,
[&pack](const DownloadTaskPtr& i) {
const auto ipack = i->getPack();
return ipack->provider == pack->provider && ipack->addonId == pack->addonId;
});
sel != m_selected.end()) {
p = sel->get()->getPack();
} else {
@ -386,8 +414,9 @@ void ResourceModel::searchRequestSucceeded(QList<ModPlatform::IndexedPack::Ptr>&
}
// When you have a Qt build with assertions turned on, proceeding here will abort the application
if (filteredNewList.size() == 0)
if (filteredNewList.size() == 0) {
return;
}
beginInsertRows(QModelIndex(), m_packs.size(), m_packs.size() + filteredNewList.size() - 1);
m_packs.append(filteredNewList);
@ -403,9 +432,9 @@ void ResourceModel::searchRequestForOneSucceeded(ModPlatform::IndexedPack::Ptr p
endInsertRows();
}
void ResourceModel::searchRequestFailed([[maybe_unused]] QString reason, int network_error_code)
void ResourceModel::searchRequestFailed([[maybe_unused]] QString reason, int networkErrorCode)
{
switch (network_error_code) {
switch (networkErrorCode) {
default:
// Network error
QMessageBox::critical(nullptr, tr("Error"), tr("A network error occurred. Could not load mods."));
@ -432,8 +461,9 @@ void ResourceModel::searchRequestFailed([[maybe_unused]] QString reason, int net
void ResourceModel::searchRequestAborted()
{
if (m_search_state != SearchState::ResetRequested)
if (m_search_state != SearchState::ResetRequested) {
qCritical() << "Search task in" << debugName() << "aborted by an unknown reason!";
}
// Retry fetching
clearData();
@ -444,19 +474,20 @@ void ResourceModel::searchRequestAborted()
void ResourceModel::versionRequestSucceeded(QVector<ModPlatform::IndexedVersion>& doc, QVariant pack, const QModelIndex& index)
{
auto current_pack = data(index, Qt::UserRole).value<ModPlatform::IndexedPack::Ptr>();
auto currentPack = data(index, Qt::UserRole).value<ModPlatform::IndexedPack::Ptr>();
// Check if the index is still valid for this resource or not
if (pack != current_pack->addonId)
if (pack != currentPack->addonId) {
return;
}
current_pack->versions = doc;
current_pack->versionsLoaded = true;
currentPack->versions = doc;
currentPack->versionsLoaded = true;
// Cache info :^)
QVariant new_pack;
new_pack.setValue(current_pack);
if (!setData(index, new_pack, Qt::UserRole)) {
QVariant newPack;
newPack.setValue(currentPack);
if (!setData(index, newPack, Qt::UserRole)) {
qWarning() << "Failed to cache resource versions!";
return;
}
@ -466,16 +497,17 @@ void ResourceModel::versionRequestSucceeded(QVector<ModPlatform::IndexedVersion>
void ResourceModel::infoRequestSucceeded(ModPlatform::IndexedPack::Ptr pack, const QModelIndex& index)
{
auto current_pack = data(index, Qt::UserRole).value<ModPlatform::IndexedPack::Ptr>();
auto currentPack = data(index, Qt::UserRole).value<ModPlatform::IndexedPack::Ptr>();
// Check if the index is still valid for this resource or not
if (pack->addonId != current_pack->addonId)
if (pack->addonId != currentPack->addonId) {
return;
}
// Cache info :^)
QVariant new_pack;
new_pack.setValue(pack);
if (!setData(index, new_pack, Qt::UserRole)) {
QVariant newPack;
newPack.setValue(pack);
if (!setData(index, newPack, Qt::UserRole)) {
qWarning() << "Failed to cache resource info!";
return;
}
@ -486,15 +518,16 @@ void ResourceModel::infoRequestSucceeded(ModPlatform::IndexedPack::Ptr pack, con
void ResourceModel::addPack(ModPlatform::IndexedPack::Ptr pack,
ModPlatform::IndexedVersion& version,
ResourceFolderModel* packs,
bool is_indexed)
bool isIndexed,
QString downloadReason)
{
version.is_currently_selected = true;
m_selected.append(makeShared<ResourceDownloadTask>(pack, version, packs, is_indexed));
m_selected.append(makeShared<ResourceDownloadTask>(std::move(pack), version, packs, isIndexed, std::move(downloadReason)));
}
void ResourceModel::removePack(const QString& rem)
{
auto pred = [&rem](const DownloadTaskPtr i) { return rem == i->getName(); };
auto pred = [&rem](const DownloadTaskPtr& i) { return rem == i->getName(); };
#if QT_VERSION >= QT_VERSION_CHECK(6, 1, 0)
m_selected.removeIf(pred);
#else
@ -506,15 +539,16 @@ void ResourceModel::removePack(const QString& rem)
++it;
}
#endif
auto pack = std::find_if(m_packs.begin(), m_packs.end(), [&rem](const ModPlatform::IndexedPack::Ptr i) { return rem == i->name; });
auto pack = std::ranges::find_if(m_packs, [&rem](const ModPlatform::IndexedPack::Ptr& i) { return rem == i->name; });
if (pack == m_packs.end()) { // ignore it if is not in the current search
return;
}
if (!pack->get()->versionsLoaded) {
return;
}
for (auto& ver : pack->get()->versions)
for (auto& ver : pack->get()->versions) {
ver.is_currently_selected = false;
}
}
bool ResourceModel::checkVersionFilters(const ModPlatform::IndexedVersion& v)

View file

@ -36,16 +36,16 @@ class ResourceModel : public QAbstractListModel {
ResourceModel(ResourceAPI* api);
~ResourceModel() override;
auto data(const QModelIndex&, int role) const -> QVariant override;
auto data(const QModelIndex& /*index*/, int role) const -> QVariant override;
auto roleNames() const -> QHash<int, QByteArray> override;
bool setData(const QModelIndex& index, const QVariant& value, int role) override;
virtual auto debugName() const -> QString;
virtual auto metaEntryBase() const -> QString = 0;
inline int rowCount(const QModelIndex& parent) const override { return parent.isValid() ? 0 : static_cast<int>(m_packs.size()); }
inline int columnCount(const QModelIndex& parent) const override { return parent.isValid() ? 0 : 1; }
inline auto flags(const QModelIndex& index) const -> Qt::ItemFlags override { return QAbstractListModel::flags(index); }
int rowCount(const QModelIndex& parent) const override { return parent.isValid() ? 0 : static_cast<int>(m_packs.size()); }
int columnCount(const QModelIndex& parent) const override { return parent.isValid() ? 0 : 1; }
auto flags(const QModelIndex& index) const -> Qt::ItemFlags override { return QAbstractListModel::flags(index); }
bool hasActiveSearchJob() const { return m_current_search_job && m_current_search_job->isRunning(); }
bool hasActiveInfoJob() const { return m_current_info_job.isRunning(); }
@ -66,7 +66,7 @@ class ResourceModel : public QAbstractListModel {
public slots:
void fetchMore(const QModelIndex& parent) override;
inline bool canFetchMore(const QModelIndex& parent) const override
bool canFetchMore(const QModelIndex& parent) const override
{
return parent.isValid() ? false : m_search_state == SearchState::CanFetchMore;
}
@ -94,7 +94,8 @@ class ResourceModel : public QAbstractListModel {
void addPack(ModPlatform::IndexedPack::Ptr pack,
ModPlatform::IndexedVersion& version,
ResourceFolderModel* packs,
bool is_indexed = false);
bool isIndexed = false,
QString downloadReason = "standalone");
void removePack(const QString& rem);
QList<DownloadTaskPtr> selectedPacks() { return m_selected; }

View file

@ -395,10 +395,13 @@ void ResourcePage::removeResourceFromDialog(const QString& packName)
m_parentDialog->removeResource(packName);
}
void ResourcePage::addResourceToPage(ModPlatform::IndexedPack::Ptr pack, ModPlatform::IndexedVersion& ver, ResourceFolderModel* baseModel)
void ResourcePage::addResourceToPage(ModPlatform::IndexedPack::Ptr pack,
ModPlatform::IndexedVersion& ver,
ResourceFolderModel* baseModel,
QString downloadReason)
{
bool isIndexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool();
m_model->addPack(std::move(pack), ver, baseModel, isIndexed);
m_model->addPack(std::move(pack), ver, baseModel, isIndexed, std::move(downloadReason));
}
void ResourcePage::modelReset()

View file

@ -9,7 +9,6 @@
#include "ResourceDownloadTask.h"
#include "modplatform/ModIndex.h"
#include "modplatform/ResourceAPI.h"
#include "ui/pages/BasePage.h"
#include "ui/pages/modplatform/ResourceModel.h"
@ -78,7 +77,10 @@ class ResourcePage : public QWidget, public BasePage {
void addResourceToDialog(ModPlatform::IndexedPack::Ptr, ModPlatform::IndexedVersion&);
void removeResourceFromDialog(const QString& packName);
virtual void removeResourceFromPage(const QString& name);
virtual void addResourceToPage(ModPlatform::IndexedPack::Ptr, ModPlatform::IndexedVersion&, ResourceFolderModel*);
virtual void addResourceToPage(ModPlatform::IndexedPack::Ptr,
ModPlatform::IndexedVersion&,
ResourceFolderModel*,
QString downloadReason = "standalone");
virtual void modelReset();

View file

@ -36,18 +36,18 @@ QMap<QString, QString> ShaderPackResourcePage::urlHandlers() const
{
QMap<QString, QString> map;
map.insert(QRegularExpression::anchoredPattern("(?:www\\.)?modrinth\\.com\\/shaders\\/([^\\/]+)\\/?"), "modrinth");
map.insert(QRegularExpression::anchoredPattern("(?:www\\.)?curseforge\\.com\\/minecraft\\/customization\\/([^\\/]+)\\/?"),
"curseforge");
map.insert(QRegularExpression::anchoredPattern("minecraft\\.curseforge\\.com\\/projects\\/([^\\/]+)\\/?"), "curseforge");
map.insert(QRegularExpression::anchoredPattern(R"((?:www\.)?curseforge\.com\/minecraft\/customization\/([^\/]+)\/?)"), "curseforge");
map.insert(QRegularExpression::anchoredPattern(R"(minecraft\.curseforge\.com\/projects\/([^\/]+)\/?)"), "curseforge");
return map;
}
void ShaderPackResourcePage::addResourceToPage(ModPlatform::IndexedPack::Ptr pack,
ModPlatform::IndexedVersion& version,
ResourceFolderModel* base_model)
ResourceFolderModel* baseModel,
QString downloadReason)
{
bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool();
m_model->addPack(pack, version, base_model, is_indexed);
bool isIndexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool();
m_model->addPack(pack, version, baseModel, isIndexed, downloadReason);
}
} // namespace ResourceDownload

View file

@ -23,7 +23,7 @@ class ShaderPackResourcePage : public ResourcePage {
static T* create(ShaderPackDownloadDialog* dialog, BaseInstance& instance)
{
auto page = new T(dialog, instance);
auto model = static_cast<ShaderPackResourceModel*>(page->getModel());
auto* model = static_cast<ShaderPackResourceModel*>(page->getModel());
connect(model, &ResourceModel::versionListUpdated, page, &ResourcePage::versionListUpdated);
connect(model, &ResourceModel::projectInfoUpdated, page, &ResourcePage::updateUi);
@ -33,17 +33,20 @@ class ShaderPackResourcePage : public ResourcePage {
}
//: The plural version of 'shader pack'
inline QString resourcesString() const override { return tr("shader packs"); }
QString resourcesString() const override { return tr("shader packs"); }
//: The singular version of 'shader packs'
inline QString resourceString() const override { return tr("shader pack"); }
QString resourceString() const override { return tr("shader pack"); }
bool supportsFiltering() const override { return false; };
void addResourceToPage(ModPlatform::IndexedPack::Ptr, ModPlatform::IndexedVersion&, ResourceFolderModel*) override;
void addResourceToPage(ModPlatform::IndexedPack::Ptr /*unused*/,
ModPlatform::IndexedVersion& /*unused*/,
ResourceFolderModel* /*unused*/,
QString downloadReason = "standalone") override;
QMap<QString, QString> urlHandlers() const override;
inline auto helpPage() const -> QString override { return "shaderpack-platform"; }
auto helpPage() const -> QString override { return "shaderpack-platform"; }
protected:
ShaderPackResourcePage(ShaderPackDownloadDialog* dialog, BaseInstance& instance);