NOISSUE Working import from Modrinth, license update to accomodate it

This commit is contained in:
Petr Mrázek 2022-05-16 00:25:36 +02:00
parent acc637cfcb
commit 5c1026bd12
9 changed files with 617 additions and 49 deletions

View File

@ -46,7 +46,7 @@ set(CMAKE_CXX_FLAGS_RELEASE " -O3 -D_FORTIFY_SOURCE=2 ${CMAKE_CXX_FLAGS_RELEASE}
if(UNIX AND APPLE)
set(CMAKE_CXX_FLAGS " -stdlib=libc++ ${CMAKE_CXX_FLAGS}")
endif()
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Werror=return-type")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Werror=return-type -O0")
# Fix build with Qt 5.13
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DQT_NO_DEPRECATED_WARNINGS=Y")

View File

@ -1,5 +1,7 @@
# MultiMC
Portions are licensed under Apache 2.0 License:
Copyright 2012-2021 MultiMC Contributors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -13,6 +15,67 @@
See the License for the specific language governing permissions and
limitations under the License.
Portions are licensed under MS-PL:
This license governs use of the accompanying software. If you use the
software, you accept this license. If you do not accept the license,
do not use the software.
1. Definitions
The terms "reproduce," "reproduction," "derivative works," and
"distribution" have the same meaning here as under U.S. copyright law.
A "contribution" is the original software, or any additions or
changes to the software.
A "contributor" is any person that distributes its contribution
under this license.
"Licensed patents" are a contributor's patent claims that read
directly on its contribution.
2. Grant of Rights
(A) Copyright Grant- Subject to the terms of this license,
including the license conditions and limitations in section 3,
each contributor grants you a non-exclusive, worldwide, royalty-free
copyright license to reproduce its contribution, prepare derivative
works of its contribution, and distribute its contribution or any
derivative works that you create.
(B) Patent Grant- Subject to the terms of this license, including
the license conditions and limitations in section 3, each contributor
grants you a non-exclusive, worldwide, royalty-free license under its
licensed patents to make, have made, use, sell, offer for sale, import,
and/or otherwise dispose of its contribution in the software or derivative
works of the contribution in the software.
3. Conditions and Limitations
(A) No Trademark License- This license does not grant you rights to
use any contributors' name, logo, or trademarks.
(B) If you bring a patent claim against any contributor over patents
that you claim are infringed by the software, your patent license
from such contributor to the software ends automatically.
(C) If you distribute any portion of the software, you must retain all
copyright, patent, trademark, and attribution notices that are present
in the software.
(D) If you distribute any portion of the software in source code form,
you may do so only under this license by including a complete copy of
this license with your distribution. If you distribute any portion of
the software in compiled or object code form, you may only do so under
a license that complies with this license.
(E) The software is licensed "as-is." You bear the risk of using it.
The contributors give no express warranties, guarantees or conditions.
You may have additional consumer rights under your local laws which
this license cannot change. To the extent permitted under your local
laws, the contributors exclude the implied warranties of merchantability,
fitness for a particular purpose and non-infringement.
# MinGW runtime (Windows)
Copyright (c) 2012 MinGW.org project

View File

@ -859,6 +859,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
m_metacache->addBase("ModpacksCHPacks", QDir("cache/ModpacksCHPacks").absolutePath());
m_metacache->addBase("TechnicPacks", QDir("cache/TechnicPacks").absolutePath());
m_metacache->addBase("FlamePacks", QDir("cache/FlamePacks").absolutePath());
m_metacache->addBase("ModrinthPacks", QDir("cache/ModrinthPacks").absolutePath());
m_metacache->addBase("root", QDir::currentPath());
m_metacache->addBase("translations", QDir("translations").absolutePath());
m_metacache->addBase("icons", QDir("cache/icons").absolutePath());

View File

