From 22f5128e398f34f01f78751008aca594fa7a7eee Mon Sep 17 00:00:00 2001 From: Ryan Cao <70191398+ryanccn@users.noreply.github.com> Date: Sun, 8 May 2022 15:22:50 +0800 Subject: [PATCH] adopt changes from #497 remapped --- CMakeLists.txt | 4 + buildconfig/BuildConfig.cpp.in | 1 + buildconfig/BuildConfig.h | 12 +- .../modplatform/flame/FileResolvingTask.cpp | 34 ++--- launcher/modplatform/flame/FlameAPI.h | 32 ++++- launcher/modplatform/flame/FlameModIndex.cpp | 52 ++----- launcher/modplatform/flame/FlamePackIndex.cpp | 50 +++---- launcher/modplatform/flame/PackManifest.cpp | 87 ++++-------- launcher/net/Download.cpp | 114 ++++++---------- launcher/ui/pages/modplatform/ModModel.cpp | 49 ++++--- .../pages/modplatform/flame/FlameModModel.cpp | 4 +- .../ui/pages/modplatform/flame/FlameModel.cpp | 129 +++++++----------- .../ui/pages/modplatform/flame/FlamePage.cpp | 71 ++++------ 13 files changed, 261 insertions(+), 378 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a7824a992..d1c20bb07 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -89,6 +89,10 @@ set(Launcher_IMGUR_CLIENT_ID "5b97b0713fba4a3" CACHE STRING "Client ID you can g # MSA Client ID set(Launcher_MSA_CLIENT_ID "549033b2-1532-4d4e-ae77-1bbaa46f9d74" CACHE STRING "Client ID you can get from Microsoft Identity Platform when you register an application") +# CurseForge API Key +# CHANGE THIS IF YOU FORK THIS PROJECT! +set(Launcher_CURSEFORGE_API_KEY "$2a$10$iR1RdPDG95FWdILZbHuoMOlV4vL4eckBx7QPZR6SVZmliEb9ZQplu" CACHE STRING "CurseForge API Key") + # Bug tracker URL set(Launcher_BUG_TRACKER_URL "https://github.com/PolyMC/PolyMC/issues" CACHE STRING "URL for the bug tracker.") diff --git a/buildconfig/BuildConfig.cpp.in b/buildconfig/BuildConfig.cpp.in index 7360d964d..70f8f7f05 100644 --- a/buildconfig/BuildConfig.cpp.in +++ b/buildconfig/BuildConfig.cpp.in @@ -90,6 +90,7 @@ Config::Config() HELP_URL = "@Launcher_HELP_URL@"; IMGUR_CLIENT_ID = "@Launcher_IMGUR_CLIENT_ID@"; MSA_CLIENT_ID = "@Launcher_MSA_CLIENT_ID@"; + CURSEFORGE_API_KEY = "@Launcher_CURSEFORGE_API_KEY@"; META_URL = "@Launcher_META_URL@"; BUG_TRACKER_URL = "@Launcher_BUG_TRACKER_URL@"; diff --git a/buildconfig/BuildConfig.h b/buildconfig/BuildConfig.h index 6304387cb..a653e3cff 100644 --- a/buildconfig/BuildConfig.h +++ b/buildconfig/BuildConfig.h @@ -39,9 +39,8 @@ /** * \brief The Config class holds all the build-time information passed from the build system. */ -class Config -{ -public: +class Config { + public: Config(); QString LAUNCHER_NAME; QString LAUNCHER_DISPLAYNAME; @@ -74,7 +73,6 @@ public: /// URL for the updater's channel QString UPDATER_BASE; - /// User-Agent to use. QString USER_AGENT; @@ -116,6 +114,11 @@ public: */ QString MSA_CLIENT_ID; + /** + * Client API key for CurseForge + */ + QString CURSEFORGE_API_KEY; + /** * Metadata repository URL prefix */ @@ -154,4 +157,3 @@ public: }; extern const Config BuildConfig; - diff --git a/launcher/modplatform/flame/FileResolvingTask.cpp b/launcher/modplatform/flame/FileResolvingTask.cpp index 3889a9359..95924a681 100644 --- a/launcher/modplatform/flame/FileResolvingTask.cpp +++ b/launcher/modplatform/flame/FileResolvingTask.cpp @@ -1,14 +1,9 @@ #include "FileResolvingTask.h" #include "Json.h" -namespace { - const char * metabase = "https://cursemeta.dries007.net"; -} - Flame::FileResolvingTask::FileResolvingTask(shared_qobject_ptr network, Flame::Manifest& toProcess) : m_network(network), m_toProcess(toProcess) -{ -} +{} void Flame::FileResolvingTask::executeTask() { @@ -17,14 +12,13 @@ void Flame::FileResolvingTask::executeTask() m_dljob = new NetJob("Mod id resolver", m_network); results.resize(m_toProcess.files.size()); int index = 0; - for(auto & file: m_toProcess.files) - { + for (auto& file : m_toProcess.files) { auto projectIdStr = QString::number(file.projectId); auto fileIdStr = QString::number(file.fileId); - QString metaurl = QString("%1/%2/%3.json").arg(metabase, projectIdStr, fileIdStr); + QString metaurl = QString("https://api.curseforge.com/v1/mods/%1/files/%2").arg(projectIdStr, fileIdStr); auto dl = Net::Download::makeByteArray(QUrl(metaurl), &results[index]); m_dljob->addNetAction(dl); - index ++; + index++; } connect(m_dljob.get(), &NetJob::finished, this, &Flame::FileResolvingTask::netJobFinished); m_dljob->start(); @@ -34,16 +28,11 @@ void Flame::FileResolvingTask::netJobFinished() { bool failed = false; int index = 0; - for(auto & bytes: results) - { - auto & out = m_toProcess.files[index]; - try - { + for (auto& bytes : results) { + auto& out = m_toProcess.files[index]; + try { failed &= (!out.parseFromBytes(bytes)); - } - catch (const JSONValidationError &e) - { - + } catch (const JSONValidationError& e) { qCritical() << "Resolving of" << out.projectId << out.fileId << "failed because of a parsing error:"; qCritical() << e.cause(); qCritical() << "JSON:"; @@ -52,12 +41,9 @@ void Flame::FileResolvingTask::netJobFinished() } index++; } - if(!failed) - { + if (!failed) { emitSucceeded(); - } - else - { + } else { emitFailed(tr("Some mod ID resolving tasks failed.")); } } diff --git a/launcher/modplatform/flame/FlameAPI.h b/launcher/modplatform/flame/FlameAPI.h index ce02df65f..61628e603 100644 --- a/launcher/modplatform/flame/FlameAPI.h +++ b/launcher/modplatform/flame/FlameAPI.h @@ -3,33 +3,53 @@ #include "modplatform/helpers/NetworkModAPI.h" class FlameAPI : public NetworkModAPI { + private: + inline auto getSortFieldInt(QString sortString) const -> int + { + return sortString == "Featured" ? 1 + : sortString == "Popularity" ? 2 + : sortString == "LastUpdated" ? 3 + : sortString == "Name" ? 4 + : sortString == "Author" ? 5 + : sortString == "TotalDownloads" ? 6 + : sortString == "Category" ? 7 + : sortString == "GameVersion" ? 8 + : 1; + } + private: inline auto getModSearchURL(SearchArgs& args) const -> QString override { auto gameVersionStr = args.versions.size() != 0 ? QString("gameVersion=%1").arg(args.versions.front().toString()) : QString(); return QString( - "https://addons-ecs.forgesvc.net/api/v2/addon/search?" + "https://api.curseforge.com/v1/mods/search?" "gameId=432&" - "categoryId=0&" - "sectionId=6&" + "classId=6&" "index=%1&" "pageSize=25&" "searchFilter=%2&" - "sort=%3&" + "sortField=%3&" + "sortOrder=desc&" "modLoaderType=%4&" "%5") .arg(args.offset) .arg(args.search) - .arg(args.sorting) + .arg(getSortFieldInt(args.sorting)) .arg(getMappedModLoader(args.mod_loader)) .arg(gameVersionStr); }; inline auto getVersionsURL(VersionSearchArgs& args) const -> QString override { - return QString("https://addons-ecs.forgesvc.net/api/v2/addon/%1/files").arg(args.addonId); + QString gameVersionQuery = args.mcVersions.size() == 1 ? QString("gameVersion=%1&").arg(args.mcVersions.front().toString()) : ""; + QString modLoaderQuery = QString("modLoaderType=%1&").arg(getMappedModLoader(args.loader)); + + return QString("https://api.curseforge.com/v1/mods/%1/files?pageSize=10000&%2%3") + .arg(args.addonId) + .arg(gameVersionQuery) + .arg(modLoaderQuery); }; public: diff --git a/launcher/modplatform/flame/FlameModIndex.cpp b/launcher/modplatform/flame/FlameModIndex.cpp index c7b86b5c1..6334e88ce 100644 --- a/launcher/modplatform/flame/FlameModIndex.cpp +++ b/launcher/modplatform/flame/FlameModIndex.cpp @@ -10,23 +10,12 @@ void FlameMod::loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj) { pack.addonId = Json::requireInteger(obj, "id"); pack.name = Json::requireString(obj, "name"); - pack.websiteUrl = Json::ensureString(obj, "websiteUrl", ""); + pack.websiteUrl = Json::ensureString(Json::ensureObject(obj, "links"), "websiteUrl", ""); pack.description = Json::ensureString(obj, "summary", ""); - bool thumbnailFound = false; - auto attachments = Json::requireArray(obj, "attachments"); - for (auto attachmentRaw : attachments) { - auto attachmentObj = Json::requireObject(attachmentRaw); - bool isDefault = attachmentObj.value("isDefault").toBool(false); - if (isDefault) { - thumbnailFound = true; - pack.logoName = Json::requireString(attachmentObj, "title"); - pack.logoUrl = Json::requireString(attachmentObj, "thumbnailUrl"); - break; - } - } - - if (!thumbnailFound) { throw JSONValidationError(QString("Pack without an icon, skipping: %1").arg(pack.name)); } + QJsonObject logo = Json::requireObject(obj, "logo"); + pack.logoName = Json::requireString(logo, "title"); + pack.logoUrl = Json::requireString(logo, "thumbnailUrl"); auto authors = Json::requireArray(obj, "authors"); for (auto authorIter : authors) { @@ -45,18 +34,22 @@ void FlameMod::loadIndexedPackVersions(ModPlatform::IndexedPack& pack, { QVector unsortedVersions; auto profile = (dynamic_cast(inst))->getPackProfile(); - bool hasFabric = FlameAPI::getMappedModLoader(profile->getModLoader()) == ModAPI::Fabric; QString mcVersion = profile->getComponentVersion("net.minecraft"); for (auto versionIter : arr) { auto obj = versionIter.toObject(); - auto versionArray = Json::requireArray(obj, "gameVersion"); - if (versionArray.isEmpty()) { continue; } + auto versionArray = Json::requireArray(obj, "gameVersions"); + if (versionArray.isEmpty()) { + continue; + } ModPlatform::IndexedVersion file; for (auto mcVer : versionArray) { - file.mcVersion.append(mcVer.toString()); + auto str = mcVer.toString(); + + if (str.indexOf('.') > -1) + file.mcVersion.append(mcVer.toString()); } file.addonId = pack.addonId; @@ -66,28 +59,9 @@ void FlameMod::loadIndexedPackVersions(ModPlatform::IndexedPack& pack, file.downloadUrl = Json::requireString(obj, "downloadUrl"); file.fileName = Json::requireString(obj, "fileName"); - auto modules = Json::requireArray(obj, "modules"); - bool is_valid_fabric_version = false; - for (auto m : modules) { - auto fname = Json::requireString(m.toObject(), "foldername"); - // FIXME: This does not work properly when a mod supports more than one mod loader, since - // FIXME: This also doesn't deal with Quilt mods at the moment - // they bundle the meta files for all of them in the same arquive, even when that version - // doesn't support the given mod loader. - if (hasFabric) { - if (fname == "fabric.mod.json") { - is_valid_fabric_version = true; - break; - } - } else - break; - // NOTE: Since we're not validating forge versions, we can just skip this loop. - } - - if (hasFabric && !is_valid_fabric_version) continue; - unsortedVersions.append(file); } + auto orderSortPredicate = [](const ModPlatform::IndexedVersion& a, const ModPlatform::IndexedVersion& b) -> bool { // dates are in RFC 3339 format return a.date > b.date; diff --git a/launcher/modplatform/flame/FlamePackIndex.cpp b/launcher/modplatform/flame/FlamePackIndex.cpp index 3d8ea22ae..549cace65 100644 --- a/launcher/modplatform/flame/FlamePackIndex.cpp +++ b/launcher/modplatform/flame/FlamePackIndex.cpp @@ -2,76 +2,63 @@ #include "Json.h" -void Flame::loadIndexedPack(Flame::IndexedPack & pack, QJsonObject & obj) +void Flame::loadIndexedPack(Flame::IndexedPack& pack, QJsonObject& obj) { pack.addonId = Json::requireInteger(obj, "id"); pack.name = Json::requireString(obj, "name"); pack.websiteUrl = Json::ensureString(obj, "websiteUrl", ""); pack.description = Json::ensureString(obj, "summary", ""); - bool thumbnailFound = false; - auto attachments = Json::requireArray(obj, "attachments"); - for(auto attachmentRaw: attachments) { - auto attachmentObj = Json::requireObject(attachmentRaw); - bool isDefault = attachmentObj.value("isDefault").toBool(false); - if(isDefault) { - thumbnailFound = true; - pack.logoName = Json::requireString(attachmentObj, "title"); - pack.logoUrl = Json::requireString(attachmentObj, "thumbnailUrl"); - break; - } - } - - if(!thumbnailFound) { - throw JSONValidationError(QString("Pack without an icon, skipping: %1").arg(pack.name)); - } + auto logo = Json::requireObject(obj, "logo"); + pack.logoName = Json::requireString(logo, "title"); + pack.logoUrl = Json::requireString(logo, "thumbnailUrl"); auto authors = Json::requireArray(obj, "authors"); - for(auto authorIter: authors) { + for (auto authorIter : authors) { auto author = Json::requireObject(authorIter); Flame::ModpackAuthor packAuthor; packAuthor.name = Json::requireString(author, "name"); packAuthor.url = Json::requireString(author, "url"); pack.authors.append(packAuthor); } - int defaultFileId = Json::requireInteger(obj, "defaultFileId"); + int defaultFileId = Json::requireInteger(obj, "mainFileId"); bool found = false; // check if there are some files before adding the pack auto files = Json::requireArray(obj, "latestFiles"); - for(auto fileIter: files) { + for (auto fileIter : files) { auto file = Json::requireObject(fileIter); int id = Json::requireInteger(file, "id"); // NOTE: for now, ignore everything that's not the default... - if(id != defaultFileId) { + if (id != defaultFileId) { continue; } - auto versionArray = Json::requireArray(file, "gameVersion"); - if(versionArray.size() < 1) { + auto versionArray = Json::requireArray(file, "gameVersions"); + if (versionArray.size() < 1) { continue; } found = true; break; } - if(!found) { + if (!found) { throw JSONValidationError(QString("Pack with no good file, skipping: %1").arg(pack.name)); } } -void Flame::loadIndexedPackVersions(Flame::IndexedPack & pack, QJsonArray & arr) +void Flame::loadIndexedPackVersions(Flame::IndexedPack& pack, QJsonArray& arr) { QVector unsortedVersions; - for(auto versionIter: arr) { + for (auto versionIter : arr) { auto version = Json::requireObject(versionIter); - Flame::IndexedVersion file; + Flame::IndexedVersion file; file.addonId = pack.addonId; file.fileId = Json::requireInteger(version, "id"); - auto versionArray = Json::requireArray(version, "gameVersion"); - if(versionArray.size() < 1) { + auto versionArray = Json::requireArray(version, "gameVersions"); + if (versionArray.size() < 1) { continue; } @@ -82,10 +69,7 @@ void Flame::loadIndexedPackVersions(Flame::IndexedPack & pack, QJsonArray & arr) unsortedVersions.append(file); } - auto orderSortPredicate = [](const IndexedVersion & a, const IndexedVersion & b) -> bool - { - return a.fileId > b.fileId; - }; + auto orderSortPredicate = [](const IndexedVersion& a, const IndexedVersion& b) -> bool { return a.fileId > b.fileId; }; std::sort(unsortedVersions.begin(), unsortedVersions.end(), orderSortPredicate); pack.versions = unsortedVersions; pack.versionsLoaded = true; diff --git a/launcher/modplatform/flame/PackManifest.cpp b/launcher/modplatform/flame/PackManifest.cpp index b928fd168..e4f90c1a1 100644 --- a/launcher/modplatform/flame/PackManifest.cpp +++ b/launcher/modplatform/flame/PackManifest.cpp @@ -1,28 +1,27 @@ #include "PackManifest.h" #include "Json.h" -static void loadFileV1(Flame::File & f, QJsonObject & file) +static void loadFileV1(Flame::File& f, QJsonObject& file) { f.projectId = Json::requireInteger(file, "projectID"); f.fileId = Json::requireInteger(file, "fileID"); f.required = Json::ensureBoolean(file, QString("required"), true); } -static void loadModloaderV1(Flame::Modloader & m, QJsonObject & modLoader) +static void loadModloaderV1(Flame::Modloader& m, QJsonObject& modLoader) { m.id = Json::requireString(modLoader, "id"); m.primary = Json::ensureBoolean(modLoader, QString("primary"), false); } -static void loadMinecraftV1(Flame::Minecraft & m, QJsonObject & minecraft) +static void loadMinecraftV1(Flame::Minecraft& m, QJsonObject& minecraft) { m.version = Json::requireString(minecraft, "version"); // extra libraries... apparently only used for a custom Minecraft launcher in the 1.2.5 FTB retro pack // intended use is likely hardcoded in the 'Flame' client, the manifest says nothing m.libraries = Json::ensureString(minecraft, QString("libraries"), QString()); auto arr = Json::ensureArray(minecraft, "modLoaders", QJsonArray()); - for (QJsonValueRef item : arr) - { + for (QJsonValueRef item : arr) { auto obj = Json::requireObject(item); Flame::Modloader loader; loadModloaderV1(loader, obj); @@ -30,16 +29,15 @@ static void loadMinecraftV1(Flame::Minecraft & m, QJsonObject & minecraft) } } -static void loadManifestV1(Flame::Manifest & m, QJsonObject & manifest) +static void loadManifestV1(Flame::Manifest& m, QJsonObject& manifest) { auto mc = Json::requireObject(manifest, "minecraft"); loadMinecraftV1(m.minecraft, mc); m.name = Json::ensureString(manifest, QString("name"), "Unnamed"); m.version = Json::ensureString(manifest, QString("version"), QString()); - m.author = Json::ensureString(manifest, QString("author"), "Anonymous Coward"); + m.author = Json::ensureString(manifest, QString("author"), "Anonymous"); auto arr = Json::ensureArray(manifest, "files", QJsonArray()); - for (QJsonValueRef item : arr) - { + for (QJsonValueRef item : arr) { auto obj = Json::requireObject(item); Flame::File file; loadFileV1(file, obj); @@ -48,18 +46,16 @@ static void loadManifestV1(Flame::Manifest & m, QJsonObject & manifest) m.overrides = Json::ensureString(manifest, "overrides", "overrides"); } -void Flame::loadManifest(Flame::Manifest & m, const QString &filepath) +void Flame::loadManifest(Flame::Manifest& m, const QString& filepath) { auto doc = Json::requireDocument(filepath); auto obj = Json::requireObject(doc); m.manifestType = Json::requireString(obj, "manifestType"); - if(m.manifestType != "minecraftModpack") - { + if (m.manifestType != "minecraftModpack") { throw JSONValidationError("Not a modpack manifest!"); } m.manifestVersion = Json::requireInteger(obj, "manifestVersion"); - if(m.manifestVersion != 1) - { + if (m.manifestVersion != 1) { throw JSONValidationError(QString("Unknown manifest version (%1)").arg(m.manifestVersion)); } loadManifestV1(m, obj); @@ -68,59 +64,30 @@ void Flame::loadManifest(Flame::Manifest & m, const QString &filepath) bool Flame::File::parseFromBytes(const QByteArray& bytes) { auto doc = Json::requireDocument(bytes); - auto obj = Json::requireObject(doc); - // result code signifies true failure. - if(obj.contains("code")) - { - qCritical() << "Resolving of" << projectId << fileId << "failed because of a negative result:"; - qCritical() << bytes; - return false; + if (!doc.isObject()) { + throw JSONValidationError(QString("data is not an object? that's not supposed to happen")); } - fileName = Json::requireString(obj, "FileNameOnDisk"); - QString rawUrl = Json::requireString(obj, "DownloadURL"); + auto obj = Json::ensureObject(doc.object(), "data"); + + fileName = Json::requireString(obj, "fileName"); + + QString rawUrl = Json::requireString(obj, "downloadUrl"); url = QUrl(rawUrl, QUrl::TolerantMode); - if(!url.isValid()) - { + if (!url.isValid()) { throw JSONValidationError(QString("Invalid URL: %1").arg(rawUrl)); } // This is a piece of a Flame project JSON pulled out into the file metadata (here) for convenience // It is also optional - QJsonObject projObj = Json::ensureObject(obj, "_Project", {}); - if(!projObj.isEmpty()) - { - QString strType = Json::ensureString(projObj, "PackageType", "mod").toLower(); - if(strType == "singlefile") - { - type = File::Type::SingleFile; - } - else if(strType == "ctoc") - { - type = File::Type::Ctoc; - } - else if(strType == "cmod2") - { - type = File::Type::Cmod2; - } - else if(strType == "mod") - { - type = File::Type::Mod; - } - else if(strType == "folder") - { - type = File::Type::Folder; - } - else if(strType == "modpack") - { - type = File::Type::Modpack; - } - else - { - qCritical() << "Resolving of" << projectId << fileId << "failed because of unknown file type:" << strType; - type = File::Type::Unknown; - return false; - } - targetFolder = Json::ensureString(projObj, "Path", "mods"); + type = File::Type::SingleFile; + + if (fileName.endsWith(".zip")) { + // this is probably a resource pack + targetFolder = "resourcepacks"; + } else { + // this is probably a mod, dunno what else could modpacks download + targetFolder = "mods"; } + resolved = true; return true; } diff --git a/launcher/net/Download.cpp b/launcher/net/Download.cpp index b314573fb..65cc8f67a 100644 --- a/launcher/net/Download.cpp +++ b/launcher/net/Download.cpp @@ -15,27 +15,27 @@ #include "Download.h" -#include #include #include +#include -#include "FileSystem.h" -#include "ChecksumValidator.h" -#include "MetaCacheSink.h" #include "ByteArraySink.h" +#include "ChecksumValidator.h" +#include "FileSystem.h" +#include "MetaCacheSink.h" #include "BuildConfig.h" namespace Net { -Download::Download():NetAction() +Download::Download() : NetAction() { m_status = Job_NotStarted; } Download::Ptr Download::makeCached(QUrl url, MetaEntryPtr entry, Options options) { - Download * dl = new Download(); + Download* dl = new Download(); dl->m_url = url; dl->m_options = options; auto md5Node = new ChecksumValidator(QCryptographicHash::Md5); @@ -45,9 +45,9 @@ Download::Ptr Download::makeCached(QUrl url, MetaEntryPtr entry, Options options return dl; } -Download::Ptr Download::makeByteArray(QUrl url, QByteArray *output, Options options) +Download::Ptr Download::makeByteArray(QUrl url, QByteArray* output, Options options) { - Download * dl = new Download(); + Download* dl = new Download(); dl->m_url = url; dl->m_options = options; dl->m_sink.reset(new ByteArraySink(output)); @@ -56,30 +56,28 @@ Download::Ptr Download::makeByteArray(QUrl url, QByteArray *output, Options opti Download::Ptr Download::makeFile(QUrl url, QString path, Options options) { - Download * dl = new Download(); + Download* dl = new Download(); dl->m_url = url; dl->m_options = options; dl->m_sink.reset(new FileSink(path)); return dl; } -void Download::addValidator(Validator * v) +void Download::addValidator(Validator* v) { m_sink->addValidator(v); } void Download::startImpl() { - if(m_status == Job_Aborted) - { + if (m_status == Job_Aborted) { qWarning() << "Attempt to start an aborted Download:" << m_url.toString(); emit aborted(m_index_within_job); return; } QNetworkRequest request(m_url); m_status = m_sink->init(request); - switch(m_status) - { + switch (m_status) { case Job_Finished: emit succeeded(m_index_within_job); qDebug() << "Download cache hit " << m_url.toString(); @@ -87,7 +85,7 @@ void Download::startImpl() case Job_InProgress: qDebug() << "Downloading " << m_url.toString(); break; - case Job_Failed_Proceed: // this is meaningless in this context. We do need a sink. + case Job_Failed_Proceed: // this is meaningless in this context. We do need a sink. case Job_NotStarted: case Job_Failed: emit failed(m_index_within_job); @@ -97,8 +95,11 @@ void Download::startImpl() } request.setHeader(QNetworkRequest::UserAgentHeader, BuildConfig.USER_AGENT); + if (request.url().host().contains("api.curseforge.com")) { + request.setRawHeader("x-api-key", BuildConfig.CURSEFORGE_API_KEY.toUtf8()); + }; - QNetworkReply *rep = m_network->get(request); + QNetworkReply* rep = m_network->get(request); m_reply.reset(rep); connect(rep, SIGNAL(downloadProgress(qint64, qint64)), SLOT(downloadProgress(qint64, qint64))); @@ -117,17 +118,12 @@ void Download::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) void Download::downloadError(QNetworkReply::NetworkError error) { - if(error == QNetworkReply::OperationCanceledError) - { + if (error == QNetworkReply::OperationCanceledError) { qCritical() << "Aborted " << m_url.toString(); m_status = Job_Aborted; - } - else - { - if(m_options & Option::AcceptLocalFiles) - { - if(m_sink->hasLocalData()) - { + } else { + if (m_options & Option::AcceptLocalFiles) { + if (m_sink->hasLocalData()) { m_status = Job_Failed_Proceed; return; } @@ -138,11 +134,10 @@ void Download::downloadError(QNetworkReply::NetworkError error) } } -void Download::sslErrors(const QList & errors) +void Download::sslErrors(const QList& errors) { int i = 1; - for (auto error : errors) - { + for (auto error : errors) { qCritical() << "Download" << m_url.toString() << "SSL Error #" << i << " : " << error.errorString(); auto cert = error.certificate(); qCritical() << "Certificate in question:\n" << cert.toText(); @@ -153,33 +148,27 @@ void Download::sslErrors(const QList & errors) bool Download::handleRedirect() { QUrl redirect = m_reply->header(QNetworkRequest::LocationHeader).toUrl(); - if(!redirect.isValid()) - { - if(!m_reply->hasRawHeader("Location")) - { + if (!redirect.isValid()) { + if (!m_reply->hasRawHeader("Location")) { // no redirect -> it's fine to continue return false; } // there is a Location header, but it's not correct. we need to apply some workarounds... QByteArray redirectBA = m_reply->rawHeader("Location"); - if(redirectBA.size() == 0) - { + if (redirectBA.size() == 0) { // empty, yet present redirect header? WTF? return false; } QString redirectStr = QString::fromUtf8(redirectBA); - if(redirectStr.startsWith("//")) - { + if (redirectStr.startsWith("//")) { /* * IF the URL begins with //, we need to insert the URL scheme. * See: https://bugreports.qt.io/browse/QTBUG-41061 * See: http://tools.ietf.org/html/rfc3986#section-4.2 */ redirectStr = m_reply->url().scheme() + ":" + redirectStr; - } - else if(redirectStr.startsWith("/")) - { + } else if (redirectStr.startsWith("/")) { /* * IF the URL begins with /, we need to process it as a relative URL */ @@ -193,16 +182,13 @@ bool Download::handleRedirect() * FIXME: report Qt bug for this */ redirect = QUrl(redirectStr, QUrl::TolerantMode); - if(!redirect.isValid()) - { + if (!redirect.isValid()) { qWarning() << "Failed to parse redirect URL:" << redirectStr; downloadError(QNetworkReply::ProtocolFailure); return false; } qDebug() << "Fixed location header:" << redirect; - } - else - { + } else { qDebug() << "Location header:" << redirect; } @@ -212,35 +198,28 @@ bool Download::handleRedirect() return true; } - void Download::downloadFinished() { // handle HTTP redirection first - if(handleRedirect()) - { + if (handleRedirect()) { qDebug() << "Download redirected:" << m_url.toString(); return; } // if the download failed before this point ... - if (m_status == Job_Failed_Proceed) - { + if (m_status == Job_Failed_Proceed) { qDebug() << "Download failed but we are allowed to proceed:" << m_url.toString(); m_sink->abort(); m_reply.reset(); emit succeeded(m_index_within_job); return; - } - else if (m_status == Job_Failed) - { + } else if (m_status == Job_Failed) { qDebug() << "Download failed in previous step:" << m_url.toString(); m_sink->abort(); m_reply.reset(); emit failed(m_index_within_job); return; - } - else if(m_status == Job_Aborted) - { + } else if (m_status == Job_Aborted) { qDebug() << "Download aborted in previous step:" << m_url.toString(); m_sink->abort(); m_reply.reset(); @@ -250,16 +229,14 @@ void Download::downloadFinished() // make sure we got all the remaining data, if any auto data = m_reply->readAll(); - if(data.size()) - { + if (data.size()) { qDebug() << "Writing extra" << data.size() << "bytes to" << m_target_path; m_status = m_sink->write(data); } // otherwise, finalize the whole graph m_status = m_sink->finalize(*m_reply.get()); - if (m_status != Job_Finished) - { + if (m_status != Job_Finished) { qDebug() << "Download failed to finalize:" << m_url.toString(); m_sink->abort(); m_reply.reset(); @@ -273,32 +250,25 @@ void Download::downloadFinished() void Download::downloadReadyRead() { - if(m_status == Job_InProgress) - { + if (m_status == Job_InProgress) { auto data = m_reply->readAll(); m_status = m_sink->write(data); - if(m_status == Job_Failed) - { + if (m_status == Job_Failed) { qCritical() << "Failed to process response chunk for " << m_target_path; } // qDebug() << "Download" << m_url.toString() << "gained" << data.size() << "bytes"; - } - else - { + } else { qCritical() << "Cannot write to " << m_target_path << ", illegal status" << m_status; } } -} +} // namespace Net bool Net::Download::abort() { - if(m_reply) - { + if (m_reply) { m_reply->abort(); - } - else - { + } else { m_status = Job_Aborted; } return true; diff --git a/launcher/ui/pages/modplatform/ModModel.cpp b/launcher/ui/pages/modplatform/ModModel.cpp index e82e1cdb2..540ee2fdc 100644 --- a/launcher/ui/pages/modplatform/ModModel.cpp +++ b/launcher/ui/pages/modplatform/ModModel.cpp @@ -21,7 +21,8 @@ auto ListModel::debugName() const -> QString void ListModel::fetchMore(const QModelIndex& parent) { - if (parent.isValid()) return; + if (parent.isValid()) + return; if (nextSearchOffset == 0) { qWarning() << "fetchMore with 0 offset is wrong..."; return; @@ -32,7 +33,9 @@ void ListModel::fetchMore(const QModelIndex& parent) auto ListModel::data(const QModelIndex& index, int role) const -> QVariant { int pos = index.row(); - if (pos >= modpacks.size() || pos < 0 || !index.isValid()) { return QString("INVALID INDEX %1").arg(pos); } + if (pos >= modpacks.size() || pos < 0 || !index.isValid()) { + return QString("INVALID INDEX %1").arg(pos); + } ModPlatform::IndexedPack pack = modpacks.at(pos); if (role == Qt::DisplayRole) { @@ -46,7 +49,9 @@ auto ListModel::data(const QModelIndex& index, int role) const -> QVariant } return pack.description; } else if (role == Qt::DecorationRole) { - if (m_logoMap.contains(pack.logoName)) { return (m_logoMap.value(pack.logoName)); } + if (m_logoMap.contains(pack.logoName)) { + return (m_logoMap.value(pack.logoName)); + } QIcon icon = APPLICATION->getThemedIcon("screenshot-placeholder"); ((ListModel*)this)->requestLogo(pack.logoName, pack.logoUrl); return icon; @@ -63,16 +68,15 @@ void ListModel::requestModVersions(ModPlatform::IndexedPack const& current) { auto profile = (dynamic_cast((dynamic_cast(parent()))->m_instance))->getPackProfile(); - m_parent->apiProvider()->getVersions(this, - { current.addonId.toString(), getMineVersions(), profile->getModLoader() }); + m_parent->apiProvider()->getVersions(this, { current.addonId.toString(), getMineVersions(), profile->getModLoader() }); } void ListModel::performPaginatedSearch() { auto profile = (dynamic_cast((dynamic_cast(parent()))->m_instance))->getPackProfile(); - m_parent->apiProvider()->searchMods(this, - { nextSearchOffset, currentSearchTerm, getSorts()[currentSort], profile->getModLoader(), getMineVersions() }); + m_parent->apiProvider()->searchMods( + this, { nextSearchOffset, currentSearchTerm, getSorts()[currentSort], profile->getModLoader(), getMineVersions() }); } void ListModel::refresh() @@ -93,11 +97,9 @@ void ListModel::refresh() void ListModel::searchWithTerm(const QString& term, const int sort, const bool filter_changed) { - if (currentSearchTerm == term - && currentSearchTerm.isNull() == term.isNull() - && currentSort == sort - && !filter_changed) - { return; } + if (currentSearchTerm == term && currentSearchTerm.isNull() == term.isNull() && currentSort == sort && !filter_changed) { + return; + } currentSearchTerm = term; currentSort = sort; @@ -118,7 +120,9 @@ void ListModel::getLogo(const QString& logo, const QString& logoUrl, LogoCallbac void ListModel::requestLogo(QString logo, QString url) { - if (m_loadingLogos.contains(logo) || m_failedLogos.contains(logo)) { return; } + if (m_loadingLogos.contains(logo) || m_failedLogos.contains(logo)) { + return; + } MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry(m_parent->metaEntryBase(), QString("logos/%1").arg(logo.section(".", 0, 0))); @@ -129,7 +133,9 @@ void ListModel::requestLogo(QString logo, QString url) QObject::connect(job, &NetJob::succeeded, this, [this, logo, fullPath, job] { job->deleteLater(); emit logoLoaded(logo, QIcon(fullPath)); - if (waitingCallbacks.contains(logo)) { waitingCallbacks.value(logo)(fullPath); } + if (waitingCallbacks.contains(logo)) { + waitingCallbacks.value(logo)(fullPath); + } }); QObject::connect(job, &NetJob::failed, this, [this, logo, job] { @@ -148,7 +154,9 @@ void ListModel::logoLoaded(QString logo, QIcon out) m_loadingLogos.removeAll(logo); m_logoMap.insert(logo, out); for (int i = 0; i < modpacks.size(); i++) { - if (modpacks[i].logoName == logo) { emit dataChanged(createIndex(i, 0), createIndex(i, 0), { Qt::DecorationRole }); } + if (modpacks[i].logoName == logo) { + emit dataChanged(createIndex(i, 0), createIndex(i, 0), { Qt::DecorationRole }); + } } } @@ -199,7 +207,9 @@ void ListModel::searchRequestFailed(QString reason) // 409 Gone, notify user to update QMessageBox::critical(nullptr, tr("Error"), //: %1 refers to the launcher itself - QString("%1 %2").arg(m_parent->displayName()).arg(tr("API version too old!\nPlease update %1!").arg(BuildConfig.LAUNCHER_NAME))); + QString("%1 %2") + .arg(m_parent->displayName()) + .arg(tr("API version too old!\nPlease update %1!").arg(BuildConfig.LAUNCHER_NAME))); } jobPtr.reset(); @@ -218,9 +228,12 @@ void ListModel::searchRequestFailed(QString reason) void ListModel::versionRequestSucceeded(QJsonDocument doc, QString addonId) { auto& current = m_parent->getCurrent(); - if (addonId != current.addonId) { return; } + if (addonId != current.addonId) { + return; + } + + auto arr = doc.isObject() ? Json::ensureArray(doc.object(), "data") : doc.array(); - QJsonArray arr = doc.array(); try { loadIndexedPackVersions(current, arr); } catch (const JSONValidationError& e) { diff --git a/launcher/ui/pages/modplatform/flame/FlameModModel.cpp b/launcher/ui/pages/modplatform/flame/FlameModModel.cpp index 905fb2dd1..8de2e545c 100644 --- a/launcher/ui/pages/modplatform/flame/FlameModModel.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameModModel.cpp @@ -1,5 +1,5 @@ #include "FlameModModel.h" - +#include "Json.h" #include "modplatform/flame/FlameModIndex.h" namespace FlameMod { @@ -19,7 +19,7 @@ void ListModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& auto ListModel::documentToArray(QJsonDocument& obj) const -> QJsonArray { - return obj.array(); + return Json::ensureArray(obj.object(), "data"); } } // namespace FlameMod diff --git a/launcher/ui/pages/modplatform/flame/FlameModel.cpp b/launcher/ui/pages/modplatform/flame/FlameModel.cpp index fe163caee..f97536e8a 100644 --- a/launcher/ui/pages/modplatform/flame/FlameModel.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameModel.cpp @@ -1,6 +1,6 @@ #include "FlameModel.h" -#include "Application.h" #include +#include "Application.h" #include #include @@ -9,61 +9,46 @@ namespace Flame { -ListModel::ListModel(QObject *parent) : QAbstractListModel(parent) -{ -} +ListModel::ListModel(QObject* parent) : QAbstractListModel(parent) {} -ListModel::~ListModel() -{ -} +ListModel::~ListModel() {} -int ListModel::rowCount(const QModelIndex &parent) const +int ListModel::rowCount(const QModelIndex& parent) const { return modpacks.size(); } -int ListModel::columnCount(const QModelIndex &parent) const +int ListModel::columnCount(const QModelIndex& parent) const { return 1; } -QVariant ListModel::data(const QModelIndex &index, int role) const +QVariant ListModel::data(const QModelIndex& index, int role) const { int pos = index.row(); - if(pos >= modpacks.size() || pos < 0 || !index.isValid()) - { + if (pos >= modpacks.size() || pos < 0 || !index.isValid()) { return QString("INVALID INDEX %1").arg(pos); } IndexedPack pack = modpacks.at(pos); - if(role == Qt::DisplayRole) - { + if (role == Qt::DisplayRole) { return pack.name; - } - else if (role == Qt::ToolTipRole) - { - if(pack.description.length() > 100) - { - //some magic to prevent to long tooltips and replace html linebreaks + } else if (role == Qt::ToolTipRole) { + if (pack.description.length() > 100) { + // some magic to prevent to long tooltips and replace html linebreaks QString edit = pack.description.left(97); edit = edit.left(edit.lastIndexOf("
")).left(edit.lastIndexOf(" ")).append("..."); return edit; - } return pack.description; - } - else if(role == Qt::DecorationRole) - { - if(m_logoMap.contains(pack.logoName)) - { + } else if (role == Qt::DecorationRole) { + if (m_logoMap.contains(pack.logoName)) { return (m_logoMap.value(pack.logoName)); } QIcon icon = APPLICATION->getThemedIcon("screenshot-placeholder"); - ((ListModel *)this)->requestLogo(pack.logoName, pack.logoUrl); + ((ListModel*)this)->requestLogo(pack.logoName, pack.logoUrl); return icon; - } - else if(role == Qt::UserRole) - { + } else if (role == Qt::UserRole) { QVariant v; v.setValue(pack); return v; @@ -76,9 +61,9 @@ void ListModel::logoLoaded(QString logo, QIcon out) { m_loadingLogos.removeAll(logo); m_logoMap.insert(logo, out); - for(int i = 0; i < modpacks.size(); i++) { - if(modpacks[i].logoName == logo) { - emit dataChanged(createIndex(i, 0), createIndex(i, 0), {Qt::DecorationRole}); + for (int i = 0; i < modpacks.size(); i++) { + if (modpacks[i].logoName == logo) { + emit dataChanged(createIndex(i, 0), createIndex(i, 0), { Qt::DecorationRole }); } } } @@ -91,8 +76,7 @@ void ListModel::logoFailed(QString logo) void ListModel::requestLogo(QString logo, QString url) { - if(m_loadingLogos.contains(logo) || m_failedLogos.contains(logo)) - { + if (m_loadingLogos.contains(logo) || m_failedLogos.contains(logo)) { return; } @@ -101,18 +85,15 @@ void ListModel::requestLogo(QString logo, QString url) job->addNetAction(Net::Download::makeCached(QUrl(url), entry)); auto fullPath = entry->getFullPath(); - QObject::connect(job, &NetJob::succeeded, this, [this, logo, fullPath, job] - { + QObject::connect(job, &NetJob::succeeded, this, [this, logo, fullPath, job] { job->deleteLater(); emit logoLoaded(logo, QIcon(fullPath)); - if(waitingCallbacks.contains(logo)) - { + if (waitingCallbacks.contains(logo)) { waitingCallbacks.value(logo)(fullPath); } }); - QObject::connect(job, &NetJob::failed, this, [this, logo, job] - { + QObject::connect(job, &NetJob::failed, this, [this, logo, job] { job->deleteLater(); emit logoFailed(logo); }); @@ -122,19 +103,16 @@ void ListModel::requestLogo(QString logo, QString url) m_loadingLogos.append(logo); } -void ListModel::getLogo(const QString &logo, const QString &logoUrl, LogoCallback callback) +void ListModel::getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback) { - if(m_logoMap.contains(logo)) - { + if (m_logoMap.contains(logo)) { callback(APPLICATION->metacache()->resolveEntry("FlamePacks", QString("logos/%1").arg(logo.section(".", 0, 0)))->getFullPath()); - } - else - { + } else { requestLogo(logo, logoUrl); } } -Qt::ItemFlags ListModel::flags(const QModelIndex &index) const +Qt::ItemFlags ListModel::flags(const QModelIndex& index) const { return QAbstractListModel::flags(index); } @@ -148,7 +126,7 @@ void ListModel::fetchMore(const QModelIndex& parent) { if (parent.isValid()) return; - if(nextSearchOffset == 0) { + if (nextSearchOffset == 0) { qWarning() << "fetchMore with 0 offset is wrong..."; return; } @@ -157,17 +135,20 @@ void ListModel::fetchMore(const QModelIndex& parent) void ListModel::performPaginatedSearch() { - NetJob *netJob = new NetJob("Flame::Search", APPLICATION->network()); + NetJob* netJob = new NetJob("Flame::Search", APPLICATION->network()); auto searchUrl = QString( - "https://addons-ecs.forgesvc.net/api/v2/addon/search?" - "categoryId=0&" - "gameId=432&" - "index=%1&" - "pageSize=25&" - "searchFilter=%2&" - "sectionId=4471&" - "sort=%3" - ).arg(nextSearchOffset).arg(currentSearchTerm).arg(currentSort); + "https://api.curseforge.com/v1/mods/search?" + "gameId=432&" + "classId=4471&" + "index=%1&" + "pageSize=25&" + "searchFilter=%2&" + "sortField=%3&" + "sortOrder=desc") + .arg(nextSearchOffset) + .arg(currentSearchTerm) + .arg(currentSort + 1); + netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), &response)); jobPtr = netJob; jobPtr->start(); @@ -177,17 +158,16 @@ void ListModel::performPaginatedSearch() void ListModel::searchWithTerm(const QString& term, int sort) { - if(currentSearchTerm == term && currentSearchTerm.isNull() == term.isNull() && currentSort == sort) { + if (currentSearchTerm == term && currentSearchTerm.isNull() == term.isNull() && currentSort == sort) { return; } currentSearchTerm = term; currentSort = sort; - if(jobPtr) { + if (jobPtr) { jobPtr->abort(); searchState = ResetRequested; return; - } - else { + } else { beginResetModel(); modpacks.clear(); endResetModel(); @@ -203,30 +183,28 @@ void Flame::ListModel::searchRequestFinished() QJsonParseError parse_error; QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error); - if(parse_error.error != QJsonParseError::NoError) { - qWarning() << "Error while parsing JSON response from CurseForge at " << parse_error.offset << " reason: " << parse_error.errorString(); + if (parse_error.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response from CurseForge at " << parse_error.offset + << " reason: " << parse_error.errorString(); qWarning() << response; return; } QList newList; - auto packs = doc.array(); - for(auto packRaw : packs) { + auto packs = Json::ensureArray(doc.object(), "data"); + for (auto packRaw : packs) { auto packObj = packRaw.toObject(); Flame::IndexedPack pack; - try - { + try { Flame::loadIndexedPack(pack, packObj); newList.append(pack); - } - catch(const JSONValidationError &e) - { + } catch (const JSONValidationError& e) { qWarning() << "Error while loading pack from CurseForge: " << e.cause(); continue; } } - if(packs.size() < 25) { + if (packs.size() < 25) { searchState = Finished; } else { nextSearchOffset += 25; @@ -241,7 +219,7 @@ void Flame::ListModel::searchRequestFailed(QString reason) { jobPtr.reset(); - if(searchState == ResetRequested) { + if (searchState == ResetRequested) { beginResetModel(); modpacks.clear(); endResetModel(); @@ -253,5 +231,4 @@ void Flame::ListModel::searchRequestFailed(QString reason) } } -} - +} // namespace Flame diff --git a/launcher/ui/pages/modplatform/flame/FlamePage.cpp b/launcher/ui/pages/modplatform/flame/FlamePage.cpp index c90294ce7..ec7746217 100644 --- a/launcher/ui/pages/modplatform/flame/FlamePage.cpp +++ b/launcher/ui/pages/modplatform/flame/FlamePage.cpp @@ -39,13 +39,12 @@ #include #include "Application.h" +#include "FlameModel.h" +#include "InstanceImportTask.h" #include "Json.h" #include "ui/dialogs/NewInstanceDialog.h" -#include "InstanceImportTask.h" -#include "FlameModel.h" -FlamePage::FlamePage(NewInstanceDialog* dialog, QWidget *parent) - : QWidget(parent), ui(new Ui::FlamePage), dialog(dialog) +FlamePage::FlamePage(NewInstanceDialog* dialog, QWidget* parent) : QWidget(parent), ui(new Ui::FlamePage), dialog(dialog) { ui->setupUi(this); connect(ui->searchButton, &QPushButton::clicked, this, &FlamePage::triggerSearch); @@ -112,10 +111,8 @@ void FlamePage::onSelectionChanged(QModelIndex first, QModelIndex second) { ui->versionSelectionBox->clear(); - if(!first.isValid()) - { - if(isOpened) - { + if (!first.isValid()) { + if (isOpened) { dialog->setSuggestedPack(); } return; @@ -130,14 +127,14 @@ void FlamePage::onSelectionChanged(QModelIndex first, QModelIndex second) else text = "" + name + ""; if (!current.authors.empty()) { - auto authorToStr = [](Flame::ModpackAuthor & author) { - if(author.url.isEmpty()) { + auto authorToStr = [](Flame::ModpackAuthor& author) { + if (author.url.isEmpty()) { return author.name; } return QString("%2").arg(author.url, author.name); }; QStringList authorStrs; - for(auto & author: current.authors) { + for (auto& author : current.authors) { authorStrs.push_back(authorToStr(author)); } text += "
" + tr(" by ") + authorStrs.join(", "); @@ -146,53 +143,46 @@ void FlamePage::onSelectionChanged(QModelIndex first, QModelIndex second) ui->packDescription->setHtml(text + current.description); - if (current.versionsLoaded == false) - { + if (current.versionsLoaded == false) { qDebug() << "Loading flame modpack versions"; auto netJob = new NetJob(QString("Flame::PackVersions(%1)").arg(current.name), APPLICATION->network()); auto response = new QByteArray(); int addonId = current.addonId; - netJob->addNetAction(Net::Download::makeByteArray(QString("https://addons-ecs.forgesvc.net/api/v2/addon/%1/files").arg(addonId), response)); + netJob->addNetAction(Net::Download::makeByteArray(QString("https://api.curseforge.com/v1/mods/%1/files").arg(addonId), response)); - QObject::connect(netJob, &NetJob::succeeded, this, [this, response, addonId] - { - if(addonId != current.addonId){ - return; //wrong request + QObject::connect(netJob, &NetJob::succeeded, this, [this, response, addonId] { + if (addonId != current.addonId) { + return; // wrong request } QJsonParseError parse_error; QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); - if(parse_error.error != QJsonParseError::NoError) { - qWarning() << "Error while parsing JSON response from CurseForge at " << parse_error.offset << " reason: " << parse_error.errorString(); + if (parse_error.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response from CurseForge at " << parse_error.offset + << " reason: " << parse_error.errorString(); qWarning() << *response; return; } - QJsonArray arr = doc.array(); - try - { + auto arr = Json::ensureArray(doc.object(), "data"); + try { Flame::loadIndexedPackVersions(current, arr); - } - catch(const JSONValidationError &e) - { + } catch (const JSONValidationError& e) { qDebug() << *response; qWarning() << "Error while reading flame modpack version: " << e.cause(); } - for(auto version : current.versions) { + for (auto version : current.versions) { ui->versionSelectionBox->addItem(version.version, QVariant(version.downloadUrl)); } suggestCurrent(); }); - QObject::connect(netJob, &NetJob::finished, this, [response, netJob] - { + QObject::connect(netJob, &NetJob::finished, this, [response, netJob] { netJob->deleteLater(); delete response; }); netJob->start(); - } - else - { - for(auto version : current.versions) { + } else { + for (auto version : current.versions) { ui->versionSelectionBox->addItem(version.version, QVariant(version.downloadUrl)); } @@ -202,13 +192,11 @@ void FlamePage::onSelectionChanged(QModelIndex first, QModelIndex second) void FlamePage::suggestCurrent() { - if(!isOpened) - { + if (!isOpened) { return; } - if (selectedVersion.isEmpty()) - { + if (selectedVersion.isEmpty()) { dialog->setSuggestedPack(); return; } @@ -216,16 +204,13 @@ void FlamePage::suggestCurrent() dialog->setSuggestedPack(current.name, new InstanceImportTask(selectedVersion)); QString editedLogoName; editedLogoName = "curseforge_" + current.logoName.section(".", 0, 0); - listModel->getLogo(current.logoName, current.logoUrl, [this, editedLogoName](QString logo) - { - dialog->setSuggestedIconFromFile(logo, editedLogoName); - }); + listModel->getLogo(current.logoName, current.logoUrl, + [this, editedLogoName](QString logo) { dialog->setSuggestedIconFromFile(logo, editedLogoName); }); } void FlamePage::onVersionSelectionChanged(QString data) { - if(data.isNull() || data.isEmpty()) - { + if (data.isNull() || data.isEmpty()) { selectedVersion = ""; return; }