feat(RD): add texture pack downloader

This extends the resource pack downloader, with the custom behavior of
filtering the versions that shows up, to those <= 1.6. As always, Flame
is funky and requires a bit more workarounds than average.

This will also get a nice improvement when the Version parsing and
comparison PR gets merged! :D

Signed-off-by: flow <flowlnlnln@gmail.com>
This commit is contained in:
flow
2023-01-29 18:07:49 -03:00
parent b724607e31
commit ada5e88eb9
22 changed files with 550 additions and 20 deletions

View File

@ -98,7 +98,7 @@ class ResourceModel : public QAbstractListModel {
/** Functions to load data into a pack.
*
* Those are needed for the same reason as ddocumentToArray, and NEED to be re-implemented in the same way.
* Those are needed for the same reason as documentToArray, and NEED to be re-implemented in the same way.
*/
virtual void loadIndexedPack(ModPlatform::IndexedPack&, QJsonObject&);

View File

@ -9,7 +9,7 @@
namespace ResourceDownload {
ResourcePackResourcePage::ResourcePackResourcePage(ResourcePackDownloadDialog* dialog, BaseInstance& instance)
ResourcePackResourcePage::ResourcePackResourcePage(ResourceDownloadDialog* dialog, BaseInstance& instance)
: ResourcePage(dialog, instance)
{
connect(m_ui->searchButton, &QPushButton::clicked, this, &ResourcePackResourcePage::triggerSearch);

View File

@ -39,7 +39,7 @@ class ResourcePackResourcePage : public ResourcePage {
[[nodiscard]] QMap<QString, QString> urlHandlers() const override;
protected:
ResourcePackResourcePage(ResourcePackDownloadDialog* dialog, BaseInstance& instance);
ResourcePackResourcePage(ResourceDownloadDialog* dialog, BaseInstance& instance);
protected slots:
void triggerSearch() override;

View File

@ -0,0 +1,80 @@
#include "TexturePackModel.h"
#include "Application.h"
#include "meta/Index.h"
#include "meta/Version.h"
static std::list<Version> s_availableVersions = {};
namespace ResourceDownload {
TexturePackResourceModel::TexturePackResourceModel(BaseInstance const& inst, ResourceAPI* api)
: ResourcePackResourceModel(inst, api), m_version_list(APPLICATION->metadataIndex()->get("net.minecraft"))
{
if (!m_version_list->isLoaded()) {
qDebug() << "Loading version list...";
auto task = m_version_list->getLoadTask();
if (!task->isRunning())
task->start();
}
}
void waitOnVersionListLoad(Meta::VersionList::Ptr version_list)
{
QEventLoop load_version_list_loop;
QTimer time_limit_for_list_load;
time_limit_for_list_load.setTimerType(Qt::TimerType::CoarseTimer);
time_limit_for_list_load.setSingleShot(true);
time_limit_for_list_load.callOnTimeout(&load_version_list_loop, &QEventLoop::quit);
time_limit_for_list_load.start(4000);
auto task = version_list->getLoadTask();
QObject::connect(task.get(), &Task::finished, &load_version_list_loop, &QEventLoop::quit);
load_version_list_loop.exec();
if (time_limit_for_list_load.isActive())
time_limit_for_list_load.stop();
}
ResourceAPI::SearchArgs TexturePackResourceModel::createSearchArguments()
{
if (s_availableVersions.empty())
waitOnVersionListLoad(m_version_list);
auto args = ResourcePackResourceModel::createSearchArguments();
if (!m_version_list->isLoaded()) {
qCritical() << "The version list could not be loaded. Falling back to showing all entries.";
return args;
}
if (s_availableVersions.empty()) {
for (auto&& version : m_version_list->versions()) {
// FIXME: This duplicates the logic in meta for the 'texturepacks' trait. However, we don't have access to that
// information from the index file alone. Also, downloading every version's file isn't a very good idea.
if (auto ver = version->toComparableVersion(); ver <= maximumTexturePackVersion())
s_availableVersions.push_back(ver);
}
}
Q_ASSERT(!s_availableVersions.empty());
args.versions = s_availableVersions;
return args;
}
ResourceAPI::VersionSearchArgs TexturePackResourceModel::createVersionsArguments(QModelIndex& entry)
{
auto args = ResourcePackResourceModel::createVersionsArguments(entry);
if (!m_version_list->isLoaded()) {
qCritical() << "The version list could not be loaded. Falling back to showing all entries.";
return args;
}
args.mcVersions = s_availableVersions;
return args;
}
} // namespace ResourceDownload

View File

@ -0,0 +1,23 @@
#pragma once
#include "meta/VersionList.h"
#include "ui/pages/modplatform/ResourcePackModel.h"
namespace ResourceDownload {
class TexturePackResourceModel : public ResourcePackResourceModel {
Q_OBJECT
public:
TexturePackResourceModel(BaseInstance const& inst, ResourceAPI* api);
[[nodiscard]] inline ::Version maximumTexturePackVersion() const { return { "1.6" }; }
ResourceAPI::SearchArgs createSearchArguments() override;
ResourceAPI::VersionSearchArgs createVersionsArguments(QModelIndex&) override;
protected:
Meta::VersionList::Ptr m_version_list;
};
} // namespace ResourceDownload

View File

@ -0,0 +1,46 @@
#pragma once
#include "ui_ResourcePage.h"
#include "ui/dialogs/ResourceDownloadDialog.h"
#include "ui/pages/modplatform/ResourcePackPage.h"
#include "ui/pages/modplatform/TexturePackModel.h"
namespace Ui {
class ResourcePage;
}
namespace ResourceDownload {
class TexturePackDownloadDialog;
class TexturePackResourcePage : public ResourcePackResourcePage {
Q_OBJECT
public:
template <typename T>
static T* create(TexturePackDownloadDialog* dialog, BaseInstance& instance)
{
auto page = new T(dialog, instance);
auto model = static_cast<TexturePackResourceModel*>(page->getModel());
connect(model, &ResourceModel::versionListUpdated, page, &ResourcePage::updateVersionList);
connect(model, &ResourceModel::projectInfoUpdated, page, &ResourcePage::updateUi);
return page;
}
//: The plural version of 'texture pack'
[[nodiscard]] inline QString resourcesString() const override { return tr("texture packs"); }
//: The singular version of 'texture packs'
[[nodiscard]] inline QString resourceString() const override { return tr("texture pack"); }
protected:
TexturePackResourcePage(TexturePackDownloadDialog* dialog, BaseInstance& instance)
: ResourcePackResourcePage(dialog, instance)
{
connect(m_ui->searchButton, &QPushButton::clicked, this, &TexturePackResourcePage::triggerSearch);
connect(m_ui->packView, &QListView::doubleClicked, this, &TexturePackResourcePage::onResourceSelected);
}
};
} // namespace ResourceDownload

View File

@ -57,5 +57,63 @@ auto FlameResourcePackModel::documentToArray(QJsonDocument& obj) const -> QJsonA
return Json::ensureArray(obj.object(), "data");
}
FlameTexturePackModel::FlameTexturePackModel(const BaseInstance& base) : TexturePackResourceModel(base, new FlameAPI) {}
void FlameTexturePackModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj)
{
FlameMod::loadIndexedPack(m, obj);
}
// We already deal with the URLs when initializing the pack, due to the API response's structure
void FlameTexturePackModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj)
{
FlameMod::loadBody(m, obj);
}
void FlameTexturePackModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr)
{
FlameMod::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_base_instance);
QVector<ModPlatform::IndexedVersion> filtered_versions(m.versions.size());
// FIXME: Client-side version filtering. This won't take into account any user-selected filtering.
for (auto const& version : m.versions) {
auto const& mc_versions = version.mcVersion;
if (std::any_of(mc_versions.constBegin(), mc_versions.constEnd(),
[this](auto const& mc_version){ return Version(mc_version) <= maximumTexturePackVersion(); }))
filtered_versions.push_back(version);
}
m.versions = filtered_versions;
}
ResourceAPI::SearchArgs FlameTexturePackModel::createSearchArguments()
{
auto args = TexturePackResourceModel::createSearchArguments();
auto profile = static_cast<const MinecraftInstance&>(m_base_instance).getPackProfile();
QString instance_minecraft_version = profile->getComponentVersion("net.minecraft");
// Bypass the texture pack logic, because we can't do multiple versions in the API query
args.versions = { instance_minecraft_version };
return args;
}
ResourceAPI::VersionSearchArgs FlameTexturePackModel::createVersionsArguments(QModelIndex& entry)
{
auto args = TexturePackResourceModel::createVersionsArguments(entry);
// Bypass the texture pack logic, because we can't do multiple versions in the API query
args.mcVersions = {};
return args;
}
auto FlameTexturePackModel::documentToArray(QJsonDocument& obj) const -> QJsonArray
{
return Json::ensureArray(obj.object(), "data");
}
} // namespace ResourceDownload

