feat(RD): add resource pack downloader

Signed-off-by: flow <flowlnlnln@gmail.com>
This commit is contained in:
flow
2022-12-16 20:26:10 -03:00
parent e89a10945c
commit c3ea303a37
20 changed files with 538 additions and 23 deletions

View File

@ -0,0 +1,42 @@
#include "ResourcePackModel.h"
#include <QMessageBox>
namespace ResourceDownload {
ResourcePackResourceModel::ResourcePackResourceModel(BaseInstance const& base_inst, ResourceAPI* api)
: ResourceModel(api), m_base_instance(base_inst){};
/******** Make data requests ********/
ResourceAPI::SearchArgs ResourcePackResourceModel::createSearchArguments()
{
auto sort = getCurrentSortingMethodByIndex();
return { ModPlatform::ResourceType::RESOURCE_PACK, m_next_search_offset, m_search_term, sort };
}
ResourceAPI::VersionSearchArgs ResourcePackResourceModel::createVersionsArguments(QModelIndex& entry)
{
auto& pack = m_packs[entry.row()];
return { pack };
}
ResourceAPI::ProjectInfoArgs ResourcePackResourceModel::createInfoArguments(QModelIndex& entry)
{
auto& pack = m_packs[entry.row()];
return { pack };
}
void ResourcePackResourceModel::searchWithTerm(const QString& term, unsigned int sort)
{
if (m_search_term == term && m_search_term.isNull() == term.isNull() && m_current_sort_index == sort) {
return;
}
setSearchTerm(term);
m_current_sort_index = sort;
refresh();
}
} // namespace ResourceDownload

View File

@ -0,0 +1,39 @@
#pragma once
#include <QAbstractListModel>
#include "BaseInstance.h"
#include "modplatform/ModIndex.h"
#include "ui/pages/modplatform/ResourceModel.h"
class Version;
namespace ResourceDownload {
class ResourcePackResourceModel : public ResourceModel {
Q_OBJECT
public:
ResourcePackResourceModel(BaseInstance const&, ResourceAPI*);
/* Ask the API for more information */
void searchWithTerm(const QString& term, unsigned int sort);
void loadIndexedPack(ModPlatform::IndexedPack&, QJsonObject&) override = 0;
void loadExtraPackInfo(ModPlatform::IndexedPack&, QJsonObject&) override = 0;
void loadIndexedPackVersions(ModPlatform::IndexedPack&, QJsonArray&) override = 0;
public slots:
ResourceAPI::SearchArgs createSearchArguments() override;
ResourceAPI::VersionSearchArgs createVersionsArguments(QModelIndex&) override;
ResourceAPI::ProjectInfoArgs createInfoArguments(QModelIndex&) override;
protected:
const BaseInstance& m_base_instance;
auto documentToArray(QJsonDocument& obj) const -> QJsonArray override = 0;
};
} // namespace ResourceDownload

View File

@ -0,0 +1,42 @@
#include "ResourcePackPage.h"
#include "ui_ResourcePage.h"
#include "ResourcePackModel.h"
#include "ui/dialogs/ResourceDownloadDialog.h"
#include <QRegularExpression>
namespace ResourceDownload {
ResourcePackResourcePage::ResourcePackResourcePage(ResourcePackDownloadDialog* dialog, BaseInstance& instance)
: ResourcePage(dialog, instance)
{
connect(m_ui->searchButton, &QPushButton::clicked, this, &ResourcePackResourcePage::triggerSearch);
connect(m_ui->packView, &QListView::doubleClicked, this, &ResourcePackResourcePage::onResourceSelected);
}
/******** Callbacks to events in the UI (set up in the derived classes) ********/
void ResourcePackResourcePage::triggerSearch()
{
m_ui->packView->clearSelection();
m_ui->packDescription->clear();
m_ui->versionSelectionBox->clear();
updateSelectionButton();
static_cast<ResourcePackResourceModel*>(m_model)->searchWithTerm(getSearchTerm(), m_ui->sortByBox->currentData().toUInt());
m_fetch_progress.watch(m_model->activeSearchJob().get());
}
QMap<QString, QString> ResourcePackResourcePage::urlHandlers() const
{
QMap<QString, QString> map;
map.insert(QRegularExpression::anchoredPattern("(?:www\\.)?modrinth\\.com\\/resourcepack\\/([^\\/]+)\\/?"), "modrinth");
map.insert(QRegularExpression::anchoredPattern("(?:www\\.)?curseforge\\.com\\/minecraft\\/texture-packs\\/([^\\/]+)\\/?"), "curseforge");
map.insert(QRegularExpression::anchoredPattern("minecraft\\.curseforge\\.com\\/projects\\/([^\\/]+)\\/?"), "curseforge");
return map;
}
} // namespace ResourceDownload