@ -474,7 +474,7 @@ void InstanceImportTask::processMultiMC()
void InstanceImportTask::processModrinth() {
std::vector<Modrinth::File> files;
QString minecraftVersion, fabricVersion, forgeVersion;
QString minecraftVersion, fabricVersion, quiltVersion, forgeVersion;
try
{
QString indexPath = FS::PathCombine(m_stagingPath, "modrinth.index.json");
@ -490,40 +490,53 @@ void InstanceImportTask::processModrinth() {
}
auto jsonFiles = Json::requireIsArrayOf<QJsonObject>(obj, "files", "modrinth.index.json");
std::transform(jsonFiles.begin(), jsonFiles.end(), std::back_inserter(files), [](const QJsonObject& obj)
for(auto & obj: jsonFiles) {
Modrinth::File file;
file.path = Json::requireString(obj, "path");
// env doesn't have to be present, in that case mod is required
auto env = Json::ensureObject(obj, "env");
auto clientEnv = Json::ensureString(env, "client", "required");
if(clientEnv == "required") {
// NOOP
}
else if(clientEnv == "optional") {
file.path += ".disabled";
}
else if(clientEnv == "unsupported") {
continue;
}
QJsonObject hashes = Json::requireObject(obj, "hashes");
QString hash;
QCryptographicHash::Algorithm hashAlgorithm;
hash = Json::ensureString(hashes, "sha256");
hashAlgorithm = QCryptographicHash::Sha256;
if (hash.isEmpty())
{
Modrinth::File file;
file.path = Json::requireString(obj, "path");
QString supported = Json::ensureString(Json::ensureObject(obj, "env"));
QJsonObject hashes = Json::requireObject(obj, "hashes");
QString hash;
QCryptographicHash::Algorithm hashAlgorithm;
hash = Json::ensureString(hashes, "sha256");
hashAlgorithm = QCryptographicHash::Sha256;
hash = Json::ensureString(hashes, "sha512");
hashAlgorithm = QCryptographicHash::Sha512;
if (hash.isEmpty())
{
hash = Json::ensureString(hashes, "sha512");
hashAlgorithm = QCryptographicHash::Sha512;
hash = Json::ensureString(hashes, "sha1");
hashAlgorithm = QCryptographicHash::Sha1;
if (hash.isEmpty())
{
hash = Json::ensureString(hashes, "sha1");
hashAlgorithm = QCryptographicHash::Sha1;
if (hash.isEmpty())
{
throw JSONValidationError("No hash found for: " + file.path);
}
throw JSONValidationError("No hash found for: " + file.path);
}
}
file.hash = QByteArray::fromHex(hash.toLatin1());
file.hashAlgorithm = hashAlgorithm;
// Do not use requireUrl, which uses StrictMode, instead use QUrl's default TolerantMode (as Modrinth seems to incorrectly handle spaces)
file.download = Json::requireString(Json::ensureArray(obj, "downloads").first(), "Download URL for " + file.path);
if (!file.download.isValid())
{
throw JSONValidationError("Download URL for " + file.path + " is not a correctly formatted URL");
}
return file;
});
}
file.hash = QByteArray::fromHex(hash.toLatin1());
file.hashAlgorithm = hashAlgorithm;
// Do not use requireUrl, which uses StrictMode, instead use QUrl's default TolerantMode (as Modrinth seems to incorrectly handle spaces)
file.download = Json::requireString(Json::ensureArray(obj, "downloads").first(), "Download URL for " + file.path);
if (!file.download.isValid())
{
throw JSONValidationError("Download URL for " + file.path + " is not a correctly formatted URL");
}
files.push_back(file);
}
auto dependencies = Json::requireObject(obj, "dependencies", "modrinth.index.json");
for (auto it = dependencies.begin(), end = dependencies.end(); it != end; ++it)
@ -541,6 +554,12 @@ void InstanceImportTask::processModrinth() {
throw JSONValidationError("Duplicate Fabric Loader version");
fabricVersion = Json::requireString(*it, "Fabric Loader version");
}
else if (name == "quilt-loader")
{
if (!quiltVersion.isEmpty())
throw JSONValidationError("Duplicate Quilt Loader version");
quiltVersion = Json::requireString(*it, "Quilt Loader version");
}
else if (name == "forge")
{
if (!forgeVersion.isEmpty())
@ -583,6 +602,8 @@ void InstanceImportTask::processModrinth() {
components->setComponentVersion("net.minecraft", minecraftVersion, true);
if (!fabricVersion.isEmpty())
components->setComponentVersion("net.fabricmc.fabric-loader", fabricVersion, true);
if (!quiltVersion.isEmpty())
components->setComponentVersion("org.quiltmc.quilt-loader", quiltVersion, true);
if (!forgeVersion.isEmpty())
components->setComponentVersion("net.minecraftforge", forgeVersion, true);
if (m_instIcon != "default")
@ -602,11 +623,10 @@ void InstanceImportTask::processModrinth() {
m_filesNetJob->addNetAction(dl);
}
connect(m_filesNetJob.get(), &NetJob::succeeded, this, [&]()
{
m_filesNetJob.reset();
emitSucceeded();
}
);
{
m_filesNetJob.reset();
emitSucceeded();
});
connect(m_filesNetJob.get(), &NetJob::failed, [&](const QString &reason)
{
m_filesNetJob.reset();

View File

@ -10,8 +10,40 @@
#include <QString>
#include <QMetaType>
#include <QUrl>
#include <QDateTime>
#include <QVector>
namespace Modrinth {
enum class LoadState {
NotLoaded = 0,
Loaded = 1,
Errored = 2
};
enum class VersionType {
Alpha,
Beta,
Release,
Unknown
};
struct Download {
bool valid = false;
QString filename;
QString url;
QString sha1;
uint64_t size = 0;
bool primary = false;
};
struct Version {
QString name;
Download download;
QDateTime released;
VersionType type = VersionType::Unknown;
bool featured = false;
};
struct Modpack {
QString id;
@ -20,10 +52,15 @@ struct Modpack {
QString author;
QString description;
bool metadataLoaded = false;
LoadState detailsLoaded = LoadState::NotLoaded;
QString wikiUrl;
QString body;
LoadState versionsLoaded = LoadState::NotLoaded;
QVector<Version> versions;
};
}
Q_DECLARE_METATYPE(Modrinth::Download)
Q_DECLARE_METATYPE(Modrinth::Version)
Q_DECLARE_METATYPE(Modrinth::Modpack)

View File

@ -108,12 +108,12 @@ void Modrinth::ListModel::performPaginatedSearch()
auto *netJob = new NetJob("Modrinth::Search", APPLICATION->network());
QString searchUrl = "";
if (currentSearchTerm.isEmpty()) {
searchUrl = QString("https://staging-api.modrinth.com/v2/search?facets=[[%22project_type:modpack%22]]&index=%1&limit=25&offset=%2").arg(currentSort).arg(nextSearchOffset);
searchUrl = QString("https://api.modrinth.com/v2/search?facets=[[%22project_type:modpack%22]]&index=%1&limit=25&offset=%2").arg(currentSort).arg(nextSearchOffset);
}
else
{
searchUrl = QString(
"https://staging-api.modrinth.com/v2/search?facets=[[%22project_type:modpack%22]]&index=%1&limit=25&offset=%2&query=%3"
"https://api.modrinth.com/v2/search?facets=[[%22project_type:modpack%22]]&index=%1&limit=25&offset=%2&query=%3"
).arg(currentSort).arg(nextSearchOffset).arg(currentSearchTerm);
}
netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), &response));
@ -136,7 +136,7 @@ void Modrinth::ListModel::searchRequestFinished()
return;
}
QList<Modrinth::Modpack> newList;
QVector<Modrinth::Modpack> newList;
QJsonArray hits;
int total_hits;
@ -228,8 +228,8 @@ void Modrinth::ListModel::requestLogo(const QString &logo, const QUrl &url)
return;
}
MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("FlamePacks", QString("logos/%1").arg(logo.section(".", 0, 0)));
auto *job = new NetJob(QString("Flame Icon Download %1").arg(logo), APPLICATION->network());
MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("ModrinthPacks", QString("logos/%1").arg(logo.section(".", 0, 0)));
auto *job = new NetJob(QString("Modrinth Icon Download %1").arg(logo), APPLICATION->network());
job->addNetAction(Net::Download::makeCached(url, entry));
auto fullPath = entry->getFullPath();
@ -239,10 +239,7 @@ void Modrinth::ListModel::requestLogo(const QString &logo, const QUrl &url)
QSize size = icon.actualSize(QSize(48, 48));
if (size.width() < 48 && size.height() < 48)
{
/*while (size.width() < 48 && size.height() < 48)
size *= 2;
icon = icon.pixmap(48, 48).scaled(size);*/
icon = icon.pixmap(48,48).scaled(48,48, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation);
icon = icon.pixmap(48, 48).scaled(48,48, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation);
}
logoLoaded(logo, icon);
if(waitingCallbacks.contains(logo))
@ -260,3 +257,315 @@ void Modrinth::ListModel::requestLogo(const QString &logo, const QUrl &url)
m_loadingLogos.append(logo);
}
void Modrinth::ListModel::getPackDetails(const QString& id)
{
auto index = getIndexFromId(id);
if(!index) {
return;
}
if(isPackDetailInProgress()) {
queuedPackDetailRequest = id;
cancelPackDetail();
return;
}
currentPackDetailRequest = id;
QString detailsUrl = "https://api.modrinth.com/v2/project/" + id;
auto & modpack = modpacks[*index];
if(modpack.detailsLoaded != LoadState::Loaded)
{
auto *netJob = new NetJob("Modrinth::PackDetails", APPLICATION->network());
netJob->addNetAction(Net::Download::makeByteArray(QUrl(detailsUrl), &detailsResponse));
detailsPtr = netJob;
detailsPtr->start();
QObject::connect(netJob, &NetJob::succeeded, this, &ListModel::detailsRequestFinished);
QObject::connect(netJob, &NetJob::failed, this, &ListModel::detailsRequestFailed);
}
QString versionsUrl = detailsUrl + "/version";
if(modpack.versionsLoaded != LoadState::Loaded)
{
auto *netJob = new NetJob("Modrinth::PackVersions", APPLICATION->network());
netJob->addNetAction(Net::Download::makeByteArray(QUrl(versionsUrl), &versionsResponse));
versionsPtr = netJob;
versionsPtr->start();
QObject::connect(netJob, &NetJob::succeeded, this, &ListModel::versionsRequestFinished);
QObject::connect(netJob, &NetJob::failed, this, &ListModel::versionsRequestFailed);
}
}
bool Modrinth::ListModel::isPackDetailInProgress()
{
return detailsPtr || versionsPtr;
}
void Modrinth::ListModel::cancelPackDetail()
{
if(detailsPtr) {
detailsPtr->abort();
}
if(versionsPtr) {
versionsPtr->abort();
}
}
nonstd::optional<int> Modrinth::ListModel::getIndexFromId(const QString& id)
{
for(int i = 0; i < modpacks.size(); i++) {
if(modpacks[i].id == id) {
return i;
}
}
return nonstd::nullopt;
}
nonstd::optional<Modrinth::Modpack> Modrinth::ListModel::getModpackById(const QString& id)
{
auto index = getIndexFromId(id);
if(!index) {
return nonstd::nullopt;
}
return modpacks[*index];
}
namespace {
bool parseDetailsInto(QByteArray & input, Modrinth::Modpack& output) {
QJsonParseError parse_error;
QJsonDocument doc = QJsonDocument::fromJson(input, &parse_error);
if(parse_error.error != QJsonParseError::NoError)
{
qWarning() << "Error while parsing pack details response from Modrinth at " << parse_error.offset << " reason: " << parse_error.errorString();
qWarning() << input;
return false;
}
try
{
auto obj = Json::requireObject(doc);
QString body = Json::requireString(obj, "body");
output.body = body;
return true;
}
catch(const JSONValidationError &e)
{
qWarning() << "Error while parsing response from Modrinth: " << e.cause();
return false;
}
}
}
void Modrinth::ListModel::detailsRequestFinished()
{
auto index = getIndexFromId(currentPackDetailRequest);
if(index) {
auto & modpack = modpacks[*index];
if(parseDetailsInto(detailsResponse, modpack)) {
modpack.detailsLoaded = LoadState::Loaded;
}
else {
modpack.detailsLoaded = LoadState::Errored;
}
emit packDataChanged(currentPackDetailRequest);
}
detailsPtr.reset();
checkDetailsDone();
}
void Modrinth::ListModel::detailsRequestFailed()
{
auto index = getIndexFromId(currentPackDetailRequest);
if(index) {
auto & modpack = modpacks[*index];
if(modpack.detailsLoaded == LoadState::NotLoaded) {
modpack.detailsLoaded = LoadState::Errored;
emit packDataChanged(currentPackDetailRequest);
}
}
detailsPtr.reset();
checkDetailsDone();
}
/*
{
"id": "8mMRnfwS",
"project_id": "WCJmvhgU",
"author_id": "akScBBW1",
"featured": true,
"name": "Third Release",
"version_number": "2022.1.12",
"changelog": "This is the third release!",
"changelog_url": null,
"date_published": "2022-01-12T20:41:27+00:00",
"downloads": 22,
"version_type": "release",
"files": [
{
"hashes": {
"sha1": "0fe87efacfd25c4c5e011cd1433e9be494b23b1c",
"sha512": "d43e148d35d0267b49ed14a7bb4bb5879aa3ebbf5805c2fe125f0f0f19fd84994242bdcd568399c9ff80ecfbe0e975ab632d8c71773bc09d54cfc857f9a6f716"
},
"url": "https://cdn.modrinth.com/data/WCJmvhgU/versions/2022.1.12/waffles_Modpack-2022.1.12no-hydrogen.mrpack",
"filename": "waffles_Modpack-2022.1.12no-hydrogen.mrpack",
"primary": false,
"size": 0
}
],
"dependencies": [],
"game_versions": [
"1.18.1"
],
"loaders": [
"fabric"
]
}
*/
bool parseFile(QJsonObject & fileObj, Modrinth::Download & out) {
out.primary = Json::requireBoolean(fileObj, "primary");
out.size = Json::requireInteger(fileObj, "size");
out.url = Json::requireString(fileObj, "url");
out.filename = Json::requireString(fileObj, "filename");
auto hashesObj = fileObj["hashes"].toObject();
out.sha1 = Json::requireString(hashesObj, "sha1");
if(!out.filename.endsWith(".mrpack")) {
out.valid = false;
return false;
}
else {
out.valid = true;
return true;
}
}
namespace {
bool parseVersionsInto(QByteArray & input, Modrinth::Modpack& output) {
QJsonParseError parse_error;
QJsonDocument doc = QJsonDocument::fromJson(input, &parse_error);
if(parse_error.error != QJsonParseError::NoError)
{
qWarning() << "Error while parsing pack versions response from Modrinth at " << parse_error.offset << " reason: " << parse_error.errorString();
qWarning() << input;
return false;
}
qDebug() << input;
try
{
QVector<Modrinth::Version> newList;
QJsonArray versions = Json::requireArray(doc);
for (auto obj : versions)
{
auto packObj = obj.toObject();
Modrinth::Version version;
try
{
if (Json::ensureString(packObj, "client_side", "required") == QStringLiteral("unsupported"))
continue;
version.name = Json::requireString(packObj, "version_number");
version.released = Json::requireDateTime(packObj, "date_published");
version.featured = Json::requireBoolean(packObj, "featured");
auto versionTypeString = Json::requireString(packObj, "version_type");
if(versionTypeString == "alpha") {
version.type = Modrinth::VersionType::Alpha;
}
else if(versionTypeString == "beta") {
version.type = Modrinth::VersionType::Beta;
}
else if (versionTypeString == "release") {
version.type = Modrinth::VersionType::Release;
}
else {
qWarning() << "Unknown version type of Modrinth modpack: " << versionTypeString;
version.type = Modrinth::VersionType::Unknown;
}
Modrinth::Download fallbackOut = {};
auto filesArray = Json::requireArray(packObj, "files");
for(int i = 0; i < filesArray.size(); i++) {
Modrinth::Download maybeFileOut = {};
QJsonObject fileObj = filesArray[i].toObject();
parseFile(fileObj, maybeFileOut);
if(i == 0) {
fallbackOut = maybeFileOut;
}
if(maybeFileOut.valid && maybeFileOut.primary) {
version.download = maybeFileOut;
break;
}
}
if(!version.download.valid) {
version.download = fallbackOut;
}
if(version.download.valid) {
newList.append(version);
}
}
catch(const JSONValidationError &e)
{
qWarning() << "Error while loading pack from Modrinth: " << e.cause();
continue;
}
}
output.versions = newList;
return true;
}
catch(const JSONValidationError &e)
{
qWarning() << "Error while parsing response from Modrinth: " << e.cause();
return false;
}
}
}
void Modrinth::ListModel::versionsRequestFinished()
{
auto index = getIndexFromId(currentPackDetailRequest);
if(index) {
auto & modpack = modpacks[*index];
parseVersionsInto(versionsResponse, modpack);
modpack.versionsLoaded = LoadState::Loaded;
emit packDataChanged(currentPackDetailRequest);
}
versionsPtr.reset();
checkDetailsDone();
}
void Modrinth::ListModel::versionsRequestFailed()
{
auto index = getIndexFromId(currentPackDetailRequest);
if(index) {
auto & modpack = modpacks[*index];
if(modpack.versionsLoaded == LoadState::NotLoaded) {
modpack.versionsLoaded = LoadState::Errored;
emit packDataChanged(currentPackDetailRequest);
}
}
versionsPtr.reset();
checkDetailsDone();
}
void Modrinth::ListModel::checkDetailsDone()
{
if(isPackDetailInProgress()) {
return;
}
// all detail requests are finished
currentPackDetailRequest.clear();
// is there a new one queued?
if(!queuedPackDetailRequest.isNull()) {
getPackDetails(queuedPackDetailRequest);
queuedPackDetailRequest.clear();
}
}

