Merge pull request #384 from jamierocks/technic-improvements

This commit is contained in:
Sefa Eyeoglu 2022-04-06 10:52:27 +02:00 committed by GitHub
commit 99d569ed0e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 585 additions and 218 deletions

View File

@ -140,6 +140,12 @@ public:
QString ATL_DOWNLOAD_SERVER_URL = "https://download.nodecdn.net/containers/atl/"; QString ATL_DOWNLOAD_SERVER_URL = "https://download.nodecdn.net/containers/atl/";
QString TECHNIC_API_BASE_URL = "https://api.technicpack.net/";
/**
* The build that is reported to the Technic API.
*/
QString TECHNIC_API_BUILD = "multimc";
/** /**
* \brief Converts the Version to a string. * \brief Converts the Version to a string.
* \return The version number in string format (major.minor.revision.build). * \return The version number in string format (major.minor.revision.build).

View File

@ -539,6 +539,8 @@ set(TECHNIC_SOURCES
modplatform/technic/SingleZipPackInstallTask.cpp modplatform/technic/SingleZipPackInstallTask.cpp
modplatform/technic/SolderPackInstallTask.h modplatform/technic/SolderPackInstallTask.h
modplatform/technic/SolderPackInstallTask.cpp modplatform/technic/SolderPackInstallTask.cpp
modplatform/technic/SolderPackManifest.h
modplatform/technic/SolderPackManifest.cpp
modplatform/technic/TechnicPackProcessor.h modplatform/technic/TechnicPackProcessor.h
modplatform/technic/TechnicPackProcessor.cpp modplatform/technic/TechnicPackProcessor.cpp
) )

View File

@ -1,4 +1,24 @@
/* Copyright 2013-2021 MultiMC Contributors // SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (c) 2021-2022 Jamie Mansfield <jmansfield@cadixdev.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -19,16 +39,23 @@
#include <Json.h> #include <Json.h>
#include <QtConcurrentRun> #include <QtConcurrentRun>
#include <MMCZip.h> #include <MMCZip.h>
#include "TechnicPackProcessor.h" #include "TechnicPackProcessor.h"
#include "SolderPackManifest.h"
#include "net/ChecksumValidator.h"
Technic::SolderPackInstallTask::SolderPackInstallTask( Technic::SolderPackInstallTask::SolderPackInstallTask(
shared_qobject_ptr<QNetworkAccessManager> network, shared_qobject_ptr<QNetworkAccessManager> network,
const QUrl &sourceUrl, const QUrl &solderUrl,
const QString &pack,
const QString &version,
const QString &minecraftVersion const QString &minecraftVersion
) { ) {
m_sourceUrl = sourceUrl; m_solderUrl = solderUrl;
m_minecraftVersion = minecraftVersion; m_pack = pack;
m_version = version;
m_network = network; m_network = network;
m_minecraftVersion = minecraftVersion;
} }
bool Technic::SolderPackInstallTask::abort() { bool Technic::SolderPackInstallTask::abort() {
@ -41,34 +68,12 @@ bool Technic::SolderPackInstallTask::abort() {
void Technic::SolderPackInstallTask::executeTask() void Technic::SolderPackInstallTask::executeTask()
{ {
setStatus(tr("Finding recommended version:\n%1").arg(m_sourceUrl.toString())); setStatus(tr("Resolving modpack files"));
m_filesNetJob = new NetJob(tr("Finding recommended version"), m_network);
m_filesNetJob->addNetAction(Net::Download::makeByteArray(m_sourceUrl, &m_response));
auto job = m_filesNetJob.get();
connect(job, &NetJob::succeeded, this, &Technic::SolderPackInstallTask::versionSucceeded);
connect(job, &NetJob::failed, this, &Technic::SolderPackInstallTask::downloadFailed);
m_filesNetJob->start();
}
void Technic::SolderPackInstallTask::versionSucceeded()
{
try
{
QJsonDocument doc = Json::requireDocument(m_response);
QJsonObject obj = Json::requireObject(doc);
QString version = Json::requireString(obj, "recommended", "__placeholder__");
m_sourceUrl = m_sourceUrl.toString() + '/' + version;
}
catch (const JSONValidationError &e)
{
emitFailed(e.cause());
m_filesNetJob.reset();
return;
}
setStatus(tr("Resolving modpack files:\n%1").arg(m_sourceUrl.toString()));
m_filesNetJob = new NetJob(tr("Resolving modpack files"), m_network); m_filesNetJob = new NetJob(tr("Resolving modpack files"), m_network);
m_filesNetJob->addNetAction(Net::Download::makeByteArray(m_sourceUrl, &m_response)); auto sourceUrl = QString("%1/modpack/%2/%3").arg(m_solderUrl.toString(), m_pack, m_version);
m_filesNetJob->addNetAction(Net::Download::makeByteArray(sourceUrl, &m_response));
auto job = m_filesNetJob.get(); auto job = m_filesNetJob.get();
connect(job, &NetJob::succeeded, this, &Technic::SolderPackInstallTask::fileListSucceeded); connect(job, &NetJob::succeeded, this, &Technic::SolderPackInstallTask::fileListSucceeded);
connect(job, &NetJob::failed, this, &Technic::SolderPackInstallTask::downloadFailed); connect(job, &NetJob::failed, this, &Technic::SolderPackInstallTask::downloadFailed);
@ -77,38 +82,47 @@ void Technic::SolderPackInstallTask::versionSucceeded()
void Technic::SolderPackInstallTask::fileListSucceeded() void Technic::SolderPackInstallTask::fileListSucceeded()
{ {
setStatus(tr("Downloading modpack:")); setStatus(tr("Downloading modpack"));
QStringList modUrls;
try QJsonParseError parse_error {};
{ QJsonDocument doc = QJsonDocument::fromJson(m_response, &parse_error);
QJsonDocument doc = Json::requireDocument(m_response); if (parse_error.error != QJsonParseError::NoError) {
QJsonObject obj = Json::requireObject(doc); qWarning() << "Error while parsing JSON response from Solder at " << parse_error.offset << " reason: " << parse_error.errorString();
QString minecraftVersion = Json::ensureString(obj, "minecraft", QString(), "__placeholder__"); qWarning() << m_response;
if (!minecraftVersion.isEmpty()) return;
m_minecraftVersion = minecraftVersion;
QJsonArray mods = Json::requireArray(obj, "mods", "'mods'");
for (auto mod: mods)
{
QJsonObject modObject = Json::requireObject(mod);
modUrls.append(Json::requireString(modObject, "url", "'url'"));
} }
auto obj = doc.object();
TechnicSolder::PackBuild build;
try {
TechnicSolder::loadPackBuild(build, obj);
} }
catch (const JSONValidationError &e) catch (const JSONValidationError& e) {
{ emitFailed(tr("Could not understand pack manifest:\n") + e.cause());
emitFailed(e.cause());
m_filesNetJob.reset(); m_filesNetJob.reset();
return; return;
} }
if (!build.minecraft.isEmpty())
m_minecraftVersion = build.minecraft;
m_filesNetJob = new NetJob(tr("Downloading modpack"), m_network); m_filesNetJob = new NetJob(tr("Downloading modpack"), m_network);
int i = 0; int i = 0;
for (auto &modUrl: modUrls) for (const auto &mod : build.mods) {
{
auto path = FS::PathCombine(m_outputDir.path(), QString("%1").arg(i)); auto path = FS::PathCombine(m_outputDir.path(), QString("%1").arg(i));
m_filesNetJob->addNetAction(Net::Download::makeFile(modUrl, path));
auto dl = Net::Download::makeFile(mod.url, path);
if (!mod.md5.isEmpty()) {
auto rawMd5 = QByteArray::fromHex(mod.md5.toLatin1());
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Md5, rawMd5));
}
m_filesNetJob->addNetAction(dl);
i++; i++;
} }
m_modCount = modUrls.size(); m_modCount = build.mods.size();
connect(m_filesNetJob.get(), &NetJob::succeeded, this, &Technic::SolderPackInstallTask::downloadSucceeded); connect(m_filesNetJob.get(), &NetJob::succeeded, this, &Technic::SolderPackInstallTask::downloadSucceeded);
connect(m_filesNetJob.get(), &NetJob::progress, this, &Technic::SolderPackInstallTask::downloadProgressChanged); connect(m_filesNetJob.get(), &NetJob::progress, this, &Technic::SolderPackInstallTask::downloadProgressChanged);
@ -206,6 +220,5 @@ void Technic::SolderPackInstallTask::extractFinished()
void Technic::SolderPackInstallTask::extractAborted() void Technic::SolderPackInstallTask::extractAborted()
{ {
emitFailed(tr("Instance import has been aborted.")); emitFailed(tr("Instance import has been aborted."));
return;
} }

View File

@ -1,4 +1,24 @@
/* Copyright 2013-2021 MultiMC Contributors // SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (c) 2021-2022 Jamie Mansfield <jmansfield@cadixdev.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -27,7 +47,7 @@ namespace Technic
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit SolderPackInstallTask(shared_qobject_ptr<QNetworkAccessManager> network, const QUrl &sourceUrl, const QString &minecraftVersion); explicit SolderPackInstallTask(shared_qobject_ptr<QNetworkAccessManager> network, const QUrl &solderUrl, const QString& pack, const QString& version, const QString &minecraftVersion);
bool canAbort() const override { return true; } bool canAbort() const override { return true; }
bool abort() override; bool abort() override;
@ -37,7 +57,6 @@ namespace Technic
virtual void executeTask() override; virtual void executeTask() override;
private slots: private slots:
void versionSucceeded();
void fileListSucceeded(); void fileListSucceeded();
void downloadSucceeded(); void downloadSucceeded();
void downloadFailed(QString reason); void downloadFailed(QString reason);
@ -51,7 +70,9 @@ namespace Technic
shared_qobject_ptr<QNetworkAccessManager> m_network; shared_qobject_ptr<QNetworkAccessManager> m_network;
NetJob::Ptr m_filesNetJob; NetJob::Ptr m_filesNetJob;
QUrl m_sourceUrl; QUrl m_solderUrl;
QString m_pack;
QString m_version;
QString m_minecraftVersion; QString m_minecraftVersion;
QByteArray m_response; QByteArray m_response;
QTemporaryDir m_outputDir; QTemporaryDir m_outputDir;

View File

@ -0,0 +1,58 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "SolderPackManifest.h"
#include "Json.h"
namespace TechnicSolder {
void loadPack(Pack& v, QJsonObject& obj)
{
v.recommended = Json::requireString(obj, "recommended");
v.latest = Json::requireString(obj, "latest");
auto builds = Json::requireArray(obj, "builds");
for (const auto buildRaw : builds) {
auto build = Json::requireString(buildRaw);
v.builds.append(build);
}
}
static void loadPackBuildMod(PackBuildMod& b, QJsonObject& obj)
{
b.name = Json::requireString(obj, "name");
b.version = Json::requireString(obj, "version");
b.md5 = Json::requireString(obj, "md5");
b.url = Json::requireString(obj, "url");
}
void loadPackBuild(PackBuild& v, QJsonObject& obj)
{
v.minecraft = Json::requireString(obj, "minecraft");
auto mods = Json::requireArray(obj, "mods");
for (const auto modRaw : mods) {
auto modObj = Json::requireObject(modRaw);
PackBuildMod mod;
loadPackBuildMod(mod, modObj);
v.mods.append(mod);
}
}
}

View File

@ -0,0 +1,49 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <QString>
#include <QVector>
#include <QJsonObject>
namespace TechnicSolder {
struct Pack {
QString recommended;
QString latest;
QVector<QString> builds;
};
void loadPack(Pack& v, QJsonObject& obj);
struct PackBuildMod {
QString name;
QString version;
QString md5;
QString url;
};
struct PackBuild {
QString minecraft;
QVector<PackBuildMod> mods;
};
void loadPackBuild(PackBuild& v, QJsonObject& obj);
}

View File

@ -1,4 +1,24 @@
/* Copyright 2020-2021 MultiMC Contributors // SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (c) 2021-2022 Jamie Mansfield <jmansfield@cadixdev.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2020-2021 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -17,6 +37,7 @@
#include <QList> #include <QList>
#include <QString> #include <QString>
#include <QVector>
namespace Technic { namespace Technic {
struct Modpack { struct Modpack {
@ -36,6 +57,11 @@ struct Modpack {
QString websiteUrl; QString websiteUrl;
QString author; QString author;
QString description; QString description;
QString currentVersion;
bool versionsLoaded = false;
QString recommended;
QVector<QString> versions;
}; };
} }

View File

@ -1,4 +1,24 @@
/* Copyright 2020-2021 MultiMC Contributors // SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (c) 2021 Jamie Mansfield <jmansfield@cadixdev.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2020-2021 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -15,6 +35,7 @@
#include "TechnicModel.h" #include "TechnicModel.h"
#include "Application.h" #include "Application.h"
#include "BuildConfig.h"
#include "Json.h" #include "Json.h"
#include <QIcon> #include <QIcon>
@ -94,13 +115,24 @@ void Technic::ListModel::performSearch()
NetJob *netJob = new NetJob("Technic::Search", APPLICATION->network()); NetJob *netJob = new NetJob("Technic::Search", APPLICATION->network());
QString searchUrl = ""; QString searchUrl = "";
if (currentSearchTerm.isEmpty()) { if (currentSearchTerm.isEmpty()) {
searchUrl = "https://api.technicpack.net/trending?build=multimc"; searchUrl = QString("%1trending?build=%2")
.arg(BuildConfig.TECHNIC_API_BASE_URL, BuildConfig.TECHNIC_API_BUILD);
searchMode = List;
} }
else else if (currentSearchTerm.startsWith("http://api.technicpack.net/modpack/")) {
{ searchUrl = QString("https://%1?build=%2")
.arg(currentSearchTerm.mid(7), BuildConfig.TECHNIC_API_BUILD);
searchMode = Single;
}
else if (currentSearchTerm.startsWith("https://api.technicpack.net/modpack/")) {
searchUrl = QString("%1?build=%2").arg(currentSearchTerm, BuildConfig.TECHNIC_API_BUILD);
searchMode = Single;
}
else {
searchUrl = QString( searchUrl = QString(
"https://api.technicpack.net/search?build=multimc&q=%1" "%1search?build=%2&q=%3"
).arg(currentSearchTerm); ).arg(BuildConfig.TECHNIC_API_BASE_URL, BuildConfig.TECHNIC_API_BUILD, currentSearchTerm);
searchMode = List;
} }
netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), &response)); netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), &response));
jobPtr = netJob; jobPtr = netJob;
@ -125,6 +157,9 @@ void Technic::ListModel::searchRequestFinished()
QList<Modpack> newList; QList<Modpack> newList;
try { try {
auto root = Json::requireObject(doc); auto root = Json::requireObject(doc);
switch (searchMode) {
case List: {
auto objs = Json::requireArray(root, "modpacks"); auto objs = Json::requireArray(root, "modpacks");
for (auto technicPack: objs) { for (auto technicPack: objs) {
Modpack pack; Modpack pack;
@ -146,6 +181,35 @@ void Technic::ListModel::searchRequestFinished()
pack.broken = false; pack.broken = false;
newList.append(pack); newList.append(pack);
} }
break;
}
case Single: {
if (root.contains("error")) {
// Invalid API url
break;
}
Modpack pack;
pack.name = Json::requireString(root, "displayName");
pack.slug = Json::requireString(root, "name");
if (root.contains("icon")) {
auto iconObj = Json::requireObject(root, "icon");
auto iconUrl = Json::requireString(iconObj, "url");
pack.logoUrl = iconUrl;
pack.logoName = iconUrl.section(QLatin1Char('/'), -1).section(QLatin1Char('.'), 0, 0);
}
else {
pack.logoUrl = "null";
pack.logoName = "null";
}
pack.broken = false;
newList.append(pack);
break;
}
}
} }
catch (const JSONValidationError &err) catch (const JSONValidationError &err)
{ {

View File

@ -1,4 +1,24 @@
/* Copyright 2020-2021 MultiMC Contributors // SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (c) 2021 Jamie Mansfield <jmansfield@cadixdev.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2020-2021 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -63,6 +83,10 @@ private:
ResetRequested, ResetRequested,
Finished Finished
} searchState = None; } searchState = None;
enum SearchMode {
List,
Single,
} searchMode = List;
NetJob::Ptr jobPtr; NetJob::Ptr jobPtr;
QByteArray response; QByteArray response;
}; };

View File

@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
/* /*
* PolyMC - Minecraft Launcher * PolyMC - Minecraft Launcher
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org> * Copyright (c) 2021-2022 Jamie Mansfield <jmansfield@cadixdev.org>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -40,12 +40,14 @@
#include "ui/dialogs/NewInstanceDialog.h" #include "ui/dialogs/NewInstanceDialog.h"
#include "BuildConfig.h"
#include "TechnicModel.h" #include "TechnicModel.h"
#include "modplatform/technic/SingleZipPackInstallTask.h" #include "modplatform/technic/SingleZipPackInstallTask.h"
#include "modplatform/technic/SolderPackInstallTask.h" #include "modplatform/technic/SolderPackInstallTask.h"
#include "Json.h" #include "Json.h"
#include "Application.h" #include "Application.h"
#include "modplatform/technic/SolderPackManifest.h"
TechnicPage::TechnicPage(NewInstanceDialog* dialog, QWidget *parent) TechnicPage::TechnicPage(NewInstanceDialog* dialog, QWidget *parent)
: QWidget(parent), ui(new Ui::TechnicPage), dialog(dialog) : QWidget(parent), ui(new Ui::TechnicPage), dialog(dialog)
@ -55,7 +57,9 @@ TechnicPage::TechnicPage(NewInstanceDialog* dialog, QWidget *parent)
ui->searchEdit->installEventFilter(this); ui->searchEdit->installEventFilter(this);
model = new Technic::ListModel(this); model = new Technic::ListModel(this);
ui->packView->setModel(model); ui->packView->setModel(model);
connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &TechnicPage::onSelectionChanged); connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &TechnicPage::onSelectionChanged);
connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &TechnicPage::onVersionSelectionChanged);
} }
bool TechnicPage::eventFilter(QObject* watched, QEvent* event) bool TechnicPage::eventFilter(QObject* watched, QEvent* event)
@ -98,13 +102,14 @@ void TechnicPage::triggerSearch() {
void TechnicPage::onSelectionChanged(QModelIndex first, QModelIndex second) void TechnicPage::onSelectionChanged(QModelIndex first, QModelIndex second)
{ {
ui->versionSelectionBox->clear();
if(!first.isValid()) if(!first.isValid())
{ {
if(isOpened) if(isOpened)
{ {
dialog->setSuggestedPack(); dialog->setSuggestedPack();
} }
//ui->frame->clear();
return; return;
} }
@ -137,17 +142,19 @@ void TechnicPage::suggestCurrent()
} }
NetJob *netJob = new NetJob(QString("Technic::PackMeta(%1)").arg(current.name), APPLICATION->network()); NetJob *netJob = new NetJob(QString("Technic::PackMeta(%1)").arg(current.name), APPLICATION->network());
std::shared_ptr<QByteArray> response = std::make_shared<QByteArray>();
QString slug = current.slug; QString slug = current.slug;
netJob->addNetAction(Net::Download::makeByteArray(QString("https://api.technicpack.net/modpack/%1?build=multimc").arg(slug), response.get())); netJob->addNetAction(Net::Download::makeByteArray(QString("%1modpack/%2?build=%3").arg(BuildConfig.TECHNIC_API_BASE_URL, slug, BuildConfig.TECHNIC_API_BUILD), &response));
QObject::connect(netJob, &NetJob::succeeded, this, [this, response, slug] QObject::connect(netJob, &NetJob::succeeded, this, [this, slug]
{ {
jobPtr.reset();
if (current.slug != slug) if (current.slug != slug)
{ {
return; return;
} }
QJsonParseError parse_error;
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); QJsonParseError parse_error {};
QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error);
QJsonObject obj = doc.object(); QJsonObject obj = doc.object();
if(parse_error.error != QJsonParseError::NoError) if(parse_error.error != QJsonParseError::NoError)
{ {
@ -189,10 +196,14 @@ void TechnicPage::suggestCurrent()
current.websiteUrl = Json::ensureString(obj, "platformUrl", QString(), "__placeholder__"); current.websiteUrl = Json::ensureString(obj, "platformUrl", QString(), "__placeholder__");
current.author = Json::ensureString(obj, "user", QString(), "__placeholder__"); current.author = Json::ensureString(obj, "user", QString(), "__placeholder__");
current.description = Json::ensureString(obj, "description", QString(), "__placeholder__"); current.description = Json::ensureString(obj, "description", QString(), "__placeholder__");
current.currentVersion = Json::ensureString(obj, "version", QString(), "__placeholder__");
current.metadataLoaded = true; current.metadataLoaded = true;
metadataLoaded(); metadataLoaded();
}); });
netJob->start();
jobPtr = netJob;
jobPtr->start();
} }
// expects current.metadataLoaded to be true // expects current.metadataLoaded to be true
@ -202,25 +213,119 @@ void TechnicPage::metadataLoaded()
QString name = current.name; QString name = current.name;
if (current.websiteUrl.isEmpty()) if (current.websiteUrl.isEmpty())
// This allows injecting HTML here. text = name.toHtmlEscaped();
text = name;
else else
// URL not properly escaped for inclusion in HTML. The name allows for injecting HTML. text = "<a href=\"" + current.websiteUrl.toHtmlEscaped() + "\">" + name.toHtmlEscaped() + "</a>";
text = "<a href=\"" + current.websiteUrl + "\">" + name + "</a>";
if (!current.author.isEmpty()) { if (!current.author.isEmpty()) {
// This allows injecting HTML here text += "<br>" + tr(" by ") + current.author.toHtmlEscaped();
text += tr(" by ") + current.author; }
text += "<br><br>";
ui->packDescription->setHtml(text + current.description);
// Strip trailing forward-slashes from Solder URL's
if (current.isSolder) {
while (current.url.endsWith('/')) current.url.chop(1);
}
// Display versions from Solder
if (!current.isSolder) {
// If the pack isn't a Solder pack, it only has the single version
ui->versionSelectionBox->addItem(current.currentVersion);
}
else if (current.versionsLoaded) {
// reverse foreach, so that the newest versions are first
for (auto i = current.versions.size(); i--;) {
ui->versionSelectionBox->addItem(current.versions.at(i));
}
ui->versionSelectionBox->setCurrentText(current.recommended);
}
else {
// For now, until the versions are pulled from the Solder instance, display the current
// version so we can display something quicker
ui->versionSelectionBox->addItem(current.currentVersion);
auto* netJob = new NetJob(QString("Technic::SolderMeta(%1)").arg(current.name), APPLICATION->network());
auto url = QString("%1/modpack/%2").arg(current.url, current.slug);
netJob->addNetAction(Net::Download::makeByteArray(QUrl(url), &response));
QObject::connect(netJob, &NetJob::succeeded, this, &TechnicPage::onSolderLoaded);
jobPtr = netJob;
jobPtr->start();
}
selectVersion();
}
void TechnicPage::selectVersion() {
if (!isOpened) {
return;
}
if (current.broken) {
dialog->setSuggestedPack();
return;
} }
ui->frame->setModText(text);
ui->frame->setModDescription(current.description);
if (!current.isSolder) if (!current.isSolder)
{ {
dialog->setSuggestedPack(current.name, new Technic::SingleZipPackInstallTask(current.url, current.minecraftVersion)); dialog->setSuggestedPack(current.name + " " + selectedVersion, new Technic::SingleZipPackInstallTask(current.url, current.minecraftVersion));
} }
else else
{ {
while (current.url.endsWith('/')) current.url.chop(1); dialog->setSuggestedPack(current.name + " " + selectedVersion, new Technic::SolderPackInstallTask(APPLICATION->network(), current.url, current.slug, selectedVersion, current.minecraftVersion));
dialog->setSuggestedPack(current.name, new Technic::SolderPackInstallTask(APPLICATION->network(), current.url + "/modpack/" + current.slug, current.minecraftVersion));
} }
} }
void TechnicPage::onSolderLoaded() {
jobPtr.reset();
auto fallback = [this]() {
current.versionsLoaded = true;
current.versions.clear();
current.versions.append(current.currentVersion);
};
current.versions.clear();
QJsonParseError parse_error {};
auto doc = QJsonDocument::fromJson(response, &parse_error);
if (parse_error.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response from Solder at " << parse_error.offset << " reason: " << parse_error.errorString();
qWarning() << response;
fallback();
return;
}
auto obj = doc.object();
TechnicSolder::Pack pack;
try {
TechnicSolder::loadPack(pack, obj);
}
catch (const JSONValidationError& err) {
qCritical() << "Couldn't parse Solder pack metadata:" << err.cause();
fallback();
return;
}
current.versionsLoaded = true;
current.recommended = pack.recommended;
current.versions.append(pack.builds);
// Finally, let's reload :)
ui->versionSelectionBox->clear();
metadataLoaded();
}
void TechnicPage::onVersionSelectionChanged(QString data) {
if (data.isNull() || data.isEmpty()) {
selectedVersion = "";
return;
}
selectedVersion = data;
selectVersion();
}

View File

@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
/* /*
* PolyMC - Minecraft Launcher * PolyMC - Minecraft Launcher
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org> * Copyright (c) 2021-2022 Jamie Mansfield <jmansfield@cadixdev.org>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -39,6 +39,7 @@
#include "ui/pages/BasePage.h" #include "ui/pages/BasePage.h"
#include <Application.h> #include <Application.h>
#include "net/NetJob.h"
#include "tasks/Task.h" #include "tasks/Task.h"
#include "TechnicData.h" #include "TechnicData.h"
@ -86,14 +87,22 @@ public:
private: private:
void suggestCurrent(); void suggestCurrent();
void metadataLoaded(); void metadataLoaded();
void selectVersion();
private slots: private slots:
void triggerSearch(); void triggerSearch();
void onSelectionChanged(QModelIndex first, QModelIndex second); void onSelectionChanged(QModelIndex first, QModelIndex second);
void onSolderLoaded();
void onVersionSelectionChanged(QString data);
private: private:
Ui::TechnicPage *ui = nullptr; Ui::TechnicPage *ui = nullptr;
NewInstanceDialog* dialog = nullptr; NewInstanceDialog* dialog = nullptr;
Technic::ListModel* model = nullptr; Technic::ListModel* model = nullptr;
Technic::Modpack current; Technic::Modpack current;
QString selectedVersion;
NetJob::Ptr jobPtr;
QByteArray response;
}; };

View File

@ -10,44 +10,44 @@
<height>405</height> <height>405</height>
</rect> </rect>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QGridLayout" name="gridLayout">
<item> <item row="3" column="0" colspan="2">
<widget class="QWidget" name="widget" native="true"> <layout class="QGridLayout" name="gridLayout_3">
<layout class="QHBoxLayout" name="horizontalLayout"> <item row="0" column="2">
<property name="leftMargin"> <widget class="QComboBox" name="versionSelectionBox"/>
<number>0</number> </item>
<item row="0" column="1">
<widget class="QLabel" name="label">
<property name="text">
<string>Version selected:</string>
</property> </property>
<property name="topMargin"> <property name="alignment">
<number>0</number> <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLineEdit" name="searchEdit">
<property name="placeholderText">
<string>Search and filter...</string>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item row="0" column="0">
<widget class="QPushButton" name="searchButton"> <spacer name="horizontalSpacer">
<property name="text"> <property name="orientation">
<string>Search</string> <enum>Qt::Horizontal</enum>
</property> </property>
</widget> <property name="sizeType">
<enum>QSizePolicy::Preferred</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>1</width>
<height>1</height>
</size>
</property>
</spacer>
</item> </item>
</layout> </layout>
</widget>
</item> </item>
<item> <item row="2" column="0" colspan="2">
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<widget class="QListView" name="packView"> <widget class="QListView" name="packView">
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="alternatingRowColors"> <property name="alternatingRowColors">
<bool>true</bool> <bool>true</bool>
</property> </property>
@ -59,37 +59,27 @@
</property> </property>
</widget> </widget>
</item> </item>
<item> <item row="0" column="1">
<widget class="MCModInfoFrame" name="frame"> <widget class="QTextBrowser" name="packDescription"/>
<property name="sizePolicy"> </item>
<sizepolicy hsizetype="Preferred" vsizetype="Minimum"> </layout>
<horstretch>0</horstretch> </item>
<verstretch>0</verstretch> <item row="1" column="0">
</sizepolicy> <widget class="QLineEdit" name="searchEdit">
<property name="placeholderText">
<string>Search and filter...</string>
</property> </property>
<property name="frameShape"> </widget>
<enum>QFrame::StyledPanel</enum> </item>
</property> <item row="1" column="1">
<property name="frameShadow"> <widget class="QPushButton" name="searchButton">
<enum>QFrame::Raised</enum> <property name="text">
<string>Search</string>
</property> </property>
</widget> </widget>
</item> </item>
</layout> </layout>
</widget> </widget>
<customwidgets>
<customwidget>
<class>MCModInfoFrame</class>
<extends>QFrame</extends>
<header>ui/widgets/MCModInfoFrame.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>searchEdit</tabstop>
<tabstop>searchButton</tabstop>
<tabstop>packView</tabstop>
</tabstops>
<resources/> <resources/>
<connections/> <connections/>
</ui> </ui>