View File

@ -46,4 +46,25 @@ class FlameResourcePackModel : public ResourcePackResourceModel {
auto documentToArray(QJsonDocument& obj) const -> QJsonArray override;
};
class FlameTexturePackModel : public TexturePackResourceModel {
Q_OBJECT
public:
FlameTexturePackModel(const BaseInstance&);
~FlameTexturePackModel() override = default;
private:
[[nodiscard]] QString debugName() const override { return Flame::debugName() + " (Model)"; }
[[nodiscard]] QString metaEntryBase() const override { return Flame::metaEntryBase(); }
void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) override;
void loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) override;
void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) override;
ResourceAPI::SearchArgs createSearchArguments() override;
ResourceAPI::VersionSearchArgs createVersionsArguments(QModelIndex&) override;
auto documentToArray(QJsonDocument& obj) const -> QJsonArray override;
};
} // namespace ResourceDownload

View File

@ -133,10 +133,50 @@ void FlameResourcePackPage::openUrl(const QUrl& url)
ResourcePackResourcePage::openUrl(url);
}
FlameTexturePackPage::FlameTexturePackPage(TexturePackDownloadDialog* dialog, BaseInstance& instance)
: TexturePackResourcePage(dialog, instance)
{
m_model = new FlameTexturePackModel(instance);
m_ui->packView->setModel(m_model);
addSortings();
// sometimes Qt just ignores virtual slots and doesn't work as intended it seems,
// so it's best not to connect them in the parent's contructor...
connect(m_ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch()));
connect(m_ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &FlameTexturePackPage::onSelectionChanged);
connect(m_ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &FlameTexturePackPage::onVersionSelectionChanged);
connect(m_ui->resourceSelectionButton, &QPushButton::clicked, this, &FlameTexturePackPage::onResourceSelected);
m_ui->packDescription->setMetaEntry(metaEntryBase());
}
bool FlameTexturePackPage::optedOut(ModPlatform::IndexedVersion& ver) const
{
return isOptedOut(ver);
}
void FlameTexturePackPage::openUrl(const QUrl& url)
{
if (url.scheme().isEmpty()) {
QString query = url.query(QUrl::FullyDecoded);
if (query.startsWith("remoteUrl=")) {
// attempt to resolve url from warning page
query.remove(0, 10);
ResourcePackResourcePage::openUrl({QUrl::fromPercentEncoding(query.toUtf8())}); // double decoding is necessary
return;
}
}
TexturePackResourcePage::openUrl(url);
}
// I don't know why, but doing this on the parent class makes it so that
// other mod providers start loading before being selected, at least with
// my Qt, so we need to implement this in every derived class...
auto FlameModPage::shouldDisplay() const -> bool { return true; }
auto FlameResourcePackPage::shouldDisplay() const -> bool { return true; }
auto FlameTexturePackPage::shouldDisplay() const -> bool { return true; }
} // namespace ResourceDownload