View File

@ -12,6 +12,8 @@
#include "net/NetJob.h"
#include <QAbstractListModel>
#include <QVector>
#include <nonstd/optional>
namespace Modrinth {
@ -31,11 +33,22 @@ public:
void fetchMore(const QModelIndex &parent) override;
void searchWithTerm(const QString &term, const QString &sort);
void getPackDetails(const QString &id);
nonstd::optional<Modpack> getModpackById(const QString &id);
signals:
void packDataChanged(const QString &id);
private slots:
void searchRequestFinished();
void searchRequestFailed();
void detailsRequestFinished();
void detailsRequestFailed();
void versionsRequestFinished();
void versionsRequestFailed();
private:
void performPaginatedSearch();
@ -44,7 +57,13 @@ private:
void requestLogo(const QString &logo, const QUrl &url);
QList<Modpack> modpacks;
nonstd::optional<int> getIndexFromId(const QString &id);
bool isPackDetailInProgress();
void cancelPackDetail();
void checkDetailsDone();
QVector<Modpack> modpacks;
QStringList m_failedLogos;
QStringList m_loadingLogos;
QMap<QString, QIcon> m_logoMap;
@ -59,8 +78,17 @@ private:
ResetRequested,
Finished
} searchState = None;
QString queuedPackDetailRequest;
QString currentPackDetailRequest;
NetJob::Ptr jobPtr;
QByteArray response;
NetJob::Ptr detailsPtr;
QByteArray detailsResponse;
NetJob::Ptr versionsPtr;
QByteArray versionsResponse;
};
}

