mirror of
https://github.com/PrismLauncher/PrismLauncher.git
synced 2025-06-12 21:27:44 +02:00
Merge branch 'develop' of https://github.com/PrismLauncher/PrismLauncher into filters
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
This commit is contained in:
@ -206,8 +206,8 @@ int64_t calculateWorldSize(const QFileInfo& file)
|
||||
QDirIterator it(file.absoluteFilePath(), QDir::Files, QDirIterator::Subdirectories);
|
||||
int64_t total = 0;
|
||||
while (it.hasNext()) {
|
||||
total += it.fileInfo().size();
|
||||
it.next();
|
||||
total += it.fileInfo().size();
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
@ -347,7 +347,7 @@ bool parseMinecraftProfileMojang(QByteArray& data, MinecraftProfile& output)
|
||||
Skin skinOut;
|
||||
// fill in default skin info ourselves, as this endpoint doesn't provide it
|
||||
bool steve = isDefaultModelSteve(output.id);
|
||||
skinOut.variant = steve ? "classic" : "slim";
|
||||
skinOut.variant = steve ? "CLASSIC" : "SLIM";
|
||||
skinOut.url = steve ? SKIN_URL_STEVE : SKIN_URL_ALEX;
|
||||
// sadly we can't figure this out, but I don't think it really matters...
|
||||
skinOut.id = "00000000-0000-0000-0000-000000000000";
|
||||
|
@ -65,29 +65,24 @@ std::pair<Version, Version> DataPack::compatibleVersions() const
|
||||
return s_pack_format_versions.constFind(m_pack_format).value();
|
||||
}
|
||||
|
||||
std::pair<int, bool> DataPack::compare(const Resource& other, SortType type) const
|
||||
int DataPack::compare(const Resource& other, SortType type) const
|
||||
{
|
||||
auto const& cast_other = static_cast<DataPack const&>(other);
|
||||
|
||||
switch (type) {
|
||||
default: {
|
||||
auto res = Resource::compare(other, type);
|
||||
if (res.first != 0)
|
||||
return res;
|
||||
break;
|
||||
}
|
||||
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, type == SortType::PACK_FORMAT };
|
||||
return 1;
|
||||
if (this_ver < other_ver)
|
||||
return { -1, type == SortType::PACK_FORMAT };
|
||||
return -1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return { 0, false };
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool DataPack::applyFilter(QRegularExpression filter) const
|
||||
|
@ -56,7 +56,7 @@ class DataPack : public Resource {
|
||||
|
||||
bool valid() const override;
|
||||
|
||||
[[nodiscard]] auto compare(Resource const& other, SortType type) const -> std::pair<int, bool> override;
|
||||
[[nodiscard]] int compare(Resource const& other, SortType type) const override;
|
||||
[[nodiscard]] bool applyFilter(QRegularExpression filter) const override;
|
||||
|
||||
protected:
|
||||
|
@ -80,7 +80,7 @@ void Mod::setDetails(const ModDetails& details)
|
||||
m_local_details = details;
|
||||
}
|
||||
|
||||
std::pair<int, bool> Mod::compare(const Resource& other, SortType type) const
|
||||
int Mod::compare(const Resource& other, SortType type) const
|
||||
{
|
||||
auto cast_other = dynamic_cast<Mod const*>(&other);
|
||||
if (!cast_other)
|
||||
@ -90,59 +90,49 @@ std::pair<int, bool> Mod::compare(const Resource& other, SortType type) const
|
||||
default:
|
||||
case SortType::ENABLED:
|
||||
case SortType::NAME:
|
||||
case SortType::DATE: {
|
||||
auto res = Resource::compare(other, type);
|
||||
if (res.first != 0)
|
||||
return res;
|
||||
break;
|
||||
}
|
||||
case SortType::DATE:
|
||||
case SortType::SIZE:
|
||||
return Resource::compare(other, type);
|
||||
case SortType::VERSION: {
|
||||
auto this_ver = Version(version());
|
||||
auto other_ver = Version(cast_other->version());
|
||||
if (this_ver > other_ver)
|
||||
return { 1, type == SortType::VERSION };
|
||||
return 1;
|
||||
if (this_ver < other_ver)
|
||||
return { -1, type == SortType::VERSION };
|
||||
return -1;
|
||||
break;
|
||||
}
|
||||
case SortType::PROVIDER: {
|
||||
auto compare_result =
|
||||
QString::compare(provider().value_or("Unknown"), cast_other->provider().value_or("Unknown"), Qt::CaseInsensitive);
|
||||
if (compare_result != 0)
|
||||
return { compare_result, type == SortType::PROVIDER };
|
||||
break;
|
||||
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, type == SortType::SIDE };
|
||||
return 1;
|
||||
else if (side() < cast_other->side())
|
||||
return { -1, type == SortType::SIDE };
|
||||
return -1;
|
||||
break;
|
||||
}
|
||||
case SortType::LOADERS: {
|
||||
if (loaders() > cast_other->loaders())
|
||||
return { 1, type == SortType::LOADERS };
|
||||
return 1;
|
||||
else if (loaders() < cast_other->loaders())
|
||||
return { -1, type == SortType::LOADERS };
|
||||
return -1;
|
||||
break;
|
||||
}
|
||||
case SortType::MC_VERSIONS: {
|
||||
auto thisVersion = mcVersions().join(",");
|
||||
auto otherVersion = cast_other->mcVersions().join(",");
|
||||
auto compare_result = QString::compare(thisVersion, otherVersion, Qt::CaseInsensitive);
|
||||
if (compare_result != 0)
|
||||
return { compare_result, type == SortType::MC_VERSIONS };
|
||||
break;
|
||||
return QString::compare(thisVersion, otherVersion, Qt::CaseInsensitive);
|
||||
}
|
||||
case SortType::RELEASE_TYPE: {
|
||||
if (releaseType() > cast_other->releaseType())
|
||||
return { 1, type == SortType::RELEASE_TYPE };
|
||||
return 1;
|
||||
else if (releaseType() < cast_other->releaseType())
|
||||
return { -1, type == SortType::RELEASE_TYPE };
|
||||
return -1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return { 0, false };
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool Mod::applyFilter(QRegularExpression filter) const
|
||||
|
@ -94,7 +94,7 @@ class Mod : public Resource {
|
||||
|
||||
bool valid() const override;
|
||||
|
||||
[[nodiscard]] auto compare(Resource const& other, SortType type) const -> std::pair<int, bool> override;
|
||||
[[nodiscard]] int compare(Resource const& other, SortType type) const override;
|
||||
[[nodiscard]] bool applyFilter(QRegularExpression filter) const override;
|
||||
|
||||
// Delete all the files of this mod
|
||||
|
@ -48,7 +48,6 @@
|
||||
#include <QThreadPool>
|
||||
#include <QUrl>
|
||||
#include <QUuid>
|
||||
#include <algorithm>
|
||||
|
||||
#include "Application.h"
|
||||
|
||||
@ -65,17 +64,18 @@
|
||||
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)
|
||||
{
|
||||
m_column_names = QStringList(
|
||||
{ "Enable", "Image", "Name", "Version", "Last Modified", "Provider", "Side", "Loaders", "Miecraft Versions", "Release Type" });
|
||||
m_column_names = QStringList({ "Enable", "Image", "Name", "Version", "Last Modified", "Provider", "Size", "Side", "Loaders",
|
||||
"Miecraft Versions", "Release Type" });
|
||||
m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Version"), tr("Last Modified"), tr("Provider"),
|
||||
tr("Side"), tr("Loaders"), tr("Miecraft Versions"), tr("Release Type") });
|
||||
m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::VERSION, SortType::DATE,
|
||||
SortType::PROVIDER, SortType::SIDE, SortType::LOADERS, SortType::MC_VERSIONS, SortType::RELEASE_TYPE };
|
||||
tr("Size"), tr("Side"), tr("Loaders"), tr("Miecraft Versions"), tr("Release Type") });
|
||||
m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::VERSION,
|
||||
SortType::DATE, SortType::PROVIDER, SortType::SIZE, SortType::SIDE,
|
||||
SortType::LOADERS, SortType::MC_VERSIONS, SortType::RELEASE_TYPE };
|
||||
m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive,
|
||||
QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive,
|
||||
QHeaderView::Interactive, QHeaderView::Interactive };
|
||||
m_columnsHideable = { false, true, false, true, true, true, true, true, true, true };
|
||||
m_columnsHiddenByDefault = { false, false, false, false, false, false, true, true, true, true };
|
||||
QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive };
|
||||
m_columnsHideable = { false, true, false, true, true, true, true, true, true, true, true };
|
||||
m_columnsHiddenByDefault = { false, false, false, false, false, false, false, true, true, true, true };
|
||||
}
|
||||
|
||||
QVariant ModFolderModel::data(const QModelIndex& index, int role) const
|
||||
@ -133,12 +133,14 @@ QVariant ModFolderModel::data(const QModelIndex& index, int role) const
|
||||
case ReleaseTypeColumn: {
|
||||
return at(row)->releaseType().toString();
|
||||
}
|
||||
case SizeColumn:
|
||||
return m_resources[row]->sizeStr();
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
case Qt::ToolTipRole:
|
||||
if (column == NAME_COLUMN) {
|
||||
if (column == NameColumn) {
|
||||
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."
|
||||
@ -152,7 +154,7 @@ QVariant ModFolderModel::data(const QModelIndex& index, int role) const
|
||||
}
|
||||
return m_resources[row]->internal_id();
|
||||
case Qt::DecorationRole: {
|
||||
if (column == NAME_COLUMN && (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);
|
||||
@ -191,6 +193,7 @@ QVariant ModFolderModel::headerData(int section, [[maybe_unused]] Qt::Orientatio
|
||||
case LoadersColumn:
|
||||
case McVersionsColumn:
|
||||
case ReleaseTypeColumn:
|
||||
case SizeColumn:
|
||||
return columnNames().at(section);
|
||||
default:
|
||||
return QVariant();
|
||||
@ -216,6 +219,8 @@ QVariant ModFolderModel::headerData(int section, [[maybe_unused]] Qt::Orientatio
|
||||
return tr("The supported minecraft versions.");
|
||||
case ReleaseTypeColumn:
|
||||
return tr("The release type.");
|
||||
case SizeColumn:
|
||||
return tr("The size of the mod.");
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
|
@ -69,6 +69,7 @@ class ModFolderModel : public ResourceFolderModel {
|
||||
VersionColumn,
|
||||
DateColumn,
|
||||
ProviderColumn,
|
||||
SizeColumn,
|
||||
SideColumn,
|
||||
LoadersColumn,
|
||||
McVersionsColumn,
|
||||
|
@ -1,9 +1,12 @@
|
||||
#include "Resource.h"
|
||||
|
||||
#include <QDirIterator>
|
||||
#include <QFileInfo>
|
||||
#include <QRegularExpression>
|
||||
#include <tuple>
|
||||
|
||||
#include "FileSystem.h"
|
||||
#include "StringUtils.h"
|
||||
|
||||
Resource::Resource(QObject* parent) : QObject(parent) {}
|
||||
|
||||
@ -18,6 +21,20 @@ void Resource::setFile(QFileInfo file_info)
|
||||
parseFile();
|
||||
}
|
||||
|
||||
std::tuple<QString, qint64> calculateFileSize(const QFileInfo& file)
|
||||
{
|
||||
if (file.isDir()) {
|
||||
auto dir = QDir(file.absoluteFilePath());
|
||||
dir.setFilter(QDir::AllEntries | QDir::NoDotAndDotDot);
|
||||
auto count = dir.count();
|
||||
auto str = QObject::tr("item");
|
||||
if (count != 1)
|
||||
str = QObject::tr("items");
|
||||
return { QString("%1 %2").arg(QString::number(count), str), count };
|
||||
}
|
||||
return { StringUtils::humanReadableFileSize(file.size(), true), file.size() };
|
||||
}
|
||||
|
||||
void Resource::parseFile()
|
||||
{
|
||||
QString file_name{ m_file_info.fileName() };
|
||||
@ -26,6 +43,7 @@ void Resource::parseFile()
|
||||
|
||||
m_internal_id = file_name;
|
||||
|
||||
std::tie(m_size_str, m_size_info) = calculateFileSize(m_file_info);
|
||||
if (m_file_info.isDir()) {
|
||||
m_type = ResourceType::FOLDER;
|
||||
m_name = file_name;
|
||||
@ -61,15 +79,15 @@ static void removeThePrefix(QString& string)
|
||||
string = string.trimmed();
|
||||
}
|
||||
|
||||
std::pair<int, bool> Resource::compare(const Resource& other, SortType type) const
|
||||
int Resource::compare(const Resource& other, SortType type) const
|
||||
{
|
||||
switch (type) {
|
||||
default:
|
||||
case SortType::ENABLED:
|
||||
if (enabled() && !other.enabled())
|
||||
return { 1, type == SortType::ENABLED };
|
||||
return 1;
|
||||
if (!enabled() && other.enabled())
|
||||
return { -1, type == SortType::ENABLED };
|
||||
return -1;
|
||||
break;
|
||||
case SortType::NAME: {
|
||||
QString this_name{ name() };
|
||||
@ -78,20 +96,31 @@ std::pair<int, bool> Resource::compare(const Resource& other, SortType type) con
|
||||
removeThePrefix(this_name);
|
||||
removeThePrefix(other_name);
|
||||
|
||||
auto compare_result = QString::compare(this_name, other_name, Qt::CaseInsensitive);
|
||||
if (compare_result != 0)
|
||||
return { compare_result, type == SortType::NAME };
|
||||
break;
|
||||
return QString::compare(this_name, other_name, Qt::CaseInsensitive);
|
||||
}
|
||||
case SortType::DATE:
|
||||
if (dateTimeChanged() > other.dateTimeChanged())
|
||||
return { 1, type == SortType::DATE };
|
||||
return 1;
|
||||
if (dateTimeChanged() < other.dateTimeChanged())
|
||||
return { -1, type == SortType::DATE };
|
||||
return -1;
|
||||
break;
|
||||
case SortType::SIZE: {
|
||||
if (this->type() != other.type()) {
|
||||
if (this->type() == ResourceType::FOLDER)
|
||||
return -1;
|
||||
if (other.type() == ResourceType::FOLDER)
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (sizeInfo() > other.sizeInfo())
|
||||
return 1;
|
||||
if (sizeInfo() < other.sizeInfo())
|
||||
return -1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return { 0, false };
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool Resource::applyFilter(QRegularExpression filter) const
|
||||
|
@ -50,7 +50,7 @@ enum class ResourceType {
|
||||
LITEMOD, //!< The resource is a litemod
|
||||
};
|
||||
|
||||
enum class SortType { NAME, DATE, VERSION, ENABLED, PACK_FORMAT, PROVIDER, SIDE, LOADERS, MC_VERSIONS, RELEASE_TYPE };
|
||||
enum class SortType { NAME, DATE, VERSION, ENABLED, PACK_FORMAT, PROVIDER, SIZE, SIDE, LOADERS, MC_VERSIONS, RELEASE_TYPE };
|
||||
|
||||
enum class EnableAction { ENABLE, DISABLE, TOGGLE };
|
||||
|
||||
@ -80,6 +80,8 @@ class Resource : public QObject {
|
||||
[[nodiscard]] auto internal_id() const -> QString { return m_internal_id; }
|
||||
[[nodiscard]] auto type() const -> ResourceType { return m_type; }
|
||||
[[nodiscard]] bool enabled() const { return m_enabled; }
|
||||
[[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 bool valid() const { return m_type != ResourceType::UNKNOWN; }
|
||||
@ -88,10 +90,8 @@ class Resource : public QObject {
|
||||
* > 0: 'this' comes after 'other'
|
||||
* = 0: 'this' is equal to 'other'
|
||||
* < 0: 'this' comes before 'other'
|
||||
*
|
||||
* The second argument in the pair is true if the sorting type that decided which one is greater was 'type'.
|
||||
*/
|
||||
[[nodiscard]] virtual auto compare(Resource const& other, SortType type = SortType::NAME) const -> std::pair<int, bool>;
|
||||
[[nodiscard]] virtual int compare(Resource const& other, SortType type = SortType::NAME) const;
|
||||
|
||||
/** Returns whether the given filter should filter out 'this' (false),
|
||||
* or if such filter includes the Resource (true).
|
||||
@ -152,4 +152,6 @@ class Resource : public QObject {
|
||||
bool m_is_resolving = false;
|
||||
bool m_is_resolved = false;
|
||||
int m_resolution_ticket = 0;
|
||||
QString m_size_str;
|
||||
qint64 m_size_info;
|
||||
};
|
||||
|
@ -16,6 +16,7 @@
|
||||
#include "FileSystem.h"
|
||||
|
||||
#include "QVariantUtils.h"
|
||||
#include "StringUtils.h"
|
||||
#include "minecraft/mod/tasks/BasicFolderLoadTask.h"
|
||||
|
||||
#include "settings/Setting.h"
|
||||
@ -416,15 +417,17 @@ QVariant ResourceFolderModel::data(const QModelIndex& index, int role) const
|
||||
switch (role) {
|
||||
case Qt::DisplayRole:
|
||||
switch (column) {
|
||||
case NAME_COLUMN:
|
||||
case NameColumn:
|
||||
return m_resources[row]->name();
|
||||
case DATE_COLUMN:
|
||||
case DateColumn:
|
||||
return m_resources[row]->dateTimeChanged();
|
||||
case SizeColumn:
|
||||
return m_resources[row]->sizeStr();
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
case Qt::ToolTipRole:
|
||||
if (column == NAME_COLUMN) {
|
||||
if (column == NameColumn) {
|
||||
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."
|
||||
@ -440,14 +443,14 @@ QVariant ResourceFolderModel::data(const QModelIndex& index, int role) const
|
||||
|
||||
return m_resources[row]->internal_id();
|
||||
case Qt::DecorationRole: {
|
||||
if (column == NAME_COLUMN && (at(row).isSymLinkUnder(instDirPath()) || at(row).isMoreThanOneHardLink()))
|
||||
if (column == NameColumn && (at(row).isSymLinkUnder(instDirPath()) || at(row).isMoreThanOneHardLink()))
|
||||
return APPLICATION->getThemedIcon("status-yellow");
|
||||
|
||||
return {};
|
||||
}
|
||||
case Qt::CheckStateRole:
|
||||
switch (column) {
|
||||
case ACTIVE_COLUMN:
|
||||
case ActiveColumn:
|
||||
return m_resources[row]->enabled() ? Qt::Checked : Qt::Unchecked;
|
||||
default:
|
||||
return {};
|
||||
@ -486,24 +489,27 @@ QVariant ResourceFolderModel::headerData(int section, [[maybe_unused]] Qt::Orien
|
||||
switch (role) {
|
||||
case Qt::DisplayRole:
|
||||
switch (section) {
|
||||
case ACTIVE_COLUMN:
|
||||
case NAME_COLUMN:
|
||||
case DATE_COLUMN:
|
||||
case ActiveColumn:
|
||||
case NameColumn:
|
||||
case DateColumn:
|
||||
case SizeColumn:
|
||||
return columnNames().at(section);
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
case Qt::ToolTipRole: {
|
||||
switch (section) {
|
||||
case ACTIVE_COLUMN:
|
||||
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 NAME_COLUMN:
|
||||
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 DATE_COLUMN:
|
||||
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 SizeColumn:
|
||||
return tr("The size of the resource.");
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
@ -614,12 +620,10 @@ SortType ResourceFolderModel::columnToSortKey(size_t column) const
|
||||
auto const& resource_right = model->at(source_right.row());
|
||||
|
||||
auto compare_result = resource_left.compare(resource_right, column_sort_key);
|
||||
if (compare_result.first == 0)
|
||||
if (compare_result == 0)
|
||||
return QSortFilterProxyModel::lessThan(source_left, source_right);
|
||||
|
||||
if (compare_result.second || sortOrder() != Qt::DescendingOrder)
|
||||
return (compare_result.first < 0);
|
||||
return (compare_result.first > 0);
|
||||
return compare_result < 0;
|
||||
}
|
||||
|
||||
QString ResourceFolderModel::instDirPath() const
|
||||
|
@ -96,7 +96,7 @@ class ResourceFolderModel : public QAbstractListModel {
|
||||
/* Qt behavior */
|
||||
|
||||
/* Basic columns */
|
||||
enum Columns { ACTIVE_COLUMN = 0, NAME_COLUMN, DATE_COLUMN, NUM_COLUMNS };
|
||||
enum Columns { ActiveColumn = 0, NameColumn, DateColumn, 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()); }
|
||||
@ -195,12 +195,13 @@ 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 };
|
||||
QStringList m_column_names = { "Enable", "Name", "Last Modified" };
|
||||
QStringList m_column_names_translated = { tr("Enable"), tr("Name"), tr("Last Modified") };
|
||||
QList<QHeaderView::ResizeMode> m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive };
|
||||
QList<bool> m_columnsHideable = { false, false, true };
|
||||
QList<bool> m_columnsHiddenByDefault = { false, false, false };
|
||||
QList<SortType> m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::DATE, SortType::SIZE };
|
||||
QStringList m_column_names = { "Enable", "Name", "Last Modified", "Size" };
|
||||
QStringList m_column_names_translated = { tr("Enable"), tr("Name"), tr("Last Modified"), tr("Size") };
|
||||
QList<QHeaderView::ResizeMode> m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive,
|
||||
QHeaderView::Interactive };
|
||||
QList<bool> m_columnsHideable = { false, false, true, true };
|
||||
QList<bool> m_columnsHiddenByDefault = { false, false, false, false };
|
||||
|
||||
QDir m_dir;
|
||||
BaseInstance* m_instance;
|
||||
|
@ -94,29 +94,24 @@ std::pair<Version, Version> ResourcePack::compatibleVersions() const
|
||||
return s_pack_format_versions.constFind(m_pack_format).value();
|
||||
}
|
||||
|
||||
std::pair<int, bool> ResourcePack::compare(const Resource& other, SortType type) const
|
||||
int ResourcePack::compare(const Resource& other, SortType type) const
|
||||
{
|
||||
auto const& cast_other = static_cast<ResourcePack const&>(other);
|
||||
|
||||
switch (type) {
|
||||
default: {
|
||||
auto res = Resource::compare(other, type);
|
||||
if (res.first != 0)
|
||||
return res;
|
||||
break;
|
||||
}
|
||||
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, type == SortType::PACK_FORMAT };
|
||||
return 1;
|
||||
if (this_ver < other_ver)
|
||||
return { -1, type == SortType::PACK_FORMAT };
|
||||
return -1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return { 0, false };
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool ResourcePack::applyFilter(QRegularExpression filter) const
|
||||
|
@ -44,7 +44,7 @@ class ResourcePack : public Resource {
|
||||
|
||||
bool valid() const override;
|
||||
|
||||
[[nodiscard]] auto compare(Resource const& other, SortType type) const -> std::pair<int, bool> override;
|
||||
[[nodiscard]] int compare(Resource const& other, SortType type) const override;
|
||||
[[nodiscard]] bool applyFilter(QRegularExpression filter) const override;
|
||||
|
||||
protected:
|
||||
|
@ -44,18 +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"
|
||||
|
||||
ResourcePackFolderModel::ResourcePackFolderModel(const QString& dir, BaseInstance* instance) : ResourceFolderModel(QDir(dir), instance)
|
||||
{
|
||||
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") });
|
||||
m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::PACK_FORMAT, SortType::DATE };
|
||||
m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive,
|
||||
QHeaderView::Interactive };
|
||||
m_columnsHideable = { false, true, false, true, true };
|
||||
m_columnsHiddenByDefault = { false, false, false, false, false };
|
||||
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,
|
||||
QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive };
|
||||
m_columnsHideable = { false, true, false, true, true, true };
|
||||
m_columnsHiddenByDefault = { false, false, false, false, false, false };
|
||||
}
|
||||
|
||||
QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const
|
||||
@ -86,6 +87,8 @@ QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const
|
||||
}
|
||||
case DateColumn:
|
||||
return m_resources[row]->dateTimeChanged();
|
||||
case SizeColumn:
|
||||
return m_resources[row]->sizeStr();
|
||||
|
||||
default:
|
||||
return {};
|
||||
@ -145,6 +148,7 @@ QVariant ResourcePackFolderModel::headerData(int section, [[maybe_unused]] Qt::O
|
||||
case PackFormatColumn:
|
||||
case DateColumn:
|
||||
case ImageColumn:
|
||||
case SizeColumn:
|
||||
return columnNames().at(section);
|
||||
default:
|
||||
return {};
|
||||
@ -161,6 +165,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 SizeColumn:
|
||||
return tr("The size of the resource pack.");
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
|
@ -7,7 +7,7 @@
|
||||
class ResourcePackFolderModel : public ResourceFolderModel {
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum Columns { ActiveColumn = 0, ImageColumn, NameColumn, PackFormatColumn, DateColumn, NUM_COLUMNS };
|
||||
enum Columns { ActiveColumn = 0, ImageColumn, NameColumn, PackFormatColumn, DateColumn, SizeColumn, NUM_COLUMNS };
|
||||
|
||||
explicit ResourcePackFolderModel(const QString& dir, BaseInstance* instance);
|
||||
|
||||
|
@ -37,6 +37,7 @@
|
||||
|
||||
#include "Application.h"
|
||||
|
||||
#include "StringUtils.h"
|
||||
#include "TexturePackFolderModel.h"
|
||||
|
||||
#include "minecraft/mod/tasks/BasicFolderLoadTask.h"
|
||||
@ -49,7 +50,6 @@ TexturePackFolderModel::TexturePackFolderModel(const QString& dir, BaseInstance*
|
||||
m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::DATE };
|
||||
m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive };
|
||||
m_columnsHideable = { false, true, false, true };
|
||||
m_columnsHiddenByDefault = { false, false, false, false };
|
||||
}
|
||||
|
||||
Task* TexturePackFolderModel::createUpdateTask()
|
||||
@ -77,6 +77,8 @@ QVariant TexturePackFolderModel::data(const QModelIndex& index, int role) const
|
||||
return m_resources[row]->name();
|
||||
case DateColumn:
|
||||
return m_resources[row]->dateTimeChanged();
|
||||
case SizeColumn:
|
||||
return m_resources[row]->sizeStr();
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
@ -128,6 +130,7 @@ QVariant TexturePackFolderModel::headerData(int section, [[maybe_unused]] Qt::Or
|
||||
case NameColumn:
|
||||
case DateColumn:
|
||||
case ImageColumn:
|
||||
case SizeColumn:
|
||||
return columnNames().at(section);
|
||||
default:
|
||||
return {};
|
||||
@ -136,13 +139,15 @@ QVariant TexturePackFolderModel::headerData(int section, [[maybe_unused]] Qt::Or
|
||||
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?");
|
||||
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 resource.");
|
||||
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 resource was last changed (or added).");
|
||||
return tr("The date and time this texture pack was last changed (or added).");
|
||||
case SizeColumn:
|
||||
return tr("The size of the texture pack.");
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
|
@ -44,7 +44,7 @@ class TexturePackFolderModel : public ResourceFolderModel {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum Columns { ActiveColumn = 0, ImageColumn, NameColumn, DateColumn, NUM_COLUMNS };
|
||||
enum Columns { ActiveColumn = 0, ImageColumn, NameColumn, DateColumn, SizeColumn, NUM_COLUMNS };
|
||||
|
||||
explicit TexturePackFolderModel(const QString& dir, std::shared_ptr<const BaseInstance> instance);
|
||||
|
||||
|
@ -1,121 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* This file incorporates work covered by the following copyright and
|
||||
* permission notice:
|
||||
*
|
||||
* Copyright 2013-2021 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "CapeChange.h"
|
||||
|
||||
#include <QHttpMultiPart>
|
||||
#include <QNetworkRequest>
|
||||
|
||||
#include "Application.h"
|
||||
|
||||
CapeChange::CapeChange(QObject* parent, QString token, QString cape) : Task(parent), m_capeId(cape), m_token(token) {}
|
||||
|
||||
void CapeChange::setCape([[maybe_unused]] QString& cape)
|
||||
{
|
||||
QNetworkRequest request(QUrl("https://api.minecraftservices.com/minecraft/profile/capes/active"));
|
||||
auto requestString = QString("{\"capeId\":\"%1\"}").arg(m_capeId);
|
||||
request.setRawHeader("Authorization", QString("Bearer %1").arg(m_token).toLocal8Bit());
|
||||
QNetworkReply* rep = APPLICATION->network()->put(request, requestString.toUtf8());
|
||||
|
||||
setStatus(tr("Equipping cape"));
|
||||
|
||||
m_reply = shared_qobject_ptr<QNetworkReply>(rep);
|
||||
connect(rep, &QNetworkReply::uploadProgress, this, &CapeChange::setProgress);
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15
|
||||
connect(rep, &QNetworkReply::errorOccurred, this, &CapeChange::downloadError);
|
||||
#else
|
||||
connect(rep, QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::error), this, &CapeChange::downloadError);
|
||||
#endif
|
||||
connect(rep, &QNetworkReply::sslErrors, this, &CapeChange::sslErrors);
|
||||
connect(rep, &QNetworkReply::finished, this, &CapeChange::downloadFinished);
|
||||
}
|
||||
|
||||
void CapeChange::clearCape()
|
||||
{
|
||||
QNetworkRequest request(QUrl("https://api.minecraftservices.com/minecraft/profile/capes/active"));
|
||||
auto requestString = QString("{\"capeId\":\"%1\"}").arg(m_capeId);
|
||||
request.setRawHeader("Authorization", QString("Bearer %1").arg(m_token).toLocal8Bit());
|
||||
QNetworkReply* rep = APPLICATION->network()->deleteResource(request);
|
||||
|
||||
setStatus(tr("Removing cape"));
|
||||
|
||||
m_reply = shared_qobject_ptr<QNetworkReply>(rep);
|
||||
connect(rep, &QNetworkReply::uploadProgress, this, &CapeChange::setProgress);
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15
|
||||
connect(rep, &QNetworkReply::errorOccurred, this, &CapeChange::downloadError);
|
||||
#else
|
||||
connect(rep, QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::error), this, &CapeChange::downloadError);
|
||||
#endif
|
||||
connect(rep, &QNetworkReply::sslErrors, this, &CapeChange::sslErrors);
|
||||
connect(rep, &QNetworkReply::finished, this, &CapeChange::downloadFinished);
|
||||
}
|
||||
|
||||
void CapeChange::executeTask()
|
||||
{
|
||||
if (m_capeId.isEmpty()) {
|
||||
clearCape();
|
||||
} else {
|
||||
setCape(m_capeId);
|
||||
}
|
||||
}
|
||||
|
||||
void CapeChange::downloadError(QNetworkReply::NetworkError error)
|
||||
{
|
||||
// error happened during download.
|
||||
qCritical() << "Network error: " << error;
|
||||
emitFailed(m_reply->errorString());
|
||||
}
|
||||
|
||||
void CapeChange::sslErrors(const QList<QSslError>& errors)
|
||||
{
|
||||
int i = 1;
|
||||
for (auto error : errors) {
|
||||
qCritical() << "Cape change SSL Error #" << i << " : " << error.errorString();
|
||||
auto cert = error.certificate();
|
||||
qCritical() << "Certificate in question:\n" << cert.toText();
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
void CapeChange::downloadFinished()
|
||||
{
|
||||
// if the download failed
|
||||
if (m_reply->error() != QNetworkReply::NetworkError::NoError) {
|
||||
emitFailed(QString("Network error: %1").arg(m_reply->errorString()));
|
||||
m_reply.reset();
|
||||
return;
|
||||
}
|
||||
emitSucceeded();
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <QFile>
|
||||
#include <QtNetwork/QtNetwork>
|
||||
#include <memory>
|
||||
#include "QObjectPtr.h"
|
||||
#include "tasks/Task.h"
|
||||
|
||||
class CapeChange : public Task {
|
||||
Q_OBJECT
|
||||
public:
|
||||
CapeChange(QObject* parent, QString token, QString capeId);
|
||||
virtual ~CapeChange() {}
|
||||
|
||||
private:
|
||||
void setCape(QString& cape);
|
||||
void clearCape();
|
||||
|
||||
private:
|
||||
QString m_capeId;
|
||||
QString m_token;
|
||||
shared_qobject_ptr<QNetworkReply> m_reply;
|
||||
|
||||
protected:
|
||||
virtual void executeTask();
|
||||
|
||||
public slots:
|
||||
void downloadError(QNetworkReply::NetworkError);
|
||||
void sslErrors(const QList<QSslError>& errors);
|
||||
void downloadFinished();
|
||||
};
|
@ -1,90 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* This file incorporates work covered by the following copyright and
|
||||
* permission notice:
|
||||
*
|
||||
* Copyright 2013-2021 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "SkinDelete.h"
|
||||
|
||||
#include <QHttpMultiPart>
|
||||
#include <QNetworkRequest>
|
||||
|
||||
#include "Application.h"
|
||||
|
||||
SkinDelete::SkinDelete(QObject* parent, QString token) : Task(parent), m_token(token) {}
|
||||
|
||||
void SkinDelete::executeTask()
|
||||
{
|
||||
QNetworkRequest request(QUrl("https://api.minecraftservices.com/minecraft/profile/skins/active"));
|
||||
request.setRawHeader("Authorization", QString("Bearer %1").arg(m_token).toLocal8Bit());
|
||||
QNetworkReply* rep = APPLICATION->network()->deleteResource(request);
|
||||
m_reply = shared_qobject_ptr<QNetworkReply>(rep);
|
||||
|
||||
setStatus(tr("Deleting skin"));
|
||||
connect(rep, &QNetworkReply::uploadProgress, this, &SkinDelete::setProgress);
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15
|
||||
connect(rep, &QNetworkReply::errorOccurred, this, &SkinDelete::downloadError);
|
||||
#else
|
||||
connect(rep, QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::error), this, &SkinDelete::downloadError);
|
||||
#endif
|
||||
connect(rep, &QNetworkReply::sslErrors, this, &SkinDelete::sslErrors);
|
||||
connect(rep, &QNetworkReply::finished, this, &SkinDelete::downloadFinished);
|
||||
}
|
||||
|
||||
void SkinDelete::downloadError(QNetworkReply::NetworkError error)
|
||||
{
|
||||
// error happened during download.
|
||||
qCritical() << "Network error: " << error;
|
||||
emitFailed(m_reply->errorString());
|
||||
}
|
||||
|
||||
void SkinDelete::sslErrors(const QList<QSslError>& errors)
|
||||
{
|
||||
int i = 1;
|
||||
for (auto error : errors) {
|
||||
qCritical() << "Skin Delete SSL Error #" << i << " : " << error.errorString();
|
||||
auto cert = error.certificate();
|
||||
qCritical() << "Certificate in question:\n" << cert.toText();
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
void SkinDelete::downloadFinished()
|
||||
{
|
||||
// if the download failed
|
||||
if (m_reply->error() != QNetworkReply::NetworkError::NoError) {
|
||||
emitFailed(QString("Network error: %1").arg(m_reply->errorString()));
|
||||
m_reply.reset();
|
||||
return;
|
||||
}
|
||||
emitSucceeded();
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <QFile>
|
||||
#include <QtNetwork/QtNetwork>
|
||||
#include "tasks/Task.h"
|
||||
|
||||
using SkinDeletePtr = shared_qobject_ptr<class SkinDelete>;
|
||||
|
||||
class SkinDelete : public Task {
|
||||
Q_OBJECT
|
||||
public:
|
||||
SkinDelete(QObject* parent, QString token);
|
||||
virtual ~SkinDelete() = default;
|
||||
|
||||
private:
|
||||
QString m_token;
|
||||
shared_qobject_ptr<QNetworkReply> m_reply;
|
||||
|
||||
protected:
|
||||
virtual void executeTask();
|
||||
|
||||
public slots:
|
||||
void downloadError(QNetworkReply::NetworkError);
|
||||
void sslErrors(const QList<QSslError>& errors);
|
||||
void downloadFinished();
|
||||
};
|
@ -1,118 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* This file incorporates work covered by the following copyright and
|
||||
* permission notice:
|
||||
*
|
||||
* Copyright 2013-2021 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "SkinUpload.h"
|
||||
|
||||
#include <QHttpMultiPart>
|
||||
#include <QNetworkRequest>
|
||||
|
||||
#include "Application.h"
|
||||
|
||||
QByteArray getVariant(SkinUpload::Model model)
|
||||
{
|
||||
switch (model) {
|
||||
default:
|
||||
qDebug() << "Unknown skin type!";
|
||||
case SkinUpload::STEVE:
|
||||
return "CLASSIC";
|
||||
case SkinUpload::ALEX:
|
||||
return "SLIM";
|
||||
}
|
||||
}
|
||||
|
||||
SkinUpload::SkinUpload(QObject* parent, QString token, QByteArray skin, SkinUpload::Model model)
|
||||
: Task(parent), m_model(model), m_skin(skin), m_token(token)
|
||||
{}
|
||||
|
||||
void SkinUpload::executeTask()
|
||||
{
|
||||
QNetworkRequest request(QUrl("https://api.minecraftservices.com/minecraft/profile/skins"));
|
||||
request.setRawHeader("Authorization", QString("Bearer %1").arg(m_token).toLocal8Bit());
|
||||
QHttpMultiPart* multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
|
||||
|
||||
QHttpPart skin;
|
||||
skin.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("image/png"));
|
||||
skin.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"file\"; filename=\"skin.png\""));
|
||||
skin.setBody(m_skin);
|
||||
|
||||
QHttpPart model;
|
||||
model.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"variant\""));
|
||||
model.setBody(getVariant(m_model));
|
||||
|
||||
multiPart->append(skin);
|
||||
multiPart->append(model);
|
||||
|
||||
QNetworkReply* rep = APPLICATION->network()->post(request, multiPart);
|
||||
m_reply = shared_qobject_ptr<QNetworkReply>(rep);
|
||||
|
||||
setStatus(tr("Uploading skin"));
|
||||
connect(rep, &QNetworkReply::uploadProgress, this, &SkinUpload::setProgress);
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15
|
||||
connect(rep, &QNetworkReply::errorOccurred, this, &SkinUpload::downloadError);
|
||||
#else
|
||||
connect(rep, QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::error), this, &SkinUpload::downloadError);
|
||||
#endif
|
||||
connect(rep, &QNetworkReply::sslErrors, this, &SkinUpload::sslErrors);
|
||||
connect(rep, &QNetworkReply::finished, this, &SkinUpload::downloadFinished);
|
||||
}
|
||||
|
||||
void SkinUpload::downloadError(QNetworkReply::NetworkError error)
|
||||
{
|
||||
// error happened during download.
|
||||
qCritical() << "Network error: " << error;
|
||||
emitFailed(m_reply->errorString());
|
||||
}
|
||||
|
||||
void SkinUpload::sslErrors(const QList<QSslError>& errors)
|
||||
{
|
||||
int i = 1;
|
||||
for (auto error : errors) {
|
||||
qCritical() << "Skin Upload SSL Error #" << i << " : " << error.errorString();
|
||||
auto cert = error.certificate();
|
||||
qCritical() << "Certificate in question:\n" << cert.toText();
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
void SkinUpload::downloadFinished()
|
||||
{
|
||||
// if the download failed
|
||||
if (m_reply->error() != QNetworkReply::NetworkError::NoError) {
|
||||
emitFailed(QString("Network error: %1").arg(m_reply->errorString()));
|
||||
m_reply.reset();
|
||||
return;
|
||||
}
|
||||
emitSucceeded();
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <QFile>
|
||||
#include <QtNetwork/QtNetwork>
|
||||
#include <memory>
|
||||
#include "tasks/Task.h"
|
||||
|
||||
using SkinUploadPtr = shared_qobject_ptr<class SkinUpload>;
|
||||
|
||||
class SkinUpload : public Task {
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum Model { STEVE, ALEX };
|
||||
|
||||
// Note this class takes ownership of the file.
|
||||
SkinUpload(QObject* parent, QString token, QByteArray skin, Model model = STEVE);
|
||||
virtual ~SkinUpload() {}
|
||||
|
||||
private:
|
||||
Model m_model;
|
||||
QByteArray m_skin;
|
||||
QString m_token;
|
||||
shared_qobject_ptr<QNetworkReply> m_reply;
|
||||
|
||||
protected:
|
||||
virtual void executeTask();
|
||||
|
||||
public slots:
|
||||
|
||||
void downloadError(QNetworkReply::NetworkError);
|
||||
void sslErrors(const QList<QSslError>& errors);
|
||||
|
||||
void downloadFinished();
|
||||
};
|
74
launcher/minecraft/skins/CapeChange.cpp
Normal file
74
launcher/minecraft/skins/CapeChange.cpp
Normal file
@ -0,0 +1,74 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* This file incorporates work covered by the following copyright and
|
||||
* permission notice:
|
||||
*
|
||||
* Copyright 2013-2021 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "CapeChange.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "net/ByteArraySink.h"
|
||||
#include "net/StaticHeaderProxy.h"
|
||||
|
||||
CapeChange::CapeChange(QString token, QString cape) : NetRequest(), m_capeId(cape), m_token(token)
|
||||
{
|
||||
logCat = taskMCSkinsLogC;
|
||||
}
|
||||
|
||||
QNetworkReply* CapeChange::getReply(QNetworkRequest& request)
|
||||
{
|
||||
if (m_capeId.isEmpty()) {
|
||||
setStatus(tr("Removing cape"));
|
||||
return m_network->deleteResource(request);
|
||||
} else {
|
||||
setStatus(tr("Equipping cape"));
|
||||
return m_network->put(request, QString("{\"capeId\":\"%1\"}").arg(m_capeId).toUtf8());
|
||||
}
|
||||
}
|
||||
|
||||
void CapeChange::init()
|
||||
{
|
||||
addHeaderProxy(new Net::StaticHeaderProxy(QList<Net::HeaderPair>{
|
||||
{ "Authorization", QString("Bearer %1").arg(m_token).toLocal8Bit() },
|
||||
}));
|
||||
}
|
||||
|
||||
CapeChange::Ptr CapeChange::make(QString token, QString capeId)
|
||||
{
|
||||
auto up = makeShared<CapeChange>(token, capeId);
|
||||
up->m_url = QUrl("https://api.minecraftservices.com/minecraft/profile/capes/active");
|
||||
up->setObjectName(QString("BYTES:") + up->m_url.toString());
|
||||
up->m_sink.reset(new Net::ByteArraySink(std::make_shared<QByteArray>()));
|
||||
return up;
|
||||
}
|
39
launcher/minecraft/skins/CapeChange.h
Normal file
39
launcher/minecraft/skins/CapeChange.h
Normal file
@ -0,0 +1,39 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "net/NetRequest.h"
|
||||
|
||||
class CapeChange : public Net::NetRequest {
|
||||
Q_OBJECT
|
||||
public:
|
||||
using Ptr = shared_qobject_ptr<CapeChange>;
|
||||
CapeChange(QString token, QString capeId);
|
||||
virtual ~CapeChange() = default;
|
||||
|
||||
static CapeChange::Ptr make(QString token, QString capeId);
|
||||
void init() override;
|
||||
|
||||
protected:
|
||||
virtual QNetworkReply* getReply(QNetworkRequest&) override;
|
||||
|
||||
private:
|
||||
QString m_capeId;
|
||||
QString m_token;
|
||||
};
|
66
launcher/minecraft/skins/SkinDelete.cpp
Normal file
66
launcher/minecraft/skins/SkinDelete.cpp
Normal file
@ -0,0 +1,66 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* This file incorporates work covered by the following copyright and
|
||||
* permission notice:
|
||||
*
|
||||
* Copyright 2013-2021 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "SkinDelete.h"
|
||||
|
||||
#include "net/ByteArraySink.h"
|
||||
#include "net/StaticHeaderProxy.h"
|
||||
|
||||
SkinDelete::SkinDelete(QString token) : NetRequest(), m_token(token)
|
||||
{
|
||||
logCat = taskMCSkinsLogC;
|
||||
}
|
||||
|
||||
QNetworkReply* SkinDelete::getReply(QNetworkRequest& request)
|
||||
{
|
||||
setStatus(tr("Deleting skin"));
|
||||
return m_network->deleteResource(request);
|
||||
}
|
||||
|
||||
void SkinDelete::init()
|
||||
{
|
||||
addHeaderProxy(new Net::StaticHeaderProxy(QList<Net::HeaderPair>{
|
||||
{ "Authorization", QString("Bearer %1").arg(m_token).toLocal8Bit() },
|
||||
}));
|
||||
}
|
||||
|
||||
SkinDelete::Ptr SkinDelete::make(QString token)
|
||||
{
|
||||
auto up = makeShared<SkinDelete>(token);
|
||||
up->m_url = QUrl("https://api.minecraftservices.com/minecraft/profile/skins/active");
|
||||
up->m_sink.reset(new Net::ByteArraySink(std::make_shared<QByteArray>()));
|
||||
return up;
|
||||
}
|
38
launcher/minecraft/skins/SkinDelete.h
Normal file
38
launcher/minecraft/skins/SkinDelete.h
Normal file
@ -0,0 +1,38 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "net/NetRequest.h"
|
||||
|
||||
class SkinDelete : public Net::NetRequest {
|
||||
Q_OBJECT
|
||||
public:
|
||||
using Ptr = shared_qobject_ptr<SkinDelete>;
|
||||
SkinDelete(QString token);
|
||||
virtual ~SkinDelete() = default;
|
||||
|
||||
static SkinDelete::Ptr make(QString token);
|
||||
void init() override;
|
||||
|
||||
protected:
|
||||
virtual QNetworkReply* getReply(QNetworkRequest&) override;
|
||||
|
||||
private:
|
||||
QString m_token;
|
||||
};
|
389
launcher/minecraft/skins/SkinList.cpp
Normal file
389
launcher/minecraft/skins/SkinList.cpp
Normal file
@ -0,0 +1,389 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "SkinList.h"
|
||||
|
||||
#include <QFileInfo>
|
||||
#include <QMimeData>
|
||||
|
||||
#include "FileSystem.h"
|
||||
#include "Json.h"
|
||||
#include "minecraft/skins/SkinModel.h"
|
||||
|
||||
SkinList::SkinList(QObject* parent, QString path, MinecraftAccountPtr acct) : QAbstractListModel(parent), m_acct(acct)
|
||||
{
|
||||
FS::ensureFolderPathExists(m_dir.absolutePath());
|
||||
m_dir.setFilter(QDir::Readable | QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs);
|
||||
m_dir.setSorting(QDir::Name | QDir::IgnoreCase | QDir::LocaleAware);
|
||||
m_watcher.reset(new QFileSystemWatcher(this));
|
||||
is_watching = false;
|
||||
connect(m_watcher.get(), &QFileSystemWatcher::directoryChanged, this, &SkinList::directoryChanged);
|
||||
connect(m_watcher.get(), &QFileSystemWatcher::fileChanged, this, &SkinList::fileChanged);
|
||||
directoryChanged(path);
|
||||
}
|
||||
|
||||
void SkinList::startWatching()
|
||||
{
|
||||
if (is_watching) {
|
||||
return;
|
||||
}
|
||||
update();
|
||||
is_watching = m_watcher->addPath(m_dir.absolutePath());
|
||||
if (is_watching) {
|
||||
qDebug() << "Started watching " << m_dir.absolutePath();
|
||||
} else {
|
||||
qDebug() << "Failed to start watching " << m_dir.absolutePath();
|
||||
}
|
||||
}
|
||||
|
||||
void SkinList::stopWatching()
|
||||
{
|
||||
save();
|
||||
if (!is_watching) {
|
||||
return;
|
||||
}
|
||||
is_watching = !m_watcher->removePath(m_dir.absolutePath());
|
||||
if (!is_watching) {
|
||||
qDebug() << "Stopped watching " << m_dir.absolutePath();
|
||||
} else {
|
||||
qDebug() << "Failed to stop watching " << m_dir.absolutePath();
|
||||
}
|
||||
}
|
||||
|
||||
bool SkinList::update()
|
||||
{
|
||||
QVector<SkinModel> newSkins;
|
||||
m_dir.refresh();
|
||||
|
||||
auto manifestInfo = QFileInfo(m_dir.absoluteFilePath("index.json"));
|
||||
if (manifestInfo.exists()) {
|
||||
try {
|
||||
auto doc = Json::requireDocument(manifestInfo.absoluteFilePath(), "SkinList JSON file");
|
||||
const auto root = doc.object();
|
||||
auto skins = Json::ensureArray(root, "skins");
|
||||
for (auto jSkin : skins) {
|
||||
SkinModel s(m_dir, Json::ensureObject(jSkin));
|
||||
if (s.isValid()) {
|
||||
newSkins << s;
|
||||
}
|
||||
}
|
||||
} catch (const Exception& e) {
|
||||
qCritical() << "Couldn't load skins json:" << e.cause();
|
||||
}
|
||||
}
|
||||
|
||||
bool needsSave = false;
|
||||
const auto& skin = m_acct->accountData()->minecraftProfile.skin;
|
||||
if (!skin.url.isEmpty() && !skin.data.isEmpty()) {
|
||||
QPixmap skinTexture;
|
||||
SkinModel* nskin = nullptr;
|
||||
for (auto i = 0; i < newSkins.size(); i++) {
|
||||
if (newSkins[i].getURL() == skin.url) {
|
||||
nskin = &newSkins[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!nskin) {
|
||||
auto name = m_acct->profileName() + ".png";
|
||||
if (QFileInfo(m_dir.absoluteFilePath(name)).exists()) {
|
||||
name = QUrl(skin.url).fileName() + ".png";
|
||||
}
|
||||
auto path = m_dir.absoluteFilePath(name);
|
||||
if (skinTexture.loadFromData(skin.data, "PNG") && skinTexture.save(path)) {
|
||||
SkinModel s(path);
|
||||
s.setModel(skin.variant.toUpper() == "SLIM" ? SkinModel::SLIM : SkinModel::CLASSIC);
|
||||
s.setCapeId(m_acct->accountData()->minecraftProfile.currentCape);
|
||||
s.setURL(skin.url);
|
||||
newSkins << s;
|
||||
needsSave = true;
|
||||
}
|
||||
} else {
|
||||
nskin->setCapeId(m_acct->accountData()->minecraftProfile.currentCape);
|
||||
nskin->setModel(skin.variant.toUpper() == "SLIM" ? SkinModel::SLIM : SkinModel::CLASSIC);
|
||||
}
|
||||
}
|
||||
|
||||
auto folderContents = m_dir.entryInfoList();
|
||||
// if there are any untracked files...
|
||||
for (QFileInfo entry : folderContents) {
|
||||
if (!entry.isFile() && entry.suffix() != "png")
|
||||
continue;
|
||||
|
||||
SkinModel w(entry.absoluteFilePath());
|
||||
if (w.isValid()) {
|
||||
auto add = true;
|
||||
for (auto s : newSkins) {
|
||||
if (s.name() == w.name()) {
|
||||
add = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (add) {
|
||||
newSkins.append(w);
|
||||
needsSave = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
std::sort(newSkins.begin(), newSkins.end(),
|
||||
[](const SkinModel& a, const SkinModel& b) { return a.getPath().localeAwareCompare(b.getPath()) < 0; });
|
||||
beginResetModel();
|
||||
m_skin_list.swap(newSkins);
|
||||
endResetModel();
|
||||
if (needsSave)
|
||||
save();
|
||||
return true;
|
||||
}
|
||||
|
||||
void SkinList::directoryChanged(const QString& path)
|
||||
{
|
||||
QDir new_dir(path);
|
||||
if (!new_dir.exists())
|
||||
if (!FS::ensureFolderPathExists(new_dir.absolutePath()))
|
||||
return;
|
||||
if (m_dir.absolutePath() != new_dir.absolutePath()) {
|
||||
m_dir.setPath(path);
|
||||
m_dir.refresh();
|
||||
if (is_watching)
|
||||
stopWatching();
|
||||
startWatching();
|
||||
}
|
||||
update();
|
||||
}
|
||||
|
||||
void SkinList::fileChanged(const QString& path)
|
||||
{
|
||||
qDebug() << "Checking " << path;
|
||||
QFileInfo checkfile(path);
|
||||
if (!checkfile.exists())
|
||||
return;
|
||||
|
||||
for (int i = 0; i < m_skin_list.count(); i++) {
|
||||
if (m_skin_list[i].getPath() == checkfile.absoluteFilePath()) {
|
||||
m_skin_list[i].refresh();
|
||||
dataChanged(index(i), index(i));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QStringList SkinList::mimeTypes() const
|
||||
{
|
||||
return { "text/uri-list" };
|
||||
}
|
||||
|
||||
Qt::DropActions SkinList::supportedDropActions() const
|
||||
{
|
||||
return Qt::CopyAction;
|
||||
}
|
||||
|
||||
bool SkinList::dropMimeData(const QMimeData* data,
|
||||
Qt::DropAction action,
|
||||
[[maybe_unused]] int row,
|
||||
[[maybe_unused]] int column,
|
||||
[[maybe_unused]] const QModelIndex& parent)
|
||||
{
|
||||
if (action == Qt::IgnoreAction)
|
||||
return true;
|
||||
// check if the action is supported
|
||||
if (!data || !(action & supportedDropActions()))
|
||||
return false;
|
||||
|
||||
// files dropped from outside?
|
||||
if (data->hasUrls()) {
|
||||
auto urls = data->urls();
|
||||
QStringList skinFiles;
|
||||
for (auto url : urls) {
|
||||
// only local files may be dropped...
|
||||
if (!url.isLocalFile())
|
||||
continue;
|
||||
skinFiles << url.toLocalFile();
|
||||
}
|
||||
installSkins(skinFiles);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Qt::ItemFlags SkinList::flags(const QModelIndex& index) const
|
||||
{
|
||||
Qt::ItemFlags f = Qt::ItemIsDropEnabled | QAbstractListModel::flags(index);
|
||||
if (index.isValid()) {
|
||||
f |= (Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable);
|
||||
}
|
||||
return f;
|
||||
}
|
||||
|
||||
QVariant SkinList::data(const QModelIndex& index, int role) const
|
||||
{
|
||||
if (!index.isValid())
|
||||
return QVariant();
|
||||
|
||||
int row = index.row();
|
||||
|
||||
if (row < 0 || row >= m_skin_list.size())
|
||||
return QVariant();
|
||||
auto skin = m_skin_list[row];
|
||||
switch (role) {
|
||||
case Qt::DecorationRole:
|
||||
return skin.getTexture();
|
||||
case Qt::DisplayRole:
|
||||
return skin.name();
|
||||
case Qt::UserRole:
|
||||
return skin.name();
|
||||
case Qt::EditRole:
|
||||
return skin.name();
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
int SkinList::rowCount(const QModelIndex& parent) const
|
||||
{
|
||||
return parent.isValid() ? 0 : m_skin_list.size();
|
||||
}
|
||||
|
||||
void SkinList::installSkins(const QStringList& iconFiles)
|
||||
{
|
||||
for (QString file : iconFiles)
|
||||
installSkin(file);
|
||||
}
|
||||
|
||||
QString SkinList::installSkin(const QString& file, const QString& name)
|
||||
{
|
||||
if (file.isEmpty())
|
||||
return tr("Path is empty.");
|
||||
QFileInfo fileinfo(file);
|
||||
if (!fileinfo.exists())
|
||||
return tr("File doesn't exist.");
|
||||
if (!fileinfo.isFile())
|
||||
return tr("Not a file.");
|
||||
if (!fileinfo.isReadable())
|
||||
return tr("File is not readable.");
|
||||
if (fileinfo.suffix() != "png" && !SkinModel(fileinfo.absoluteFilePath()).isValid())
|
||||
return tr("Skin images must be 64x64 or 64x32 pixel PNG files.");
|
||||
|
||||
QString target = FS::PathCombine(m_dir.absolutePath(), name.isEmpty() ? fileinfo.fileName() : name);
|
||||
|
||||
return QFile::copy(file, target) ? "" : tr("Unable to copy file");
|
||||
}
|
||||
|
||||
int SkinList::getSkinIndex(const QString& key) const
|
||||
{
|
||||
for (int i = 0; i < m_skin_list.count(); i++) {
|
||||
if (m_skin_list[i].name() == key) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
const SkinModel* SkinList::skin(const QString& key) const
|
||||
{
|
||||
int idx = getSkinIndex(key);
|
||||
if (idx == -1)
|
||||
return nullptr;
|
||||
return &m_skin_list[idx];
|
||||
}
|
||||
|
||||
SkinModel* SkinList::skin(const QString& key)
|
||||
{
|
||||
int idx = getSkinIndex(key);
|
||||
if (idx == -1)
|
||||
return nullptr;
|
||||
return &m_skin_list[idx];
|
||||
}
|
||||
|
||||
bool SkinList::deleteSkin(const QString& key, const bool trash)
|
||||
{
|
||||
int idx = getSkinIndex(key);
|
||||
if (idx != -1) {
|
||||
auto s = m_skin_list[idx];
|
||||
if (trash) {
|
||||
if (FS::trash(s.getPath(), nullptr)) {
|
||||
m_skin_list.remove(idx);
|
||||
save();
|
||||
return true;
|
||||
}
|
||||
} else if (QFile::remove(s.getPath())) {
|
||||
m_skin_list.remove(idx);
|
||||
save();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void SkinList::save()
|
||||
{
|
||||
QJsonObject doc;
|
||||
QJsonArray arr;
|
||||
for (auto s : m_skin_list) {
|
||||
arr << s.toJSON();
|
||||
}
|
||||
doc["skins"] = arr;
|
||||
Json::write(doc, m_dir.absoluteFilePath("index.json"));
|
||||
}
|
||||
|
||||
int SkinList::getSelectedAccountSkin()
|
||||
{
|
||||
const auto& skin = m_acct->accountData()->minecraftProfile.skin;
|
||||
for (int i = 0; i < m_skin_list.count(); i++) {
|
||||
if (m_skin_list[i].getURL() == skin.url) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool SkinList::setData(const QModelIndex& idx, const QVariant& value, int role)
|
||||
{
|
||||
if (!idx.isValid() || role != Qt::EditRole) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int row = idx.row();
|
||||
if (row < 0 || row >= m_skin_list.size())
|
||||
return false;
|
||||
auto& skin = m_skin_list[row];
|
||||
auto newName = value.toString();
|
||||
if (skin.name() != newName) {
|
||||
skin.rename(newName);
|
||||
save();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void SkinList::updateSkin(SkinModel* s)
|
||||
{
|
||||
auto done = false;
|
||||
for (auto i = 0; i < m_skin_list.size(); i++) {
|
||||
if (m_skin_list[i].getPath() == s->getPath()) {
|
||||
m_skin_list[i].setCapeId(s->getCapeId());
|
||||
m_skin_list[i].setModel(s->getModel());
|
||||
m_skin_list[i].setURL(s->getURL());
|
||||
done = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!done) {
|
||||
beginInsertRows(QModelIndex(), m_skin_list.count(), m_skin_list.count() + 1);
|
||||
m_skin_list.append(*s);
|
||||
endInsertRows();
|
||||
}
|
||||
save();
|
||||
}
|
80
launcher/minecraft/skins/SkinList.h
Normal file
80
launcher/minecraft/skins/SkinList.h
Normal file
@ -0,0 +1,80 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QAbstractListModel>
|
||||
#include <QDir>
|
||||
#include <QFileSystemWatcher>
|
||||
|
||||
#include "QObjectPtr.h"
|
||||
#include "SkinModel.h"
|
||||
#include "minecraft/auth/MinecraftAccount.h"
|
||||
|
||||
class SkinList : public QAbstractListModel {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit SkinList(QObject* parent, QString path, MinecraftAccountPtr acct);
|
||||
virtual ~SkinList() { save(); };
|
||||
|
||||
int getSkinIndex(const QString& key) const;
|
||||
|
||||
virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
|
||||
bool setData(const QModelIndex& idx, const QVariant& value, int role) override;
|
||||
virtual int rowCount(const QModelIndex& parent = QModelIndex()) const override;
|
||||
|
||||
virtual QStringList mimeTypes() const override;
|
||||
virtual Qt::DropActions supportedDropActions() const override;
|
||||
virtual bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) override;
|
||||
virtual Qt::ItemFlags flags(const QModelIndex& index) const override;
|
||||
|
||||
bool deleteSkin(const QString& key, const bool trash);
|
||||
|
||||
void installSkins(const QStringList& iconFiles);
|
||||
QString installSkin(const QString& file, const QString& name = {});
|
||||
|
||||
const SkinModel* skin(const QString& key) const;
|
||||
SkinModel* skin(const QString& key);
|
||||
|
||||
void startWatching();
|
||||
void stopWatching();
|
||||
|
||||
QString getDir() const { return m_dir.absolutePath(); }
|
||||
void save();
|
||||
int getSelectedAccountSkin();
|
||||
|
||||
void updateSkin(SkinModel* s);
|
||||
|
||||
private:
|
||||
// hide copy constructor
|
||||
SkinList(const SkinList&) = delete;
|
||||
// hide assign op
|
||||
SkinList& operator=(const SkinList&) = delete;
|
||||
|
||||
protected slots:
|
||||
void directoryChanged(const QString& path);
|
||||
void fileChanged(const QString& path);
|
||||
bool update();
|
||||
|
||||
private:
|
||||
shared_qobject_ptr<QFileSystemWatcher> m_watcher;
|
||||
bool is_watching;
|
||||
QVector<SkinModel> m_skin_list;
|
||||
QDir m_dir;
|
||||
MinecraftAccountPtr m_acct;
|
||||
};
|
78
launcher/minecraft/skins/SkinModel.cpp
Normal file
78
launcher/minecraft/skins/SkinModel.cpp
Normal file
@ -0,0 +1,78 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "SkinModel.h"
|
||||
#include <QFileInfo>
|
||||
#include <QImage>
|
||||
#include <QPainter>
|
||||
#include <QTransform>
|
||||
|
||||
#include "FileSystem.h"
|
||||
#include "Json.h"
|
||||
|
||||
SkinModel::SkinModel(QString path) : m_path(path), m_texture(path), m_model(Model::CLASSIC) {}
|
||||
|
||||
SkinModel::SkinModel(QDir skinDir, QJsonObject obj)
|
||||
: m_cape_id(Json::ensureString(obj, "capeId")), m_model(Model::CLASSIC), m_url(Json::ensureString(obj, "url"))
|
||||
{
|
||||
auto name = Json::ensureString(obj, "name");
|
||||
|
||||
if (auto model = Json::ensureString(obj, "model"); model == "SLIM") {
|
||||
m_model = Model::SLIM;
|
||||
}
|
||||
m_path = skinDir.absoluteFilePath(name) + ".png";
|
||||
m_texture = QPixmap(m_path);
|
||||
}
|
||||
|
||||
QString SkinModel::name() const
|
||||
{
|
||||
return QFileInfo(m_path).baseName();
|
||||
}
|
||||
|
||||
bool SkinModel::rename(QString newName)
|
||||
{
|
||||
auto info = QFileInfo(m_path);
|
||||
m_path = FS::PathCombine(info.absolutePath(), newName + ".png");
|
||||
return FS::move(info.absoluteFilePath(), m_path);
|
||||
}
|
||||
|
||||
QJsonObject SkinModel::toJSON() const
|
||||
{
|
||||
QJsonObject obj;
|
||||
obj["name"] = name();
|
||||
obj["capeId"] = m_cape_id;
|
||||
obj["url"] = m_url;
|
||||
obj["model"] = getModelString();
|
||||
return obj;
|
||||
}
|
||||
|
||||
QString SkinModel::getModelString() const
|
||||
{
|
||||
switch (m_model) {
|
||||
case CLASSIC:
|
||||
return "CLASSIC";
|
||||
case SLIM:
|
||||
return "SLIM";
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
bool SkinModel::isValid() const
|
||||
{
|
||||
return !m_texture.isNull() && (m_texture.size().height() == 32 || m_texture.size().height() == 64) && m_texture.size().width() == 64;
|
||||
}
|
57
launcher/minecraft/skins/SkinModel.h
Normal file
57
launcher/minecraft/skins/SkinModel.h
Normal file
@ -0,0 +1,57 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QDir>
|
||||
#include <QJsonObject>
|
||||
#include <QPixmap>
|
||||
|
||||
class SkinModel {
|
||||
public:
|
||||
enum Model { CLASSIC, SLIM };
|
||||
|
||||
SkinModel() = default;
|
||||
SkinModel(QString path);
|
||||
SkinModel(QDir skinDir, QJsonObject obj);
|
||||
virtual ~SkinModel() = default;
|
||||
|
||||
QString name() const;
|
||||
QString getModelString() const;
|
||||
bool isValid() const;
|
||||
QString getPath() const { return m_path; }
|
||||
QPixmap getTexture() const { return m_texture; }
|
||||
QString getCapeId() const { return m_cape_id; }
|
||||
Model getModel() const { return m_model; }
|
||||
QString getURL() const { return m_url; }
|
||||
|
||||
bool rename(QString newName);
|
||||
void setCapeId(QString capeID) { m_cape_id = capeID; }
|
||||
void setModel(Model model) { m_model = model; }
|
||||
void setURL(QString url) { m_url = url; }
|
||||
void refresh() { m_texture = QPixmap(m_path); }
|
||||
|
||||
QJsonObject toJSON() const;
|
||||
|
||||
private:
|
||||
QString m_path;
|
||||
QPixmap m_texture;
|
||||
QString m_cape_id;
|
||||
Model m_model;
|
||||
QString m_url;
|
||||
};
|
84
launcher/minecraft/skins/SkinUpload.cpp
Normal file
84
launcher/minecraft/skins/SkinUpload.cpp
Normal file
@ -0,0 +1,84 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* This file incorporates work covered by the following copyright and
|
||||
* permission notice:
|
||||
*
|
||||
* Copyright 2013-2021 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "SkinUpload.h"
|
||||
|
||||
#include <QHttpMultiPart>
|
||||
|
||||
#include "FileSystem.h"
|
||||
#include "net/ByteArraySink.h"
|
||||
#include "net/StaticHeaderProxy.h"
|
||||
|
||||
SkinUpload::SkinUpload(QString token, QString path, QString variant) : NetRequest(), m_token(token), m_path(path), m_variant(variant)
|
||||
{
|
||||
logCat = taskMCSkinsLogC;
|
||||
}
|
||||
|
||||
QNetworkReply* SkinUpload::getReply(QNetworkRequest& request)
|
||||
{
|
||||
QHttpMultiPart* multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType, this);
|
||||
|
||||
QHttpPart skin;
|
||||
skin.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("image/png"));
|
||||
skin.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"file\"; filename=\"skin.png\""));
|
||||
|
||||
skin.setBody(FS::read(m_path));
|
||||
|
||||
QHttpPart model;
|
||||
model.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"variant\""));
|
||||
model.setBody(m_variant.toUtf8());
|
||||
|
||||
multiPart->append(skin);
|
||||
multiPart->append(model);
|
||||
setStatus(tr("Uploading skin"));
|
||||
return m_network->post(request, multiPart);
|
||||
}
|
||||
|
||||
void SkinUpload::init()
|
||||
{
|
||||
addHeaderProxy(new Net::StaticHeaderProxy(QList<Net::HeaderPair>{
|
||||
{ "Authorization", QString("Bearer %1").arg(m_token).toLocal8Bit() },
|
||||
}));
|
||||
}
|
||||
|
||||
SkinUpload::Ptr SkinUpload::make(QString token, QString path, QString variant)
|
||||
{
|
||||
auto up = makeShared<SkinUpload>(token, path, variant);
|
||||
up->m_url = QUrl("https://api.minecraftservices.com/minecraft/profile/skins");
|
||||
up->setObjectName(QString("BYTES:") + up->m_url.toString());
|
||||
up->m_sink.reset(new Net::ByteArraySink(std::make_shared<QByteArray>()));
|
||||
return up;
|
||||
}
|
42
launcher/minecraft/skins/SkinUpload.h
Normal file
42
launcher/minecraft/skins/SkinUpload.h
Normal file
@ -0,0 +1,42 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "net/NetRequest.h"
|
||||
|
||||
class SkinUpload : public Net::NetRequest {
|
||||
Q_OBJECT
|
||||
public:
|
||||
using Ptr = shared_qobject_ptr<SkinUpload>;
|
||||
|
||||
// Note this class takes ownership of the file.
|
||||
SkinUpload(QString token, QString path, QString variant);
|
||||
virtual ~SkinUpload() = default;
|
||||
|
||||
static SkinUpload::Ptr make(QString token, QString path, QString variant);
|
||||
void init() override;
|
||||
|
||||
protected:
|
||||
virtual QNetworkReply* getReply(QNetworkRequest&) override;
|
||||
|
||||
private:
|
||||
QString m_token;
|
||||
QString m_path;
|
||||
QString m_variant;
|
||||
};
|
Reference in New Issue
Block a user