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:
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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("\",\""));
|
||||
};
|
||||
};
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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");
|
||||
}
|
||||
|
||||
|
319
launcher/modplatform/modrinth/ModrinthPackExportTask.cpp
Normal file
319
launcher/modplatform/modrinth/ModrinthPackExportTask.cpp
Normal 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);
|
||||
}
|
77
launcher/modplatform/modrinth/ModrinthPackExportTask.h
Normal file
77
launcher/modplatform/modrinth/ModrinthPackExportTask.h
Normal 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();
|
||||
};
|
@ -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();
|
||||
}
|
@ -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
|
||||
|
Reference in New Issue
Block a user