Merge remote-tracking branch 'upstream/develop' into data-packs
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
This commit is contained in:
@@ -131,12 +131,12 @@ QPixmap DataPack::image(QSize size, Qt::AspectRatioMode mode) const
|
||||
if (!m_pack_image_cache_key.was_ever_used) {
|
||||
return {};
|
||||
} else {
|
||||
qDebug() << "Resource Pack" << name() << "Had it's image evicted from the cache. reloading...";
|
||||
qDebug() << "Data Pack" << name() << "Had it's image evicted from the cache. reloading...";
|
||||
PixmapCache::markCacheMissByEviciton();
|
||||
}
|
||||
|
||||
// Imaged got evicted from the cache. Re-process it and retry.
|
||||
DataPackUtils::processPackPNG(*this);
|
||||
DataPackUtils::processPackPNG(this);
|
||||
return image(size);
|
||||
}
|
||||
|
||||
|
||||
@@ -36,20 +36,18 @@ class Version;
|
||||
class DataPack : public Resource {
|
||||
Q_OBJECT
|
||||
public:
|
||||
using Ptr = shared_qobject_ptr<Resource>;
|
||||
|
||||
DataPack(QObject* parent = nullptr) : Resource(parent) {}
|
||||
DataPack(QFileInfo file_info) : Resource(file_info) {}
|
||||
|
||||
/** Gets the numerical ID of the pack format. */
|
||||
[[nodiscard]] int packFormat() const { return m_pack_format; }
|
||||
/** Gets, respectively, the lower and upper versions supported by the set pack format. */
|
||||
[[nodiscard]] std::pair<Version, Version> compatibleVersions() const;
|
||||
[[nodiscard]] virtual std::pair<Version, Version> compatibleVersions() const;
|
||||
|
||||
/** Gets the description of the data pack. */
|
||||
[[nodiscard]] QString description() const { return m_description; }
|
||||
|
||||
/** Gets the image of the resource pack, converted to a QPixmap for drawing, and scaled to size. */
|
||||
/** Gets the image of the data pack, converted to a QPixmap for drawing, and scaled to size. */
|
||||
[[nodiscard]] QPixmap image(QSize size, Qt::AspectRatioMode mode = Qt::AspectRatioMode::IgnoreAspectRatio) const;
|
||||
|
||||
/** Thread-safe. */
|
||||
@@ -66,6 +64,8 @@ class DataPack : public Resource {
|
||||
[[nodiscard]] int compare(Resource const& other, SortType type) const override;
|
||||
[[nodiscard]] bool applyFilter(QRegularExpression filter) const override;
|
||||
|
||||
virtual QString directory() { return "/data"; }
|
||||
|
||||
protected:
|
||||
mutable QMutex m_data_lock;
|
||||
|
||||
|
||||
@@ -45,11 +45,9 @@
|
||||
#include "Application.h"
|
||||
#include "Version.h"
|
||||
|
||||
#include "minecraft/mod/tasks/BasicFolderLoadTask.h"
|
||||
#include "minecraft/mod/tasks/LocalDataPackParseTask.h"
|
||||
#include "minecraft/mod/tasks/LocalResourcePackParseTask.h"
|
||||
|
||||
DataPackFolderModel::DataPackFolderModel(const QString& dir, BaseInstance* instance) : ResourceFolderModel(QDir(dir), instance)
|
||||
DataPackFolderModel::DataPackFolderModel(const QString& 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", "Pack Format", "Last Modified" });
|
||||
m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Pack Format"), tr("Last Modified") });
|
||||
@@ -73,12 +71,12 @@ QVariant DataPackFolderModel::data(const QModelIndex& index, int role) const
|
||||
case NameColumn:
|
||||
return m_resources[row]->name();
|
||||
case PackFormatColumn: {
|
||||
auto resource = at(row);
|
||||
auto pack_format = resource->packFormat();
|
||||
auto& resource = at(row);
|
||||
auto pack_format = resource.packFormat();
|
||||
if (pack_format == 0)
|
||||
return tr("Unrecognized");
|
||||
|
||||
auto version_bounds = resource->compatibleVersions();
|
||||
auto version_bounds = resource.compatibleVersions();
|
||||
if (version_bounds.first.toString().isEmpty())
|
||||
return QString::number(pack_format);
|
||||
|
||||
@@ -92,10 +90,10 @@ QVariant DataPackFolderModel::data(const QModelIndex& index, int role) const
|
||||
return {};
|
||||
}
|
||||
case Qt::DecorationRole: {
|
||||
if (column == NameColumn && (at(row)->isSymLinkUnder(instDirPath()) || at(row)->isMoreThanOneHardLink()))
|
||||
if (column == NameColumn && (at(row).isSymLinkUnder(instDirPath()) || at(row).isMoreThanOneHardLink()))
|
||||
return APPLICATION->getThemedIcon("status-yellow");
|
||||
if (column == ImageColumn) {
|
||||
return at(row)->image({ 32, 32 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding);
|
||||
return at(row).image({ 32, 32 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
@@ -105,14 +103,14 @@ QVariant DataPackFolderModel::data(const QModelIndex& index, int role) const
|
||||
return tr("The data pack format ID, as well as the Minecraft versions it was designed for.");
|
||||
}
|
||||
if (column == NameColumn) {
|
||||
if (at(row)->isSymLinkUnder(instDirPath())) {
|
||||
if (at(row).isSymLinkUnder(instDirPath())) {
|
||||
return m_resources[row]->internal_id() +
|
||||
tr("\nWarning: This resource is symbolically linked from elsewhere. Editing it will also change the original."
|
||||
"\nCanonical Path: %1")
|
||||
.arg(at(row)->fileinfo().canonicalFilePath());
|
||||
.arg(at(row).fileinfo().canonicalFilePath());
|
||||
;
|
||||
}
|
||||
if (at(row)->isMoreThanOneHardLink()) {
|
||||
if (at(row).isMoreThanOneHardLink()) {
|
||||
return m_resources[row]->internal_id() +
|
||||
tr("\nWarning: This resource is hard linked elsewhere. Editing it will also change the original.");
|
||||
}
|
||||
@@ -127,7 +125,7 @@ QVariant DataPackFolderModel::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 {};
|
||||
}
|
||||
@@ -180,12 +178,12 @@ int DataPackFolderModel::columnCount(const QModelIndex& parent) const
|
||||
return parent.isValid() ? 0 : NUM_COLUMNS;
|
||||
}
|
||||
|
||||
Task* DataPackFolderModel::createUpdateTask()
|
||||
Resource* DataPackFolderModel::createResource(const QFileInfo& file)
|
||||
{
|
||||
return new BasicFolderLoadTask(m_dir, [](QFileInfo const& entry) { return makeShared<DataPack>(entry); });
|
||||
return new DataPack(file);
|
||||
}
|
||||
|
||||
Task* DataPackFolderModel::createParseTask(Resource& resource)
|
||||
{
|
||||
return new LocalDataPackParseTask(m_next_resolution_ticket, static_cast<DataPack&>(resource));
|
||||
return new LocalDataPackParseTask(m_next_resolution_ticket, static_cast<DataPack*>(&resource));
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ class DataPackFolderModel : public ResourceFolderModel {
|
||||
public:
|
||||
enum Columns { ActiveColumn = 0, ImageColumn, NameColumn, PackFormatColumn, DateColumn, NUM_COLUMNS };
|
||||
|
||||
explicit DataPackFolderModel(const QString& dir, BaseInstance* instance);
|
||||
explicit DataPackFolderModel(const QString& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent = nullptr);
|
||||
|
||||
virtual QString id() const override { return "datapacks"; }
|
||||
|
||||
@@ -55,7 +55,7 @@ class DataPackFolderModel : public ResourceFolderModel {
|
||||
[[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;
|
||||
[[nodiscard]] Task* createParseTask(Resource&) override;
|
||||
|
||||
RESOURCE_HELPERS(DataPack)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -36,17 +36,17 @@
|
||||
*/
|
||||
|
||||
#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"
|
||||
|
||||
@@ -55,24 +55,6 @@ 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;
|
||||
@@ -100,33 +82,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;
|
||||
}
|
||||
}
|
||||
@@ -147,28 +124,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;
|
||||
@@ -180,10 +135,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
|
||||
@@ -191,16 +143,52 @@ 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::modLoaderTypesToList(modLoaders)) {
|
||||
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
|
||||
@@ -213,73 +201,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_pack_image_cache_key.was_read_attempt = false;
|
||||
m_packImageCacheKey.wasReadAttempt = false;
|
||||
}
|
||||
}
|
||||
|
||||
auto Mod::provider() const -> std::optional<QString>
|
||||
{
|
||||
if (metadata())
|
||||
return ModPlatform::ProviderCapabilities::readableName(metadata()->provider);
|
||||
return {};
|
||||
}
|
||||
|
||||
auto Mod::side() const -> Metadata::ModSide
|
||||
{
|
||||
if (metadata())
|
||||
return metadata()->side;
|
||||
return Metadata::ModSide::UniversalSide;
|
||||
}
|
||||
|
||||
auto Mod::releaseType() const -> ModPlatform::IndexedVersionType
|
||||
{
|
||||
if (metadata())
|
||||
return metadata()->releaseType;
|
||||
return ModPlatform::IndexedVersionType::VersionType::Unknown;
|
||||
}
|
||||
|
||||
auto Mod::loaders() const -> ModPlatform::ModLoaderTypes
|
||||
{
|
||||
if (metadata())
|
||||
return metadata()->loaders;
|
||||
return {};
|
||||
}
|
||||
|
||||
auto Mod::mcVersions() const -> QStringList
|
||||
{
|
||||
if (metadata())
|
||||
return metadata()->mcVersions;
|
||||
return {};
|
||||
}
|
||||
|
||||
auto Mod::licenses() const -> const QList<ModLicense>&
|
||||
{
|
||||
return details().licenses;
|
||||
@@ -290,45 +222,53 @@ auto Mod::issueTracker() const -> QString
|
||||
return details().issue_tracker;
|
||||
}
|
||||
|
||||
void Mod::setIcon(QImage new_image) const
|
||||
QPixmap Mod::setIcon(QImage new_image) const
|
||||
{
|
||||
QMutexLocker locker(&m_data_lock);
|
||||
|
||||
Q_ASSERT(!new_image.isNull());
|
||||
|
||||
if (m_pack_image_cache_key.key.isValid())
|
||||
PixmapCache::remove(m_pack_image_cache_key.key);
|
||||
if (m_packImageCacheKey.key.isValid())
|
||||
PixmapCache::remove(m_packImageCacheKey.key);
|
||||
|
||||
// scale the image to avoid flooding the pixmapcache
|
||||
auto pixmap =
|
||||
QPixmap::fromImage(new_image.scaled({ 64, 64 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding, Qt::SmoothTransformation));
|
||||
|
||||
m_pack_image_cache_key.key = PixmapCache::insert(pixmap);
|
||||
m_pack_image_cache_key.was_ever_used = true;
|
||||
m_pack_image_cache_key.was_read_attempt = true;
|
||||
m_packImageCacheKey.key = PixmapCache::insert(pixmap);
|
||||
m_packImageCacheKey.wasEverUsed = true;
|
||||
m_packImageCacheKey.wasReadAttempt = true;
|
||||
return pixmap;
|
||||
}
|
||||
|
||||
QPixmap Mod::icon(QSize size, Qt::AspectRatioMode mode) const
|
||||
{
|
||||
QPixmap cached_image;
|
||||
if (PixmapCache::find(m_pack_image_cache_key.key, &cached_image)) {
|
||||
auto pixmap_transform = [&size, &mode](QPixmap pixmap) {
|
||||
if (size.isNull())
|
||||
return cached_image;
|
||||
return cached_image.scaled(size, mode, Qt::SmoothTransformation);
|
||||
return pixmap;
|
||||
return pixmap.scaled(size, mode, Qt::SmoothTransformation);
|
||||
};
|
||||
|
||||
QPixmap cached_image;
|
||||
if (PixmapCache::find(m_packImageCacheKey.key, &cached_image)) {
|
||||
return pixmap_transform(cached_image);
|
||||
}
|
||||
|
||||
// No valid image we can get
|
||||
if ((!m_pack_image_cache_key.was_ever_used && m_pack_image_cache_key.was_read_attempt) || iconPath().isEmpty())
|
||||
if ((!m_packImageCacheKey.wasEverUsed && m_packImageCacheKey.wasReadAttempt) || iconPath().isEmpty())
|
||||
return {};
|
||||
|
||||
if (m_pack_image_cache_key.was_ever_used) {
|
||||
if (m_packImageCacheKey.wasEverUsed) {
|
||||
qDebug() << "Mod" << name() << "Had it's icon evicted from the cache. reloading...";
|
||||
PixmapCache::markCacheMissByEviciton();
|
||||
}
|
||||
// Image got evicted from the cache or an attempt to load it has not been made. load it and retry.
|
||||
m_pack_image_cache_key.was_read_attempt = true;
|
||||
ModUtils::loadIconFile(*this);
|
||||
return icon(size);
|
||||
m_packImageCacheKey.wasReadAttempt = true;
|
||||
if (ModUtils::loadIconFile(*this, &cached_image)) {
|
||||
return pixmap_transform(cached_image);
|
||||
}
|
||||
// Image failed to load
|
||||
return {};
|
||||
}
|
||||
|
||||
bool Mod::valid() const
|
||||
|
||||
@@ -48,7 +48,6 @@
|
||||
|
||||
#include "ModDetails.h"
|
||||
#include "Resource.h"
|
||||
#include "modplatform/ModIndex.h"
|
||||
|
||||
class Mod : public Resource {
|
||||
Q_OBJECT
|
||||
@@ -58,43 +57,33 @@ 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; }
|
||||
/** Gets the icon of the mod, converted to a QPixmap for drawing, and scaled to size. */
|
||||
[[nodiscard]] QPixmap icon(QSize size, Qt::AspectRatioMode mode = Qt::AspectRatioMode::IgnoreAspectRatio) const;
|
||||
/** Thread-safe. */
|
||||
void setIcon(QImage new_image) const;
|
||||
QPixmap setIcon(QImage new_image) const;
|
||||
|
||||
auto metadata() -> std::shared_ptr<Metadata::ModStruct>;
|
||||
auto metadata() const -> const std::shared_ptr<Metadata::ModStruct>;
|
||||
|
||||
void setStatus(ModStatus status);
|
||||
void setMetadata(std::shared_ptr<Metadata::ModStruct>&& metadata);
|
||||
void setMetadata(const Metadata::ModStruct& metadata) { setMetadata(std::make_shared<Metadata::ModStruct>(metadata)); }
|
||||
void setDetails(const ModDetails& details);
|
||||
|
||||
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
|
||||
@@ -111,7 +100,7 @@ class Mod : public Resource {
|
||||
|
||||
struct {
|
||||
QPixmapCache::Key key;
|
||||
bool was_ever_used = false;
|
||||
bool was_read_attempt = false;
|
||||
} mutable m_pack_image_cache_key;
|
||||
bool wasEverUsed = false;
|
||||
bool wasReadAttempt = false;
|
||||
} mutable m_packImageCacheKey;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -51,18 +51,10 @@
|
||||
|
||||
#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 "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 +84,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 +92,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 +147,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 +188,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 +213,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);
|
||||
@@ -379,51 +240,7 @@ void ModFolderModel::onParseSucceeded(int ticket, QString mod_id)
|
||||
|
||||
auto result = cast_task->result();
|
||||
if (result && resource)
|
||||
resource->finishResolvingWithDetails(std::move(result->details));
|
||||
static_cast<Mod*>(resource.get())->finishResolvingWithDetails(std::move(result->details));
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -47,11 +47,6 @@
|
||||
#include "Mod.h"
|
||||
#include "ResourceFolderModel.h"
|
||||
|
||||
#include "minecraft/mod/tasks/LocalModParseTask.h"
|
||||
#include "minecraft/mod/tasks/ModFolderLoadTask.h"
|
||||
#include "modplatform/ModIndex.h"
|
||||
|
||||
class LegacyInstance;
|
||||
class BaseInstance;
|
||||
class QFileSystemWatcher;
|
||||
|
||||
@@ -76,8 +71,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 +80,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;
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
#include <QObject>
|
||||
#include <QPointer>
|
||||
|
||||
#include "MetadataHandler.h"
|
||||
#include "QObjectPtr.h"
|
||||
|
||||
enum class ResourceType {
|
||||
@@ -50,7 +51,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 +92,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 +135,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 +166,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;
|
||||
|
||||
|
||||
@@ -11,20 +11,23 @@
|
||||
#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());
|
||||
@@ -35,10 +38,9 @@ ResourceFolderModel::ResourceFolderModel(QDir dir, BaseInstance* instance, QObje
|
||||
|
||||
connect(&m_watcher, &QFileSystemWatcher::directoryChanged, this, &ResourceFolderModel::directoryChanged);
|
||||
connect(&m_helper_thread_task, &ConcurrentTask::finished, this, [this] { m_helper_thread_task.clear(); });
|
||||
#ifndef LAUNCHER_TEST
|
||||
// in tests the application macro doesn't work
|
||||
m_helper_thread_task.setMaxConcurrent(APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt());
|
||||
#endif
|
||||
if (APPLICATION_DYN) { // in tests the application macro doesn't work
|
||||
m_helper_thread_task.setMaxConcurrent(APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt());
|
||||
}
|
||||
}
|
||||
|
||||
ResourceFolderModel::~ResourceFolderModel()
|
||||
@@ -49,6 +51,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;
|
||||
|
||||
@@ -159,11 +164,51 @@ bool ResourceFolderModel::installResource(QString original_path)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ResourceFolderModel::uninstallResource(QString file_name)
|
||||
void ResourceFolderModel::installResourceWithFlameMetadata(QString path, ModPlatform::IndexedVersion& vers)
|
||||
{
|
||||
auto install = [this, path] { installResource(std::move(path)); };
|
||||
if (vers.addonId.isValid()) {
|
||||
ModPlatform::IndexedPack pack{
|
||||
vers.addonId,
|
||||
ModPlatform::ResourceProvider::FLAME,
|
||||
};
|
||||
|
||||
auto response = std::make_shared<QByteArray>();
|
||||
auto job = FlameAPI().getProject(vers.addonId.toString(), response);
|
||||
QObject::connect(job.get(), &Task::failed, this, install);
|
||||
QObject::connect(job.get(), &Task::aborted, this, install);
|
||||
QObject::connect(job.get(), &Task::succeeded, [response, this, &vers, install, &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, this, install);
|
||||
update_metadata.start();
|
||||
});
|
||||
|
||||
job->start();
|
||||
} else {
|
||||
install();
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
@@ -179,13 +224,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();
|
||||
@@ -193,6 +236,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())
|
||||
@@ -246,7 +305,7 @@ bool ResourceFolderModel::update()
|
||||
connect(m_current_update_task.get(), &Task::failed, this, &ResourceFolderModel::onUpdateFailed, Qt::ConnectionType::QueuedConnection);
|
||||
connect(
|
||||
m_current_update_task.get(), &Task::finished, this,
|
||||
[=] {
|
||||
[this] {
|
||||
m_current_update_task.reset();
|
||||
if (m_scheduled_update) {
|
||||
m_scheduled_update = false;
|
||||
@@ -262,7 +321,7 @@ bool ResourceFolderModel::update()
|
||||
return true;
|
||||
}
|
||||
|
||||
void ResourceFolderModel::resolveResource(Resource* res)
|
||||
void ResourceFolderModel::resolveResource(Resource::Ptr res)
|
||||
{
|
||||
if (!res->shouldResolve()) {
|
||||
return;
|
||||
@@ -278,11 +337,14 @@ void ResourceFolderModel::resolveResource(Resource* res)
|
||||
m_active_parse_tasks.insert(ticket, task);
|
||||
|
||||
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);
|
||||
task.get(), &Task::succeeded, this, [this, ticket, res] { onParseSucceeded(ticket, res->internal_id()); },
|
||||
Qt::ConnectionType::QueuedConnection);
|
||||
connect(
|
||||
task.get(), &Task::failed, this, [this, ticket, res] { onParseFailed(ticket, res->internal_id()); },
|
||||
Qt::ConnectionType::QueuedConnection);
|
||||
connect(
|
||||
task.get(), &Task::finished, this,
|
||||
[=] {
|
||||
[this, ticket] {
|
||||
m_active_parse_tasks.remove(ticket);
|
||||
emit parseFinished();
|
||||
},
|
||||
@@ -297,7 +359,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;
|
||||
|
||||
@@ -318,7 +380,7 @@ void ResourceFolderModel::onUpdateSucceeded()
|
||||
void ResourceFolderModel::onParseSucceeded(int ticket, QString resource_id)
|
||||
{
|
||||
auto iter = m_active_parse_tasks.constFind(ticket);
|
||||
if (iter == m_active_parse_tasks.constEnd())
|
||||
if (iter == m_active_parse_tasks.constEnd() || !m_resources_index.contains(resource_id))
|
||||
return;
|
||||
|
||||
int row = m_resources_index[resource_id];
|
||||
@@ -327,7 +389,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
|
||||
@@ -417,6 +483,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:
|
||||
@@ -488,22 +556,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:
|
||||
@@ -630,7 +699,7 @@ QString ResourceFolderModel::instDirPath() const
|
||||
void ResourceFolderModel::onParseFailed(int ticket, QString resource_id)
|
||||
{
|
||||
auto iter = m_active_parse_tasks.constFind(ticket);
|
||||
if (iter == m_active_parse_tasks.constEnd())
|
||||
if (iter == m_active_parse_tasks.constEnd() || !m_resources_index.contains(resource_id))
|
||||
return;
|
||||
|
||||
auto removed_index = m_resources_index[resource_id];
|
||||
@@ -649,3 +718,126 @@ void ResourceFolderModel::onParseFailed(int ticket, QString resource_id)
|
||||
}
|
||||
endRemoveRows();
|
||||
}
|
||||
|
||||
void ResourceFolderModel::applyUpdates(QSet<QString>& current_set, QSet<QString>& new_set, QMap<QString, Resource::Ptr>& new_resources)
|
||||
{
|
||||
// see if the kept resources changed in some way
|
||||
{
|
||||
QSet<QString> kept_set = current_set;
|
||||
kept_set.intersect(new_set);
|
||||
|
||||
for (auto const& kept : kept_set) {
|
||||
auto row_it = m_resources_index.constFind(kept);
|
||||
Q_ASSERT(row_it != m_resources_index.constEnd());
|
||||
auto row = row_it.value();
|
||||
|
||||
auto& new_resource = new_resources[kept];
|
||||
auto const& current_resource = m_resources.at(row);
|
||||
|
||||
if (new_resource->dateTimeChanged() == current_resource->dateTimeChanged()) {
|
||||
// no significant change, ignore...
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the resource is resolving, but something about it changed, we don't want to
|
||||
// continue the resolving.
|
||||
if (current_resource->isResolving()) {
|
||||
auto ticket = current_resource->resolutionTicket();
|
||||
if (m_active_parse_tasks.contains(ticket)) {
|
||||
auto task = (*m_active_parse_tasks.find(ticket)).get();
|
||||
task->abort();
|
||||
}
|
||||
}
|
||||
|
||||
m_resources[row].reset(new_resource);
|
||||
resolveResource(m_resources.at(row));
|
||||
emit dataChanged(index(row, 0), index(row, columnCount(QModelIndex()) - 1));
|
||||
}
|
||||
}
|
||||
|
||||
// remove resources no longer present
|
||||
{
|
||||
QSet<QString> removed_set = current_set;
|
||||
removed_set.subtract(new_set);
|
||||
|
||||
QList<int> removed_rows;
|
||||
for (auto& removed : removed_set)
|
||||
removed_rows.append(m_resources_index[removed]);
|
||||
|
||||
std::sort(removed_rows.begin(), removed_rows.end(), std::greater<int>());
|
||||
|
||||
for (auto& removed_index : removed_rows) {
|
||||
auto removed_it = m_resources.begin() + removed_index;
|
||||
|
||||
Q_ASSERT(removed_it != m_resources.end());
|
||||
|
||||
if ((*removed_it)->isResolving()) {
|
||||
auto ticket = (*removed_it)->resolutionTicket();
|
||||
if (m_active_parse_tasks.contains(ticket)) {
|
||||
auto task = (*m_active_parse_tasks.find(ticket)).get();
|
||||
task->abort();
|
||||
}
|
||||
}
|
||||
|
||||
beginRemoveRows(QModelIndex(), removed_index, removed_index);
|
||||
m_resources.erase(removed_it);
|
||||
endRemoveRows();
|
||||
}
|
||||
}
|
||||
|
||||
// add new resources to the end
|
||||
{
|
||||
QSet<QString> added_set = new_set;
|
||||
added_set.subtract(current_set);
|
||||
|
||||
// When you have a Qt build with assertions turned on, proceeding here will abort the application
|
||||
if (added_set.size() > 0) {
|
||||
beginInsertRows(QModelIndex(), static_cast<int>(m_resources.size()),
|
||||
static_cast<int>(m_resources.size() + added_set.size() - 1));
|
||||
|
||||
for (auto& added : added_set) {
|
||||
auto res = new_resources[added];
|
||||
m_resources.append(res);
|
||||
resolveResource(m_resources.last());
|
||||
}
|
||||
|
||||
endInsertRows();
|
||||
}
|
||||
}
|
||||
|
||||
// update index
|
||||
{
|
||||
m_resources_index.clear();
|
||||
int idx = 0;
|
||||
for (auto const& mod : qAsConst(m_resources)) {
|
||||
m_resources_index[mod->internal_id()] = idx;
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
}
|
||||
Resource::Ptr ResourceFolderModel::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 *iter;
|
||||
}
|
||||
QList<Resource*> ResourceFolderModel::allResources()
|
||||
{
|
||||
QList<Resource*> result;
|
||||
result.reserve(m_resources.size());
|
||||
for (const Resource ::Ptr& resource : m_resources)
|
||||
result.append((resource.get()));
|
||||
return result;
|
||||
}
|
||||
QList<Resource*> ResourceFolderModel::selectedResources(const QModelIndexList& indexes)
|
||||
{
|
||||
QList<Resource*> result;
|
||||
for (const QModelIndex& index : indexes) {
|
||||
if (index.column() != 0)
|
||||
continue;
|
||||
result.append(&at(index.row()));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -19,6 +19,38 @@
|
||||
|
||||
class QSortFilterProxyModel;
|
||||
|
||||
/* A macro to define useful functions to handle Resource* -> T* more easily on derived classes */
|
||||
#define RESOURCE_HELPERS(T) \
|
||||
[[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()); \
|
||||
} \
|
||||
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 +61,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 +81,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 +93,15 @@ class ResourceFolderModel : public QAbstractListModel {
|
||||
*/
|
||||
virtual bool installResource(QString path);
|
||||
|
||||
virtual void 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'.
|
||||
*
|
||||
@@ -76,13 +113,17 @@ class ResourceFolderModel : public QAbstractListModel {
|
||||
virtual bool update();
|
||||
|
||||
/** Creates a new parse task, if needed, for 'res' and start it.*/
|
||||
virtual void resolveResource(Resource* res);
|
||||
virtual void resolveResource(Resource::Ptr res);
|
||||
|
||||
[[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; }
|
||||
|
||||
[[nodiscard]] Resource& at(int index) { return *m_resources[index].get(); }
|
||||
[[nodiscard]] const Resource& at(int index) const { return *m_resources.at(index).get(); }
|
||||
QList<Resource*> selectedResources(const QModelIndexList& indexes);
|
||||
QList<Resource*> allResources();
|
||||
|
||||
[[nodiscard]] Resource::Ptr find(QString id);
|
||||
|
||||
[[nodiscard]] QDir const& dir() const { return m_dir; }
|
||||
|
||||
@@ -96,7 +137,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 +195,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().
|
||||
*
|
||||
@@ -167,10 +211,8 @@ class ResourceFolderModel : public QAbstractListModel {
|
||||
* It uses set operations to find differences between the current state and the updated state,
|
||||
* to act only on those disparities.
|
||||
*
|
||||
* The implementation is at the end of this header.
|
||||
*/
|
||||
template <typename T>
|
||||
void applyUpdates(QSet<QString>& current_set, QSet<QString>& new_set, QMap<QString, T>& new_resources);
|
||||
void applyUpdates(QSet<QString>& current_set, QSet<QString>& new_set, QMap<QString, Resource::Ptr>& new_resources);
|
||||
|
||||
protected slots:
|
||||
void directoryChanged(QString);
|
||||
@@ -195,19 +237,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;
|
||||
|
||||
@@ -220,133 +265,3 @@ class ResourceFolderModel : public QAbstractListModel {
|
||||
QMap<int, Task::Ptr> m_active_parse_tasks;
|
||||
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)
|
||||
{
|
||||
// see if the kept resources changed in some way
|
||||
{
|
||||
QSet<QString> kept_set = current_set;
|
||||
kept_set.intersect(new_set);
|
||||
|
||||
for (auto const& kept : kept_set) {
|
||||
auto row_it = m_resources_index.constFind(kept);
|
||||
Q_ASSERT(row_it != m_resources_index.constEnd());
|
||||
auto row = row_it.value();
|
||||
|
||||
auto& new_resource = new_resources[kept];
|
||||
auto const& current_resource = m_resources.at(row);
|
||||
|
||||
if (new_resource->dateTimeChanged() == current_resource->dateTimeChanged()) {
|
||||
// no significant change, ignore...
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the resource is resolving, but something about it changed, we don't want to
|
||||
// continue the resolving.
|
||||
if (current_resource->isResolving()) {
|
||||
auto ticket = current_resource->resolutionTicket();
|
||||
if (m_active_parse_tasks.contains(ticket)) {
|
||||
auto task = (*m_active_parse_tasks.find(ticket)).get();
|
||||
task->abort();
|
||||
}
|
||||
}
|
||||
|
||||
m_resources[row].reset(new_resource);
|
||||
resolveResource(m_resources.at(row).get());
|
||||
emit dataChanged(index(row, 0), index(row, columnCount(QModelIndex()) - 1));
|
||||
}
|
||||
}
|
||||
|
||||
// remove resources no longer present
|
||||
{
|
||||
QSet<QString> removed_set = current_set;
|
||||
removed_set.subtract(new_set);
|
||||
|
||||
QList<int> removed_rows;
|
||||
for (auto& removed : removed_set)
|
||||
removed_rows.append(m_resources_index[removed]);
|
||||
|
||||
std::sort(removed_rows.begin(), removed_rows.end(), std::greater<int>());
|
||||
|
||||
for (auto& removed_index : removed_rows) {
|
||||
auto removed_it = m_resources.begin() + removed_index;
|
||||
|
||||
Q_ASSERT(removed_it != m_resources.end());
|
||||
|
||||
if ((*removed_it)->isResolving()) {
|
||||
auto ticket = (*removed_it)->resolutionTicket();
|
||||
if (m_active_parse_tasks.contains(ticket)) {
|
||||
auto task = (*m_active_parse_tasks.find(ticket)).get();
|
||||
task->abort();
|
||||
}
|
||||
}
|
||||
|
||||
beginRemoveRows(QModelIndex(), removed_index, removed_index);
|
||||
m_resources.erase(removed_it);
|
||||
endRemoveRows();
|
||||
}
|
||||
}
|
||||
|
||||
// add new resources to the end
|
||||
{
|
||||
QSet<QString> added_set = new_set;
|
||||
added_set.subtract(current_set);
|
||||
|
||||
// When you have a Qt build with assertions turned on, proceeding here will abort the application
|
||||
if (added_set.size() > 0) {
|
||||
beginInsertRows(QModelIndex(), static_cast<int>(m_resources.size()),
|
||||
static_cast<int>(m_resources.size() + added_set.size() - 1));
|
||||
|
||||
for (auto& added : added_set) {
|
||||
auto res = new_resources[added];
|
||||
m_resources.append(res);
|
||||
resolveResource(m_resources.last().get());
|
||||
}
|
||||
|
||||
endInsertRows();
|
||||
}
|
||||
}
|
||||
|
||||
// update index
|
||||
{
|
||||
m_resources_index.clear();
|
||||
int idx = 0;
|
||||
for (auto const& mod : qAsConst(m_resources)) {
|
||||
m_resources_index[mod->internal_id()] = idx;
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,8 +8,6 @@
|
||||
#include "MTPixmapCache.h"
|
||||
#include "Version.h"
|
||||
|
||||
#include "minecraft/mod/tasks/LocalResourcePackParseTask.h"
|
||||
|
||||
// Values taken from:
|
||||
// https://minecraft.wiki/w/Pack_format#List_of_resource_pack_formats
|
||||
static const QMap<int, std::pair<Version, Version>> s_pack_format_versions = {
|
||||
@@ -31,69 +29,6 @@ static const QMap<int, std::pair<Version, Version>> s_pack_format_versions = {
|
||||
{ 34, { Version("24w21a"), Version("1.21") } }
|
||||
};
|
||||
|
||||
void ResourcePack::setPackFormat(int new_format_id)
|
||||
{
|
||||
QMutexLocker locker(&m_data_lock);
|
||||
|
||||
if (!s_pack_format_versions.contains(new_format_id)) {
|
||||
qWarning() << "Pack format '" << new_format_id << "' is not a recognized resource pack id!";
|
||||
}
|
||||
|
||||
m_pack_format = new_format_id;
|
||||
}
|
||||
|
||||
void ResourcePack::setDescription(QString new_description)
|
||||
{
|
||||
QMutexLocker locker(&m_data_lock);
|
||||
|
||||
m_description = new_description;
|
||||
}
|
||||
|
||||
void ResourcePack::setImage(QImage new_image) const
|
||||
{
|
||||
QMutexLocker locker(&m_data_lock);
|
||||
|
||||
Q_ASSERT(!new_image.isNull());
|
||||
|
||||
if (m_pack_image_cache_key.key.isValid())
|
||||
PixmapCache::instance().remove(m_pack_image_cache_key.key);
|
||||
|
||||
// scale the image to avoid flooding the pixmapcache
|
||||
auto pixmap =
|
||||
QPixmap::fromImage(new_image.scaled({ 64, 64 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding, Qt::SmoothTransformation));
|
||||
|
||||
m_pack_image_cache_key.key = PixmapCache::instance().insert(pixmap);
|
||||
m_pack_image_cache_key.was_ever_used = true;
|
||||
|
||||
// This can happen if the pixmap is too big to fit in the cache :c
|
||||
if (!m_pack_image_cache_key.key.isValid()) {
|
||||
qWarning() << "Could not insert a image cache entry! Ignoring it.";
|
||||
m_pack_image_cache_key.was_ever_used = false;
|
||||
}
|
||||
}
|
||||
|
||||
QPixmap ResourcePack::image(QSize size, Qt::AspectRatioMode mode) const
|
||||
{
|
||||
QPixmap cached_image;
|
||||
if (PixmapCache::instance().find(m_pack_image_cache_key.key, &cached_image)) {
|
||||
if (size.isNull())
|
||||
return cached_image;
|
||||
return cached_image.scaled(size, mode, Qt::SmoothTransformation);
|
||||
}
|
||||
|
||||
// No valid image we can get
|
||||
if (!m_pack_image_cache_key.was_ever_used) {
|
||||
return {};
|
||||
} else {
|
||||
qDebug() << "Resource Pack" << name() << "Had it's image evicted from the cache. reloading...";
|
||||
PixmapCache::markCacheMissByEviciton();
|
||||
}
|
||||
|
||||
// Imaged got evicted from the cache. Re-process it and retry.
|
||||
ResourcePackUtils::processPackPNG(*this);
|
||||
return image(size);
|
||||
}
|
||||
|
||||
std::pair<Version, Version> ResourcePack::compatibleVersions() const
|
||||
{
|
||||
if (!s_pack_format_versions.contains(m_pack_format)) {
|
||||
@@ -102,44 +37,3 @@ std::pair<Version, Version> ResourcePack::compatibleVersions() const
|
||||
|
||||
return s_pack_format_versions.constFind(m_pack_format).value();
|
||||
}
|
||||
|
||||
int ResourcePack::compare(const Resource& other, SortType type) const
|
||||
{
|
||||
auto const& cast_other = static_cast<ResourcePack const&>(other);
|
||||
switch (type) {
|
||||
default:
|
||||
return Resource::compare(other, type);
|
||||
case SortType::PACK_FORMAT: {
|
||||
auto this_ver = packFormat();
|
||||
auto other_ver = cast_other.packFormat();
|
||||
|
||||
if (this_ver > other_ver)
|
||||
return 1;
|
||||
if (this_ver < other_ver)
|
||||
return -1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool ResourcePack::applyFilter(QRegularExpression filter) const
|
||||
{
|
||||
if (filter.match(description()).hasMatch())
|
||||
return true;
|
||||
|
||||
if (filter.match(QString::number(packFormat())).hasMatch())
|
||||
return true;
|
||||
|
||||
if (filter.match(compatibleVersions().first.toString()).hasMatch())
|
||||
return true;
|
||||
if (filter.match(compatibleVersions().second.toString()).hasMatch())
|
||||
return true;
|
||||
|
||||
return Resource::applyFilter(filter);
|
||||
}
|
||||
|
||||
bool ResourcePack::valid() const
|
||||
{
|
||||
return m_pack_format != 0;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "Resource.h"
|
||||
#include "minecraft/mod/DataPack.h"
|
||||
|
||||
#include <QImage>
|
||||
#include <QMutex>
|
||||
@@ -14,58 +15,14 @@ class Version;
|
||||
* Store localized descriptions
|
||||
* */
|
||||
|
||||
class ResourcePack : public Resource {
|
||||
class ResourcePack : public DataPack {
|
||||
Q_OBJECT
|
||||
public:
|
||||
using Ptr = shared_qobject_ptr<Resource>;
|
||||
ResourcePack(QObject* parent = nullptr) : DataPack(parent) {}
|
||||
ResourcePack(QFileInfo file_info) : DataPack(file_info) {}
|
||||
|
||||
ResourcePack(QObject* parent = nullptr) : Resource(parent) {}
|
||||
ResourcePack(QFileInfo file_info) : Resource(file_info) {}
|
||||
|
||||
/** Gets the numerical ID of the pack format. */
|
||||
[[nodiscard]] int packFormat() const { return m_pack_format; }
|
||||
/** Gets, respectively, the lower and upper versions supported by the set pack format. */
|
||||
[[nodiscard]] std::pair<Version, Version> compatibleVersions() const;
|
||||
[[nodiscard]] std::pair<Version, Version> compatibleVersions() const override;
|
||||
|
||||
/** Gets the description of the resource pack. */
|
||||
[[nodiscard]] QString description() const { return m_description; }
|
||||
|
||||
/** Gets the image of the resource pack, converted to a QPixmap for drawing, and scaled to size. */
|
||||
[[nodiscard]] QPixmap image(QSize size, Qt::AspectRatioMode mode = Qt::AspectRatioMode::IgnoreAspectRatio) const;
|
||||
|
||||
/** Thread-safe. */
|
||||
void setPackFormat(int new_format_id);
|
||||
|
||||
/** Thread-safe. */
|
||||
void setDescription(QString new_description);
|
||||
|
||||
/** Thread-safe. */
|
||||
void setImage(QImage new_image) const;
|
||||
|
||||
bool valid() const override;
|
||||
|
||||
[[nodiscard]] int compare(Resource const& other, SortType type) const override;
|
||||
[[nodiscard]] bool applyFilter(QRegularExpression filter) const override;
|
||||
|
||||
protected:
|
||||
mutable QMutex m_data_lock;
|
||||
|
||||
/* The 'version' of a resource pack, as defined in the pack.mcmeta file.
|
||||
* See https://minecraft.wiki/w/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta
|
||||
*/
|
||||
int m_pack_format = 0;
|
||||
|
||||
/** The resource pack's description, as defined in the pack.mcmeta file.
|
||||
*/
|
||||
QString m_description;
|
||||
|
||||
/** The resource pack's image file cache key, for access in the QPixmapCache global instance.
|
||||
*
|
||||
* The 'was_ever_used' state simply identifies whether the key was never inserted on the cache (true),
|
||||
* so as to tell whether a cache entry is inexistent or if it was just evicted from the cache.
|
||||
*/
|
||||
struct {
|
||||
QPixmapCache::Key key;
|
||||
bool was_ever_used = false;
|
||||
} mutable m_pack_image_cache_key;
|
||||
virtual QString directory() { return "/assets"; }
|
||||
};
|
||||
|
||||
@@ -44,19 +44,19 @@
|
||||
#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/LocalDataPackParseTask.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 +73,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 +87,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 +108,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 +130,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 +149,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 +159,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 +167,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,12 +189,7 @@ 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));
|
||||
return new LocalDataPackParseTask(m_next_resolution_ticket, dynamic_cast<ResourcePack*>(&resource));
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <QDir>
|
||||
#include <QMap>
|
||||
#include <QObject>
|
||||
#include <QThread>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "FileSystem.h"
|
||||
#include "minecraft/mod/Resource.h"
|
||||
|
||||
#include "tasks/Task.h"
|
||||
|
||||
/** Very simple task that just loads a folder's contents directly.
|
||||
*/
|
||||
class BasicFolderLoadTask : public Task {
|
||||
Q_OBJECT
|
||||
public:
|
||||
struct Result {
|
||||
QMap<QString, Resource::Ptr> resources;
|
||||
};
|
||||
using ResultPtr = std::shared_ptr<Result>;
|
||||
|
||||
[[nodiscard]] ResultPtr result() const { return m_result; }
|
||||
|
||||
public:
|
||||
BasicFolderLoadTask(QDir dir) : Task(nullptr, false), m_dir(dir), m_result(new Result), m_thread_to_spawn_into(thread())
|
||||
{
|
||||
m_create_func = [](QFileInfo const& entry) -> Resource::Ptr { return makeShared<Resource>(entry); };
|
||||
}
|
||||
BasicFolderLoadTask(QDir dir, std::function<Resource::Ptr(QFileInfo const&)> create_function)
|
||||
: Task(nullptr, false)
|
||||
, m_dir(dir)
|
||||
, m_result(new Result)
|
||||
, m_create_func(std::move(create_function))
|
||||
, m_thread_to_spawn_into(thread())
|
||||
{}
|
||||
|
||||
[[nodiscard]] bool canAbort() const override { return true; }
|
||||
bool abort() override
|
||||
{
|
||||
m_aborted.store(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
void executeTask() override
|
||||
{
|
||||
if (thread() != m_thread_to_spawn_into)
|
||||
connect(this, &Task::finished, this->thread(), &QThread::quit);
|
||||
|
||||
m_dir.refresh();
|
||||
for (auto entry : m_dir.entryInfoList()) {
|
||||
auto filePath = entry.absoluteFilePath();
|
||||
auto newFilePath = FS::getUniqueResourceName(filePath);
|
||||
if (newFilePath != filePath) {
|
||||
FS::move(filePath, newFilePath);
|
||||
entry = QFileInfo(newFilePath);
|
||||
}
|
||||
auto resource = m_create_func(entry);
|
||||
resource->moveToThread(m_thread_to_spawn_into);
|
||||
m_result->resources.insert(resource->internal_id(), resource);
|
||||
}
|
||||
|
||||
if (m_aborted)
|
||||
emit finished();
|
||||
else
|
||||
emitSucceeded();
|
||||
}
|
||||
|
||||
private:
|
||||
QDir m_dir;
|
||||
ResultPtr m_result;
|
||||
|
||||
std::atomic<bool> m_aborted = false;
|
||||
|
||||
std::function<Resource::Ptr(QFileInfo const&)> m_create_func;
|
||||
|
||||
/** This is the thread in which we should put new mod objects */
|
||||
QThread* m_thread_to_spawn_into;
|
||||
};
|
||||
@@ -52,11 +52,10 @@ static bool checkDependencies(std::shared_ptr<GetModDependenciesTask::PackDepend
|
||||
(!loaders || !sel->version.loaders || sel->version.loaders & loaders);
|
||||
}
|
||||
|
||||
GetModDependenciesTask::GetModDependenciesTask(QObject* parent,
|
||||
BaseInstance* instance,
|
||||
GetModDependenciesTask::GetModDependenciesTask(BaseInstance* instance,
|
||||
ModFolderModel* folder,
|
||||
QList<std::shared_ptr<PackDependency>> selected)
|
||||
: SequentialTask(parent, tr("Get dependencies"))
|
||||
: SequentialTask(tr("Get dependencies"))
|
||||
, m_selected(selected)
|
||||
, m_flame_provider{ ModPlatform::ResourceProvider::FLAME, std::make_shared<ResourceDownload::FlameModModel>(*instance),
|
||||
std::make_shared<FlameAPI>() }
|
||||
@@ -185,7 +184,7 @@ Task::Ptr GetModDependenciesTask::prepareDependencyTask(const ModPlatform::Depen
|
||||
auto provider = providerName == m_flame_provider.name ? m_flame_provider : m_modrinth_provider;
|
||||
|
||||
auto tasks = makeShared<SequentialTask>(
|
||||
this, QString("DependencyInfo: %1").arg(dep.addonId.toString().isEmpty() ? dep.version : dep.addonId.toString()));
|
||||
QString("DependencyInfo: %1").arg(dep.addonId.toString().isEmpty() ? dep.version : dep.addonId.toString()));
|
||||
|
||||
if (!dep.addonId.toString().isEmpty()) {
|
||||
tasks->addTask(getProjectInfoTask(pDep));
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <QDir>
|
||||
#include <QEventLoop>
|
||||
#include <QList>
|
||||
#include <QVariant>
|
||||
#include <functional>
|
||||
@@ -61,10 +60,7 @@ class GetModDependenciesTask : public SequentialTask {
|
||||
std::shared_ptr<ResourceAPI> api;
|
||||
};
|
||||
|
||||
explicit GetModDependenciesTask(QObject* parent,
|
||||
BaseInstance* instance,
|
||||
ModFolderModel* folder,
|
||||
QList<std::shared_ptr<PackDependency>> selected);
|
||||
explicit GetModDependenciesTask(BaseInstance* instance, ModFolderModel* folder, QList<std::shared_ptr<PackDependency>> selected);
|
||||
|
||||
auto getDependecies() const -> QList<std::shared_ptr<PackDependency>> { return m_pack_dependencies; }
|
||||
QHash<QString, PackDependencyExtraInfo> getExtraInfo();
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
|
||||
#include "FileSystem.h"
|
||||
#include "Json.h"
|
||||
#include "minecraft/mod/ResourcePack.h"
|
||||
|
||||
#include <quazip/quazip.h>
|
||||
#include <quazip/quazipdir.h>
|
||||
@@ -32,9 +33,9 @@
|
||||
|
||||
namespace DataPackUtils {
|
||||
|
||||
bool process(DataPack& pack, ProcessingLevel level)
|
||||
bool process(DataPack* pack, ProcessingLevel level)
|
||||
{
|
||||
switch (pack.type()) {
|
||||
switch (pack->type()) {
|
||||
case ResourceType::FOLDER:
|
||||
return DataPackUtils::processFolder(pack, level);
|
||||
case ResourceType::ZIPFILE:
|
||||
@@ -45,16 +46,16 @@ bool process(DataPack& pack, ProcessingLevel level)
|
||||
}
|
||||
}
|
||||
|
||||
bool processFolder(DataPack& pack, ProcessingLevel level)
|
||||
bool processFolder(DataPack* pack, ProcessingLevel level)
|
||||
{
|
||||
Q_ASSERT(pack.type() == ResourceType::FOLDER);
|
||||
Q_ASSERT(pack->type() == ResourceType::FOLDER);
|
||||
|
||||
auto mcmeta_invalid = [&pack]() {
|
||||
qWarning() << "Data pack at" << pack.fileinfo().filePath() << "does not have a valid pack.mcmeta";
|
||||
qWarning() << "Data pack at" << pack->fileinfo().filePath() << "does not have a valid pack.mcmeta";
|
||||
return false; // the mcmeta is not optional
|
||||
};
|
||||
|
||||
QFileInfo mcmeta_file_info(FS::PathCombine(pack.fileinfo().filePath(), "pack.mcmeta"));
|
||||
QFileInfo mcmeta_file_info(FS::PathCombine(pack->fileinfo().filePath(), "pack.mcmeta"));
|
||||
if (mcmeta_file_info.exists() && mcmeta_file_info.isFile()) {
|
||||
QFile mcmeta_file(mcmeta_file_info.filePath());
|
||||
if (!mcmeta_file.open(QIODevice::ReadOnly))
|
||||
@@ -72,7 +73,7 @@ bool processFolder(DataPack& pack, ProcessingLevel level)
|
||||
return mcmeta_invalid(); // mcmeta file isn't a valid file
|
||||
}
|
||||
|
||||
QFileInfo data_dir_info(FS::PathCombine(pack.fileinfo().filePath(), "data"));
|
||||
QFileInfo data_dir_info(FS::PathCombine(pack->fileinfo().filePath(), pack->directory()));
|
||||
if (!data_dir_info.exists() || !data_dir_info.isDir()) {
|
||||
return false; // data dir does not exists or isn't valid
|
||||
}
|
||||
@@ -80,13 +81,12 @@ bool processFolder(DataPack& pack, ProcessingLevel level)
|
||||
if (level == ProcessingLevel::BasicInfoOnly) {
|
||||
return true; // only need basic info already checked
|
||||
}
|
||||
|
||||
auto png_invalid = [&pack]() {
|
||||
qWarning() << "Data pack at" << pack.fileinfo().filePath() << "does not have a valid pack.png";
|
||||
qWarning() << "Data pack at" << pack->fileinfo().filePath() << "does not have a valid pack.png";
|
||||
return true; // the png is optional
|
||||
};
|
||||
|
||||
QFileInfo image_file_info(FS::PathCombine(pack.fileinfo().filePath(), "pack.png"));
|
||||
QFileInfo image_file_info(FS::PathCombine(pack->fileinfo().filePath(), "pack.png"));
|
||||
if (image_file_info.exists() && image_file_info.isFile()) {
|
||||
QFile pack_png_file(image_file_info.filePath());
|
||||
if (!pack_png_file.open(QIODevice::ReadOnly))
|
||||
@@ -107,18 +107,18 @@ bool processFolder(DataPack& pack, ProcessingLevel level)
|
||||
return true; // all tests passed
|
||||
}
|
||||
|
||||
bool processZIP(DataPack& pack, ProcessingLevel level)
|
||||
bool processZIP(DataPack* pack, ProcessingLevel level)
|
||||
{
|
||||
Q_ASSERT(pack.type() == ResourceType::ZIPFILE);
|
||||
Q_ASSERT(pack->type() == ResourceType::ZIPFILE);
|
||||
|
||||
QuaZip zip(pack.fileinfo().filePath());
|
||||
QuaZip zip(pack->fileinfo().filePath());
|
||||
if (!zip.open(QuaZip::mdUnzip))
|
||||
return false; // can't open zip file
|
||||
|
||||
QuaZipFile file(&zip);
|
||||
|
||||
auto mcmeta_invalid = [&pack]() {
|
||||
qWarning() << "Data pack at" << pack.fileinfo().filePath() << "does not have a valid pack.mcmeta";
|
||||
qWarning() << "Data pack at" << pack->fileinfo().filePath() << "does not have a valid pack.mcmeta";
|
||||
return false; // the mcmeta is not optional
|
||||
};
|
||||
|
||||
@@ -142,7 +142,7 @@ bool processZIP(DataPack& pack, ProcessingLevel level)
|
||||
}
|
||||
|
||||
QuaZipDir zipDir(&zip);
|
||||
if (!zipDir.exists("/data")) {
|
||||
if (!zipDir.exists(pack->directory())) {
|
||||
return false; // data dir does not exists at zip root
|
||||
}
|
||||
|
||||
@@ -152,7 +152,7 @@ bool processZIP(DataPack& pack, ProcessingLevel level)
|
||||
}
|
||||
|
||||
auto png_invalid = [&pack]() {
|
||||
qWarning() << "Data pack at" << pack.fileinfo().filePath() << "does not have a valid pack.png";
|
||||
qWarning() << "Data pack at" << pack->fileinfo().filePath() << "does not have a valid pack.png";
|
||||
return true; // the png is optional
|
||||
};
|
||||
|
||||
@@ -176,21 +176,22 @@ bool processZIP(DataPack& pack, ProcessingLevel level)
|
||||
zip.close();
|
||||
return png_invalid(); // could not set pack.mcmeta as current file.
|
||||
}
|
||||
|
||||
zip.close();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// https://minecraft.wiki/w/Data_pack#pack.mcmeta
|
||||
bool processMCMeta(DataPack& pack, QByteArray&& raw_data)
|
||||
// https://minecraft.wiki/w/Raw_JSON_text_format
|
||||
// https://minecraft.wiki/w/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta
|
||||
bool processMCMeta(DataPack* pack, QByteArray&& raw_data)
|
||||
{
|
||||
try {
|
||||
auto json_doc = QJsonDocument::fromJson(raw_data);
|
||||
auto pack_obj = Json::requireObject(json_doc.object(), "pack", {});
|
||||
|
||||
pack.setPackFormat(Json::ensureInteger(pack_obj, "pack_format", 0));
|
||||
pack.setDescription(Json::ensureString(pack_obj, "description", ""));
|
||||
pack->setPackFormat(Json::ensureInteger(pack_obj, "pack_format", 0));
|
||||
pack->setDescription(DataPackUtils::processComponent(pack_obj.value("description")));
|
||||
} catch (Json::JsonException& e) {
|
||||
qWarning() << "JsonException: " << e.what() << e.cause();
|
||||
return false;
|
||||
@@ -198,11 +199,92 @@ bool processMCMeta(DataPack& pack, QByteArray&& raw_data)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool processPackPNG(const DataPack& pack, QByteArray&& raw_data)
|
||||
QString buildStyle(const QJsonObject& obj)
|
||||
{
|
||||
QStringList styles;
|
||||
if (auto color = Json::ensureString(obj, "color"); !color.isEmpty()) {
|
||||
styles << QString("color: %1;").arg(color);
|
||||
}
|
||||
if (obj.contains("bold")) {
|
||||
QString weight = "normal";
|
||||
if (Json::ensureBoolean(obj, "bold", false)) {
|
||||
weight = "bold";
|
||||
}
|
||||
styles << QString("font-weight: %1;").arg(weight);
|
||||
}
|
||||
if (obj.contains("italic")) {
|
||||
QString style = "normal";
|
||||
if (Json::ensureBoolean(obj, "italic", false)) {
|
||||
style = "italic";
|
||||
}
|
||||
styles << QString("font-style: %1;").arg(style);
|
||||
}
|
||||
|
||||
return styles.isEmpty() ? "" : QString("style=\"%1\"").arg(styles.join(" "));
|
||||
}
|
||||
|
||||
QString processComponent(const QJsonArray& value, bool strikethrough, bool underline)
|
||||
{
|
||||
QString result;
|
||||
for (auto current : value)
|
||||
result += processComponent(current, strikethrough, underline);
|
||||
return result;
|
||||
}
|
||||
|
||||
QString processComponent(const QJsonObject& obj, bool strikethrough, bool underline)
|
||||
{
|
||||
underline = Json::ensureBoolean(obj, "underlined", underline);
|
||||
strikethrough = Json::ensureBoolean(obj, "strikethrough", strikethrough);
|
||||
|
||||
QString result = Json::ensureString(obj, "text");
|
||||
if (underline) {
|
||||
result = QString("<u>%1</u>").arg(result);
|
||||
}
|
||||
if (strikethrough) {
|
||||
result = QString("<s>%1</s>").arg(result);
|
||||
}
|
||||
// the extra needs to be a array
|
||||
result += processComponent(Json::ensureArray(obj, "extra"), strikethrough, underline);
|
||||
if (auto style = buildStyle(obj); !style.isEmpty()) {
|
||||
result = QString("<span %1>%2</span>").arg(style, result);
|
||||
}
|
||||
if (obj.contains("clickEvent")) {
|
||||
auto click_event = Json::ensureObject(obj, "clickEvent");
|
||||
auto action = Json::ensureString(click_event, "action");
|
||||
auto value = Json::ensureString(click_event, "value");
|
||||
if (action == "open_url" && !value.isEmpty()) {
|
||||
result = QString("<a href=\"%1\">%2</a>").arg(value, result);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
QString processComponent(const QJsonValue& value, bool strikethrough, bool underline)
|
||||
{
|
||||
if (value.isString()) {
|
||||
return value.toString();
|
||||
}
|
||||
if (value.isBool()) {
|
||||
return value.toBool() ? "true" : "false";
|
||||
}
|
||||
if (value.isDouble()) {
|
||||
return QString::number(value.toDouble());
|
||||
}
|
||||
if (value.isArray()) {
|
||||
return processComponent(value.toArray(), strikethrough, underline);
|
||||
}
|
||||
if (value.isObject()) {
|
||||
return processComponent(value.toObject(), strikethrough, underline);
|
||||
}
|
||||
qWarning() << "Invalid component type!";
|
||||
return {};
|
||||
}
|
||||
|
||||
bool processPackPNG(const DataPack* pack, QByteArray&& raw_data)
|
||||
{
|
||||
auto img = QImage::fromData(raw_data);
|
||||
if (!img.isNull()) {
|
||||
pack.setImage(img);
|
||||
pack->setImage(img);
|
||||
} else {
|
||||
qWarning() << "Failed to parse pack.png.";
|
||||
return false;
|
||||
@@ -210,16 +292,16 @@ bool processPackPNG(const DataPack& pack, QByteArray&& raw_data)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool processPackPNG(const DataPack& pack)
|
||||
bool processPackPNG(const DataPack* pack)
|
||||
{
|
||||
auto png_invalid = [&pack]() {
|
||||
qWarning() << "Data pack at" << pack.fileinfo().filePath() << "does not have a valid pack.png";
|
||||
qWarning() << "Data pack at" << pack->fileinfo().filePath() << "does not have a valid pack.png";
|
||||
return false;
|
||||
};
|
||||
|
||||
switch (pack.type()) {
|
||||
switch (pack->type()) {
|
||||
case ResourceType::FOLDER: {
|
||||
QFileInfo image_file_info(FS::PathCombine(pack.fileinfo().filePath(), "pack.png"));
|
||||
QFileInfo image_file_info(FS::PathCombine(pack->fileinfo().filePath(), "pack.png"));
|
||||
if (image_file_info.exists() && image_file_info.isFile()) {
|
||||
QFile pack_png_file(image_file_info.filePath());
|
||||
if (!pack_png_file.open(QIODevice::ReadOnly))
|
||||
@@ -239,7 +321,7 @@ bool processPackPNG(const DataPack& pack)
|
||||
return false; // not processed correctly; https://github.com/PrismLauncher/PrismLauncher/issues/1740
|
||||
}
|
||||
case ResourceType::ZIPFILE: {
|
||||
QuaZip zip(pack.fileinfo().filePath());
|
||||
QuaZip zip(pack->fileinfo().filePath());
|
||||
if (!zip.open(QuaZip::mdUnzip))
|
||||
return false; // can't open zip file
|
||||
|
||||
@@ -273,26 +355,25 @@ bool processPackPNG(const DataPack& pack)
|
||||
bool validate(QFileInfo file)
|
||||
{
|
||||
DataPack dp{ file };
|
||||
return DataPackUtils::process(dp, ProcessingLevel::BasicInfoOnly) && dp.valid();
|
||||
return DataPackUtils::process(&dp, ProcessingLevel::BasicInfoOnly) && dp.valid();
|
||||
}
|
||||
|
||||
bool validateResourcePack(QFileInfo file)
|
||||
{
|
||||
ResourcePack rp{ file };
|
||||
return DataPackUtils::process(&rp, ProcessingLevel::BasicInfoOnly) && rp.valid();
|
||||
}
|
||||
|
||||
} // namespace DataPackUtils
|
||||
|
||||
LocalDataPackParseTask::LocalDataPackParseTask(int token, DataPack& dp) : Task(nullptr, false), m_token(token), m_data_pack(dp) {}
|
||||
|
||||
bool LocalDataPackParseTask::abort()
|
||||
{
|
||||
m_aborted = true;
|
||||
return true;
|
||||
}
|
||||
LocalDataPackParseTask::LocalDataPackParseTask(int token, DataPack* dp) : Task(false), m_token(token), m_data_pack(dp) {}
|
||||
|
||||
void LocalDataPackParseTask::executeTask()
|
||||
{
|
||||
if (!DataPackUtils::process(m_data_pack))
|
||||
if (!DataPackUtils::process(m_data_pack)) {
|
||||
emitFailed("process failed");
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_aborted)
|
||||
emitAborted();
|
||||
else
|
||||
emitSucceeded();
|
||||
}
|
||||
emitSucceeded();
|
||||
}
|
||||
@@ -32,29 +32,32 @@ namespace DataPackUtils {
|
||||
|
||||
enum class ProcessingLevel { Full, BasicInfoOnly };
|
||||
|
||||
bool process(DataPack& pack, ProcessingLevel level = ProcessingLevel::Full);
|
||||
bool process(DataPack* pack, ProcessingLevel level = ProcessingLevel::Full);
|
||||
|
||||
bool processZIP(DataPack& pack, ProcessingLevel level = ProcessingLevel::Full);
|
||||
bool processFolder(DataPack& pack, ProcessingLevel level = ProcessingLevel::Full);
|
||||
bool processZIP(DataPack* pack, ProcessingLevel level = ProcessingLevel::Full);
|
||||
bool processFolder(DataPack* pack, ProcessingLevel level = ProcessingLevel::Full);
|
||||
|
||||
bool processMCMeta(DataPack& pack, QByteArray&& raw_data);
|
||||
bool processPackPNG(const DataPack& pack, QByteArray&& raw_data);
|
||||
bool processMCMeta(DataPack* pack, QByteArray&& raw_data);
|
||||
|
||||
QString processComponent(const QJsonValue& value, bool strikethrough = false, bool underline = false);
|
||||
|
||||
bool processPackPNG(const DataPack* pack, QByteArray&& raw_data);
|
||||
|
||||
/// processes ONLY the pack.png (rest of the pack may be invalid)
|
||||
bool processPackPNG(const DataPack& pack);
|
||||
bool processPackPNG(const DataPack* pack);
|
||||
|
||||
/** Checks whether a file is valid as a data pack or not. */
|
||||
bool validate(QFileInfo file);
|
||||
|
||||
/** Checks whether a file is valid as a resource pack or not. */
|
||||
bool validateResourcePack(QFileInfo file);
|
||||
|
||||
} // namespace DataPackUtils
|
||||
|
||||
class LocalDataPackParseTask : public Task {
|
||||
Q_OBJECT
|
||||
public:
|
||||
LocalDataPackParseTask(int token, DataPack& dp);
|
||||
|
||||
[[nodiscard]] bool canAbort() const override { return true; }
|
||||
bool abort() override;
|
||||
LocalDataPackParseTask(int token, DataPack* dp);
|
||||
|
||||
void executeTask() override;
|
||||
|
||||
@@ -63,7 +66,5 @@ class LocalDataPackParseTask : public Task {
|
||||
private:
|
||||
int m_token;
|
||||
|
||||
DataPack& m_data_pack;
|
||||
|
||||
bool m_aborted = false;
|
||||
};
|
||||
DataPack* m_data_pack;
|
||||
};
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonValue>
|
||||
#include <QRegularExpression>
|
||||
#include <QString>
|
||||
|
||||
#include "FileSystem.h"
|
||||
@@ -15,6 +16,8 @@
|
||||
#include "minecraft/mod/ModDetails.h"
|
||||
#include "settings/INIFile.h"
|
||||
|
||||
static QRegularExpression newlineRegex("\r\n|\n|\r");
|
||||
|
||||
namespace ModUtils {
|
||||
|
||||
// NEW format
|
||||
@@ -24,7 +27,7 @@ namespace ModUtils {
|
||||
// https://github.com/MinecraftForge/FML/wiki/FML-mod-information-file/5bf6a2d05145ec79387acc0d45c958642fb049fc
|
||||
ModDetails ReadMCModInfo(QByteArray contents)
|
||||
{
|
||||
auto getInfoFromArray = [&](QJsonArray arr) -> ModDetails {
|
||||
auto getInfoFromArray = [](QJsonArray arr) -> ModDetails {
|
||||
if (!arr.at(0).isObject()) {
|
||||
return {};
|
||||
}
|
||||
@@ -290,86 +293,90 @@ ModDetails ReadFabricModInfo(QByteArray contents)
|
||||
// https://github.com/QuiltMC/rfcs/blob/master/specification/0002-quilt.mod.json.md
|
||||
ModDetails ReadQuiltModInfo(QByteArray contents)
|
||||
{
|
||||
QJsonParseError jsonError;
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(contents, &jsonError);
|
||||
auto object = Json::requireObject(jsonDoc, "quilt.mod.json");
|
||||
auto schemaVersion = Json::ensureInteger(object.value("schema_version"), 0, "Quilt schema_version");
|
||||
|
||||
ModDetails details;
|
||||
try {
|
||||
QJsonParseError jsonError;
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(contents, &jsonError);
|
||||
auto object = Json::requireObject(jsonDoc, "quilt.mod.json");
|
||||
auto schemaVersion = Json::ensureInteger(object.value("schema_version"), 0, "Quilt schema_version");
|
||||
|
||||
// https://github.com/QuiltMC/rfcs/blob/be6ba280d785395fefa90a43db48e5bfc1d15eb4/specification/0002-quilt.mod.json.md
|
||||
if (schemaVersion == 1) {
|
||||
auto modInfo = Json::requireObject(object.value("quilt_loader"), "Quilt mod info");
|
||||
// https://github.com/QuiltMC/rfcs/blob/be6ba280d785395fefa90a43db48e5bfc1d15eb4/specification/0002-quilt.mod.json.md
|
||||
if (schemaVersion == 1) {
|
||||
auto modInfo = Json::requireObject(object.value("quilt_loader"), "Quilt mod info");
|
||||
|
||||
details.mod_id = Json::requireString(modInfo.value("id"), "Mod ID");
|
||||
details.version = Json::requireString(modInfo.value("version"), "Mod version");
|
||||
details.mod_id = Json::requireString(modInfo.value("id"), "Mod ID");
|
||||
details.version = Json::requireString(modInfo.value("version"), "Mod version");
|
||||
|
||||
auto modMetadata = Json::ensureObject(modInfo.value("metadata"));
|
||||
auto modMetadata = Json::ensureObject(modInfo.value("metadata"));
|
||||
|
||||
details.name = Json::ensureString(modMetadata.value("name"), details.mod_id);
|
||||
details.description = Json::ensureString(modMetadata.value("description"));
|
||||
details.name = Json::ensureString(modMetadata.value("name"), details.mod_id);
|
||||
details.description = Json::ensureString(modMetadata.value("description"));
|
||||
|
||||
auto modContributors = Json::ensureObject(modMetadata.value("contributors"));
|
||||
auto modContributors = Json::ensureObject(modMetadata.value("contributors"));
|
||||
|
||||
// We don't really care about the role of a contributor here
|
||||
details.authors += modContributors.keys();
|
||||
// We don't really care about the role of a contributor here
|
||||
details.authors += modContributors.keys();
|
||||
|
||||
auto modContact = Json::ensureObject(modMetadata.value("contact"));
|
||||
auto modContact = Json::ensureObject(modMetadata.value("contact"));
|
||||
|
||||
if (modContact.contains("homepage")) {
|
||||
details.homeurl = Json::requireString(modContact.value("homepage"));
|
||||
}
|
||||
if (modContact.contains("issues")) {
|
||||
details.issue_tracker = Json::requireString(modContact.value("issues"));
|
||||
}
|
||||
if (modContact.contains("homepage")) {
|
||||
details.homeurl = Json::requireString(modContact.value("homepage"));
|
||||
}
|
||||
if (modContact.contains("issues")) {
|
||||
details.issue_tracker = Json::requireString(modContact.value("issues"));
|
||||
}
|
||||
|
||||
if (modMetadata.contains("license")) {
|
||||
auto license = modMetadata.value("license");
|
||||
if (license.isArray()) {
|
||||
for (auto l : license.toArray()) {
|
||||
if (l.isString()) {
|
||||
details.licenses.append(ModLicense(l.toString()));
|
||||
} else if (l.isObject()) {
|
||||
auto obj = l.toObject();
|
||||
details.licenses.append(ModLicense(obj.value("name").toString(), obj.value("id").toString(),
|
||||
obj.value("url").toString(), obj.value("description").toString()));
|
||||
if (modMetadata.contains("license")) {
|
||||
auto license = modMetadata.value("license");
|
||||
if (license.isArray()) {
|
||||
for (auto l : license.toArray()) {
|
||||
if (l.isString()) {
|
||||
details.licenses.append(ModLicense(l.toString()));
|
||||
} else if (l.isObject()) {
|
||||
auto obj = l.toObject();
|
||||
details.licenses.append(ModLicense(obj.value("name").toString(), obj.value("id").toString(),
|
||||
obj.value("url").toString(), obj.value("description").toString()));
|
||||
}
|
||||
}
|
||||
} else if (license.isString()) {
|
||||
details.licenses.append(ModLicense(license.toString()));
|
||||
} else if (license.isObject()) {
|
||||
auto obj = license.toObject();
|
||||
details.licenses.append(ModLicense(obj.value("name").toString(), obj.value("id").toString(),
|
||||
obj.value("url").toString(), obj.value("description").toString()));
|
||||
}
|
||||
}
|
||||
|
||||
if (modMetadata.contains("icon")) {
|
||||
auto icon = modMetadata.value("icon");
|
||||
if (icon.isObject()) {
|
||||
auto obj = icon.toObject();
|
||||
// take the largest icon
|
||||
int largest = 0;
|
||||
for (auto key : obj.keys()) {
|
||||
auto size = key.split('x').first().toInt();
|
||||
if (size > largest) {
|
||||
largest = size;
|
||||
}
|
||||
}
|
||||
if (largest > 0) {
|
||||
auto key = QString::number(largest) + "x" + QString::number(largest);
|
||||
details.icon_file = obj.value(key).toString();
|
||||
} else { // parsing the sizes failed
|
||||
// take the first
|
||||
for (auto i : obj) {
|
||||
details.icon_file = i.toString();
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (icon.isString()) {
|
||||
details.icon_file = icon.toString();
|
||||
}
|
||||
} else if (license.isString()) {
|
||||
details.licenses.append(ModLicense(license.toString()));
|
||||
} else if (license.isObject()) {
|
||||
auto obj = license.toObject();
|
||||
details.licenses.append(ModLicense(obj.value("name").toString(), obj.value("id").toString(), obj.value("url").toString(),
|
||||
obj.value("description").toString()));
|
||||
}
|
||||
}
|
||||
|
||||
if (modMetadata.contains("icon")) {
|
||||
auto icon = modMetadata.value("icon");
|
||||
if (icon.isObject()) {
|
||||
auto obj = icon.toObject();
|
||||
// take the largest icon
|
||||
int largest = 0;
|
||||
for (auto key : obj.keys()) {
|
||||
auto size = key.split('x').first().toInt();
|
||||
if (size > largest) {
|
||||
largest = size;
|
||||
}
|
||||
}
|
||||
if (largest > 0) {
|
||||
auto key = QString::number(largest) + "x" + QString::number(largest);
|
||||
details.icon_file = obj.value(key).toString();
|
||||
} else { // parsing the sizes failed
|
||||
// take the first
|
||||
for (auto i : obj) {
|
||||
details.icon_file = i.toString();
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (icon.isString()) {
|
||||
details.icon_file = icon.toString();
|
||||
}
|
||||
}
|
||||
} catch (const Exception& e) {
|
||||
qWarning() << "Unable to parse mod info:" << e.cause();
|
||||
}
|
||||
return details;
|
||||
}
|
||||
@@ -487,11 +494,11 @@ bool processZIP(Mod& mod, [[maybe_unused]] ProcessingLevel level)
|
||||
}
|
||||
|
||||
// quick and dirty line-by-line parser
|
||||
auto manifestLines = file.readAll().split('\n');
|
||||
auto manifestLines = QString(file.readAll()).split(newlineRegex);
|
||||
QString manifestVersion = "";
|
||||
for (auto& line : manifestLines) {
|
||||
if (QString(line).startsWith("Implementation-Version: ")) {
|
||||
manifestVersion = QString(line).remove("Implementation-Version: ");
|
||||
if (line.startsWith("Implementation-Version: ", Qt::CaseInsensitive)) {
|
||||
manifestVersion = line.remove("Implementation-Version: ", Qt::CaseInsensitive);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -647,11 +654,11 @@ bool validate(QFileInfo file)
|
||||
return ModUtils::process(mod, ProcessingLevel::BasicInfoOnly) && mod.valid();
|
||||
}
|
||||
|
||||
bool processIconPNG(const Mod& mod, QByteArray&& raw_data)
|
||||
bool processIconPNG(const Mod& mod, QByteArray&& raw_data, QPixmap* pixmap)
|
||||
{
|
||||
auto img = QImage::fromData(raw_data);
|
||||
if (!img.isNull()) {
|
||||
mod.setIcon(img);
|
||||
*pixmap = mod.setIcon(img);
|
||||
} else {
|
||||
qWarning() << "Failed to parse mod logo:" << mod.iconPath() << "from" << mod.name();
|
||||
return false;
|
||||
@@ -659,15 +666,15 @@ bool processIconPNG(const Mod& mod, QByteArray&& raw_data)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool loadIconFile(const Mod& mod)
|
||||
bool loadIconFile(const Mod& mod, QPixmap* pixmap)
|
||||
{
|
||||
if (mod.iconPath().isEmpty()) {
|
||||
qWarning() << "No Iconfile set, be sure to parse the mod first";
|
||||
return false;
|
||||
}
|
||||
|
||||
auto png_invalid = [&mod]() {
|
||||
qWarning() << "Mod at" << mod.fileinfo().filePath() << "does not have a valid icon";
|
||||
auto png_invalid = [&mod](const QString& reason) {
|
||||
qWarning() << "Mod at" << mod.fileinfo().filePath() << "does not have a valid icon:" << reason;
|
||||
return false;
|
||||
};
|
||||
|
||||
@@ -676,24 +683,26 @@ bool loadIconFile(const Mod& mod)
|
||||
QFileInfo icon_info(FS::PathCombine(mod.fileinfo().filePath(), mod.iconPath()));
|
||||
if (icon_info.exists() && icon_info.isFile()) {
|
||||
QFile icon(icon_info.filePath());
|
||||
if (!icon.open(QIODevice::ReadOnly))
|
||||
return false;
|
||||
if (!icon.open(QIODevice::ReadOnly)) {
|
||||
return png_invalid("failed to open file " + icon_info.filePath());
|
||||
}
|
||||
auto data = icon.readAll();
|
||||
|
||||
bool icon_result = ModUtils::processIconPNG(mod, std::move(data));
|
||||
bool icon_result = ModUtils::processIconPNG(mod, std::move(data), pixmap);
|
||||
|
||||
icon.close();
|
||||
|
||||
if (!icon_result) {
|
||||
return png_invalid(); // icon invalid
|
||||
return png_invalid("invalid png image"); // icon invalid
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return png_invalid("file '" + icon_info.filePath() + "' does not exists or is not a file");
|
||||
}
|
||||
case ResourceType::ZIPFILE: {
|
||||
QuaZip zip(mod.fileinfo().filePath());
|
||||
if (!zip.open(QuaZip::mdUnzip))
|
||||
return false;
|
||||
return png_invalid("failed to open '" + mod.fileinfo().filePath() + "' as a zip archive");
|
||||
|
||||
QuaZipFile file(&zip);
|
||||
|
||||
@@ -701,35 +710,34 @@ bool loadIconFile(const Mod& mod)
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
qCritical() << "Failed to open file in zip.";
|
||||
zip.close();
|
||||
return png_invalid();
|
||||
return png_invalid("Failed to open '" + mod.iconPath() + "' in zip archive");
|
||||
}
|
||||
|
||||
auto data = file.readAll();
|
||||
|
||||
bool icon_result = ModUtils::processIconPNG(mod, std::move(data));
|
||||
bool icon_result = ModUtils::processIconPNG(mod, std::move(data), pixmap);
|
||||
|
||||
file.close();
|
||||
if (!icon_result) {
|
||||
return png_invalid(); // icon png invalid
|
||||
return png_invalid("invalid png image"); // icon png invalid
|
||||
}
|
||||
} else {
|
||||
return png_invalid(); // could not set icon as current file.
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return png_invalid("Failed to set '" + mod.iconPath() +
|
||||
"' as current file in zip archive"); // could not set icon as current file.
|
||||
}
|
||||
case ResourceType::LITEMOD: {
|
||||
return false; // can lightmods even have icons?
|
||||
return png_invalid("litemods do not have icons"); // can lightmods even have icons?
|
||||
}
|
||||
default:
|
||||
qWarning() << "Invalid type for mod, can not load icon.";
|
||||
return false;
|
||||
return png_invalid("Invalid type for mod, can not load icon.");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ModUtils
|
||||
|
||||
LocalModParseTask::LocalModParseTask(int token, ResourceType type, const QFileInfo& modFile)
|
||||
: Task(nullptr, false), m_token(token), m_type(type), m_modFile(modFile), m_result(new Result())
|
||||
: Task(false), m_token(token), m_type(type), m_modFile(modFile), m_result(new Result())
|
||||
{}
|
||||
|
||||
bool LocalModParseTask::abort()
|
||||
|
||||
@@ -26,8 +26,8 @@ bool processLitemod(Mod& mod, ProcessingLevel level = ProcessingLevel::Full);
|
||||
/** Checks whether a file is valid as a mod or not. */
|
||||
bool validate(QFileInfo file);
|
||||
|
||||
bool processIconPNG(const Mod& mod, QByteArray&& raw_data);
|
||||
bool loadIconFile(const Mod& mod);
|
||||
bool processIconPNG(const Mod& mod, QByteArray&& raw_data, QPixmap* pixmap);
|
||||
bool loadIconFile(const Mod& mod, QPixmap* pixmap);
|
||||
} // namespace ModUtils
|
||||
|
||||
class LocalModParseTask : public Task {
|
||||
@@ -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;
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
|
||||
#include "FileSystem.h"
|
||||
#include "Json.h"
|
||||
#include "minecraft/mod/tasks/LocalDataPackParseTask.h"
|
||||
|
||||
#include <quazip/quazip.h>
|
||||
#include <quazip/quazipdir.h>
|
||||
@@ -29,155 +30,6 @@
|
||||
|
||||
namespace ResourcePackUtils {
|
||||
|
||||
bool process(ResourcePack& pack, ProcessingLevel level)
|
||||
{
|
||||
switch (pack.type()) {
|
||||
case ResourceType::FOLDER:
|
||||
return ResourcePackUtils::processFolder(pack, level);
|
||||
case ResourceType::ZIPFILE:
|
||||
return ResourcePackUtils::processZIP(pack, level);
|
||||
default:
|
||||
qWarning() << "Invalid type for resource pack parse task!";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool processFolder(ResourcePack& pack, ProcessingLevel level)
|
||||
{
|
||||
Q_ASSERT(pack.type() == ResourceType::FOLDER);
|
||||
|
||||
auto mcmeta_invalid = [&pack]() {
|
||||
qWarning() << "Resource pack at" << pack.fileinfo().filePath() << "does not have a valid pack.mcmeta";
|
||||
return false; // the mcmeta is not optional
|
||||
};
|
||||
|
||||
QFileInfo mcmeta_file_info(FS::PathCombine(pack.fileinfo().filePath(), "pack.mcmeta"));
|
||||
if (mcmeta_file_info.exists() && mcmeta_file_info.isFile()) {
|
||||
QFile mcmeta_file(mcmeta_file_info.filePath());
|
||||
if (!mcmeta_file.open(QIODevice::ReadOnly))
|
||||
return mcmeta_invalid(); // can't open mcmeta file
|
||||
|
||||
auto data = mcmeta_file.readAll();
|
||||
|
||||
bool mcmeta_result = ResourcePackUtils::processMCMeta(pack, std::move(data));
|
||||
|
||||
mcmeta_file.close();
|
||||
if (!mcmeta_result) {
|
||||
return mcmeta_invalid(); // mcmeta invalid
|
||||
}
|
||||
} else {
|
||||
return mcmeta_invalid(); // mcmeta file isn't a valid file
|
||||
}
|
||||
|
||||
QFileInfo assets_dir_info(FS::PathCombine(pack.fileinfo().filePath(), "assets"));
|
||||
if (!assets_dir_info.exists() || !assets_dir_info.isDir()) {
|
||||
return false; // assets dir does not exists or isn't valid
|
||||
}
|
||||
|
||||
if (level == ProcessingLevel::BasicInfoOnly) {
|
||||
return true; // only need basic info already checked
|
||||
}
|
||||
|
||||
auto png_invalid = [&pack]() {
|
||||
qWarning() << "Resource pack at" << pack.fileinfo().filePath() << "does not have a valid pack.png";
|
||||
return true; // the png is optional
|
||||
};
|
||||
|
||||
QFileInfo image_file_info(FS::PathCombine(pack.fileinfo().filePath(), "pack.png"));
|
||||
if (image_file_info.exists() && image_file_info.isFile()) {
|
||||
QFile pack_png_file(image_file_info.filePath());
|
||||
if (!pack_png_file.open(QIODevice::ReadOnly))
|
||||
return png_invalid(); // can't open pack.png file
|
||||
|
||||
auto data = pack_png_file.readAll();
|
||||
|
||||
bool pack_png_result = ResourcePackUtils::processPackPNG(pack, std::move(data));
|
||||
|
||||
pack_png_file.close();
|
||||
if (!pack_png_result) {
|
||||
return png_invalid(); // pack.png invalid
|
||||
}
|
||||
} else {
|
||||
return png_invalid(); // pack.png does not exists or is not a valid file.
|
||||
}
|
||||
|
||||
return true; // all tests passed
|
||||
}
|
||||
|
||||
bool processZIP(ResourcePack& pack, ProcessingLevel level)
|
||||
{
|
||||
Q_ASSERT(pack.type() == ResourceType::ZIPFILE);
|
||||
|
||||
QuaZip zip(pack.fileinfo().filePath());
|
||||
if (!zip.open(QuaZip::mdUnzip))
|
||||
return false; // can't open zip file
|
||||
|
||||
QuaZipFile file(&zip);
|
||||
|
||||
auto mcmeta_invalid = [&pack]() {
|
||||
qWarning() << "Resource pack at" << pack.fileinfo().filePath() << "does not have a valid pack.mcmeta";
|
||||
return false; // the mcmeta is not optional
|
||||
};
|
||||
|
||||
if (zip.setCurrentFile("pack.mcmeta")) {
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
qCritical() << "Failed to open file in zip.";
|
||||
zip.close();
|
||||
return mcmeta_invalid();
|
||||
}
|
||||
|
||||
auto data = file.readAll();
|
||||
|
||||
bool mcmeta_result = ResourcePackUtils::processMCMeta(pack, std::move(data));
|
||||
|
||||
file.close();
|
||||
if (!mcmeta_result) {
|
||||
return mcmeta_invalid(); // mcmeta invalid
|
||||
}
|
||||
} else {
|
||||
return mcmeta_invalid(); // could not set pack.mcmeta as current file.
|
||||
}
|
||||
|
||||
QuaZipDir zipDir(&zip);
|
||||
if (!zipDir.exists("/assets")) {
|
||||
return false; // assets dir does not exists at zip root
|
||||
}
|
||||
|
||||
if (level == ProcessingLevel::BasicInfoOnly) {
|
||||
zip.close();
|
||||
return true; // only need basic info already checked
|
||||
}
|
||||
|
||||
auto png_invalid = [&pack]() {
|
||||
qWarning() << "Resource pack at" << pack.fileinfo().filePath() << "does not have a valid pack.png";
|
||||
return true; // the png is optional
|
||||
};
|
||||
|
||||
if (zip.setCurrentFile("pack.png")) {
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
qCritical() << "Failed to open file in zip.";
|
||||
zip.close();
|
||||
return png_invalid();
|
||||
}
|
||||
|
||||
auto data = file.readAll();
|
||||
|
||||
bool pack_png_result = ResourcePackUtils::processPackPNG(pack, std::move(data));
|
||||
|
||||
file.close();
|
||||
zip.close();
|
||||
if (!pack_png_result) {
|
||||
return png_invalid(); // pack.png invalid
|
||||
}
|
||||
} else {
|
||||
zip.close();
|
||||
return png_invalid(); // could not set pack.mcmeta as current file.
|
||||
}
|
||||
|
||||
zip.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
QString buildStyle(const QJsonObject& obj)
|
||||
{
|
||||
QStringList styles;
|
||||
@@ -259,30 +111,11 @@ QString processComponent(const QJsonValue& value, bool strikethrough, bool under
|
||||
return {};
|
||||
}
|
||||
|
||||
// https://minecraft.wiki/w/Raw_JSON_text_format
|
||||
// https://minecraft.wiki/w/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta
|
||||
bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data)
|
||||
{
|
||||
try {
|
||||
auto json_doc = QJsonDocument::fromJson(raw_data);
|
||||
auto pack_obj = Json::requireObject(json_doc.object(), "pack", {});
|
||||
|
||||
pack.setPackFormat(Json::ensureInteger(pack_obj, "pack_format", 0));
|
||||
|
||||
pack.setDescription(processComponent(pack_obj.value("description")));
|
||||
|
||||
} catch (Json::JsonException& e) {
|
||||
qWarning() << "JsonException: " << e.what() << e.cause();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool processPackPNG(const ResourcePack& pack, QByteArray&& raw_data)
|
||||
bool processPackPNG(const ResourcePack* pack, QByteArray&& raw_data)
|
||||
{
|
||||
auto img = QImage::fromData(raw_data);
|
||||
if (!img.isNull()) {
|
||||
pack.setImage(img);
|
||||
pack->setImage(img);
|
||||
} else {
|
||||
qWarning() << "Failed to parse pack.png.";
|
||||
return false;
|
||||
@@ -290,16 +123,16 @@ bool processPackPNG(const ResourcePack& pack, QByteArray&& raw_data)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool processPackPNG(const ResourcePack& pack)
|
||||
bool processPackPNG(const ResourcePack* pack)
|
||||
{
|
||||
auto png_invalid = [&pack]() {
|
||||
qWarning() << "Resource pack at" << pack.fileinfo().filePath() << "does not have a valid pack.png";
|
||||
qWarning() << "Resource pack at" << pack->fileinfo().filePath() << "does not have a valid pack.png";
|
||||
return false;
|
||||
};
|
||||
|
||||
switch (pack.type()) {
|
||||
switch (pack->type()) {
|
||||
case ResourceType::FOLDER: {
|
||||
QFileInfo image_file_info(FS::PathCombine(pack.fileinfo().filePath(), "pack.png"));
|
||||
QFileInfo image_file_info(FS::PathCombine(pack->fileinfo().filePath(), "pack.png"));
|
||||
if (image_file_info.exists() && image_file_info.isFile()) {
|
||||
QFile pack_png_file(image_file_info.filePath());
|
||||
if (!pack_png_file.open(QIODevice::ReadOnly))
|
||||
@@ -319,7 +152,7 @@ bool processPackPNG(const ResourcePack& pack)
|
||||
return false; // not processed correctly; https://github.com/PrismLauncher/PrismLauncher/issues/1740
|
||||
}
|
||||
case ResourceType::ZIPFILE: {
|
||||
QuaZip zip(pack.fileinfo().filePath());
|
||||
QuaZip zip(pack->fileinfo().filePath());
|
||||
if (!zip.open(QuaZip::mdUnzip))
|
||||
return false; // can't open zip file
|
||||
|
||||
@@ -353,30 +186,7 @@ bool processPackPNG(const ResourcePack& pack)
|
||||
bool validate(QFileInfo file)
|
||||
{
|
||||
ResourcePack rp{ file };
|
||||
return ResourcePackUtils::process(rp, ProcessingLevel::BasicInfoOnly) && rp.valid();
|
||||
return DataPackUtils::process(&rp, DataPackUtils::ProcessingLevel::BasicInfoOnly) && rp.valid();
|
||||
}
|
||||
|
||||
} // namespace ResourcePackUtils
|
||||
|
||||
LocalResourcePackParseTask::LocalResourcePackParseTask(int token, ResourcePack& rp)
|
||||
: Task(nullptr, false), m_token(token), m_resource_pack(rp)
|
||||
{}
|
||||
|
||||
bool LocalResourcePackParseTask::abort()
|
||||
{
|
||||
m_aborted = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void LocalResourcePackParseTask::executeTask()
|
||||
{
|
||||
if (!ResourcePackUtils::process(m_resource_pack)) {
|
||||
emitFailed("this is not a resource pack");
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_aborted)
|
||||
emitAborted();
|
||||
else
|
||||
emitSucceeded();
|
||||
}
|
||||
|
||||
@@ -23,44 +23,14 @@
|
||||
|
||||
#include "minecraft/mod/ResourcePack.h"
|
||||
|
||||
#include "tasks/Task.h"
|
||||
|
||||
namespace ResourcePackUtils {
|
||||
|
||||
enum class ProcessingLevel { Full, BasicInfoOnly };
|
||||
|
||||
bool process(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Full);
|
||||
|
||||
bool processZIP(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Full);
|
||||
bool processFolder(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Full);
|
||||
|
||||
QString processComponent(const QJsonValue& value, bool strikethrough = false, bool underline = false);
|
||||
bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data);
|
||||
bool processPackPNG(const ResourcePack& pack, QByteArray&& raw_data);
|
||||
bool processPackPNG(const ResourcePack* pack, QByteArray&& raw_data);
|
||||
|
||||
/// processes ONLY the pack.png (rest of the pack may be invalid)
|
||||
bool processPackPNG(const ResourcePack& pack);
|
||||
bool processPackPNG(const ResourcePack* pack);
|
||||
|
||||
/** Checks whether a file is valid as a resource pack or not. */
|
||||
bool validate(QFileInfo file);
|
||||
} // namespace ResourcePackUtils
|
||||
|
||||
class LocalResourcePackParseTask : public Task {
|
||||
Q_OBJECT
|
||||
public:
|
||||
LocalResourcePackParseTask(int token, ResourcePack& rp);
|
||||
|
||||
[[nodiscard]] bool canAbort() const override { return true; }
|
||||
bool abort() override;
|
||||
|
||||
void executeTask() override;
|
||||
|
||||
[[nodiscard]] int token() const { return m_token; }
|
||||
|
||||
private:
|
||||
int m_token;
|
||||
|
||||
ResourcePack& m_resource_pack;
|
||||
|
||||
bool m_aborted = false;
|
||||
};
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
|
||||
#include "LocalDataPackParseTask.h"
|
||||
#include "LocalModParseTask.h"
|
||||
#include "LocalResourcePackParseTask.h"
|
||||
#include "LocalShaderPackParseTask.h"
|
||||
#include "LocalTexturePackParseTask.h"
|
||||
#include "LocalWorldSaveParseTask.h"
|
||||
@@ -46,7 +45,7 @@ PackedResourceType identify(QFileInfo file)
|
||||
// mods can contain resource and data packs so they must be tested first
|
||||
qDebug() << file.fileName() << "is a mod";
|
||||
return PackedResourceType::Mod;
|
||||
} else if (ResourcePackUtils::validate(file)) {
|
||||
} else if (DataPackUtils::validateResourcePack(file)) {
|
||||
qDebug() << file.fileName() << "is a resource pack";
|
||||
return PackedResourceType::ResourcePack;
|
||||
} else if (TexturePackUtils::validate(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;
|
||||
@@ -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;
|
||||
};
|
||||
@@ -93,7 +93,7 @@ bool validate(QFileInfo file)
|
||||
|
||||
} // namespace ShaderPackUtils
|
||||
|
||||
LocalShaderPackParseTask::LocalShaderPackParseTask(int token, ShaderPack& sp) : Task(nullptr, false), m_token(token), m_shader_pack(sp) {}
|
||||
LocalShaderPackParseTask::LocalShaderPackParseTask(int token, ShaderPack& sp) : Task(false), m_token(token), m_shader_pack(sp) {}
|
||||
|
||||
bool LocalShaderPackParseTask::abort()
|
||||
{
|
||||
|
||||
@@ -230,8 +230,7 @@ bool validate(QFileInfo file)
|
||||
|
||||
} // namespace TexturePackUtils
|
||||
|
||||
LocalTexturePackParseTask::LocalTexturePackParseTask(int token, TexturePack& rp) : Task(nullptr, false), m_token(token), m_texture_pack(rp)
|
||||
{}
|
||||
LocalTexturePackParseTask::LocalTexturePackParseTask(int token, TexturePack& rp) : Task(false), m_token(token), m_texture_pack(rp) {}
|
||||
|
||||
bool LocalTexturePackParseTask::abort()
|
||||
{
|
||||
|
||||
@@ -170,7 +170,7 @@ bool validate(QFileInfo file)
|
||||
|
||||
} // namespace WorldSaveUtils
|
||||
|
||||
LocalWorldSaveParseTask::LocalWorldSaveParseTask(int token, WorldSave& save) : Task(nullptr, false), m_token(token), m_save(save) {}
|
||||
LocalWorldSaveParseTask::LocalWorldSaveParseTask(int token, WorldSave& save) : Task(false), m_token(token), m_save(save) {}
|
||||
|
||||
bool LocalWorldSaveParseTask::abort()
|
||||
{
|
||||
@@ -180,8 +180,10 @@ bool LocalWorldSaveParseTask::abort()
|
||||
|
||||
void LocalWorldSaveParseTask::executeTask()
|
||||
{
|
||||
if (!WorldSaveUtils::process(m_save))
|
||||
if (!WorldSaveUtils::process(m_save)) {
|
||||
emitFailed("this is not a world");
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_aborted)
|
||||
emitAborted();
|
||||
|
||||
@@ -34,24 +34,30 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "ModFolderLoadTask.h"
|
||||
#include "ResourceFolderLoadTask.h"
|
||||
|
||||
#include "Application.h"
|
||||
#include "FileSystem.h"
|
||||
#include "minecraft/mod/MetadataHandler.h"
|
||||
|
||||
#include <QThread>
|
||||
|
||||
ModFolderLoadTask::ModFolderLoadTask(QDir mods_dir, QDir index_dir, bool is_indexed, bool clean_orphan)
|
||||
: Task(nullptr, false)
|
||||
, m_mods_dir(mods_dir)
|
||||
ResourceFolderLoadTask::ResourceFolderLoadTask(const QDir& resource_dir,
|
||||
const QDir& index_dir,
|
||||
bool is_indexed,
|
||||
bool clean_orphan,
|
||||
std::function<Resource*(const QFileInfo&)> create_function)
|
||||
: Task(false)
|
||||
, 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);
|
||||
@@ -62,40 +68,44 @@ 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;
|
||||
}
|
||||
auto newFilePath = FS::getUniqueResourceName(filePath);
|
||||
if (newFilePath != filePath) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -103,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)
|
||||
@@ -122,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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
Reference in New Issue
Block a user