This commit is contained in:
Trial97 2024-10-30 19:35:25 +02:00
commit 0e80aae1b8
No known key found for this signature in database
GPG Key ID: 55EF5DA53DB36318
69 changed files with 1868 additions and 1376 deletions

View File

@ -242,6 +242,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
{ { "s", "server" }, "Join the specified server on launch (only valid in combination with --launch)", "address" }, { { "s", "server" }, "Join the specified server on launch (only valid in combination with --launch)", "address" },
{ { "w", "world" }, "Join the specified world on launch (only valid in combination with --launch)", "world" }, { { "w", "world" }, "Join the specified world on launch (only valid in combination with --launch)", "world" },
{ { "a", "profile" }, "Use the account specified by its profile name (only valid in combination with --launch)", "profile" }, { { "a", "profile" }, "Use the account specified by its profile name (only valid in combination with --launch)", "profile" },
{ { "o", "offline" }, "Launch offline, with given player name (only valid in combination with --launch)", "offline" },
{ "alive", "Write a small '" + liveCheckFile + "' file after the launcher starts" }, { "alive", "Write a small '" + liveCheckFile + "' file after the launcher starts" },
{ { "I", "import" }, "Import instance or resource from specified local path or URL", "url" }, { { "I", "import" }, "Import instance or resource from specified local path or URL", "url" },
{ "show", "Opens the window for the specified instance (by instance ID)", "show" } }); { "show", "Opens the window for the specified instance (by instance ID)", "show" } });
@ -257,6 +258,10 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
m_serverToJoin = parser.value("server"); m_serverToJoin = parser.value("server");
m_worldToJoin = parser.value("world"); m_worldToJoin = parser.value("world");
m_profileToUse = parser.value("profile"); m_profileToUse = parser.value("profile");
if (parser.isSet("offline")) {
m_offline = true;
m_offlineName = parser.value("offline");
}
m_liveCheck = parser.isSet("alive"); m_liveCheck = parser.isSet("alive");
m_instanceIdToShowWindowOf = parser.value("show"); m_instanceIdToShowWindowOf = parser.value("show");
@ -271,8 +276,9 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
} }
// error if --launch is missing with --server or --profile // error if --launch is missing with --server or --profile
if (((!m_serverToJoin.isEmpty() || !m_worldToJoin.isEmpty()) || !m_profileToUse.isEmpty()) && m_instanceIdToLaunch.isEmpty()) { if ((!m_serverToJoin.isEmpty() || !m_worldToJoin.isEmpty() || !m_profileToUse.isEmpty() || m_offline) &&
std::cerr << "--server and --profile can only be used in combination with --launch!" << std::endl; m_instanceIdToLaunch.isEmpty()) {
std::cerr << "--server, --profile and --offline can only be used in combination with --launch!" << std::endl;
m_status = Application::Failed; m_status = Application::Failed;
return; return;
} }
@ -397,6 +403,10 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
if (!m_profileToUse.isEmpty()) { if (!m_profileToUse.isEmpty()) {
launch.args["profile"] = m_profileToUse; launch.args["profile"] = m_profileToUse;
} }
if (m_offline) {
launch.args["offline_enabled"] = "true";
launch.args["offline_name"] = m_offlineName;
}
m_peerInstance->sendMessage(launch.serialize(), timeout); m_peerInstance->sendMessage(launch.serialize(), timeout);
} }
m_status = Application::Succeeded; m_status = Application::Succeeded;
@ -1209,7 +1219,7 @@ void Application::performMainStartupAction()
qDebug() << " Launching with account" << m_profileToUse; qDebug() << " Launching with account" << m_profileToUse;
} }
launch(inst, true, false, targetToJoin, accountToUse); launch(inst, !m_offline, false, targetToJoin, accountToUse, m_offlineName);
return; return;
} }
} }
@ -1308,6 +1318,8 @@ void Application::messageReceived(const QByteArray& message)
QString server = received.args["server"]; QString server = received.args["server"];
QString world = received.args["world"]; QString world = received.args["world"];
QString profile = received.args["profile"]; QString profile = received.args["profile"];
bool offline = received.args["offline_enabled"] == "true";
QString offlineName = received.args["offline_name"];
InstancePtr instance; InstancePtr instance;
if (!id.isEmpty()) { if (!id.isEmpty()) {
@ -1337,7 +1349,7 @@ void Application::messageReceived(const QByteArray& message)
} }
} }
launch(instance, true, false, serverObject, accountObject); launch(instance, !offline, false, serverObject, accountObject, offlineName);
} else { } else {
qWarning() << "Received invalid message" << message; qWarning() << "Received invalid message" << message;
} }
@ -1375,7 +1387,12 @@ bool Application::openJsonEditor(const QString& filename)
} }
} }
bool Application::launch(InstancePtr instance, bool online, bool demo, MinecraftTarget::Ptr targetToJoin, MinecraftAccountPtr accountToUse) bool Application::launch(InstancePtr instance,
bool online,
bool demo,
MinecraftTarget::Ptr targetToJoin,
MinecraftAccountPtr accountToUse,
const QString& offlineName)
{ {
if (m_updateRunning) { if (m_updateRunning) {
qDebug() << "Cannot launch instances while an update is running. Please try again when updates are completed."; qDebug() << "Cannot launch instances while an update is running. Please try again when updates are completed.";
@ -1395,6 +1412,7 @@ bool Application::launch(InstancePtr instance, bool online, bool demo, Minecraft
controller->setProfiler(profilers().value(instance->settings()->get("Profiler").toString(), nullptr).get()); controller->setProfiler(profilers().value(instance->settings()->get("Profiler").toString(), nullptr).get());
controller->setTargetToJoin(targetToJoin); controller->setTargetToJoin(targetToJoin);
controller->setAccountToUse(accountToUse); controller->setAccountToUse(accountToUse);
controller->setOfflineName(offlineName);
if (window) { if (window) {
controller->setParentWidget(window); controller->setParentWidget(window);
} else if (m_mainWindow) { } else if (m_mainWindow) {

View File

@ -211,7 +211,8 @@ class Application : public QApplication {
bool online = true, bool online = true,
bool demo = false, bool demo = false,
MinecraftTarget::Ptr targetToJoin = nullptr, MinecraftTarget::Ptr targetToJoin = nullptr,
MinecraftAccountPtr accountToUse = nullptr); MinecraftAccountPtr accountToUse = nullptr,
const QString& offlineName = QString());
bool kill(InstancePtr instance); bool kill(InstancePtr instance);
void closeCurrentWindow(); void closeCurrentWindow();
@ -300,6 +301,8 @@ class Application : public QApplication {
QString m_serverToJoin; QString m_serverToJoin;
QString m_worldToJoin; QString m_worldToJoin;
QString m_profileToUse; QString m_profileToUse;
bool m_offline = false;
QString m_offlineName;
bool m_liveCheck = false; bool m_liveCheck = false;
QList<QUrl> m_urlsToImport; QList<QUrl> m_urlsToImport;
QString m_instanceIdToShowWindowOf; QString m_instanceIdToShowWindowOf;

View File

@ -345,13 +345,12 @@ set(MINECRAFT_SOURCES
minecraft/mod/TexturePackFolderModel.h minecraft/mod/TexturePackFolderModel.h
minecraft/mod/TexturePackFolderModel.cpp minecraft/mod/TexturePackFolderModel.cpp
minecraft/mod/ShaderPackFolderModel.h minecraft/mod/ShaderPackFolderModel.h
minecraft/mod/tasks/BasicFolderLoadTask.h minecraft/mod/tasks/ResourceFolderLoadTask.h
minecraft/mod/tasks/ModFolderLoadTask.h minecraft/mod/tasks/ResourceFolderLoadTask.cpp
minecraft/mod/tasks/ModFolderLoadTask.cpp
minecraft/mod/tasks/LocalModParseTask.h minecraft/mod/tasks/LocalModParseTask.h
minecraft/mod/tasks/LocalModParseTask.cpp minecraft/mod/tasks/LocalModParseTask.cpp
minecraft/mod/tasks/LocalModUpdateTask.h minecraft/mod/tasks/LocalResourceUpdateTask.h
minecraft/mod/tasks/LocalModUpdateTask.cpp minecraft/mod/tasks/LocalResourceUpdateTask.cpp
minecraft/mod/tasks/LocalDataPackParseTask.h minecraft/mod/tasks/LocalDataPackParseTask.h
minecraft/mod/tasks/LocalDataPackParseTask.cpp minecraft/mod/tasks/LocalDataPackParseTask.cpp
minecraft/mod/tasks/LocalResourcePackParseTask.h minecraft/mod/tasks/LocalResourcePackParseTask.h
@ -1069,8 +1068,8 @@ SET(LAUNCHER_SOURCES
ui/dialogs/BlockedModsDialog.h ui/dialogs/BlockedModsDialog.h
ui/dialogs/ChooseProviderDialog.h ui/dialogs/ChooseProviderDialog.h
ui/dialogs/ChooseProviderDialog.cpp ui/dialogs/ChooseProviderDialog.cpp
ui/dialogs/ModUpdateDialog.cpp ui/dialogs/ResourceUpdateDialog.cpp
ui/dialogs/ModUpdateDialog.h ui/dialogs/ResourceUpdateDialog.h
ui/dialogs/InstallLoaderDialog.cpp ui/dialogs/InstallLoaderDialog.cpp
ui/dialogs/InstallLoaderDialog.h ui/dialogs/InstallLoaderDialog.h

View File

@ -235,10 +235,15 @@ void LaunchController::login()
if (!m_session->wants_online) { if (!m_session->wants_online) {
// we ask the user for a player name // we ask the user for a player name
bool ok = false; bool ok = false;
auto name = askOfflineName(m_session->player_name, m_session->demo, ok); QString name;
if (!ok) { if (m_offlineName.isEmpty()) {
tryagain = false; name = askOfflineName(m_session->player_name, m_session->demo, ok);
break; if (!ok) {
tryagain = false;
break;
}
} else {
name = m_offlineName;
} }
m_session->MakeOffline(name); m_session->MakeOffline(name);
// offline flavored game from here :3 // offline flavored game from here :3

View File

@ -56,6 +56,8 @@ class LaunchController : public Task {
void setOnline(bool online) { m_online = online; } void setOnline(bool online) { m_online = online; }
void setOfflineName(const QString& offlineName) { m_offlineName = offlineName; }
void setDemo(bool demo) { m_demo = demo; } void setDemo(bool demo) { m_demo = demo; }
void setProfiler(BaseProfilerFactory* profiler) { m_profiler = profiler; } void setProfiler(BaseProfilerFactory* profiler) { m_profiler = profiler; }
@ -88,6 +90,7 @@ class LaunchController : public Task {
private: private:
BaseProfilerFactory* m_profiler = nullptr; BaseProfilerFactory* m_profiler = nullptr;
bool m_online = true; bool m_online = true;
QString m_offlineName;
bool m_demo = false; bool m_demo = false;
InstancePtr m_instance; InstancePtr m_instance;
QWidget* m_parentWidget = nullptr; QWidget* m_parentWidget = nullptr;

View File

@ -33,7 +33,7 @@ class shared_qobject_ptr : public QSharedPointer<T> {
{} {}
void reset() { QSharedPointer<T>::reset(); } void reset() { QSharedPointer<T>::reset(); }
void reset(T*&& other) void reset(T* other)
{ {
shared_qobject_ptr<T> t(other); shared_qobject_ptr<T> t(other);
this->swap(t); this->swap(t);

View File

@ -35,9 +35,9 @@ ResourceDownloadTask::ResourceDownloadTask(ModPlatform::IndexedPack::Ptr pack,
QString custom_target_folder) QString custom_target_folder)
: m_pack(std::move(pack)), m_pack_version(std::move(version)), m_pack_model(packs), m_custom_target_folder(custom_target_folder) : m_pack(std::move(pack)), m_pack_version(std::move(version)), m_pack_model(packs), m_custom_target_folder(custom_target_folder)
{ {
if (auto model = dynamic_cast<ModFolderModel*>(m_pack_model.get()); model && is_indexed) { if (is_indexed) {
m_update_task.reset(new LocalModUpdateTask(model->indexDir(), *m_pack, m_pack_version)); m_update_task.reset(new LocalResourceUpdateTask(m_pack_model->indexDir(), *m_pack, m_pack_version));
connect(m_update_task.get(), &LocalModUpdateTask::hasOldMod, this, &ResourceDownloadTask::hasOldResource); connect(m_update_task.get(), &LocalResourceUpdateTask::hasOldResource, this, &ResourceDownloadTask::hasOldResource);
addTask(m_update_task); addTask(m_update_task);
} }
@ -91,12 +91,8 @@ void ResourceDownloadTask::downloadSucceeded()
m_filesNetJob.reset(); m_filesNetJob.reset();
auto name = std::get<0>(to_delete); auto name = std::get<0>(to_delete);
auto filename = std::get<1>(to_delete); auto filename = std::get<1>(to_delete);
if (!name.isEmpty() && filename != m_pack_version.fileName) { if (!name.isEmpty() && filename != m_pack_version.fileName)
if (auto model = dynamic_cast<ModFolderModel*>(m_pack_model.get()); model) m_pack_model->uninstallResource(filename, true);
model->uninstallMod(filename, true);
else
m_pack_model->uninstallResource(filename);
}
} }
void ResourceDownloadTask::downloadFailed(QString reason) void ResourceDownloadTask::downloadFailed(QString reason)

View File

@ -22,7 +22,7 @@
#include "net/NetJob.h" #include "net/NetJob.h"
#include "tasks/SequentialTask.h" #include "tasks/SequentialTask.h"
#include "minecraft/mod/tasks/LocalModUpdateTask.h" #include "minecraft/mod/tasks/LocalResourceUpdateTask.h"
#include "modplatform/ModIndex.h" #include "modplatform/ModIndex.h"
class ResourceFolderModel; class ResourceFolderModel;
@ -50,7 +50,7 @@ class ResourceDownloadTask : public SequentialTask {
QString m_custom_target_folder; QString m_custom_target_folder;
NetJob::Ptr m_filesNetJob; NetJob::Ptr m_filesNetJob;
LocalModUpdateTask::Ptr m_update_task; LocalResourceUpdateTask::Ptr m_update_task;
void downloadProgressChanged(qint64 current, qint64 total); void downloadProgressChanged(qint64 current, qint64 total);
void downloadFailed(QString reason); void downloadFailed(QString reason);

View File

@ -1203,7 +1203,7 @@ std::shared_ptr<ModFolderModel> MinecraftInstance::loaderModList()
{ {
if (!m_loader_mod_list) { if (!m_loader_mod_list) {
bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool();
m_loader_mod_list.reset(new ModFolderModel(modsRoot(), this, is_indexed)); m_loader_mod_list.reset(new ModFolderModel(modsRoot(), this, is_indexed, true));
} }
return m_loader_mod_list; return m_loader_mod_list;
} }
@ -1212,7 +1212,7 @@ std::shared_ptr<ModFolderModel> MinecraftInstance::coreModList()
{ {
if (!m_core_mod_list) { if (!m_core_mod_list) {
bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool();
m_core_mod_list.reset(new ModFolderModel(coreModsDir(), this, is_indexed)); m_core_mod_list.reset(new ModFolderModel(coreModsDir(), this, is_indexed, true));
} }
return m_core_mod_list; return m_core_mod_list;
} }
@ -1229,7 +1229,8 @@ std::shared_ptr<ModFolderModel> MinecraftInstance::nilModList()
std::shared_ptr<ResourcePackFolderModel> MinecraftInstance::resourcePackList() std::shared_ptr<ResourcePackFolderModel> MinecraftInstance::resourcePackList()
{ {
if (!m_resource_pack_list) { if (!m_resource_pack_list) {
m_resource_pack_list.reset(new ResourcePackFolderModel(resourcePacksDir(), this)); bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool();
m_resource_pack_list.reset(new ResourcePackFolderModel(resourcePacksDir(), this, is_indexed, true));
} }
return m_resource_pack_list; return m_resource_pack_list;
} }
@ -1237,7 +1238,8 @@ std::shared_ptr<ResourcePackFolderModel> MinecraftInstance::resourcePackList()
std::shared_ptr<TexturePackFolderModel> MinecraftInstance::texturePackList() std::shared_ptr<TexturePackFolderModel> MinecraftInstance::texturePackList()
{ {
if (!m_texture_pack_list) { if (!m_texture_pack_list) {
m_texture_pack_list.reset(new TexturePackFolderModel(texturePacksDir(), this)); bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool();
m_texture_pack_list.reset(new TexturePackFolderModel(texturePacksDir(), this, is_indexed, true));
} }
return m_texture_pack_list; return m_texture_pack_list;
} }
@ -1245,11 +1247,17 @@ std::shared_ptr<TexturePackFolderModel> MinecraftInstance::texturePackList()
std::shared_ptr<ShaderPackFolderModel> MinecraftInstance::shaderPackList() std::shared_ptr<ShaderPackFolderModel> MinecraftInstance::shaderPackList()
{ {
if (!m_shader_pack_list) { if (!m_shader_pack_list) {
m_shader_pack_list.reset(new ShaderPackFolderModel(shaderPacksDir(), this)); bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool();
m_shader_pack_list.reset(new ShaderPackFolderModel(shaderPacksDir(), this, is_indexed, true));
} }
return m_shader_pack_list; return m_shader_pack_list;
} }
QList<std::shared_ptr<ResourceFolderModel>> MinecraftInstance::resourceLists()
{
return { loaderModList(), coreModList(), nilModList(), resourcePackList(), texturePackList(), shaderPackList() };
}
std::shared_ptr<WorldList> MinecraftInstance::worldList() std::shared_ptr<WorldList> MinecraftInstance::worldList()
{ {
if (!m_world_list) { if (!m_world_list) {

View File

@ -116,6 +116,7 @@ class MinecraftInstance : public BaseInstance {
std::shared_ptr<ResourcePackFolderModel> resourcePackList(); std::shared_ptr<ResourcePackFolderModel> resourcePackList();
std::shared_ptr<TexturePackFolderModel> texturePackList(); std::shared_ptr<TexturePackFolderModel> texturePackList();
std::shared_ptr<ShaderPackFolderModel> shaderPackList(); std::shared_ptr<ShaderPackFolderModel> shaderPackList();
QList<std::shared_ptr<ResourceFolderModel>> resourceLists();
std::shared_ptr<WorldList> worldList(); std::shared_ptr<WorldList> worldList();
std::shared_ptr<GameOptions> gameOptionsModel(); std::shared_ptr<GameOptions> gameOptionsModel();

View File

@ -74,12 +74,12 @@ void MSADeviceCodeStep::perform()
m_task->setAskRetry(false); m_task->setAskRetry(false);
m_task->addNetAction(m_request); m_task->addNetAction(m_request);
connect(m_task.get(), &Task::finished, this, &MSADeviceCodeStep::deviceAutorizationFinished); connect(m_task.get(), &Task::finished, this, &MSADeviceCodeStep::deviceAuthorizationFinished);
m_task->start(); m_task->start();
} }
struct DeviceAutorizationResponse { struct DeviceAuthorizationResponse {
QString device_code; QString device_code;
QString user_code; QString user_code;
QString verification_uri; QString verification_uri;
@ -90,17 +90,17 @@ struct DeviceAutorizationResponse {
QString error_description; QString error_description;
}; };
DeviceAutorizationResponse parseDeviceAutorizationResponse(const QByteArray& data) DeviceAuthorizationResponse parseDeviceAuthorizationResponse(const QByteArray& data)
{ {
QJsonParseError err; QJsonParseError err;
QJsonDocument doc = QJsonDocument::fromJson(data, &err); QJsonDocument doc = QJsonDocument::fromJson(data, &err);
if (err.error != QJsonParseError::NoError) { if (err.error != QJsonParseError::NoError) {
qWarning() << "Failed to parse device autorization response due to err:" << err.errorString(); qWarning() << "Failed to parse device authorization response due to err:" << err.errorString();
return {}; return {};
} }
if (!doc.isObject()) { if (!doc.isObject()) {
qWarning() << "Device autorization response is not an object"; qWarning() << "Device authorization response is not an object";
return {}; return {};
} }
auto obj = doc.object(); auto obj = doc.object();
@ -111,9 +111,9 @@ DeviceAutorizationResponse parseDeviceAutorizationResponse(const QByteArray& dat
}; };
} }
void MSADeviceCodeStep::deviceAutorizationFinished() void MSADeviceCodeStep::deviceAuthorizationFinished()
{ {
auto rsp = parseDeviceAutorizationResponse(*m_response); auto rsp = parseDeviceAuthorizationResponse(*m_response);
if (!rsp.error.isEmpty() || !rsp.error_description.isEmpty()) { if (!rsp.error.isEmpty() || !rsp.error_description.isEmpty()) {
qWarning() << "Device authorization failed:" << rsp.error; qWarning() << "Device authorization failed:" << rsp.error;
emit finished(AccountTaskState::STATE_FAILED_HARD, emit finished(AccountTaskState::STATE_FAILED_HARD,
@ -208,12 +208,12 @@ AuthenticationResponse parseAuthenticationResponse(const QByteArray& data)
QJsonParseError err; QJsonParseError err;
QJsonDocument doc = QJsonDocument::fromJson(data, &err); QJsonDocument doc = QJsonDocument::fromJson(data, &err);
if (err.error != QJsonParseError::NoError) { if (err.error != QJsonParseError::NoError) {
qWarning() << "Failed to parse device autorization response due to err:" << err.errorString(); qWarning() << "Failed to parse device authorization response due to err:" << err.errorString();
return {}; return {};
} }
if (!doc.isObject()) { if (!doc.isObject()) {
qWarning() << "Device autorization response is not an object"; qWarning() << "Device authorization response is not an object";
return {}; return {};
} }
auto obj = doc.object(); auto obj = doc.object();
@ -274,4 +274,4 @@ void MSADeviceCodeStep::authenticationFinished()
m_data->msaToken.refresh_token = rsp.refresh_token; m_data->msaToken.refresh_token = rsp.refresh_token;
m_data->msaToken.token = rsp.access_token; m_data->msaToken.token = rsp.access_token;
emit finished(AccountTaskState::STATE_WORKING, tr("Got")); emit finished(AccountTaskState::STATE_WORKING, tr("Got"));
} }

View File

@ -58,7 +58,7 @@ class MSADeviceCodeStep : public AuthStep {
void authorizeWithBrowser(QString url, QString code, int expiresIn); void authorizeWithBrowser(QString url, QString code, int expiresIn);
private slots: private slots:
void deviceAutorizationFinished(); void deviceAuthorizationFinished();
void startPoolTimer(); void startPoolTimer();
void authenticateUser(); void authenticateUser();
void authenticationFinished(); void authenticationFinished();

View File

@ -26,33 +26,48 @@
// launcher/minecraft/mod/Mod.h // launcher/minecraft/mod/Mod.h
class Mod; class Mod;
/* Abstraction file for easily changing the way metadata is stored / handled namespace Metadata {
* Needs to be a class because of -Wunused-function and no C++17 [[maybe_unused]] using ModStruct = Packwiz::V1::Mod;
* */ using ModSide = Packwiz::V1::Side;
class Metadata {
public:
using ModStruct = Packwiz::V1::Mod;
using ModSide = Packwiz::V1::Side;
static auto create(QDir& index_dir, ModPlatform::IndexedPack& mod_pack, ModPlatform::IndexedVersion& mod_version) -> ModStruct inline auto create(const QDir& index_dir, ModPlatform::IndexedPack& mod_pack, ModPlatform::IndexedVersion& mod_version) -> ModStruct
{ {
return Packwiz::V1::createModFormat(index_dir, mod_pack, mod_version); return Packwiz::V1::createModFormat(index_dir, mod_pack, mod_version);
} }
static auto create(QDir& index_dir, Mod& internal_mod, QString mod_slug) -> ModStruct inline auto create(const QDir& index_dir, Mod& internal_mod, QString mod_slug) -> ModStruct
{ {
return Packwiz::V1::createModFormat(index_dir, internal_mod, mod_slug); return Packwiz::V1::createModFormat(index_dir, internal_mod, std::move(mod_slug));
} }
static void update(QDir& index_dir, ModStruct& mod) { Packwiz::V1::updateModIndex(index_dir, mod); } inline void update(const QDir& index_dir, ModStruct& mod)
{
Packwiz::V1::updateModIndex(index_dir, mod);
}
static void remove(QDir& index_dir, QString mod_slug) { Packwiz::V1::deleteModIndex(index_dir, mod_slug); } inline void remove(const QDir& index_dir, QString mod_slug)
{
Packwiz::V1::deleteModIndex(index_dir, mod_slug);
}
static void remove(QDir& index_dir, QVariant& mod_id) { Packwiz::V1::deleteModIndex(index_dir, mod_id); } inline void remove(const QDir& index_dir, QVariant& mod_id)
{
Packwiz::V1::deleteModIndex(index_dir, mod_id);
}
static auto get(QDir& index_dir, QString mod_slug) -> ModStruct { return Packwiz::V1::getIndexForMod(index_dir, mod_slug); } inline auto get(const QDir& index_dir, QString mod_slug) -> ModStruct
{
return Packwiz::V1::getIndexForMod(index_dir, std::move(mod_slug));
}
static auto get(QDir& index_dir, QVariant& mod_id) -> ModStruct { return Packwiz::V1::getIndexForMod(index_dir, mod_id); } inline auto get(const QDir& index_dir, QVariant& mod_id) -> ModStruct
{
return Packwiz::V1::getIndexForMod(index_dir, mod_id);
}
static auto modSideToString(ModSide side) -> QString { return Packwiz::V1::sideToString(side); } inline auto modSideToString(ModSide side) -> QString
}; {
return Packwiz::V1::sideToString(side);
}
}; // namespace Metadata

View File

@ -38,42 +38,22 @@
#include "Mod.h" #include "Mod.h"
#include <qpixmap.h> #include <qpixmap.h>
#include <QDebug>
#include <QDir> #include <QDir>
#include <QRegularExpression> #include <QRegularExpression>
#include <QString> #include <QString>
#include "MTPixmapCache.h" #include "MTPixmapCache.h"
#include "MetadataHandler.h" #include "MetadataHandler.h"
#include "Resource.h"
#include "Version.h" #include "Version.h"
#include "minecraft/mod/ModDetails.h" #include "minecraft/mod/ModDetails.h"
#include "minecraft/mod/Resource.h"
#include "minecraft/mod/tasks/LocalModParseTask.h" #include "minecraft/mod/tasks/LocalModParseTask.h"
#include "modplatform/ModIndex.h"
Mod::Mod(const QFileInfo& file) : Resource(file), m_local_details() Mod::Mod(const QFileInfo& file) : Resource(file), m_local_details()
{ {
m_enabled = (file.suffix() != "disabled"); m_enabled = (file.suffix() != "disabled");
} }
Mod::Mod(const QDir& mods_dir, const Metadata::ModStruct& metadata) : Mod(mods_dir.absoluteFilePath(metadata.filename))
{
m_name = metadata.name;
m_local_details.metadata = std::make_shared<Metadata::ModStruct>(std::move(metadata));
}
void Mod::setStatus(ModStatus status)
{
m_local_details.status = status;
}
void Mod::setMetadata(std::shared_ptr<Metadata::ModStruct>&& metadata)
{
if (status() == ModStatus::NoMetadata)
setStatus(ModStatus::Installed);
m_local_details.metadata = metadata;
}
void Mod::setDetails(const ModDetails& details) void Mod::setDetails(const ModDetails& details)
{ {
m_local_details = details; m_local_details = details;
@ -101,33 +81,28 @@ int Mod::compare(const Resource& other, SortType type) const
return -1; return -1;
break; break;
} }
case SortType::PROVIDER: {
return QString::compare(provider().value_or("Unknown"), cast_other->provider().value_or("Unknown"), Qt::CaseInsensitive);
}
case SortType::SIDE: { case SortType::SIDE: {
if (side() > cast_other->side()) auto compare_result = QString::compare(side(), cast_other->side(), Qt::CaseInsensitive);
return 1; if (compare_result != 0)
else if (side() < cast_other->side()) return compare_result;
return -1;
break;
}
case SortType::LOADERS: {
if (loaders() > cast_other->loaders())
return 1;
else if (loaders() < cast_other->loaders())
return -1;
break; break;
} }
case SortType::MC_VERSIONS: { case SortType::MC_VERSIONS: {
auto thisVersion = mcVersions().join(","); auto compare_result = QString::compare(mcVersions(), cast_other->mcVersions(), Qt::CaseInsensitive);
auto otherVersion = cast_other->mcVersions().join(","); if (compare_result != 0)
return QString::compare(thisVersion, otherVersion, Qt::CaseInsensitive); return compare_result;
break;
}
case SortType::LOADERS: {
auto compare_result = QString::compare(loaders(), cast_other->loaders(), Qt::CaseInsensitive);
if (compare_result != 0)
return compare_result;
break;
} }
case SortType::RELEASE_TYPE: { case SortType::RELEASE_TYPE: {
if (releaseType() > cast_other->releaseType()) auto compare_result = QString::compare(releaseType(), cast_other->releaseType(), Qt::CaseInsensitive);
return 1; if (compare_result != 0)
else if (releaseType() < cast_other->releaseType()) return compare_result;
return -1;
break; break;
} }
} }
@ -148,28 +123,6 @@ bool Mod::applyFilter(QRegularExpression filter) const
return Resource::applyFilter(filter); return Resource::applyFilter(filter);
} }
auto Mod::destroy(QDir& index_dir, bool preserve_metadata, bool attempt_trash) -> bool
{
if (!preserve_metadata) {
qDebug() << QString("Destroying metadata for '%1' on purpose").arg(name());
destroyMetadata(index_dir);
}
return Resource::destroy(attempt_trash);
}
void Mod::destroyMetadata(QDir& index_dir)
{
if (metadata()) {
Metadata::remove(index_dir, metadata()->slug);
} else {
auto n = name();
Metadata::remove(index_dir, n);
}
m_local_details.metadata = nullptr;
}
auto Mod::details() const -> const ModDetails& auto Mod::details() const -> const ModDetails&
{ {
return m_local_details; return m_local_details;
@ -181,10 +134,7 @@ auto Mod::name() const -> QString
if (!d_name.isEmpty()) if (!d_name.isEmpty())
return d_name; return d_name;
if (metadata()) return Resource::name();
return metadata()->name;
return m_name;
} }
auto Mod::version() const -> QString auto Mod::version() const -> QString
@ -192,16 +142,55 @@ auto Mod::version() const -> QString
return details().version; return details().version;
} }
auto Mod::homeurl() const -> QString auto Mod::homepage() const -> QString
{ {
return details().homeurl; QString metaUrl = Resource::homepage();
if (metaUrl.isEmpty())
return details().homeurl;
else
return metaUrl;
} }
auto Mod::metaurl() const -> QString auto Mod::loaders() const -> QString
{ {
if (metadata() == nullptr) if (metadata()) {
return homeurl(); QStringList loaders;
return ModPlatform::getMetaURL(metadata()->provider, metadata()->project_id); auto modLoaders = metadata()->loaders;
for (auto loader : { ModPlatform::NeoForge, ModPlatform::Forge, ModPlatform::Cauldron, ModPlatform::LiteLoader, ModPlatform::Fabric,
ModPlatform::Quilt }) {
if (modLoaders & loader) {
loaders << getModLoaderAsString(loader);
}
}
return loaders.join(", ");
}
return {};
}
auto Mod::side() const -> QString
{
if (metadata())
return Metadata::modSideToString(metadata()->side);
return Metadata::modSideToString(Metadata::ModSide::UniversalSide);
}
auto Mod::mcVersions() const -> QString
{
if (metadata())
return metadata()->mcVersions.join(", ");
return {};
}
auto Mod::releaseType() const -> QString
{
if (metadata())
return metadata()->releaseType.toString();
return ModPlatform::IndexedVersionType().toString();
} }
auto Mod::description() const -> QString auto Mod::description() const -> QString
@ -214,73 +203,17 @@ auto Mod::authors() const -> QStringList
return details().authors; return details().authors;
} }
auto Mod::status() const -> ModStatus
{
return details().status;
}
auto Mod::metadata() -> std::shared_ptr<Metadata::ModStruct>
{
return m_local_details.metadata;
}
auto Mod::metadata() const -> const std::shared_ptr<Metadata::ModStruct>
{
return m_local_details.metadata;
}
void Mod::finishResolvingWithDetails(ModDetails&& details) void Mod::finishResolvingWithDetails(ModDetails&& details)
{ {
m_is_resolving = false; m_is_resolving = false;
m_is_resolved = true; m_is_resolved = true;
std::shared_ptr<Metadata::ModStruct> metadata = details.metadata;
if (details.status == ModStatus::Unknown)
details.status = m_local_details.status;
m_local_details = std::move(details); m_local_details = std::move(details);
if (metadata)
setMetadata(std::move(metadata));
if (!iconPath().isEmpty()) { if (!iconPath().isEmpty()) {
m_packImageCacheKey.wasReadAttempt = false; m_packImageCacheKey.wasReadAttempt = false;
} }
} }
auto Mod::provider() const -> std::optional<QString>
{
if (metadata())
return ModPlatform::ProviderCapabilities::readableName(metadata()->provider);
return {};
}
auto Mod::side() const -> Metadata::ModSide
{
if (metadata())
return metadata()->side;
return Metadata::ModSide::UniversalSide;
}
auto Mod::releaseType() const -> ModPlatform::IndexedVersionType
{
if (metadata())
return metadata()->releaseType;
return ModPlatform::IndexedVersionType::VersionType::Unknown;
}
auto Mod::loaders() const -> ModPlatform::ModLoaderTypes
{
if (metadata())
return metadata()->loaders;
return {};
}
auto Mod::mcVersions() const -> QStringList
{
if (metadata())
return metadata()->mcVersions;
return {};
}
auto Mod::licenses() const -> const QList<ModLicense>& auto Mod::licenses() const -> const QList<ModLicense>&
{ {
return details().licenses; return details().licenses;

View File

@ -48,7 +48,6 @@
#include "ModDetails.h" #include "ModDetails.h"
#include "Resource.h" #include "Resource.h"
#include "modplatform/ModIndex.h"
class Mod : public Resource { class Mod : public Resource {
Q_OBJECT Q_OBJECT
@ -58,24 +57,20 @@ class Mod : public Resource {
Mod() = default; Mod() = default;
Mod(const QFileInfo& file); Mod(const QFileInfo& file);
Mod(const QDir& mods_dir, const Metadata::ModStruct& metadata);
Mod(QString file_path) : Mod(QFileInfo(file_path)) {} Mod(QString file_path) : Mod(QFileInfo(file_path)) {}
auto details() const -> const ModDetails&; auto details() const -> const ModDetails&;
auto name() const -> QString override; auto name() const -> QString override;
auto version() const -> QString; auto version() const -> QString;
auto homeurl() const -> QString; auto homepage() const -> QString override;
auto description() const -> QString; auto description() const -> QString;
auto authors() const -> QStringList; auto authors() const -> QStringList;
auto status() const -> ModStatus;
auto provider() const -> std::optional<QString>;
auto licenses() const -> const QList<ModLicense>&; auto licenses() const -> const QList<ModLicense>&;
auto issueTracker() const -> QString; auto issueTracker() const -> QString;
auto metaurl() const -> QString; auto side() const -> QString;
auto side() const -> Metadata::ModSide; auto loaders() const -> QString;
auto loaders() const -> ModPlatform::ModLoaderTypes; auto mcVersions() const -> QString;
auto mcVersions() const -> QStringList; auto releaseType() const -> QString;
auto releaseType() const -> ModPlatform::IndexedVersionType;
/** Get the intneral path to the mod's icon file*/ /** Get the intneral path to the mod's icon file*/
QString iconPath() const { return m_local_details.icon_file; } QString iconPath() const { return m_local_details.icon_file; }
@ -84,17 +79,11 @@ class Mod : public Resource {
/** Thread-safe. */ /** Thread-safe. */
QPixmap setIcon(QImage new_image) const; QPixmap setIcon(QImage new_image) const;
auto metadata() -> std::shared_ptr<Metadata::ModStruct>;
auto metadata() const -> const std::shared_ptr<Metadata::ModStruct>;
void setStatus(ModStatus status);
void setMetadata(std::shared_ptr<Metadata::ModStruct>&& metadata);
void setMetadata(const Metadata::ModStruct& metadata) { setMetadata(std::make_shared<Metadata::ModStruct>(metadata)); }
void setDetails(const ModDetails& details); void setDetails(const ModDetails& details);
bool valid() const override; bool valid() const override;
[[nodiscard]] int compare(Resource const& other, SortType type) const override; [[nodiscard]] int compare(const Resource & other, SortType type) const override;
[[nodiscard]] bool applyFilter(QRegularExpression filter) const override; [[nodiscard]] bool applyFilter(QRegularExpression filter) const override;
// Delete all the files of this mod // Delete all the files of this mod

View File

@ -43,13 +43,6 @@
#include "minecraft/mod/MetadataHandler.h" #include "minecraft/mod/MetadataHandler.h"
enum class ModStatus {
Installed, // Both JAR and Metadata are present
NotInstalled, // Only the Metadata is present
NoMetadata, // Only the JAR is present
Unknown, // Default status
};
struct ModLicense { struct ModLicense {
QString name = {}; QString name = {};
QString id = {}; QString id = {};
@ -149,12 +142,6 @@ struct ModDetails {
/* Path of mod logo */ /* Path of mod logo */
QString icon_file = {}; QString icon_file = {};
/* Installation status of the mod */
ModStatus status = ModStatus::Unknown;
/* Metadata information, if any */
std::shared_ptr<Metadata::ModStruct> metadata = nullptr;
ModDetails() = default; ModDetails() = default;
/** Metadata should be handled manually to properly set the mod status. */ /** Metadata should be handled manually to properly set the mod status. */
@ -169,40 +156,9 @@ struct ModDetails {
, issue_tracker(other.issue_tracker) , issue_tracker(other.issue_tracker)
, licenses(other.licenses) , licenses(other.licenses)
, icon_file(other.icon_file) , icon_file(other.icon_file)
, status(other.status)
{} {}
ModDetails& operator=(const ModDetails& other) ModDetails& operator=(const ModDetails& other) = default;
{
this->mod_id = other.mod_id;
this->name = other.name;
this->version = other.version;
this->mcversion = other.mcversion;
this->homeurl = other.homeurl;
this->description = other.description;
this->authors = other.authors;
this->issue_tracker = other.issue_tracker;
this->licenses = other.licenses;
this->icon_file = other.icon_file;
this->status = other.status;
return *this; ModDetails& operator=(ModDetails&& other) = default;
}
ModDetails& operator=(const ModDetails&& other)
{
this->mod_id = other.mod_id;
this->name = other.name;
this->version = other.version;
this->mcversion = other.mcversion;
this->homeurl = other.homeurl;
this->description = other.description;
this->authors = other.authors;
this->issue_tracker = other.issue_tracker;
this->licenses = other.licenses;
this->icon_file = other.icon_file;
this->status = other.status;
return *this;
}
}; };

View File

@ -48,21 +48,19 @@
#include <QThreadPool> #include <QThreadPool>
#include <QUrl> #include <QUrl>
#include <QUuid> #include <QUuid>
#include <algorithm>
#include "Application.h" #include "Application.h"
#include "Json.h" #include "Json.h"
#include "minecraft/mod/MetadataHandler.h"
#include "minecraft/mod/Resource.h"
#include "minecraft/mod/tasks/LocalModParseTask.h" #include "minecraft/mod/tasks/LocalModParseTask.h"
#include "minecraft/mod/tasks/LocalModUpdateTask.h" #include "minecraft/mod/tasks/LocalResourceUpdateTask.h"
#include "minecraft/mod/tasks/ModFolderLoadTask.h"
#include "modplatform/ModIndex.h" #include "modplatform/ModIndex.h"
#include "modplatform/flame/FlameAPI.h" #include "modplatform/flame/FlameAPI.h"
#include "modplatform/flame/FlameModIndex.h" #include "modplatform/flame/FlameModIndex.h"
ModFolderModel::ModFolderModel(const QString& dir, BaseInstance* instance, bool is_indexed, bool create_dir) ModFolderModel::ModFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent)
: ResourceFolderModel(QDir(dir), instance, nullptr, create_dir), m_is_indexed(is_indexed) : ResourceFolderModel(QDir(dir), instance, is_indexed, create_dir, parent)
{ {
m_column_names = QStringList({ "Enable", "Image", "Name", "Version", "Last Modified", "Provider", "Size", "Side", "Loaders", m_column_names = QStringList({ "Enable", "Image", "Name", "Version", "Last Modified", "Provider", "Size", "Side", "Loaders",
"Minecraft Versions", "Release Type" }); "Minecraft Versions", "Release Type" });
@ -92,7 +90,7 @@ QVariant ModFolderModel::data(const QModelIndex& index, int role) const
case NameColumn: case NameColumn:
return m_resources[row]->name(); return m_resources[row]->name();
case VersionColumn: { case VersionColumn: {
switch (m_resources[row]->type()) { switch (at(row).type()) {
case ResourceType::FOLDER: case ResourceType::FOLDER:
return tr("Folder"); return tr("Folder");
case ResourceType::SINGLEFILE: case ResourceType::SINGLEFILE:
@ -100,64 +98,50 @@ QVariant ModFolderModel::data(const QModelIndex& index, int role) const
default: default:
break; break;
} }
return at(row)->version(); return at(row).version();
} }
case DateColumn: case DateColumn:
return m_resources[row]->dateTimeChanged(); return at(row).dateTimeChanged();
case ProviderColumn: { case ProviderColumn: {
auto provider = at(row)->provider(); return at(row).provider();
if (!provider.has_value()) {
//: Unknown mod provider (i.e. not Modrinth, CurseForge, etc...)
return tr("Unknown");
}
return provider.value();
} }
case SideColumn: { case SideColumn: {
return Metadata::modSideToString(at(row)->side()); return at(row).side();
} }
case LoadersColumn: { case LoadersColumn: {
QStringList loaders; return at(row).loaders();
auto modLoaders = at(row)->loaders();
for (auto loader : { ModPlatform::NeoForge, ModPlatform::Forge, ModPlatform::Cauldron, ModPlatform::LiteLoader,
ModPlatform::Fabric, ModPlatform::Quilt }) {
if (modLoaders & loader) {
loaders << getModLoaderAsString(loader);
}
}
return loaders.join(", ");
} }
case McVersionsColumn: { case McVersionsColumn: {
return at(row)->mcVersions().join(", "); return at(row).mcVersions();
} }
case ReleaseTypeColumn: { case ReleaseTypeColumn: {
return at(row)->releaseType().toString(); return at(row).releaseType();
} }
case SizeColumn: case SizeColumn:
return m_resources[row]->sizeStr(); return at(row).sizeStr();
default: default:
return QVariant(); return QVariant();
} }
case Qt::ToolTipRole: case Qt::ToolTipRole:
if (column == NameColumn) { if (column == NameColumn) {
if (at(row)->isSymLinkUnder(instDirPath())) { if (at(row).isSymLinkUnder(instDirPath())) {
return m_resources[row]->internal_id() + return m_resources[row]->internal_id() +
tr("\nWarning: This resource is symbolically linked from elsewhere. Editing it will also change the original." tr("\nWarning: This resource is symbolically linked from elsewhere. Editing it will also change the original."
"\nCanonical Path: %1") "\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() + return m_resources[row]->internal_id() +
tr("\nWarning: This resource is hard linked elsewhere. Editing it will also change the original."); tr("\nWarning: This resource is hard linked elsewhere. Editing it will also change the original.");
} }
} }
return m_resources[row]->internal_id(); return m_resources[row]->internal_id();
case Qt::DecorationRole: { 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"); return APPLICATION->getThemedIcon("status-yellow");
if (column == ImageColumn) { if (column == ImageColumn) {
return at(row)->icon({ 32, 32 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding); return at(row).icon({ 32, 32 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding);
} }
return {}; return {};
} }
@ -169,7 +153,7 @@ QVariant ModFolderModel::data(const QModelIndex& index, int role) const
case Qt::CheckStateRole: case Qt::CheckStateRole:
switch (column) { switch (column) {
case ActiveColumn: case ActiveColumn:
return at(row)->enabled() ? Qt::Checked : Qt::Unchecked; return at(row).enabled() ? Qt::Checked : Qt::Unchecked;
default: default:
return QVariant(); return QVariant();
} }
@ -210,7 +194,7 @@ QVariant ModFolderModel::headerData(int section, [[maybe_unused]] Qt::Orientatio
case DateColumn: case DateColumn:
return tr("The date and time this mod was last changed (or added)."); return tr("The date and time this mod was last changed (or added).");
case ProviderColumn: case ProviderColumn:
return tr("Where the mod was downloaded from."); return tr("The source provider of the mod.");
case SideColumn: case SideColumn:
return tr("On what environment the mod is running."); return tr("On what environment the mod is running.");
case LoadersColumn: case LoadersColumn:
@ -235,133 +219,16 @@ int ModFolderModel::columnCount(const QModelIndex& parent) const
return parent.isValid() ? 0 : NUM_COLUMNS; return parent.isValid() ? 0 : NUM_COLUMNS;
} }
Task* ModFolderModel::createUpdateTask()
{
auto index_dir = indexDir();
auto task = new ModFolderLoadTask(dir(), index_dir, m_is_indexed, m_first_folder_load);
m_first_folder_load = false;
return task;
}
Task* ModFolderModel::createParseTask(Resource& resource) Task* ModFolderModel::createParseTask(Resource& resource)
{ {
return new LocalModParseTask(m_next_resolution_ticket, resource.type(), resource.fileinfo()); return new LocalModParseTask(m_next_resolution_ticket, resource.type(), resource.fileinfo());
} }
bool ModFolderModel::uninstallMod(const QString& filename, bool preserve_metadata)
{
for (auto mod : allMods()) {
if (mod->getOriginalFileName() == filename) {
auto index_dir = indexDir();
mod->destroy(index_dir, preserve_metadata, false);
update();
return true;
}
}
return false;
}
bool ModFolderModel::deleteMods(const QModelIndexList& indexes)
{
if (indexes.isEmpty())
return true;
for (auto i : indexes) {
if (i.column() != 0) {
continue;
}
auto m = at(i.row());
auto index_dir = indexDir();
m->destroy(index_dir);
}
update();
return true;
}
bool ModFolderModel::deleteModsMetadata(const QModelIndexList& indexes)
{
if (indexes.isEmpty())
return true;
for (auto i : indexes) {
if (i.column() != 0) {
continue;
}
auto m = at(i.row());
auto index_dir = indexDir();
m->destroyMetadata(index_dir);
}
update();
return true;
}
bool ModFolderModel::isValid() bool ModFolderModel::isValid()
{ {
return m_dir.exists() && m_dir.isReadable(); return m_dir.exists() && m_dir.isReadable();
} }
bool ModFolderModel::startWatching()
{
// Remove orphaned metadata next time
m_first_folder_load = true;
return ResourceFolderModel::startWatching({ m_dir.absolutePath(), indexDir().absolutePath() });
}
bool ModFolderModel::stopWatching()
{
return ResourceFolderModel::stopWatching({ m_dir.absolutePath(), indexDir().absolutePath() });
}
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::onUpdateSucceeded()
{
auto update_results = static_cast<ModFolderLoadTask*>(m_current_update_task.get())->result();
auto& new_mods = update_results->mods;
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
auto current_list = m_resources_index.keys();
QSet<QString> current_set(current_list.begin(), current_list.end());
auto new_list = new_mods.keys();
QSet<QString> new_set(new_list.begin(), new_list.end());
#else
QSet<QString> current_set(m_resources_index.keys().toSet());
QSet<QString> new_set(new_mods.keys().toSet());
#endif
applyUpdates(current_set, new_set, new_mods);
}
void ModFolderModel::onParseSucceeded(int ticket, QString mod_id) void ModFolderModel::onParseSucceeded(int ticket, QString mod_id)
{ {
auto iter = m_active_parse_tasks.constFind(ticket); auto iter = m_active_parse_tasks.constFind(ticket);
@ -383,47 +250,3 @@ void ModFolderModel::onParseSucceeded(int ticket, QString mod_id)
emit dataChanged(index(row), index(row, columnCount(QModelIndex()) - 1)); emit dataChanged(index(row), index(row, columnCount(QModelIndex()) - 1));
} }
static const FlameAPI flameAPI;
bool ModFolderModel::installMod(QString file_path, ModPlatform::IndexedVersion& vers)
{
if (vers.addonId.isValid()) {
ModPlatform::IndexedPack pack{
vers.addonId,
ModPlatform::ResourceProvider::FLAME,
};
QEventLoop loop;
auto response = std::make_shared<QByteArray>();
auto job = flameAPI.getProject(vers.addonId.toString(), response);
QObject::connect(job.get(), &Task::failed, [&loop] { loop.quit(); });
QObject::connect(job.get(), &Task::aborted, &loop, &QEventLoop::quit);
QObject::connect(job.get(), &Task::succeeded, [response, this, &vers, &loop, &pack] {
QJsonParseError parse_error{};
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
if (parse_error.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response for mod info at " << parse_error.offset
<< " reason: " << parse_error.errorString();
qDebug() << *response;
return;
}
try {
auto obj = Json::requireObject(Json::requireObject(doc), "data");
FlameMod::loadIndexedPack(pack, obj);
} catch (const JSONValidationError& e) {
qDebug() << doc;
qWarning() << "Error while reading mod info: " << e.cause();
}
LocalModUpdateTask update_metadata(indexDir(), pack, vers);
QObject::connect(&update_metadata, &Task::finished, &loop, &QEventLoop::quit);
update_metadata.start();
});
job->start();
loop.exec();
}
return ResourceFolderModel::installResource(file_path);
}

View File

@ -48,10 +48,9 @@
#include "ResourceFolderModel.h" #include "ResourceFolderModel.h"
#include "minecraft/mod/tasks/LocalModParseTask.h" #include "minecraft/mod/tasks/LocalModParseTask.h"
#include "minecraft/mod/tasks/ModFolderLoadTask.h" #include "minecraft/mod/tasks/ResourceFolderLoadTask.h"
#include "modplatform/ModIndex.h" #include "modplatform/ModIndex.h"
class LegacyInstance;
class BaseInstance; class BaseInstance;
class QFileSystemWatcher; class QFileSystemWatcher;
@ -76,8 +75,7 @@ class ModFolderModel : public ResourceFolderModel {
ReleaseTypeColumn, ReleaseTypeColumn,
NUM_COLUMNS NUM_COLUMNS
}; };
enum ModStatusAction { Disable, Enable, Toggle }; ModFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent = nullptr);
ModFolderModel(const QString& dir, BaseInstance* instance, bool is_indexed = false, bool create_dir = true);
virtual QString id() const override { return "mods"; } virtual QString id() const override { return "mods"; }
@ -86,34 +84,13 @@ class ModFolderModel : public ResourceFolderModel {
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
int columnCount(const QModelIndex& parent) const override; int columnCount(const QModelIndex& parent) const override;
[[nodiscard]] Task* createUpdateTask() override; [[nodiscard]] Resource* createResource(const QFileInfo& file) override { return new Mod(file); }
[[nodiscard]] Task* createParseTask(Resource&) override; [[nodiscard]] Task* createParseTask(Resource&) override;
bool installMod(QString file_path) { return ResourceFolderModel::installResource(file_path); }
bool installMod(QString file_path, ModPlatform::IndexedVersion& vers);
bool uninstallMod(const QString& filename, bool preserve_metadata = false);
/// Deletes all the selected mods
bool deleteMods(const QModelIndexList& indexes);
bool deleteModsMetadata(const QModelIndexList& indexes);
bool isValid(); bool isValid();
bool startWatching() override;
bool stopWatching() override;
QDir indexDir() { return { QString("%1/.index").arg(dir().absolutePath()) }; }
auto selectedMods(QModelIndexList& indexes) -> QList<Mod*>;
auto allMods() -> QList<Mod*>;
RESOURCE_HELPERS(Mod) RESOURCE_HELPERS(Mod)
private slots: private slots:
void onUpdateSucceeded() override;
void onParseSucceeded(int ticket, QString resource_id) override; void onParseSucceeded(int ticket, QString resource_id) override;
protected:
bool m_is_indexed;
bool m_first_folder_load = true;
}; };

View File

@ -21,7 +21,7 @@ void Resource::setFile(QFileInfo file_info)
parseFile(); parseFile();
} }
std::tuple<QString, qint64> calculateFileSize(const QFileInfo& file) static std::tuple<QString, qint64> calculateFileSize(const QFileInfo& file)
{ {
if (file.isDir()) { if (file.isDir()) {
auto dir = QDir(file.absoluteFilePath()); auto dir = QDir(file.absoluteFilePath());
@ -72,6 +72,14 @@ void Resource::parseFile()
m_changed_date_time = m_file_info.lastModified(); m_changed_date_time = m_file_info.lastModified();
} }
auto Resource::name() const -> QString
{
if (metadata())
return metadata()->name;
return m_name;
}
static void removeThePrefix(QString& string) static void removeThePrefix(QString& string)
{ {
QRegularExpression regex(QStringLiteral("^(?:the|teh) +"), QRegularExpression::CaseInsensitiveOption); QRegularExpression regex(QStringLiteral("^(?:the|teh) +"), QRegularExpression::CaseInsensitiveOption);
@ -79,6 +87,30 @@ static void removeThePrefix(QString& string)
string = string.trimmed(); string = string.trimmed();
} }
auto Resource::provider() const -> QString
{
if (metadata())
return ModPlatform::ProviderCapabilities::readableName(metadata()->provider);
return tr("Unknown");
}
auto Resource::homepage() const -> QString
{
if (metadata())
return ModPlatform::getMetaURL(metadata()->provider, metadata()->project_id);
return {};
}
void Resource::setMetadata(std::shared_ptr<Metadata::ModStruct>&& metadata)
{
if (status() == ResourceStatus::NO_METADATA)
setStatus(ResourceStatus::INSTALLED);
m_metadata = metadata;
}
int Resource::compare(const Resource& other, SortType type) const int Resource::compare(const Resource& other, SortType type) const
{ {
switch (type) { switch (type) {
@ -93,6 +125,7 @@ int Resource::compare(const Resource& other, SortType type) const
QString this_name{ name() }; QString this_name{ name() };
QString other_name{ other.name() }; QString other_name{ other.name() };
// TODO do we need this? it could result in 0 being returned
removeThePrefix(this_name); removeThePrefix(this_name);
removeThePrefix(other_name); removeThePrefix(other_name);
@ -118,6 +151,12 @@ int Resource::compare(const Resource& other, SortType type) const
return -1; return -1;
break; break;
} }
case SortType::PROVIDER: {
auto compare_result = QString::compare(provider(), other.provider(), Qt::CaseInsensitive);
if (compare_result != 0)
return compare_result;
break;
}
} }
return 0; return 0;
@ -174,10 +213,27 @@ bool Resource::enable(EnableAction action)
return true; return true;
} }
bool Resource::destroy(bool attemptTrash) auto Resource::destroy(const QDir& index_dir, bool preserve_metadata, bool attempt_trash) -> bool
{ {
m_type = ResourceType::UNKNOWN; m_type = ResourceType::UNKNOWN;
return (attemptTrash && FS::trash(m_file_info.filePath())) || FS::deletePath(m_file_info.filePath());
if (!preserve_metadata) {
qDebug() << QString("Destroying metadata for '%1' on purpose").arg(name());
destroyMetadata(index_dir);
}
return (attempt_trash && FS::trash(m_file_info.filePath())) || FS::deletePath(m_file_info.filePath());
}
auto Resource::destroyMetadata(const QDir& index_dir) -> void
{
if (metadata()) {
Metadata::remove(index_dir, metadata()->slug);
} else {
auto n = name();
Metadata::remove(index_dir, n);
}
m_metadata = nullptr;
} }
bool Resource::isSymLinkUnder(const QString& instPath) const bool Resource::isSymLinkUnder(const QString& instPath) const

View File

@ -40,6 +40,8 @@
#include <QObject> #include <QObject>
#include <QPointer> #include <QPointer>
#include "MetadataHandler.h"
#include "ModDetails.h"
#include "QObjectPtr.h" #include "QObjectPtr.h"
enum class ResourceType { enum class ResourceType {
@ -50,7 +52,14 @@ enum class ResourceType {
LITEMOD, //!< The resource is a litemod LITEMOD, //!< The resource is a litemod
}; };
enum class SortType { NAME, DATE, VERSION, ENABLED, PACK_FORMAT, PROVIDER, SIZE, SIDE, LOADERS, MC_VERSIONS, RELEASE_TYPE }; enum class ResourceStatus {
INSTALLED, // Both JAR and Metadata are present
NOT_INSTALLED, // Only the Metadata is present
NO_METADATA, // Only the JAR is present
UNKNOWN, // Default status
};
enum class SortType { NAME, DATE, VERSION, ENABLED, PACK_FORMAT, PROVIDER, SIZE, SIDE, MC_VERSIONS, LOADERS, RELEASE_TYPE };
enum class EnableAction { ENABLE, DISABLE, TOGGLE }; enum class EnableAction { ENABLE, DISABLE, TOGGLE };
@ -84,9 +93,19 @@ class Resource : public QObject {
[[nodiscard]] QString sizeStr() const { return m_size_str; } [[nodiscard]] QString sizeStr() const { return m_size_str; }
[[nodiscard]] qint64 sizeInfo() const { return m_size_info; } [[nodiscard]] qint64 sizeInfo() const { return m_size_info; }
[[nodiscard]] virtual auto name() const -> QString { return m_name; } [[nodiscard]] virtual auto name() const -> QString;
[[nodiscard]] virtual bool valid() const { return m_type != ResourceType::UNKNOWN; } [[nodiscard]] virtual bool valid() const { return m_type != ResourceType::UNKNOWN; }
[[nodiscard]] auto status() const -> ResourceStatus { return m_status; };
[[nodiscard]] auto metadata() -> std::shared_ptr<Metadata::ModStruct> { return m_metadata; }
[[nodiscard]] auto metadata() const -> std::shared_ptr<const Metadata::ModStruct> { return m_metadata; }
[[nodiscard]] auto provider() const -> QString;
[[nodiscard]] virtual auto homepage() const -> QString;
void setStatus(ResourceStatus status) { m_status = status; }
void setMetadata(std::shared_ptr<Metadata::ModStruct>&& metadata);
void setMetadata(const Metadata::ModStruct& metadata) { setMetadata(std::make_shared<Metadata::ModStruct>(metadata)); }
/** Compares two Resources, for sorting purposes, considering a ascending order, returning: /** Compares two Resources, for sorting purposes, considering a ascending order, returning:
* > 0: 'this' comes after 'other' * > 0: 'this' comes after 'other'
* = 0: 'this' is equal to 'other' * = 0: 'this' is equal to 'other'
@ -117,7 +136,9 @@ class Resource : public QObject {
} }
// Delete all files of this resource. // Delete all files of this resource.
bool destroy(bool attemptTrash = true); 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) -> void;
[[nodiscard]] auto isSymLink() const -> bool { return m_file_info.isSymLink(); } [[nodiscard]] auto isSymLink() const -> bool { return m_file_info.isSymLink(); }
@ -146,6 +167,11 @@ class Resource : public QObject {
/* The type of file we're dealing with. */ /* The type of file we're dealing with. */
ResourceType m_type = ResourceType::UNKNOWN; ResourceType m_type = ResourceType::UNKNOWN;
/* Installation status of the resource. */
ResourceStatus m_status = ResourceStatus::UNKNOWN;
std::shared_ptr<Metadata::ModStruct> m_metadata = nullptr;
/* Whether the resource is enabled (e.g. shows up in the game) or not. */ /* Whether the resource is enabled (e.g. shows up in the game) or not. */
bool m_enabled = true; bool m_enabled = true;

View File

@ -11,20 +11,25 @@
#include <QStyle> #include <QStyle>
#include <QThreadPool> #include <QThreadPool>
#include <QUrl> #include <QUrl>
#include <utility>
#include "Application.h" #include "Application.h"
#include "FileSystem.h" #include "FileSystem.h"
#include "QVariantUtils.h" #include "QVariantUtils.h"
#include "StringUtils.h" #include "StringUtils.h"
#include "minecraft/mod/tasks/BasicFolderLoadTask.h" #include "minecraft/mod/tasks/ResourceFolderLoadTask.h"
#include "Json.h"
#include "minecraft/mod/tasks/LocalResourceUpdateTask.h"
#include "modplatform/flame/FlameAPI.h"
#include "modplatform/flame/FlameModIndex.h"
#include "settings/Setting.h" #include "settings/Setting.h"
#include "tasks/Task.h" #include "tasks/Task.h"
#include "ui/dialogs/CustomMessageBox.h" #include "ui/dialogs/CustomMessageBox.h"
ResourceFolderModel::ResourceFolderModel(QDir dir, BaseInstance* instance, QObject* parent, bool create_dir) ResourceFolderModel::ResourceFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent)
: QAbstractListModel(parent), m_dir(dir), m_instance(instance), m_watcher(this) : QAbstractListModel(parent), m_dir(dir), m_instance(instance), m_watcher(this), m_is_indexed(is_indexed)
{ {
if (create_dir) { if (create_dir) {
FS::ensureFolderPathExists(m_dir.absolutePath()); FS::ensureFolderPathExists(m_dir.absolutePath());
@ -48,6 +53,9 @@ ResourceFolderModel::~ResourceFolderModel()
bool ResourceFolderModel::startWatching(const QStringList& paths) bool ResourceFolderModel::startWatching(const QStringList& paths)
{ {
// Remove orphaned metadata next time
m_first_folder_load = true;
if (m_is_watching) if (m_is_watching)
return false; return false;
@ -158,11 +166,55 @@ bool ResourceFolderModel::installResource(QString original_path)
return false; return false;
} }
bool ResourceFolderModel::uninstallResource(QString file_name) bool ResourceFolderModel::installResourceWithFlameMetadata(QString path, ModPlatform::IndexedVersion& vers)
{
if (vers.addonId.isValid()) {
ModPlatform::IndexedPack pack{
vers.addonId,
ModPlatform::ResourceProvider::FLAME,
};
QEventLoop loop;
auto response = std::make_shared<QByteArray>();
auto job = FlameAPI().getProject(vers.addonId.toString(), response);
QObject::connect(job.get(), &Task::failed, [&loop] { loop.quit(); });
QObject::connect(job.get(), &Task::aborted, &loop, &QEventLoop::quit);
QObject::connect(job.get(), &Task::succeeded, [response, this, &vers, &loop, &pack] {
QJsonParseError parse_error{};
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
if (parse_error.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response for mod info at " << parse_error.offset
<< " reason: " << parse_error.errorString();
qDebug() << *response;
return;
}
try {
auto obj = Json::requireObject(Json::requireObject(doc), "data");
FlameMod::loadIndexedPack(pack, obj);
} catch (const JSONValidationError& e) {
qDebug() << doc;
qWarning() << "Error while reading mod info: " << e.cause();
}
LocalResourceUpdateTask update_metadata(indexDir(), pack, vers);
QObject::connect(&update_metadata, &Task::finished, &loop, &QEventLoop::quit);
update_metadata.start();
});
job->start();
loop.exec();
}
return installResource(std::move(path));
}
bool ResourceFolderModel::uninstallResource(QString file_name, bool preserve_metadata)
{ {
for (auto& resource : m_resources) { for (auto& resource : m_resources) {
if (resource->fileinfo().fileName() == file_name) { if (resource->fileinfo().fileName() == file_name) {
auto res = resource->destroy(false); auto res = resource->destroy(indexDir(), preserve_metadata, false);
update(); update();
@ -178,13 +230,11 @@ bool ResourceFolderModel::deleteResources(const QModelIndexList& indexes)
return true; return true;
for (auto i : indexes) { for (auto i : indexes) {
if (i.column() != 0) { if (i.column() != 0)
continue; continue;
}
auto& resource = m_resources.at(i.row()); auto& resource = m_resources.at(i.row());
resource->destroy(indexDir());
resource->destroy();
} }
update(); update();
@ -192,6 +242,22 @@ bool ResourceFolderModel::deleteResources(const QModelIndexList& indexes)
return true; return true;
} }
void ResourceFolderModel::deleteMetadata(const QModelIndexList& indexes)
{
if (indexes.isEmpty())
return;
for (auto i : indexes) {
if (i.column() != 0)
continue;
auto& resource = m_resources.at(i.row());
resource->destroyMetadata(indexDir());
}
update();
}
bool ResourceFolderModel::setResourceEnabled(const QModelIndexList& indexes, EnableAction action) bool ResourceFolderModel::setResourceEnabled(const QModelIndexList& indexes, EnableAction action)
{ {
if (indexes.isEmpty()) if (indexes.isEmpty())
@ -299,7 +365,7 @@ void ResourceFolderModel::resolveResource(Resource* res)
void ResourceFolderModel::onUpdateSucceeded() void ResourceFolderModel::onUpdateSucceeded()
{ {
auto update_results = static_cast<BasicFolderLoadTask*>(m_current_update_task.get())->result(); auto update_results = static_cast<ResourceFolderLoadTask*>(m_current_update_task.get())->result();
auto& new_resources = update_results->resources; auto& new_resources = update_results->resources;
@ -329,7 +395,11 @@ void ResourceFolderModel::onParseSucceeded(int ticket, QString resource_id)
Task* ResourceFolderModel::createUpdateTask() Task* ResourceFolderModel::createUpdateTask()
{ {
return new BasicFolderLoadTask(m_dir); auto index_dir = indexDir();
auto task = new ResourceFolderLoadTask(dir(), index_dir, m_is_indexed, m_first_folder_load,
[this](const QFileInfo& file) { return createResource(file); });
m_first_folder_load = false;
return task;
} }
bool ResourceFolderModel::hasPendingParseTasks() const bool ResourceFolderModel::hasPendingParseTasks() const
@ -419,6 +489,8 @@ QVariant ResourceFolderModel::data(const QModelIndex& index, int role) const
return m_resources[row]->name(); return m_resources[row]->name();
case DateColumn: case DateColumn:
return m_resources[row]->dateTimeChanged(); return m_resources[row]->dateTimeChanged();
case ProviderColumn:
return m_resources[row]->provider();
case SizeColumn: case SizeColumn:
return m_resources[row]->sizeStr(); return m_resources[row]->sizeStr();
default: default:
@ -490,22 +562,23 @@ QVariant ResourceFolderModel::headerData(int section, [[maybe_unused]] Qt::Orien
case ActiveColumn: case ActiveColumn:
case NameColumn: case NameColumn:
case DateColumn: case DateColumn:
case ProviderColumn:
case SizeColumn: case SizeColumn:
return columnNames().at(section); return columnNames().at(section);
default: default:
return {}; return {};
} }
case Qt::ToolTipRole: { case Qt::ToolTipRole: {
//: Here, resource is a generic term for external resources, like Mods, Resource Packs, Shader Packs, etc.
switch (section) { switch (section) {
case ActiveColumn: case ActiveColumn:
//: Here, resource is a generic term for external resources, like Mods, Resource Packs, Shader Packs, etc.
return tr("Is the resource enabled?"); return tr("Is the resource enabled?");
case NameColumn: case NameColumn:
//: Here, resource is a generic term for external resources, like Mods, Resource Packs, Shader Packs, etc.
return tr("The name of the resource."); return tr("The name of the resource.");
case DateColumn: case DateColumn:
//: Here, resource is a generic term for external resources, like Mods, Resource Packs, Shader Packs, etc.
return tr("The date and time this resource was last changed (or added)."); return tr("The date and time this resource was last changed (or added).");
case ProviderColumn:
return tr("The source provider of the resource.");
case SizeColumn: case SizeColumn:
return tr("The size of the resource."); return tr("The size of the resource.");
default: default:

View File

@ -19,6 +19,58 @@
class QSortFilterProxyModel; 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(), \
[id](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. /** A basic model for external resources.
* *
* This model manages a list of resources. As such, external users of such resources do not own them, * This model manages a list of resources. As such, external users of such resources do not own them,
@ -29,7 +81,7 @@ class QSortFilterProxyModel;
class ResourceFolderModel : public QAbstractListModel { class ResourceFolderModel : public QAbstractListModel {
Q_OBJECT Q_OBJECT
public: public:
ResourceFolderModel(QDir, BaseInstance* instance, QObject* parent = nullptr, bool create_dir = true); ResourceFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent = nullptr);
~ResourceFolderModel() override; ~ResourceFolderModel() override;
virtual QString id() const { return "resource"; } virtual QString id() const { return "resource"; }
@ -49,8 +101,10 @@ class ResourceFolderModel : public QAbstractListModel {
bool stopWatching(const QStringList& paths); bool stopWatching(const QStringList& paths);
/* Helper methods for subclasses, using a predetermined list of paths. */ /* Helper methods for subclasses, using a predetermined list of paths. */
virtual bool startWatching() { return startWatching({ m_dir.absolutePath() }); } virtual bool startWatching() { return startWatching({ indexDir().absolutePath(), m_dir.absolutePath() }); }
virtual bool stopWatching() { return stopWatching({ m_dir.absolutePath() }); } virtual bool stopWatching() { return stopWatching({ indexDir().absolutePath(), m_dir.absolutePath() }); }
QDir indexDir() { return { QString("%1/.index").arg(dir().absolutePath()) }; }
/** Given a path in the system, install that resource, moving it to its place in the /** Given a path in the system, install that resource, moving it to its place in the
* instance file hierarchy. * instance file hierarchy.
@ -59,12 +113,15 @@ class ResourceFolderModel : public QAbstractListModel {
*/ */
virtual bool installResource(QString path); virtual bool installResource(QString path);
virtual bool installResourceWithFlameMetadata(QString path, ModPlatform::IndexedVersion& vers);
/** Uninstall (i.e. remove all data about it) a resource, given its file name. /** Uninstall (i.e. remove all data about it) a resource, given its file name.
* *
* Returns whether the removal was successful. * Returns whether the removal was successful.
*/ */
virtual bool uninstallResource(QString file_name); virtual bool uninstallResource(QString file_name, bool preserve_metadata = false);
virtual bool deleteResources(const QModelIndexList&); virtual bool deleteResources(const QModelIndexList&);
virtual void deleteMetadata(const QModelIndexList&);
/** Applies the given 'action' to the resources in 'indexes'. /** Applies the given 'action' to the resources in 'indexes'.
* *
@ -80,9 +137,7 @@ class ResourceFolderModel : public QAbstractListModel {
[[nodiscard]] qsizetype size() const { return m_resources.size(); } [[nodiscard]] qsizetype size() const { return m_resources.size(); }
[[nodiscard]] bool empty() const { return size() == 0; } [[nodiscard]] bool empty() const { return size() == 0; }
[[nodiscard]] Resource& at(int index) { return *m_resources.at(index); } RESOURCE_HELPERS(Resource)
[[nodiscard]] Resource const& at(int index) const { return *m_resources.at(index); }
[[nodiscard]] QList<Resource::Ptr> const& all() const { return m_resources; }
[[nodiscard]] QDir const& dir() const { return m_dir; } [[nodiscard]] QDir const& dir() const { return m_dir; }
@ -96,7 +151,8 @@ class ResourceFolderModel : public QAbstractListModel {
/* Qt behavior */ /* Qt behavior */
/* Basic columns */ /* Basic columns */
enum Columns { ActiveColumn = 0, NameColumn, DateColumn, SizeColumn, NUM_COLUMNS }; enum Columns { ActiveColumn = 0, NameColumn, DateColumn, ProviderColumn, SizeColumn, NUM_COLUMNS };
QStringList columnNames(bool translated = true) const { return translated ? m_column_names_translated : m_column_names; } QStringList columnNames(bool translated = true) const { return translated ? m_column_names_translated : m_column_names; }
[[nodiscard]] int rowCount(const QModelIndex& parent = {}) const override { return parent.isValid() ? 0 : static_cast<int>(size()); } [[nodiscard]] int rowCount(const QModelIndex& parent = {}) const override { return parent.isValid() ? 0 : static_cast<int>(size()); }
@ -153,7 +209,9 @@ class ResourceFolderModel : public QAbstractListModel {
* This Task is normally executed when opening a page, so it shouldn't contain much heavy work. * This Task is normally executed when opening a page, so it shouldn't contain much heavy work.
* If such work is needed, try using it in the Task create by createParseTask() instead! * If such work is needed, try using it in the Task create by createParseTask() instead!
*/ */
[[nodiscard]] virtual Task* createUpdateTask(); [[nodiscard]] Task* createUpdateTask();
[[nodiscard]] virtual Resource* createResource(const QFileInfo& info) { return new Resource(info); }
/** This creates a new parse task to be executed by onUpdateSucceeded(). /** This creates a new parse task to be executed by onUpdateSucceeded().
* *
@ -195,19 +253,22 @@ class ResourceFolderModel : public QAbstractListModel {
protected: protected:
// Represents the relationship between a column's index (represented by the list index), and it's sorting key. // Represents the relationship between a column's index (represented by the list index), and it's sorting key.
// As such, the order in with they appear is very important! // As such, the order in with they appear is very important!
QList<SortType> m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::DATE, SortType::SIZE }; QList<SortType> m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::DATE, SortType::PROVIDER, SortType::SIZE };
QStringList m_column_names = { "Enable", "Name", "Last Modified", "Size" }; QStringList m_column_names = { "Enable", "Name", "Last Modified", "Provider", "Size" };
QStringList m_column_names_translated = { tr("Enable"), tr("Name"), tr("Last Modified"), tr("Size") }; QStringList m_column_names_translated = { tr("Enable"), tr("Name"), tr("Last Modified"), tr("Provider"), tr("Size") };
QList<QHeaderView::ResizeMode> m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive, QList<QHeaderView::ResizeMode> m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive,
QHeaderView::Interactive }; QHeaderView::Interactive, QHeaderView::Interactive };
QList<bool> m_columnsHideable = { false, false, true, true }; QList<bool> m_columnsHideable = { false, false, true, true, true };
QList<bool> m_columnsHiddenByDefault = { false, false, false, false }; QList<bool> m_columnsHiddenByDefault = { false, false, false, false, true };
QDir m_dir; QDir m_dir;
BaseInstance* m_instance; BaseInstance* m_instance;
QFileSystemWatcher m_watcher; QFileSystemWatcher m_watcher;
bool m_is_watching = false; bool m_is_watching = false;
bool m_is_indexed;
bool m_first_folder_load = true;
Task::Ptr m_current_update_task = nullptr; Task::Ptr m_current_update_task = nullptr;
bool m_scheduled_update = false; bool m_scheduled_update = false;
@ -221,37 +282,6 @@ class ResourceFolderModel : public QAbstractListModel {
std::atomic<int> m_next_resolution_ticket = 0; 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(), \
[id](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 definition to avoid some code duplication */
template <typename T> template <typename T>
void ResourceFolderModel::applyUpdates(QSet<QString>& current_set, QSet<QString>& new_set, QMap<QString, T>& new_resources) void ResourceFolderModel::applyUpdates(QSet<QString>& current_set, QSet<QString>& new_set, QMap<QString, T>& new_resources)

View File

@ -44,19 +44,20 @@
#include "Application.h" #include "Application.h"
#include "Version.h" #include "Version.h"
#include "minecraft/mod/Resource.h"
#include "minecraft/mod/tasks/BasicFolderLoadTask.h"
#include "minecraft/mod/tasks/LocalResourcePackParseTask.h" #include "minecraft/mod/tasks/LocalResourcePackParseTask.h"
#include "minecraft/mod/tasks/ResourceFolderLoadTask.h"
ResourcePackFolderModel::ResourcePackFolderModel(const QString& dir, BaseInstance* instance) : ResourceFolderModel(QDir(dir), instance) ResourcePackFolderModel::ResourcePackFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent)
: ResourceFolderModel(dir, instance, is_indexed, create_dir, parent)
{ {
m_column_names = QStringList({ "Enable", "Image", "Name", "Pack Format", "Last Modified", "Size" }); m_column_names = QStringList({ "Enable", "Image", "Name", "Pack Format", "Last Modified", "Provider", "Size" });
m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Pack Format"), tr("Last Modified"), tr("Size") }); m_column_names_translated =
m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::PACK_FORMAT, SortType::DATE, SortType::SIZE }; QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Pack Format"), tr("Last Modified"), tr("Provider"), tr("Size") });
m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Stretch, m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::PACK_FORMAT,
SortType::DATE, SortType::PROVIDER, SortType::SIZE };
m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive,
QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive }; QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive };
m_columnsHideable = { false, true, false, true, true, true }; m_columnsHideable = { false, true, false, true, true, true, true };
m_columnsHiddenByDefault = { false, false, false, false, false, false };
} }
QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const
@ -73,12 +74,12 @@ QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const
case NameColumn: case NameColumn:
return m_resources[row]->name(); return m_resources[row]->name();
case PackFormatColumn: { case PackFormatColumn: {
auto resource = at(row); auto& resource = at(row);
auto pack_format = resource->packFormat(); auto pack_format = resource.packFormat();
if (pack_format == 0) if (pack_format == 0)
return tr("Unrecognized"); return tr("Unrecognized");
auto version_bounds = resource->compatibleVersions(); auto version_bounds = resource.compatibleVersions();
if (version_bounds.first.toString().isEmpty()) if (version_bounds.first.toString().isEmpty())
return QString::number(pack_format); return QString::number(pack_format);
@ -87,17 +88,18 @@ QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const
} }
case DateColumn: case DateColumn:
return m_resources[row]->dateTimeChanged(); return m_resources[row]->dateTimeChanged();
case ProviderColumn:
return m_resources[row]->provider();
case SizeColumn: case SizeColumn:
return m_resources[row]->sizeStr(); return m_resources[row]->sizeStr();
default: default:
return {}; return {};
} }
case Qt::DecorationRole: { 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"); return APPLICATION->getThemedIcon("status-yellow");
if (column == ImageColumn) { if (column == ImageColumn) {
return at(row)->image({ 32, 32 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding); return at(row).image({ 32, 32 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding);
} }
return {}; return {};
} }
@ -107,14 +109,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."); return tr("The resource pack format ID, as well as the Minecraft versions it was designed for.");
} }
if (column == NameColumn) { if (column == NameColumn) {
if (at(row)->isSymLinkUnder(instDirPath())) { if (at(row).isSymLinkUnder(instDirPath())) {
return m_resources[row]->internal_id() + return m_resources[row]->internal_id() +
tr("\nWarning: This resource is symbolically linked from elsewhere. Editing it will also change the original." tr("\nWarning: This resource is symbolically linked from elsewhere. Editing it will also change the original."
"\nCanonical Path: %1") "\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() + return m_resources[row]->internal_id() +
tr("\nWarning: This resource is hard linked elsewhere. Editing it will also change the original."); tr("\nWarning: This resource is hard linked elsewhere. Editing it will also change the original.");
} }
@ -129,7 +131,7 @@ QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const
case Qt::CheckStateRole: case Qt::CheckStateRole:
switch (column) { switch (column) {
case ActiveColumn: case ActiveColumn:
return at(row)->enabled() ? Qt::Checked : Qt::Unchecked; return at(row).enabled() ? Qt::Checked : Qt::Unchecked;
default: default:
return {}; return {};
} }
@ -148,6 +150,7 @@ QVariant ResourcePackFolderModel::headerData(int section, [[maybe_unused]] Qt::O
case PackFormatColumn: case PackFormatColumn:
case DateColumn: case DateColumn:
case ImageColumn: case ImageColumn:
case ProviderColumn:
case SizeColumn: case SizeColumn:
return columnNames().at(section); return columnNames().at(section);
default: default:
@ -157,7 +160,7 @@ QVariant ResourcePackFolderModel::headerData(int section, [[maybe_unused]] Qt::O
case Qt::ToolTipRole: case Qt::ToolTipRole:
switch (section) { switch (section) {
case ActiveColumn: case ActiveColumn:
return tr("Is the resource pack enabled? (Only valid for ZIPs)"); return tr("Is the resource pack enabled?");
case NameColumn: case NameColumn:
return tr("The name of the resource pack."); return tr("The name of the resource pack.");
case PackFormatColumn: case PackFormatColumn:
@ -165,6 +168,8 @@ QVariant ResourcePackFolderModel::headerData(int section, [[maybe_unused]] Qt::O
return tr("The resource pack format ID, as well as the Minecraft versions it was designed for."); return tr("The resource pack format ID, as well as the Minecraft versions it was designed for.");
case DateColumn: case DateColumn:
return tr("The date and time this resource pack was last changed (or added)."); return tr("The date and time this resource pack was last changed (or added).");
case ProviderColumn:
return tr("The source provider of the resource pack.");
case SizeColumn: case SizeColumn:
return tr("The size of the resource pack."); return tr("The size of the resource pack.");
default: default:
@ -185,11 +190,6 @@ int ResourcePackFolderModel::columnCount(const QModelIndex& parent) const
return parent.isValid() ? 0 : NUM_COLUMNS; return parent.isValid() ? 0 : NUM_COLUMNS;
} }
Task* ResourcePackFolderModel::createUpdateTask()
{
return new BasicFolderLoadTask(m_dir, [](QFileInfo const& entry) { return makeShared<ResourcePack>(entry); });
}
Task* ResourcePackFolderModel::createParseTask(Resource& resource) Task* ResourcePackFolderModel::createParseTask(Resource& resource)
{ {
return new LocalResourcePackParseTask(m_next_resolution_ticket, static_cast<ResourcePack&>(resource)); return new LocalResourcePackParseTask(m_next_resolution_ticket, static_cast<ResourcePack&>(resource));

View File

@ -7,18 +7,18 @@
class ResourcePackFolderModel : public ResourceFolderModel { class ResourcePackFolderModel : public ResourceFolderModel {
Q_OBJECT Q_OBJECT
public: public:
enum Columns { ActiveColumn = 0, ImageColumn, NameColumn, PackFormatColumn, DateColumn, SizeColumn, NUM_COLUMNS }; enum Columns { ActiveColumn = 0, ImageColumn, NameColumn, PackFormatColumn, DateColumn, ProviderColumn, SizeColumn, NUM_COLUMNS };
explicit ResourcePackFolderModel(const QString& dir, BaseInstance* instance); explicit ResourcePackFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent = nullptr);
virtual QString id() const override { return "resourcepacks"; } QString id() const override { return "resourcepacks"; }
[[nodiscard]] QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; [[nodiscard]] QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
[[nodiscard]] QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; [[nodiscard]] QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
[[nodiscard]] int columnCount(const QModelIndex& parent) const override; [[nodiscard]] int columnCount(const QModelIndex& parent) const override;
[[nodiscard]] Task* createUpdateTask() override; [[nodiscard]] Resource* createResource(const QFileInfo& file) override { return new ResourcePack(file); }
[[nodiscard]] Task* createParseTask(Resource&) override; [[nodiscard]] Task* createParseTask(Resource&) override;
RESOURCE_HELPERS(ResourcePack) RESOURCE_HELPERS(ResourcePack)

View File

@ -2,24 +2,24 @@
#include "ResourceFolderModel.h" #include "ResourceFolderModel.h"
#include "minecraft/mod/ShaderPack.h" #include "minecraft/mod/ShaderPack.h"
#include "minecraft/mod/tasks/BasicFolderLoadTask.h"
#include "minecraft/mod/tasks/LocalShaderPackParseTask.h" #include "minecraft/mod/tasks/LocalShaderPackParseTask.h"
class ShaderPackFolderModel : public ResourceFolderModel { class ShaderPackFolderModel : public ResourceFolderModel {
Q_OBJECT Q_OBJECT
public: public:
explicit ShaderPackFolderModel(const QString& dir, BaseInstance* instance) : ResourceFolderModel(QDir(dir), instance) {} explicit ShaderPackFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent = nullptr)
: ResourceFolderModel(dir, instance, is_indexed, create_dir, parent)
{}
virtual QString id() const override { return "shaderpacks"; } virtual QString id() const override { return "shaderpacks"; }
[[nodiscard]] Task* createUpdateTask() override [[nodiscard]] Resource* createResource(const QFileInfo& info) override { return new ShaderPack(info); }
{
return new BasicFolderLoadTask(m_dir, [](QFileInfo const& entry) { return makeShared<ShaderPack>(entry); });
}
[[nodiscard]] Task* createParseTask(Resource& resource) override [[nodiscard]] Task* createParseTask(Resource& resource) override
{ {
return new LocalShaderPackParseTask(m_next_resolution_ticket, static_cast<ShaderPack&>(resource)); return new LocalShaderPackParseTask(m_next_resolution_ticket, static_cast<ShaderPack&>(resource));
} }
RESOURCE_HELPERS(ShaderPack);
}; };

View File

@ -39,22 +39,18 @@
#include "TexturePackFolderModel.h" #include "TexturePackFolderModel.h"
#include "minecraft/mod/tasks/BasicFolderLoadTask.h"
#include "minecraft/mod/tasks/LocalTexturePackParseTask.h" #include "minecraft/mod/tasks/LocalTexturePackParseTask.h"
#include "minecraft/mod/tasks/ResourceFolderLoadTask.h"
TexturePackFolderModel::TexturePackFolderModel(const QString& dir, BaseInstance* instance) : ResourceFolderModel(QDir(dir), instance) TexturePackFolderModel::TexturePackFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent)
: ResourceFolderModel(QDir(dir), instance, is_indexed, create_dir, parent)
{ {
m_column_names = QStringList({ "Enable", "Image", "Name", "Last Modified", "Size" }); m_column_names = QStringList({ "Enable", "Image", "Name", "Last Modified", "Provider", "Size" });
m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Last Modified"), tr("Size") }); m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Last Modified"), tr("Provider"), tr("Size") });
m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::DATE, SortType::SIZE }; m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::DATE, SortType::PROVIDER, SortType::SIZE };
m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive, m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive };
QHeaderView::Interactive }; m_columnsHideable = { false, true, false, true, true, true };
m_columnsHideable = { false, true, false, true, true }; m_columnsHiddenByDefault = { false, false, false, false, false, true };
}
Task* TexturePackFolderModel::createUpdateTask()
{
return new BasicFolderLoadTask(m_dir, [](QFileInfo const& entry) { return makeShared<TexturePack>(entry); });
} }
Task* TexturePackFolderModel::createParseTask(Resource& resource) Task* TexturePackFolderModel::createParseTask(Resource& resource)
@ -77,6 +73,8 @@ QVariant TexturePackFolderModel::data(const QModelIndex& index, int role) const
return m_resources[row]->name(); return m_resources[row]->name();
case DateColumn: case DateColumn:
return m_resources[row]->dateTimeChanged(); return m_resources[row]->dateTimeChanged();
case ProviderColumn:
return m_resources[row]->provider();
case SizeColumn: case SizeColumn:
return m_resources[row]->sizeStr(); return m_resources[row]->sizeStr();
default: default:
@ -84,14 +82,14 @@ QVariant TexturePackFolderModel::data(const QModelIndex& index, int role) const
} }
case Qt::ToolTipRole: case Qt::ToolTipRole:
if (column == NameColumn) { if (column == NameColumn) {
if (at(row)->isSymLinkUnder(instDirPath())) { if (at(row).isSymLinkUnder(instDirPath())) {
return m_resources[row]->internal_id() + return m_resources[row]->internal_id() +
tr("\nWarning: This resource is symbolically linked from elsewhere. Editing it will also change the original." tr("\nWarning: This resource is symbolically linked from elsewhere. Editing it will also change the original."
"\nCanonical Path: %1") "\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() + return m_resources[row]->internal_id() +
tr("\nWarning: This resource is hard linked elsewhere. Editing it will also change the original."); tr("\nWarning: This resource is hard linked elsewhere. Editing it will also change the original.");
} }
@ -99,10 +97,10 @@ QVariant TexturePackFolderModel::data(const QModelIndex& index, int role) const
return m_resources[row]->internal_id(); return m_resources[row]->internal_id();
case Qt::DecorationRole: { 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"); return APPLICATION->getThemedIcon("status-yellow");
if (column == ImageColumn) { if (column == ImageColumn) {
return at(row)->image({ 32, 32 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding); return at(row).image({ 32, 32 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding);
} }
return {}; return {};
} }
@ -130,6 +128,7 @@ QVariant TexturePackFolderModel::headerData(int section, [[maybe_unused]] Qt::Or
case NameColumn: case NameColumn:
case DateColumn: case DateColumn:
case ImageColumn: case ImageColumn:
case ProviderColumn:
case SizeColumn: case SizeColumn:
return columnNames().at(section); return columnNames().at(section);
default: default:
@ -138,14 +137,13 @@ QVariant TexturePackFolderModel::headerData(int section, [[maybe_unused]] Qt::Or
case Qt::ToolTipRole: { case Qt::ToolTipRole: {
switch (section) { switch (section) {
case ActiveColumn: case ActiveColumn:
//: Here, resource is a generic term for external resources, like Mods, Resource Packs, Shader Packs, etc.
return tr("Is the texture pack enabled?"); return tr("Is the texture pack enabled?");
case NameColumn: case NameColumn:
//: Here, resource is a generic term for external resources, like Mods, Resource Packs, Shader Packs, etc.
return tr("The name of the texture pack."); return tr("The name of the texture pack.");
case DateColumn: case DateColumn:
//: Here, resource is a generic term for external resources, like Mods, Resource Packs, Shader Packs, etc.
return tr("The date and time this texture pack was last changed (or added)."); return tr("The date and time this texture pack was last changed (or added).");
case ProviderColumn:
return tr("The source provider of the texture pack.");
case SizeColumn: case SizeColumn:
return tr("The size of the texture pack."); return tr("The size of the texture pack.");
default: default:

View File

@ -44,9 +44,9 @@ class TexturePackFolderModel : public ResourceFolderModel {
Q_OBJECT Q_OBJECT
public: public:
enum Columns { ActiveColumn = 0, ImageColumn, NameColumn, DateColumn, SizeColumn, NUM_COLUMNS }; enum Columns { ActiveColumn = 0, ImageColumn, NameColumn, DateColumn, ProviderColumn, SizeColumn, NUM_COLUMNS };
explicit TexturePackFolderModel(const QString& dir, std::shared_ptr<const BaseInstance> instance); explicit TexturePackFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent = nullptr);
virtual QString id() const override { return "texturepacks"; } virtual QString id() const override { return "texturepacks"; }
@ -55,8 +55,7 @@ class TexturePackFolderModel : public ResourceFolderModel {
[[nodiscard]] QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; [[nodiscard]] QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
[[nodiscard]] int columnCount(const QModelIndex& parent) const override; [[nodiscard]] int columnCount(const QModelIndex& parent) const override;
explicit TexturePackFolderModel(const QString& dir, BaseInstance* instance); [[nodiscard]] Resource* createResource(const QFileInfo& file) override { return new TexturePack(file); }
[[nodiscard]] Task* createUpdateTask() override;
[[nodiscard]] Task* createParseTask(Resource&) override; [[nodiscard]] Task* createParseTask(Resource&) override;
RESOURCE_HELPERS(TexturePack) RESOURCE_HELPERS(TexturePack)

View File

@ -47,11 +47,6 @@ class LocalModParseTask : public Task {
[[nodiscard]] int token() const { return m_token; } [[nodiscard]] int token() const { return m_token; }
private:
void processAsZip();
void processAsFolder();
void processAsLitemod();
private: private:
int m_token; int m_token;
ResourceType m_type; ResourceType m_type;

View File

@ -17,7 +17,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
#include "LocalModUpdateTask.h" #include "LocalResourceUpdateTask.h"
#include "FileSystem.h" #include "FileSystem.h"
#include "minecraft/mod/MetadataHandler.h" #include "minecraft/mod/MetadataHandler.h"
@ -26,12 +26,12 @@
#include <windows.h> #include <windows.h>
#endif #endif
LocalModUpdateTask::LocalModUpdateTask(QDir index_dir, ModPlatform::IndexedPack& mod, ModPlatform::IndexedVersion& mod_version) LocalResourceUpdateTask::LocalResourceUpdateTask(QDir index_dir, ModPlatform::IndexedPack& project, ModPlatform::IndexedVersion& version)
: m_index_dir(index_dir), m_mod(mod), m_mod_version(mod_version) : m_index_dir(index_dir), m_project(project), m_version(version)
{ {
// Ensure a '.index' folder exists in the mods folder, and create it if it does not // Ensure a '.index' folder exists in the mods folder, and create it if it does not
if (!FS::ensureFolderPathExists(index_dir.path())) { if (!FS::ensureFolderPathExists(index_dir.path())) {
emitFailed(QString("Unable to create index for mod %1!").arg(m_mod.name)); emitFailed(QString("Unable to create index directory at %1!").arg(index_dir.absolutePath()));
} }
#ifdef Q_OS_WIN32 #ifdef Q_OS_WIN32
@ -39,28 +39,28 @@ LocalModUpdateTask::LocalModUpdateTask(QDir index_dir, ModPlatform::IndexedPack&
#endif #endif
} }
void LocalModUpdateTask::executeTask() void LocalResourceUpdateTask::executeTask()
{ {
setStatus(tr("Updating index for mod:\n%1").arg(m_mod.name)); setStatus(tr("Updating index for resource:\n%1").arg(m_project.name));
auto old_metadata = Metadata::get(m_index_dir, m_mod.addonId); auto old_metadata = Metadata::get(m_index_dir, m_project.addonId);
if (old_metadata.isValid()) { if (old_metadata.isValid()) {
emit hasOldMod(old_metadata.name, old_metadata.filename); emit hasOldResource(old_metadata.name, old_metadata.filename);
if (m_mod.slug.isEmpty()) if (m_project.slug.isEmpty())
m_mod.slug = old_metadata.slug; m_project.slug = old_metadata.slug;
} }
auto pw_mod = Metadata::create(m_index_dir, m_mod, m_mod_version); auto pw_mod = Metadata::create(m_index_dir, m_project, m_version);
if (pw_mod.isValid()) { if (pw_mod.isValid()) {
Metadata::update(m_index_dir, pw_mod); Metadata::update(m_index_dir, pw_mod);
emitSucceeded(); emitSucceeded();
} else { } else {
qCritical() << "Tried to update an invalid mod!"; qCritical() << "Tried to update an invalid resource!";
emitFailed(tr("Invalid metadata")); emitFailed(tr("Invalid metadata"));
} }
} }
auto LocalModUpdateTask::abort() -> bool auto LocalResourceUpdateTask::abort() -> bool
{ {
emitAborted(); emitAborted();
return true; return true;

View File

@ -23,12 +23,12 @@
#include "modplatform/ModIndex.h" #include "modplatform/ModIndex.h"
#include "tasks/Task.h" #include "tasks/Task.h"
class LocalModUpdateTask : public Task { class LocalResourceUpdateTask : public Task {
Q_OBJECT Q_OBJECT
public: public:
using Ptr = shared_qobject_ptr<LocalModUpdateTask>; using Ptr = shared_qobject_ptr<LocalResourceUpdateTask>;
explicit LocalModUpdateTask(QDir index_dir, ModPlatform::IndexedPack& mod, ModPlatform::IndexedVersion& mod_version); explicit LocalResourceUpdateTask(QDir index_dir, ModPlatform::IndexedPack& project, ModPlatform::IndexedVersion& version);
auto canAbort() const -> bool override { return true; } auto canAbort() const -> bool override { return true; }
auto abort() -> bool override; auto abort() -> bool override;
@ -38,10 +38,10 @@ class LocalModUpdateTask : public Task {
void executeTask() override; void executeTask() override;
signals: signals:
void hasOldMod(QString name, QString filename); void hasOldResource(QString name, QString filename);
private: private:
QDir m_index_dir; QDir m_index_dir;
ModPlatform::IndexedPack m_mod; ModPlatform::IndexedPack m_project;
ModPlatform::IndexedVersion m_mod_version; ModPlatform::IndexedVersion m_version;
}; };

View File

@ -34,7 +34,7 @@
* limitations under the License. * limitations under the License.
*/ */
#include "ModFolderLoadTask.h" #include "ResourceFolderLoadTask.h"
#include "Application.h" #include "Application.h"
#include "FileSystem.h" #include "FileSystem.h"
@ -42,17 +42,22 @@
#include <QThread> #include <QThread>
ModFolderLoadTask::ModFolderLoadTask(QDir mods_dir, QDir index_dir, bool is_indexed, bool clean_orphan) ResourceFolderLoadTask::ResourceFolderLoadTask(const QDir& resource_dir,
const QDir& index_dir,
bool is_indexed,
bool clean_orphan,
std::function<Resource*(const QFileInfo&)> create_function)
: Task(nullptr, false) : Task(nullptr, false)
, m_mods_dir(mods_dir) , m_resource_dir(resource_dir)
, m_index_dir(index_dir) , m_index_dir(index_dir)
, m_is_indexed(is_indexed) , m_is_indexed(is_indexed)
, m_clean_orphan(clean_orphan) , m_clean_orphan(clean_orphan)
, m_create_func(create_function)
, m_result(new Result()) , m_result(new Result())
, m_thread_to_spawn_into(thread()) , m_thread_to_spawn_into(thread())
{} {}
void ModFolderLoadTask::executeTask() void ResourceFolderLoadTask::executeTask()
{ {
if (thread() != m_thread_to_spawn_into) if (thread() != m_thread_to_spawn_into)
connect(this, &Task::finished, this->thread(), &QThread::quit); connect(this, &Task::finished, this->thread(), &QThread::quit);
@ -63,8 +68,8 @@ void ModFolderLoadTask::executeTask()
} }
// Read JAR files that don't have metadata // Read JAR files that don't have metadata
m_mods_dir.refresh(); m_resource_dir.refresh();
for (auto entry : m_mods_dir.entryInfoList()) { for (auto entry : m_resource_dir.entryInfoList()) {
auto filePath = entry.absoluteFilePath(); auto filePath = entry.absoluteFilePath();
if (auto app = APPLICATION_DYN; app && app->checkQSavePath(filePath)) { if (auto app = APPLICATION_DYN; app && app->checkQSavePath(filePath)) {
continue; continue;
@ -74,32 +79,33 @@ void ModFolderLoadTask::executeTask()
FS::move(filePath, newFilePath); FS::move(filePath, newFilePath);
entry = QFileInfo(newFilePath); entry = QFileInfo(newFilePath);
} }
Mod* mod(new Mod(entry));
if (mod->enabled()) { Resource* resource = m_create_func(entry);
if (m_result->mods.contains(mod->internal_id())) {
m_result->mods[mod->internal_id()]->setStatus(ModStatus::Installed); if (resource->enabled()) {
if (m_result->resources.contains(resource->internal_id())) {
m_result->resources[resource->internal_id()]->setStatus(ResourceStatus::INSTALLED);
// Delete the object we just created, since a valid one is already in the mods list. // Delete the object we just created, since a valid one is already in the mods list.
delete mod; delete resource;
} else { } else {
m_result->mods[mod->internal_id()].reset(std::move(mod)); m_result->resources[resource->internal_id()].reset(resource);
m_result->mods[mod->internal_id()]->setStatus(ModStatus::NoMetadata); m_result->resources[resource->internal_id()]->setStatus(ResourceStatus::NO_METADATA);
} }
} else { } else {
QString chopped_id = mod->internal_id().chopped(9); QString chopped_id = resource->internal_id().chopped(9);
if (m_result->mods.contains(chopped_id)) { if (m_result->resources.contains(chopped_id)) {
m_result->mods[mod->internal_id()].reset(std::move(mod)); m_result->resources[resource->internal_id()].reset(resource);
auto metadata = m_result->mods[chopped_id]->metadata(); auto metadata = m_result->resources[chopped_id]->metadata();
if (metadata) { if (metadata) {
mod->setMetadata(*metadata); resource->setMetadata(*metadata);
m_result->mods[mod->internal_id()]->setStatus(ModStatus::Installed); m_result->resources[resource->internal_id()]->setStatus(ResourceStatus::INSTALLED);
m_result->mods.remove(chopped_id); m_result->resources.remove(chopped_id);
} }
} else { } else {
m_result->mods[mod->internal_id()].reset(std::move(mod)); m_result->resources[resource->internal_id()].reset(resource);
m_result->mods[mod->internal_id()]->setStatus(ModStatus::NoMetadata); m_result->resources[resource->internal_id()]->setStatus(ResourceStatus::NO_METADATA);
} }
} }
} }
@ -107,17 +113,17 @@ void ModFolderLoadTask::executeTask()
// Remove orphan metadata to prevent issues // Remove orphan metadata to prevent issues
// See https://github.com/PolyMC/PolyMC/issues/996 // See https://github.com/PolyMC/PolyMC/issues/996
if (m_clean_orphan) { if (m_clean_orphan) {
QMutableMapIterator iter(m_result->mods); QMutableMapIterator iter(m_result->resources);
while (iter.hasNext()) { while (iter.hasNext()) {
auto mod = iter.next().value(); auto resource = iter.next().value();
if (mod->status() == ModStatus::NotInstalled) { if (resource->status() == ResourceStatus::NOT_INSTALLED) {
mod->destroy(m_index_dir, false, false); resource->destroy(m_index_dir, false, false);
iter.remove(); iter.remove();
} }
} }
} }
for (auto mod : m_result->mods) for (auto mod : m_result->resources)
mod->moveToThread(m_thread_to_spawn_into); mod->moveToThread(m_thread_to_spawn_into);
if (m_aborted) if (m_aborted)
@ -126,18 +132,18 @@ void ModFolderLoadTask::executeTask()
emitSucceeded(); emitSucceeded();
} }
void ModFolderLoadTask::getFromMetadata() void ResourceFolderLoadTask::getFromMetadata()
{ {
m_index_dir.refresh(); m_index_dir.refresh();
for (auto entry : m_index_dir.entryList(QDir::Files)) { for (auto entry : m_index_dir.entryList(QDir::Files)) {
auto metadata = Metadata::get(m_index_dir, entry); auto metadata = Metadata::get(m_index_dir, entry);
if (!metadata.isValid()) { if (!metadata.isValid())
continue; continue;
}
auto* mod = new Mod(m_mods_dir, metadata); auto* resource = m_create_func(QFileInfo(m_resource_dir.filePath(metadata.filename)));
mod->setStatus(ModStatus::NotInstalled); resource->setMetadata(metadata);
m_result->mods[mod->internal_id()].reset(std::move(mod)); resource->setStatus(ResourceStatus::NOT_INSTALLED);
m_result->resources[resource->internal_id()].reset(resource);
} }
} }

View File

@ -44,17 +44,21 @@
#include "minecraft/mod/Mod.h" #include "minecraft/mod/Mod.h"
#include "tasks/Task.h" #include "tasks/Task.h"
class ModFolderLoadTask : public Task { class ResourceFolderLoadTask : public Task {
Q_OBJECT Q_OBJECT
public: public:
struct Result { struct Result {
QMap<QString, Mod::Ptr> mods; QMap<QString, Resource::Ptr> resources;
}; };
using ResultPtr = std::shared_ptr<Result>; using ResultPtr = std::shared_ptr<Result>;
ResultPtr result() const { return m_result; } ResultPtr result() const { return m_result; }
public: public:
ModFolderLoadTask(QDir mods_dir, QDir index_dir, bool is_indexed, bool clean_orphan = false); ResourceFolderLoadTask(const QDir& resource_dir,
const QDir& index_dir,
bool is_indexed,
bool clean_orphan,
std::function<Resource*(const QFileInfo&)> create_function);
[[nodiscard]] bool canAbort() const override { return true; } [[nodiscard]] bool canAbort() const override { return true; }
bool abort() override bool abort() override
@ -69,9 +73,10 @@ class ModFolderLoadTask : public Task {
void getFromMetadata(); void getFromMetadata();
private: private:
QDir m_mods_dir, m_index_dir; QDir m_resource_dir, m_index_dir;
bool m_is_indexed; bool m_is_indexed;
bool m_clean_orphan; bool m_clean_orphan;
std::function<Resource*(QFileInfo const&)> m_create_func;
ResultPtr m_result; ResultPtr m_result;
std::atomic<bool> m_aborted = false; std::atomic<bool> m_aborted = false;

View File

@ -3,6 +3,7 @@
#include "minecraft/mod/Mod.h" #include "minecraft/mod/Mod.h"
#include "minecraft/mod/tasks/GetModDependenciesTask.h" #include "minecraft/mod/tasks/GetModDependenciesTask.h"
#include "modplatform/ModIndex.h" #include "modplatform/ModIndex.h"
#include "modplatform/ResourceAPI.h"
#include "tasks/Task.h" #include "tasks/Task.h"
class ResourceDownloadTask; class ResourceDownloadTask;
@ -12,13 +13,18 @@ class CheckUpdateTask : public Task {
Q_OBJECT Q_OBJECT
public: public:
CheckUpdateTask(QList<Mod*>& mods, CheckUpdateTask(QList<Resource*>& resources,
std::list<Version>& mcVersions, std::list<Version>& mcVersions,
QList<ModPlatform::ModLoaderType> loadersList, QList<ModPlatform::ModLoaderType> loadersList,
std::shared_ptr<ModFolderModel> mods_folder) std::shared_ptr<ResourceFolderModel> resourceModel)
: Task(nullptr), m_mods(mods), m_game_versions(mcVersions), m_loaders_list(loadersList), m_mods_folder(mods_folder) {}; : Task()
, m_resources(resources)
, m_game_versions(mcVersions)
, m_loaders_list(std::move(loadersList))
, m_resource_model(std::move(resourceModel))
{}
struct UpdatableMod { struct Update {
QString name; QString name;
QString old_hash; QString old_hash;
QString old_version; QString old_version;
@ -30,28 +36,28 @@ class CheckUpdateTask : public Task {
bool enabled = true; bool enabled = true;
public: public:
UpdatableMod(QString name, Update(QString name,
QString old_h, QString old_h,
QString old_v, QString old_v,
QString new_v, QString new_v,
std::optional<ModPlatform::IndexedVersionType> new_v_type, std::optional<ModPlatform::IndexedVersionType> new_v_type,
QString changelog, QString changelog,
ModPlatform::ResourceProvider p, ModPlatform::ResourceProvider p,
shared_qobject_ptr<ResourceDownloadTask> t, shared_qobject_ptr<ResourceDownloadTask> t,
bool enabled = true) bool enabled = true)
: name(name) : name(std::move(name))
, old_hash(old_h) , old_hash(std::move(old_h))
, old_version(old_v) , old_version(std::move(old_v))
, new_version(new_v) , new_version(std::move(new_v))
, new_version_type(new_v_type) , new_version_type(std::move(new_v_type))
, changelog(changelog) , changelog(std::move(changelog))
, provider(p) , provider(p)
, download(t) , download(std::move(t))
, enabled(enabled) , enabled(enabled)
{} {}
}; };
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); } auto getDependencies() -> QList<std::shared_ptr<GetModDependenciesTask::PackDependency>>&& { return std::move(m_deps); }
public slots: public slots:
@ -61,14 +67,14 @@ class CheckUpdateTask : public Task {
void executeTask() override = 0; void executeTask() override = 0;
signals: signals:
void checkFailed(Mod* failed, QString reason, QUrl recover_url = {}); void checkFailed(Resource* failed, QString reason, QUrl recover_url = {});
protected: protected:
QList<Mod*>& m_mods; QList<Resource*>& m_resources;
std::list<Version>& m_game_versions; std::list<Version>& m_game_versions;
QList<ModPlatform::ModLoaderType> m_loaders_list; QList<ModPlatform::ModLoaderType> m_loaders_list;
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; QList<std::shared_ptr<GetModDependenciesTask::PackDependency>> m_deps;
}; };

View File

@ -7,7 +7,7 @@
#include "Json.h" #include "Json.h"
#include "minecraft/mod/Mod.h" #include "minecraft/mod/Mod.h"
#include "minecraft/mod/tasks/LocalModUpdateTask.h" #include "minecraft/mod/tasks/LocalResourceUpdateTask.h"
#include "modplatform/flame/FlameAPI.h" #include "modplatform/flame/FlameAPI.h"
#include "modplatform/flame/FlameModIndex.h" #include "modplatform/flame/FlameModIndex.h"
@ -18,55 +18,56 @@
static ModrinthAPI modrinth_api; static ModrinthAPI modrinth_api;
static FlameAPI flame_api; static FlameAPI flame_api;
EnsureMetadataTask::EnsureMetadataTask(Mod* mod, QDir dir, ModPlatform::ResourceProvider prov) EnsureMetadataTask::EnsureMetadataTask(Resource* resource, QDir dir, ModPlatform::ResourceProvider prov)
: Task(nullptr), m_index_dir(dir), m_provider(prov), m_hashing_task(nullptr), m_current_task(nullptr) : Task(nullptr), m_index_dir(dir), m_provider(prov), m_hashing_task(nullptr), m_current_task(nullptr)
{ {
auto hash_task = createNewHash(mod); auto hash_task = createNewHash(resource);
if (!hash_task) if (!hash_task)
return; return;
connect(hash_task.get(), &Hashing::Hasher::resultsReady, [this, mod](QString hash) { m_mods.insert(hash, mod); }); connect(hash_task.get(), &Hashing::Hasher::resultsReady, [this, resource](QString hash) { m_resources.insert(hash, resource); });
connect(hash_task.get(), &Task::failed, [this, mod] { emitFail(mod, "", RemoveFromList::No); }); connect(hash_task.get(), &Task::failed, [this, resource] { emitFail(resource, "", RemoveFromList::No); });
hash_task->start(); hash_task->start();
} }
EnsureMetadataTask::EnsureMetadataTask(QList<Mod*>& mods, QDir dir, ModPlatform::ResourceProvider prov) EnsureMetadataTask::EnsureMetadataTask(QList<Resource*>& resources, QDir dir, ModPlatform::ResourceProvider prov)
: Task(nullptr), m_index_dir(dir), m_provider(prov), m_current_task(nullptr) : Task(nullptr), m_index_dir(dir), m_provider(prov), m_current_task(nullptr)
{ {
m_hashing_task.reset(new ConcurrentTask(this, "MakeHashesTask", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt())); m_hashing_task.reset(new ConcurrentTask(this, "MakeHashesTask", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt()));
for (auto* mod : mods) { for (auto* resource : resources) {
auto hash_task = createNewHash(mod); auto hash_task = createNewHash(resource);
if (!hash_task) if (!hash_task)
continue; continue;
connect(hash_task.get(), &Hashing::Hasher::resultsReady, [this, mod](QString hash) { m_mods.insert(hash, mod); }); connect(hash_task.get(), &Hashing::Hasher::resultsReady, [this, resource](QString hash) { m_resources.insert(hash, resource); });
connect(hash_task.get(), &Task::failed, [this, mod] { emitFail(mod, "", RemoveFromList::No); }); connect(hash_task.get(), &Task::failed, [this, resource] { emitFail(resource, "", RemoveFromList::No); });
m_hashing_task->addTask(hash_task); m_hashing_task->addTask(hash_task);
} }
} }
EnsureMetadataTask::EnsureMetadataTask(QHash<QString, Mod*>& mods, QDir dir, ModPlatform::ResourceProvider prov)
: Task(nullptr), m_mods(mods), m_index_dir(dir), m_provider(prov), m_current_task(nullptr) EnsureMetadataTask::EnsureMetadataTask(QHash<QString, Resource*>& resources, QDir dir, ModPlatform::ResourceProvider prov)
: Task(nullptr), m_resources(resources), m_index_dir(dir), m_provider(prov), m_current_task(nullptr)
{} {}
Hashing::Hasher::Ptr EnsureMetadataTask::createNewHash(Mod* mod) Hashing::Hasher::Ptr EnsureMetadataTask::createNewHash(Resource* resource)
{ {
if (!mod || !mod->valid() || mod->type() == ResourceType::FOLDER) if (!resource || !resource->valid() || resource->type() == ResourceType::FOLDER)
return nullptr; return nullptr;
return Hashing::createHasher(mod->fileinfo().absoluteFilePath(), m_provider); return Hashing::createHasher(resource->fileinfo().absoluteFilePath(), m_provider);
} }
QString EnsureMetadataTask::getExistingHash(Mod* mod) QString EnsureMetadataTask::getExistingHash(Resource* resource)
{ {
// Check for already computed hashes // Check for already computed hashes
// (linear on the number of mods vs. linear on the size of the mod's JAR) // (linear on the number of mods vs. linear on the size of the mod's JAR)
auto it = m_mods.keyValueBegin(); auto it = m_resources.keyValueBegin();
while (it != m_mods.keyValueEnd()) { while (it != m_resources.keyValueEnd()) {
if ((*it).second == mod) if ((*it).second == resource)
break; break;
it++; it++;
} }
// We already have the hash computed // We already have the hash computed
if (it != m_mods.keyValueEnd()) { if (it != m_resources.keyValueEnd()) {
return (*it).first; return (*it).first;
} }
@ -86,25 +87,25 @@ bool EnsureMetadataTask::abort()
void EnsureMetadataTask::executeTask() void EnsureMetadataTask::executeTask()
{ {
setStatus(tr("Checking if mods have metadata...")); setStatus(tr("Checking if resources have metadata..."));
for (auto* mod : m_mods) { for (auto* resource : m_resources) {
if (!mod->valid()) { if (!resource->valid()) {
qDebug() << "Mod" << mod->name() << "is invalid!"; qDebug() << "Resource" << resource->name() << "is invalid!";
emitFail(mod); emitFail(resource);
continue; continue;
} }
// They already have the right metadata :o // They already have the right metadata :o
if (mod->status() != ModStatus::NoMetadata && mod->metadata() && mod->metadata()->provider == m_provider) { if (resource->status() != ResourceStatus::NO_METADATA && resource->metadata() && resource->metadata()->provider == m_provider) {
qDebug() << "Mod" << mod->name() << "already has metadata!"; qDebug() << "Resource" << resource->name() << "already has metadata!";
emitReady(mod); emitReady(resource);
continue; continue;
} }
// Folders don't have metadata // Folders don't have metadata
if (mod->type() == ResourceType::FOLDER) { if (resource->type() == ResourceType::FOLDER) {
emitReady(mod); emitReady(resource);
} }
} }
@ -120,9 +121,9 @@ void EnsureMetadataTask::executeTask()
} }
auto invalidade_leftover = [this] { auto invalidade_leftover = [this] {
for (auto mod = m_mods.constBegin(); mod != m_mods.constEnd(); mod++) for (auto resource = m_resources.constBegin(); resource != m_resources.constEnd(); resource++)
emitFail(mod.value(), mod.key(), RemoveFromList::No); emitFail(resource.value(), resource.key(), RemoveFromList::No);
m_mods.clear(); m_resources.clear();
emitSucceeded(); emitSucceeded();
}; };
@ -162,53 +163,53 @@ void EnsureMetadataTask::executeTask()
m_current_task.reset(); m_current_task.reset();
}); });
if (m_mods.size() > 1) if (m_resources.size() > 1)
setStatus(tr("Requesting metadata information from %1...").arg(ModPlatform::ProviderCapabilities::readableName(m_provider))); setStatus(tr("Requesting metadata information from %1...").arg(ModPlatform::ProviderCapabilities::readableName(m_provider)));
else if (!m_mods.empty()) else if (!m_resources.empty())
setStatus(tr("Requesting metadata information from %1 for '%2'...") setStatus(tr("Requesting metadata information from %1 for '%2'...")
.arg(ModPlatform::ProviderCapabilities::readableName(m_provider), m_mods.begin().value()->name())); .arg(ModPlatform::ProviderCapabilities::readableName(m_provider), m_resources.begin().value()->name()));
m_current_task = version_task; m_current_task = version_task;
version_task->start(); version_task->start();
} }
void EnsureMetadataTask::emitReady(Mod* m, QString key, RemoveFromList remove) void EnsureMetadataTask::emitReady(Resource* resource, QString key, RemoveFromList remove)
{ {
if (!m) { if (!resource) {
qCritical() << "Tried to mark a null mod as ready."; qCritical() << "Tried to mark a null resource as ready.";
if (!key.isEmpty()) if (!key.isEmpty())
m_mods.remove(key); m_resources.remove(key);
return; return;
} }
qDebug() << QString("Generated metadata for %1").arg(m->name()); qDebug() << QString("Generated metadata for %1").arg(resource->name());
emit metadataReady(m); emit metadataReady(resource);
if (remove == RemoveFromList::Yes) { if (remove == RemoveFromList::Yes) {
if (key.isEmpty()) if (key.isEmpty())
key = getExistingHash(m); key = getExistingHash(resource);
m_mods.remove(key); m_resources.remove(key);
} }
} }
void EnsureMetadataTask::emitFail(Mod* m, QString key, RemoveFromList remove) void EnsureMetadataTask::emitFail(Resource* resource, QString key, RemoveFromList remove)
{ {
if (!m) { if (!resource) {
qCritical() << "Tried to mark a null mod as failed."; qCritical() << "Tried to mark a null resource as failed.";
if (!key.isEmpty()) if (!key.isEmpty())
m_mods.remove(key); m_resources.remove(key);
return; return;
} }
qDebug() << QString("Failed to generate metadata for %1").arg(m->name()); qDebug() << QString("Failed to generate metadata for %1").arg(resource->name());
emit metadataFailed(m); emit metadataFailed(resource);
if (remove == RemoveFromList::Yes) { if (remove == RemoveFromList::Yes) {
if (key.isEmpty()) if (key.isEmpty())
key = getExistingHash(m); key = getExistingHash(resource);
m_mods.remove(key); m_resources.remove(key);
} }
} }
@ -219,7 +220,7 @@ Task::Ptr EnsureMetadataTask::modrinthVersionsTask()
auto hash_type = ModPlatform::ProviderCapabilities::hashType(ModPlatform::ResourceProvider::MODRINTH).first(); auto hash_type = ModPlatform::ProviderCapabilities::hashType(ModPlatform::ResourceProvider::MODRINTH).first();
auto response = std::make_shared<QByteArray>(); auto response = std::make_shared<QByteArray>();
auto ver_task = modrinth_api.currentVersions(m_mods.keys(), hash_type, response); auto ver_task = modrinth_api.currentVersions(m_resources.keys(), hash_type, response);
// Prevents unfortunate timings when aborting the task // Prevents unfortunate timings when aborting the task
if (!ver_task) if (!ver_task)
@ -239,20 +240,20 @@ Task::Ptr EnsureMetadataTask::modrinthVersionsTask()
try { try {
auto entries = Json::requireObject(doc); auto entries = Json::requireObject(doc);
for (auto& hash : m_mods.keys()) { for (auto& hash : m_resources.keys()) {
auto mod = m_mods.find(hash).value(); auto resource = m_resources.find(hash).value();
try { try {
auto entry = Json::requireObject(entries, hash); auto entry = Json::requireObject(entries, hash);
setStatus(tr("Parsing API response from Modrinth for '%1'...").arg(mod->name())); setStatus(tr("Parsing API response from Modrinth for '%1'...").arg(resource->name()));
qDebug() << "Getting version for" << mod->name() << "from Modrinth"; qDebug() << "Getting version for" << resource->name() << "from Modrinth";
m_temp_versions.insert(hash, Modrinth::loadIndexedPackVersion(entry)); m_temp_versions.insert(hash, Modrinth::loadIndexedPackVersion(entry));
} catch (Json::JsonException& e) { } catch (Json::JsonException& e) {
qDebug() << e.cause(); qDebug() << e.cause();
qDebug() << entries; qDebug() << entries;
emitFail(mod); emitFail(resource);
} }
} }
} catch (Json::JsonException& e) { } catch (Json::JsonException& e) {
@ -324,23 +325,23 @@ Task::Ptr EnsureMetadataTask::modrinthProjectsTask()
auto hash = addonIds.find(pack.addonId.toString()).value(); auto hash = addonIds.find(pack.addonId.toString()).value();
auto mod_iter = m_mods.find(hash); auto resource_iter = m_resources.find(hash);
if (mod_iter == m_mods.end()) { if (resource_iter == m_resources.end()) {
qWarning() << "Invalid project id from the API response."; qWarning() << "Invalid project id from the API response.";
continue; continue;
} }
auto* mod = mod_iter.value(); auto* resource = resource_iter.value();
try { try {
setStatus(tr("Parsing API response from Modrinth for '%1'...").arg(mod->name())); setStatus(tr("Parsing API response from Modrinth for '%1'...").arg(resource->name()));
modrinthCallback(pack, m_temp_versions.find(hash).value(), mod); modrinthCallback(pack, m_temp_versions.find(hash).value(), resource);
} catch (Json::JsonException& e) { } catch (Json::JsonException& e) {
qDebug() << e.cause(); qDebug() << e.cause();
qDebug() << entries; qDebug() << entries;
emitFail(mod); emitFail(resource);
} }
} }
}); });
@ -354,7 +355,7 @@ Task::Ptr EnsureMetadataTask::flameVersionsTask()
auto response = std::make_shared<QByteArray>(); auto response = std::make_shared<QByteArray>();
QList<uint> fingerprints; QList<uint> fingerprints;
for (auto& murmur : m_mods.keys()) { for (auto& murmur : m_resources.keys()) {
fingerprints.push_back(murmur.toUInt()); fingerprints.push_back(murmur.toUInt());
} }
@ -394,13 +395,13 @@ Task::Ptr EnsureMetadataTask::flameVersionsTask()
} }
auto fingerprint = QString::number(Json::ensureVariant(file_obj, "fileFingerprint").toUInt()); auto fingerprint = QString::number(Json::ensureVariant(file_obj, "fileFingerprint").toUInt());
auto mod = m_mods.find(fingerprint); auto resource = m_resources.find(fingerprint);
if (mod == m_mods.end()) { if (resource == m_resources.end()) {
qWarning() << "Invalid fingerprint from the API response."; qWarning() << "Invalid fingerprint from the API response.";
continue; continue;
} }
setStatus(tr("Parsing API response from CurseForge for '%1'...").arg((*mod)->name())); setStatus(tr("Parsing API response from CurseForge for '%1'...").arg((*resource)->name()));
m_temp_versions.insert(fingerprint, FlameMod::loadIndexedPackVersion(file_obj)); m_temp_versions.insert(fingerprint, FlameMod::loadIndexedPackVersion(file_obj));
} }
@ -417,7 +418,7 @@ Task::Ptr EnsureMetadataTask::flameVersionsTask()
Task::Ptr EnsureMetadataTask::flameProjectsTask() Task::Ptr EnsureMetadataTask::flameProjectsTask()
{ {
QHash<QString, QString> addonIds; QHash<QString, QString> addonIds;
for (auto const& hash : m_mods.keys()) { for (auto const& hash : m_resources.keys()) {
if (m_temp_versions.contains(hash)) { if (m_temp_versions.contains(hash)) {
auto data = m_temp_versions.find(hash).value(); auto data = m_temp_versions.find(hash).value();
@ -464,20 +465,20 @@ Task::Ptr EnsureMetadataTask::flameProjectsTask()
auto id = QString::number(Json::requireInteger(entry_obj, "id")); auto id = QString::number(Json::requireInteger(entry_obj, "id"));
auto hash = addonIds.find(id).value(); auto hash = addonIds.find(id).value();
auto mod = m_mods.find(hash).value(); auto resource = m_resources.find(hash).value();
try { try {
setStatus(tr("Parsing API response from CurseForge for '%1'...").arg(mod->name())); setStatus(tr("Parsing API response from CurseForge for '%1'...").arg(resource->name()));
ModPlatform::IndexedPack pack; ModPlatform::IndexedPack pack;
FlameMod::loadIndexedPack(pack, entry_obj); FlameMod::loadIndexedPack(pack, entry_obj);
flameCallback(pack, m_temp_versions.find(hash).value(), mod); flameCallback(pack, m_temp_versions.find(hash).value(), resource);
} catch (Json::JsonException& e) { } catch (Json::JsonException& e) {
qDebug() << e.cause(); qDebug() << e.cause();
qDebug() << entries; qDebug() << entries;
emitFail(mod); emitFail(resource);
} }
} }
} catch (Json::JsonException& e) { } catch (Json::JsonException& e) {
@ -489,17 +490,17 @@ Task::Ptr EnsureMetadataTask::flameProjectsTask()
return proj_task; return proj_task;
} }
void EnsureMetadataTask::modrinthCallback(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& ver, Mod* mod) void EnsureMetadataTask::modrinthCallback(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& ver, Resource* resource)
{ {
// Prevent file name mismatch // Prevent file name mismatch
ver.fileName = mod->fileinfo().fileName(); ver.fileName = resource->fileinfo().fileName();
if (ver.fileName.endsWith(".disabled")) if (ver.fileName.endsWith(".disabled"))
ver.fileName.chop(9); ver.fileName.chop(9);
QDir tmp_index_dir(m_index_dir); QDir tmp_index_dir(m_index_dir);
{ {
LocalModUpdateTask update_metadata(m_index_dir, pack, ver); LocalResourceUpdateTask update_metadata(m_index_dir, pack, ver);
QEventLoop loop; QEventLoop loop;
QObject::connect(&update_metadata, &Task::finished, &loop, &QEventLoop::quit); QObject::connect(&update_metadata, &Task::finished, &loop, &QEventLoop::quit);
@ -513,27 +514,27 @@ void EnsureMetadataTask::modrinthCallback(ModPlatform::IndexedPack& pack, ModPla
auto metadata = Metadata::get(tmp_index_dir, pack.slug); auto metadata = Metadata::get(tmp_index_dir, pack.slug);
if (!metadata.isValid()) { if (!metadata.isValid()) {
qCritical() << "Failed to generate metadata at last step!"; qCritical() << "Failed to generate metadata at last step!";
emitFail(mod); emitFail(resource);
return; return;
} }
mod->setMetadata(metadata); resource->setMetadata(metadata);
emitReady(mod); emitReady(resource);
} }
void EnsureMetadataTask::flameCallback(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& ver, Mod* mod) void EnsureMetadataTask::flameCallback(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& ver, Resource* resource)
{ {
try { try {
// Prevent file name mismatch // Prevent file name mismatch
ver.fileName = mod->fileinfo().fileName(); ver.fileName = resource->fileinfo().fileName();
if (ver.fileName.endsWith(".disabled")) if (ver.fileName.endsWith(".disabled"))
ver.fileName.chop(9); ver.fileName.chop(9);
QDir tmp_index_dir(m_index_dir); QDir tmp_index_dir(m_index_dir);
{ {
LocalModUpdateTask update_metadata(m_index_dir, pack, ver); LocalResourceUpdateTask update_metadata(m_index_dir, pack, ver);
QEventLoop loop; QEventLoop loop;
QObject::connect(&update_metadata, &Task::finished, &loop, &QEventLoop::quit); QObject::connect(&update_metadata, &Task::finished, &loop, &QEventLoop::quit);
@ -547,16 +548,16 @@ void EnsureMetadataTask::flameCallback(ModPlatform::IndexedPack& pack, ModPlatfo
auto metadata = Metadata::get(tmp_index_dir, pack.slug); auto metadata = Metadata::get(tmp_index_dir, pack.slug);
if (!metadata.isValid()) { if (!metadata.isValid()) {
qCritical() << "Failed to generate metadata at last step!"; qCritical() << "Failed to generate metadata at last step!";
emitFail(mod); emitFail(resource);
return; return;
} }
mod->setMetadata(metadata); resource->setMetadata(metadata);
emitReady(mod); emitReady(resource);
} catch (Json::JsonException& e) { } catch (Json::JsonException& e) {
qDebug() << e.cause(); qDebug() << e.cause();
emitFail(mod); emitFail(resource);
} }
} }

View File

@ -1,22 +1,23 @@
#pragma once #pragma once
#include "ModIndex.h" #include "ModIndex.h"
#include "net/NetJob.h"
#include "modplatform/helpers/HashUtils.h" #include "modplatform/helpers/HashUtils.h"
#include "minecraft/mod/Resource.h"
#include "tasks/ConcurrentTask.h" #include "tasks/ConcurrentTask.h"
#include <QDir>
class Mod; class Mod;
class QDir;
class EnsureMetadataTask : public Task { class EnsureMetadataTask : public Task {
Q_OBJECT Q_OBJECT
public: public:
EnsureMetadataTask(Mod*, QDir, ModPlatform::ResourceProvider = ModPlatform::ResourceProvider::MODRINTH); EnsureMetadataTask(Resource*, QDir, ModPlatform::ResourceProvider = ModPlatform::ResourceProvider::MODRINTH);
EnsureMetadataTask(QList<Mod*>&, QDir, ModPlatform::ResourceProvider = ModPlatform::ResourceProvider::MODRINTH); EnsureMetadataTask(QList<Resource*>&, QDir, ModPlatform::ResourceProvider = ModPlatform::ResourceProvider::MODRINTH);
EnsureMetadataTask(QHash<QString, Mod*>&, QDir, ModPlatform::ResourceProvider = ModPlatform::ResourceProvider::MODRINTH); EnsureMetadataTask(QHash<QString, Resource*>&, QDir, ModPlatform::ResourceProvider = ModPlatform::ResourceProvider::MODRINTH);
~EnsureMetadataTask() = default; ~EnsureMetadataTask() = default;
@ -37,23 +38,23 @@ class EnsureMetadataTask : public Task {
// Helpers // Helpers
enum class RemoveFromList { Yes, No }; enum class RemoveFromList { Yes, No };
void emitReady(Mod*, QString key = {}, RemoveFromList = RemoveFromList::Yes); void emitReady(Resource*, QString key = {}, RemoveFromList = RemoveFromList::Yes);
void emitFail(Mod*, QString key = {}, RemoveFromList = RemoveFromList::Yes); void emitFail(Resource*, QString key = {}, RemoveFromList = RemoveFromList::Yes);
// Hashes and stuff // Hashes and stuff
auto createNewHash(Mod*) -> Hashing::Hasher::Ptr; auto createNewHash(Resource*) -> Hashing::Hasher::Ptr;
auto getExistingHash(Mod*) -> QString; auto getExistingHash(Resource*) -> QString;
private slots: private slots:
void modrinthCallback(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& ver, Mod*); void modrinthCallback(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& ver, Resource*);
void flameCallback(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& ver, Mod*); void flameCallback(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& ver, Resource*);
signals: signals:
void metadataReady(Mod*); void metadataReady(Resource*);
void metadataFailed(Mod*); void metadataFailed(Resource*);
private: private:
QHash<QString, Mod*> m_mods; QHash<QString, Resource*> m_resources;
QDir m_index_dir; QDir m_index_dir;
ModPlatform::ResourceProvider m_provider; ModPlatform::ResourceProvider m_provider;

View File

@ -87,7 +87,7 @@ class ResourceAPI {
struct VersionSearchArgs { struct VersionSearchArgs {
ModPlatform::IndexedPack pack; ModPlatform::IndexedPack pack;
std::optional<std::list<Version> > mcVersions; std::optional<std::list<Version>> mcVersions;
std::optional<ModPlatform::ModLoaderTypes> loaders; std::optional<ModPlatform::ModLoaderTypes> loaders;
VersionSearchArgs(VersionSearchArgs const&) = default; VersionSearchArgs(VersionSearchArgs const&) = default;

View File

@ -121,59 +121,65 @@ ModPlatform::IndexedVersion FlameCheckUpdate::getFileInfo(int addonId, int fileI
* */ * */
void FlameCheckUpdate::executeTask() void FlameCheckUpdate::executeTask()
{ {
setStatus(tr("Preparing mods for CurseForge...")); setStatus(tr("Preparing resources for CurseForge..."));
int i = 0; int i = 0;
for (auto* mod : m_mods) { for (auto* resource : m_resources) {
setStatus(tr("Getting API response from CurseForge for '%1'...").arg(mod->name())); setStatus(tr("Getting API response from CurseForge for '%1'...").arg(resource->name()));
setProgress(i++, m_mods.size()); setProgress(i++, m_resources.size());
auto latest_vers = api.getLatestVersions({ { mod->metadata()->project_id.toString() }, m_game_versions }); auto latest_vers = api.getLatestVersions({ { resource->metadata()->project_id.toString() }, m_game_versions });
// Check if we were aborted while getting the latest version // Check if we were aborted while getting the latest version
if (m_was_aborted) { if (m_was_aborted) {
aborted(); aborted();
return; return;
} }
auto latest_ver = api.getLatestVersion(latest_vers, m_loaders_list, mod->loaders()); auto latest_ver = api.getLatestVersion(latest_vers, m_loaders_list, resource->metadata()->loaders);
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.has_value() || !latest_ver->addonId.isValid()) { if (!latest_ver.has_value() || !latest_ver->addonId.isValid()) {
emit checkFailed(mod, tr("No valid version found for this mod. It's probably unavailable for the current game " QString reason;
"version / mod loader.")); 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; 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.value()); auto pack = getProjectInfo(latest_ver.value());
auto recover_url = QString("%1/download/%2").arg(pack.websiteUrl, latest_ver->fileId.toString()); 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; continue;
} }
// Fake pack with the necessary info to pass to the download task :) // Fake pack with the necessary info to pass to the download task :)
auto pack = std::make_shared<ModPlatform::IndexedPack>(); auto pack = std::make_shared<ModPlatform::IndexedPack>();
pack->name = mod->name(); pack->name = resource->name();
pack->slug = mod->metadata()->slug; pack->slug = resource->metadata()->slug;
pack->addonId = mod->metadata()->project_id; pack->addonId = resource->metadata()->project_id;
pack->websiteUrl = mod->homeurl();
for (auto& author : mod->authors())
pack->authors.append({ author });
pack->description = mod->description();
pack->provider = ModPlatform::ResourceProvider::FLAME; pack->provider = ModPlatform::ResourceProvider::FLAME;
if (!latest_ver->hash.isEmpty() && (mod->metadata()->hash != latest_ver->hash || mod->status() == ModStatus::NotInstalled)) { if (!latest_ver->hash.isEmpty() &&
auto old_version = mod->version(); (resource->metadata()->hash != latest_ver->hash || resource->status() == ResourceStatus::NOT_INSTALLED)) {
if (old_version.isEmpty() && mod->status() != ModStatus::NotInstalled) { auto old_version = resource->metadata()->version_number;
auto current_ver = getFileInfo(latest_ver->addonId.toInt(), mod->metadata()->file_id.toInt()); if (old_version.isEmpty()) {
old_version = current_ver.version; if (resource->status() == ResourceStatus::NOT_INSTALLED)
old_version = tr("Not installed");
else
old_version = tr("Unknown");
} }
auto download_task = makeShared<ResourceDownloadTask>(pack, latest_ver.value(), m_mods_folder); auto download_task = makeShared<ResourceDownloadTask>(pack, latest_ver.value(), m_resource_model);
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()), api.getModFileChangelog(latest_ver->addonId.toInt(), latest_ver->fileId.toInt()),
ModPlatform::ResourceProvider::FLAME, download_task, mod->enabled()); ModPlatform::ResourceProvider::FLAME, download_task, resource->enabled());
} }
m_deps.append(std::make_shared<GetModDependenciesTask::PackDependency>(pack, latest_ver.value())); m_deps.append(std::make_shared<GetModDependenciesTask::PackDependency>(pack, latest_ver.value()));
} }

View File

@ -1,5 +1,6 @@
#pragma once #pragma once
#include "Application.h"
#include "modplatform/CheckUpdateTask.h" #include "modplatform/CheckUpdateTask.h"
#include "net/NetJob.h" #include "net/NetJob.h"
@ -7,11 +8,11 @@ class FlameCheckUpdate : public CheckUpdateTask {
Q_OBJECT Q_OBJECT
public: public:
FlameCheckUpdate(QList<Mod*>& mods, FlameCheckUpdate(QList<Resource*>& resources,
std::list<Version>& mcVersions, std::list<Version>& mcVersions,
QList<ModPlatform::ModLoaderType> loadersList, QList<ModPlatform::ModLoaderType> loadersList,
std::shared_ptr<ModFolderModel> mods_folder) std::shared_ptr<ResourceFolderModel> resourceModel)
: CheckUpdateTask(mods, mcVersions, loadersList, mods_folder) : CheckUpdateTask(resources, mcVersions, std::move(loadersList), std::move(resourceModel))
{} {}
public slots: public slots:

View File

@ -36,7 +36,7 @@
#include "FlameInstanceCreationTask.h" #include "FlameInstanceCreationTask.h"
#include "QObjectPtr.h" #include "QObjectPtr.h"
#include "minecraft/mod/tasks/LocalModUpdateTask.h" #include "minecraft/mod/tasks/LocalResourceUpdateTask.h"
#include "modplatform/flame/FileResolvingTask.h" #include "modplatform/flame/FileResolvingTask.h"
#include "modplatform/flame/FlameAPI.h" #include "modplatform/flame/FlameAPI.h"
#include "modplatform/flame/FlameModIndex.h" #include "modplatform/flame/FlameModIndex.h"
@ -676,6 +676,7 @@ void FlameCreationTask::validateZIPResources(QEventLoop& loop)
break; break;
} }
} }
// TODO make this work with other sorts of resource
auto task = makeShared<ConcurrentTask>(this, "CreateModMetadata", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt()); auto task = makeShared<ConcurrentTask>(this, "CreateModMetadata", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt());
auto results = m_mod_id_resolver->getResults().files; auto results = m_mod_id_resolver->getResults().files;
auto folder = FS::PathCombine(m_stagingPath, "minecraft", "mods", ".index"); auto folder = FS::PathCombine(m_stagingPath, "minecraft", "mods", ".index");
@ -683,7 +684,7 @@ void FlameCreationTask::validateZIPResources(QEventLoop& loop)
if (file.targetFolder != "mods" || (file.version.fileName.endsWith(".zip") && !zipMods.contains(file.version.fileName))) { if (file.targetFolder != "mods" || (file.version.fileName.endsWith(".zip") && !zipMods.contains(file.version.fileName))) {
continue; continue;
} }
task->addTask(makeShared<LocalModUpdateTask>(folder, file.pack, file.version)); task->addTask(makeShared<LocalResourceUpdateTask>(folder, file.pack, file.version));
} }
connect(task.get(), &Task::finished, &loop, &QEventLoop::quit); connect(task.get(), &Task::finished, &loop, &QEventLoop::quit);
m_process_update_file_info_job = task; m_process_update_file_info_job = task;

View File

@ -28,7 +28,7 @@ QString toHTML(QList<Mod*> mods, OptionalData extraData)
auto meta = mod->metadata(); auto meta = mod->metadata();
auto modName = mod->name().toHtmlEscaped(); auto modName = mod->name().toHtmlEscaped();
if (extraData & Url) { if (extraData & Url) {
auto url = mod->metaurl().toHtmlEscaped(); auto url = mod->homepage().toHtmlEscaped();
if (!url.isEmpty()) if (!url.isEmpty())
modName = QString("<a href=\"%1\">%2</a>").arg(url, modName); modName = QString("<a href=\"%1\">%2</a>").arg(url, modName);
} }
@ -65,7 +65,7 @@ QString toMarkdown(QList<Mod*> mods, OptionalData extraData)
auto meta = mod->metadata(); auto meta = mod->metadata();
auto modName = toMarkdownEscaped(mod->name()); auto modName = toMarkdownEscaped(mod->name());
if (extraData & Url) { if (extraData & Url) {
auto url = mod->metaurl(); auto url = mod->homepage();
if (!url.isEmpty()) if (!url.isEmpty())
modName = QString("[%1](%2)").arg(modName, url); modName = QString("[%1](%2)").arg(modName, url);
} }
@ -95,7 +95,7 @@ QString toPlainTXT(QList<Mod*> mods, OptionalData extraData)
auto line = modName; auto line = modName;
if (extraData & Url) { if (extraData & Url) {
auto url = mod->metaurl(); auto url = mod->homepage();
if (!url.isEmpty()) if (!url.isEmpty())
line += QString(" (%1)").arg(url); line += QString(" (%1)").arg(url);
} }
@ -124,7 +124,7 @@ QString toJSON(QList<Mod*> mods, OptionalData extraData)
QJsonObject line; QJsonObject line;
line["name"] = modName; line["name"] = modName;
if (extraData & Url) { if (extraData & Url) {
auto url = mod->metaurl(); auto url = mod->homepage();
if (!url.isEmpty()) if (!url.isEmpty())
line["url"] = url; line["url"] = url;
} }
@ -156,7 +156,7 @@ QString toCSV(QList<Mod*> mods, OptionalData extraData)
data << modName; data << modName;
if (extraData & Url) if (extraData & Url)
data << mod->metaurl(); data << mod->homepage();
if (extraData & Version) { if (extraData & Version) {
auto ver = mod->version(); auto ver = mod->version();
if (ver.isEmpty() && meta != nullptr) if (ver.isEmpty() && meta != nullptr)
@ -203,7 +203,7 @@ QString exportToModList(QList<Mod*> mods, QString lineTemplate)
for (auto mod : mods) { for (auto mod : mods) {
auto meta = mod->metadata(); auto meta = mod->metadata();
auto modName = mod->name(); auto modName = mod->name();
auto url = mod->metaurl(); auto url = mod->homepage();
auto ver = mod->version(); auto ver = mod->version();
if (ver.isEmpty() && meta != nullptr) if (ver.isEmpty() && meta != nullptr)
ver = meta->version().toString(); ver = meta->version().toString();

View File

@ -14,14 +14,6 @@
static ModrinthAPI api; static ModrinthAPI api;
ModrinthCheckUpdate::ModrinthCheckUpdate(QList<Mod*>& mods,
std::list<Version>& mcVersions,
QList<ModPlatform::ModLoaderType> loadersList,
std::shared_ptr<ModFolderModel> mods_folder)
: CheckUpdateTask(mods, mcVersions, loadersList, mods_folder)
, m_hash_type(ModPlatform::ProviderCapabilities::hashType(ModPlatform::ResourceProvider::MODRINTH).first())
{}
bool ModrinthCheckUpdate::abort() bool ModrinthCheckUpdate::abort()
{ {
if (m_job) if (m_job)
@ -36,24 +28,24 @@ bool ModrinthCheckUpdate::abort()
* */ * */
void ModrinthCheckUpdate::executeTask() void ModrinthCheckUpdate::executeTask()
{ {
setStatus(tr("Preparing mods for Modrinth...")); setStatus(tr("Preparing resources for Modrinth..."));
setProgress(0, 9); setProgress(0, (m_loaders_list.isEmpty() ? 1 : m_loaders_list.length()) * 2 + 1);
auto hashing_task = auto hashing_task =
makeShared<ConcurrentTask>(this, "MakeModrinthHashesTask", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt()); makeShared<ConcurrentTask>(this, "MakeModrinthHashesTask", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt());
for (auto* mod : m_mods) { for (auto* resource : m_resources) {
auto hash = mod->metadata()->hash; auto hash = resource->metadata()->hash;
// Sadly the API can only handle one hash type per call, se we // 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 // need to generate a new hash if the current one is innadequate
// (though it will rarely happen, if at all) // (though it will rarely happen, if at all)
if (mod->metadata()->hash_format != m_hash_type) { if (resource->metadata()->hash_format != m_hash_type) {
auto hash_task = Hashing::createHasher(mod->fileinfo().absoluteFilePath(), ModPlatform::ResourceProvider::MODRINTH); auto hash_task = Hashing::createHasher(resource->fileinfo().absoluteFilePath(), ModPlatform::ResourceProvider::MODRINTH);
connect(hash_task.get(), &Hashing::Hasher::resultsReady, [this, mod](QString hash) { m_mappings.insert(hash, mod); }); connect(hash_task.get(), &Hashing::Hasher::resultsReady, [this, resource](QString hash) { m_mappings.insert(hash, resource); });
connect(hash_task.get(), &Task::failed, [this] { failed("Failed to generate hash"); }); connect(hash_task.get(), &Task::failed, [this] { failed("Failed to generate hash"); });
hashing_task->addTask(hash_task); hashing_task->addTask(hash_task);
} else { } else {
m_mappings.insert(hash, mod); m_mappings.insert(hash, resource);
} }
} }
@ -62,10 +54,28 @@ void ModrinthCheckUpdate::executeTask()
hashing_task->start(); hashing_task->start();
} }
void ModrinthCheckUpdate::checkVersionsResponse(std::shared_ptr<QByteArray> response, void ModrinthCheckUpdate::getUpdateModsForLoader(std::optional<ModPlatform::ModLoaderTypes> loader)
ModPlatform::ModLoaderTypes loader,
bool forceModLoaderCheck)
{ {
setStatus(tr("Waiting for the API response from Modrinth..."));
setProgress(m_progress + 1, m_progressTotal);
auto response = std::make_shared<QByteArray>();
QStringList hashes = m_mappings.keys();
auto job = api.latestVersions(hashes, m_hash_type, m_game_versions, loader, response);
connect(job.get(), &Task::succeeded, this, [this, response, loader] { checkVersionsResponse(response, loader); });
connect(job.get(), &Task::failed, this, &ModrinthCheckUpdate::checkNextLoader);
m_job = job;
job->start();
}
void ModrinthCheckUpdate::checkVersionsResponse(std::shared_ptr<QByteArray> response, std::optional<ModPlatform::ModLoaderTypes> loader)
{
setStatus(tr("Parsing the API response from Modrinth..."));
setProgress(m_progress + 1, m_progressTotal);
QJsonParseError parse_error{}; QJsonParseError parse_error{};
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
if (parse_error.error != QJsonParseError::NoError) { if (parse_error.error != QJsonParseError::NoError) {
@ -77,21 +87,21 @@ void ModrinthCheckUpdate::checkVersionsResponse(std::shared_ptr<QByteArray> resp
return; return;
} }
setStatus(tr("Parsing the API response from Modrinth..."));
setProgress(m_next_loader_idx * 2, 9);
try { try {
for (auto hash : m_mappings.keys()) { auto iter = m_mappings.begin();
if (forceModLoaderCheck && !(m_mappings[hash]->loaders() & loader)) {
continue; while (iter != m_mappings.end()) {
} const QString hash = iter.key();
Resource* resource = iter.value();
auto project_obj = doc[hash].toObject(); auto project_obj = doc[hash].toObject();
// If the returned project is empty, but we have Modrinth metadata, // If the returned project is empty, but we have Modrinth metadata,
// it means this specific version is not available // it means this specific version is not available
if (project_obj.isEmpty()) { if (project_obj.isEmpty()) {
qDebug() << "Mod " << m_mappings.find(hash).value()->name() << " got an empty response." << "Hash: " << hash; qDebug() << "Mod " << m_mappings.find(hash).value()->name() << " got an empty response."
<< "Hash: " << hash;
++iter;
continue; continue;
} }
@ -101,7 +111,7 @@ void ModrinthCheckUpdate::checkVersionsResponse(std::shared_ptr<QByteArray> resp
static auto flags = { ModPlatform::ModLoaderType::NeoForge, ModPlatform::ModLoaderType::Forge, static auto flags = { ModPlatform::ModLoaderType::NeoForge, ModPlatform::ModLoaderType::Forge,
ModPlatform::ModLoaderType::Quilt, ModPlatform::ModLoaderType::Fabric }; ModPlatform::ModLoaderType::Quilt, ModPlatform::ModLoaderType::Fabric };
for (auto flag : flags) { for (auto flag : flags) {
if (loader.testFlag(flag)) { if (loader.has_value() && loader->testFlag(flag)) {
loader_filter = ModPlatform::getModLoaderAsString(flag); loader_filter = ModPlatform::getModLoaderAsString(flag);
break; break;
} }
@ -116,106 +126,72 @@ void ModrinthCheckUpdate::checkVersionsResponse(std::shared_ptr<QByteArray> resp
auto project_ver = Modrinth::loadIndexedPackVersion(project_obj, m_hash_type, loader_filter); auto project_ver = Modrinth::loadIndexedPackVersion(project_obj, m_hash_type, loader_filter);
if (project_ver.downloadUrl.isEmpty()) { if (project_ver.downloadUrl.isEmpty()) {
qCritical() << "Modrinth mod without download url!" << project_ver.fileName; qCritical() << "Modrinth mod without download url!" << project_ver.fileName;
++iter;
continue; continue;
} }
auto mod_iter = m_mappings.find(hash);
if (mod_iter == m_mappings.end()) {
qCritical() << "Failed to remap mod from Modrinth!";
continue;
}
auto mod = *mod_iter;
m_mappings.remove(hash);
auto key = project_ver.hash;
// Fake pack with the necessary info to pass to the download task :) // Fake pack with the necessary info to pass to the download task :)
auto pack = std::make_shared<ModPlatform::IndexedPack>(); auto pack = std::make_shared<ModPlatform::IndexedPack>();
pack->name = mod->name(); pack->name = resource->name();
pack->slug = mod->metadata()->slug; pack->slug = resource->metadata()->slug;
pack->addonId = mod->metadata()->project_id; pack->addonId = resource->metadata()->project_id;
pack->websiteUrl = mod->homeurl();
for (auto& author : mod->authors())
pack->authors.append({ author });
pack->description = mod->description();
pack->provider = ModPlatform::ResourceProvider::MODRINTH; pack->provider = ModPlatform::ResourceProvider::MODRINTH;
if ((key != hash && project_ver.is_preferred) || (mod->status() == ModStatus::NotInstalled)) { if ((project_ver.hash != hash && project_ver.is_preferred) || (resource->status() == ResourceStatus::NOT_INSTALLED)) {
if (mod->version() == project_ver.version_number) auto download_task = makeShared<ResourceDownloadTask>(pack, project_ver, m_resource_model);
continue;
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, mod->enabled()); project_ver.changelog, ModPlatform::ResourceProvider::MODRINTH, download_task, resource->enabled());
} }
m_deps.append(std::make_shared<GetModDependenciesTask::PackDependency>(pack, project_ver)); m_deps.append(std::make_shared<GetModDependenciesTask::PackDependency>(pack, project_ver));
iter = m_mappings.erase(iter);
} }
} catch (Json::JsonException& e) { } catch (Json::JsonException& e) {
emitFailed(e.cause() + " : " + e.what()); emitFailed(e.cause() + ": " + e.what());
return; return;
} }
checkNextLoader(); checkNextLoader();
} }
void ModrinthCheckUpdate::getUpdateModsForLoader(ModPlatform::ModLoaderTypes loader, bool forceModLoaderCheck)
{
auto response = std::make_shared<QByteArray>();
QStringList hashes;
if (forceModLoaderCheck) {
for (auto hash : m_mappings.keys()) {
if (m_mappings[hash]->loaders() & loader) {
hashes.append(hash);
}
}
} else {
hashes = m_mappings.keys();
}
auto job = api.latestVersions(hashes, m_hash_type, m_game_versions, loader, response);
connect(job.get(), &Task::succeeded, this,
[this, response, loader, forceModLoaderCheck] { checkVersionsResponse(response, loader, forceModLoaderCheck); });
connect(job.get(), &Task::failed, this, &ModrinthCheckUpdate::checkNextLoader);
setStatus(tr("Waiting for the API response from Modrinth..."));
setProgress(m_next_loader_idx * 2 - 1, 9);
m_job = job;
job->start();
}
void ModrinthCheckUpdate::checkNextLoader() void ModrinthCheckUpdate::checkNextLoader()
{ {
if (m_mappings.isEmpty()) { if (m_mappings.isEmpty()) {
emitSucceeded(); emitSucceeded();
return; return;
} }
if (m_next_loader_idx < m_loaders_list.size()) {
getUpdateModsForLoader(m_loaders_list.at(m_next_loader_idx)); if (m_loaders_list.isEmpty() && m_loader_idx == 0) {
m_next_loader_idx++; getUpdateModsForLoader({});
m_loader_idx++;
return; return;
} }
static auto flags = { ModPlatform::ModLoaderType::NeoForge, ModPlatform::ModLoaderType::Forge, ModPlatform::ModLoaderType::Quilt,
ModPlatform::ModLoaderType::Fabric }; if (m_loader_idx < m_loaders_list.size()) {
for (auto flag : flags) { getUpdateModsForLoader(m_loaders_list.at(m_loader_idx));
if (!m_loaders_list.contains(flag)) { m_loader_idx++;
m_loaders_list.append(flag); return;
m_next_loader_idx++;
setProgress(m_next_loader_idx * 2 - 1, 9);
for (auto m : m_mappings) {
if (m->loaders() & flag) {
getUpdateModsForLoader(flag, true);
return;
}
}
setProgress(m_next_loader_idx * 2, 9);
}
} }
for (auto m : m_mappings) {
emit checkFailed(m, for (auto resource : m_mappings) {
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);
} }
emitSucceeded(); emitSucceeded();
return;
} }

View File

@ -6,23 +6,26 @@ class ModrinthCheckUpdate : public CheckUpdateTask {
Q_OBJECT Q_OBJECT
public: public:
ModrinthCheckUpdate(QList<Mod*>& mods, ModrinthCheckUpdate(QList<Resource*>& resources,
std::list<Version>& mcVersions, std::list<Version>& mcVersions,
QList<ModPlatform::ModLoaderType> loadersList, QList<ModPlatform::ModLoaderType> loadersList,
std::shared_ptr<ModFolderModel> mods_folder); std::shared_ptr<ResourceFolderModel> resourceModel)
: CheckUpdateTask(resources, mcVersions, std::move(loadersList), std::move(resourceModel))
, m_hash_type(ModPlatform::ProviderCapabilities::hashType(ModPlatform::ResourceProvider::MODRINTH).first())
{}
public slots: public slots:
bool abort() override; bool abort() override;
protected slots: protected slots:
void executeTask() override; void executeTask() override;
void getUpdateModsForLoader(ModPlatform::ModLoaderTypes loader, bool forceModLoaderCheck = false); void getUpdateModsForLoader(std::optional<ModPlatform::ModLoaderTypes> loader);
void checkVersionsResponse(std::shared_ptr<QByteArray> response, ModPlatform::ModLoaderTypes loader, bool forceModLoaderCheck = false); void checkVersionsResponse(std::shared_ptr<QByteArray> response, std::optional<ModPlatform::ModLoaderTypes> loader);
void checkNextLoader(); void checkNextLoader();
private: private:
Task::Ptr m_job = nullptr; Task::Ptr m_job = nullptr;
QHash<QString, Mod*> m_mappings; QHash<QString, Resource*> m_mappings;
QString m_hash_type; QString m_hash_type;
int m_next_loader_idx = 0; int m_loader_idx = 0;
}; };

View File

@ -243,7 +243,8 @@ bool ModrinthCreationTask::createInstance()
auto root_modpack_path = FS::PathCombine(m_stagingPath, m_root_path); auto root_modpack_path = FS::PathCombine(m_stagingPath, m_root_path);
auto root_modpack_url = QUrl::fromLocalFile(root_modpack_path); auto root_modpack_url = QUrl::fromLocalFile(root_modpack_path);
QHash<QString, Mod*> mods; // TODO make this work with other sorts of resource
QHash<QString, Resource*> resources;
for (auto file : m_files) { for (auto file : m_files) {
auto fileName = file.path; auto fileName = file.path;
fileName = FS::RemoveInvalidPathChars(fileName); fileName = FS::RemoveInvalidPathChars(fileName);
@ -259,7 +260,7 @@ bool ModrinthCreationTask::createInstance()
ModDetails d; ModDetails d;
d.mod_id = file_path; d.mod_id = file_path;
mod->setDetails(d); mod->setDetails(d);
mods[file.hash.toHex()] = mod; resources[file.hash.toHex()] = mod;
} }
qDebug() << "Will try to download" << file.downloads.front() << "to" << file_path; qDebug() << "Will try to download" << file.downloads.front() << "to" << file_path;
@ -302,15 +303,15 @@ bool ModrinthCreationTask::createInstance()
loop.exec(); loop.exec();
if (!ended_well) { if (!ended_well) {
for (auto m : mods) { for (auto resource : resources) {
delete m; delete resource;
} }
return ended_well; return ended_well;
} }
QEventLoop ensureMetaLoop; QEventLoop ensureMetaLoop;
QDir folder = FS::PathCombine(instance.modsRoot(), ".index"); QDir folder = FS::PathCombine(instance.modsRoot(), ".index");
auto ensureMetadataTask = makeShared<EnsureMetadataTask>(mods, folder, ModPlatform::ResourceProvider::MODRINTH); auto ensureMetadataTask = makeShared<EnsureMetadataTask>(resources, folder, ModPlatform::ResourceProvider::MODRINTH);
connect(ensureMetadataTask.get(), &Task::succeeded, this, [&ended_well]() { ended_well = true; }); connect(ensureMetadataTask.get(), &Task::succeeded, this, [&ended_well]() { ended_well = true; });
connect(ensureMetadataTask.get(), &Task::finished, &ensureMetaLoop, &QEventLoop::quit); connect(ensureMetadataTask.get(), &Task::finished, &ensureMetaLoop, &QEventLoop::quit);
connect(ensureMetadataTask.get(), &Task::progress, [this](qint64 current, qint64 total) { connect(ensureMetadataTask.get(), &Task::progress, [this](qint64 current, qint64 total) {
@ -323,10 +324,10 @@ bool ModrinthCreationTask::createInstance()
m_task = ensureMetadataTask; m_task = ensureMetadataTask;
ensureMetaLoop.exec(); ensureMetaLoop.exec();
for (auto m : mods) { for (auto resource : resources) {
delete m; delete resource;
} }
mods.clear(); resources.clear();
// Update information of the already installed instance, if any. // Update information of the already installed instance, if any.
if (m_instance && ended_well) { if (m_instance && ended_well) {

View File

@ -123,7 +123,7 @@ void ModrinthPackExportTask::collectHashes()
modIter != allMods.end()) { modIter != allMods.end()) {
const Mod* mod = *modIter; const Mod* mod = *modIter;
if (mod->metadata() != nullptr) { if (mod->metadata() != nullptr) {
QUrl& url = mod->metadata()->url; const QUrl& url = mod->metadata()->url;
// ensure the url is permitted on modrinth.com // ensure the url is permitted on modrinth.com
if (!url.isEmpty() && BuildConfig.MODRINTH_MRPACK_HOSTS.contains(url.host())) { if (!url.isEmpty() && BuildConfig.MODRINTH_MRPACK_HOSTS.contains(url.host())) {
qDebug() << "Resolving" << relative << "from index"; qDebug() << "Resolving" << relative << "from index";

View File

@ -35,7 +35,7 @@
namespace Packwiz { namespace Packwiz {
auto getRealIndexName(QDir& index_dir, QString normalized_fname, bool should_find_match) -> QString auto getRealIndexName(const QDir& index_dir, QString normalized_fname, bool should_find_match) -> QString
{ {
QFile index_file(index_dir.absoluteFilePath(normalized_fname)); QFile index_file(index_dir.absoluteFilePath(normalized_fname));
@ -90,7 +90,7 @@ auto intEntry(toml::table table, QString entry_name) -> int
return node.value_or(0); return node.value_or(0);
} }
auto V1::createModFormat([[maybe_unused]] QDir& index_dir, auto V1::createModFormat([[maybe_unused]] const QDir& index_dir,
ModPlatform::IndexedPack& mod_pack, ModPlatform::IndexedPack& mod_pack,
ModPlatform::IndexedVersion& mod_version) -> Mod ModPlatform::IndexedVersion& mod_version) -> Mod
{ {
@ -119,10 +119,14 @@ auto V1::createModFormat([[maybe_unused]] QDir& index_dir,
mod.mcVersions.sort(); mod.mcVersions.sort();
mod.releaseType = mod_version.version_type; mod.releaseType = mod_version.version_type;
mod.version_number = mod_version.version_number;
if (mod.version_number.isNull()) // on CurseForge, there is only a version name - not a version number
mod.version_number = mod_version.version;
return mod; return mod;
} }
auto V1::createModFormat(QDir& index_dir, [[maybe_unused]] ::Mod& internal_mod, QString slug) -> Mod auto V1::createModFormat(const QDir& index_dir, [[maybe_unused]] ::Mod& internal_mod, QString slug) -> Mod
{ {
// Try getting metadata if it exists // Try getting metadata if it exists
Mod mod{ getIndexForMod(index_dir, slug) }; Mod mod{ getIndexForMod(index_dir, slug) };
@ -134,7 +138,7 @@ auto V1::createModFormat(QDir& index_dir, [[maybe_unused]] ::Mod& internal_mod,
return {}; return {};
} }
void V1::updateModIndex(QDir& index_dir, Mod& mod) void V1::updateModIndex(const QDir& index_dir, Mod& mod)
{ {
if (!mod.isValid()) { if (!mod.isValid()) {
qCritical() << QString("Tried to update metadata of an invalid mod!"); qCritical() << QString("Tried to update metadata of an invalid mod!");
@ -211,6 +215,7 @@ void V1::updateModIndex(QDir& index_dir, Mod& mod)
{ "x-prismlauncher-loaders", loaders }, { "x-prismlauncher-loaders", loaders },
{ "x-prismlauncher-mc-versions", mcVersions }, { "x-prismlauncher-mc-versions", mcVersions },
{ "x-prismlauncher-release-type", mod.releaseType.toString().toStdString() }, { "x-prismlauncher-release-type", mod.releaseType.toString().toStdString() },
{ "x-prismlauncher-version-number", mod.version_number.toStdString() },
{ "download", { "download",
toml::table{ toml::table{
{ "mode", mod.mode.toStdString() }, { "mode", mod.mode.toStdString() },
@ -228,7 +233,7 @@ void V1::updateModIndex(QDir& index_dir, Mod& mod)
index_file.close(); index_file.close();
} }
void V1::deleteModIndex(QDir& index_dir, QString& mod_slug) void V1::deleteModIndex(const QDir& index_dir, QString& mod_slug)
{ {
auto normalized_fname = indexFileName(mod_slug); auto normalized_fname = indexFileName(mod_slug);
auto real_fname = getRealIndexName(index_dir, normalized_fname); auto real_fname = getRealIndexName(index_dir, normalized_fname);
@ -247,7 +252,7 @@ void V1::deleteModIndex(QDir& index_dir, QString& mod_slug)
} }
} }
void V1::deleteModIndex(QDir& index_dir, QVariant& mod_id) void V1::deleteModIndex(const QDir& index_dir, QVariant& mod_id)
{ {
for (auto& file_name : index_dir.entryList(QDir::Filter::Files)) { for (auto& file_name : index_dir.entryList(QDir::Filter::Files)) {
auto mod = getIndexForMod(index_dir, file_name); auto mod = getIndexForMod(index_dir, file_name);
@ -259,7 +264,7 @@ void V1::deleteModIndex(QDir& index_dir, QVariant& mod_id)
} }
} }
auto V1::getIndexForMod(QDir& index_dir, QString slug) -> Mod auto V1::getIndexForMod(const QDir& index_dir, QString slug) -> Mod
{ {
Mod mod; Mod mod;
@ -295,7 +300,7 @@ auto V1::getIndexForMod(QDir& index_dir, QString slug) -> Mod
mod.name = stringEntry(table, "name"); mod.name = stringEntry(table, "name");
mod.filename = stringEntry(table, "filename"); mod.filename = stringEntry(table, "filename");
mod.side = stringToSide(stringEntry(table, "side")); mod.side = stringToSide(stringEntry(table, "side"));
mod.releaseType = ModPlatform::IndexedVersionType(stringEntry(table, "x-prismlauncher-release-type")); mod.releaseType = ModPlatform::IndexedVersionType(table["x-prismlauncher-release-type"].value_or(""));
if (auto loaders = table["x-prismlauncher-loaders"]; loaders && loaders.is_array()) { if (auto loaders = table["x-prismlauncher-loaders"]; loaders && loaders.is_array()) {
for (auto&& loader : *loaders.as_array()) { for (auto&& loader : *loaders.as_array()) {
if (loader.is_string()) { if (loader.is_string()) {
@ -315,6 +320,7 @@ auto V1::getIndexForMod(QDir& index_dir, QString slug) -> Mod
mod.mcVersions.sort(); mod.mcVersions.sort();
} }
} }
mod.version_number = table["x-prismlauncher-version-number"].value_or("");
{ // [download] info { // [download] info
auto download_table = table["download"].as_table(); auto download_table = table["download"].as_table();
@ -356,7 +362,7 @@ auto V1::getIndexForMod(QDir& index_dir, QString slug) -> Mod
return mod; return mod;
} }
auto V1::getIndexForMod(QDir& index_dir, QVariant& mod_id) -> Mod auto V1::getIndexForMod(const QDir& index_dir, QVariant& mod_id) -> Mod
{ {
for (auto& file_name : index_dir.entryList(QDir::Filter::Files)) { for (auto& file_name : index_dir.entryList(QDir::Filter::Files)) {
auto mod = getIndexForMod(index_dir, file_name); auto mod = getIndexForMod(index_dir, file_name);

View File

@ -32,11 +32,13 @@ class Mod;
namespace Packwiz { namespace Packwiz {
auto getRealIndexName(QDir& index_dir, QString normalized_index_name, bool should_match = false) -> QString; auto getRealIndexName(const QDir& index_dir, QString normalized_index_name, bool should_match = false) -> QString;
class V1 { class V1 {
public: public:
enum class Side { ClientSide = 1 << 0, ServerSide = 1 << 1, UniversalSide = ClientSide | ServerSide }; enum class Side { ClientSide = 1 << 0, ServerSide = 1 << 1, UniversalSide = ClientSide | ServerSide };
// can also represent other resources beside loader mods - but this is what packwiz calls it
struct Mod { struct Mod {
QString slug{}; QString slug{};
QString name{}; QString name{};
@ -56,6 +58,7 @@ class V1 {
ModPlatform::ResourceProvider provider{}; ModPlatform::ResourceProvider provider{};
QVariant file_id{}; QVariant file_id{};
QVariant project_id{}; QVariant project_id{};
QString version_number{};
public: public:
// This is a totally heuristic, but should work for now. // This is a totally heuristic, but should work for now.
@ -70,33 +73,33 @@ class V1 {
/* Generates the object representing the information in a mod.pw.toml file via /* Generates the object representing the information in a mod.pw.toml file via
* its common representation in the launcher, when downloading mods. * its common representation in the launcher, when downloading mods.
* */ * */
static auto createModFormat(QDir& index_dir, ModPlatform::IndexedPack& mod_pack, ModPlatform::IndexedVersion& mod_version) -> Mod; static auto createModFormat(const QDir& index_dir, ModPlatform::IndexedPack& mod_pack, ModPlatform::IndexedVersion& mod_version) -> Mod;
/* Generates the object representing the information in a mod.pw.toml file via /* Generates the object representing the information in a mod.pw.toml file via
* its common representation in the launcher, plus a necessary slug. * its common representation in the launcher, plus a necessary slug.
* */ * */
static auto createModFormat(QDir& index_dir, ::Mod& internal_mod, QString slug) -> Mod; static auto createModFormat(const QDir& index_dir, ::Mod& internal_mod, QString slug) -> Mod;
/* Updates the mod index for the provided mod. /* Updates the mod index for the provided mod.
* This creates a new index if one does not exist already * This creates a new index if one does not exist already
* TODO: Ask the user if they want to override, and delete the old mod's files, or keep the old one. * TODO: Ask the user if they want to override, and delete the old mod's files, or keep the old one.
* */ * */
static void updateModIndex(QDir& index_dir, Mod& mod); static void updateModIndex(const QDir& index_dir, Mod& mod);
/* Deletes the metadata for the mod with the given slug. If the metadata doesn't exist, it does nothing. */ /* Deletes the metadata for the mod with the given slug. If the metadata doesn't exist, it does nothing. */
static void deleteModIndex(QDir& index_dir, QString& mod_slug); static void deleteModIndex(const QDir& index_dir, QString& mod_slug);
/* Deletes the metadata for the mod with the given id. If the metadata doesn't exist, it does nothing. */ /* Deletes the metadata for the mod with the given id. If the metadata doesn't exist, it does nothing. */
static void deleteModIndex(QDir& index_dir, QVariant& mod_id); static void deleteModIndex(const QDir& index_dir, QVariant& mod_id);
/* Gets the metadata for a mod with a particular file name. /* Gets the metadata for a mod with a particular file name.
* If the mod doesn't have a metadata, it simply returns an empty Mod object. * If the mod doesn't have a metadata, it simply returns an empty Mod object.
* */ * */
static auto getIndexForMod(QDir& index_dir, QString slug) -> Mod; static auto getIndexForMod(const QDir& index_dir, QString slug) -> Mod;
/* Gets the metadata for a mod with a particular id. /* Gets the metadata for a mod with a particular id.
* If the mod doesn't have a metadata, it simply returns an empty Mod object. * If the mod doesn't have a metadata, it simply returns an empty Mod object.
* */ * */
static auto getIndexForMod(QDir& index_dir, QVariant& mod_id) -> Mod; static auto getIndexForMod(const QDir& index_dir, QVariant& mod_id) -> Mod;
static auto sideToString(Side side) -> QString; static auto sideToString(Side side) -> QString;
static auto stringToSide(QString side) -> Side; static auto stringToSide(QString side) -> Side;

View File

@ -1048,7 +1048,7 @@ void MainWindow::processURLs(QList<QUrl> urls)
qWarning() << "Importing of Data Packs not supported at this time. Ignoring" << localFileName; qWarning() << "Importing of Data Packs not supported at this time. Ignoring" << localFileName;
break; break;
case PackedResourceType::Mod: case PackedResourceType::Mod:
minecraftInst->loaderModList()->installMod(localFileName, version); minecraftInst->loaderModList()->installResourceWithFlameMetadata(localFileName, version);
break; break;
case PackedResourceType::ShaderPack: case PackedResourceType::ShaderPack:
minecraftInst->shaderPackList()->installResource(localFileName); minecraftInst->shaderPackList()->installResource(localFileName);

View File

@ -88,9 +88,9 @@ ExportPackDialog::ExportPackDialog(InstancePtr instance, QWidget* parent, ModPla
MinecraftInstance* mcInstance = dynamic_cast<MinecraftInstance*>(instance.get()); MinecraftInstance* mcInstance = dynamic_cast<MinecraftInstance*>(instance.get());
if (mcInstance) { if (mcInstance) {
const QDir index = mcInstance->loaderModList()->indexDir(); for (auto& resourceModel : mcInstance->resourceLists())
if (index.exists()) if (resourceModel->indexDir().exists())
proxy->ignoreFilesWithPath().insert(root.relativeFilePath(index.absolutePath())); proxy->ignoreFilesWithPath().insert(root.relativeFilePath(resourceModel->indexDir().absolutePath()));
} }
ui->files->setModel(proxy); ui->files->setModel(proxy);

View File

@ -381,7 +381,7 @@ QList<BasePage*> ShaderPackDownloadDialog::getPages()
return pages; return pages;
} }
void ModDownloadDialog::setModMetadata(std::shared_ptr<Metadata::ModStruct> meta) void ResourceDownloadDialog::setResourceMetadata(const std::shared_ptr<Metadata::ModStruct>& meta)
{ {
switch (meta->provider) { switch (meta->provider) {
case ModPlatform::ResourceProvider::MODRINTH: case ModPlatform::ResourceProvider::MODRINTH:

View File

@ -69,6 +69,8 @@ class ResourceDownloadDialog : public QDialog, public BasePageProvider {
const QList<DownloadTaskPtr> getTasks(); const QList<DownloadTaskPtr> getTasks();
[[nodiscard]] const std::shared_ptr<ResourceFolderModel> getBaseModel() const { return m_base_model; } [[nodiscard]] const std::shared_ptr<ResourceFolderModel> getBaseModel() const { return m_base_model; }
void setResourceMetadata(const std::shared_ptr<Metadata::ModStruct>& meta);
public slots: public slots:
void accept() override; void accept() override;
void reject() override; void reject() override;
@ -107,8 +109,6 @@ class ModDownloadDialog final : public ResourceDownloadDialog {
QList<BasePage*> getPages() override; QList<BasePage*> getPages() override;
GetModDependenciesTask::Ptr getModDependenciesTask() override; GetModDependenciesTask::Ptr getModDependenciesTask() override;
void setModMetadata(std::shared_ptr<Metadata::ModStruct>);
private: private:
BaseInstance* m_instance; BaseInstance* m_instance;
}; };

View File

@ -1,5 +1,4 @@
#include "ModUpdateDialog.h" #include "ResourceUpdateDialog.h"
#include "Application.h"
#include "ChooseProviderDialog.h" #include "ChooseProviderDialog.h"
#include "CustomMessageBox.h" #include "CustomMessageBox.h"
#include "ProgressDialog.h" #include "ProgressDialog.h"
@ -36,27 +35,29 @@ static QList<ModPlatform::ModLoaderType> mcLoadersList(BaseInstance* inst)
return static_cast<MinecraftInstance*>(inst)->getPackProfile()->getModLoadersList(); return static_cast<MinecraftInstance*>(inst)->getPackProfile()->getModLoadersList();
} }
ModUpdateDialog::ModUpdateDialog(QWidget* parent, ResourceUpdateDialog::ResourceUpdateDialog(QWidget* parent,
BaseInstance* instance, BaseInstance* instance,
const std::shared_ptr<ModFolderModel> mods, const std::shared_ptr<ResourceFolderModel> resource_model,
QList<Mod*>& search_for, QList<Resource*>& search_for,
bool includeDeps) bool include_deps,
: ReviewMessageBox(parent, tr("Confirm mods to update"), "") bool filter_loaders)
: ReviewMessageBox(parent, tr("Confirm resources to update"), "")
, m_parent(parent) , m_parent(parent)
, m_mod_model(mods) , m_resource_model(resource_model)
, m_candidates(search_for) , m_candidates(search_for)
, m_second_try_metadata( , m_second_try_metadata(
new ConcurrentTask(nullptr, "Second Metadata Search", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt())) new ConcurrentTask(nullptr, "Second Metadata Search", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt()))
, m_instance(instance) , m_instance(instance)
, m_include_deps(includeDeps) , m_include_deps(include_deps)
, m_filter_loaders(filter_loaders)
{ {
ReviewMessageBox::setGeometry(0, 0, 800, 600); ReviewMessageBox::setGeometry(0, 0, 800, 600);
ui->explainLabel->setText(tr("You're about to update the following mods:")); ui->explainLabel->setText(tr("You're about to update the following resources:"));
ui->onlyCheckedLabel->setText(tr("Only mods with a check will be updated!")); ui->onlyCheckedLabel->setText(tr("Only resources with a check will be updated!"));
} }
void ModUpdateDialog::checkCandidates() void ResourceUpdateDialog::checkCandidates()
{ {
// Ensure mods have valid metadata // Ensure mods have valid metadata
auto went_well = ensureMetadata(); auto went_well = ensureMetadata();
@ -75,8 +76,8 @@ void ModUpdateDialog::checkCandidates()
} }
ScrollMessageBox message_dialog(m_parent, tr("Metadata generation failed"), ScrollMessageBox message_dialog(m_parent, tr("Metadata generation failed"),
tr("Could not generate metadata for the following mods:<br>" tr("Could not generate metadata for the following resources:<br>"
"Do you wish to proceed without those mods?"), "Do you wish to proceed without those resources?"),
text); text);
message_dialog.setModal(true); message_dialog.setModal(true);
if (message_dialog.exec() == QDialog::Rejected) { if (message_dialog.exec() == QDialog::Rejected) {
@ -87,21 +88,25 @@ void ModUpdateDialog::checkCandidates()
} }
auto versions = mcVersions(m_instance); auto versions = mcVersions(m_instance);
auto loadersList = mcLoadersList(m_instance); auto loadersList = m_filter_loaders ? mcLoadersList(m_instance) : QList<ModPlatform::ModLoaderType>();
SequentialTask check_task(m_parent, tr("Checking for updates")); SequentialTask check_task(m_parent, tr("Checking for updates"));
if (!m_modrinth_to_update.empty()) { if (!m_modrinth_to_update.empty()) {
m_modrinth_check_task.reset(new ModrinthCheckUpdate(m_modrinth_to_update, versions, loadersList, m_mod_model)); m_modrinth_check_task.reset(new ModrinthCheckUpdate(m_modrinth_to_update, versions, loadersList, m_resource_model));
connect(m_modrinth_check_task.get(), &CheckUpdateTask::checkFailed, this, 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 }); }); [this](Resource* resource, QString reason, QUrl recover_url) {
m_failed_check_update.append({ resource, reason, recover_url });
});
check_task.addTask(m_modrinth_check_task); check_task.addTask(m_modrinth_check_task);
} }
if (!m_flame_to_update.empty()) { if (!m_flame_to_update.empty()) {
m_flame_check_task.reset(new FlameCheckUpdate(m_flame_to_update, versions, loadersList, m_mod_model)); m_flame_check_task.reset(new FlameCheckUpdate(m_flame_to_update, versions, loadersList, m_resource_model));
connect(m_flame_check_task.get(), &CheckUpdateTask::checkFailed, this, 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 }); }); [this](Resource* resource, QString reason, QUrl recover_url) {
m_failed_check_update.append({ resource, reason, recover_url });
});
check_task.addTask(m_flame_check_task); check_task.addTask(m_flame_check_task);
} }
@ -132,11 +137,11 @@ void ModUpdateDialog::checkCandidates()
// Add found updates for Modrinth // Add found updates for Modrinth
if (m_modrinth_check_task) { 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) { for (auto& updatable : modrinth_updates) {
qDebug() << QString("Mod %1 has an update available!").arg(updatable.name); qDebug() << QString("Mod %1 has an update available!").arg(updatable.name);
appendMod(updatable); appendResource(updatable);
m_tasks.insert(updatable.name, updatable.download); m_tasks.insert(updatable.name, updatable.download);
} }
selectedVers.append(m_modrinth_check_task->getDependencies()); selectedVers.append(m_modrinth_check_task->getDependencies());
@ -144,11 +149,11 @@ void ModUpdateDialog::checkCandidates()
// Add found updated for Flame // Add found updated for Flame
if (m_flame_check_task) { 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) { for (auto& updatable : flame_updates) {
qDebug() << QString("Mod %1 has an update available!").arg(updatable.name); qDebug() << QString("Mod %1 has an update available!").arg(updatable.name);
appendMod(updatable); appendResource(updatable);
m_tasks.insert(updatable.name, updatable.download); m_tasks.insert(updatable.name, updatable.download);
} }
selectedVers.append(m_flame_check_task->getDependencies()); selectedVers.append(m_flame_check_task->getDependencies());
@ -175,8 +180,8 @@ void ModUpdateDialog::checkCandidates()
} }
ScrollMessageBox message_dialog(m_parent, tr("Failed to check for updates"), ScrollMessageBox message_dialog(m_parent, tr("Failed to check for updates"),
tr("Could not check or get the following mods for updates:<br>" tr("Could not check or get the following resources for updates:<br>"
"Do you wish to proceed without those mods?"), "Do you wish to proceed without those resources?"),
text); text);
message_dialog.setModal(true); message_dialog.setModal(true);
if (message_dialog.exec() == QDialog::Rejected) { if (message_dialog.exec() == QDialog::Rejected) {
@ -187,59 +192,58 @@ void ModUpdateDialog::checkCandidates()
} }
if (m_include_deps && !APPLICATION->settings()->get("ModDependenciesDisabled").toBool()) { // dependencies 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());
connect(depTask.get(), &Task::failed, this, if (mod_model != nullptr) {
[this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); }); auto depTask = makeShared<GetModDependenciesTask>(this, m_instance, mod_model, selectedVers);
auto weak = depTask.toWeakRef(); connect(depTask.get(), &Task::failed, this, [this](const QString& reason) {
connect(depTask.get(), &Task::succeeded, this, [this, weak]() { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec();
QStringList warnings; });
if (auto depTask = weak.lock()) { auto weak = depTask.toWeakRef();
warnings = depTask->warnings(); connect(depTask.get(), &Task::succeeded, this, [this, weak]() {
QStringList warnings;
if (auto depTask = weak.lock()) {
warnings = depTask->warnings();
}
if (warnings.count()) {
CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->exec();
}
});
ProgressDialog progress_dialog_deps(m_parent);
progress_dialog_deps.setSkipButton(true, tr("Abort"));
progress_dialog_deps.setWindowTitle(tr("Checking for dependencies..."));
auto dret = progress_dialog_deps.execWithTask(depTask.get());
// If the dialog was skipped / some download error happened
if (dret == QDialog::DialogCode::Rejected) {
m_aborted = true;
QMetaObject::invokeMethod(this, "reject", Qt::QueuedConnection);
return;
} }
if (warnings.count()) { static FlameAPI api;
CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->exec();
auto dependencyExtraInfo = depTask->getExtraInfo();
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_resource_model);
auto extraInfo = dependencyExtraInfo.value(dep->version.addonId.toString());
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, !extraInfo.maybe_installed
};
appendResource(updatable, extraInfo.required_by);
m_tasks.insert(updatable.name, updatable.download);
} }
});
ProgressDialog progress_dialog_deps(m_parent);
progress_dialog_deps.setSkipButton(true, tr("Abort"));
progress_dialog_deps.setWindowTitle(tr("Checking for dependencies..."));
auto dret = progress_dialog_deps.execWithTask(depTask.get());
// If the dialog was skipped / some download error happened
if (dret == QDialog::DialogCode::Rejected) {
m_aborted = true;
QMetaObject::invokeMethod(this, "reject", Qt::QueuedConnection);
return;
}
static FlameAPI api;
auto dependencyExtraInfo = depTask->getExtraInfo();
for (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);
auto extraInfo = dependencyExtraInfo.value(dep->version.addonId.toString());
CheckUpdateTask::UpdatableMod updatable = { dep->pack->name,
dep->version.hash,
"",
dep->version.version,
dep->version.version_type,
changelog,
dep->pack->provider,
download_task,
!extraInfo.maybe_installed };
appendMod(updatable, extraInfo.required_by);
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) { if (ui->modTreeWidget->topLevelItemCount() == 0) {
m_no_updates = true; m_no_updates = true;
} else { } else {
@ -261,7 +265,7 @@ void ModUpdateDialog::checkCandidates()
} }
// Part 1: Ensure we have a valid metadata // Part 1: Ensure we have a valid metadata
auto ModUpdateDialog::ensureMetadata() -> bool auto ResourceUpdateDialog::ensureMetadata() -> bool
{ {
auto index_dir = indexDir(); auto index_dir = indexDir();
@ -269,27 +273,27 @@ auto ModUpdateDialog::ensureMetadata() -> bool
// A better use of data structures here could remove the need for this QHash // A better use of data structures here could remove the need for this QHash
QHash<QString, bool> should_try_others; QHash<QString, bool> should_try_others;
QList<Mod*> modrinth_tmp; QList<Resource*> modrinth_tmp;
QList<Mod*> flame_tmp; QList<Resource*> flame_tmp;
bool confirm_rest = false; bool confirm_rest = false;
bool try_others_rest = false; bool try_others_rest = false;
bool skip_rest = false; bool skip_rest = false;
ModPlatform::ResourceProvider provider_rest = ModPlatform::ResourceProvider::MODRINTH; ModPlatform::ResourceProvider provider_rest = ModPlatform::ResourceProvider::MODRINTH;
auto addToTmp = [&modrinth_tmp, &flame_tmp](Mod* m, ModPlatform::ResourceProvider p) { auto addToTmp = [&modrinth_tmp, &flame_tmp](Resource* resource, ModPlatform::ResourceProvider p) {
switch (p) { switch (p) {
case ModPlatform::ResourceProvider::MODRINTH: case ModPlatform::ResourceProvider::MODRINTH:
modrinth_tmp.push_back(m); modrinth_tmp.push_back(resource);
break; break;
case ModPlatform::ResourceProvider::FLAME: case ModPlatform::ResourceProvider::FLAME:
flame_tmp.push_back(m); flame_tmp.push_back(resource);
break; break;
} }
}; };
for (auto candidate : m_candidates) { for (auto candidate : m_candidates) {
if (candidate->status() != ModStatus::NoMetadata) { if (candidate->status() != ResourceStatus::NO_METADATA) {
onMetadataEnsured(candidate); onMetadataEnsured(candidate);
continue; continue;
} }
@ -308,7 +312,7 @@ auto ModUpdateDialog::ensureMetadata() -> bool
} }
ChooseProviderDialog chooser(this); 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. " "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.") "To do this, please select a mod provider which we can use to check for updates for this mod.")
.arg(candidate->name())); .arg(candidate->name()));
@ -332,8 +336,8 @@ auto ModUpdateDialog::ensureMetadata() -> bool
if (!modrinth_tmp.empty()) { if (!modrinth_tmp.empty()) {
auto modrinth_task = makeShared<EnsureMetadataTask>(modrinth_tmp, index_dir, ModPlatform::ResourceProvider::MODRINTH); 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::metadataReady, [this](Resource* candidate) { onMetadataEnsured(candidate); });
connect(modrinth_task.get(), &EnsureMetadataTask::metadataFailed, [this, &should_try_others](Mod* 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); onMetadataFailed(candidate, should_try_others.find(candidate->internal_id()).value(), ModPlatform::ResourceProvider::MODRINTH);
}); });
connect(modrinth_task.get(), &EnsureMetadataTask::failed, connect(modrinth_task.get(), &EnsureMetadataTask::failed,
@ -347,8 +351,8 @@ auto ModUpdateDialog::ensureMetadata() -> bool
if (!flame_tmp.empty()) { if (!flame_tmp.empty()) {
auto flame_task = makeShared<EnsureMetadataTask>(flame_tmp, index_dir, ModPlatform::ResourceProvider::FLAME); 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::metadataReady, [this](Resource* candidate) { onMetadataEnsured(candidate); });
connect(flame_task.get(), &EnsureMetadataTask::metadataFailed, [this, &should_try_others](Mod* 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); onMetadataFailed(candidate, should_try_others.find(candidate->internal_id()).value(), ModPlatform::ResourceProvider::FLAME);
}); });
connect(flame_task.get(), &EnsureMetadataTask::failed, connect(flame_task.get(), &EnsureMetadataTask::failed,
@ -370,18 +374,18 @@ auto ModUpdateDialog::ensureMetadata() -> bool
return (ret_metadata != QDialog::DialogCode::Rejected); return (ret_metadata != QDialog::DialogCode::Rejected);
} }
void ModUpdateDialog::onMetadataEnsured(Mod* mod) void ResourceUpdateDialog::onMetadataEnsured(Resource* resource)
{ {
// When the mod is a folder, for instance // When the mod is a folder, for instance
if (!mod->metadata()) if (!resource->metadata())
return; return;
switch (mod->metadata()->provider) { switch (resource->metadata()->provider) {
case ModPlatform::ResourceProvider::MODRINTH: case ModPlatform::ResourceProvider::MODRINTH:
m_modrinth_to_update.push_back(mod); m_modrinth_to_update.push_back(resource);
break; break;
case ModPlatform::ResourceProvider::FLAME: case ModPlatform::ResourceProvider::FLAME:
m_flame_to_update.push_back(mod); m_flame_to_update.push_back(resource);
break; break;
} }
} }
@ -398,26 +402,26 @@ ModPlatform::ResourceProvider next(ModPlatform::ResourceProvider p)
return ModPlatform::ResourceProvider::FLAME; 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) { if (try_others) {
auto index_dir = indexDir(); auto index_dir = indexDir();
auto task = makeShared<EnsureMetadataTask>(mod, index_dir, next(first_choice)); auto task = makeShared<EnsureMetadataTask>(resource, index_dir, next(first_choice));
connect(task.get(), &EnsureMetadataTask::metadataReady, [this](Mod* candidate) { onMetadataEnsured(candidate); }); connect(task.get(), &EnsureMetadataTask::metadataReady, [this](Resource* candidate) { onMetadataEnsured(candidate); });
connect(task.get(), &EnsureMetadataTask::metadataFailed, [this](Mod* candidate) { onMetadataFailed(candidate, false); }); connect(task.get(), &EnsureMetadataTask::metadataFailed, [this](Resource* candidate) { onMetadataFailed(candidate, false); });
connect(task.get(), &EnsureMetadataTask::failed, 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); m_second_try_metadata->addTask(task);
} else { } else {
QString reason{ tr("Couldn't find a valid version on the selected mod provider(s)") }; 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); auto item_top = new QTreeWidgetItem(ui->modTreeWidget);
item_top->setCheckState(0, info.enabled ? Qt::CheckState::Checked : Qt::CheckState::Unchecked); item_top->setCheckState(0, info.enabled ? Qt::CheckState::Checked : Qt::CheckState::Unchecked);
@ -431,7 +435,7 @@ void ModUpdateDialog::appendMod(CheckUpdateTask::UpdatableMod const& info, QStri
provider_item->setText(0, tr("Provider: %1").arg(ModPlatform::ProviderCapabilities::readableName(info.provider))); provider_item->setText(0, tr("Provider: %1").arg(ModPlatform::ProviderCapabilities::readableName(info.provider)));
auto old_version_item = new QTreeWidgetItem(item_top); 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); auto new_version_item = new QTreeWidgetItem(item_top);
new_version_item->setText(0, tr("New version: %1").arg(info.new_version)); new_version_item->setText(0, tr("New version: %1").arg(info.new_version));
@ -485,7 +489,7 @@ void ModUpdateDialog::appendMod(CheckUpdateTask::UpdatableMod const& info, QStri
ui->modTreeWidget->addTopLevelItem(item_top); ui->modTreeWidget->addTopLevelItem(item_top);
} }
auto ModUpdateDialog::getTasks() -> const QList<ResourceDownloadTask::Ptr> auto ResourceUpdateDialog::getTasks() -> const QList<ResourceDownloadTask::Ptr>
{ {
QList<ResourceDownloadTask::Ptr> list; QList<ResourceDownloadTask::Ptr> list;

View File

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

View File

@ -132,9 +132,9 @@ class InstallJavaPage : public QWidget, public BasePage {
m_recommended_majors = majors; m_recommended_majors = majors;
recommendedFilterChanged(); recommendedFilterChanged();
} }
void setRecomend(bool recomend) void setRecommend(bool recommend)
{ {
m_recommend = recomend; m_recommend = recommend;
recommendedFilterChanged(); recommendedFilterChanged();
} }
void recommendedFilterChanged() void recommendedFilterChanged()
@ -202,7 +202,7 @@ InstallDialog::InstallDialog(const QString& uid, BaseInstance* instance, QWidget
recommendedCheckBox->setCheckState(Qt::CheckState::Checked); recommendedCheckBox->setCheckState(Qt::CheckState::Checked);
connect(recommendedCheckBox, &QCheckBox::stateChanged, this, [this](int state) { connect(recommendedCheckBox, &QCheckBox::stateChanged, this, [this](int state) {
for (BasePage* page : container->getPages()) { for (BasePage* page : container->getPages()) {
pageCast(page)->setRecomend(state == Qt::Checked); pageCast(page)->setRecommend(state == Qt::Checked);
} }
}); });
@ -261,7 +261,7 @@ InstallDialog::InstallDialog(const QString& uid, BaseInstance* instance, QWidget
container->selectPage(page->id()); container->selectPage(page->id());
auto cast = pageCast(page); auto cast = pageCast(page);
cast->setRecomend(true); cast->setRecommend(true);
connect(cast, &InstallJavaPage::selectionChanged, this, [this, cast] { validate(cast); }); connect(cast, &InstallJavaPage::selectionChanged, this, [this, cast] { validate(cast); });
if (!recommendedJavas.isEmpty()) { if (!recommendedJavas.isEmpty()) {
cast->setRecommendedMajors(recommendedJavas); cast->setRecommendedMajors(recommendedJavas);
@ -344,4 +344,4 @@ void InstallDialog::done(int result)
} // namespace Java } // namespace Java
#include "InstallJavaDialog.moc" #include "InstallJavaDialog.moc"

View File

@ -74,6 +74,7 @@ ExternalResourcesPage::ExternalResourcesPage(BaseInstance* instance, std::shared
connect(ui->actionRemoveItem, &QAction::triggered, this, &ExternalResourcesPage::removeItem); connect(ui->actionRemoveItem, &QAction::triggered, this, &ExternalResourcesPage::removeItem);
connect(ui->actionEnableItem, &QAction::triggered, this, &ExternalResourcesPage::enableItem); connect(ui->actionEnableItem, &QAction::triggered, this, &ExternalResourcesPage::enableItem);
connect(ui->actionDisableItem, &QAction::triggered, this, &ExternalResourcesPage::disableItem); connect(ui->actionDisableItem, &QAction::triggered, this, &ExternalResourcesPage::disableItem);
connect(ui->actionViewHomepage, &QAction::triggered, this, &ExternalResourcesPage::viewHomepage);
connect(ui->actionViewConfigs, &QAction::triggered, this, &ExternalResourcesPage::viewConfigs); connect(ui->actionViewConfigs, &QAction::triggered, this, &ExternalResourcesPage::viewConfigs);
connect(ui->actionViewFolder, &QAction::triggered, this, &ExternalResourcesPage::viewFolder); connect(ui->actionViewFolder, &QAction::triggered, this, &ExternalResourcesPage::viewFolder);
@ -81,16 +82,28 @@ ExternalResourcesPage::ExternalResourcesPage(BaseInstance* instance, std::shared
connect(ui->treeView, &ModListView::activated, this, &ExternalResourcesPage::itemActivated); connect(ui->treeView, &ModListView::activated, this, &ExternalResourcesPage::itemActivated);
auto selection_model = ui->treeView->selectionModel(); auto selection_model = ui->treeView->selectionModel();
connect(selection_model, &QItemSelectionModel::currentChanged, this, &ExternalResourcesPage::current);
connect(selection_model, &QItemSelectionModel::currentChanged, this, [this](const QModelIndex& current, const QModelIndex& previous) {
if (!current.isValid()) {
ui->frame->clear();
return;
}
updateFrame(current, previous);
});
auto updateExtra = [this]() { auto updateExtra = [this]() {
if (updateExtraInfo) if (updateExtraInfo)
updateExtraInfo(id(), extraHeaderInfoString()); updateExtraInfo(id(), extraHeaderInfoString());
}; };
connect(selection_model, &QItemSelectionModel::selectionChanged, this, updateExtra); connect(selection_model, &QItemSelectionModel::selectionChanged, this, updateExtra);
connect(model.get(), &ResourceFolderModel::updateFinished, this, updateExtra); connect(model.get(), &ResourceFolderModel::updateFinished, this, updateExtra);
connect(model.get(), &ResourceFolderModel::parseFinished, this, updateExtra); connect(model.get(), &ResourceFolderModel::parseFinished, this, updateExtra);
connect(ui->filterEdit, &QLineEdit::textChanged, this, &ExternalResourcesPage::filterTextChanged); connect(selection_model, &QItemSelectionModel::selectionChanged, this, [this] { updateActions(); });
connect(m_model.get(), &ResourceFolderModel::rowsInserted, this, [this] { updateActions(); });
connect(m_model.get(), &ResourceFolderModel::rowsRemoved, this, [this] { updateActions(); });
auto viewHeader = ui->treeView->header(); auto viewHeader = ui->treeView->header();
viewHeader->setContextMenuPolicy(Qt::CustomContextMenu); viewHeader->setContextMenuPolicy(Qt::CustomContextMenu);
@ -289,6 +302,16 @@ void ExternalResourcesPage::disableItem()
m_model->setResourceEnabled(selection.indexes(), EnableAction::DISABLE); m_model->setResourceEnabled(selection.indexes(), EnableAction::DISABLE);
} }
void ExternalResourcesPage::viewHomepage()
{
auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes();
for (auto resource : m_model->selectedResources(selection)) {
auto url = resource->homepage();
if (!url.isEmpty())
DesktopServices::openUrl(url);
}
}
void ExternalResourcesPage::viewConfigs() void ExternalResourcesPage::viewConfigs()
{ {
DesktopServices::openPath(m_instance->instanceConfigFolder(), true); DesktopServices::openPath(m_instance->instanceConfigFolder(), true);
@ -299,23 +322,32 @@ void ExternalResourcesPage::viewFolder()
DesktopServices::openPath(m_model->dir().absolutePath(), true); DesktopServices::openPath(m_model->dir().absolutePath(), true);
} }
bool ExternalResourcesPage::current(const QModelIndex& current, const QModelIndex& previous) void ExternalResourcesPage::updateActions()
{ {
if (!current.isValid()) { const bool hasSelection = ui->treeView->selectionModel()->hasSelection();
ui->frame->clear(); const QModelIndexList selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes();
return false; const QList<Resource*> selectedResources = m_model->selectedResources(selection);
}
return onSelectionChanged(current, previous); ui->actionUpdateItem->setEnabled(!m_model->empty());
ui->actionResetItemMetadata->setEnabled(hasSelection);
ui->actionChangeVersion->setEnabled(selectedResources.size() == 1 && selectedResources[0]->metadata() != nullptr);
ui->actionRemoveItem->setEnabled(hasSelection);
ui->actionEnableItem->setEnabled(hasSelection);
ui->actionDisableItem->setEnabled(hasSelection);
ui->actionViewHomepage->setEnabled(hasSelection && std::any_of(selectedResources.begin(), selectedResources.end(),
[](Resource* resource) { return !resource->homepage().isEmpty(); }));
ui->actionExportMetadata->setEnabled(!m_model->empty());
} }
bool ExternalResourcesPage::onSelectionChanged(const QModelIndex& current, [[maybe_unused]] const QModelIndex& previous) void ExternalResourcesPage::updateFrame(const QModelIndex& current, [[maybe_unused]] const QModelIndex& previous)
{ {
auto sourceCurrent = m_filterModel->mapToSource(current); auto sourceCurrent = m_filterModel->mapToSource(current);
int row = sourceCurrent.row(); int row = sourceCurrent.row();
Resource const& resource = m_model->at(row); Resource const& resource = m_model->at(row);
ui->frame->updateWithResource(resource); ui->frame->updateWithResource(resource);
return true;
} }
QString ExternalResourcesPage::extraHeaderInfoString() QString ExternalResourcesPage::extraHeaderInfoString()

View File

@ -42,9 +42,8 @@ class ExternalResourcesPage : public QMainWindow, public BasePage {
QMenu* createPopupMenu() override; QMenu* createPopupMenu() override;
public slots: public slots:
bool current(const QModelIndex& current, const QModelIndex& previous); virtual void updateActions();
virtual void updateFrame(const QModelIndex& current, const QModelIndex& previous);
virtual bool onSelectionChanged(const QModelIndex& current, const QModelIndex& previous);
protected slots: protected slots:
void itemActivated(const QModelIndex& index); void itemActivated(const QModelIndex& index);
@ -57,6 +56,8 @@ class ExternalResourcesPage : public QMainWindow, public BasePage {
virtual void enableItem(); virtual void enableItem();
virtual void disableItem(); virtual void disableItem();
virtual void viewHomepage();
virtual void viewFolder(); virtual void viewFolder();
virtual void viewConfigs(); virtual void viewConfigs();

View File

@ -60,7 +60,7 @@
<bool>true</bool> <bool>true</bool>
</property> </property>
<property name="dragDropMode"> <property name="dragDropMode">
<enum>QAbstractItemView::DropOnly</enum> <enum>QAbstractItemView::NoDragDrop</enum>
</property> </property>
<property name="uniformRowHeights"> <property name="uniformRowHeights">
<bool>true</bool> <bool>true</bool>
@ -74,7 +74,7 @@
<string>Actions</string> <string>Actions</string>
</property> </property>
<property name="toolButtonStyle"> <property name="toolButtonStyle">
<enum>Qt::ToolButtonTextOnly</enum> <enum>Qt::ToolButtonIconOnly</enum>
</property> </property>
<property name="useDefaultAction" stdset="0"> <property name="useDefaultAction" stdset="0">
<bool>true</bool> <bool>true</bool>
@ -90,39 +90,50 @@
<addaction name="actionRemoveItem"/> <addaction name="actionRemoveItem"/>
<addaction name="actionEnableItem"/> <addaction name="actionEnableItem"/>
<addaction name="actionDisableItem"/> <addaction name="actionDisableItem"/>
<addaction name="separator"/>
<addaction name="actionViewHomepage"/>
<addaction name="actionViewConfigs"/> <addaction name="actionViewConfigs"/>
<addaction name="actionViewFolder"/> <addaction name="actionViewFolder"/>
</widget> </widget>
<action name="actionAddItem"> <action name="actionAddItem">
<property name="text"> <property name="text">
<string>&amp;Add</string> <string>&amp;Add File</string>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>Add</string> <string>Add a locally downloaded file.</string>
</property> </property>
</action> </action>
<action name="actionRemoveItem"> <action name="actionRemoveItem">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text"> <property name="text">
<string>&amp;Remove</string> <string>&amp;Remove</string>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>Remove selected item</string> <string>Remove all selected items.</string>
</property> </property>
</action> </action>
<action name="actionEnableItem"> <action name="actionEnableItem">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text"> <property name="text">
<string>&amp;Enable</string> <string>&amp;Enable</string>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>Enable selected item</string> <string>Enable all selected items.</string>
</property> </property>
</action> </action>
<action name="actionDisableItem"> <action name="actionDisableItem">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text"> <property name="text">
<string>&amp;Disable</string> <string>&amp;Disable</string>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>Disable selected item</string> <string>Disable all selected items.</string>
</property> </property>
</action> </action>
<action name="actionViewConfigs"> <action name="actionViewConfigs">
@ -137,6 +148,9 @@
<property name="text"> <property name="text">
<string>View &amp;Folder</string> <string>View &amp;Folder</string>
</property> </property>
<property name="toolTip">
<string>Open the folder in the system file manager.</string>
</property>
</action> </action>
<action name="actionDownloadItem"> <action name="actionDownloadItem">
<property name="enabled"> <property name="enabled">
@ -146,40 +160,70 @@
<string>&amp;Download</string> <string>&amp;Download</string>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>Download a new resource</string> <string>Download resources from online mod platforms.</string>
</property>
</action>
<action name="actionVisitItemPage">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Visit mod's page</string>
</property>
<property name="toolTip">
<string>Go to mods home page</string>
</property> </property>
</action> </action>
<action name="actionUpdateItem"> <action name="actionUpdateItem">
<property name="enabled"> <property name="enabled">
<bool>true</bool> <bool>false</bool>
</property> </property>
<property name="text"> <property name="text">
<string>Check for &amp;Updates</string> <string>Check for &amp;Updates</string>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>Try to check or update all selected resources (all resources if none are selected)</string> <string>Try to check or update all selected resources (all resources if none are selected).</string>
</property>
</action>
<action name="actionResetItemMetadata">
<property name="text">
<string>Reset Update Metadata</string>
</property>
<property name="menuRole">
<enum>QAction::NoRole</enum>
</property>
</action>
<action name="actionVerifyItemDependencies">
<property name="text">
<string>Verify Dependencies</string>
</property>
<property name="menuRole">
<enum>QAction::NoRole</enum>
</property> </property>
</action> </action>
<action name="actionExportMetadata"> <action name="actionExportMetadata">
<property name="enabled"> <property name="enabled">
<bool>true</bool> <bool>false</bool>
</property> </property>
<property name="text"> <property name="text">
<string>Export modlist</string> <string>Export List</string>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>Export mod's metadata to text</string> <string>Export resource's metadata to text.</string>
</property>
</action>
<action name="actionChangeVersion">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Change Version</string>
</property>
<property name="toolTip">
<string>Change a resource's version.</string>
</property>
<property name="menuRole">
<enum>QAction::NoRole</enum>
</property>
</action>
<action name="actionViewHomepage">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>View Homepage</string>
</property>
<property name="toolTip">
<string>View the homepages of all selected items.</string>
</property> </property>
</action> </action>
</widget> </widget>

View File

@ -53,8 +53,8 @@
#include "ui/GuiUtil.h" #include "ui/GuiUtil.h"
#include "ui/dialogs/CustomMessageBox.h" #include "ui/dialogs/CustomMessageBox.h"
#include "ui/dialogs/ModUpdateDialog.h"
#include "ui/dialogs/ResourceDownloadDialog.h" #include "ui/dialogs/ResourceDownloadDialog.h"
#include "ui/dialogs/ResourceUpdateDialog.h"
#include "DesktopServices.h" #include "DesktopServices.h"
@ -71,98 +71,47 @@
#include "tasks/Task.h" #include "tasks/Task.h"
#include "ui/dialogs/ProgressDialog.h" #include "ui/dialogs/ProgressDialog.h"
ModFolderPage::ModFolderPage(BaseInstance* inst, std::shared_ptr<ModFolderModel> mods, QWidget* parent) ModFolderPage::ModFolderPage(BaseInstance* inst, std::shared_ptr<ModFolderModel> model, QWidget* parent)
: ExternalResourcesPage(inst, mods, parent), m_model(mods) : ExternalResourcesPage(inst, model, parent), m_model(model)
{ {
// This is structured like that so that these changes ui->actionDownloadItem->setText(tr("Download Mods"));
// do not affect the Resource pack and Shader pack tabs ui->actionDownloadItem->setToolTip(tr("Download mods from online mod platforms"));
{ ui->actionDownloadItem->setEnabled(true);
ui->actionDownloadItem->setText(tr("Download mods")); ui->actionsToolbar->insertActionBefore(ui->actionAddItem, ui->actionDownloadItem);
ui->actionDownloadItem->setToolTip(tr("Download mods from online mod platforms"));
ui->actionDownloadItem->setEnabled(true);
ui->actionAddItem->setText(tr("Add file"));
ui->actionAddItem->setToolTip(tr("Add a locally downloaded file"));
ui->actionsToolbar->insertActionBefore(ui->actionAddItem, ui->actionDownloadItem); connect(ui->actionDownloadItem, &QAction::triggered, this, &ModFolderPage::downloadMods);
connect(ui->actionDownloadItem, &QAction::triggered, this, &ModFolderPage::installMods); ui->actionUpdateItem->setToolTip(tr("Try to check or update all selected mods (all mods if none are selected)"));
connect(ui->actionUpdateItem, &QAction::triggered, this, &ModFolderPage::updateMods);
ui->actionsToolbar->insertActionBefore(ui->actionAddItem, ui->actionUpdateItem);
// update menu auto updateMenu = new QMenu(this);
auto updateMenu = ui->actionUpdateItem->menu();
if (updateMenu) {
updateMenu->clear();
} else {
updateMenu = new QMenu(this);
}
auto update = updateMenu->addAction(tr("Check for Updates")); auto update = updateMenu->addAction(tr("Check for Updates"));
update->setToolTip(tr("Try to check or update all selected mods (all mods if none are selected)")); connect(update, &QAction::triggered, this, &ModFolderPage::updateMods);
connect(update, &QAction::triggered, this, &ModFolderPage::updateMods);
auto updateWithDeps = updateMenu->addAction(tr("Verify Dependencies")); updateMenu->addAction(ui->actionVerifyItemDependencies);
updateWithDeps->setToolTip( connect(ui->actionVerifyItemDependencies, &QAction::triggered, this, [this] { updateMods(true); });
tr("Try to update and check for missing dependencies all selected mods (all mods if none are selected)"));
connect(updateWithDeps, &QAction::triggered, this, [this] { updateMods(true); });
auto depsDisabled = APPLICATION->settings()->getSetting("ModDependenciesDisabled"); auto depsDisabled = APPLICATION->settings()->getSetting("ModDependenciesDisabled");
updateWithDeps->setVisible(!depsDisabled->get().toBool()); ui->actionVerifyItemDependencies->setVisible(!depsDisabled->get().toBool());
connect(depsDisabled.get(), &Setting::SettingChanged, this, connect(depsDisabled.get(), &Setting::SettingChanged, this,
[updateWithDeps](const Setting& setting, QVariant value) { updateWithDeps->setVisible(!value.toBool()); }); [this](const Setting& setting, const QVariant& value) { ui->actionVerifyItemDependencies->setVisible(!value.toBool()); });
auto actionRemoveItemMetadata = updateMenu->addAction(tr("Reset update metadata")); updateMenu->addAction(ui->actionResetItemMetadata);
actionRemoveItemMetadata->setToolTip(tr("Remove mod's metadata")); connect(ui->actionResetItemMetadata, &QAction::triggered, this, &ModFolderPage::deleteModMetadata);
connect(actionRemoveItemMetadata, &QAction::triggered, this, &ModFolderPage::deleteModMetadata);
actionRemoveItemMetadata->setEnabled(false);
ui->actionUpdateItem->setMenu(updateMenu); ui->actionUpdateItem->setMenu(updateMenu);
ui->actionUpdateItem->setToolTip(tr("Try to check or update all selected mods (all mods if none are selected)")); ui->actionChangeVersion->setToolTip(tr("Change a mod's version."));
connect(ui->actionUpdateItem, &QAction::triggered, this, &ModFolderPage::updateMods); connect(ui->actionChangeVersion, &QAction::triggered, this, &ModFolderPage::changeModVersion);
ui->actionsToolbar->insertActionBefore(ui->actionAddItem, ui->actionUpdateItem); ui->actionsToolbar->insertActionAfter(ui->actionUpdateItem, ui->actionChangeVersion);
ui->actionVisitItemPage->setToolTip(tr("Go to mod's home page")); ui->actionViewHomepage->setToolTip(tr("View the homepages of all selected mods."));
ui->actionsToolbar->addAction(ui->actionVisitItemPage);
connect(ui->actionVisitItemPage, &QAction::triggered, this, &ModFolderPage::visitModPages);
auto changeVersion = new QAction(tr("Change Version"), this); ui->actionExportMetadata->setToolTip(tr("Export mod's metadata to text."));
changeVersion->setToolTip(tr("Change mod version")); connect(ui->actionExportMetadata, &QAction::triggered, this, &ModFolderPage::exportModMetadata);
changeVersion->setEnabled(false); ui->actionsToolbar->insertActionAfter(ui->actionViewHomepage, ui->actionExportMetadata);
ui->actionsToolbar->insertActionAfter(ui->actionUpdateItem, changeVersion);
connect(changeVersion, &QAction::triggered, this, &ModFolderPage::changeModVersion);
ui->actionsToolbar->insertActionAfter(ui->actionVisitItemPage, ui->actionExportMetadata);
connect(ui->actionExportMetadata, &QAction::triggered, this, &ModFolderPage::exportModMetadata);
auto check_allow_update = [this] { return ui->treeView->selectionModel()->hasSelection() || !m_model->empty(); };
connect(ui->treeView->selectionModel(), &QItemSelectionModel::selectionChanged, this,
[this, check_allow_update, actionRemoveItemMetadata, changeVersion] {
ui->actionUpdateItem->setEnabled(check_allow_update());
auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes();
auto mods_list = m_model->selectedMods(selection);
auto selected = std::count_if(mods_list.cbegin(), mods_list.cend(),
[](Mod* v) { return v->metadata() != nullptr || v->homeurl().size() != 0; });
if (selected <= 1) {
ui->actionVisitItemPage->setText(tr("Visit mod's page"));
ui->actionVisitItemPage->setToolTip(tr("Go to mod's home page"));
} else {
ui->actionVisitItemPage->setText(tr("Visit mods' pages"));
ui->actionVisitItemPage->setToolTip(tr("Go to the pages of the selected mods"));
}
changeVersion->setEnabled(mods_list.length() == 1 && mods_list[0]->metadata() != nullptr);
ui->actionVisitItemPage->setEnabled(selected != 0);
actionRemoveItemMetadata->setEnabled(selected != 0);
});
auto updateButtons = [this, check_allow_update] { ui->actionUpdateItem->setEnabled(check_allow_update()); };
connect(mods.get(), &ModFolderModel::rowsInserted, this, updateButtons);
connect(mods.get(), &ModFolderModel::rowsRemoved, this, updateButtons);
connect(mods.get(), &ModFolderModel::updateFinished, this, updateButtons);
}
} }
bool ModFolderPage::shouldDisplay() const bool ModFolderPage::shouldDisplay() const
@ -170,15 +119,12 @@ bool ModFolderPage::shouldDisplay() const
return true; return true;
} }
bool ModFolderPage::onSelectionChanged(const QModelIndex& current, [[maybe_unused]] const QModelIndex& previous) void ModFolderPage::updateFrame(const QModelIndex& current, [[maybe_unused]] const QModelIndex& previous)
{ {
auto sourceCurrent = m_filterModel->mapToSource(current); auto sourceCurrent = m_filterModel->mapToSource(current);
int row = sourceCurrent.row(); int row = sourceCurrent.row();
Mod const* m = m_model->at(row); const Mod& mod = m_model->at(row);
if (m) ui->frame->updateWithMod(mod);
ui->frame->updateWithMod(*m);
return true;
} }
void ModFolderPage::removeItems(const QItemSelection& selection) void ModFolderPage::removeItems(const QItemSelection& selection)
@ -193,10 +139,10 @@ void ModFolderPage::removeItems(const QItemSelection& selection)
if (response != QMessageBox::Yes) if (response != QMessageBox::Yes)
return; return;
} }
m_model->deleteMods(selection.indexes()); m_model->deleteResources(selection.indexes());
} }
void ModFolderPage::installMods() void ModFolderPage::downloadMods()
{ {
if (m_instance->typeName() != "Minecraft") if (m_instance->typeName() != "Minecraft")
return; // this is a null instance or a legacy instance return; // this is a null instance or a legacy instance
@ -209,7 +155,7 @@ void ModFolderPage::installMods()
ResourceDownload::ModDownloadDialog mdownload(this, m_model, m_instance); ResourceDownload::ModDownloadDialog mdownload(this, m_model, m_instance);
if (mdownload.exec()) { if (mdownload.exec()) {
auto tasks = new ConcurrentTask(this, "Download Mods", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); auto tasks = new ConcurrentTask(this, tr("Download Mods"), APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt());
connect(tasks, &Task::failed, [this, tasks](QString reason) { connect(tasks, &Task::failed, [this, tasks](QString reason) {
CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show();
tasks->deleteLater(); tasks->deleteLater();
@ -266,12 +212,12 @@ void ModFolderPage::updateMods(bool includeDeps)
} }
auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes(); 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(); bool use_all = mods_list.empty();
if (use_all) 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, true);
update_dialog.checkCandidates(); update_dialog.checkCandidates();
if (update_dialog.aborted()) { if (update_dialog.aborted()) {
@ -321,6 +267,90 @@ void ModFolderPage::updateMods(bool includeDeps)
} }
} }
void ModFolderPage::deleteModMetadata()
{
auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes();
auto selectionCount = m_model->selectedMods(selection).length();
if (selectionCount == 0)
return;
if (selectionCount > 1) {
auto response = CustomMessageBox::selectable(this, tr("Confirm Removal"),
tr("You are about to remove the metadata for %1 mods.\n"
"Are you sure?")
.arg(selectionCount),
QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No)
->exec();
if (response != QMessageBox::Yes)
return;
}
m_model->deleteMetadata(selection);
}
void ModFolderPage::changeModVersion()
{
if (m_instance->typeName() != "Minecraft")
return; // this is a null instance or a legacy instance
auto profile = static_cast<MinecraftInstance*>(m_instance)->getPackProfile();
if (!profile->getModLoaders().has_value()) {
QMessageBox::critical(this, tr("Error"), tr("Please install a mod loader first!"));
return;
}
if (APPLICATION->settings()->get("ModMetadataDisabled").toBool()) {
QMessageBox::critical(this, tr("Error"), tr("Mod updates are unavailable when metadata is disabled!"));
return;
}
auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes();
auto mods_list = m_model->selectedMods(selection);
if (mods_list.length() != 1 || mods_list[0]->metadata() == nullptr)
return;
ResourceDownload::ModDownloadDialog mdownload(this, m_model, m_instance);
mdownload.setResourceMetadata((*mods_list.begin())->metadata());
if (mdownload.exec()) {
auto tasks = new ConcurrentTask(this, "Download Mods", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt());
connect(tasks, &Task::failed, [this, tasks](QString reason) {
CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show();
tasks->deleteLater();
});
connect(tasks, &Task::aborted, [this, tasks]() {
CustomMessageBox::selectable(this, tr("Aborted"), tr("Download stopped by user."), QMessageBox::Information)->show();
tasks->deleteLater();
});
connect(tasks, &Task::succeeded, [this, tasks]() {
QStringList warnings = tasks->warnings();
if (warnings.count())
CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show();
tasks->deleteLater();
});
for (auto& task : mdownload.getTasks()) {
tasks->addTask(task);
}
ProgressDialog loadDialog(this);
loadDialog.setSkipButton(true, tr("Abort"));
loadDialog.execWithTask(tasks);
m_model->update();
}
}
void ModFolderPage::exportModMetadata()
{
auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes();
auto selectedMods = m_model->selectedMods(selection);
if (selectedMods.length() == 0)
selectedMods = m_model->allMods();
std::sort(selectedMods.begin(), selectedMods.end(), [](const Mod* a, const Mod* b) { return a->name() < b->name(); });
ExportToModListDialog dlg(m_instance->name(), selectedMods, this);
dlg.exec();
}
CoreModFolderPage::CoreModFolderPage(BaseInstance* inst, std::shared_ptr<ModFolderModel> mods, QWidget* parent) CoreModFolderPage::CoreModFolderPage(BaseInstance* inst, std::shared_ptr<ModFolderModel> mods, QWidget* parent)
: ModFolderPage(inst, mods, parent) : ModFolderPage(inst, mods, parent)
{ {
@ -369,97 +399,3 @@ bool NilModFolderPage::shouldDisplay() const
{ {
return m_model->dir().exists(); return m_model->dir().exists();
} }
void ModFolderPage::visitModPages()
{
auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes();
for (auto mod : m_model->selectedMods(selection)) {
auto url = mod->metaurl();
if (!url.isEmpty())
DesktopServices::openUrl(url);
}
}
void ModFolderPage::deleteModMetadata()
{
auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes();
auto selectionCount = m_model->selectedMods(selection).length();
if (selectionCount == 0)
return;
if (selectionCount > 1) {
auto response = CustomMessageBox::selectable(this, tr("Confirm Removal"),
tr("You are about to remove the metadata for %1 mods.\n"
"Are you sure?")
.arg(selectionCount),
QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No)
->exec();
if (response != QMessageBox::Yes)
return;
}
m_model->deleteModsMetadata(selection);
}
void ModFolderPage::changeModVersion()
{
if (m_instance->typeName() != "Minecraft")
return; // this is a null instance or a legacy instance
auto profile = static_cast<MinecraftInstance*>(m_instance)->getPackProfile();
if (!profile->getModLoaders().has_value()) {
QMessageBox::critical(this, tr("Error"), tr("Please install a mod loader first!"));
return;
}
if (APPLICATION->settings()->get("ModMetadataDisabled").toBool()) {
QMessageBox::critical(this, tr("Error"), tr("Mod updates are unavailable when metadata is disabled!"));
return;
}
auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes();
auto mods_list = m_model->selectedMods(selection);
if (mods_list.length() != 1 || mods_list[0]->metadata() == nullptr)
return;
ResourceDownload::ModDownloadDialog mdownload(this, m_model, m_instance);
mdownload.setModMetadata((*mods_list.begin())->metadata());
if (mdownload.exec()) {
auto tasks = new ConcurrentTask(this, "Download Mods", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt());
connect(tasks, &Task::failed, [this, tasks](QString reason) {
CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show();
tasks->deleteLater();
});
connect(tasks, &Task::aborted, [this, tasks]() {
CustomMessageBox::selectable(this, tr("Aborted"), tr("Download stopped by user."), QMessageBox::Information)->show();
tasks->deleteLater();
});
connect(tasks, &Task::succeeded, [this, tasks]() {
QStringList warnings = tasks->warnings();
if (warnings.count())
CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show();
tasks->deleteLater();
});
for (auto& task : mdownload.getTasks()) {
tasks->addTask(task);
}
ProgressDialog loadDialog(this);
loadDialog.setSkipButton(true, tr("Abort"));
loadDialog.execWithTask(tasks);
m_model->update();
}
}
void ModFolderPage::exportModMetadata()
{
auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes();
auto selectedMods = m_model->selectedMods(selection);
if (selectedMods.length() == 0)
selectedMods = m_model->allMods();
std::sort(selectedMods.begin(), selectedMods.end(), [](const Mod* a, const Mod* b) { return a->name() < b->name(); });
ExportToModListDialog dlg(m_instance->name(), selectedMods, this);
dlg.exec();
}

View File

@ -44,7 +44,7 @@ class ModFolderPage : public ExternalResourcesPage {
Q_OBJECT Q_OBJECT
public: public:
explicit ModFolderPage(BaseInstance* inst, std::shared_ptr<ModFolderModel> mods, QWidget* parent = nullptr); explicit ModFolderPage(BaseInstance* inst, std::shared_ptr<ModFolderModel> model, QWidget* parent = nullptr);
virtual ~ModFolderPage() = default; virtual ~ModFolderPage() = default;
void setFilter(const QString& filter) { m_fileSelectionFilter = filter; } void setFilter(const QString& filter) { m_fileSelectionFilter = filter; }
@ -57,16 +57,15 @@ class ModFolderPage : public ExternalResourcesPage {
virtual bool shouldDisplay() const override; virtual bool shouldDisplay() const override;
public slots: public slots:
bool onSelectionChanged(const QModelIndex& current, const QModelIndex& previous) override; void updateFrame(const QModelIndex& current, const QModelIndex& previous) override;
private slots: private slots:
void removeItems(const QItemSelection& selection) override; void removeItems(const QItemSelection& selection) override;
void downloadMods();
void updateMods(bool includeDeps = false);
void deleteModMetadata(); void deleteModMetadata();
void exportModMetadata(); void exportModMetadata();
void installMods();
void updateMods(bool includeDeps = false);
void visitModPages();
void changeModVersion(); void changeModVersion();
protected: protected:

View File

@ -42,35 +42,51 @@
#include "ui/dialogs/CustomMessageBox.h" #include "ui/dialogs/CustomMessageBox.h"
#include "ui/dialogs/ProgressDialog.h" #include "ui/dialogs/ProgressDialog.h"
#include "ui/dialogs/ResourceDownloadDialog.h" #include "ui/dialogs/ResourceDownloadDialog.h"
#include "ui/dialogs/ResourceUpdateDialog.h"
ResourcePackPage::ResourcePackPage(MinecraftInstance* instance, std::shared_ptr<ResourcePackFolderModel> model, QWidget* parent) ResourcePackPage::ResourcePackPage(MinecraftInstance* instance, std::shared_ptr<ResourcePackFolderModel> model, QWidget* parent)
: ExternalResourcesPage(instance, model, parent) : ExternalResourcesPage(instance, model, parent), m_model(model)
{ {
ui->actionDownloadItem->setText(tr("Download packs")); ui->actionDownloadItem->setText(tr("Download Packs"));
ui->actionDownloadItem->setToolTip(tr("Download resource packs from online platforms")); ui->actionDownloadItem->setToolTip(tr("Download resource packs from online mod platforms"));
ui->actionDownloadItem->setEnabled(true); ui->actionDownloadItem->setEnabled(true);
connect(ui->actionDownloadItem, &QAction::triggered, this, &ResourcePackPage::downloadRPs);
ui->actionsToolbar->insertActionBefore(ui->actionAddItem, ui->actionDownloadItem); ui->actionsToolbar->insertActionBefore(ui->actionAddItem, ui->actionDownloadItem);
ui->actionViewConfigs->setVisible(false); connect(ui->actionDownloadItem, &QAction::triggered, this, &ResourcePackPage::downloadResourcePacks);
ui->actionUpdateItem->setToolTip(tr("Try to check or update all selected resource packs (all resource packs if none are selected)"));
connect(ui->actionUpdateItem, &QAction::triggered, this, &ResourcePackPage::updateResourcePacks);
ui->actionsToolbar->insertActionBefore(ui->actionAddItem, ui->actionUpdateItem);
auto updateMenu = new QMenu(this);
auto update = updateMenu->addAction(ui->actionUpdateItem->text());
connect(update, &QAction::triggered, this, &ResourcePackPage::updateResourcePacks);
updateMenu->addAction(ui->actionResetItemMetadata);
connect(ui->actionResetItemMetadata, &QAction::triggered, this, &ResourcePackPage::deleteResourcePackMetadata);
ui->actionUpdateItem->setMenu(updateMenu);
ui->actionChangeVersion->setToolTip(tr("Change a mod's version."));
connect(ui->actionChangeVersion, &QAction::triggered, this, &ResourcePackPage::changeResourcePackVersion);
ui->actionsToolbar->insertActionAfter(ui->actionUpdateItem, ui->actionChangeVersion);
} }
bool ResourcePackPage::onSelectionChanged(const QModelIndex& current, [[maybe_unused]] const QModelIndex& previous) void ResourcePackPage::updateFrame(const QModelIndex& current, [[maybe_unused]] const QModelIndex& previous)
{ {
auto sourceCurrent = m_filterModel->mapToSource(current); auto sourceCurrent = m_filterModel->mapToSource(current);
int row = sourceCurrent.row(); int row = sourceCurrent.row();
auto& rp = static_cast<ResourcePack&>(m_model->at(row)); auto& rp = static_cast<ResourcePack&>(m_model->at(row));
ui->frame->updateWithResourcePack(rp); ui->frame->updateWithResourcePack(rp);
return true;
} }
void ResourcePackPage::downloadRPs() void ResourcePackPage::downloadResourcePacks()
{ {
if (m_instance->typeName() != "Minecraft") if (m_instance->typeName() != "Minecraft")
return; // this is a null instance or a legacy instance return; // this is a null instance or a legacy instance
ResourceDownload::ResourcePackDownloadDialog mdownload(this, std::static_pointer_cast<ResourcePackFolderModel>(m_model), m_instance); ResourceDownload::ResourcePackDownloadDialog mdownload(this, m_model, m_instance);
if (mdownload.exec()) { if (mdownload.exec()) {
auto tasks = auto tasks =
new ConcurrentTask(this, "Download Resource Pack", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); new ConcurrentTask(this, "Download Resource Pack", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt());
@ -101,3 +117,157 @@ void ResourcePackPage::downloadRPs()
m_model->update(); m_model->update();
} }
} }
void ResourcePackPage::updateResourcePacks()
{
if (m_instance->typeName() != "Minecraft")
return; // this is a null instance or a legacy instance
auto profile = static_cast<MinecraftInstance*>(m_instance)->getPackProfile();
if (APPLICATION->settings()->get("ModMetadataDisabled").toBool()) {
QMessageBox::critical(this, tr("Error"), tr("Resource pack updates are unavailable when metadata is disabled!"));
return;
}
if (m_instance != nullptr && m_instance->isRunning()) {
auto response = CustomMessageBox::selectable(
this, tr("Confirm Update"),
tr("Updating resource packs while the game is running may cause pack duplication and game crashes.\n"
"The old files may not be deleted as they are in use.\n"
"Are you sure you want to do this?"),
QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No)
->exec();
if (response != QMessageBox::Yes)
return;
}
auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes();
auto mods_list = m_model->selectedResources(selection);
bool use_all = mods_list.empty();
if (use_all)
mods_list = m_model->allResources();
ResourceUpdateDialog update_dialog(this, m_instance, m_model, mods_list, false, false);
update_dialog.checkCandidates();
if (update_dialog.aborted()) {
CustomMessageBox::selectable(this, tr("Aborted"), tr("The resource pack updater was aborted!"), QMessageBox::Warning)->show();
return;
}
if (update_dialog.noUpdates()) {
QString message{ tr("'%1' is up-to-date! :)").arg(mods_list.front()->name()) };
if (mods_list.size() > 1) {
if (use_all) {
message = tr("All resource packs are up-to-date! :)");
} else {
message = tr("All selected resource packs are up-to-date! :)");
}
}
CustomMessageBox::selectable(this, tr("Update checker"), message)->exec();
return;
}
if (update_dialog.exec()) {
auto tasks =
new ConcurrentTask(this, "Download Resource Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt());
connect(tasks, &Task::failed, [this, tasks](QString reason) {
CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show();
tasks->deleteLater();
});
connect(tasks, &Task::aborted, [this, tasks]() {
CustomMessageBox::selectable(this, tr("Aborted"), tr("Download stopped by user."), QMessageBox::Information)->show();
tasks->deleteLater();
});
connect(tasks, &Task::succeeded, [this, tasks]() {
QStringList warnings = tasks->warnings();
if (warnings.count()) {
CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show();
}
tasks->deleteLater();
});
for (auto task : update_dialog.getTasks()) {
tasks->addTask(task);
}
ProgressDialog loadDialog(this);
loadDialog.setSkipButton(true, tr("Abort"));
loadDialog.execWithTask(tasks);
m_model->update();
}
}
void ResourcePackPage::deleteResourcePackMetadata()
{
auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes();
auto selectionCount = m_model->selectedResourcePacks(selection).length();
if (selectionCount == 0)
return;
if (selectionCount > 1) {
auto response = CustomMessageBox::selectable(this, tr("Confirm Removal"),
tr("You are about to remove the metadata for %1 resource packs.\n"
"Are you sure?")
.arg(selectionCount),
QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No)
->exec();
if (response != QMessageBox::Yes)
return;
}
m_model->deleteMetadata(selection);
}
void ResourcePackPage::changeResourcePackVersion()
{
if (m_instance->typeName() != "Minecraft")
return; // this is a null instance or a legacy instance
if (APPLICATION->settings()->get("ModMetadataDisabled").toBool()) {
QMessageBox::critical(this, tr("Error"), tr("Resource pack updates are unavailable when metadata is disabled!"));
return;
}
const QModelIndexList rows = ui->treeView->selectionModel()->selectedRows();
if (rows.count() != 1)
return;
Resource &resource = m_model->at(m_filterModel->mapToSource(rows[0]).row());
if (resource.metadata() == nullptr)
return;
ResourceDownload::ResourcePackDownloadDialog mdownload(this, m_model, m_instance);
mdownload.setResourceMetadata(resource.metadata());
if (mdownload.exec()) {
auto tasks =
new ConcurrentTask(this, "Download Resource Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt());
connect(tasks, &Task::failed, [this, tasks](QString reason) {
CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show();
tasks->deleteLater();
});
connect(tasks, &Task::aborted, [this, tasks]() {
CustomMessageBox::selectable(this, tr("Aborted"), tr("Download stopped by user."), QMessageBox::Information)->show();
tasks->deleteLater();
});
connect(tasks, &Task::succeeded, [this, tasks]() {
QStringList warnings = tasks->warnings();
if (warnings.count())
CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show();
tasks->deleteLater();
});
for (auto& task : mdownload.getTasks()) {
tasks->addTask(task);
}
ProgressDialog loadDialog(this);
loadDialog.setSkipButton(true, tr("Abort"));
loadDialog.execWithTask(tasks);
m_model->update();
}
}

View File

@ -58,6 +58,14 @@ class ResourcePackPage : public ExternalResourcesPage {
} }
public slots: public slots:
bool onSelectionChanged(const QModelIndex& current, const QModelIndex& previous) override; void updateFrame(const QModelIndex& current, const QModelIndex& previous) override;
void downloadRPs();
private slots:
void downloadResourcePacks();
void updateResourcePacks();
void deleteResourcePackMetadata();
void changeResourcePackVersion();
protected:
std::shared_ptr<ResourcePackFolderModel> m_model;
}; };

View File

@ -45,27 +45,198 @@
#include "ui/dialogs/CustomMessageBox.h" #include "ui/dialogs/CustomMessageBox.h"
#include "ui/dialogs/ProgressDialog.h" #include "ui/dialogs/ProgressDialog.h"
#include "ui/dialogs/ResourceDownloadDialog.h" #include "ui/dialogs/ResourceDownloadDialog.h"
#include "ui/dialogs/ResourceUpdateDialog.h"
ShaderPackPage::ShaderPackPage(MinecraftInstance* instance, std::shared_ptr<ShaderPackFolderModel> model, QWidget* parent) ShaderPackPage::ShaderPackPage(MinecraftInstance* instance, std::shared_ptr<ShaderPackFolderModel> model, QWidget* parent)
: ExternalResourcesPage(instance, model, parent) : ExternalResourcesPage(instance, model, parent), m_model(model)
{ {
ui->actionDownloadItem->setText(tr("Download shaders")); ui->actionDownloadItem->setText(tr("Download Packs"));
ui->actionDownloadItem->setToolTip(tr("Download shaders from online platforms")); ui->actionDownloadItem->setToolTip(tr("Download shader packs from online mod platforms"));
ui->actionDownloadItem->setEnabled(true); ui->actionDownloadItem->setEnabled(true);
connect(ui->actionDownloadItem, &QAction::triggered, this, &ShaderPackPage::downloadShaders);
ui->actionsToolbar->insertActionBefore(ui->actionAddItem, ui->actionDownloadItem); ui->actionsToolbar->insertActionBefore(ui->actionAddItem, ui->actionDownloadItem);
ui->actionViewConfigs->setVisible(false); connect(ui->actionDownloadItem, &QAction::triggered, this, &ShaderPackPage::downloadShaderPack);
ui->actionUpdateItem->setToolTip(tr("Try to check or update all selected shader packs (all shader packs if none are selected)"));
connect(ui->actionUpdateItem, &QAction::triggered, this, &ShaderPackPage::updateShaderPacks);
ui->actionsToolbar->insertActionBefore(ui->actionAddItem, ui->actionUpdateItem);
auto updateMenu = new QMenu(this);
auto update = updateMenu->addAction(ui->actionUpdateItem->text());
connect(update, &QAction::triggered, this, &ShaderPackPage::updateShaderPacks);
updateMenu->addAction(ui->actionResetItemMetadata);
connect(ui->actionResetItemMetadata, &QAction::triggered, this, &ShaderPackPage::deleteShaderPackMetadata);
ui->actionUpdateItem->setMenu(updateMenu);
ui->actionChangeVersion->setToolTip(tr("Change a shader pack's version."));
connect(ui->actionChangeVersion, &QAction::triggered, this, &ShaderPackPage::changeShaderPackVersion);
ui->actionsToolbar->insertActionAfter(ui->actionUpdateItem, ui->actionChangeVersion);
} }
void ShaderPackPage::downloadShaders() void ShaderPackPage::downloadShaderPack()
{ {
if (m_instance->typeName() != "Minecraft") if (m_instance->typeName() != "Minecraft")
return; // this is a null instance or a legacy instance return; // this is a null instance or a legacy instance
ResourceDownload::ShaderPackDownloadDialog mdownload(this, std::static_pointer_cast<ShaderPackFolderModel>(m_model), m_instance); ResourceDownload::ShaderPackDownloadDialog mdownload(this, m_model, m_instance);
if (mdownload.exec()) { if (mdownload.exec()) {
auto tasks = new ConcurrentTask(this, "Download Shaders", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); auto tasks = new ConcurrentTask(this, "Download Shader Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt());
connect(tasks, &Task::failed, [this, tasks](QString reason) {
CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show();
tasks->deleteLater();
});
connect(tasks, &Task::aborted, [this, tasks]() {
CustomMessageBox::selectable(this, tr("Aborted"), tr("Download stopped by user."), QMessageBox::Information)->show();
tasks->deleteLater();
});
connect(tasks, &Task::succeeded, [this, tasks]() {
QStringList warnings = tasks->warnings();
if (warnings.count())
CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show();
tasks->deleteLater();
});
for (auto& task : mdownload.getTasks()) {
tasks->addTask(task);
}
ProgressDialog loadDialog(this);
loadDialog.setSkipButton(true, tr("Abort"));
loadDialog.execWithTask(tasks);
m_model->update();
}
}
void ShaderPackPage::updateShaderPacks()
{
if (m_instance->typeName() != "Minecraft")
return; // this is a null instance or a legacy instance
auto profile = static_cast<MinecraftInstance*>(m_instance)->getPackProfile();
if (APPLICATION->settings()->get("ModMetadataDisabled").toBool()) {
QMessageBox::critical(this, tr("Error"), tr("Shader pack updates are unavailable when metadata is disabled!"));
return;
}
if (m_instance != nullptr && m_instance->isRunning()) {
auto response =
CustomMessageBox::selectable(this, tr("Confirm Update"),
tr("Updating shader packs while the game is running may pack duplication and game crashes.\n"
"The old files may not be deleted as they are in use.\n"
"Are you sure you want to do this?"),
QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No)
->exec();
if (response != QMessageBox::Yes)
return;
}
auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes();
auto mods_list = m_model->selectedResources(selection);
bool use_all = mods_list.empty();
if (use_all)
mods_list = m_model->allResources();
ResourceUpdateDialog update_dialog(this, m_instance, m_model, mods_list, false, false);
update_dialog.checkCandidates();
if (update_dialog.aborted()) {
CustomMessageBox::selectable(this, tr("Aborted"), tr("The shader pack updater was aborted!"), QMessageBox::Warning)->show();
return;
}
if (update_dialog.noUpdates()) {
QString message{ tr("'%1' is up-to-date! :)").arg(mods_list.front()->name()) };
if (mods_list.size() > 1) {
if (use_all) {
message = tr("All shader packs are up-to-date! :)");
} else {
message = tr("All selected shader packs are up-to-date! :)");
}
}
CustomMessageBox::selectable(this, tr("Update checker"), message)->exec();
return;
}
if (update_dialog.exec()) {
auto tasks = new ConcurrentTask(this, "Download Shader Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt());
connect(tasks, &Task::failed, [this, tasks](QString reason) {
CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show();
tasks->deleteLater();
});
connect(tasks, &Task::aborted, [this, tasks]() {
CustomMessageBox::selectable(this, tr("Aborted"), tr("Download stopped by user."), QMessageBox::Information)->show();
tasks->deleteLater();
});
connect(tasks, &Task::succeeded, [this, tasks]() {
QStringList warnings = tasks->warnings();
if (warnings.count()) {
CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show();
}
tasks->deleteLater();
});
for (auto task : update_dialog.getTasks()) {
tasks->addTask(task);
}
ProgressDialog loadDialog(this);
loadDialog.setSkipButton(true, tr("Abort"));
loadDialog.execWithTask(tasks);
m_model->update();
}
}
void ShaderPackPage::deleteShaderPackMetadata()
{
auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes();
auto selectionCount = m_model->selectedShaderPacks(selection).length();
if (selectionCount == 0)
return;
if (selectionCount > 1) {
auto response = CustomMessageBox::selectable(this, tr("Confirm Removal"),
tr("You are about to remove the metadata for %1 shader packs.\n"
"Are you sure?")
.arg(selectionCount),
QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No)
->exec();
if (response != QMessageBox::Yes)
return;
}
m_model->deleteMetadata(selection);
}
void ShaderPackPage::changeShaderPackVersion()
{
if (m_instance->typeName() != "Minecraft")
return; // this is a null instance or a legacy instance
if (APPLICATION->settings()->get("ModMetadataDisabled").toBool()) {
QMessageBox::critical(this, tr("Error"), tr("Shader pack updates are unavailable when metadata is disabled!"));
return;
}
const QModelIndexList rows = ui->treeView->selectionModel()->selectedRows();
if (rows.count() != 1)
return;
Resource &resource = m_model->at(m_filterModel->mapToSource(rows[0]).row());
if (resource.metadata() == nullptr)
return;
ResourceDownload::ShaderPackDownloadDialog mdownload(this, m_model, m_instance);
mdownload.setResourceMetadata(resource.metadata());
if (mdownload.exec()) {
auto tasks =
new ConcurrentTask(this, "Download Shader Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt());
connect(tasks, &Task::failed, [this, tasks](QString reason) { connect(tasks, &Task::failed, [this, tasks](QString reason) {
CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show();
tasks->deleteLater(); tasks->deleteLater();

View File

@ -53,5 +53,11 @@ class ShaderPackPage : public ExternalResourcesPage {
bool shouldDisplay() const override { return true; } bool shouldDisplay() const override { return true; }
public slots: public slots:
void downloadShaders(); void downloadShaderPack();
void updateShaderPacks();
void deleteShaderPackMetadata();
void changeShaderPackVersion();
private:
std::shared_ptr<ShaderPackFolderModel> m_model;
}; };

View File

@ -44,35 +44,206 @@
#include "ui/dialogs/CustomMessageBox.h" #include "ui/dialogs/CustomMessageBox.h"
#include "ui/dialogs/ProgressDialog.h" #include "ui/dialogs/ProgressDialog.h"
#include "ui/dialogs/ResourceDownloadDialog.h" #include "ui/dialogs/ResourceDownloadDialog.h"
#include "ui/dialogs/ResourceUpdateDialog.h"
TexturePackPage::TexturePackPage(MinecraftInstance* instance, std::shared_ptr<TexturePackFolderModel> model, QWidget* parent) TexturePackPage::TexturePackPage(MinecraftInstance* instance, std::shared_ptr<TexturePackFolderModel> model, QWidget* parent)
: ExternalResourcesPage(instance, model, parent) : ExternalResourcesPage(instance, model, parent), m_model(model)
{ {
ui->actionDownloadItem->setText(tr("Download packs")); ui->actionDownloadItem->setText(tr("Download Packs"));
ui->actionDownloadItem->setToolTip(tr("Download texture packs from online platforms")); ui->actionDownloadItem->setToolTip(tr("Download texture packs from online mod platforms"));
ui->actionDownloadItem->setEnabled(true); ui->actionDownloadItem->setEnabled(true);
connect(ui->actionDownloadItem, &QAction::triggered, this, &TexturePackPage::downloadTPs);
ui->actionsToolbar->insertActionBefore(ui->actionAddItem, ui->actionDownloadItem); ui->actionsToolbar->insertActionBefore(ui->actionAddItem, ui->actionDownloadItem);
ui->actionViewConfigs->setVisible(false); connect(ui->actionDownloadItem, &QAction::triggered, this, &TexturePackPage::downloadTexturePacks);
ui->actionUpdateItem->setToolTip(tr("Try to check or update all selected texture packs (all texture packs if none are selected)"));
connect(ui->actionUpdateItem, &QAction::triggered, this, &TexturePackPage::updateTexturePacks);
ui->actionsToolbar->insertActionBefore(ui->actionAddItem, ui->actionUpdateItem);
auto updateMenu = new QMenu(this);
auto update = updateMenu->addAction(ui->actionUpdateItem->text());
connect(update, &QAction::triggered, this, &TexturePackPage::updateTexturePacks);
updateMenu->addAction(ui->actionResetItemMetadata);
connect(ui->actionResetItemMetadata, &QAction::triggered, this, &TexturePackPage::deleteTexturePackMetadata);
ui->actionUpdateItem->setMenu(updateMenu);
ui->actionChangeVersion->setToolTip(tr("Change a texture pack's version."));
connect(ui->actionChangeVersion, &QAction::triggered, this, &TexturePackPage::changeTexturePackVersion);
ui->actionsToolbar->insertActionAfter(ui->actionUpdateItem, ui->actionChangeVersion);
ui->actionViewHomepage->setToolTip(tr("View the homepages of all selected texture packs."));
} }
bool TexturePackPage::onSelectionChanged(const QModelIndex& current, [[maybe_unused]] const QModelIndex& previous) void TexturePackPage::updateFrame(const QModelIndex& current, [[maybe_unused]] const QModelIndex& previous)
{ {
auto sourceCurrent = m_filterModel->mapToSource(current); auto sourceCurrent = m_filterModel->mapToSource(current);
int row = sourceCurrent.row(); int row = sourceCurrent.row();
auto& rp = static_cast<TexturePack&>(m_model->at(row)); auto& rp = static_cast<TexturePack&>(m_model->at(row));
ui->frame->updateWithTexturePack(rp); ui->frame->updateWithTexturePack(rp);
return true;
} }
void TexturePackPage::downloadTPs() void TexturePackPage::downloadTexturePacks()
{ {
if (m_instance->typeName() != "Minecraft") if (m_instance->typeName() != "Minecraft")
return; // this is a null instance or a legacy instance return; // this is a null instance or a legacy instance
ResourceDownload::TexturePackDownloadDialog mdownload(this, std::static_pointer_cast<TexturePackFolderModel>(m_model), m_instance); ResourceDownload::TexturePackDownloadDialog mdownload(this, m_model, m_instance);
if (mdownload.exec()) {
auto tasks =
new ConcurrentTask(this, "Download Texture Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt());
connect(tasks, &Task::failed, [this, tasks](QString reason) {
CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show();
tasks->deleteLater();
});
connect(tasks, &Task::aborted, [this, tasks]() {
CustomMessageBox::selectable(this, tr("Aborted"), tr("Download stopped by user."), QMessageBox::Information)->show();
tasks->deleteLater();
});
connect(tasks, &Task::succeeded, [this, tasks]() {
QStringList warnings = tasks->warnings();
if (warnings.count())
CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show();
tasks->deleteLater();
});
for (auto& task : mdownload.getTasks()) {
tasks->addTask(task);
}
ProgressDialog loadDialog(this);
loadDialog.setSkipButton(true, tr("Abort"));
loadDialog.execWithTask(tasks);
m_model->update();
}
}
void TexturePackPage::updateTexturePacks()
{
if (m_instance->typeName() != "Minecraft")
return; // this is a null instance or a legacy instance
auto profile = static_cast<MinecraftInstance*>(m_instance)->getPackProfile();
if (APPLICATION->settings()->get("ModMetadataDisabled").toBool()) {
QMessageBox::critical(this, tr("Error"), tr("Texture pack updates are unavailable when metadata is disabled!"));
return;
}
if (m_instance != nullptr && m_instance->isRunning()) {
auto response = CustomMessageBox::selectable(
this, tr("Confirm Update"),
tr("Updating texture packs while the game is running may cause pack duplication and game crashes.\n"
"The old files may not be deleted as they are in use.\n"
"Are you sure you want to do this?"),
QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No)
->exec();
if (response != QMessageBox::Yes)
return;
}
auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes();
auto mods_list = m_model->selectedResources(selection);
bool use_all = mods_list.empty();
if (use_all)
mods_list = m_model->allResources();
ResourceUpdateDialog update_dialog(this, m_instance, m_model, mods_list, false, false);
update_dialog.checkCandidates();
if (update_dialog.aborted()) {
CustomMessageBox::selectable(this, tr("Aborted"), tr("The texture pack updater was aborted!"), QMessageBox::Warning)->show();
return;
}
if (update_dialog.noUpdates()) {
QString message{ tr("'%1' is up-to-date! :)").arg(mods_list.front()->name()) };
if (mods_list.size() > 1) {
if (use_all) {
message = tr("All texture packs are up-to-date! :)");
} else {
message = tr("All selected texture packs are up-to-date! :)");
}
}
CustomMessageBox::selectable(this, tr("Update checker"), message)->exec();
return;
}
if (update_dialog.exec()) {
auto tasks =
new ConcurrentTask(this, "Download Texture Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt());
connect(tasks, &Task::failed, [this, tasks](QString reason) {
CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show();
tasks->deleteLater();
});
connect(tasks, &Task::aborted, [this, tasks]() {
CustomMessageBox::selectable(this, tr("Aborted"), tr("Download stopped by user."), QMessageBox::Information)->show();
tasks->deleteLater();
});
connect(tasks, &Task::succeeded, [this, tasks]() {
QStringList warnings = tasks->warnings();
if (warnings.count()) {
CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show();
}
tasks->deleteLater();
});
for (auto task : update_dialog.getTasks()) {
tasks->addTask(task);
}
ProgressDialog loadDialog(this);
loadDialog.setSkipButton(true, tr("Abort"));
loadDialog.execWithTask(tasks);
m_model->update();
}
}
void TexturePackPage::deleteTexturePackMetadata()
{
auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes();
auto selectionCount = m_model->selectedTexturePacks(selection).length();
if (selectionCount == 0)
return;
if (selectionCount > 1) {
auto response = CustomMessageBox::selectable(this, tr("Confirm Removal"),
tr("You are about to remove the metadata for %1 texture packs.\n"
"Are you sure?")
.arg(selectionCount),
QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No)
->exec();
if (response != QMessageBox::Yes)
return;
}
m_model->deleteMetadata(selection);
}
void TexturePackPage::changeTexturePackVersion() {
if (m_instance->typeName() != "Minecraft")
return; // this is a null instance or a legacy instance
if (APPLICATION->settings()->get("ModMetadataDisabled").toBool()) {
QMessageBox::critical(this, tr("Error"), tr("Texture pack updates are unavailable when metadata is disabled!"));
return;
}
const QModelIndexList rows = ui->treeView->selectionModel()->selectedRows();
if (rows.count() != 1)
return;
Resource &resource = m_model->at(m_filterModel->mapToSource(rows[0]).row());
if (resource.metadata() == nullptr)
return;
ResourceDownload::TexturePackDownloadDialog mdownload(this, m_model, m_instance);
mdownload.setResourceMetadata(resource.metadata());
if (mdownload.exec()) { if (mdownload.exec()) {
auto tasks = auto tasks =
new ConcurrentTask(this, "Download Texture Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); new ConcurrentTask(this, "Download Texture Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt());

View File

@ -55,6 +55,12 @@ class TexturePackPage : public ExternalResourcesPage {
virtual bool shouldDisplay() const override { return m_instance->traits().contains("texturepacks"); } virtual bool shouldDisplay() const override { return m_instance->traits().contains("texturepacks"); }
public slots: public slots:
bool onSelectionChanged(const QModelIndex& current, const QModelIndex& previous) override; void updateFrame(const QModelIndex& current, const QModelIndex& previous) override;
void downloadTPs(); void downloadTexturePacks();
void updateTexturePacks();
void deleteTexturePackMetadata();
void changeTexturePackVersion();
private:
std::shared_ptr<TexturePackFolderModel> m_model;
}; };

View File

@ -42,12 +42,13 @@
#include "ui/dialogs/CustomMessageBox.h" #include "ui/dialogs/CustomMessageBox.h"
#include "ui_ResourcePage.h" #include "ui_ResourcePage.h"
#include <StringUtils.h>
#include <QDesktopServices> #include <QDesktopServices>
#include <QKeyEvent> #include <QKeyEvent>
#include "Markdown.h" #include "Markdown.h"
#include "StringUtils.h" #include "Application.h"
#include "ui/dialogs/ResourceDownloadDialog.h" #include "ui/dialogs/ResourceDownloadDialog.h"
#include "ui/pages/modplatform/ResourceModel.h" #include "ui/pages/modplatform/ResourceModel.h"
#include "ui/widgets/ProjectItem.h" #include "ui/widgets/ProjectItem.h"
@ -349,7 +350,8 @@ void ResourcePage::addResourceToPage(ModPlatform::IndexedPack::Ptr pack,
ModPlatform::IndexedVersion& ver, ModPlatform::IndexedVersion& ver,
const std::shared_ptr<ResourceFolderModel> base_model) const std::shared_ptr<ResourceFolderModel> base_model)
{ {
m_model->addPack(pack, ver, base_model); bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool();
m_model->addPack(pack, ver, base_model, is_indexed);
} }
void ResourcePage::removeResourceFromPage(const QString& name) void ResourcePage::removeResourceFromPage(const QString& name)

View File

@ -8,6 +8,7 @@
#include "ShaderPackModel.h" #include "ShaderPackModel.h"
#include "Application.h"
#include "ui/dialogs/ResourceDownloadDialog.h" #include "ui/dialogs/ResourceDownloadDialog.h"
#include <QRegularExpression> #include <QRegularExpression>
@ -48,10 +49,11 @@ void ShaderPackResourcePage::addResourceToPage(ModPlatform::IndexedPack::Ptr pac
ModPlatform::IndexedVersion& version, ModPlatform::IndexedVersion& version,
const std::shared_ptr<ResourceFolderModel> base_model) const std::shared_ptr<ResourceFolderModel> base_model)
{ {
bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool();
QString custom_target_folder; QString custom_target_folder;
if (version.loaders & ModPlatform::Cauldron) if (version.loaders & ModPlatform::Cauldron)
custom_target_folder = QStringLiteral("resourcepacks"); custom_target_folder = QStringLiteral("resourcepacks");
m_model->addPack(pack, version, base_model, false, custom_target_folder); m_model->addPack(pack, version, base_model, is_indexed, custom_target_folder);
} }
} // namespace ResourceDownload } // namespace ResourceDownload

View File

@ -84,7 +84,7 @@ void InfoFrame::updateWithMod(Mod const& m)
QString text = ""; QString text = "";
QString name = ""; QString name = "";
QString link = m.metaurl(); QString link = m.homepage();
if (m.name().isEmpty()) if (m.name().isEmpty())
name = m.internal_id(); name = m.internal_id();
else else
@ -93,7 +93,7 @@ void InfoFrame::updateWithMod(Mod const& m)
if (link.isEmpty()) if (link.isEmpty())
text = name; text = name;
else { else {
text = "<a href=\"" + link + "\">" + name + "</a>"; text = "<a href=\"" + QUrl(link).toEncoded() + "\">" + name + "</a>";
} }
if (!m.authors().isEmpty()) if (!m.authors().isEmpty())
text += " by " + m.authors().join(", "); text += " by " + m.authors().join(", ");
@ -145,7 +145,13 @@ void InfoFrame::updateWithMod(Mod const& m)
void InfoFrame::updateWithResource(const Resource& resource) void InfoFrame::updateWithResource(const Resource& resource)
{ {
setName(resource.name()); const QString homepage = resource.homepage();
if (!homepage.isEmpty())
setName("<a href=\"" + homepage + "\">" + resource.name() + "</a>");
else
setName(resource.name());
setImage(); setImage();
} }
@ -209,14 +215,28 @@ QString InfoFrame::renderColorCodes(QString input)
void InfoFrame::updateWithResourcePack(ResourcePack& resource_pack) void InfoFrame::updateWithResourcePack(ResourcePack& resource_pack)
{ {
setName(renderColorCodes(resource_pack.name())); QString name = renderColorCodes(resource_pack.name());
const QString homepage = resource_pack.homepage();
if (!homepage.isEmpty()) {
name = "<a href=\"" + homepage + "\">" + name + "</a>";
}
setName(name);
setDescription(renderColorCodes(resource_pack.description())); setDescription(renderColorCodes(resource_pack.description()));
setImage(resource_pack.image({ 64, 64 })); setImage(resource_pack.image({ 64, 64 }));
} }
void InfoFrame::updateWithTexturePack(TexturePack& texture_pack) void InfoFrame::updateWithTexturePack(TexturePack& texture_pack)
{ {
setName(renderColorCodes(texture_pack.name())); QString name = renderColorCodes(texture_pack.name());
const QString homepage = texture_pack.homepage();
if (!homepage.isEmpty()) {
name = "<a href=\"" + homepage + "\">" + name + "</a>";
}
setName(name);
setDescription(renderColorCodes(texture_pack.description())); setDescription(renderColorCodes(texture_pack.description()));
setImage(texture_pack.image({ 64, 64 })); setImage(texture_pack.image({ 64, 64 }));
} }

View File

@ -4,7 +4,7 @@
<launchable type="desktop-id">org.prismlauncher.PrismLauncher.desktop</launchable> <launchable type="desktop-id">org.prismlauncher.PrismLauncher.desktop</launchable>
<name>Prism Launcher</name> <name>Prism Launcher</name>
<developer_name>Prism Launcher Contributors</developer_name> <developer_name>Prism Launcher Contributors</developer_name>
<summary>A custom launcher for Minecraft that allows you to easily manage multiple installations of Minecraft at once</summary> <summary>Custom Minecraft Launcher to easily manage multiple Minecraft installations at once</summary>
<metadata_license>CC0-1.0</metadata_license> <metadata_license>CC0-1.0</metadata_license>
<project_license>GPL-3.0-only</project_license> <project_license>GPL-3.0-only</project_license>
<url type="homepage">https://prismlauncher.org/</url> <url type="homepage">https://prismlauncher.org/</url>

View File

@ -87,7 +87,7 @@ class ResourceFolderModelTest : public QObject {
QEventLoop loop; QEventLoop loop;
ModFolderModel m(tempDir.path(), nullptr, true); ModFolderModel m(tempDir.path(), nullptr, true, true);
connect(&m, &ModFolderModel::updateFinished, &loop, &QEventLoop::quit); connect(&m, &ModFolderModel::updateFinished, &loop, &QEventLoop::quit);
@ -96,7 +96,7 @@ class ResourceFolderModelTest : public QObject {
expire_timer.setSingleShot(true); expire_timer.setSingleShot(true);
expire_timer.start(4000); expire_timer.start(4000);
m.installMod(folder); m.installResource(folder);
loop.exec(); loop.exec();
@ -111,7 +111,7 @@ class ResourceFolderModelTest : public QObject {
QString folder = source + '/'; QString folder = source + '/';
QTemporaryDir tempDir; QTemporaryDir tempDir;
QEventLoop loop; QEventLoop loop;
ModFolderModel m(tempDir.path(), nullptr, true); ModFolderModel m(tempDir.path(), nullptr, true, true);
connect(&m, &ModFolderModel::updateFinished, &loop, &QEventLoop::quit); connect(&m, &ModFolderModel::updateFinished, &loop, &QEventLoop::quit);
@ -120,7 +120,7 @@ class ResourceFolderModelTest : public QObject {
expire_timer.setSingleShot(true); expire_timer.setSingleShot(true);
expire_timer.start(4000); expire_timer.start(4000);
m.installMod(folder); m.installResource(folder);
loop.exec(); loop.exec();
@ -134,7 +134,7 @@ class ResourceFolderModelTest : public QObject {
void test_addFromWatch() void test_addFromWatch()
{ {
QString source = QFINDTESTDATA("testdata/ResourceFolderModel"); QString source = QFINDTESTDATA("testdata/ResourceFolderModel");
ModFolderModel model(source, nullptr); ModFolderModel model(source, nullptr, false, true);
QCOMPARE(model.size(), 0); QCOMPARE(model.size(), 0);
@ -154,7 +154,7 @@ class ResourceFolderModelTest : public QObject {
QString file_mod = QFINDTESTDATA("testdata/ResourceFolderModel/supercoolmod.jar"); QString file_mod = QFINDTESTDATA("testdata/ResourceFolderModel/supercoolmod.jar");
QTemporaryDir tmp; QTemporaryDir tmp;
ResourceFolderModel model(QDir(tmp.path()), nullptr); ResourceFolderModel model(QDir(tmp.path()), nullptr, false, false);
QCOMPARE(model.size(), 0); QCOMPARE(model.size(), 0);
@ -199,7 +199,7 @@ class ResourceFolderModelTest : public QObject {
QString file_mod = QFINDTESTDATA("testdata/ResourceFolderModel/supercoolmod.jar"); QString file_mod = QFINDTESTDATA("testdata/ResourceFolderModel/supercoolmod.jar");
QTemporaryDir tmp; QTemporaryDir tmp;
ResourceFolderModel model(tmp.path(), nullptr); ResourceFolderModel model(tmp.path(), nullptr, false, false);
QCOMPARE(model.size(), 0); QCOMPARE(model.size(), 0);
@ -210,7 +210,7 @@ class ResourceFolderModelTest : public QObject {
EXEC_UPDATE_TASK(model.installResource(file_mod), QVERIFY) EXEC_UPDATE_TASK(model.installResource(file_mod), QVERIFY)
} }
for (auto res : model.all()) for (auto res : model.allResources())
qDebug() << res->name(); qDebug() << res->name();
QCOMPARE(model.size(), 2); QCOMPARE(model.size(), 2);