View File

@ -44,7 +44,7 @@
#include "ui/pages/modplatform/ModPage.h"
#include "ui/pages/modplatform/ResourcePackPage.h"
#include "ui/pages/modplatform/ShaderPackPage.h"
#include "ui/pages/modplatform/TexturePackPage.h"
namespace ResourceDownload {
@ -111,4 +111,31 @@ class FlameResourcePackPage : public ResourcePackResourcePage {
void openUrl(const QUrl& url) override;
};
class FlameTexturePackPage : public TexturePackResourcePage {
Q_OBJECT
public:
static FlameTexturePackPage* create(TexturePackDownloadDialog* dialog, BaseInstance& instance)
{
return TexturePackResourcePage::create<FlameTexturePackPage>(dialog, instance);
}
FlameTexturePackPage(TexturePackDownloadDialog* dialog, BaseInstance& instance);
~FlameTexturePackPage() override = default;
[[nodiscard]] bool shouldDisplay() const override;
[[nodiscard]] inline auto displayName() const -> QString override { return Flame::displayName(); }
[[nodiscard]] inline auto icon() const -> QIcon override { return Flame::icon(); }
[[nodiscard]] inline auto id() const -> QString override { return Flame::id(); }
[[nodiscard]] inline auto debugName() const -> QString override { return Flame::debugName(); }
[[nodiscard]] inline auto metaEntryBase() const -> QString override { return Flame::metaEntryBase(); }
[[nodiscard]] inline auto helpPage() const -> QString override { return ""; }
bool optedOut(ModPlatform::IndexedVersion& ver) const override;
void openUrl(const QUrl& url) override;
};
} // namespace ResourceDownload

View File

@ -69,6 +69,28 @@ auto ModrinthResourcePackModel::documentToArray(QJsonDocument& obj) const -> QJs
return obj.object().value("hits").toArray();
}
ModrinthTexturePackModel::ModrinthTexturePackModel(const BaseInstance& base) : TexturePackResourceModel(base, new ModrinthAPI){}
void ModrinthTexturePackModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj)
{
::Modrinth::loadIndexedPack(m, obj);
}
void ModrinthTexturePackModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj)
{
::Modrinth::loadExtraPackData(m, obj);
}
void ModrinthTexturePackModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr)
{
::Modrinth::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_base_instance);
}
auto ModrinthTexturePackModel::documentToArray(QJsonDocument& obj) const -> QJsonArray
{
return obj.object().value("hits").toArray();
}
ModrinthShaderPackModel::ModrinthShaderPackModel(const BaseInstance& base) : ShaderPackResourceModel(base, new ModrinthAPI){}
void ModrinthShaderPackModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj)

