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" },
{ { "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" },
{ { "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" },
{ { "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" } });
@ -257,6 +258,10 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
m_serverToJoin = parser.value("server");
m_worldToJoin = parser.value("world");
m_profileToUse = parser.value("profile");
if (parser.isSet("offline")) {
m_offline = true;
m_offlineName = parser.value("offline");
}
m_liveCheck = parser.isSet("alive");
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
if (((!m_serverToJoin.isEmpty() || !m_worldToJoin.isEmpty()) || !m_profileToUse.isEmpty()) && m_instanceIdToLaunch.isEmpty()) {
std::cerr << "--server and --profile can only be used in combination with --launch!" << std::endl;
if ((!m_serverToJoin.isEmpty() || !m_worldToJoin.isEmpty() || !m_profileToUse.isEmpty() || m_offline) &&
m_instanceIdToLaunch.isEmpty()) {
std::cerr << "--server, --profile and --offline can only be used in combination with --launch!" << std::endl;
m_status = Application::Failed;
return;
}
@ -397,6 +403,10 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
if (!m_profileToUse.isEmpty()) {
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_status = Application::Succeeded;
@ -1209,7 +1219,7 @@ void Application::performMainStartupAction()
qDebug() << " Launching with account" << m_profileToUse;
}
launch(inst, true, false, targetToJoin, accountToUse);
launch(inst, !m_offline, false, targetToJoin, accountToUse, m_offlineName);
return;
}
}
@ -1308,6 +1318,8 @@ void Application::messageReceived(const QByteArray& message)
QString server = received.args["server"];
QString world = received.args["world"];
QString profile = received.args["profile"];
bool offline = received.args["offline_enabled"] == "true";
QString offlineName = received.args["offline_name"];
InstancePtr instance;
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 {
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) {
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->setTargetToJoin(targetToJoin);
controller->setAccountToUse(accountToUse);
controller->setOfflineName(offlineName);
if (window) {
controller->setParentWidget(window);
} else if (m_mainWindow) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1203,7 +1203,7 @@ std::shared_ptr<ModFolderModel> MinecraftInstance::loaderModList()
{
if (!m_loader_mod_list) {
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;
}
@ -1212,7 +1212,7 @@ std::shared_ptr<ModFolderModel> MinecraftInstance::coreModList()
{
if (!m_core_mod_list) {
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;
}
@ -1229,7 +1229,8 @@ std::shared_ptr<ModFolderModel> MinecraftInstance::nilModList()
std::shared_ptr<ResourcePackFolderModel> MinecraftInstance::resourcePackList()
{
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;
}
@ -1237,7 +1238,8 @@ std::shared_ptr<ResourcePackFolderModel> MinecraftInstance::resourcePackList()
std::shared_ptr<TexturePackFolderModel> MinecraftInstance::texturePackList()
{
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;
}
@ -1245,11 +1247,17 @@ std::shared_ptr<TexturePackFolderModel> MinecraftInstance::texturePackList()
std::shared_ptr<ShaderPackFolderModel> MinecraftInstance::shaderPackList()
{
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;
}
QList<std::shared_ptr<ResourceFolderModel>> MinecraftInstance::resourceLists()
{
return { loaderModList(), coreModList(), nilModList(), resourcePackList(), texturePackList(), shaderPackList() };
}
std::shared_ptr<WorldList> MinecraftInstance::worldList()
{
if (!m_world_list) {

View File

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

View File

@ -74,12 +74,12 @@ void MSADeviceCodeStep::perform()
m_task->setAskRetry(false);
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();
}
struct DeviceAutorizationResponse {
struct DeviceAuthorizationResponse {
QString device_code;
QString user_code;
QString verification_uri;
@ -90,17 +90,17 @@ struct DeviceAutorizationResponse {
QString error_description;
};
DeviceAutorizationResponse parseDeviceAutorizationResponse(const QByteArray& data)
DeviceAuthorizationResponse parseDeviceAuthorizationResponse(const QByteArray& data)
{
QJsonParseError err;
QJsonDocument doc = QJsonDocument::fromJson(data, &err);
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 {};
}
if (!doc.isObject()) {
qWarning() << "Device autorization response is not an object";
qWarning() << "Device authorization response is not an object";
return {};
}
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()) {
qWarning() << "Device authorization failed:" << rsp.error;
emit finished(AccountTaskState::STATE_FAILED_HARD,
@ -208,12 +208,12 @@ AuthenticationResponse parseAuthenticationResponse(const QByteArray& data)
QJsonParseError err;
QJsonDocument doc = QJsonDocument::fromJson(data, &err);
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 {};
}
if (!doc.isObject()) {
qWarning() << "Device autorization response is not an object";
qWarning() << "Device authorization response is not an object";
return {};
}
auto obj = doc.object();
@ -274,4 +274,4 @@ void MSADeviceCodeStep::authenticationFinished()
m_data->msaToken.refresh_token = rsp.refresh_token;
m_data->msaToken.token = rsp.access_token;
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);
private slots:
void deviceAutorizationFinished();
void deviceAuthorizationFinished();
void startPoolTimer();
void authenticateUser();
void authenticationFinished();

View File

@ -26,33 +26,48 @@
// launcher/minecraft/mod/Mod.h
class Mod;
/* Abstraction file for easily changing the way metadata is stored / handled
* Needs to be a class because of -Wunused-function and no C++17 [[maybe_unused]]
* */
class Metadata {
public:
using ModStruct = Packwiz::V1::Mod;
using ModSide = Packwiz::V1::Side;
namespace Metadata {
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
{
return Packwiz::V1::createModFormat(index_dir, mod_pack, mod_version);
}
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);
}
static auto create(QDir& index_dir, Mod& internal_mod, QString mod_slug) -> ModStruct
{
return Packwiz::V1::createModFormat(index_dir, internal_mod, mod_slug);
}
inline auto create(const QDir& index_dir, Mod& internal_mod, QString mod_slug) -> ModStruct
{
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 <qpixmap.h>
#include <QDebug>
#include <QDir>
#include <QRegularExpression>
#include <QString>
#include "MTPixmapCache.h"
#include "MetadataHandler.h"
#include "Resource.h"
#include "Version.h"
#include "minecraft/mod/ModDetails.h"
#include "minecraft/mod/Resource.h"
#include "minecraft/mod/tasks/LocalModParseTask.h"
#include "modplatform/ModIndex.h"
Mod::Mod(const QFileInfo& file) : Resource(file), m_local_details()
{
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)
{
m_local_details = details;
@ -101,33 +81,28 @@ int Mod::compare(const Resource& other, SortType type) const
return -1;
break;
}
case SortType::PROVIDER: {
return QString::compare(provider().value_or("Unknown"), cast_other->provider().value_or("Unknown"), Qt::CaseInsensitive);
}
case SortType::SIDE: {
if (side() > cast_other->side())
return 1;
else if (side() < cast_other->side())
return -1;
break;
}
case SortType::LOADERS: {
if (loaders() > cast_other->loaders())
return 1;
else if (loaders() < cast_other->loaders())
return -1;
auto compare_result = QString::compare(side(), cast_other->side(), Qt::CaseInsensitive);
if (compare_result != 0)
return compare_result;
break;
}
case SortType::MC_VERSIONS: {
auto thisVersion = mcVersions().join(",");
auto otherVersion = cast_other->mcVersions().join(",");
return QString::compare(thisVersion, otherVersion, Qt::CaseInsensitive);
auto compare_result = QString::compare(mcVersions(), cast_other->mcVersions(), Qt::CaseInsensitive);
if (compare_result != 0)
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: {
if (releaseType() > cast_other->releaseType())
return 1;
else if (releaseType() < cast_other->releaseType())
return -1;
auto compare_result = QString::compare(releaseType(), cast_other->releaseType(), Qt::CaseInsensitive);
if (compare_result != 0)
return compare_result;
break;
}
}
@ -148,28 +123,6 @@ bool Mod::applyFilter(QRegularExpression filter) const
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&
{
return m_local_details;
@ -181,10 +134,7 @@ auto Mod::name() const -> QString
if (!d_name.isEmpty())
return d_name;
if (metadata())
return metadata()->name;
return m_name;
return Resource::name();
}
auto Mod::version() const -> QString
@ -192,16 +142,55 @@ auto Mod::version() const -> QString
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)
return homeurl();
return ModPlatform::getMetaURL(metadata()->provider, metadata()->project_id);
if (metadata()) {
QStringList loaders;
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
@ -214,73 +203,17 @@ auto Mod::authors() const -> QStringList
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)
{
m_is_resolving = false;
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);
if (metadata)
setMetadata(std::move(metadata));
if (!iconPath().isEmpty()) {
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>&
{
return details().licenses;

View File

@ -48,7 +48,6 @@
#include "ModDetails.h"
#include "Resource.h"
#include "modplatform/ModIndex.h"
class Mod : public Resource {
Q_OBJECT
@ -58,24 +57,20 @@ class Mod : public Resource {
Mod() = default;
Mod(const QFileInfo& file);
Mod(const QDir& mods_dir, const Metadata::ModStruct& metadata);
Mod(QString file_path) : Mod(QFileInfo(file_path)) {}
auto details() const -> const ModDetails&;
auto name() const -> QString override;
auto version() const -> QString;
auto homeurl() const -> QString;
auto homepage() const -> QString override;
auto description() const -> QString;
auto authors() const -> QStringList;
auto status() const -> ModStatus;
auto provider() const -> std::optional<QString>;
auto licenses() const -> const QList<ModLicense>&;
auto issueTracker() const -> QString;
auto metaurl() const -> QString;
auto side() const -> Metadata::ModSide;
auto loaders() const -> ModPlatform::ModLoaderTypes;
auto mcVersions() const -> QStringList;
auto releaseType() const -> ModPlatform::IndexedVersionType;
auto side() const -> QString;
auto loaders() const -> QString;
auto mcVersions() const -> QString;
auto releaseType() const -> QString;
/** Get the intneral path to the mod's icon file*/
QString iconPath() const { return m_local_details.icon_file; }
@ -84,17 +79,11 @@ class Mod : public Resource {
/** Thread-safe. */
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);
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;
// Delete all the files of this mod

View File

@ -43,13 +43,6 @@
#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 {
QString name = {};
QString id = {};
@ -149,12 +142,6 @@ struct ModDetails {
/* Path of mod logo */
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;
/** Metadata should be handled manually to properly set the mod status. */
@ -169,40 +156,9 @@ struct ModDetails {
, issue_tracker(other.issue_tracker)
, licenses(other.licenses)
, icon_file(other.icon_file)
, status(other.status)
{}
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;
ModDetails& operator=(const ModDetails& other) = default;
return *this;
}
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;
}
ModDetails& operator=(ModDetails&& other) = default;
};

View File

@ -48,21 +48,19 @@
#include <QThreadPool>
#include <QUrl>
#include <QUuid>
#include <algorithm>
#include "Application.h"
#include "Json.h"
#include "minecraft/mod/MetadataHandler.h"
#include "minecraft/mod/Resource.h"
#include "minecraft/mod/tasks/LocalModParseTask.h"
#include "minecraft/mod/tasks/LocalModUpdateTask.h"
#include "minecraft/mod/tasks/ModFolderLoadTask.h"
#include "minecraft/mod/tasks/LocalResourceUpdateTask.h"
#include "modplatform/ModIndex.h"
#include "modplatform/flame/FlameAPI.h"
#include "modplatform/flame/FlameModIndex.h"
ModFolderModel::ModFolderModel(const QString& dir, BaseInstance* instance, bool is_indexed, bool create_dir)
: ResourceFolderModel(QDir(dir), instance, nullptr, create_dir), m_is_indexed(is_indexed)
ModFolderModel::ModFolderModel(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", "Version", "Last Modified", "Provider", "Size", "Side", "Loaders",
"Minecraft Versions", "Release Type" });
@ -92,7 +90,7 @@ QVariant ModFolderModel::data(const QModelIndex& index, int role) const
case NameColumn:
return m_resources[row]->name();
case VersionColumn: {
switch (m_resources[row]->type()) {
switch (at(row).type()) {
case ResourceType::FOLDER:
return tr("Folder");
case ResourceType::SINGLEFILE:
@ -100,64 +98,50 @@ QVariant ModFolderModel::data(const QModelIndex& index, int role) const
default:
break;
}
return at(row)->version();
return at(row).version();
}
case DateColumn:
return m_resources[row]->dateTimeChanged();
return at(row).dateTimeChanged();
case ProviderColumn: {
auto provider = at(row)->provider();
if (!provider.has_value()) {
//: Unknown mod provider (i.e. not Modrinth, CurseForge, etc...)
return tr("Unknown");
}
return provider.value();
return at(row).provider();
}
case SideColumn: {
return Metadata::modSideToString(at(row)->side());
return at(row).side();
}
case LoadersColumn: {
QStringList 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(", ");
return at(row).loaders();
}
case McVersionsColumn: {
return at(row)->mcVersions().join(", ");
return at(row).mcVersions();
}
case ReleaseTypeColumn: {
return at(row)->releaseType().toString();
return at(row).releaseType();
}
case SizeColumn:
return m_resources[row]->sizeStr();
return at(row).sizeStr();
default:
return QVariant();
}
case Qt::ToolTipRole:
if (column == NameColumn) {
if (at(row)->isSymLinkUnder(instDirPath())) {
if (at(row).isSymLinkUnder(instDirPath())) {
return m_resources[row]->internal_id() +
tr("\nWarning: This resource is symbolically linked from elsewhere. Editing it will also change the original."
"\nCanonical Path: %1")
.arg(at(row)->fileinfo().canonicalFilePath());
.arg(at(row).fileinfo().canonicalFilePath());
}
if (at(row)->isMoreThanOneHardLink()) {
if (at(row).isMoreThanOneHardLink()) {
return m_resources[row]->internal_id() +
tr("\nWarning: This resource is hard linked elsewhere. Editing it will also change the original.");
}
}
return m_resources[row]->internal_id();
case Qt::DecorationRole: {
if (column == NameColumn && (at(row)->isSymLinkUnder(instDirPath()) || at(row)->isMoreThanOneHardLink()))
if (column == NameColumn && (at(row).isSymLinkUnder(instDirPath()) || at(row).isMoreThanOneHardLink()))
return APPLICATION->getThemedIcon("status-yellow");
if (column == ImageColumn) {
return at(row)->icon({ 32, 32 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding);
return at(row).icon({ 32, 32 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding);
}
return {};
}
@ -169,7 +153,7 @@ QVariant ModFolderModel::data(const QModelIndex& index, int role) const
case Qt::CheckStateRole:
switch (column) {
case ActiveColumn:
return at(row)->enabled() ? Qt::Checked : Qt::Unchecked;
return at(row).enabled() ? Qt::Checked : Qt::Unchecked;
default:
return QVariant();
}
@ -210,7 +194,7 @@ QVariant ModFolderModel::headerData(int section, [[maybe_unused]] Qt::Orientatio
case DateColumn:
return tr("The date and time this mod was last changed (or added).");
case ProviderColumn:
return tr("Where the mod was downloaded from.");
return tr("The source provider of the mod.");
case SideColumn:
return tr("On what environment the mod is running.");
case LoadersColumn:
@ -235,133 +219,16 @@ int ModFolderModel::columnCount(const QModelIndex& parent) const
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)
{
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()
{
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)
{
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));
}
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 "minecraft/mod/tasks/LocalModParseTask.h"
#include "minecraft/mod/tasks/ModFolderLoadTask.h"
#include "minecraft/mod/tasks/ResourceFolderLoadTask.h"
#include "modplatform/ModIndex.h"
class LegacyInstance;
class BaseInstance;
class QFileSystemWatcher;
@ -76,8 +75,7 @@ class ModFolderModel : public ResourceFolderModel {
ReleaseTypeColumn,
NUM_COLUMNS
};
enum ModStatusAction { Disable, Enable, Toggle };
ModFolderModel(const QString& dir, BaseInstance* instance, bool is_indexed = false, bool create_dir = true);
ModFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent = nullptr);
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;
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;
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 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)
private slots:
void onUpdateSucceeded() 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();
}
std::tuple<QString, qint64> calculateFileSize(const QFileInfo& file)
static std::tuple<QString, qint64> calculateFileSize(const QFileInfo& file)
{
if (file.isDir()) {
auto dir = QDir(file.absoluteFilePath());
@ -72,6 +72,14 @@ void Resource::parseFile()
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)
{
QRegularExpression regex(QStringLiteral("^(?:the|teh) +"), QRegularExpression::CaseInsensitiveOption);
@ -79,6 +87,30 @@ static void removeThePrefix(QString& string)
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
{
switch (type) {
@ -93,6 +125,7 @@ int Resource::compare(const Resource& other, SortType type) const
QString this_name{ name() };
QString other_name{ other.name() };
// TODO do we need this? it could result in 0 being returned
removeThePrefix(this_name);
removeThePrefix(other_name);
@ -118,6 +151,12 @@ int Resource::compare(const Resource& other, SortType type) const
return -1;
break;
}
case SortType::PROVIDER: {
auto compare_result = QString::compare(provider(), other.provider(), Qt::CaseInsensitive);
if (compare_result != 0)
return compare_result;
break;
}
}
return 0;
@ -174,10 +213,27 @@ bool Resource::enable(EnableAction action)
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;
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

View File

@ -40,6 +40,8 @@
#include <QObject>
#include <QPointer>
#include "MetadataHandler.h"
#include "ModDetails.h"
#include "QObjectPtr.h"
enum class ResourceType {
@ -50,7 +52,14 @@ enum class ResourceType {
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 };
@ -84,9 +93,19 @@ class Resource : public QObject {
[[nodiscard]] QString sizeStr() const { return m_size_str; }
[[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]] 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:
* > 0: 'this' comes after 'other'
* = 0: 'this' is equal to 'other'
@ -117,7 +136,9 @@ class Resource : public QObject {
}
// 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(); }
@ -146,6 +167,11 @@ class Resource : public QObject {
/* The type of file we're dealing with. */
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. */
bool m_enabled = true;

View File

@ -11,20 +11,25 @@
#include <QStyle>
#include <QThreadPool>
#include <QUrl>
#include <utility>
#include "Application.h"
#include "FileSystem.h"
#include "QVariantUtils.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 "tasks/Task.h"
#include "ui/dialogs/CustomMessageBox.h"
ResourceFolderModel::ResourceFolderModel(QDir dir, BaseInstance* instance, QObject* parent, bool create_dir)
: QAbstractListModel(parent), m_dir(dir), m_instance(instance), m_watcher(this)
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), m_is_indexed(is_indexed)
{
if (create_dir) {
FS::ensureFolderPathExists(m_dir.absolutePath());
@ -48,6 +53,9 @@ ResourceFolderModel::~ResourceFolderModel()
bool ResourceFolderModel::startWatching(const QStringList& paths)
{
// Remove orphaned metadata next time
m_first_folder_load = true;
if (m_is_watching)
return false;
@ -158,11 +166,55 @@ bool ResourceFolderModel::installResource(QString original_path)
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) {
if (resource->fileinfo().fileName() == file_name) {
auto res = resource->destroy(false);
auto res = resource->destroy(indexDir(), preserve_metadata, false);
update();
@ -178,13 +230,11 @@ bool ResourceFolderModel::deleteResources(const QModelIndexList& indexes)
return true;
for (auto i : indexes) {
if (i.column() != 0) {
if (i.column() != 0)
continue;
}
auto& resource = m_resources.at(i.row());
resource->destroy();
resource->destroy(indexDir());
}
update();
@ -192,6 +242,22 @@ bool ResourceFolderModel::deleteResources(const QModelIndexList& indexes)
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)
{
if (indexes.isEmpty())
@ -299,7 +365,7 @@ void ResourceFolderModel::resolveResource(Resource* res)
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;
@ -329,7 +395,11 @@ void ResourceFolderModel::onParseSucceeded(int ticket, QString resource_id)
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
@ -419,6 +489,8 @@ QVariant ResourceFolderModel::data(const QModelIndex& index, int role) const
return m_resources[row]->name();
case DateColumn:
return m_resources[row]->dateTimeChanged();
case ProviderColumn:
return m_resources[row]->provider();
case SizeColumn:
return m_resources[row]->sizeStr();
default:
@ -490,22 +562,23 @@ QVariant ResourceFolderModel::headerData(int section, [[maybe_unused]] Qt::Orien
case ActiveColumn:
case NameColumn:
case DateColumn:
case ProviderColumn:
case SizeColumn:
return columnNames().at(section);
default:
return {};
}
case Qt::ToolTipRole: {
//: Here, resource is a generic term for external resources, like Mods, Resource Packs, Shader Packs, etc.
switch (section) {
case ActiveColumn:
//: Here, resource is a generic term for external resources, like Mods, Resource Packs, Shader Packs, etc.
return tr("Is the resource enabled?");
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.");
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).");
case ProviderColumn:
return tr("The source provider of the resource.");
case SizeColumn:
return tr("The size of the resource.");
default:

View File

@ -19,6 +19,58 @@
class QSortFilterProxyModel;
/* A macro to define useful functions to handle Resource* -> T* more easily on derived classes */
#define RESOURCE_HELPERS(T) \
[[nodiscard]] T& operator[](int index) \
{ \
return *static_cast<T*>(m_resources[index].get()); \
} \
[[nodiscard]] T& at(int index) \
{ \
return *static_cast<T*>(m_resources[index].get()); \
} \
[[nodiscard]] const T& at(int index) const \
{ \
return *static_cast<const T*>(m_resources.at(index).get()); \
} \
[[nodiscard]] T& first() \
{ \
return *static_cast<T*>(m_resources.first().get()); \
} \
[[nodiscard]] T& last() \
{ \
return *static_cast<T*>(m_resources.last().get()); \
} \
[[nodiscard]] T* find(QString id) \
{ \
auto iter = std::find_if(m_resources.constBegin(), m_resources.constEnd(), \
[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.
*
* 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 {
Q_OBJECT
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;
virtual QString id() const { return "resource"; }
@ -49,8 +101,10 @@ class ResourceFolderModel : public QAbstractListModel {
bool stopWatching(const QStringList& paths);
/* Helper methods for subclasses, using a predetermined list of paths. */
virtual bool startWatching() { return startWatching({ m_dir.absolutePath() }); }
virtual bool stopWatching() { return stopWatching({ m_dir.absolutePath() }); }
virtual bool startWatching() { return startWatching({ indexDir().absolutePath(), 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
* instance file hierarchy.
@ -59,12 +113,15 @@ class ResourceFolderModel : public QAbstractListModel {
*/
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.
*
* 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 void deleteMetadata(const QModelIndexList&);
/** 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]] bool empty() const { return size() == 0; }
[[nodiscard]] Resource& at(int index) { return *m_resources.at(index); }
[[nodiscard]] Resource const& at(int index) const { return *m_resources.at(index); }
[[nodiscard]] QList<Resource::Ptr> const& all() const { return m_resources; }
RESOURCE_HELPERS(Resource)
[[nodiscard]] QDir const& dir() const { return m_dir; }
@ -96,7 +151,8 @@ class ResourceFolderModel : public QAbstractListModel {
/* Qt behavior */
/* 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; }
[[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.
* 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().
*
@ -195,19 +253,22 @@ class ResourceFolderModel : public QAbstractListModel {
protected:
// 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!
QList<SortType> m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::DATE, SortType::SIZE };
QStringList m_column_names = { "Enable", "Name", "Last Modified", "Size" };
QStringList m_column_names_translated = { tr("Enable"), tr("Name"), tr("Last Modified"), tr("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", "Provider", "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,
QHeaderView::Interactive };
QList<bool> m_columnsHideable = { false, false, true, true };
QList<bool> m_columnsHiddenByDefault = { false, false, false, false };
QHeaderView::Interactive, QHeaderView::Interactive };
QList<bool> m_columnsHideable = { false, false, true, true, true };
QList<bool> m_columnsHiddenByDefault = { false, false, false, false, true };
QDir m_dir;
BaseInstance* m_instance;
QFileSystemWatcher m_watcher;
bool m_is_watching = false;
bool m_is_indexed;
bool m_first_folder_load = true;
Task::Ptr m_current_update_task = nullptr;
bool m_scheduled_update = false;
@ -221,37 +282,6 @@ class ResourceFolderModel : public QAbstractListModel {
std::atomic<int> m_next_resolution_ticket = 0;
};
/* A macro to define useful functions to handle Resource* -> T* more easily on derived classes */
#define RESOURCE_HELPERS(T) \
[[nodiscard]] T* operator[](int index) \
{ \
return static_cast<T*>(m_resources[index].get()); \
} \
[[nodiscard]] T* at(int index) \
{ \
return static_cast<T*>(m_resources[index].get()); \
} \
[[nodiscard]] const T* at(int index) const \
{ \
return static_cast<const T*>(m_resources.at(index).get()); \
} \
[[nodiscard]] T* first() \
{ \
return static_cast<T*>(m_resources.first().get()); \
} \
[[nodiscard]] T* last() \
{ \
return static_cast<T*>(m_resources.last().get()); \
} \
[[nodiscard]] T* find(QString id) \
{ \
auto iter = std::find_if(m_resources.constBegin(), m_resources.constEnd(), \
[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 <typename T>
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 "Version.h"
#include "minecraft/mod/Resource.h"
#include "minecraft/mod/tasks/BasicFolderLoadTask.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_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Pack Format"), tr("Last Modified"), tr("Size") });
m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::PACK_FORMAT, SortType::DATE, SortType::SIZE };
m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Stretch,
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("Provider"), tr("Size") });
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 };
m_columnsHideable = { false, true, false, true, true, true };
m_columnsHiddenByDefault = { false, false, false, false, false, false };
m_columnsHideable = { false, true, false, true, true, true, true };
}
QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const
@ -73,12 +74,12 @@ QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const
case NameColumn:
return m_resources[row]->name();
case PackFormatColumn: {
auto resource = at(row);
auto pack_format = resource->packFormat();
auto& resource = at(row);
auto pack_format = resource.packFormat();
if (pack_format == 0)
return tr("Unrecognized");
auto version_bounds = resource->compatibleVersions();
auto version_bounds = resource.compatibleVersions();
if (version_bounds.first.toString().isEmpty())
return QString::number(pack_format);
@ -87,17 +88,18 @@ QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const
}
case DateColumn:
return m_resources[row]->dateTimeChanged();
case ProviderColumn:
return m_resources[row]->provider();
case SizeColumn:
return m_resources[row]->sizeStr();
default:
return {};
}
case Qt::DecorationRole: {
if (column == NameColumn && (at(row)->isSymLinkUnder(instDirPath()) || at(row)->isMoreThanOneHardLink()))
if (column == NameColumn && (at(row).isSymLinkUnder(instDirPath()) || at(row).isMoreThanOneHardLink()))
return APPLICATION->getThemedIcon("status-yellow");
if (column == ImageColumn) {
return at(row)->image({ 32, 32 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding);
return at(row).image({ 32, 32 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding);
}
return {};
}
@ -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.");
}
if (column == NameColumn) {
if (at(row)->isSymLinkUnder(instDirPath())) {
if (at(row).isSymLinkUnder(instDirPath())) {
return m_resources[row]->internal_id() +
tr("\nWarning: This resource is symbolically linked from elsewhere. Editing it will also change the original."
"\nCanonical Path: %1")
.arg(at(row)->fileinfo().canonicalFilePath());
.arg(at(row).fileinfo().canonicalFilePath());
;
}
if (at(row)->isMoreThanOneHardLink()) {
if (at(row).isMoreThanOneHardLink()) {
return m_resources[row]->internal_id() +
tr("\nWarning: This resource is hard linked elsewhere. Editing it will also change the original.");
}
@ -129,7 +131,7 @@ QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const
case Qt::CheckStateRole:
switch (column) {
case ActiveColumn:
return at(row)->enabled() ? Qt::Checked : Qt::Unchecked;
return at(row).enabled() ? Qt::Checked : Qt::Unchecked;
default:
return {};
}
@ -148,6 +150,7 @@ QVariant ResourcePackFolderModel::headerData(int section, [[maybe_unused]] Qt::O
case PackFormatColumn:
case DateColumn:
case ImageColumn:
case ProviderColumn:
case SizeColumn:
return columnNames().at(section);
default:
@ -157,7 +160,7 @@ QVariant ResourcePackFolderModel::headerData(int section, [[maybe_unused]] Qt::O
case Qt::ToolTipRole:
switch (section) {
case ActiveColumn:
return tr("Is the resource pack enabled? (Only valid for ZIPs)");
return tr("Is the resource pack enabled?");
case NameColumn:
return tr("The name of the resource pack.");
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.");
case DateColumn:
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:
return tr("The size of the resource pack.");
default:
@ -185,11 +190,6 @@ int ResourcePackFolderModel::columnCount(const QModelIndex& parent) const
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)
{
return new LocalResourcePackParseTask(m_next_resolution_ticket, static_cast<ResourcePack&>(resource));

View File

@ -7,18 +7,18 @@
class ResourcePackFolderModel : public ResourceFolderModel {
Q_OBJECT
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 headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) 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;
RESOURCE_HELPERS(ResourcePack)

View File

@ -2,24 +2,24 @@
#include "ResourceFolderModel.h"
#include "minecraft/mod/ShaderPack.h"
#include "minecraft/mod/tasks/BasicFolderLoadTask.h"
#include "minecraft/mod/tasks/LocalShaderPackParseTask.h"
class ShaderPackFolderModel : public ResourceFolderModel {
Q_OBJECT
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"; }
[[nodiscard]] Task* createUpdateTask() override
{
return new BasicFolderLoadTask(m_dir, [](QFileInfo const& entry) { return makeShared<ShaderPack>(entry); });
}
[[nodiscard]] Resource* createResource(const QFileInfo& info) override { return new ShaderPack(info); }
[[nodiscard]] Task* createParseTask(Resource& resource) override
{
return new LocalShaderPackParseTask(m_next_resolution_ticket, static_cast<ShaderPack&>(resource));
}
RESOURCE_HELPERS(ShaderPack);
};

View File

@ -39,22 +39,18 @@
#include "TexturePackFolderModel.h"
#include "minecraft/mod/tasks/BasicFolderLoadTask.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_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Last Modified"), tr("Size") });
m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::DATE, SortType::SIZE };
m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive,
QHeaderView::Interactive };
m_columnsHideable = { false, true, false, true, true };
}
Task* TexturePackFolderModel::createUpdateTask()
{
return new BasicFolderLoadTask(m_dir, [](QFileInfo const& entry) { return makeShared<TexturePack>(entry); });
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("Provider"), tr("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, QHeaderView::Interactive, QHeaderView::Interactive };
m_columnsHideable = { false, true, false, true, true, true };
m_columnsHiddenByDefault = { false, false, false, false, false, true };
}
Task* TexturePackFolderModel::createParseTask(Resource& resource)
@ -77,6 +73,8 @@ QVariant TexturePackFolderModel::data(const QModelIndex& index, int role) const
return m_resources[row]->name();
case DateColumn:
return m_resources[row]->dateTimeChanged();
case ProviderColumn:
return m_resources[row]->provider();
case SizeColumn:
return m_resources[row]->sizeStr();
default:
@ -84,14 +82,14 @@ QVariant TexturePackFolderModel::data(const QModelIndex& index, int role) const
}
case Qt::ToolTipRole:
if (column == NameColumn) {
if (at(row)->isSymLinkUnder(instDirPath())) {
if (at(row).isSymLinkUnder(instDirPath())) {
return m_resources[row]->internal_id() +
tr("\nWarning: This resource is symbolically linked from elsewhere. Editing it will also change the original."
"\nCanonical Path: %1")
.arg(at(row)->fileinfo().canonicalFilePath());
.arg(at(row).fileinfo().canonicalFilePath());
;
}
if (at(row)->isMoreThanOneHardLink()) {
if (at(row).isMoreThanOneHardLink()) {
return m_resources[row]->internal_id() +
tr("\nWarning: This resource is hard linked elsewhere. Editing it will also change the original.");
}
@ -99,10 +97,10 @@ QVariant TexturePackFolderModel::data(const QModelIndex& index, int role) const
return m_resources[row]->internal_id();
case Qt::DecorationRole: {
if (column == NameColumn && (at(row)->isSymLinkUnder(instDirPath()) || at(row)->isMoreThanOneHardLink()))
if (column == NameColumn && (at(row).isSymLinkUnder(instDirPath()) || at(row).isMoreThanOneHardLink()))
return APPLICATION->getThemedIcon("status-yellow");
if (column == ImageColumn) {
return at(row)->image({ 32, 32 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding);
return at(row).image({ 32, 32 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding);
}
return {};
}
@ -130,6 +128,7 @@ QVariant TexturePackFolderModel::headerData(int section, [[maybe_unused]] Qt::Or
case NameColumn:
case DateColumn:
case ImageColumn:
case ProviderColumn:
case SizeColumn:
return columnNames().at(section);
default:
@ -138,14 +137,13 @@ QVariant TexturePackFolderModel::headerData(int section, [[maybe_unused]] Qt::Or
case Qt::ToolTipRole: {
switch (section) {
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?");
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.");
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).");
case ProviderColumn:
return tr("The source provider of the texture pack.");
case SizeColumn:
return tr("The size of the texture pack.");
default:

View File

@ -44,9 +44,9 @@ class TexturePackFolderModel : public ResourceFolderModel {
Q_OBJECT
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"; }
@ -55,8 +55,7 @@ class TexturePackFolderModel : public ResourceFolderModel {
[[nodiscard]] QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
[[nodiscard]] int columnCount(const QModelIndex& parent) const override;
explicit TexturePackFolderModel(const QString& dir, BaseInstance* instance);
[[nodiscard]] Task* createUpdateTask() override;
[[nodiscard]] Resource* createResource(const QFileInfo& file) override { return new TexturePack(file); }
[[nodiscard]] Task* createParseTask(Resource&) override;
RESOURCE_HELPERS(TexturePack)

View File

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

View File

@ -17,7 +17,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "LocalModUpdateTask.h"
#include "LocalResourceUpdateTask.h"
#include "FileSystem.h"
#include "minecraft/mod/MetadataHandler.h"
@ -26,12 +26,12 @@
#include <windows.h>
#endif
LocalModUpdateTask::LocalModUpdateTask(QDir index_dir, ModPlatform::IndexedPack& mod, ModPlatform::IndexedVersion& mod_version)
: m_index_dir(index_dir), m_mod(mod), m_mod_version(mod_version)
LocalResourceUpdateTask::LocalResourceUpdateTask(QDir index_dir, ModPlatform::IndexedPack& project, ModPlatform::IndexedVersion& 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
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
@ -39,28 +39,28 @@ LocalModUpdateTask::LocalModUpdateTask(QDir index_dir, ModPlatform::IndexedPack&
#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()) {
emit hasOldMod(old_metadata.name, old_metadata.filename);
if (m_mod.slug.isEmpty())
m_mod.slug = old_metadata.slug;
emit hasOldResource(old_metadata.name, old_metadata.filename);
if (m_project.slug.isEmpty())
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()) {
Metadata::update(m_index_dir, pw_mod);
emitSucceeded();
} else {
qCritical() << "Tried to update an invalid mod!";
qCritical() << "Tried to update an invalid resource!";
emitFailed(tr("Invalid metadata"));
}
}
auto LocalModUpdateTask::abort() -> bool
auto LocalResourceUpdateTask::abort() -> bool
{
emitAborted();
return true;

View File

@ -23,12 +23,12 @@
#include "modplatform/ModIndex.h"
#include "tasks/Task.h"
class LocalModUpdateTask : public Task {
class LocalResourceUpdateTask : public Task {
Q_OBJECT
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 abort() -> bool override;
@ -38,10 +38,10 @@ class LocalModUpdateTask : public Task {
void executeTask() override;
signals:
void hasOldMod(QString name, QString filename);
void hasOldResource(QString name, QString filename);
private:
QDir m_index_dir;
ModPlatform::IndexedPack m_mod;
ModPlatform::IndexedVersion m_mod_version;
ModPlatform::IndexedPack m_project;
ModPlatform::IndexedVersion m_version;
};

View File

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

View File

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

View File

@ -7,7 +7,7 @@
#include "Json.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/FlameModIndex.h"
@ -18,55 +18,56 @@
static ModrinthAPI modrinth_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)
{
auto hash_task = createNewHash(mod);
auto hash_task = createNewHash(resource);
if (!hash_task)
return;
connect(hash_task.get(), &Hashing::Hasher::resultsReady, [this, mod](QString hash) { m_mods.insert(hash, mod); });
connect(hash_task.get(), &Task::failed, [this, mod] { emitFail(mod, "", RemoveFromList::No); });
connect(hash_task.get(), &Hashing::Hasher::resultsReady, [this, resource](QString hash) { m_resources.insert(hash, resource); });
connect(hash_task.get(), &Task::failed, [this, resource] { emitFail(resource, "", RemoveFromList::No); });
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)
{
m_hashing_task.reset(new ConcurrentTask(this, "MakeHashesTask", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt()));
for (auto* mod : mods) {
auto hash_task = createNewHash(mod);
for (auto* resource : resources) {
auto hash_task = createNewHash(resource);
if (!hash_task)
continue;
connect(hash_task.get(), &Hashing::Hasher::resultsReady, [this, mod](QString hash) { m_mods.insert(hash, mod); });
connect(hash_task.get(), &Task::failed, [this, mod] { emitFail(mod, "", RemoveFromList::No); });
connect(hash_task.get(), &Hashing::Hasher::resultsReady, [this, resource](QString hash) { m_resources.insert(hash, resource); });
connect(hash_task.get(), &Task::failed, [this, resource] { emitFail(resource, "", RemoveFromList::No); });
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 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
// (linear on the number of mods vs. linear on the size of the mod's JAR)
auto it = m_mods.keyValueBegin();
while (it != m_mods.keyValueEnd()) {
if ((*it).second == mod)
auto it = m_resources.keyValueBegin();
while (it != m_resources.keyValueEnd()) {
if ((*it).second == resource)
break;
it++;
}
// We already have the hash computed
if (it != m_mods.keyValueEnd()) {
if (it != m_resources.keyValueEnd()) {
return (*it).first;
}
@ -86,25 +87,25 @@ bool EnsureMetadataTask::abort()
void EnsureMetadataTask::executeTask()
{
setStatus(tr("Checking if mods have metadata..."));
setStatus(tr("Checking if resources have metadata..."));
for (auto* mod : m_mods) {
if (!mod->valid()) {
qDebug() << "Mod" << mod->name() << "is invalid!";
emitFail(mod);
for (auto* resource : m_resources) {
if (!resource->valid()) {
qDebug() << "Resource" << resource->name() << "is invalid!";
emitFail(resource);
continue;
}
// They already have the right metadata :o
if (mod->status() != ModStatus::NoMetadata && mod->metadata() && mod->metadata()->provider == m_provider) {
qDebug() << "Mod" << mod->name() << "already has metadata!";
emitReady(mod);
if (resource->status() != ResourceStatus::NO_METADATA && resource->metadata() && resource->metadata()->provider == m_provider) {
qDebug() << "Resource" << resource->name() << "already has metadata!";
emitReady(resource);
continue;
}
// Folders don't have metadata
if (mod->type() == ResourceType::FOLDER) {
emitReady(mod);
if (resource->type() == ResourceType::FOLDER) {
emitReady(resource);
}
}
@ -120,9 +121,9 @@ void EnsureMetadataTask::executeTask()
}
auto invalidade_leftover = [this] {
for (auto mod = m_mods.constBegin(); mod != m_mods.constEnd(); mod++)
emitFail(mod.value(), mod.key(), RemoveFromList::No);
m_mods.clear();
for (auto resource = m_resources.constBegin(); resource != m_resources.constEnd(); resource++)
emitFail(resource.value(), resource.key(), RemoveFromList::No);
m_resources.clear();
emitSucceeded();
};
@ -162,53 +163,53 @@ void EnsureMetadataTask::executeTask()
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)));
else if (!m_mods.empty())
else if (!m_resources.empty())
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;
version_task->start();
}
void EnsureMetadataTask::emitReady(Mod* m, QString key, RemoveFromList remove)
void EnsureMetadataTask::emitReady(Resource* resource, QString key, RemoveFromList remove)
{
if (!m) {
qCritical() << "Tried to mark a null mod as ready.";
if (!resource) {
qCritical() << "Tried to mark a null resource as ready.";
if (!key.isEmpty())
m_mods.remove(key);
m_resources.remove(key);
return;
}
qDebug() << QString("Generated metadata for %1").arg(m->name());
emit metadataReady(m);
qDebug() << QString("Generated metadata for %1").arg(resource->name());
emit metadataReady(resource);
if (remove == RemoveFromList::Yes) {
if (key.isEmpty())
key = getExistingHash(m);
m_mods.remove(key);
key = getExistingHash(resource);
m_resources.remove(key);
}
}
void EnsureMetadataTask::emitFail(Mod* m, QString key, RemoveFromList remove)
void EnsureMetadataTask::emitFail(Resource* resource, QString key, RemoveFromList remove)
{
if (!m) {
qCritical() << "Tried to mark a null mod as failed.";
if (!resource) {
qCritical() << "Tried to mark a null resource as failed.";
if (!key.isEmpty())
m_mods.remove(key);
m_resources.remove(key);
return;
}
qDebug() << QString("Failed to generate metadata for %1").arg(m->name());
emit metadataFailed(m);
qDebug() << QString("Failed to generate metadata for %1").arg(resource->name());
emit metadataFailed(resource);
if (remove == RemoveFromList::Yes) {
if (key.isEmpty())
key = getExistingHash(m);
m_mods.remove(key);
key = getExistingHash(resource);
m_resources.remove(key);
}
}
@ -219,7 +220,7 @@ Task::Ptr EnsureMetadataTask::modrinthVersionsTask()
auto hash_type = ModPlatform::ProviderCapabilities::hashType(ModPlatform::ResourceProvider::MODRINTH).first();
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
if (!ver_task)
@ -239,20 +240,20 @@ Task::Ptr EnsureMetadataTask::modrinthVersionsTask()
try {
auto entries = Json::requireObject(doc);
for (auto& hash : m_mods.keys()) {
auto mod = m_mods.find(hash).value();
for (auto& hash : m_resources.keys()) {
auto resource = m_resources.find(hash).value();
try {
auto entry = Json::requireObject(entries, hash);
setStatus(tr("Parsing API response from Modrinth for '%1'...").arg(mod->name()));
qDebug() << "Getting version for" << mod->name() << "from Modrinth";
setStatus(tr("Parsing API response from Modrinth for '%1'...").arg(resource->name()));
qDebug() << "Getting version for" << resource->name() << "from Modrinth";
m_temp_versions.insert(hash, Modrinth::loadIndexedPackVersion(entry));
} catch (Json::JsonException& e) {
qDebug() << e.cause();
qDebug() << entries;
emitFail(mod);
emitFail(resource);
}
}
} catch (Json::JsonException& e) {
@ -324,23 +325,23 @@ Task::Ptr EnsureMetadataTask::modrinthProjectsTask()
auto hash = addonIds.find(pack.addonId.toString()).value();
auto mod_iter = m_mods.find(hash);
if (mod_iter == m_mods.end()) {
auto resource_iter = m_resources.find(hash);
if (resource_iter == m_resources.end()) {
qWarning() << "Invalid project id from the API response.";
continue;
}
auto* mod = mod_iter.value();
auto* resource = resource_iter.value();
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) {
qDebug() << e.cause();
qDebug() << entries;
emitFail(mod);
emitFail(resource);
}
}
});
@ -354,7 +355,7 @@ Task::Ptr EnsureMetadataTask::flameVersionsTask()
auto response = std::make_shared<QByteArray>();
QList<uint> fingerprints;
for (auto& murmur : m_mods.keys()) {
for (auto& murmur : m_resources.keys()) {
fingerprints.push_back(murmur.toUInt());
}
@ -394,13 +395,13 @@ Task::Ptr EnsureMetadataTask::flameVersionsTask()
}
auto fingerprint = QString::number(Json::ensureVariant(file_obj, "fileFingerprint").toUInt());
auto mod = m_mods.find(fingerprint);
if (mod == m_mods.end()) {
auto resource = m_resources.find(fingerprint);
if (resource == m_resources.end()) {
qWarning() << "Invalid fingerprint from the API response.";
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));
}
@ -417,7 +418,7 @@ Task::Ptr EnsureMetadataTask::flameVersionsTask()
Task::Ptr EnsureMetadataTask::flameProjectsTask()
{
QHash<QString, QString> addonIds;
for (auto const& hash : m_mods.keys()) {
for (auto const& hash : m_resources.keys()) {
if (m_temp_versions.contains(hash)) {
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 hash = addonIds.find(id).value();
auto mod = m_mods.find(hash).value();
auto resource = m_resources.find(hash).value();
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;
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) {
qDebug() << e.cause();
qDebug() << entries;
emitFail(mod);
emitFail(resource);
}
}
} catch (Json::JsonException& e) {
@ -489,17 +490,17 @@ Task::Ptr EnsureMetadataTask::flameProjectsTask()
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
ver.fileName = mod->fileinfo().fileName();
ver.fileName = resource->fileinfo().fileName();
if (ver.fileName.endsWith(".disabled"))
ver.fileName.chop(9);
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;
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);
if (!metadata.isValid()) {
qCritical() << "Failed to generate metadata at last step!";
emitFail(mod);
emitFail(resource);
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 {
// Prevent file name mismatch
ver.fileName = mod->fileinfo().fileName();
ver.fileName = resource->fileinfo().fileName();
if (ver.fileName.endsWith(".disabled"))
ver.fileName.chop(9);
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;
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);
if (!metadata.isValid()) {
qCritical() << "Failed to generate metadata at last step!";
emitFail(mod);
emitFail(resource);
return;
}
mod->setMetadata(metadata);
resource->setMetadata(metadata);
emitReady(mod);
emitReady(resource);
} catch (Json::JsonException& e) {
qDebug() << e.cause();
emitFail(mod);
emitFail(resource);
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -36,7 +36,7 @@
#include "FlameInstanceCreationTask.h"
#include "QObjectPtr.h"
#include "minecraft/mod/tasks/LocalModUpdateTask.h"
#include "minecraft/mod/tasks/LocalResourceUpdateTask.h"
#include "modplatform/flame/FileResolvingTask.h"
#include "modplatform/flame/FlameAPI.h"
#include "modplatform/flame/FlameModIndex.h"
@ -676,6 +676,7 @@ void FlameCreationTask::validateZIPResources(QEventLoop& loop)
break;
}
}
// TODO make this work with other sorts of resource
auto task = makeShared<ConcurrentTask>(this, "CreateModMetadata", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt());
auto results = m_mod_id_resolver->getResults().files;
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))) {
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);
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 modName = mod->name().toHtmlEscaped();
if (extraData & Url) {
auto url = mod->metaurl().toHtmlEscaped();
auto url = mod->homepage().toHtmlEscaped();
if (!url.isEmpty())
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 modName = toMarkdownEscaped(mod->name());
if (extraData & Url) {
auto url = mod->metaurl();
auto url = mod->homepage();
if (!url.isEmpty())
modName = QString("[%1](%2)").arg(modName, url);
}
@ -95,7 +95,7 @@ QString toPlainTXT(QList<Mod*> mods, OptionalData extraData)
auto line = modName;
if (extraData & Url) {
auto url = mod->metaurl();
auto url = mod->homepage();
if (!url.isEmpty())
line += QString(" (%1)").arg(url);
}
@ -124,7 +124,7 @@ QString toJSON(QList<Mod*> mods, OptionalData extraData)
QJsonObject line;
line["name"] = modName;
if (extraData & Url) {
auto url = mod->metaurl();
auto url = mod->homepage();
if (!url.isEmpty())
line["url"] = url;
}
@ -156,7 +156,7 @@ QString toCSV(QList<Mod*> mods, OptionalData extraData)
data << modName;
if (extraData & Url)
data << mod->metaurl();
data << mod->homepage();
if (extraData & Version) {
auto ver = mod->version();
if (ver.isEmpty() && meta != nullptr)
@ -203,7 +203,7 @@ QString exportToModList(QList<Mod*> mods, QString lineTemplate)
for (auto mod : mods) {
auto meta = mod->metadata();
auto modName = mod->name();
auto url = mod->metaurl();
auto url = mod->homepage();
auto ver = mod->version();
if (ver.isEmpty() && meta != nullptr)
ver = meta->version().toString();

View File

@ -14,14 +14,6 @@
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()
{
if (m_job)
@ -36,24 +28,24 @@ bool ModrinthCheckUpdate::abort()
* */
void ModrinthCheckUpdate::executeTask()
{
setStatus(tr("Preparing mods for Modrinth..."));
setProgress(0, 9);
setStatus(tr("Preparing resources for Modrinth..."));
setProgress(0, (m_loaders_list.isEmpty() ? 1 : m_loaders_list.length()) * 2 + 1);
auto hashing_task =
makeShared<ConcurrentTask>(this, "MakeModrinthHashesTask", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt());
for (auto* mod : m_mods) {
auto hash = mod->metadata()->hash;
for (auto* resource : m_resources) {
auto hash = resource->metadata()->hash;
// Sadly the API can only handle one hash type per call, se we
// need to generate a new hash if the current one is innadequate
// (though it will rarely happen, if at all)
if (mod->metadata()->hash_format != m_hash_type) {
auto hash_task = Hashing::createHasher(mod->fileinfo().absoluteFilePath(), ModPlatform::ResourceProvider::MODRINTH);
connect(hash_task.get(), &Hashing::Hasher::resultsReady, [this, mod](QString hash) { m_mappings.insert(hash, mod); });
if (resource->metadata()->hash_format != m_hash_type) {
auto hash_task = Hashing::createHasher(resource->fileinfo().absoluteFilePath(), ModPlatform::ResourceProvider::MODRINTH);
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"); });
hashing_task->addTask(hash_task);
} else {
m_mappings.insert(hash, mod);
m_mappings.insert(hash, resource);
}
}
@ -62,10 +54,28 @@ void ModrinthCheckUpdate::executeTask()
hashing_task->start();
}
void ModrinthCheckUpdate::checkVersionsResponse(std::shared_ptr<QByteArray> response,
ModPlatform::ModLoaderTypes loader,
bool forceModLoaderCheck)
void ModrinthCheckUpdate::getUpdateModsForLoader(std::optional<ModPlatform::ModLoaderTypes> loader)
{
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{};
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
if (parse_error.error != QJsonParseError::NoError) {
@ -77,21 +87,21 @@ void ModrinthCheckUpdate::checkVersionsResponse(std::shared_ptr<QByteArray> resp
return;
}
setStatus(tr("Parsing the API response from Modrinth..."));
setProgress(m_next_loader_idx * 2, 9);
try {
for (auto hash : m_mappings.keys()) {
if (forceModLoaderCheck && !(m_mappings[hash]->loaders() & loader)) {
continue;
}
auto iter = m_mappings.begin();
while (iter != m_mappings.end()) {
const QString hash = iter.key();
Resource* resource = iter.value();
auto project_obj = doc[hash].toObject();
// If the returned project is empty, but we have Modrinth metadata,
// it means this specific version is not available
if (project_obj.isEmpty()) {
qDebug() << "Mod " << 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;
}
@ -101,7 +111,7 @@ void ModrinthCheckUpdate::checkVersionsResponse(std::shared_ptr<QByteArray> resp
static auto flags = { ModPlatform::ModLoaderType::NeoForge, ModPlatform::ModLoaderType::Forge,
ModPlatform::ModLoaderType::Quilt, ModPlatform::ModLoaderType::Fabric };
for (auto flag : flags) {
if (loader.testFlag(flag)) {
if (loader.has_value() && loader->testFlag(flag)) {
loader_filter = ModPlatform::getModLoaderAsString(flag);
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);
if (project_ver.downloadUrl.isEmpty()) {
qCritical() << "Modrinth mod without download url!" << project_ver.fileName;
++iter;
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 :)
auto pack = std::make_shared<ModPlatform::IndexedPack>();
pack->name = mod->name();
pack->slug = mod->metadata()->slug;
pack->addonId = mod->metadata()->project_id;
pack->websiteUrl = mod->homeurl();
for (auto& author : mod->authors())
pack->authors.append({ author });
pack->description = mod->description();
pack->name = resource->name();
pack->slug = resource->metadata()->slug;
pack->addonId = resource->metadata()->project_id;
pack->provider = ModPlatform::ResourceProvider::MODRINTH;
if ((key != hash && project_ver.is_preferred) || (mod->status() == ModStatus::NotInstalled)) {
if (mod->version() == project_ver.version_number)
continue;
if ((project_ver.hash != hash && project_ver.is_preferred) || (resource->status() == ResourceStatus::NOT_INSTALLED)) {
auto download_task = makeShared<ResourceDownloadTask>(pack, project_ver, m_resource_model);
auto download_task = makeShared<ResourceDownloadTask>(pack, project_ver, m_mods_folder);
QString old_version = resource->metadata()->version_number;
if (old_version.isEmpty()) {
if (resource->status() == ResourceStatus::NOT_INSTALLED)
old_version = tr("Not installed");
else
old_version = tr("Unknown");
}
m_updatable.emplace_back(pack->name, hash, mod->version(), project_ver.version_number, project_ver.version_type,
project_ver.changelog, ModPlatform::ResourceProvider::MODRINTH, download_task, mod->enabled());
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, resource->enabled());
}
m_deps.append(std::make_shared<GetModDependenciesTask::PackDependency>(pack, project_ver));
iter = m_mappings.erase(iter);
}
} catch (Json::JsonException& e) {
emitFailed(e.cause() + " : " + e.what());
emitFailed(e.cause() + ": " + e.what());
return;
}
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()
{
if (m_mappings.isEmpty()) {
emitSucceeded();
return;
}
if (m_next_loader_idx < m_loaders_list.size()) {
getUpdateModsForLoader(m_loaders_list.at(m_next_loader_idx));
m_next_loader_idx++;
if (m_loaders_list.isEmpty() && m_loader_idx == 0) {
getUpdateModsForLoader({});
m_loader_idx++;
return;
}
static auto flags = { ModPlatform::ModLoaderType::NeoForge, ModPlatform::ModLoaderType::Forge, ModPlatform::ModLoaderType::Quilt,
ModPlatform::ModLoaderType::Fabric };
for (auto flag : flags) {
if (!m_loaders_list.contains(flag)) {
m_loaders_list.append(flag);
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);
}
if (m_loader_idx < m_loaders_list.size()) {
getUpdateModsForLoader(m_loaders_list.at(m_loader_idx));
m_loader_idx++;
return;
}
for (auto m : m_mappings) {
emit checkFailed(m,
tr("No valid version found for this mod. It's probably unavailable for the current game version / mod loader."));
for (auto resource : m_mappings) {
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();
return;
}

View File

@ -6,23 +6,26 @@ class ModrinthCheckUpdate : public CheckUpdateTask {
Q_OBJECT
public:
ModrinthCheckUpdate(QList<Mod*>& mods,
ModrinthCheckUpdate(QList<Resource*>& resources,
std::list<Version>& mcVersions,
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:
bool abort() override;
protected slots:
void executeTask() override;
void getUpdateModsForLoader(ModPlatform::ModLoaderTypes loader, bool forceModLoaderCheck = false);
void checkVersionsResponse(std::shared_ptr<QByteArray> response, ModPlatform::ModLoaderTypes loader, bool forceModLoaderCheck = false);
void getUpdateModsForLoader(std::optional<ModPlatform::ModLoaderTypes> loader);
void checkVersionsResponse(std::shared_ptr<QByteArray> response, std::optional<ModPlatform::ModLoaderTypes> loader);
void checkNextLoader();
private:
Task::Ptr m_job = nullptr;
QHash<QString, Mod*> m_mappings;
QHash<QString, Resource*> m_mappings;
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_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) {
auto fileName = file.path;
fileName = FS::RemoveInvalidPathChars(fileName);
@ -259,7 +260,7 @@ bool ModrinthCreationTask::createInstance()
ModDetails d;
d.mod_id = file_path;
mod->setDetails(d);
mods[file.hash.toHex()] = mod;
resources[file.hash.toHex()] = mod;
}
qDebug() << "Will try to download" << file.downloads.front() << "to" << file_path;
@ -302,15 +303,15 @@ bool ModrinthCreationTask::createInstance()
loop.exec();
if (!ended_well) {
for (auto m : mods) {
delete m;
for (auto resource : resources) {
delete resource;
}
return ended_well;
}
QEventLoop ensureMetaLoop;
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::finished, &ensureMetaLoop, &QEventLoop::quit);
connect(ensureMetadataTask.get(), &Task::progress, [this](qint64 current, qint64 total) {
@ -323,10 +324,10 @@ bool ModrinthCreationTask::createInstance()
m_task = ensureMetadataTask;
ensureMetaLoop.exec();
for (auto m : mods) {
delete m;
for (auto resource : resources) {
delete resource;
}
mods.clear();
resources.clear();
// Update information of the already installed instance, if any.
if (m_instance && ended_well) {

View File

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

View File

@ -35,7 +35,7 @@
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));
@ -90,7 +90,7 @@ auto intEntry(toml::table table, QString entry_name) -> int
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::IndexedVersion& mod_version) -> Mod
{
@ -119,10 +119,14 @@ auto V1::createModFormat([[maybe_unused]] QDir& index_dir,
mod.mcVersions.sort();
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;
}
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
Mod mod{ getIndexForMod(index_dir, slug) };
@ -134,7 +138,7 @@ auto V1::createModFormat(QDir& index_dir, [[maybe_unused]] ::Mod& internal_mod,
return {};
}
void V1::updateModIndex(QDir& index_dir, Mod& mod)
void V1::updateModIndex(const QDir& index_dir, Mod& mod)
{
if (!mod.isValid()) {
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-mc-versions", mcVersions },
{ "x-prismlauncher-release-type", mod.releaseType.toString().toStdString() },
{ "x-prismlauncher-version-number", mod.version_number.toStdString() },
{ "download",
toml::table{
{ "mode", mod.mode.toStdString() },
@ -228,7 +233,7 @@ void V1::updateModIndex(QDir& index_dir, Mod& mod)
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 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)) {
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;
@ -295,7 +300,7 @@ auto V1::getIndexForMod(QDir& index_dir, QString slug) -> Mod
mod.name = stringEntry(table, "name");
mod.filename = stringEntry(table, "filename");
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()) {
for (auto&& loader : *loaders.as_array()) {
if (loader.is_string()) {
@ -315,6 +320,7 @@ auto V1::getIndexForMod(QDir& index_dir, QString slug) -> Mod
mod.mcVersions.sort();
}
}
mod.version_number = table["x-prismlauncher-version-number"].value_or("");
{ // [download] info
auto download_table = table["download"].as_table();
@ -356,7 +362,7 @@ auto V1::getIndexForMod(QDir& index_dir, QString slug) -> 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)) {
auto mod = getIndexForMod(index_dir, file_name);

View File

@ -32,11 +32,13 @@ class Mod;
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 {
public:
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 {
QString slug{};
QString name{};
@ -56,6 +58,7 @@ class V1 {
ModPlatform::ResourceProvider provider{};
QVariant file_id{};
QVariant project_id{};
QString version_number{};
public:
// 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
* 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
* 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.
* 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.
* */
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. */
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. */
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.
* 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.
* 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 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;
break;
case PackedResourceType::Mod:
minecraftInst->loaderModList()->installMod(localFileName, version);
minecraftInst->loaderModList()->installResourceWithFlameMetadata(localFileName, version);
break;
case PackedResourceType::ShaderPack:
minecraftInst->shaderPackList()->installResource(localFileName);

View File

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

View File

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

View File

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

View File

@ -1,5 +1,4 @@
#include "ModUpdateDialog.h"
#include "Application.h"
#include "ResourceUpdateDialog.h"
#include "ChooseProviderDialog.h"
#include "CustomMessageBox.h"
#include "ProgressDialog.h"
@ -36,27 +35,29 @@ static QList<ModPlatform::ModLoaderType> mcLoadersList(BaseInstance* inst)
return static_cast<MinecraftInstance*>(inst)->getPackProfile()->getModLoadersList();
}
ModUpdateDialog::ModUpdateDialog(QWidget* parent,
BaseInstance* instance,
const std::shared_ptr<ModFolderModel> mods,
QList<Mod*>& search_for,
bool includeDeps)
: ReviewMessageBox(parent, tr("Confirm mods to update"), "")
ResourceUpdateDialog::ResourceUpdateDialog(QWidget* parent,
BaseInstance* instance,
const std::shared_ptr<ResourceFolderModel> resource_model,
QList<Resource*>& search_for,
bool include_deps,
bool filter_loaders)
: ReviewMessageBox(parent, tr("Confirm resources to update"), "")
, m_parent(parent)
, m_mod_model(mods)
, m_resource_model(resource_model)
, m_candidates(search_for)
, m_second_try_metadata(
new ConcurrentTask(nullptr, "Second Metadata Search", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt()))
, m_instance(instance)
, m_include_deps(includeDeps)
, m_include_deps(include_deps)
, m_filter_loaders(filter_loaders)
{
ReviewMessageBox::setGeometry(0, 0, 800, 600);
ui->explainLabel->setText(tr("You're about to update the following mods:"));
ui->onlyCheckedLabel->setText(tr("Only mods with a check will be updated!"));
ui->explainLabel->setText(tr("You're about to update the following resources:"));
ui->onlyCheckedLabel->setText(tr("Only resources with a check will be updated!"));
}
void ModUpdateDialog::checkCandidates()
void ResourceUpdateDialog::checkCandidates()
{
// Ensure mods have valid metadata
auto went_well = ensureMetadata();
@ -75,8 +76,8 @@ void ModUpdateDialog::checkCandidates()
}
ScrollMessageBox message_dialog(m_parent, tr("Metadata generation failed"),
tr("Could not generate metadata for the following mods:<br>"
"Do you wish to proceed without those mods?"),
tr("Could not generate metadata for the following resources:<br>"
"Do you wish to proceed without those resources?"),
text);
message_dialog.setModal(true);
if (message_dialog.exec() == QDialog::Rejected) {
@ -87,21 +88,25 @@ void ModUpdateDialog::checkCandidates()
}
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"));
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,
[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);
}
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,
[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);
}
@ -132,11 +137,11 @@ void ModUpdateDialog::checkCandidates()
// Add found updates for Modrinth
if (m_modrinth_check_task) {
auto modrinth_updates = m_modrinth_check_task->getUpdatable();
auto modrinth_updates = m_modrinth_check_task->getUpdates();
for (auto& updatable : modrinth_updates) {
qDebug() << QString("Mod %1 has an update available!").arg(updatable.name);
appendMod(updatable);
appendResource(updatable);
m_tasks.insert(updatable.name, updatable.download);
}
selectedVers.append(m_modrinth_check_task->getDependencies());
@ -144,11 +149,11 @@ void ModUpdateDialog::checkCandidates()
// Add found updated for Flame
if (m_flame_check_task) {
auto flame_updates = m_flame_check_task->getUpdatable();
auto flame_updates = m_flame_check_task->getUpdates();
for (auto& updatable : flame_updates) {
qDebug() << QString("Mod %1 has an update available!").arg(updatable.name);
appendMod(updatable);
appendResource(updatable);
m_tasks.insert(updatable.name, updatable.download);
}
selectedVers.append(m_flame_check_task->getDependencies());
@ -175,8 +180,8 @@ void ModUpdateDialog::checkCandidates()
}
ScrollMessageBox message_dialog(m_parent, tr("Failed to check for updates"),
tr("Could not check or get the following mods for updates:<br>"
"Do you wish to proceed without those mods?"),
tr("Could not check or get the following resources for updates:<br>"
"Do you wish to proceed without those resources?"),
text);
message_dialog.setModal(true);
if (message_dialog.exec() == QDialog::Rejected) {
@ -187,59 +192,58 @@ void ModUpdateDialog::checkCandidates()
}
if (m_include_deps && !APPLICATION->settings()->get("ModDependenciesDisabled").toBool()) { // dependencies
auto depTask = makeShared<GetModDependenciesTask>(this, m_instance, m_mod_model.get(), selectedVers);
auto* mod_model = dynamic_cast<ModFolderModel*>(m_resource_model.get());
connect(depTask.get(), &Task::failed, this,
[this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); });
if (mod_model != nullptr) {
auto depTask = makeShared<GetModDependenciesTask>(this, m_instance, mod_model, selectedVers);
auto weak = depTask.toWeakRef();
connect(depTask.get(), &Task::succeeded, this, [this, weak]() {
QStringList warnings;
if (auto depTask = weak.lock()) {
warnings = depTask->warnings();
connect(depTask.get(), &Task::failed, this, [this](const QString& reason) {
CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec();
});
auto weak = depTask.toWeakRef();
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()) {
CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->exec();
static FlameAPI api;
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) {
m_no_updates = true;
} else {
@ -261,7 +265,7 @@ void ModUpdateDialog::checkCandidates()
}
// Part 1: Ensure we have a valid metadata
auto ModUpdateDialog::ensureMetadata() -> bool
auto ResourceUpdateDialog::ensureMetadata() -> bool
{
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
QHash<QString, bool> should_try_others;
QList<Mod*> modrinth_tmp;
QList<Mod*> flame_tmp;
QList<Resource*> modrinth_tmp;
QList<Resource*> flame_tmp;
bool confirm_rest = false;
bool try_others_rest = false;
bool skip_rest = false;
ModPlatform::ResourceProvider provider_rest = ModPlatform::ResourceProvider::MODRINTH;
auto addToTmp = [&modrinth_tmp, &flame_tmp](Mod* m, ModPlatform::ResourceProvider p) {
auto addToTmp = [&modrinth_tmp, &flame_tmp](Resource* resource, ModPlatform::ResourceProvider p) {
switch (p) {
case ModPlatform::ResourceProvider::MODRINTH:
modrinth_tmp.push_back(m);
modrinth_tmp.push_back(resource);
break;
case ModPlatform::ResourceProvider::FLAME:
flame_tmp.push_back(m);
flame_tmp.push_back(resource);
break;
}
};
for (auto candidate : m_candidates) {
if (candidate->status() != ModStatus::NoMetadata) {
if (candidate->status() != ResourceStatus::NO_METADATA) {
onMetadataEnsured(candidate);
continue;
}
@ -308,7 +312,7 @@ auto ModUpdateDialog::ensureMetadata() -> bool
}
ChooseProviderDialog chooser(this);
chooser.setDescription(tr("The mod '%1' does not have a metadata yet. We need to generate it in order to track relevant "
chooser.setDescription(tr("The resource '%1' does not have a metadata yet. We need to generate it in order to track relevant "
"information on how to update this mod. "
"To do this, please select a mod provider which we can use to check for updates for this mod.")
.arg(candidate->name()));
@ -332,8 +336,8 @@ auto ModUpdateDialog::ensureMetadata() -> bool
if (!modrinth_tmp.empty()) {
auto modrinth_task = makeShared<EnsureMetadataTask>(modrinth_tmp, index_dir, ModPlatform::ResourceProvider::MODRINTH);
connect(modrinth_task.get(), &EnsureMetadataTask::metadataReady, [this](Mod* candidate) { onMetadataEnsured(candidate); });
connect(modrinth_task.get(), &EnsureMetadataTask::metadataFailed, [this, &should_try_others](Mod* candidate) {
connect(modrinth_task.get(), &EnsureMetadataTask::metadataReady, [this](Resource* candidate) { onMetadataEnsured(candidate); });
connect(modrinth_task.get(), &EnsureMetadataTask::metadataFailed, [this, &should_try_others](Resource* candidate) {
onMetadataFailed(candidate, should_try_others.find(candidate->internal_id()).value(), ModPlatform::ResourceProvider::MODRINTH);
});
connect(modrinth_task.get(), &EnsureMetadataTask::failed,
@ -347,8 +351,8 @@ auto ModUpdateDialog::ensureMetadata() -> bool
if (!flame_tmp.empty()) {
auto flame_task = makeShared<EnsureMetadataTask>(flame_tmp, index_dir, ModPlatform::ResourceProvider::FLAME);
connect(flame_task.get(), &EnsureMetadataTask::metadataReady, [this](Mod* candidate) { onMetadataEnsured(candidate); });
connect(flame_task.get(), &EnsureMetadataTask::metadataFailed, [this, &should_try_others](Mod* candidate) {
connect(flame_task.get(), &EnsureMetadataTask::metadataReady, [this](Resource* candidate) { onMetadataEnsured(candidate); });
connect(flame_task.get(), &EnsureMetadataTask::metadataFailed, [this, &should_try_others](Resource* candidate) {
onMetadataFailed(candidate, should_try_others.find(candidate->internal_id()).value(), ModPlatform::ResourceProvider::FLAME);
});
connect(flame_task.get(), &EnsureMetadataTask::failed,
@ -370,18 +374,18 @@ auto ModUpdateDialog::ensureMetadata() -> bool
return (ret_metadata != QDialog::DialogCode::Rejected);
}
void ModUpdateDialog::onMetadataEnsured(Mod* mod)
void ResourceUpdateDialog::onMetadataEnsured(Resource* resource)
{
// When the mod is a folder, for instance
if (!mod->metadata())
if (!resource->metadata())
return;
switch (mod->metadata()->provider) {
switch (resource->metadata()->provider) {
case ModPlatform::ResourceProvider::MODRINTH:
m_modrinth_to_update.push_back(mod);
m_modrinth_to_update.push_back(resource);
break;
case ModPlatform::ResourceProvider::FLAME:
m_flame_to_update.push_back(mod);
m_flame_to_update.push_back(resource);
break;
}
}
@ -398,26 +402,26 @@ ModPlatform::ResourceProvider next(ModPlatform::ResourceProvider p)
return ModPlatform::ResourceProvider::FLAME;
}
void ModUpdateDialog::onMetadataFailed(Mod* mod, bool try_others, ModPlatform::ResourceProvider first_choice)
void ResourceUpdateDialog::onMetadataFailed(Resource* resource, bool try_others, ModPlatform::ResourceProvider first_choice)
{
if (try_others) {
auto index_dir = indexDir();
auto task = makeShared<EnsureMetadataTask>(mod, index_dir, next(first_choice));
connect(task.get(), &EnsureMetadataTask::metadataReady, [this](Mod* candidate) { onMetadataEnsured(candidate); });
connect(task.get(), &EnsureMetadataTask::metadataFailed, [this](Mod* candidate) { onMetadataFailed(candidate, false); });
auto task = makeShared<EnsureMetadataTask>(resource, index_dir, next(first_choice));
connect(task.get(), &EnsureMetadataTask::metadataReady, [this](Resource* candidate) { onMetadataEnsured(candidate); });
connect(task.get(), &EnsureMetadataTask::metadataFailed, [this](Resource* candidate) { onMetadataFailed(candidate, false); });
connect(task.get(), &EnsureMetadataTask::failed,
[this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); });
[this](const QString& reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); });
m_second_try_metadata->addTask(task);
} else {
QString reason{ tr("Couldn't find a valid version on the selected mod provider(s)") };
m_failed_metadata.append({ mod, reason });
m_failed_metadata.append({ resource, reason });
}
}
void ModUpdateDialog::appendMod(CheckUpdateTask::UpdatableMod const& info, QStringList requiredBy)
void ResourceUpdateDialog::appendResource(CheckUpdateTask::Update const& info, QStringList requiredBy)
{
auto item_top = new QTreeWidgetItem(ui->modTreeWidget);
item_top->setCheckState(0, 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)));
auto old_version_item = new QTreeWidgetItem(item_top);
old_version_item->setText(0, tr("Old version: %1").arg(info.old_version.isEmpty() ? tr("Not installed") : info.old_version));
old_version_item->setText(0, tr("Old version: %1").arg(info.old_version));
auto new_version_item = new QTreeWidgetItem(item_top);
new_version_item->setText(0, tr("New version: %1").arg(info.new_version));
@ -485,7 +489,7 @@ void ModUpdateDialog::appendMod(CheckUpdateTask::UpdatableMod const& info, QStri
ui->modTreeWidget->addTopLevelItem(item_top);
}
auto ModUpdateDialog::getTasks() -> const QList<ResourceDownloadTask::Ptr>
auto ResourceUpdateDialog::getTasks() -> const QList<ResourceDownloadTask::Ptr>
{
QList<ResourceDownloadTask::Ptr> list;

View File

@ -13,22 +13,22 @@ class ModrinthCheckUpdate;
class FlameCheckUpdate;
class ConcurrentTask;
class ModUpdateDialog final : public ReviewMessageBox {
class ResourceUpdateDialog final : public ReviewMessageBox {
Q_OBJECT
public:
explicit ModUpdateDialog(QWidget* parent, BaseInstance* instance, std::shared_ptr<ModFolderModel> mod_model, QList<Mod*>& search_for);
explicit ModUpdateDialog(QWidget* parent,
BaseInstance* instance,
std::shared_ptr<ModFolderModel> mod_model,
QList<Mod*>& search_for,
bool includeDeps);
explicit ResourceUpdateDialog(QWidget* parent,
BaseInstance* instance,
std::shared_ptr<ResourceFolderModel> resource_model,
QList<Resource*>& search_for,
bool include_deps,
bool filter_loaders);
void checkCandidates();
void appendMod(const CheckUpdateTask::UpdatableMod& info, QStringList requiredBy = {});
void appendResource(const CheckUpdateTask::Update& info, QStringList requiredBy = {});
const QList<ResourceDownloadTask::Ptr> getTasks();
auto indexDir() const -> QDir { return m_mod_model->indexDir(); }
auto indexDir() const -> QDir { return m_resource_model->indexDir(); }
auto noUpdates() const -> bool { return m_no_updates; };
auto aborted() const -> bool { return m_aborted; };
@ -37,8 +37,8 @@ class ModUpdateDialog final : public ReviewMessageBox {
auto ensureMetadata() -> bool;
private slots:
void onMetadataEnsured(Mod*);
void onMetadataFailed(Mod*,
void onMetadataEnsured(Resource* resource);
void onMetadataFailed(Resource* resource,
bool try_others = false,
ModPlatform::ResourceProvider first_choice = ModPlatform::ResourceProvider::MODRINTH);
@ -48,15 +48,15 @@ class ModUpdateDialog final : public ReviewMessageBox {
shared_qobject_ptr<ModrinthCheckUpdate> m_modrinth_check_task;
shared_qobject_ptr<FlameCheckUpdate> m_flame_check_task;
const std::shared_ptr<ModFolderModel> m_mod_model;
const std::shared_ptr<ResourceFolderModel> m_resource_model;
QList<Mod*>& m_candidates;
QList<Mod*> m_modrinth_to_update;
QList<Mod*> m_flame_to_update;
QList<Resource*>& m_candidates;
QList<Resource*> m_modrinth_to_update;
QList<Resource*> m_flame_to_update;
ConcurrentTask::Ptr m_second_try_metadata;
QList<std::tuple<Mod*, QString>> m_failed_metadata;
QList<std::tuple<Mod*, QString, QUrl>> m_failed_check_update;
QList<std::tuple<Resource*, QString>> m_failed_metadata;
QList<std::tuple<Resource*, QString, QUrl>> m_failed_check_update;
QHash<QString, ResourceDownloadTask::Ptr> m_tasks;
BaseInstance* m_instance;
@ -64,4 +64,5 @@ class ModUpdateDialog final : public ReviewMessageBox {
bool m_no_updates = false;
bool m_aborted = 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;
recommendedFilterChanged();
}
void setRecomend(bool recomend)
void setRecommend(bool recommend)
{
m_recommend = recomend;
m_recommend = recommend;
recommendedFilterChanged();
}
void recommendedFilterChanged()
@ -202,7 +202,7 @@ InstallDialog::InstallDialog(const QString& uid, BaseInstance* instance, QWidget
recommendedCheckBox->setCheckState(Qt::CheckState::Checked);
connect(recommendedCheckBox, &QCheckBox::stateChanged, this, [this](int state) {
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());
auto cast = pageCast(page);
cast->setRecomend(true);
cast->setRecommend(true);
connect(cast, &InstallJavaPage::selectionChanged, this, [this, cast] { validate(cast); });
if (!recommendedJavas.isEmpty()) {
cast->setRecommendedMajors(recommendedJavas);
@ -344,4 +344,4 @@ void InstallDialog::done(int result)
} // 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->actionEnableItem, &QAction::triggered, this, &ExternalResourcesPage::enableItem);
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->actionViewFolder, &QAction::triggered, this, &ExternalResourcesPage::viewFolder);
@ -81,16 +82,28 @@ ExternalResourcesPage::ExternalResourcesPage(BaseInstance* instance, std::shared
connect(ui->treeView, &ModListView::activated, this, &ExternalResourcesPage::itemActivated);
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]() {
if (updateExtraInfo)
updateExtraInfo(id(), extraHeaderInfoString());
};
connect(selection_model, &QItemSelectionModel::selectionChanged, this, updateExtra);
connect(model.get(), &ResourceFolderModel::updateFinished, 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();
viewHeader->setContextMenuPolicy(Qt::CustomContextMenu);
@ -289,6 +302,16 @@ void ExternalResourcesPage::disableItem()
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()
{
DesktopServices::openPath(m_instance->instanceConfigFolder(), true);
@ -299,23 +322,32 @@ void ExternalResourcesPage::viewFolder()
DesktopServices::openPath(m_model->dir().absolutePath(), true);
}
bool ExternalResourcesPage::current(const QModelIndex& current, const QModelIndex& previous)
void ExternalResourcesPage::updateActions()
{
if (!current.isValid()) {
ui->frame->clear();
return false;
}
const bool hasSelection = ui->treeView->selectionModel()->hasSelection();
const QModelIndexList selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes();
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);
int row = sourceCurrent.row();
Resource const& resource = m_model->at(row);
ui->frame->updateWithResource(resource);
return true;
}
QString ExternalResourcesPage::extraHeaderInfoString()

View File

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

View File

@ -60,7 +60,7 @@
<bool>true</bool>
</property>
<property name="dragDropMode">
<enum>QAbstractItemView::DropOnly</enum>
<enum>QAbstractItemView::NoDragDrop</enum>
</property>
<property name="uniformRowHeights">
<bool>true</bool>
@ -74,7 +74,7 @@
<string>Actions</string>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextOnly</enum>
<enum>Qt::ToolButtonIconOnly</enum>
</property>
<property name="useDefaultAction" stdset="0">
<bool>true</bool>
@ -90,39 +90,50 @@
<addaction name="actionRemoveItem"/>
<addaction name="actionEnableItem"/>
<addaction name="actionDisableItem"/>
<addaction name="separator"/>
<addaction name="actionViewHomepage"/>
<addaction name="actionViewConfigs"/>
<addaction name="actionViewFolder"/>
</widget>
<action name="actionAddItem">
<property name="text">
<string>&amp;Add</string>
<string>&amp;Add File</string>
</property>
<property name="toolTip">
<string>Add</string>
<string>Add a locally downloaded file.</string>
</property>
</action>
<action name="actionRemoveItem">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>&amp;Remove</string>
</property>
<property name="toolTip">
<string>Remove selected item</string>
<string>Remove all selected items.</string>
</property>
</action>
<action name="actionEnableItem">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>&amp;Enable</string>
</property>
<property name="toolTip">
<string>Enable selected item</string>
<string>Enable all selected items.</string>
</property>
</action>
<action name="actionDisableItem">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>&amp;Disable</string>
</property>
<property name="toolTip">
<string>Disable selected item</string>
<string>Disable all selected items.</string>
</property>
</action>
<action name="actionViewConfigs">
@ -137,6 +148,9 @@
<property name="text">
<string>View &amp;Folder</string>
</property>
<property name="toolTip">
<string>Open the folder in the system file manager.</string>
</property>
</action>
<action name="actionDownloadItem">
<property name="enabled">
@ -146,40 +160,70 @@
<string>&amp;Download</string>
</property>
<property name="toolTip">
<string>Download a new resource</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>
<string>Download resources from online mod platforms.</string>
</property>
</action>
<action name="actionUpdateItem">
<property name="enabled">
<bool>true</bool>
<bool>false</bool>
</property>
<property name="text">
<string>Check for &amp;Updates</string>
</property>
<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>
</action>
<action name="actionExportMetadata">
<property name="enabled">
<bool>true</bool>
<bool>false</bool>
</property>
<property name="text">
<string>Export modlist</string>
<string>Export List</string>
</property>
<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>
</action>
</widget>

View File

@ -53,8 +53,8 @@
#include "ui/GuiUtil.h"
#include "ui/dialogs/CustomMessageBox.h"
#include "ui/dialogs/ModUpdateDialog.h"
#include "ui/dialogs/ResourceDownloadDialog.h"
#include "ui/dialogs/ResourceUpdateDialog.h"
#include "DesktopServices.h"
@ -71,98 +71,47 @@
#include "tasks/Task.h"
#include "ui/dialogs/ProgressDialog.h"
ModFolderPage::ModFolderPage(BaseInstance* inst, std::shared_ptr<ModFolderModel> mods, QWidget* parent)
: ExternalResourcesPage(inst, mods, parent), m_model(mods)
ModFolderPage::ModFolderPage(BaseInstance* inst, std::shared_ptr<ModFolderModel> model, QWidget* parent)
: ExternalResourcesPage(inst, model, parent), m_model(model)
{
// This is structured like that so that these changes
// do not affect the Resource pack and Shader pack tabs
{
ui->actionDownloadItem->setText(tr("Download mods"));
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->actionDownloadItem->setText(tr("Download Mods"));
ui->actionDownloadItem->setToolTip(tr("Download mods from online mod platforms"));
ui->actionDownloadItem->setEnabled(true);
ui->actionsToolbar->insertActionBefore(ui->actionAddItem, ui->actionDownloadItem);
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 = ui->actionUpdateItem->menu();
if (updateMenu) {
updateMenu->clear();
} else {
updateMenu = new QMenu(this);
}
auto updateMenu = new QMenu(this);
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);
auto update = updateMenu->addAction(tr("Check for Updates"));
connect(update, &QAction::triggered, this, &ModFolderPage::updateMods);
auto updateWithDeps = updateMenu->addAction(tr("Verify Dependencies"));
updateWithDeps->setToolTip(
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); });
updateMenu->addAction(ui->actionVerifyItemDependencies);
connect(ui->actionVerifyItemDependencies, &QAction::triggered, this, [this] { updateMods(true); });
auto depsDisabled = APPLICATION->settings()->getSetting("ModDependenciesDisabled");
updateWithDeps->setVisible(!depsDisabled->get().toBool());
connect(depsDisabled.get(), &Setting::SettingChanged, this,
[updateWithDeps](const Setting& setting, QVariant value) { updateWithDeps->setVisible(!value.toBool()); });
auto depsDisabled = APPLICATION->settings()->getSetting("ModDependenciesDisabled");
ui->actionVerifyItemDependencies->setVisible(!depsDisabled->get().toBool());
connect(depsDisabled.get(), &Setting::SettingChanged, this,
[this](const Setting& setting, const QVariant& value) { ui->actionVerifyItemDependencies->setVisible(!value.toBool()); });
auto actionRemoveItemMetadata = updateMenu->addAction(tr("Reset update metadata"));
actionRemoveItemMetadata->setToolTip(tr("Remove mod's metadata"));
connect(actionRemoveItemMetadata, &QAction::triggered, this, &ModFolderPage::deleteModMetadata);
actionRemoveItemMetadata->setEnabled(false);
updateMenu->addAction(ui->actionResetItemMetadata);
connect(ui->actionResetItemMetadata, &QAction::triggered, this, &ModFolderPage::deleteModMetadata);
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)"));
connect(ui->actionUpdateItem, &QAction::triggered, this, &ModFolderPage::updateMods);
ui->actionsToolbar->insertActionBefore(ui->actionAddItem, ui->actionUpdateItem);
ui->actionChangeVersion->setToolTip(tr("Change a mod's version."));
connect(ui->actionChangeVersion, &QAction::triggered, this, &ModFolderPage::changeModVersion);
ui->actionsToolbar->insertActionAfter(ui->actionUpdateItem, ui->actionChangeVersion);
ui->actionVisitItemPage->setToolTip(tr("Go to mod's home page"));
ui->actionsToolbar->addAction(ui->actionVisitItemPage);
connect(ui->actionVisitItemPage, &QAction::triggered, this, &ModFolderPage::visitModPages);
ui->actionViewHomepage->setToolTip(tr("View the homepages of all selected mods."));
auto changeVersion = new QAction(tr("Change Version"), this);
changeVersion->setToolTip(tr("Change mod version"));
changeVersion->setEnabled(false);
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);
}
ui->actionExportMetadata->setToolTip(tr("Export mod's metadata to text."));
connect(ui->actionExportMetadata, &QAction::triggered, this, &ModFolderPage::exportModMetadata);
ui->actionsToolbar->insertActionAfter(ui->actionViewHomepage, ui->actionExportMetadata);
}
bool ModFolderPage::shouldDisplay() const
@ -170,15 +119,12 @@ bool ModFolderPage::shouldDisplay() const
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);
int row = sourceCurrent.row();
Mod const* m = m_model->at(row);
if (m)
ui->frame->updateWithMod(*m);
return true;
const Mod& mod = m_model->at(row);
ui->frame->updateWithMod(mod);
}
void ModFolderPage::removeItems(const QItemSelection& selection)
@ -193,10 +139,10 @@ void ModFolderPage::removeItems(const QItemSelection& selection)
if (response != QMessageBox::Yes)
return;
}
m_model->deleteMods(selection.indexes());
m_model->deleteResources(selection.indexes());
}
void ModFolderPage::installMods()
void ModFolderPage::downloadMods()
{
if (m_instance->typeName() != "Minecraft")
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);
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) {
CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show();
tasks->deleteLater();
@ -266,12 +212,12 @@ void ModFolderPage::updateMods(bool includeDeps)
}
auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes();
auto mods_list = m_model->selectedMods(selection);
auto mods_list = m_model->selectedResources(selection);
bool use_all = mods_list.empty();
if (use_all)
mods_list = m_model->allMods();
mods_list = m_model->allResources();
ModUpdateDialog update_dialog(this, m_instance, m_model, mods_list, includeDeps);
ResourceUpdateDialog update_dialog(this, m_instance, m_model, mods_list, includeDeps, true);
update_dialog.checkCandidates();
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)
: ModFolderPage(inst, mods, parent)
{
@ -369,97 +399,3 @@ bool NilModFolderPage::shouldDisplay() const
{
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
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;
void setFilter(const QString& filter) { m_fileSelectionFilter = filter; }
@ -57,16 +57,15 @@ class ModFolderPage : public ExternalResourcesPage {
virtual bool shouldDisplay() const override;
public slots:
bool onSelectionChanged(const QModelIndex& current, const QModelIndex& previous) override;
void updateFrame(const QModelIndex& current, const QModelIndex& previous) override;
private slots:
void removeItems(const QItemSelection& selection) override;
void downloadMods();
void updateMods(bool includeDeps = false);
void deleteModMetadata();
void exportModMetadata();
void installMods();
void updateMods(bool includeDeps = false);
void visitModPages();
void changeModVersion();
protected:

View File

@ -42,35 +42,51 @@
#include "ui/dialogs/CustomMessageBox.h"
#include "ui/dialogs/ProgressDialog.h"
#include "ui/dialogs/ResourceDownloadDialog.h"
#include "ui/dialogs/ResourceUpdateDialog.h"
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->setToolTip(tr("Download resource packs from online platforms"));
ui->actionDownloadItem->setText(tr("Download Packs"));
ui->actionDownloadItem->setToolTip(tr("Download resource packs from online mod platforms"));
ui->actionDownloadItem->setEnabled(true);
connect(ui->actionDownloadItem, &QAction::triggered, this, &ResourcePackPage::downloadRPs);
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);
int row = sourceCurrent.row();
auto& rp = static_cast<ResourcePack&>(m_model->at(row));
ui->frame->updateWithResourcePack(rp);
return true;
}
void ResourcePackPage::downloadRPs()
void ResourcePackPage::downloadResourcePacks()
{
if (m_instance->typeName() != "Minecraft")
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()) {
auto tasks =
new ConcurrentTask(this, "Download Resource Pack", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt());
@ -101,3 +117,157 @@ void ResourcePackPage::downloadRPs()
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:
bool onSelectionChanged(const QModelIndex& current, const QModelIndex& previous) override;
void downloadRPs();
void updateFrame(const QModelIndex& current, const QModelIndex& previous) override;
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/ProgressDialog.h"
#include "ui/dialogs/ResourceDownloadDialog.h"
#include "ui/dialogs/ResourceUpdateDialog.h"
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->setToolTip(tr("Download shaders from online platforms"));
ui->actionDownloadItem->setText(tr("Download Packs"));
ui->actionDownloadItem->setToolTip(tr("Download shader packs from online mod platforms"));
ui->actionDownloadItem->setEnabled(true);
connect(ui->actionDownloadItem, &QAction::triggered, this, &ShaderPackPage::downloadShaders);
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")
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()) {
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) {
CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show();
tasks->deleteLater();

View File

@ -53,5 +53,11 @@ class ShaderPackPage : public ExternalResourcesPage {
bool shouldDisplay() const override { return true; }
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/ProgressDialog.h"
#include "ui/dialogs/ResourceDownloadDialog.h"
#include "ui/dialogs/ResourceUpdateDialog.h"
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->setToolTip(tr("Download texture packs from online platforms"));
ui->actionDownloadItem->setText(tr("Download Packs"));
ui->actionDownloadItem->setToolTip(tr("Download texture packs from online mod platforms"));
ui->actionDownloadItem->setEnabled(true);
connect(ui->actionDownloadItem, &QAction::triggered, this, &TexturePackPage::downloadTPs);
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);
int row = sourceCurrent.row();
auto& rp = static_cast<TexturePack&>(m_model->at(row));
ui->frame->updateWithTexturePack(rp);
return true;
}
void TexturePackPage::downloadTPs()
void TexturePackPage::downloadTexturePacks()
{
if (m_instance->typeName() != "Minecraft")
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()) {
auto tasks =
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"); }
public slots:
bool onSelectionChanged(const QModelIndex& current, const QModelIndex& previous) override;
void downloadTPs();
void updateFrame(const QModelIndex& current, const QModelIndex& previous) override;
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_ResourcePage.h"
#include <StringUtils.h>
#include <QDesktopServices>
#include <QKeyEvent>
#include "Markdown.h"
#include "StringUtils.h"
#include "Application.h"
#include "ui/dialogs/ResourceDownloadDialog.h"
#include "ui/pages/modplatform/ResourceModel.h"
#include "ui/widgets/ProjectItem.h"
@ -349,7 +350,8 @@ void ResourcePage::addResourceToPage(ModPlatform::IndexedPack::Ptr pack,
ModPlatform::IndexedVersion& ver,
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)

View File

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

View File

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

View File

@ -4,7 +4,7 @@
<launchable type="desktop-id">org.prismlauncher.PrismLauncher.desktop</launchable>
<name>Prism Launcher</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>
<project_license>GPL-3.0-only</project_license>
<url type="homepage">https://prismlauncher.org/</url>

View File

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