Refactor updating mechanisms to work with all resources

Summary:
- It compiles
- I need to go to bed

Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
This commit is contained in:
TheKodeToad
2024-01-26 02:53:30 +00:00
parent 2c18d0f1a5
commit 97ee0a19b5
18 changed files with 298 additions and 293 deletions

View File

@ -1031,8 +1031,8 @@ SET(LAUNCHER_SOURCES
ui/dialogs/BlockedModsDialog.h
ui/dialogs/ChooseProviderDialog.h
ui/dialogs/ChooseProviderDialog.cpp
ui/dialogs/ModUpdateDialog.cpp
ui/dialogs/ModUpdateDialog.h
ui/dialogs/ResourceUpdateDialog.cpp
ui/dialogs/ResourceUpdateDialog.h
ui/dialogs/InstallLoaderDialog.cpp
ui/dialogs/InstallLoaderDialog.h

View File

@ -89,35 +89,35 @@ QVariant ModFolderModel::data(const QModelIndex& index, int role) const
default:
break;
}
return at(row)->version();
return at(row).version();
}
case DateColumn:
return m_resources[row]->dateTimeChanged();
case ProviderColumn:
return at(row)->provider();
return at(row).provider();
default:
return QVariant();
}
case Qt::ToolTipRole:
if (column == NAME_COLUMN) {
if (at(row)->isSymLinkUnder(instDirPath())) {
if (at(row).isSymLinkUnder(instDirPath())) {
return m_resources[row]->internal_id() +
tr("\nWarning: This resource is symbolically linked from elsewhere. Editing it will also change the original."
"\nCanonical Path: %1")
.arg(at(row)->fileinfo().canonicalFilePath());
.arg(at(row).fileinfo().canonicalFilePath());
}
if (at(row)->isMoreThanOneHardLink()) {
if (at(row).isMoreThanOneHardLink()) {
return m_resources[row]->internal_id() +
tr("\nWarning: This resource is hard linked elsewhere. Editing it will also change the original.");
}
}
return m_resources[row]->internal_id();
case Qt::DecorationRole: {
if (column == NAME_COLUMN && (at(row)->isSymLinkUnder(instDirPath()) || at(row)->isMoreThanOneHardLink()))
if (column == NAME_COLUMN && (at(row).isSymLinkUnder(instDirPath()) || at(row).isMoreThanOneHardLink()))
return APPLICATION->getThemedIcon("status-yellow");
if (column == ImageColumn) {
return at(row)->icon({ 32, 32 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding);
return at(row).icon({ 32, 32 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding);
}
return {};
}
@ -129,7 +129,7 @@ QVariant ModFolderModel::data(const QModelIndex& index, int role) const
case Qt::CheckStateRole:
switch (column) {
case ActiveColumn:
return at(row)->enabled() ? Qt::Checked : Qt::Unchecked;
return at(row).enabled() ? Qt::Checked : Qt::Unchecked;
default:
return QVariant();
}
@ -190,29 +190,6 @@ bool ModFolderModel::isValid()
return m_dir.exists() && m_dir.isReadable();
}
auto ModFolderModel::selectedMods(QModelIndexList& indexes) -> QList<Mod*>
{
QList<Mod*> selected_resources;
for (auto i : indexes) {
if (i.column() != 0)
continue;
selected_resources.push_back(at(i.row()));
}
return selected_resources;
}
auto ModFolderModel::allMods() -> QList<Mod*>
{
QList<Mod*> mods;
for (auto& res : qAsConst(m_resources)) {
mods.append(static_cast<Mod*>(res.get()));
}
return mods;
}
void ModFolderModel::onParseSucceeded(int ticket, QString mod_id)
{
auto iter = m_active_parse_tasks.constFind(ticket);

View File

@ -76,9 +76,6 @@ class ModFolderModel : public ResourceFolderModel {
bool isValid();
auto selectedMods(QModelIndexList& indexes) -> QList<Mod*>;
auto allMods() -> QList<Mod*>;
RESOURCE_HELPERS(Mod)
private slots:

View File

@ -184,7 +184,7 @@ auto Resource::destroy(const QDir& index_dir, bool preserve_metadata, bool attem
return (attempt_trash && FS::trash(m_file_info.filePath())) || FS::deletePath(m_file_info.filePath());
}
auto Resource::destroyMetadata(const QDir& index_dir) -> bool
auto Resource::destroyMetadata(const QDir& index_dir) -> void
{
if (metadata()) {
Metadata::remove(index_dir, metadata()->slug);

View File

@ -101,7 +101,7 @@ class Resource : public QObject {
// Delete all files of this resource.
auto destroy(const QDir& index_dir, bool preserve_metadata = false, bool attempt_trash = true) -> bool;
// Delete the metadata only.
auto destroyMetadata(const QDir& index_dir) -> bool;
auto destroyMetadata(const QDir& index_dir) -> void;
[[nodiscard]] auto isSymLink() const -> bool { return m_file_info.isSymLink(); }

View File

@ -242,10 +242,10 @@ bool ResourceFolderModel::deleteResources(const QModelIndexList& indexes)
return true;
}
bool ResourceFolderModel::deleteMetadata(const QModelIndexList& indexes)
void ResourceFolderModel::deleteMetadata(const QModelIndexList& indexes)
{
if (indexes.isEmpty())
return true;
return;
for (auto i : indexes) {
if (i.column() != 0)
@ -256,8 +256,6 @@ bool ResourceFolderModel::deleteMetadata(const QModelIndexList& indexes)
}
update();
return true;
}
bool ResourceFolderModel::setResourceEnabled(const QModelIndexList& indexes, EnableAction action)

View File

@ -19,6 +19,58 @@
class QSortFilterProxyModel;
/* A macro to define useful functions to handle Resource* -> T* more easily on derived classes */
#define RESOURCE_HELPERS(T) \
[[nodiscard]] T& operator[](int index) \
{ \
return *static_cast<T*>(m_resources[index].get()); \
} \
[[nodiscard]] T& at(int index) \
{ \
return *static_cast<T*>(m_resources[index].get()); \
} \
[[nodiscard]] const T& at(int index) const \
{ \
return *static_cast<const T*>(m_resources.at(index).get()); \
} \
[[nodiscard]] T& first() \
{ \
return *static_cast<T*>(m_resources.first().get()); \
} \
[[nodiscard]] T& last() \
{ \
return *static_cast<T*>(m_resources.last().get()); \
} \
[[nodiscard]] T* find(QString id) \
{ \
auto iter = std::find_if(m_resources.constBegin(), m_resources.constEnd(), \
[&](Resource::Ptr const& r) { return r->internal_id() == id; }); \
if (iter == m_resources.constEnd()) \
return nullptr; \
return static_cast<T*>((*iter).get()); \
} \
QList<T*> selected##T##s(const QModelIndexList& indexes) \
{ \
QList<T*> result; \
for (const QModelIndex& index : indexes) { \
if (index.column() != 0) \
continue; \
\
result.append(&at(index.row())); \
} \
return result; \
} \
QList<T*> all##T##s() \
{ \
QList<T*> result; \
result.reserve(m_resources.size()); \
\
for (const Resource::Ptr& resource : m_resources) \
result.append(static_cast<T*>(resource.get())); \
\
return result; \
}
/** A basic model for external resources.
*
* This model manages a list of resources. As such, external users of such resources do not own them,
@ -69,7 +121,7 @@ class ResourceFolderModel : public QAbstractListModel {
*/
virtual bool uninstallResource(QString file_name, bool preserve_metadata = false);
virtual bool deleteResources(const QModelIndexList&);
virtual bool deleteMetadata(const QModelIndexList&);
virtual void deleteMetadata(const QModelIndexList&);
/** Applies the given 'action' to the resources in 'indexes'.
*
@ -85,9 +137,7 @@ class ResourceFolderModel : public QAbstractListModel {
[[nodiscard]] qsizetype size() const { return m_resources.size(); }
[[nodiscard]] bool empty() const { return size() == 0; }
[[nodiscard]] Resource& at(int index) { return *m_resources.at(index); }
[[nodiscard]] Resource const& at(int index) const { return *m_resources.at(index); }
[[nodiscard]] QList<Resource::Ptr> const& all() const { return m_resources; }
RESOURCE_HELPERS(Resource)
[[nodiscard]] QDir const& dir() const { return m_dir; }
@ -232,37 +282,6 @@ class ResourceFolderModel : public QAbstractListModel {
std::atomic<int> m_next_resolution_ticket = 0;
};
/* A macro to define useful functions to handle Resource* -> T* more easily on derived classes */
#define RESOURCE_HELPERS(T) \
[[nodiscard]] T* operator[](int index) \
{ \
return static_cast<T*>(m_resources[index].get()); \
} \
[[nodiscard]] T* at(int index) \
{ \
return static_cast<T*>(m_resources[index].get()); \
} \
[[nodiscard]] const T* at(int index) const \
{ \
return static_cast<const T*>(m_resources.at(index).get()); \
} \
[[nodiscard]] T* first() \
{ \
return static_cast<T*>(m_resources.first().get()); \
} \
[[nodiscard]] T* last() \
{ \
return static_cast<T*>(m_resources.last().get()); \
} \
[[nodiscard]] T* find(QString id) \
{ \
auto iter = std::find_if(m_resources.constBegin(), m_resources.constEnd(), \
[&](Resource::Ptr const& r) { return r->internal_id() == id; }); \
if (iter == m_resources.constEnd()) \
return nullptr; \
return static_cast<T*>((*iter).get()); \
}
/* Template definition to avoid some code duplication */
template <typename T>
void ResourceFolderModel::applyUpdates(QSet<QString>& current_set, QSet<QString>& new_set, QMap<QString, T>& new_resources)

View File

@ -72,12 +72,12 @@ QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const
case NameColumn:
return m_resources[row]->name();
case PackFormatColumn: {
auto resource = at(row);
auto pack_format = resource->packFormat();
auto& resource = at(row);
auto pack_format = resource.packFormat();
if (pack_format == 0)
return tr("Unrecognized");
auto version_bounds = resource->compatibleVersions();
auto version_bounds = resource.compatibleVersions();
if (version_bounds.first.toString().isEmpty())
return QString::number(pack_format);
@ -92,10 +92,10 @@ QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const
return {};
}
case Qt::DecorationRole: {
if (column == NameColumn && (at(row)->isSymLinkUnder(instDirPath()) || at(row)->isMoreThanOneHardLink()))
if (column == NameColumn && (at(row).isSymLinkUnder(instDirPath()) || at(row).isMoreThanOneHardLink()))
return APPLICATION->getThemedIcon("status-yellow");
if (column == ImageColumn) {
return at(row)->image({ 32, 32 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding);
return at(row).image({ 32, 32 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding);
}
return {};
}
@ -105,14 +105,14 @@ QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const
return tr("The resource pack format ID, as well as the Minecraft versions it was designed for.");
}
if (column == NameColumn) {
if (at(row)->isSymLinkUnder(instDirPath())) {
if (at(row).isSymLinkUnder(instDirPath())) {
return m_resources[row]->internal_id() +
tr("\nWarning: This resource is symbolically linked from elsewhere. Editing it will also change the original."
"\nCanonical Path: %1")
.arg(at(row)->fileinfo().canonicalFilePath());
.arg(at(row).fileinfo().canonicalFilePath());
;
}
if (at(row)->isMoreThanOneHardLink()) {
if (at(row).isMoreThanOneHardLink()) {
return m_resources[row]->internal_id() +
tr("\nWarning: This resource is hard linked elsewhere. Editing it will also change the original.");
}
@ -127,7 +127,7 @@ QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const
case Qt::CheckStateRole:
switch (column) {
case ActiveColumn:
return at(row)->enabled() ? Qt::Checked : Qt::Unchecked;
return at(row).enabled() ? Qt::Checked : Qt::Unchecked;
default:
return {};
}

View File

@ -52,11 +52,6 @@ TexturePackFolderModel::TexturePackFolderModel(const QDir& dir, BaseInstance* in
m_columnsHideable = { false, true, false, true, true };
}
Task* TexturePackFolderModel::createUpdateTask()
{
return new BasicFolderLoadTask(m_dir, [](QFileInfo const& entry) { return makeShared<TexturePack>(entry); });
}
Task* TexturePackFolderModel::createParseTask(Resource& resource)
{
return new LocalTexturePackParseTask(m_next_resolution_ticket, static_cast<TexturePack&>(resource));
@ -84,14 +79,14 @@ QVariant TexturePackFolderModel::data(const QModelIndex& index, int role) const
}
case Qt::ToolTipRole:
if (column == NameColumn) {
if (at(row)->isSymLinkUnder(instDirPath())) {
if (at(row).isSymLinkUnder(instDirPath())) {
return m_resources[row]->internal_id() +
tr("\nWarning: This resource is symbolically linked from elsewhere. Editing it will also change the original."
"\nCanonical Path: %1")
.arg(at(row)->fileinfo().canonicalFilePath());
.arg(at(row).fileinfo().canonicalFilePath());
;
}
if (at(row)->isMoreThanOneHardLink()) {
if (at(row).isMoreThanOneHardLink()) {
return m_resources[row]->internal_id() +
tr("\nWarning: This resource is hard linked elsewhere. Editing it will also change the original.");
}
@ -99,10 +94,10 @@ QVariant TexturePackFolderModel::data(const QModelIndex& index, int role) const
return m_resources[row]->internal_id();
case Qt::DecorationRole: {
if (column == NameColumn && (at(row)->isSymLinkUnder(instDirPath()) || at(row)->isMoreThanOneHardLink()))
if (column == NameColumn && (at(row).isSymLinkUnder(instDirPath()) || at(row).isMoreThanOneHardLink()))
return APPLICATION->getThemedIcon("status-yellow");
if (column == ImageColumn) {
return at(row)->image({ 32, 32 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding);
return at(row).image({ 32, 32 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding);
}
return {};
}

View File

@ -13,13 +13,13 @@ class CheckUpdateTask : public Task {
Q_OBJECT
public:
CheckUpdateTask(QList<Mod*>& mods,
CheckUpdateTask(QList<Resource*>& resources,
std::list<Version>& mcVersions,
std::optional<ModPlatform::ModLoaderTypes> loaders,
std::shared_ptr<ModFolderModel> mods_folder)
: Task(nullptr), m_mods(mods), m_game_versions(mcVersions), m_loaders(loaders), m_mods_folder(mods_folder){};
std::shared_ptr<ResourceFolderModel> resource_model)
: Task(nullptr), m_resources(resources), m_game_versions(mcVersions), m_loaders(loaders), m_resource_model(resource_model){};
struct UpdatableMod {
struct Update {
QString name;
QString old_hash;
QString old_version;
@ -30,7 +30,7 @@ class CheckUpdateTask : public Task {
shared_qobject_ptr<ResourceDownloadTask> download;
public:
UpdatableMod(QString name,
Update(QString name,
QString old_h,
QString old_v,
QString new_v,
@ -49,7 +49,7 @@ class CheckUpdateTask : public Task {
{}
};
auto getUpdatable() -> std::vector<UpdatableMod>&& { return std::move(m_updatable); }
auto getUpdates() -> std::vector<Update>&& { return std::move(m_updates); }
auto getDependencies() -> QList<std::shared_ptr<GetModDependenciesTask::PackDependency>>&& { return std::move(m_deps); }
public slots:
@ -59,14 +59,14 @@ class CheckUpdateTask : public Task {
void executeTask() override = 0;
signals:
void checkFailed(Mod* failed, QString reason, QUrl recover_url = {});
void checkFailed(Resource* failed, QString reason, QUrl recover_url = {});
protected:
QList<Mod*>& m_mods;
QList<Resource*>& m_resources;
std::list<Version>& m_game_versions;
std::optional<ModPlatform::ModLoaderTypes> m_loaders;
std::shared_ptr<ModFolderModel> m_mods_folder;
std::shared_ptr<ResourceFolderModel> m_resource_model;
std::vector<UpdatableMod> m_updatable;
std::vector<Update> m_updates;
QList<std::shared_ptr<GetModDependenciesTask::PackDependency>> m_deps;
};

View File

@ -120,19 +120,19 @@ ModPlatform::IndexedVersion FlameCheckUpdate::getFileInfo(int addonId, int fileI
* */
void FlameCheckUpdate::executeTask()
{
setStatus(tr("Preparing mods for CurseForge..."));
setStatus(tr("Preparing resources for CurseForge..."));
int i = 0;
for (auto* mod : m_mods) {
if (!mod->enabled()) {
emit checkFailed(mod, tr("Disabled mods won't be updated, to prevent mod duplication issues!"));
for (auto* resource : m_resources) {
if (!resource->enabled()) {
emit checkFailed(resource, tr("Disabled resources won't be updated, to prevent resource duplication issues!"));
continue;
}
setStatus(tr("Getting API response from CurseForge for '%1'...").arg(mod->name()));
setProgress(i++, m_mods.size());
setStatus(tr("Getting API response from CurseForge for '%1'...").arg(resource->name()));
setProgress(i++, m_resources.size());
auto latest_ver = api.getLatestVersion({ { mod->metadata()->project_id.toString() }, m_game_versions, m_loaders });
auto latest_ver = api.getLatestVersion({ { resource->metadata()->project_id.toString() }, m_game_versions, m_loaders });
// Check if we were aborted while getting the latest version
if (m_was_aborted) {
@ -140,41 +140,48 @@ void FlameCheckUpdate::executeTask()
return;
}
setStatus(tr("Parsing the API response from CurseForge for '%1'...").arg(mod->name()));
setStatus(tr("Parsing the API response from CurseForge for '%1'...").arg(resource->name()));
if (!latest_ver.addonId.isValid()) {
emit checkFailed(mod, tr("No valid version found for this mod. It's probably unavailable for the current game "
"version / mod loader."));
QString reason;
if (dynamic_cast<Mod*>(resource) != nullptr)
reason =
tr("No valid version found for this resource. It's probably unavailable for the current game "
"version / mod loader.");
else
reason = tr("No valid version found for this resource. It's probably unavailable for the current game version.");
emit checkFailed(resource, reason);
continue;
}
if (latest_ver.downloadUrl.isEmpty() && latest_ver.fileId != mod->metadata()->file_id) {
if (latest_ver.downloadUrl.isEmpty() && latest_ver.fileId != resource->metadata()->file_id) {
auto pack = getProjectInfo(latest_ver);
auto recover_url = QString("%1/download/%2").arg(pack.websiteUrl, latest_ver.fileId.toString());
emit checkFailed(mod, tr("Mod has a new update available, but is not downloadable using CurseForge."), recover_url);
emit checkFailed(resource, tr("Resource has a new update available, but is not downloadable using CurseForge."), recover_url);
continue;
}
// Fake pack with the necessary info to pass to the download task :)
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->name = resource->name();
pack->slug = resource->metadata()->slug;
pack->addonId = resource->metadata()->project_id;
pack->provider = ModPlatform::ResourceProvider::FLAME;
if (!latest_ver.hash.isEmpty() && (mod->metadata()->hash != latest_ver.hash || mod->status() == ResourceStatus::NOT_INSTALLED)) {
auto old_version = mod->version();
if (old_version.isEmpty() && mod->status() != ResourceStatus::NOT_INSTALLED) {
auto current_ver = getFileInfo(latest_ver.addonId.toInt(), mod->metadata()->file_id.toInt());
old_version = current_ver.version;
if (!latest_ver.hash.isEmpty() &&
(resource->metadata()->hash != latest_ver.hash || resource->status() == ResourceStatus::NOT_INSTALLED)) {
auto download_task = makeShared<ResourceDownloadTask>(pack, latest_ver, m_resource_model);
QString old_version = resource->metadata()->version_number;
if (old_version.isEmpty()) {
if (resource->status() == ResourceStatus::NOT_INSTALLED)
old_version = tr("Not installed");
else
old_version = tr("Unknown");
}
auto download_task = makeShared<ResourceDownloadTask>(pack, latest_ver, m_mods_folder);
m_updatable.emplace_back(pack->name, mod->metadata()->hash, old_version, latest_ver.version, latest_ver.version_type,
m_updates.emplace_back(pack->name, resource->metadata()->hash, old_version, latest_ver.version, latest_ver.version_type,
api.getModFileChangelog(latest_ver.addonId.toInt(), latest_ver.fileId.toInt()),
ModPlatform::ResourceProvider::FLAME, download_task);
}

View File

@ -8,11 +8,11 @@ class FlameCheckUpdate : public CheckUpdateTask {
Q_OBJECT
public:
FlameCheckUpdate(QList<Mod*>& mods,
FlameCheckUpdate(QList<Resource*>& resources,
std::list<Version>& mcVersions,
std::optional<ModPlatform::ModLoaderTypes> loaders,
std::shared_ptr<ModFolderModel> mods_folder)
: CheckUpdateTask(mods, mcVersions, loaders, mods_folder)
std::shared_ptr<ResourceFolderModel> resource_model)
: CheckUpdateTask(resources, mcVersions, loaders, resource_model)
{}
public slots:

View File

@ -29,38 +29,38 @@ bool ModrinthCheckUpdate::abort()
* */
void ModrinthCheckUpdate::executeTask()
{
setStatus(tr("Preparing mods for Modrinth..."));
setStatus(tr("Preparing resources for Modrinth..."));
setProgress(0, 3);
QHash<QString, Mod*> mappings;
QHash<QString, Resource*> mappings;
// Create all hashes
QStringList hashes;
auto best_hash_type = ProviderCaps.hashType(ModPlatform::ResourceProvider::MODRINTH).first();
ConcurrentTask hashing_task(this, "MakeModrinthHashesTask", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt());
for (auto* mod : m_mods) {
if (!mod->enabled()) {
emit checkFailed(mod, tr("Disabled mods won't be updated, to prevent mod duplication issues!"));
for (auto* resource : m_resources) {
if (!resource->enabled()) {
emit checkFailed(resource, tr("Disabled resources won't be updated, to prevent resource duplication issues!"));
continue;
}
auto hash = mod->metadata()->hash;
auto hash = resource->metadata()->hash;
// Sadly the API can only handle one hash type per call, se we
// need to generate a new hash if the current one is innadequate
// (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(), &Hashing::Hasher::resultsReady, [&hashes, &mappings, mod](QString hash) {
if (resource->metadata()->hash_format != best_hash_type) {
auto hash_task = Hashing::createModrinthHasher(resource->fileinfo().absoluteFilePath());
connect(hash_task.get(), &Hashing::Hasher::resultsReady, [&hashes, &mappings, resource](QString hash) {
hashes.append(hash);
mappings.insert(hash, mod);
mappings.insert(hash, resource);
});
connect(hash_task.get(), &Task::failed, [this] { failed("Failed to generate hash"); });
hashing_task.addTask(hash_task);
} else {
hashes.append(hash);
mappings.insert(hash, mod);
mappings.insert(hash, resource);
}
}
@ -88,19 +88,26 @@ void ModrinthCheckUpdate::executeTask()
setProgress(2, 3);
try {
for (auto hash : mappings.keys()) {
for (auto iter = mappings.begin(); iter != mappings.end(); iter++) {
const QString& hash = iter.key();
Resource* resource = iter.value();
auto project_obj = doc[hash].toObject();
// If the returned project is empty, but we have Modrinth metadata,
// it means this specific version is not available
if (project_obj.isEmpty()) {
qDebug() << "Mod " << mappings.find(hash).value()->name() << " got an empty response.";
qDebug() << "Resource " << resource->name() << " got an empty response.";
qDebug() << "Hash: " << hash;
emit checkFailed(
mappings.find(hash).value(),
tr("No valid version found for this mod. It's probably unavailable for the current game version / mod loader."));
QString reason;
if (dynamic_cast<Mod*>(resource) != nullptr)
reason =
tr("No valid version found for this resource. It's probably unavailable for the current game "
"version / mod loader.");
else
reason = tr("No valid version found for this resource. It's probably unavailable for the current game version.");
emit checkFailed(resource, reason);
continue;
}
@ -109,7 +116,8 @@ void ModrinthCheckUpdate::executeTask()
QString loader_filter;
if (m_loaders.has_value()) {
static auto flags = { ModPlatform::ModLoaderType::NeoForge, ModPlatform::ModLoaderType::Forge,
ModPlatform::ModLoaderType::Fabric, ModPlatform::ModLoaderType::Quilt };
ModPlatform::ModLoaderType::Fabric, ModPlatform::ModLoaderType::Quilt,
ModPlatform::ModLoaderType::LiteLoader };
for (auto flag : flags) {
if (m_loaders.value().testFlag(flag)) {
loader_filter = ModPlatform::getModLoaderString(flag);
@ -126,46 +134,44 @@ void ModrinthCheckUpdate::executeTask()
auto project_ver = Modrinth::loadIndexedPackVersion(project_obj, best_hash_type, loader_filter);
if (project_ver.downloadUrl.isEmpty()) {
qCritical() << "Modrinth mod without download url!";
qCritical() << "Modrinth resource without download url!";
qCritical() << project_ver.fileName;
emit checkFailed(mappings.find(hash).value(), tr("Mod has an empty download URL"));
emit checkFailed(mappings.find(hash).value(), tr("Resource has an empty download URL"));
continue;
}
auto mod_iter = mappings.find(hash);
if (mod_iter == mappings.end()) {
qCritical() << "Failed to remap mod from Modrinth!";
auto resource_iter = mappings.find(hash);
if (resource_iter == mappings.end()) {
qCritical() << "Failed to remap resource from Modrinth!";
continue;
}
auto mod = *mod_iter;
auto key = project_ver.hash;
// Fake pack with the necessary info to pass to the download task :)
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->name = resource->name();
pack->slug = resource->metadata()->slug;
pack->addonId = resource->metadata()->project_id;
pack->provider = ModPlatform::ResourceProvider::MODRINTH;
if ((key != hash && project_ver.is_preferred) || (mod->status() == ResourceStatus::NOT_INSTALLED)) {
if (mod->version() == project_ver.version_number)
continue;
if ((project_ver.hash != hash && project_ver.is_preferred) || (resource->status() == ResourceStatus::NOT_INSTALLED)) {
auto download_task = makeShared<ResourceDownloadTask>(pack, project_ver, m_resource_model);
auto download_task = makeShared<ResourceDownloadTask>(pack, project_ver, m_mods_folder);
QString old_version = resource->metadata()->version_number;
if (old_version.isEmpty()) {
if (resource->status() == ResourceStatus::NOT_INSTALLED)
old_version = tr("Not installed");
else
old_version = tr("Unknown");
}
m_updatable.emplace_back(pack->name, hash, mod->version(), project_ver.version_number, project_ver.version_type,
m_updates.emplace_back(pack->name, hash, old_version, project_ver.version_number, project_ver.version_type,
project_ver.changelog, ModPlatform::ResourceProvider::MODRINTH, download_task);
}
m_deps.append(std::make_shared<GetModDependenciesTask::PackDependency>(pack, project_ver));
}
} catch (Json::JsonException& e) {
emitFailed(e.cause() + " : " + e.what());
emitFailed(e.cause() + ": " + e.what());
return;
}
emitSucceeded();

View File

@ -8,11 +8,11 @@ class ModrinthCheckUpdate : public CheckUpdateTask {
Q_OBJECT
public:
ModrinthCheckUpdate(QList<Mod*>& mods,
ModrinthCheckUpdate(QList<Resource*>& resources,
std::list<Version>& mcVersions,
std::optional<ModPlatform::ModLoaderTypes> loaders,
std::shared_ptr<ModFolderModel> mods_folder)
: CheckUpdateTask(mods, mcVersions, loaders, mods_folder)
std::shared_ptr<ResourceFolderModel> resource_model)
: CheckUpdateTask(resources, mcVersions, loaders, resource_model)
{}
public slots:

View File

@ -297,18 +297,20 @@ auto V1::getIndexForMod(const QDir& index_dir, QString slug) -> Mod
{ // [update] info
using Provider = ModPlatform::ResourceProvider;
auto update_table = table["update"];
if (!update_table || !update_table.is_table()) {
auto update_table = table["update"].as_table();
if (!update_table) {
qCritical() << QString("No [update] section found on mod metadata!");
return {};
}
mod.version_number = stringEntry(*update_table, "x-prismlauncher-version-number");
toml::table* mod_provider_table = nullptr;
if ((mod_provider_table = update_table[ProviderCaps.name(Provider::FLAME)].as_table())) {
if ((mod_provider_table = (*update_table)[ProviderCaps.name(Provider::FLAME)].as_table())) {
mod.provider = Provider::FLAME;
mod.file_id = intEntry(*mod_provider_table, "file-id");
mod.project_id = intEntry(*mod_provider_table, "project-id");
} else if ((mod_provider_table = update_table[ProviderCaps.name(Provider::MODRINTH)].as_table())) {
} else if ((mod_provider_table = (*update_table)[ProviderCaps.name(Provider::MODRINTH)].as_table())) {
mod.provider = Provider::MODRINTH;
mod.mod_id() = stringEntry(*mod_provider_table, "mod-id");
mod.version() = stringEntry(*mod_provider_table, "version");

View File

@ -1,4 +1,4 @@
#include "ModUpdateDialog.h"
#include "ResourceUpdateDialog.h"
#include "ChooseProviderDialog.h"
#include "CustomMessageBox.h"
#include "ProgressDialog.h"
@ -36,14 +36,14 @@ static std::optional<ModPlatform::ModLoaderTypes> mcLoaders(BaseInstance* inst)
return { static_cast<MinecraftInstance*>(inst)->getPackProfile()->getSupportedModLoaders() };
}
ModUpdateDialog::ModUpdateDialog(QWidget* parent,
ResourceUpdateDialog::ResourceUpdateDialog(QWidget* parent,
BaseInstance* instance,
const std::shared_ptr<ModFolderModel> mods,
QList<Mod*>& search_for,
const std::shared_ptr<ResourceFolderModel> resource_model,
QList<Resource*>& search_for,
bool includeDeps)
: ReviewMessageBox(parent, tr("Confirm mods to update"), "")
, m_parent(parent)
, m_mod_model(mods)
, m_resource_model(resource_model)
, m_candidates(search_for)
, m_second_try_metadata(
new ConcurrentTask(nullptr, "Second Metadata Search", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt()))
@ -56,7 +56,7 @@ ModUpdateDialog::ModUpdateDialog(QWidget* parent,
ui->onlyCheckedLabel->setText(tr("Only mods with a check will be updated!"));
}
void ModUpdateDialog::checkCandidates()
void ResourceUpdateDialog::checkCandidates()
{
// Ensure mods have valid metadata
auto went_well = ensureMetadata();
@ -92,17 +92,19 @@ void ModUpdateDialog::checkCandidates()
SequentialTask check_task(m_parent, tr("Checking for updates"));
if (!m_modrinth_to_update.empty()) {
m_modrinth_check_task.reset(new ModrinthCheckUpdate(m_modrinth_to_update, versions, loaders, m_mod_model));
connect(m_modrinth_check_task.get(), &CheckUpdateTask::checkFailed, this, [this](Mod* mod, QString reason, QUrl recover_url) {
m_failed_check_update.append({ mod, reason, recover_url });
m_modrinth_check_task.reset(new ModrinthCheckUpdate(m_modrinth_to_update, versions, loaders, m_resource_model));
connect(m_modrinth_check_task.get(), &CheckUpdateTask::checkFailed, this,
[this](Resource* resource, QString reason, QUrl recover_url) {
m_failed_check_update.append({ resource, reason, recover_url });
});
check_task.addTask(m_modrinth_check_task);
}
if (!m_flame_to_update.empty()) {
m_flame_check_task.reset(new FlameCheckUpdate(m_flame_to_update, versions, loaders, m_mod_model));
connect(m_flame_check_task.get(), &CheckUpdateTask::checkFailed, this, [this](Mod* mod, QString reason, QUrl recover_url) {
m_failed_check_update.append({ mod, reason, recover_url });
m_flame_check_task.reset(new FlameCheckUpdate(m_flame_to_update, versions, loaders, m_resource_model));
connect(m_flame_check_task.get(), &CheckUpdateTask::checkFailed, this,
[this](Resource* resource, QString reason, QUrl recover_url) {
m_failed_check_update.append({ resource, reason, recover_url });
});
check_task.addTask(m_flame_check_task);
}
@ -134,11 +136,11 @@ void ModUpdateDialog::checkCandidates()
// Add found updates for Modrinth
if (m_modrinth_check_task) {
auto modrinth_updates = m_modrinth_check_task->getUpdatable();
auto modrinth_updates = m_modrinth_check_task->getUpdates();
for (auto& updatable : modrinth_updates) {
qDebug() << QString("Mod %1 has an update available!").arg(updatable.name);
appendMod(updatable);
appendResource(updatable);
m_tasks.insert(updatable.name, updatable.download);
}
selectedVers.append(m_modrinth_check_task->getDependencies());
@ -146,11 +148,11 @@ void ModUpdateDialog::checkCandidates()
// Add found updated for Flame
if (m_flame_check_task) {
auto flame_updates = m_flame_check_task->getUpdatable();
auto flame_updates = m_flame_check_task->getUpdates();
for (auto& updatable : flame_updates) {
qDebug() << QString("Mod %1 has an update available!").arg(updatable.name);
appendMod(updatable);
appendResource(updatable);
m_tasks.insert(updatable.name, updatable.download);
}
selectedVers.append(m_flame_check_task->getDependencies());
@ -189,10 +191,13 @@ void ModUpdateDialog::checkCandidates()
}
if (m_include_deps && !APPLICATION->settings()->get("ModDependenciesDisabled").toBool()) { // dependencies
auto depTask = makeShared<GetModDependenciesTask>(this, m_instance, m_mod_model.get(), selectedVers);
auto* mod_model = dynamic_cast<ModFolderModel*>(m_resource_model.get());
if (mod_model != nullptr) {
auto depTask = makeShared<GetModDependenciesTask>(this, m_instance, mod_model, selectedVers);
connect(depTask.get(), &Task::failed, this,
[&](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); });
[&](const QString& reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); });
connect(depTask.get(), &Task::succeeded, this, [&]() {
QStringList warnings = depTask->warnings();
@ -216,22 +221,23 @@ void ModUpdateDialog::checkCandidates()
auto getRequiredBy = depTask->getRequiredBy();
for (auto dep : depTask->getDependecies()) {
for (const auto& dep : depTask->getDependecies()) {
auto changelog = dep->version.changelog;
if (dep->pack->provider == ModPlatform::ResourceProvider::FLAME)
changelog = api.getModFileChangelog(dep->version.addonId.toInt(), dep->version.fileId.toInt());
auto download_task = makeShared<ResourceDownloadTask>(dep->pack, dep->version, m_mod_model);
CheckUpdateTask::UpdatableMod updatable = {
dep->pack->name, dep->version.hash, "", dep->version.version, dep->version.version_type,
auto download_task = makeShared<ResourceDownloadTask>(dep->pack, dep->version, m_resource_model);
CheckUpdateTask::Update updatable = {
dep->pack->name, dep->version.hash, tr("Not installed"), dep->version.version, dep->version.version_type,
changelog, dep->pack->provider, download_task
};
appendMod(updatable, getRequiredBy.value(dep->version.addonId.toString()));
appendResource(updatable, getRequiredBy.value(dep->version.addonId.toString()));
m_tasks.insert(updatable.name, updatable.download);
}
}
}
// If there's no mod to be updated
// If there's no resource to be updated
if (ui->modTreeWidget->topLevelItemCount() == 0) {
m_no_updates = true;
} else {
@ -253,7 +259,7 @@ void ModUpdateDialog::checkCandidates()
}
// Part 1: Ensure we have a valid metadata
auto ModUpdateDialog::ensureMetadata() -> bool
auto ResourceUpdateDialog::ensureMetadata() -> bool
{
auto index_dir = indexDir();
@ -261,21 +267,21 @@ auto ModUpdateDialog::ensureMetadata() -> bool
// A better use of data structures here could remove the need for this QHash
QHash<QString, bool> should_try_others;
QList<Mod*> modrinth_tmp;
QList<Mod*> flame_tmp;
QList<Resource*> modrinth_tmp;
QList<Resource*> flame_tmp;
bool confirm_rest = false;
bool try_others_rest = false;
bool skip_rest = false;
ModPlatform::ResourceProvider provider_rest = ModPlatform::ResourceProvider::MODRINTH;
auto addToTmp = [&](Mod* m, ModPlatform::ResourceProvider p) {
auto addToTmp = [&](Resource* resource, ModPlatform::ResourceProvider p) {
switch (p) {
case ModPlatform::ResourceProvider::MODRINTH:
modrinth_tmp.push_back(m);
modrinth_tmp.push_back(resource);
break;
case ModPlatform::ResourceProvider::FLAME:
flame_tmp.push_back(m);
flame_tmp.push_back(resource);
break;
}
};
@ -300,7 +306,7 @@ auto ModUpdateDialog::ensureMetadata() -> bool
}
ChooseProviderDialog chooser(this);
chooser.setDescription(tr("The mod '%1' does not have a metadata yet. We need to generate it in order to track relevant "
chooser.setDescription(tr("The resource '%1' does not have a metadata yet. We need to generate it in order to track relevant "
"information on how to update this mod. "
"To do this, please select a mod provider which we can use to check for updates for this mod.")
.arg(candidate->name()));
@ -324,8 +330,8 @@ auto ModUpdateDialog::ensureMetadata() -> bool
if (!modrinth_tmp.empty()) {
auto modrinth_task = makeShared<EnsureMetadataTask>(modrinth_tmp, index_dir, ModPlatform::ResourceProvider::MODRINTH);
connect(modrinth_task.get(), &EnsureMetadataTask::metadataReady, [this](Mod* candidate) { onMetadataEnsured(candidate); });
connect(modrinth_task.get(), &EnsureMetadataTask::metadataFailed, [this, &should_try_others](Mod* candidate) {
connect(modrinth_task.get(), &EnsureMetadataTask::metadataReady, [this](Resource* candidate) { onMetadataEnsured(candidate); });
connect(modrinth_task.get(), &EnsureMetadataTask::metadataFailed, [this, &should_try_others](Resource* candidate) {
onMetadataFailed(candidate, should_try_others.find(candidate->internal_id()).value(), ModPlatform::ResourceProvider::MODRINTH);
});
connect(modrinth_task.get(), &EnsureMetadataTask::failed,
@ -339,8 +345,8 @@ auto ModUpdateDialog::ensureMetadata() -> bool
if (!flame_tmp.empty()) {
auto flame_task = makeShared<EnsureMetadataTask>(flame_tmp, index_dir, ModPlatform::ResourceProvider::FLAME);
connect(flame_task.get(), &EnsureMetadataTask::metadataReady, [this](Mod* candidate) { onMetadataEnsured(candidate); });
connect(flame_task.get(), &EnsureMetadataTask::metadataFailed, [this, &should_try_others](Mod* candidate) {
connect(flame_task.get(), &EnsureMetadataTask::metadataReady, [this](Resource* candidate) { onMetadataEnsured(candidate); });
connect(flame_task.get(), &EnsureMetadataTask::metadataFailed, [this, &should_try_others](Resource* candidate) {
onMetadataFailed(candidate, should_try_others.find(candidate->internal_id()).value(), ModPlatform::ResourceProvider::FLAME);
});
connect(flame_task.get(), &EnsureMetadataTask::failed,
@ -362,18 +368,18 @@ auto ModUpdateDialog::ensureMetadata() -> bool
return (ret_metadata != QDialog::DialogCode::Rejected);
}
void ModUpdateDialog::onMetadataEnsured(Mod* mod)
void ResourceUpdateDialog::onMetadataEnsured(Resource* resource)
{
// When the mod is a folder, for instance
if (!mod->metadata())
if (!resource->metadata())
return;
switch (mod->metadata()->provider) {
switch (resource->metadata()->provider) {
case ModPlatform::ResourceProvider::MODRINTH:
m_modrinth_to_update.push_back(mod);
m_modrinth_to_update.push_back(resource);
break;
case ModPlatform::ResourceProvider::FLAME:
m_flame_to_update.push_back(mod);
m_flame_to_update.push_back(resource);
break;
}
}
@ -390,26 +396,26 @@ ModPlatform::ResourceProvider next(ModPlatform::ResourceProvider p)
return ModPlatform::ResourceProvider::FLAME;
}
void ModUpdateDialog::onMetadataFailed(Mod* mod, bool try_others, ModPlatform::ResourceProvider first_choice)
void ResourceUpdateDialog::onMetadataFailed(Resource* resource, bool try_others, ModPlatform::ResourceProvider first_choice)
{
if (try_others) {
auto index_dir = indexDir();
auto task = makeShared<EnsureMetadataTask>(mod, index_dir, next(first_choice));
connect(task.get(), &EnsureMetadataTask::metadataReady, [this](Mod* candidate) { onMetadataEnsured(candidate); });
connect(task.get(), &EnsureMetadataTask::metadataFailed, [this](Mod* candidate) { onMetadataFailed(candidate, false); });
auto task = makeShared<EnsureMetadataTask>(resource, index_dir, next(first_choice));
connect(task.get(), &EnsureMetadataTask::metadataReady, [this](Resource* candidate) { onMetadataEnsured(candidate); });
connect(task.get(), &EnsureMetadataTask::metadataFailed, [this](Resource* candidate) { onMetadataFailed(candidate, false); });
connect(task.get(), &EnsureMetadataTask::failed,
[this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); });
[this](const QString& reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); });
m_second_try_metadata->addTask(task);
} else {
QString reason{ tr("Couldn't find a valid version on the selected mod provider(s)") };
m_failed_metadata.append({ mod, reason });
m_failed_metadata.append({ resource, reason });
}
}
void ModUpdateDialog::appendMod(CheckUpdateTask::UpdatableMod const& info, QStringList requiredBy)
void ResourceUpdateDialog::appendResource(CheckUpdateTask::Update const& info, QStringList requiredBy)
{
auto item_top = new QTreeWidgetItem(ui->modTreeWidget);
item_top->setCheckState(0, Qt::CheckState::Checked);
@ -420,7 +426,7 @@ void ModUpdateDialog::appendMod(CheckUpdateTask::UpdatableMod const& info, QStri
provider_item->setText(0, tr("Provider: %1").arg(ProviderCaps.readableName(info.provider)));
auto old_version_item = new QTreeWidgetItem(item_top);
old_version_item->setText(0, tr("Old version: %1").arg(info.old_version.isEmpty() ? tr("Not installed") : info.old_version));
old_version_item->setText(0, tr("Old version: %1").arg(info.old_version));
auto new_version_item = new QTreeWidgetItem(item_top);
new_version_item->setText(0, tr("New version: %1").arg(info.new_version));
@ -474,7 +480,7 @@ void ModUpdateDialog::appendMod(CheckUpdateTask::UpdatableMod const& info, QStri
ui->modTreeWidget->addTopLevelItem(item_top);
}
auto ModUpdateDialog::getTasks() -> const QList<ResourceDownloadTask::Ptr>
auto ResourceUpdateDialog::getTasks() -> const QList<ResourceDownloadTask::Ptr>
{
QList<ResourceDownloadTask::Ptr> list;

View File

@ -13,22 +13,21 @@ class ModrinthCheckUpdate;
class FlameCheckUpdate;
class ConcurrentTask;
class ModUpdateDialog final : public ReviewMessageBox {
class ResourceUpdateDialog final : public ReviewMessageBox {
Q_OBJECT
public:
explicit ModUpdateDialog(QWidget* parent, BaseInstance* instance, std::shared_ptr<ModFolderModel> mod_model, QList<Mod*>& search_for);
explicit ModUpdateDialog(QWidget* parent,
explicit ResourceUpdateDialog(QWidget* parent,
BaseInstance* instance,
std::shared_ptr<ModFolderModel> mod_model,
QList<Mod*>& search_for,
std::shared_ptr<ResourceFolderModel> resource_model,
QList<Resource*>& search_for,
bool includeDeps);
void checkCandidates();
void appendMod(const CheckUpdateTask::UpdatableMod& info, QStringList requiredBy = {});
void appendResource(const CheckUpdateTask::Update& info, QStringList requiredBy = {});
const QList<ResourceDownloadTask::Ptr> getTasks();
auto indexDir() const -> QDir { return m_mod_model->indexDir(); }
auto indexDir() const -> QDir { return m_resource_model->indexDir(); }
auto noUpdates() const -> bool { return m_no_updates; };
auto aborted() const -> bool { return m_aborted; };
@ -37,8 +36,8 @@ class ModUpdateDialog final : public ReviewMessageBox {
auto ensureMetadata() -> bool;
private slots:
void onMetadataEnsured(Mod*);
void onMetadataFailed(Mod*,
void onMetadataEnsured(Resource* resource);
void onMetadataFailed(Resource* resource,
bool try_others = false,
ModPlatform::ResourceProvider first_choice = ModPlatform::ResourceProvider::MODRINTH);
@ -48,15 +47,15 @@ class ModUpdateDialog final : public ReviewMessageBox {
shared_qobject_ptr<ModrinthCheckUpdate> m_modrinth_check_task;
shared_qobject_ptr<FlameCheckUpdate> m_flame_check_task;
const std::shared_ptr<ModFolderModel> m_mod_model;
const std::shared_ptr<ResourceFolderModel> m_resource_model;
QList<Mod*>& m_candidates;
QList<Mod*> m_modrinth_to_update;
QList<Mod*> m_flame_to_update;
QList<Resource*>& m_candidates;
QList<Resource*> m_modrinth_to_update;
QList<Resource*> m_flame_to_update;
ConcurrentTask::Ptr m_second_try_metadata;
QList<std::tuple<Mod*, QString>> m_failed_metadata;
QList<std::tuple<Mod*, QString, QUrl>> m_failed_check_update;
QList<std::tuple<Resource*, QString>> m_failed_metadata;
QList<std::tuple<Resource*, QString, QUrl>> m_failed_check_update;
QHash<QString, ResourceDownloadTask::Ptr> m_tasks;
BaseInstance* m_instance;

View File

@ -51,8 +51,8 @@
#include "ui/GuiUtil.h"
#include "ui/dialogs/CustomMessageBox.h"
#include "ui/dialogs/ModUpdateDialog.h"
#include "ui/dialogs/ResourceDownloadDialog.h"
#include "ui/dialogs/ResourceUpdateDialog.h"
#include "DesktopServices.h"
@ -161,9 +161,8 @@ bool ModFolderPage::onSelectionChanged(const QModelIndex& current, [[maybe_unuse
{
auto sourceCurrent = m_filterModel->mapToSource(current);
int row = sourceCurrent.row();
Mod const* m = m_model->at(row);
if (m)
ui->frame->updateWithMod(*m);
const Mod& mod = m_model->at(row);
ui->frame->updateWithMod(mod);
return true;
}
@ -253,12 +252,12 @@ void ModFolderPage::updateMods(bool includeDeps)
}
auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes();
auto mods_list = m_model->selectedMods(selection);
auto mods_list = m_model->selectedResources(selection);
bool use_all = mods_list.empty();
if (use_all)
mods_list = m_model->allMods();
mods_list = m_model->allResources();
ModUpdateDialog update_dialog(this, m_instance, m_model, mods_list, includeDeps);
ResourceUpdateDialog update_dialog(this, m_instance, m_model, mods_list, includeDeps);
update_dialog.checkCandidates();
if (update_dialog.aborted()) {