View File

@ -62,6 +62,24 @@ class ModrinthResourcePackModel : public ResourcePackResourceModel {
auto documentToArray(QJsonDocument& obj) const -> QJsonArray override;
};
class ModrinthTexturePackModel : public TexturePackResourceModel {
Q_OBJECT
public:
ModrinthTexturePackModel(const BaseInstance&);
~ModrinthTexturePackModel() override = default;
private:
[[nodiscard]] QString debugName() const override { return Modrinth::debugName() + " (Model)"; }
[[nodiscard]] QString metaEntryBase() const override { return Modrinth::metaEntryBase(); }
void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) override;
void loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) override;
void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) override;
auto documentToArray(QJsonDocument& obj) const -> QJsonArray override;
};
class ModrinthShaderPackModel : public ShaderPackResourceModel {
Q_OBJECT

View File

@ -100,6 +100,24 @@ ModrinthResourcePackPage::ModrinthResourcePackPage(ResourcePackDownloadDialog* d
m_ui->packDescription->setMetaEntry(metaEntryBase());
}
ModrinthTexturePackPage::ModrinthTexturePackPage(TexturePackDownloadDialog* dialog, BaseInstance& instance)
: TexturePackResourcePage(dialog, instance)
{
m_model = new ModrinthTexturePackModel(instance);
m_ui->packView->setModel(m_model);
addSortings();
// sometimes Qt just ignores virtual slots and doesn't work as intended it seems,
// so it's best not to connect them in the parent's constructor...
connect(m_ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch()));
connect(m_ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &ModrinthTexturePackPage::onSelectionChanged);
connect(m_ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &ModrinthTexturePackPage::onVersionSelectionChanged);
connect(m_ui->resourceSelectionButton, &QPushButton::clicked, this, &ModrinthTexturePackPage::onResourceSelected);
m_ui->packDescription->setMetaEntry(metaEntryBase());
}
ModrinthShaderPackPage::ModrinthShaderPackPage(ShaderPackDownloadDialog* dialog, BaseInstance& instance)
: ShaderPackResourcePage(dialog, instance)
{
@ -123,6 +141,7 @@ ModrinthShaderPackPage::ModrinthShaderPackPage(ShaderPackDownloadDialog* dialog,
// my Qt, so we need to implement this in every derived class...
auto ModrinthModPage::shouldDisplay() const -> bool { return true; }
auto ModrinthResourcePackPage::shouldDisplay() const -> bool { return true; }
auto ModrinthTexturePackPage::shouldDisplay() const -> bool { return true; }
auto ModrinthShaderPackPage::shouldDisplay() const -> bool { return true; }
} // namespace ResourceDownload

View File

@ -43,6 +43,7 @@
#include "ui/pages/modplatform/ModPage.h"
#include "ui/pages/modplatform/ResourcePackPage.h"
#include "ui/pages/modplatform/TexturePackPage.h"
#include "ui/pages/modplatform/ShaderPackPage.h"
namespace ResourceDownload {
@ -103,6 +104,29 @@ class ModrinthResourcePackPage : public ResourcePackResourcePage {
[[nodiscard]] inline auto helpPage() const -> QString override { return ""; }
};
class ModrinthTexturePackPage : public TexturePackResourcePage {
Q_OBJECT
public:
static ModrinthTexturePackPage* create(TexturePackDownloadDialog* dialog, BaseInstance& instance)
{
return TexturePackResourcePage::create<ModrinthTexturePackPage>(dialog, instance);
}
ModrinthTexturePackPage(TexturePackDownloadDialog* dialog, BaseInstance& instance);
~ModrinthTexturePackPage() override = default;
[[nodiscard]] bool shouldDisplay() const override;
[[nodiscard]] inline auto displayName() const -> QString override { return Modrinth::displayName(); }
[[nodiscard]] inline auto icon() const -> QIcon override { return Modrinth::icon(); }
[[nodiscard]] inline auto id() const -> QString override { return Modrinth::id(); }
[[nodiscard]] inline auto debugName() const -> QString override { return Modrinth::debugName(); }
[[nodiscard]] inline auto metaEntryBase() const -> QString override { return Modrinth::metaEntryBase(); }
[[nodiscard]] inline auto helpPage() const -> QString override { return ""; }
};
class ModrinthShaderPackPage : public ShaderPackResourcePage {
Q_OBJECT