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

Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
This commit is contained in:
Trial97
2023-06-28 13:21:00 +03:00
230 changed files with 5534 additions and 3813 deletions

View File

@ -9,19 +9,17 @@
#include "net/NetJob.h"
#include "net/Upload.h"
Task::Ptr ModrinthAPI::currentVersion(QString hash, QString hash_format, QByteArray* response)
Task::Ptr ModrinthAPI::currentVersion(QString hash, QString hash_format, std::shared_ptr<QByteArray> response)
{
auto netJob = makeShared<NetJob>(QString("Modrinth::GetCurrentVersion"), APPLICATION->network());
netJob->addNetAction(Net::Download::makeByteArray(
QString(BuildConfig.MODRINTH_PROD_URL + "/version_file/%1?algorithm=%2").arg(hash, hash_format), response));
QObject::connect(netJob.get(), &NetJob::finished, [response] { delete response; });
return netJob;
}
Task::Ptr ModrinthAPI::currentVersions(const QStringList& hashes, QString hash_format, QByteArray* response)
Task::Ptr ModrinthAPI::currentVersions(const QStringList& hashes, QString hash_format, std::shared_ptr<QByteArray> response)
{
auto netJob = makeShared<NetJob>(QString("Modrinth::GetCurrentVersions"), APPLICATION->network());
@ -35,8 +33,6 @@ Task::Ptr ModrinthAPI::currentVersions(const QStringList& hashes, QString hash_f
netJob->addNetAction(Net::Upload::makeByteArray(QString(BuildConfig.MODRINTH_PROD_URL + "/version_files"), response, body_raw));
QObject::connect(netJob.get(), &NetJob::finished, [response] { delete response; });
return netJob;
}
@ -44,7 +40,7 @@ Task::Ptr ModrinthAPI::latestVersion(QString hash,
QString hash_format,
std::optional<std::list<Version>> mcVersions,
std::optional<ModLoaderTypes> loaders,
QByteArray* response)
std::shared_ptr<QByteArray> response)
{
auto netJob = makeShared<NetJob>(QString("Modrinth::GetLatestVersion"), APPLICATION->network());
@ -67,8 +63,6 @@ Task::Ptr ModrinthAPI::latestVersion(QString hash,
netJob->addNetAction(Net::Upload::makeByteArray(
QString(BuildConfig.MODRINTH_PROD_URL + "/version_file/%1/update?algorithm=%2").arg(hash, hash_format), response, body_raw));
QObject::connect(netJob.get(), &NetJob::finished, [response] { delete response; });
return netJob;
}
@ -76,7 +70,7 @@ Task::Ptr ModrinthAPI::latestVersions(const QStringList& hashes,
QString hash_format,
std::optional<std::list<Version>> mcVersions,
std::optional<ModLoaderTypes> loaders,
QByteArray* response)
std::shared_ptr<QByteArray> response)
{
auto netJob = makeShared<NetJob>(QString("Modrinth::GetLatestVersions"), APPLICATION->network());
@ -101,22 +95,16 @@ Task::Ptr ModrinthAPI::latestVersions(const QStringList& hashes,
netJob->addNetAction(Net::Upload::makeByteArray(QString(BuildConfig.MODRINTH_PROD_URL + "/version_files/update"), response, body_raw));
QObject::connect(netJob.get(), &NetJob::finished, [response] { delete response; });
return netJob;
}
Task::Ptr ModrinthAPI::getProjects(QStringList addonIds, QByteArray* response) const
Task::Ptr ModrinthAPI::getProjects(QStringList addonIds, std::shared_ptr<QByteArray> response) const
{
auto netJob = makeShared<NetJob>(QString("Modrinth::GetProjects"), APPLICATION->network());
auto searchUrl = getMultipleModInfoURL(addonIds);
netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), response));
QObject::connect(netJob.get(), &NetJob::finished, [response, netJob] {
delete response;
});
return netJob;
}

View File

