pollymc/launcher/ui/pages/modplatform/ModModel.cpp
flow 38e20eb148
fix(RD): pass copy of IndexedPack to callbacks instead of ref.
This prevents a crash in which the pack list gets updated in a search
request meanwhile a versions / extra info request is being processed.
Previously, this situation would cause the reference in the latter
callbacks to be invalidated by an internal relocation of the pack list.

Signed-off-by: flow <flowlnlnln@gmail.com>
2023-01-13 16:23:07 -03:00

193 lines
5.6 KiB
C++

#include "ModModel.h"
#include "Json.h"
#include "minecraft/MinecraftInstance.h"
#include "minecraft/PackProfile.h"
#include <QMessageBox>
namespace ResourceDownload {
ModModel::ModModel(BaseInstance const& base_inst, ResourceAPI* api) : ResourceModel(base_inst, api) {}
/******** Make data requests ********/
ResourceAPI::SearchArgs ModModel::createSearchArguments()
{
auto profile = static_cast<MinecraftInstance const&>(m_base_instance).getPackProfile();
Q_ASSERT(profile);
Q_ASSERT(m_filter);
std::optional<std::list<Version>> versions{};
std::optional<ResourceAPI::SortingMethod> sort{};
{ // Version filter
if (!m_filter->versions.empty())
versions = m_filter->versions;
}
{ // Sorting method
auto sorting_methods = getSortingMethods();
auto method = std::find_if(sorting_methods.begin(), sorting_methods.end(),
[this](auto const& e) { return m_current_sort_index == e.index; });
if (method != sorting_methods.end())
sort = *method;
}
return { ModPlatform::ResourceType::MOD, m_next_search_offset, m_search_term, sort, profile->getModLoaders(), versions };
}
ResourceAPI::SearchCallbacks ModModel::createSearchCallbacks()
{
return { [this](auto& doc) {
if (!s_running_models.constFind(this).value())
return;
searchRequestFinished(doc);
} };
}
ResourceAPI::VersionSearchArgs ModModel::createVersionsArguments(QModelIndex& entry)
{
auto& pack = m_packs[entry.row()];
auto profile = static_cast<MinecraftInstance const&>(m_base_instance).getPackProfile();
Q_ASSERT(profile);
Q_ASSERT(m_filter);
std::optional<std::list<Version>> versions{};
if (!m_filter->versions.empty())
versions = m_filter->versions;
return { pack, versions, profile->getModLoaders() };
}
ResourceAPI::VersionSearchCallbacks ModModel::createVersionsCallbacks(QModelIndex& entry)
{
return { [this, entry](auto& doc, auto pack) {
if (!s_running_models.constFind(this).value())
return;
versionRequestSucceeded(doc, pack, entry);
} };
}
ResourceAPI::ProjectInfoArgs ModModel::createInfoArguments(QModelIndex& entry)
{
auto& pack = m_packs[entry.row()];
return { pack };
}
ResourceAPI::ProjectInfoCallbacks ModModel::createInfoCallbacks(QModelIndex& entry)
{
return { [this, entry](auto& doc, auto pack) {
if (!s_running_models.constFind(this).value())
return;
infoRequestFinished(doc, pack, entry);
} };
}
void ModModel::searchWithTerm(const QString& term, unsigned int sort, bool filter_changed)
{
if (m_search_term == term && m_search_term.isNull() == term.isNull() && m_current_sort_index == sort && !filter_changed) {
return;
}
setSearchTerm(term);
m_current_sort_index = sort;
refresh();
}
/******** Request callbacks ********/
void ModModel::searchRequestFinished(QJsonDocument& doc)
{
QList<ModPlatform::IndexedPack> newList;
auto packs = documentToArray(doc);
for (auto packRaw : packs) {
auto packObj = packRaw.toObject();
ModPlatform::IndexedPack pack;
try {
loadIndexedPack(pack, packObj);
newList.append(pack);
} catch (const JSONValidationError& e) {
qWarning() << "Error while loading mod from " << debugName() << ": " << e.cause();
continue;
}
}
if (packs.size() < 25) {
m_search_state = SearchState::Finished;
} else {
m_next_search_offset += 25;
m_search_state = SearchState::CanFetchMore;
}
// When you have a Qt build with assertions turned on, proceeding here will abort the application
if (newList.size() == 0)
return;
beginInsertRows(QModelIndex(), m_packs.size(), m_packs.size() + newList.size() - 1);
m_packs.append(newList);
endInsertRows();
}
void ModModel::infoRequestFinished(QJsonDocument& doc, ModPlatform::IndexedPack& pack, const QModelIndex& index)
{
qDebug() << "Loading mod info";
auto current_pack = data(index, Qt::UserRole).value<ModPlatform::IndexedPack>();
// Check if the index is still valid for this mod or not
if (pack.addonId != current_pack.addonId)
return;
try {
auto obj = Json::requireObject(doc);
loadExtraPackInfo(current_pack, obj);
} catch (const JSONValidationError& e) {
qDebug() << doc;
qWarning() << "Error while reading " << debugName() << " mod info: " << e.cause();
}
// Cache info :^)
QVariant new_pack;
new_pack.setValue(current_pack);
if (!setData(index, new_pack, Qt::UserRole)) {
qWarning() << "Failed to cache mod info!";
return;
}
emit projectInfoUpdated();
}
void ModModel::versionRequestSucceeded(QJsonDocument doc, ModPlatform::IndexedPack& pack, const QModelIndex& index)
{
auto arr = doc.isObject() ? Json::ensureArray(doc.object(), "data") : doc.array();
auto current_pack = data(index, Qt::UserRole).value<ModPlatform::IndexedPack>();
// Check if the index is still valid for this mod or not
if (pack.addonId != current_pack.addonId)
return;
try {
loadIndexedPackVersions(current_pack, arr);
} catch (const JSONValidationError& e) {
qDebug() << doc;
qWarning() << "Error while reading " << debugName() << " mod version: " << e.cause();
}
// Cache info :^)
QVariant new_pack;
new_pack.setValue(current_pack);
if (!setData(index, new_pack, Qt::UserRole)) {
qWarning() << "Failed to cache mod versions!";
return;
}
emit versionListUpdated();
}
} // namespace ResourceDownload