View File

@ -0,0 +1,48 @@
#pragma once
#include "ui/pages/modplatform/ResourcePage.h"
#include "ui/pages/modplatform/ResourcePackModel.h"
namespace Ui {
class ResourcePage;
}
namespace ResourceDownload {
class ResourcePackDownloadDialog;
class ResourcePackResourcePage : public ResourcePage {
Q_OBJECT
public:
template <typename T>
static T* create(ResourcePackDownloadDialog* dialog, BaseInstance& instance)
{
auto page = new T(dialog, instance);
auto model = static_cast<ResourcePackResourceModel*>(page->getModel());
connect(model, &ResourceModel::versionListUpdated, page, &ResourcePage::updateVersionList);
connect(model, &ResourceModel::projectInfoUpdated, page, &ResourcePage::updateUi);
return page;
}
~ResourcePackResourcePage() override = default;
//: The plural version of 'resource pack'
[[nodiscard]] inline QString resourcesString() const override { return tr("resource packs"); }
//: The singular version of 'resource packs'
[[nodiscard]] inline QString resourceString() const override { return tr("resource pack"); }
[[nodiscard]] bool supportsFiltering() const override { return false; };
[[nodiscard]] QMap<QString, QString> urlHandlers() const override;
protected:
ResourcePackResourcePage(ResourcePackDownloadDialog* dialog, BaseInstance& instance);
protected slots:
void triggerSearch() override;
};
} // namespace ResourceDownload

View File

@ -34,4 +34,28 @@ auto FlameModModel::documentToArray(QJsonDocument& obj) const -> QJsonArray
return Json::ensureArray(obj.object(), "data");
}
FlameResourcePackModel::FlameResourcePackModel(const BaseInstance& base) : ResourcePackResourceModel(base, new FlameAPI) {}
void FlameResourcePackModel::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 FlameResourcePackModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj)
{
FlameMod::loadBody(m, obj);
}
void FlameResourcePackModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr)
{
FlameMod::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_base_instance);
}
auto FlameResourcePackModel::documentToArray(QJsonDocument& obj) const -> QJsonArray
{
return Json::ensureArray(obj.object(), "data");
}
} // namespace ResourceDownload

View File

@ -5,6 +5,7 @@
#pragma once
#include "ui/pages/modplatform/ModModel.h"
#include "ui/pages/modplatform/ResourcePackModel.h"
#include "ui/pages/modplatform/flame/FlameResourcePages.h"
namespace ResourceDownload {
@ -27,4 +28,22 @@ class FlameModModel : public ModModel {
auto documentToArray(QJsonDocument& obj) const -> QJsonArray override;
};
class FlameResourcePackModel : public ResourcePackResourceModel {
Q_OBJECT
public:
FlameResourcePackModel(const BaseInstance&);
~FlameResourcePackModel() 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;
auto documentToArray(QJsonDocument& obj) const -> QJsonArray override;
};
} // namespace ResourceDownload

View File

@ -44,6 +44,11 @@
namespace ResourceDownload {
static bool isOptedOut(ModPlatform::IndexedVersion const& ver)
{
return ver.downloadUrl.isEmpty();
}
FlameModPage::FlameModPage(ModDownloadDialog* dialog, BaseInstance& instance)
: ModPage(dialog, instance)
{
@ -70,14 +75,9 @@ auto FlameModPage::validateVersion(ModPlatform::IndexedVersion& ver, QString min
bool FlameModPage::optedOut(ModPlatform::IndexedVersion& ver) const
{
return ver.downloadUrl.isEmpty();
return isOptedOut(ver);
}
// 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; }
void FlameModPage::openUrl(const QUrl& url)
{
if (url.scheme().isEmpty()) {
@ -94,4 +94,49 @@ void FlameModPage::openUrl(const QUrl& url)
ModPage::openUrl(url);
}
FlameResourcePackPage::FlameResourcePackPage(ResourcePackDownloadDialog* dialog, BaseInstance& instance)
: ResourcePackResourcePage(dialog, instance)
{
m_model = new FlameResourcePackModel(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, &FlameResourcePackPage::onSelectionChanged);
connect(m_ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &FlameResourcePackPage::onVersionSelectionChanged);
connect(m_ui->resourceSelectionButton, &QPushButton::clicked, this, &FlameResourcePackPage::onResourceSelected);
m_ui->packDescription->setMetaEntry(metaEntryBase());
}
bool FlameResourcePackPage::optedOut(ModPlatform::IndexedVersion& ver) const
{
return isOptedOut(ver);
}
void FlameResourcePackPage::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;
}
}
ResourcePackResourcePage::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; }
} // namespace ResourceDownload