@ -12,27 +12,23 @@
class ModrinthAPI : public NetworkResourceAPI {
public:
auto currentVersion(QString hash,
QString hash_format,
QByteArray* response) -> Task::Ptr;
auto currentVersion(QString hash, QString hash_format, std::shared_ptr<QByteArray> response) -> Task::Ptr;
auto currentVersions(const QStringList& hashes,
QString hash_format,
QByteArray* response) -> Task::Ptr;
auto currentVersions(const QStringList& hashes, QString hash_format, std::shared_ptr<QByteArray> response) -> Task::Ptr;
auto latestVersion(QString hash,
QString hash_format,
std::optional<std::list<Version>> mcVersions,
std::optional<ModLoaderTypes> loaders,
QByteArray* response) -> Task::Ptr;
std::shared_ptr<QByteArray> response) -> Task::Ptr;
auto latestVersions(const QStringList& hashes,
QString hash_format,
std::optional<std::list<Version>> mcVersions,
std::optional<ModLoaderTypes> loaders,
QByteArray* response) -> Task::Ptr;
std::optional<std::list<Version>> mcVersions,
std::optional<ModLoaderTypes> loaders,
std::shared_ptr<QByteArray> response) -> Task::Ptr;
Task::Ptr getProjects(QStringList addonIds, QByteArray* response) const override;
Task::Ptr getProjects(QStringList addonIds, std::shared_ptr<QByteArray> response) const override;
public:
[[nodiscard]] auto getSortingMethods() const -> QList<ResourceAPI::SortingMethod> override;
@ -42,7 +38,7 @@ class ModrinthAPI : public NetworkResourceAPI {
static auto getModLoaderStrings(const ModLoaderTypes types) -> const QStringList
{
QStringList l;
for (auto loader : {Forge, Fabric, Quilt}) {
for (auto loader : { Forge, Fabric, Quilt }) {
if (types & loader) {
l << getModLoaderString(loader);
}
@ -55,8 +51,7 @@ class ModrinthAPI : public NetworkResourceAPI {
static auto getModLoaderFilters(ModLoaderTypes types) -> const QString
{
QStringList l;
for (auto loader : getModLoaderStrings(types))
{
for (auto loader : getModLoaderStrings(types)) {
l << QString("\"categories:%1\"").arg(loader);
}
return l.join(',');
@ -139,16 +134,22 @@ class ModrinthAPI : public NetworkResourceAPI {
auto getGameVersionsArray(std::list<Version> mcVersions) const -> QString
{
QString s;
for(auto& ver : mcVersions){
for (auto& ver : mcVersions) {
s += QString("\"versions:%1\",").arg(ver.toString());
}
s.remove(s.length() - 1, 1); //remove last comma
s.remove(s.length() - 1, 1); // remove last comma
return s.isEmpty() ? QString() : s;
}
inline auto validateModLoaders(ModLoaderTypes loaders) const -> bool
{
return loaders & (Forge | Fabric | Quilt);
}
inline auto validateModLoaders(ModLoaderTypes loaders) const -> bool { return loaders & (Forge | Fabric | Quilt); }
[[nodiscard]] std::optional<QString> getDependencyURL(DependencySearchArgs const& args) const override
{
return args.dependency.version.length() != 0 ? QString("%1/version/%2").arg(BuildConfig.MODRINTH_PROD_URL, args.dependency.version)
: QString("%1/project/%2/version?game_versions=[\"%3\"]&loaders=[\"%4\"]")
.arg(BuildConfig.MODRINTH_PROD_URL)
.arg(args.dependency.addonId.toString())
.arg(args.mcVersion.toString())
.arg(getModLoaderStrings(args.loader).join("\",\""));
};
};

View File

@ -53,12 +53,11 @@ void ModrinthCheckUpdate::executeTask()
// (though it will rarely happen, if at all)
if (mod->metadata()->hash_format != best_hash_type) {
auto hash_task = Hashing::createModrinthHasher(mod->fileinfo().absoluteFilePath());
connect(hash_task.get(), &Task::succeeded, [&] {
QString hash (hash_task->getResult());
connect(hash_task.get(), &Hashing::Hasher::resultsReady, [&hashes, &mappings, mod](QString hash) {
hashes.append(hash);
mappings.insert(hash, mod);
});
connect(hash_task.get(), &Task::failed, [this, hash_task] { failed("Failed to generate hash"); });
connect(hash_task.get(), &Task::failed, [this] { failed("Failed to generate hash"); });
hashing_task.addTask(hash_task);
} else {
hashes.append(hash);
@ -67,11 +66,11 @@ void ModrinthCheckUpdate::executeTask()
}
QEventLoop loop;
connect(&hashing_task, &Task::finished, [&loop]{ loop.quit(); });
connect(&hashing_task, &Task::finished, [&loop] { loop.quit(); });
hashing_task.start();
loop.exec();
auto* response = new QByteArray();
auto response = std::make_shared<QByteArray>();
auto job = api.latestVersions(hashes, best_hash_type, m_game_versions, m_loaders, response);
QEventLoop lock;
@ -112,7 +111,8 @@ void ModrinthCheckUpdate::executeTask()
// so we may want to filter it
QString loader_filter;
if (m_loaders.has_value()) {
static auto flags = { ResourceAPI::ModLoaderType::Forge, ResourceAPI::ModLoaderType::Fabric, ResourceAPI::ModLoaderType::Quilt };
static auto flags = { ResourceAPI::ModLoaderType::Forge, ResourceAPI::ModLoaderType::Fabric,
ResourceAPI::ModLoaderType::Quilt };
for (auto flag : flags) {
if (m_loaders.value().testFlag(flag)) {
loader_filter = api.getModLoaderString(flag);
@ -122,7 +122,8 @@ void ModrinthCheckUpdate::executeTask()
}
// Currently, we rely on a couple heuristics to determine whether an update is actually available or not:
// - The file needs to be preferred: It is either the primary file, or the one found via (explicit) usage of the loader_filter
// - The file needs to be preferred: It is either the primary file, or the one found via (explicit) usage of the
// loader_filter
// - The version reported by the JAR is different from the version reported by the indexed version (it's usually the case)
// Such is the pain of having arbitrary files for a given version .-.
@ -149,19 +150,19 @@ void ModrinthCheckUpdate::executeTask()
continue;
// Fake pack with the necessary info to pass to the download task :)
ModPlatform::IndexedPack pack;
pack.name = mod->name();
pack.slug = mod->metadata()->slug;
pack.addonId = mod->metadata()->project_id;
pack.websiteUrl = mod->homeurl();
auto pack = std::make_shared<ModPlatform::IndexedPack>();
pack->name = mod->name();
pack->slug = mod->metadata()->slug;
pack->addonId = mod->metadata()->project_id;
pack->websiteUrl = mod->homeurl();
for (auto& author : mod->authors())
pack.authors.append({ author });
pack.description = mod->description();
pack.provider = ModPlatform::ResourceProvider::MODRINTH;
pack->authors.append({ author });
pack->description = mod->description();
pack->provider = ModPlatform::ResourceProvider::MODRINTH;
auto download_task = makeShared<ResourceDownloadTask>(pack, project_ver, m_mods_folder);
m_updatable.emplace_back(pack.name, hash, mod->version(), project_ver.version_number, project_ver.verison_type,
m_updatable.emplace_back(pack->name, hash, mod->version(), project_ver.version_number, project_ver.verison_type,
project_ver.changelog, ModPlatform::ResourceProvider::MODRINTH, download_task);
}
}

View File

@ -214,7 +214,7 @@ bool ModrinthCreationTask::createInstance()
if (m_instIcon != "default") {
instance.setIconKey(m_instIcon);
} else {
} else if (!m_managed_id.isEmpty()) {
instance.setIconKey("modrinth");
}

View File

@ -0,0 +1,319 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
*
* 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 "ModrinthPackExportTask.h"
#include <QCryptographicHash>
#include <QFileInfo>
#include <QMessageBox>
#include <QtConcurrentRun>
#include "Json.h"
#include "MMCZip.h"
#include "minecraft/PackProfile.h"
#include "minecraft/mod/ModFolderModel.h"
const QStringList ModrinthPackExportTask::PREFIXES({ "mods/", "coremods/", "resourcepacks/", "texturepacks/", "shaderpacks/" });
const QStringList ModrinthPackExportTask::FILE_EXTENSIONS({ "jar", "litemod", "zip" });
ModrinthPackExportTask::ModrinthPackExportTask(const QString& name,
const QString& version,
const QString& summary,
InstancePtr instance,
const QString& output,
MMCZip::FilterFunction filter)
: name(name)
, version(version)
, summary(summary)
, instance(instance)
, mcInstance(dynamic_cast<MinecraftInstance*>(instance.get()))
, gameRoot(instance->gameRoot())
, output(output)
, filter(filter)
{}
void ModrinthPackExportTask::executeTask()
{
setStatus(tr("Searching for files..."));
setProgress(0, 0);
collectFiles();
}
bool ModrinthPackExportTask::abort()
{
if (task != nullptr) {
task->abort();
task = nullptr;
emitAborted();
return true;
}
if (buildZipFuture.isRunning()) {
buildZipFuture.cancel();
// NOTE: Here we don't do `emitAborted()` because it will be done when `buildZipFuture` actually cancels, which may not occur immediately.
return true;
}
return false;
}
void ModrinthPackExportTask::collectFiles()
{
setAbortable(false);
QCoreApplication::processEvents();
files.clear();
if (!MMCZip::collectFileListRecursively(instance->gameRoot(), nullptr, &files, filter)) {
emitFailed(tr("Could not search for files"));
return;
}
pendingHashes.clear();
resolvedFiles.clear();
if (mcInstance) {
mcInstance->loaderModList()->update();
connect(mcInstance->loaderModList().get(), &ModFolderModel::updateFinished, this, &ModrinthPackExportTask::collectHashes);
} else
collectHashes();
}
void ModrinthPackExportTask::collectHashes()
{
for (const QFileInfo& file : files) {
QCoreApplication::processEvents();
const QString relative = gameRoot.relativeFilePath(file.absoluteFilePath());
// require sensible file types
if (!std::any_of(PREFIXES.begin(), PREFIXES.end(), [&relative](const QString& prefix) { return relative.startsWith(prefix); }))
continue;
if (!std::any_of(FILE_EXTENSIONS.begin(), FILE_EXTENSIONS.end(), [&relative](const QString& extension) {
return relative.endsWith('.' + extension) || relative.endsWith('.' + extension + ".disabled");
}))
continue;
QCryptographicHash sha512(QCryptographicHash::Algorithm::Sha512);
QFile openFile(file.absoluteFilePath());
if (!openFile.open(QFile::ReadOnly)) {
qWarning() << "Could not open" << file << "for hashing";
continue;
}
const QByteArray data = openFile.readAll();
if (openFile.error() != QFileDevice::NoError) {
qWarning() << "Could not read" << file;
continue;
}
sha512.addData(data);
auto allMods = mcInstance->loaderModList()->allMods();
if (auto modIter = std::find_if(allMods.begin(), allMods.end(), [&file](Mod* mod) { return mod->fileinfo() == file; });
modIter != allMods.end()) {
const Mod* mod = *modIter;
if (mod->metadata() != nullptr) {
QUrl& url = mod->metadata()->url;
// ensure the url is permitted on modrinth.com
if (!url.isEmpty() && BuildConfig.MODRINTH_MRPACK_HOSTS.contains(url.host())) {
qDebug() << "Resolving" << relative << "from index";
QCryptographicHash sha1(QCryptographicHash::Algorithm::Sha1);
sha1.addData(data);
ResolvedFile resolvedFile{ sha1.result().toHex(), sha512.result().toHex(), url.toEncoded(), openFile.size() };
resolvedFiles[relative] = resolvedFile;
// nice! we've managed to resolve based on local metadata!
// no need to enqueue it
continue;
}
}
}
qDebug() << "Enqueueing" << relative << "for Modrinth query";
pendingHashes[relative] = sha512.result().toHex();
}
setAbortable(true);
makeApiRequest();
}
void ModrinthPackExportTask::makeApiRequest()
{
if (pendingHashes.isEmpty())
buildZip();
else {
auto response = std::make_shared<QByteArray>();
task = api.currentVersions(pendingHashes.values(), "sha512", response);
connect(task.get(), &NetJob::succeeded, [this, response]() { parseApiResponse(response); });
connect(task.get(), &NetJob::failed, this, &ModrinthPackExportTask::emitFailed);
task->start();
}
}
void ModrinthPackExportTask::parseApiResponse(const std::shared_ptr<QByteArray> response)
{
task = nullptr;
try {
const QJsonDocument doc = Json::requireDocument(*response);
QMapIterator<QString, QString> iterator(pendingHashes);
while (iterator.hasNext()) {
iterator.next();
const QJsonObject obj = doc[iterator.value()].toObject();
if (obj.isEmpty())
continue;
const QJsonArray files = obj["files"].toArray();
if (auto fileIter = std::find_if(files.begin(), files.end(),
[&iterator](const QJsonValue& file) { return file["hashes"]["sha512"] == iterator.value(); });
fileIter != files.end()) {
// map the file to the url
resolvedFiles[iterator.key()] =
ResolvedFile{ fileIter->toObject()["hashes"].toObject()["sha1"].toString(), iterator.value(),
fileIter->toObject()["url"].toString(), fileIter->toObject()["size"].toInt() };
}
}
} catch (const Json::JsonException& e) {
emitFailed(tr("Failed to parse versions response: %1").arg(e.what()));
return;
}
pendingHashes.clear();
buildZip();
}
void ModrinthPackExportTask::buildZip()
{
setStatus(tr("Adding files..."));
buildZipFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this]() {
QuaZip zip(output);
if (!zip.open(QuaZip::mdCreate)) {
QFile::remove(output);
return BuildZipResult(tr("Could not create file"));
}
if (buildZipFuture.isCanceled())
return BuildZipResult();
QuaZipFile indexFile(&zip);
if (!indexFile.open(QIODevice::WriteOnly, QuaZipNewInfo("modrinth.index.json"))) {
QFile::remove(output);
return BuildZipResult(tr("Could not create index"));
}
indexFile.write(generateIndex());
size_t progress = 0;
for (const QFileInfo& file : files) {
if (buildZipFuture.isCanceled()) {
QFile::remove(output);
return BuildZipResult();
}
setProgress(progress, files.length());
const QString relative = gameRoot.relativeFilePath(file.absoluteFilePath());
if (!resolvedFiles.contains(relative) && !JlCompress::compressFile(&zip, file.absoluteFilePath(), "overrides/" + relative)) {
QFile::remove(output);
return BuildZipResult(tr("Could not read and compress %1").arg(relative));
}
progress++;
}
zip.close();
if (zip.getZipError() != 0) {
QFile::remove(output);
return BuildZipResult(tr("A zip error occurred"));
}
return BuildZipResult();
});
connect(&buildZipWatcher, &QFutureWatcher<BuildZipResult>::finished, this, &ModrinthPackExportTask::finish);
buildZipWatcher.setFuture(buildZipFuture);
}
void ModrinthPackExportTask::finish()
{
if (buildZipFuture.isCanceled())
emitAborted();
else {
const BuildZipResult result = buildZipFuture.result();
if (result.has_value())
emitFailed(result.value());
else
emitSucceeded();
}
}
QByteArray ModrinthPackExportTask::generateIndex()
{
QJsonObject obj;
obj["formatVersion"] = 1;
obj["game"] = "minecraft";
obj["name"] = name;
obj["versionId"] = version;
if (!summary.isEmpty())
obj["summary"] = summary;
if (mcInstance) {
auto profile = mcInstance->getPackProfile();
// collect all supported components
const ComponentPtr minecraft = profile->getComponent("net.minecraft");
const ComponentPtr quilt = profile->getComponent("org.quiltmc.quilt-loader");
const ComponentPtr fabric = profile->getComponent("net.fabricmc.fabric-loader");
const ComponentPtr forge = profile->getComponent("net.minecraftforge");
// convert all available components to mrpack dependencies
QJsonObject dependencies;
if (minecraft != nullptr)
dependencies["minecraft"] = minecraft->m_version;
if (quilt != nullptr)
dependencies["quilt-loader"] = quilt->m_version;
if (fabric != nullptr)
dependencies["fabric-loader"] = fabric->m_version;
if (forge != nullptr)
dependencies["forge"] = forge->m_version;
obj["dependencies"] = dependencies;
}
QJsonArray files;
QMapIterator<QString, ResolvedFile> iterator(resolvedFiles);
while (iterator.hasNext()) {
iterator.next();
const ResolvedFile& value = iterator.value();
QJsonObject file;
file["path"] = iterator.key();
file["downloads"] = QJsonArray({ iterator.value().url });
QJsonObject hashes;
hashes["sha1"] = value.sha1;
hashes["sha512"] = value.sha512;
file["hashes"] = hashes;
file["fileSize"] = value.size;
files << file;
}
obj["files"] = files;
return QJsonDocument(obj).toJson(QJsonDocument::Compact);
}

View File

@ -0,0 +1,77 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
*
* 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 <QFuture>
#include <QFutureWatcher>
#include "BaseInstance.h"
#include "MMCZip.h"
#include "minecraft/MinecraftInstance.h"
#include "modplatform/modrinth/ModrinthAPI.h"
#include "tasks/Task.h"
class ModrinthPackExportTask : public Task {
public:
ModrinthPackExportTask(const QString& name,
const QString& version,
const QString& summary,
InstancePtr instance,
const QString& output,
MMCZip::FilterFunction filter);
protected:
void executeTask() override;
bool abort() override;
private:
struct ResolvedFile {
QString sha1, sha512, url;
qint64 size;
};
static const QStringList PREFIXES;
static const QStringList FILE_EXTENSIONS;
// inputs
const QString name, version, summary;
const InstancePtr instance;
MinecraftInstance* mcInstance;
const QDir gameRoot;
const QString output;
const MMCZip::FilterFunction filter;
typedef std::optional<QString> BuildZipResult;
ModrinthAPI api;
QFileInfoList files;
QMap<QString, QString> pendingHashes;
QMap<QString, ResolvedFile> resolvedFiles;
Task::Ptr task;
QFuture<BuildZipResult> buildZipFuture;
QFutureWatcher<BuildZipResult> buildZipWatcher;
void collectFiles();
void collectHashes();
void makeApiRequest();
void parseApiResponse(const std::shared_ptr<QByteArray> response);
void buildZip();
void finish();
QByteArray generateIndex();
};

View File

@ -22,7 +22,7 @@
#include "Json.h"
#include "minecraft/MinecraftInstance.h"
#include "minecraft/PackProfile.h"
#include "net/NetJob.h"
#include "modplatform/ModIndex.h"
static ModrinthAPI api;
static ModPlatform::ProviderCapabilities ProviderCaps;
@ -143,6 +143,28 @@ auto Modrinth::loadIndexedPackVersion(QJsonObject& obj, QString preferred_hash_t
file.changelog = Json::requireString(obj, "changelog");
auto dependencies = Json::ensureArray(obj, "dependencies");
for (auto d : dependencies) {
auto dep = Json::ensureObject(d);
ModPlatform::Dependency dependency;
dependency.addonId = Json::ensureString(dep, "project_id");
dependency.version = Json::ensureString(dep, "version_id");
auto depType = Json::requireString(dep, "dependency_type");
if (depType == "required")
dependency.type = ModPlatform::DependencyType::REQUIRED;
else if (depType == "optional")
dependency.type = ModPlatform::DependencyType::OPTIONAL;
else if (depType == "incompatible")
dependency.type = ModPlatform::DependencyType::INCOMPATIBLE;
else if (depType == "embedded")
dependency.type = ModPlatform::DependencyType::EMBEDDED;
else
dependency.type = ModPlatform::DependencyType::UNKNOWN;
file.dependencies.append(dependency);
}
auto files = Json::requireArray(obj, "files");
int i = 0;
@ -198,3 +220,22 @@ auto Modrinth::loadIndexedPackVersion(QJsonObject& obj, QString preferred_hash_t
return {};
}
auto Modrinth::loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr) -> ModPlatform::IndexedVersion
{
QVector<ModPlatform::IndexedVersion> versions;
for (auto versionIter : arr) {
auto obj = versionIter.toObject();
auto file = loadIndexedPackVersion(obj);
if (file.fileId.isValid()) // Heuristic to check if the returned value is valid
versions.append(file);
}
auto orderSortPredicate = [](const ModPlatform::IndexedVersion& a, const ModPlatform::IndexedVersion& b) -> bool {
// dates are in RFC 3339 format
return a.date > b.date;
};
std::sort(versions.begin(), versions.end(), orderSortPredicate);
return versions.length() != 0 ? versions.front() : ModPlatform::IndexedVersion();
}

View File

@ -19,8 +19,8 @@
#include "modplatform/ModIndex.h"
#include "BaseInstance.h"
#include <QNetworkAccessManager>
#include "BaseInstance.h"
namespace Modrinth {
@ -31,5 +31,6 @@ void loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
const shared_qobject_ptr<QNetworkAccessManager>& network,
const BaseInstance* inst);
auto loadIndexedPackVersion(QJsonObject& obj, QString hash_type = "sha512", QString filename_prefer = "") -> ModPlatform::IndexedVersion;
auto loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr) -> ModPlatform::IndexedVersion;
} // namespace Modrinth