Merge pull request #1588 from TheKodeToad/resource-meta

Implement tracking and updates for files other than mods
This commit is contained in:
TheKodeToad
2024-10-30 17:03:54 +00:00
committed by GitHub
61 changed files with 1808 additions and 1344 deletions

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

@@ -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())
@@ -278,7 +344,8 @@ void ResourceFolderModel::resolveResource(Resource* res)
connect(
task.get(), &Task::succeeded, this, [=] { onParseSucceeded(ticket, res->internal_id()); }, Qt::ConnectionType::QueuedConnection);
connect(task.get(), &Task::failed, this, [=] { onParseFailed(ticket, res->internal_id()); }, Qt::ConnectionType::QueuedConnection);
connect(
task.get(), &Task::failed, this, [=] { onParseFailed(ticket, res->internal_id()); }, Qt::ConnectionType::QueuedConnection);
connect(
task.get(), &Task::finished, this,
[=] {
@@ -296,7 +363,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;
@@ -326,7 +393,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
@@ -416,6 +487,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:
@@ -487,22 +560,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(), \
[&](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<QHeaderView::ResizeMode> m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive,
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,
QHeaderView::Interactive };
QList<bool> m_columnsHideable = { false, false, true, true };
QList<bool> m_columnsHiddenByDefault = { false, false, false, false };
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(), \
[&](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;