View File

@ -43,6 +43,7 @@
#include "modplatform/ResourceAPI.h"
#include "ui/pages/modplatform/ModPage.h"
#include "ui/pages/modplatform/ResourcePackPage.h"
namespace ResourceDownload {
@ -82,4 +83,31 @@ class FlameModPage : public ModPage {
void openUrl(const QUrl& url) override;
};
class FlameResourcePackPage : public ResourcePackResourcePage {
Q_OBJECT
public:
static FlameResourcePackPage* create(ResourcePackDownloadDialog* dialog, BaseInstance& instance)
{
return ResourcePackResourcePage::create<FlameResourcePackPage>(dialog, instance);
}
FlameResourcePackPage(ResourcePackDownloadDialog* dialog, BaseInstance& instance);
~FlameResourcePackPage() 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

@ -47,4 +47,26 @@ auto ModrinthModModel::documentToArray(QJsonDocument& obj) const -> QJsonArray
return obj.object().value("hits").toArray();
}
ModrinthResourcePackModel::ModrinthResourcePackModel(const BaseInstance& base) : ResourcePackResourceModel(base, new ModrinthAPI){}
void ModrinthResourcePackModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj)
{
::Modrinth::loadIndexedPack(m, obj);
}
void ModrinthResourcePackModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj)
{
::Modrinth::loadExtraPackData(m, obj);
}
void ModrinthResourcePackModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr)
{
::Modrinth::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_base_instance);
}
auto ModrinthResourcePackModel::documentToArray(QJsonDocument& obj) const -> QJsonArray
{
return obj.object().value("hits").toArray();
}
} // namespace ResourceDownload

View File

@ -21,12 +21,11 @@
#pragma once
#include "ui/pages/modplatform/ModModel.h"
#include "ui/pages/modplatform/ResourcePackModel.h"
#include "ui/pages/modplatform/modrinth/ModrinthResourcePages.h"
namespace ResourceDownload {
class ModrinthModPage;
class ModrinthModModel : public ModModel {
Q_OBJECT
@ -45,4 +44,22 @@ class ModrinthModModel : public ModModel {
auto documentToArray(QJsonDocument& obj) const -> QJsonArray override;
};
class ModrinthResourcePackModel : public ResourcePackResourceModel {
Q_OBJECT
public:
ModrinthResourcePackModel(const BaseInstance&);
~ModrinthResourcePackModel() 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;
};
} // namespace ResourceDownload

View File

@ -82,9 +82,28 @@ auto ModrinthModPage::validateVersion(ModPlatform::IndexedVersion& ver, QString
return ver.mcVersion.contains(mineVer) && loaderCompatible;
}
ModrinthResourcePackPage::ModrinthResourcePackPage(ResourcePackDownloadDialog* dialog, BaseInstance& instance)
: ResourcePackResourcePage(dialog, instance)
{
m_model = new ModrinthResourcePackModel(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, &ModrinthResourcePackPage::onSelectionChanged);
connect(m_ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &ModrinthResourcePackPage::onVersionSelectionChanged);
connect(m_ui->resourceSelectionButton, &QPushButton::clicked, this, &ModrinthResourcePackPage::onResourceSelected);
m_ui->packDescription->setMetaEntry(metaEntryBase());
}
// 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 ModrinthModPage::shouldDisplay() const -> bool { return true; }
auto ModrinthResourcePackPage::shouldDisplay() const -> bool { return true; }
} // namespace ResourceDownload

View File

@ -42,6 +42,7 @@
#include "modplatform/ResourceAPI.h"
#include "ui/pages/modplatform/ModPage.h"
#include "ui/pages/modplatform/ResourcePackPage.h"
namespace ResourceDownload {
@ -78,4 +79,27 @@ class ModrinthModPage : public ModPage {
auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, std::optional<ResourceAPI::ModLoaderTypes> loaders = {}) const -> bool override;
};
class ModrinthResourcePackPage : public ResourcePackResourcePage {
Q_OBJECT
public:
static ModrinthResourcePackPage* create(ResourcePackDownloadDialog* dialog, BaseInstance& instance)
{
return ResourcePackResourcePage::create<ModrinthResourcePackPage>(dialog, instance);
}
ModrinthResourcePackPage(ResourcePackDownloadDialog* dialog, BaseInstance& instance);
~ModrinthResourcePackPage() 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 ""; }
};
} // namespace ResourceDownload