View File

@ -22,6 +22,8 @@
#include "ui_ModrinthPage.h"
#include <QKeyEvent>
#include <HoeDown.h>
#include <InstanceImportTask.h>
ModrinthPage::ModrinthPage(NewInstanceDialog *dialog, QWidget *parent) : QWidget(parent), ui(new Ui::ModrinthPage), dialog(dialog)
{
@ -42,7 +44,8 @@ ModrinthPage::ModrinthPage(NewInstanceDialog *dialog, QWidget *parent) : QWidget
connect(ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch()));
connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &ModrinthPage::onSelectionChanged);
//connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &ModrinthPage::onVersionSelectionChanged);
connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &ModrinthPage::onVersionSelectionChanged);
connect(model, &Modrinth::ListModel::packDataChanged, this, &ModrinthPage::onPackDataChanged);
}
ModrinthPage::~ModrinthPage()
@ -85,9 +88,112 @@ void ModrinthPage::onSelectionChanged(QModelIndex first, QModelIndex second) {
}
current = model->data(first, Qt::UserRole).value<Modrinth::Modpack>();
model->getPackDetails(current.id);
updateCurrentPackUI();
suggestCurrent();
}
void ModrinthPage::suggestCurrent() {
void ModrinthPage::onVersionSelectionChanged(const QString& version) {
if(version.isEmpty() || ui->versionSelectionBox->count() == 0) {
currentVersion = Modrinth::Version();
}
else {
currentVersion = ui->versionSelectionBox->currentData().value<Modrinth::Version>();
}
}
}
void ModrinthPage::suggestCurrent()
{
if(!isOpened)
{
return;
}
if (!currentVersion.name.size())
{
dialog->setSuggestedPack();
return;
}
dialog->setSuggestedPack(current.name + " " + currentVersion.name, new InstanceImportTask(currentVersion.download.url));
MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("ModrinthPacks", QString("logos/%1").arg(current.id));
dialog->setSuggestedIconFromFile(entry->getFullPath(), QString("modrinth-%1").arg(current.id));
}
void ModrinthPage::onPackDataChanged(const QString& id)
{
if(id != current.id) {
return;
}
auto newData = model->getModpackById(id);
if(newData) {
current = *newData;
updateCurrentPackUI();
}
}
namespace {
QString processMarkdown(QString input)
{
HoeDown hoedown;
return hoedown.process(input.toUtf8());
}
}
QString versionToString(const Modrinth::Version& version) {
switch(version.type) {
case Modrinth::VersionType::Alpha: {
return QString("%1 (Alpha)").arg(version.name);
}
case Modrinth::VersionType::Beta: {
return QString("%1 (Beta)").arg(version.name);
}
case Modrinth::VersionType::Release: {
return version.name;
}
case Modrinth::VersionType::Unknown: {
return QString("%1 (?)").arg(version.name);
}
}
}
void ModrinthPage::updateCurrentPackUI()
{
switch(current.detailsLoaded) {
case Modrinth::LoadState::Errored: {
ui->packDescription->setText(tr("Failed to get Modrinth modpack details..."));
break;
}
case Modrinth::LoadState::NotLoaded: {
ui->packDescription->setText(tr("Loading..."));
break;
}
case Modrinth::LoadState::Loaded: {
ui->packDescription->setText(processMarkdown(current.body));
break;
}
}
if(current.versions.size() == 0) {
ui->versionSelectionBox->clear();
}
else {
ui->versionSelectionBox->clear();
int releaseFound = -1;
int i = 0;
for(auto & version: current.versions) {
ui->versionSelectionBox->addItem(versionToString(version), QVariant::fromValue(version));
if(releaseFound == -1 && version.type == Modrinth::VersionType::Release) {
releaseFound = i;
}
i++;
}
if(releaseFound != -1) {
ui->versionSelectionBox->setCurrentIndex(releaseFound);
}
else if(current.versions.size() != 0) {
ui->versionSelectionBox->setCurrentIndex(0);
}
// select first release found from the top
}
suggestCurrent();
}

View File

@ -44,7 +44,7 @@ public:
QString displayName() const override
{
return tr("Modrinth");
return tr("Modrinth (WIP)");
}
QIcon icon() const override
{
@ -62,12 +62,16 @@ public:
private slots:
void triggerSearch();
void onSelectionChanged(QModelIndex first, QModelIndex second);
void onVersionSelectionChanged(const QString & version);
void onPackDataChanged(const QString &id);
private:
void updateCurrentPackUI();
void suggestCurrent();
Ui::ModrinthPage *ui;
NewInstanceDialog *dialog;
Modrinth::ListModel *model = nullptr;
Modrinth::Modpack current;
Modrinth::Version currentVersion;
};