Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
This commit is contained in:
Trial97 2023-06-23 20:01:17 +03:00
commit 69c709b05a
No known key found for this signature in database
GPG Key ID: 55EF5DA53DB36318
95 changed files with 1872 additions and 936 deletions

View File

@ -191,7 +191,7 @@ jobs:
if: runner.os == 'Linux' if: runner.os == 'Linux'
run: | run: |
sudo apt-get -y update sudo apt-get -y update
sudo apt-get -y install ninja-build extra-cmake-modules scdoc sudo apt-get -y install ninja-build extra-cmake-modules scdoc appstream
- name: Install Dependencies (macOS) - name: Install Dependencies (macOS)
if: runner.os == 'macOS' if: runner.os == 'macOS'
@ -250,6 +250,7 @@ jobs:
wget "https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage" wget "https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage"
${{ github.workspace }}/.github/scripts/prepare_JREs.sh ${{ github.workspace }}/.github/scripts/prepare_JREs.sh
sudo apt install libopengl0
- name: Add QT_HOST_PATH var (Windows MSVC arm64) - name: Add QT_HOST_PATH var (Windows MSVC arm64)
if: runner.os == 'Windows' && matrix.architecture == 'arm64' if: runner.os == 'Windows' && matrix.architecture == 'arm64'
@ -467,7 +468,8 @@ jobs:
shell: bash shell: bash
run: | run: |
cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_APPIMAGE_DIR }}/usr cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_APPIMAGE_DIR }}/usr
mv ${{ env.INSTALL_APPIMAGE_DIR }}/usr/share/metainfo/org.prismlauncher.PrismLauncher.metainfo.xml ${{ env.INSTALL_APPIMAGE_DIR }}/usr/share/metainfo/org.prismlauncher.PrismLauncher.appdata.xml
export "NO_APPSTREAM=1" # we have to skip appstream checking because appstream on ubuntu 20.04 is outdated
export OUTPUT="PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage" export OUTPUT="PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage"
chmod +x linuxdeploy-*.AppImage chmod +x linuxdeploy-*.AppImage
@ -482,7 +484,8 @@ jobs:
cp -r /home/runner/work/PrismLauncher/Qt/${{ matrix.qt_version }}/gcc_64/plugins/iconengines/* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/plugins/iconengines cp -r /home/runner/work/PrismLauncher/Qt/${{ matrix.qt_version }}/gcc_64/plugins/iconengines/* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/plugins/iconengines
cp /usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/ cp /usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/
cp /usr/lib/x86_64-linux-gnu/libssl.so.1.1 ${{ env.INSTALL_APPIMAGE_DIR }}//usr/lib/ cp /usr/lib/x86_64-linux-gnu/libssl.so.1.1 ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/
cp /usr/lib/x86_64-linux-gnu/libOpenGL.so.0* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/
LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib" LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib"
LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/jvm/java-8-openjdk/lib/amd64/server" LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/jvm/java-8-openjdk/lib/amd64/server"
@ -599,7 +602,7 @@ jobs:
submodules: 'true' submodules: 'true'
- name: Install nix - name: Install nix
if: inputs.build_type == 'Debug' if: inputs.build_type == 'Debug'
uses: cachix/install-nix-action@v21 uses: cachix/install-nix-action@v22
with: with:
install_url: https://nixos.org/nix/install install_url: https://nixos.org/nix/install
extra_nix_config: | extra_nix_config: |

View File

@ -102,7 +102,7 @@ namespace fs = ghc::filesystem;
#include <linux/fs.h> #include <linux/fs.h>
#include <sys/ioctl.h> #include <sys/ioctl.h>
#include <unistd.h> #include <unistd.h>
#elif defined(Q_OS_MACOS) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD) #elif defined(Q_OS_MACOS) || defined(Q_OS_OPENBSD)
#include <sys/attr.h> #include <sys/attr.h>
#include <sys/clonefile.h> #include <sys/clonefile.h>
#elif defined(Q_OS_WIN) #elif defined(Q_OS_WIN)
@ -1151,7 +1151,7 @@ bool clone_file(const QString& src, const QString& dst, std::error_code& ec)
return false; return false;
} }
#elif defined(Q_OS_MACOS) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD) #elif defined(Q_OS_MACOS) || defined(Q_OS_OPENBSD)
if (!macos_bsd_clonefile(src_path, dst_path, ec)) { if (!macos_bsd_clonefile(src_path, dst_path, ec)) {
qDebug() << "failed macos_bsd_clonefile:"; qDebug() << "failed macos_bsd_clonefile:";
@ -1380,7 +1380,7 @@ bool linux_ficlone(const std::string& src_path, const std::string& dst_path, std
return true; return true;
} }
#elif defined(Q_OS_MACOS) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD) #elif defined(Q_OS_MACOS) || defined(Q_OS_OPENBSD)
bool macos_bsd_clonefile(const std::string& src_path, const std::string& dst_path, std::error_code& ec) bool macos_bsd_clonefile(const std::string& src_path, const std::string& dst_path, std::error_code& ec)
{ {

View File

@ -3,6 +3,8 @@
#include <QCoreApplication> #include <QCoreApplication>
#include <QPixmapCache> #include <QPixmapCache>
#include <QThread> #include <QThread>
#include <QTime>
#include <QDebug>
#define GET_TYPE() \ #define GET_TYPE() \
Qt::ConnectionType type; \ Qt::ConnectionType type; \
@ -60,6 +62,8 @@ class PixmapCache final : public QObject {
DEFINE_FUNC_ONE_PARAM(remove, bool, const QPixmapCache::Key&) DEFINE_FUNC_ONE_PARAM(remove, bool, const QPixmapCache::Key&)
DEFINE_FUNC_TWO_PARAM(replace, bool, const QPixmapCache::Key&, const QPixmap&) DEFINE_FUNC_TWO_PARAM(replace, bool, const QPixmapCache::Key&, const QPixmap&)
DEFINE_FUNC_ONE_PARAM(setCacheLimit, bool, int) DEFINE_FUNC_ONE_PARAM(setCacheLimit, bool, int)
DEFINE_FUNC_NO_PARAM(markCacheMissByEviciton, bool)
DEFINE_FUNC_ONE_PARAM(setFastEvictionThreshold, bool, int)
// NOTE: Every function returns something non-void to simplify the macros. // NOTE: Every function returns something non-void to simplify the macros.
private slots: private slots:
@ -90,6 +94,43 @@ class PixmapCache final : public QObject {
return true; return true;
} }
/**
* Mark that a cache miss occurred because of a eviction if too many of these occur too fast the cache size is increased
* @return if the cache size was increased
*/
bool _markCacheMissByEviciton()
{
auto now = QTime::currentTime();
if (!m_last_cache_miss_by_eviciton.isNull()) {
auto diff = m_last_cache_miss_by_eviciton.msecsTo(now);
if (diff < 1000) { // less than a second ago
++m_consecutive_fast_evicitons;
} else {
m_consecutive_fast_evicitons = 0;
}
}
m_last_cache_miss_by_eviciton = now;
if (m_consecutive_fast_evicitons >= m_consecutive_fast_evicitons_threshold) {
// double the cache size
auto newSize = _cacheLimit() * 2;
qDebug() << m_consecutive_fast_evicitons << "pixmap cache misses by eviction happened too fast, doubling cache size to"
<< newSize;
_setCacheLimit(newSize);
m_consecutive_fast_evicitons = 0;
return true;
}
return false;
}
bool _setFastEvictionThreshold(int threshold)
{
m_consecutive_fast_evicitons_threshold = threshold;
return true;
}
private: private:
static PixmapCache* s_instance; static PixmapCache* s_instance;
QTime m_last_cache_miss_by_eviciton;
int m_consecutive_fast_evicitons = 0;
int m_consecutive_fast_evicitons_threshold = 15;
}; };

View File

@ -85,7 +85,7 @@ void JavaChecker::performCheck()
process->setProgram(m_path); process->setProgram(m_path);
process->setProcessChannelMode(QProcess::SeparateChannels); process->setProcessChannelMode(QProcess::SeparateChannels);
process->setProcessEnvironment(CleanEnviroment()); process->setProcessEnvironment(CleanEnviroment());
qDebug() << "Running java checker: " + m_path + args.join(" ");; qDebug() << "Running java checker:" << m_path << args.join(" ");
connect(process.get(), QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this, &JavaChecker::finished); connect(process.get(), QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this, &JavaChecker::finished);
connect(process.get(), &QProcess::errorOccurred, this, &JavaChecker::error); connect(process.get(), &QProcess::errorOccurred, this, &JavaChecker::error);
@ -128,7 +128,7 @@ void JavaChecker::finished(int exitcode, QProcess::ExitStatus status)
result.outLog = m_stdout; result.outLog = m_stdout;
qDebug() << "STDOUT" << m_stdout; qDebug() << "STDOUT" << m_stdout;
qWarning() << "STDERR" << m_stderr; qWarning() << "STDERR" << m_stderr;
qDebug() << "Java checker finished with status " << status << " exit code " << exitcode; qDebug() << "Java checker finished with status" << status << "exit code" << exitcode;
if (status == QProcess::CrashExit || exitcode == 1) if (status == QProcess::CrashExit || exitcode == 1)
{ {

View File

@ -333,13 +333,13 @@ QVariant AccountList::data(const QModelIndex &index, int role) const
case MigrationColumn: { case MigrationColumn: {
if(account->isMSA() || account->isOffline()) { if(account->isMSA() || account->isOffline()) {
return tr("N/A", "Can Migrate?"); return tr("N/A", "Can Migrate");
} }
if (account->canMigrate()) { if (account->canMigrate()) {
return tr("Yes", "Can Migrate?"); return tr("Yes", "Can Migrate");
} }
else { else {
return tr("No", "Can Migrate?"); return tr("No", "Can Migrate");
} }
} }

View File

@ -41,9 +41,11 @@
#include <QString> #include <QString>
#include <QRegularExpression> #include <QRegularExpression>
#include "MTPixmapCache.h"
#include "MetadataHandler.h" #include "MetadataHandler.h"
#include "Version.h" #include "Version.h"
#include "minecraft/mod/ModDetails.h" #include "minecraft/mod/ModDetails.h"
#include "minecraft/mod/tasks/LocalModParseTask.h"
static ModPlatform::ProviderCapabilities ProviderCaps; static ModPlatform::ProviderCapabilities ProviderCaps;
@ -201,7 +203,10 @@ void Mod::finishResolvingWithDetails(ModDetails&& details)
m_local_details = std::move(details); m_local_details = std::move(details);
if (metadata) if (metadata)
setMetadata(std::move(metadata)); setMetadata(std::move(metadata));
}; if (!iconPath().isEmpty()) {
m_pack_image_cache_key.was_read_attempt = false;
}
}
auto Mod::provider() const -> std::optional<QString> auto Mod::provider() const -> std::optional<QString>
{ {
@ -210,6 +215,56 @@ auto Mod::provider() const -> std::optional<QString>
return {}; return {};
} }
auto Mod::licenses() const -> const QList<ModLicense>&
{
return details().licenses;
}
auto Mod::issueTracker() const -> QString
{
return details().issue_tracker;
}
void 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);
// scale the image to avoid flooding the pixmapcache
auto pixmap = QPixmap::fromImage(new_image.scaled({64, 64}, Qt::AspectRatioMode::KeepAspectRatioByExpanding));
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;
}
QPixmap Mod::icon(QSize size, Qt::AspectRatioMode mode) const
{
QPixmap cached_image;
if (PixmapCache::find(m_pack_image_cache_key.key, &cached_image)) {
if (size.isNull())
return cached_image;
return cached_image.scaled(size, mode);
}
// 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())
return {};
if (m_pack_image_cache_key.was_ever_used) {
qDebug() << "Mod" << name() << "Had it's icon evicted form 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);
}
bool Mod::valid() const bool Mod::valid() const
{ {
return !m_local_details.mod_id.isEmpty(); return !m_local_details.mod_id.isEmpty();

View File

@ -38,6 +38,10 @@
#include <QDateTime> #include <QDateTime>
#include <QFileInfo> #include <QFileInfo>
#include <QList> #include <QList>
#include <QImage>
#include <QMutex>
#include <QPixmap>
#include <QPixmapCache>
#include <optional> #include <optional>
@ -64,6 +68,15 @@ public:
auto authors() const -> QStringList; auto authors() const -> QStringList;
auto status() const -> ModStatus; auto status() const -> ModStatus;
auto provider() const -> std::optional<QString>; auto provider() const -> std::optional<QString>;
auto licenses() const -> const QList<ModLicense>&;
auto issueTracker() 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;
auto metadata() -> std::shared_ptr<Metadata::ModStruct>; auto metadata() -> std::shared_ptr<Metadata::ModStruct>;
auto metadata() const -> const std::shared_ptr<Metadata::ModStruct>; auto metadata() const -> const std::shared_ptr<Metadata::ModStruct>;
@ -85,4 +98,13 @@ public:
protected: protected:
ModDetails m_local_details; ModDetails m_local_details;
mutable QMutex m_data_lock;
struct {
QPixmapCache::Key key;
bool was_ever_used = false;
bool was_read_attempt = false;
} mutable m_pack_image_cache_key;
}; };

View File

@ -39,6 +39,7 @@
#include <QString> #include <QString>
#include <QStringList> #include <QStringList>
#include <QUrl>
#include "minecraft/mod/MetadataHandler.h" #include "minecraft/mod/MetadataHandler.h"
@ -49,6 +50,84 @@ enum class ModStatus {
Unknown, // Default status Unknown, // Default status
}; };
struct ModLicense {
QString name = {};
QString id = {};
QString url = {};
QString description = {};
ModLicense() {}
ModLicense(const QString license) {
// FIXME: come up with a better license parseing.
// handle SPDX identifiers? https://spdx.org/licenses/
auto parts = license.split(' ');
QStringList notNameParts = {};
for (auto part : parts) {
auto url = QUrl(part);
if (part.startsWith("(") && part.endsWith(")"))
url = QUrl(part.mid(1, part.size() - 2));
if (url.isValid() && !url.scheme().isEmpty() && !url.host().isEmpty()) {
this->url = url.toString();
notNameParts.append(part);
continue;
}
}
for (auto part : notNameParts) {
parts.removeOne(part);
}
auto licensePart = parts.join(' ');
this->name = licensePart;
this->description = licensePart;
if (parts.size() == 1) {
this->id = parts.first();
}
}
ModLicense(const QString name, const QString id, const QString url, const QString description) {
this->name = name;
this->id = id;
this->url = url;
this->description = description;
}
ModLicense(const ModLicense& other)
: name(other.name)
, id(other.id)
, url(other.url)
, description(other.description)
{}
ModLicense& operator=(const ModLicense& other)
{
this->name = other.name;
this->id = other.id;
this->url = other.url;
this->description = other.description;
return *this;
}
ModLicense& operator=(const ModLicense&& other)
{
this->name = other.name;
this->id = other.id;
this->url = other.url;
this->description = other.description;
return *this;
}
bool isEmpty() {
return this->name.isEmpty() && this->id.isEmpty() && this->url.isEmpty() && this->description.isEmpty();
}
};
struct ModDetails struct ModDetails
{ {
/* Mod ID as defined in the ModLoader-specific metadata */ /* Mod ID as defined in the ModLoader-specific metadata */
@ -72,6 +151,15 @@ struct ModDetails
/* List of the author's names */ /* List of the author's names */
QStringList authors = {}; QStringList authors = {};
/* Issue Tracker URL */
QString issue_tracker = {};
/* License */
QList<ModLicense> licenses = {};
/* Path of mod logo */
QString icon_file = {};
/* Installation status of the mod */ /* Installation status of the mod */
ModStatus status = ModStatus::Unknown; ModStatus status = ModStatus::Unknown;
@ -89,6 +177,9 @@ struct ModDetails
, homeurl(other.homeurl) , homeurl(other.homeurl)
, description(other.description) , description(other.description)
, authors(other.authors) , authors(other.authors)
, issue_tracker(other.issue_tracker)
, licenses(other.licenses)
, icon_file(other.icon_file)
, status(other.status) , status(other.status)
{} {}
@ -101,6 +192,9 @@ struct ModDetails
this->homeurl = other.homeurl; this->homeurl = other.homeurl;
this->description = other.description; this->description = other.description;
this->authors = other.authors; this->authors = other.authors;
this->issue_tracker = other.issue_tracker;
this->licenses = other.licenses;
this->icon_file = other.icon_file;
this->status = other.status; this->status = other.status;
return *this; return *this;
@ -115,6 +209,9 @@ struct ModDetails
this->homeurl = other.homeurl; this->homeurl = other.homeurl;
this->description = other.description; this->description = other.description;
this->authors = other.authors; this->authors = other.authors;
this->issue_tracker = other.issue_tracker;
this->licenses = other.licenses;
this->icon_file = other.icon_file;
this->status = other.status; this->status = other.status;
return *this; return *this;

View File

@ -37,6 +37,7 @@
#include "ModFolderModel.h" #include "ModFolderModel.h"
#include <FileSystem.h> #include <FileSystem.h>
#include <qheaderview.h>
#include <QDebug> #include <QDebug>
#include <QFileSystemWatcher> #include <QFileSystemWatcher>
#include <QIcon> #include <QIcon>
@ -52,12 +53,14 @@
#include "minecraft/mod/tasks/LocalModParseTask.h" #include "minecraft/mod/tasks/LocalModParseTask.h"
#include "minecraft/mod/tasks/ModFolderLoadTask.h" #include "minecraft/mod/tasks/ModFolderLoadTask.h"
#include "modplatform/ModIndex.h"
ModFolderModel::ModFolderModel(const QString& dir, BaseInstance* instance, bool is_indexed, bool create_dir) 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) : ResourceFolderModel(QDir(dir), instance, nullptr, create_dir), m_is_indexed(is_indexed)
{ {
m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::VERSION, SortType::DATE, SortType::PROVIDER }; m_column_names = QStringList({ "Enable", "Image", "Name", "Version", "Last Modified", "Provider" });
m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Version"), tr("Last Modified"), tr("Provider") });
m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME , SortType::VERSION, SortType::DATE, SortType::PROVIDER};
m_column_resize_modes = { QHeaderView::ResizeToContents, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::ResizeToContents, QHeaderView::ResizeToContents, QHeaderView::ResizeToContents};
} }
QVariant ModFolderModel::data(const QModelIndex &index, int role) const QVariant ModFolderModel::data(const QModelIndex &index, int role) const
@ -118,7 +121,9 @@ QVariant ModFolderModel::data(const QModelIndex &index, int role) const
case Qt::DecorationRole: { case Qt::DecorationRole: {
if (column == NAME_COLUMN && (at(row)->isSymLinkUnder(instDirPath()) || at(row)->isMoreThanOneHardLink())) if (column == NAME_COLUMN && (at(row)->isSymLinkUnder(instDirPath()) || at(row)->isMoreThanOneHardLink()))
return APPLICATION->getThemedIcon("status-yellow"); return APPLICATION->getThemedIcon("status-yellow");
if (column == ImageColumn) {
return at(row)->icon({32, 32}, Qt::AspectRatioMode::KeepAspectRatioByExpanding);
}
return {}; return {};
} }
case Qt::CheckStateRole: case Qt::CheckStateRole:
@ -142,15 +147,12 @@ QVariant ModFolderModel::headerData(int section, Qt::Orientation orientation, in
switch (section) switch (section)
{ {
case ActiveColumn: case ActiveColumn:
return QString();
case NameColumn: case NameColumn:
return tr("Name");
case VersionColumn: case VersionColumn:
return tr("Version");
case DateColumn: case DateColumn:
return tr("Last changed");
case ProviderColumn: case ProviderColumn:
return tr("Provider"); case ImageColumn:
return columnNames().at(section);
default: default:
return QVariant(); return QVariant();
} }

View File

@ -64,6 +64,7 @@ public:
enum Columns enum Columns
{ {
ActiveColumn = 0, ActiveColumn = 0,
ImageColumn,
NameColumn, NameColumn,
VersionColumn, VersionColumn,
DateColumn, DateColumn,
@ -77,6 +78,8 @@ public:
}; };
ModFolderModel(const QString &dir, BaseInstance* instance, bool is_indexed = false, bool create_dir = true); ModFolderModel(const QString &dir, BaseInstance* instance, bool is_indexed = false, bool create_dir = true);
virtual QString id() const override { return "mods"; }
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;

View File

@ -8,12 +8,15 @@
#include <QStyle> #include <QStyle>
#include <QThreadPool> #include <QThreadPool>
#include <QUrl> #include <QUrl>
#include <QMenu>
#include "Application.h" #include "Application.h"
#include "FileSystem.h" #include "FileSystem.h"
#include "QVariantUtils.h"
#include "minecraft/mod/tasks/BasicFolderLoadTask.h" #include "minecraft/mod/tasks/BasicFolderLoadTask.h"
#include "settings/Setting.h"
#include "tasks/Task.h" #include "tasks/Task.h"
ResourceFolderModel::ResourceFolderModel(QDir dir, BaseInstance* instance, QObject* parent, bool create_dir) ResourceFolderModel::ResourceFolderModel(QDir dir, BaseInstance* instance, QObject* parent, bool create_dir)
@ -471,10 +474,10 @@ QVariant ResourceFolderModel::headerData(int section, Qt::Orientation orientatio
switch (role) { switch (role) {
case Qt::DisplayRole: case Qt::DisplayRole:
switch (section) { switch (section) {
case ACTIVE_COLUMN:
case NAME_COLUMN: case NAME_COLUMN:
return tr("Name");
case DATE_COLUMN: case DATE_COLUMN:
return tr("Last modified"); return columnNames().at(section);
default: default:
return {}; return {};
} }
@ -500,6 +503,75 @@ QVariant ResourceFolderModel::headerData(int section, Qt::Orientation orientatio
return {}; return {};
} }
void ResourceFolderModel::setupHeaderAction(QAction* act, int column)
{
Q_ASSERT(act);
act->setText(columnNames().at(column));
}
void ResourceFolderModel::saveHiddenColumn(int column, bool hidden)
{
auto const setting_name = QString("UI/%1_Page/HiddenColumns").arg(id());
auto setting = (m_instance->settings()->contains(setting_name)) ?
m_instance->settings()->getSetting(setting_name) : m_instance->settings()->registerSetting(setting_name);
auto hiddenColumns = setting->get().toStringList();
auto name = columnNames(false).at(column);
auto index = hiddenColumns.indexOf(name);
if (index >= 0 && !hidden) {
hiddenColumns.removeAt(index);
} else if ( index < 0 && hidden) {
hiddenColumns.append(name);
}
setting->set(hiddenColumns);
}
void ResourceFolderModel::loadHiddenColumns(QTreeView *tree)
{
auto const setting_name = QString("UI/%1_Page/HiddenColumns").arg(id());
auto setting = (m_instance->settings()->contains(setting_name)) ?
m_instance->settings()->getSetting(setting_name) : m_instance->settings()->registerSetting(setting_name);
auto hiddenColumns = setting->get().toStringList();
auto col_names = columnNames(false);
for (auto col_name : hiddenColumns) {
auto index = col_names.indexOf(col_name);
if (index >= 0)
tree->setColumnHidden(index, true);
}
}
QMenu* ResourceFolderModel::createHeaderContextMenu(QTreeView* tree)
{
auto menu = new QMenu(tree);
menu->addSeparator()->setText(tr("Show / Hide Columns"));
for (int col = 0; col < columnCount(); ++col) {
auto act = new QAction(menu);
setupHeaderAction(act, col);
act->setCheckable(true);
act->setChecked(!tree->isColumnHidden(col));
connect(act, &QAction::toggled, tree, [this, col, tree](bool toggled){
tree->setColumnHidden(col, !toggled);
for(int c = 0; c < columnCount(); ++c) {
if (m_column_resize_modes.at(c) == QHeaderView::ResizeToContents)
tree->resizeColumnToContents(c);
}
saveHiddenColumn(col, !toggled);
});
menu->addAction(act);
}
return menu;
}
QSortFilterProxyModel* ResourceFolderModel::createFilterProxyModel(QObject* parent) QSortFilterProxyModel* ResourceFolderModel::createFilterProxyModel(QObject* parent)
{ {
return new ProxyModel(parent); return new ProxyModel(parent);

View File

@ -1,5 +1,8 @@
#pragma once #pragma once
#include <QHeaderView>
#include <QAction>
#include <QTreeView>
#include <QAbstractListModel> #include <QAbstractListModel>
#include <QDir> #include <QDir>
#include <QFileSystemWatcher> #include <QFileSystemWatcher>
@ -29,6 +32,8 @@ class ResourceFolderModel : public QAbstractListModel {
ResourceFolderModel(QDir, BaseInstance* instance, QObject* parent = nullptr, bool create_dir = true); ResourceFolderModel(QDir, BaseInstance* instance, QObject* parent = nullptr, bool create_dir = true);
~ResourceFolderModel() override; ~ResourceFolderModel() override;
virtual QString id() const { return "resource"; }
/** Starts watching the paths for changes. /** Starts watching the paths for changes.
* *
* Returns whether starting to watch all the paths was successful. * Returns whether starting to watch all the paths was successful.
@ -92,6 +97,7 @@ class ResourceFolderModel : public QAbstractListModel {
/* Basic columns */ /* Basic columns */
enum Columns { ACTIVE_COLUMN = 0, NAME_COLUMN, DATE_COLUMN, NUM_COLUMNS }; enum Columns { ACTIVE_COLUMN = 0, NAME_COLUMN, DATE_COLUMN, 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()); } [[nodiscard]] int rowCount(const QModelIndex& parent = {}) const override { return parent.isValid() ? 0 : static_cast<int>(size()); }
[[nodiscard]] int columnCount(const QModelIndex& parent = {}) const override { return parent.isValid() ? 0 : NUM_COLUMNS; }; [[nodiscard]] int columnCount(const QModelIndex& parent = {}) const override { return parent.isValid() ? 0 : NUM_COLUMNS; };
@ -110,6 +116,11 @@ class ResourceFolderModel : public QAbstractListModel {
[[nodiscard]] QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; [[nodiscard]] QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
void setupHeaderAction(QAction* act, int column);
void saveHiddenColumn(int column, bool hidden);
void loadHiddenColumns(QTreeView* tree);
QMenu* createHeaderContextMenu(QTreeView* tree);
/** This creates a proxy model to filter / sort the model for a UI. /** This creates a proxy model to filter / sort the model for a UI.
* *
* The actual comparisons and filtering are done directly by the Resource, so to modify behavior go there instead! * The actual comparisons and filtering are done directly by the Resource, so to modify behavior go there instead!
@ -117,6 +128,7 @@ class ResourceFolderModel : public QAbstractListModel {
QSortFilterProxyModel* createFilterProxyModel(QObject* parent = nullptr); QSortFilterProxyModel* createFilterProxyModel(QObject* parent = nullptr);
[[nodiscard]] SortType columnToSortKey(size_t column) const; [[nodiscard]] SortType columnToSortKey(size_t column) const;
[[nodiscard]] QList<QHeaderView::ResizeMode> columnResizeModes() const { return m_column_resize_modes; }
class ProxyModel : public QSortFilterProxyModel { class ProxyModel : public QSortFilterProxyModel {
public: public:
@ -187,6 +199,9 @@ class ResourceFolderModel : public QAbstractListModel {
// Represents the relationship between a column's index (represented by the list index), and it's sorting key. // Represents the relationship between a column's index (represented by the list index), and it's sorting key.
// As such, the order in with they appear is very important! // As such, the order in with they appear is very important!
QList<SortType> m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::DATE }; 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::ResizeToContents, QHeaderView::Stretch, QHeaderView::ResizeToContents };
bool m_can_interact = true; bool m_can_interact = true;

View File

@ -40,7 +40,7 @@ void ResourcePack::setDescription(QString new_description)
m_description = new_description; m_description = new_description;
} }
void ResourcePack::setImage(QImage new_image) void ResourcePack::setImage(QImage new_image) const
{ {
QMutexLocker locker(&m_data_lock); QMutexLocker locker(&m_data_lock);
@ -49,7 +49,10 @@ void ResourcePack::setImage(QImage new_image)
if (m_pack_image_cache_key.key.isValid()) if (m_pack_image_cache_key.key.isValid())
PixmapCache::instance().remove(m_pack_image_cache_key.key); PixmapCache::instance().remove(m_pack_image_cache_key.key);
m_pack_image_cache_key.key = PixmapCache::instance().insert(QPixmap::fromImage(new_image)); // scale the image to avoid flooding the pixmapcache
auto pixmap = QPixmap::fromImage(new_image.scaled({64, 64}, Qt::AspectRatioMode::KeepAspectRatioByExpanding));
m_pack_image_cache_key.key = PixmapCache::instance().insert(pixmap);
m_pack_image_cache_key.was_ever_used = true; m_pack_image_cache_key.was_ever_used = true;
// This can happen if the pixmap is too big to fit in the cache :c // This can happen if the pixmap is too big to fit in the cache :c
@ -59,21 +62,25 @@ void ResourcePack::setImage(QImage new_image)
} }
} }
QPixmap ResourcePack::image(QSize size) QPixmap ResourcePack::image(QSize size, Qt::AspectRatioMode mode) const
{ {
QPixmap cached_image; QPixmap cached_image;
if (PixmapCache::instance().find(m_pack_image_cache_key.key, &cached_image)) { if (PixmapCache::instance().find(m_pack_image_cache_key.key, &cached_image)) {
if (size.isNull()) if (size.isNull())
return cached_image; return cached_image;
return cached_image.scaled(size); return cached_image.scaled(size, mode);
} }
// No valid image we can get // No valid image we can get
if (!m_pack_image_cache_key.was_ever_used) if (!m_pack_image_cache_key.was_ever_used) {
return {}; 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. // Imaged got evicted from the cache. Re-process it and retry.
ResourcePackUtils::process(*this); ResourcePackUtils::processPackPNG(*this);
return image(size); return image(size);
} }

View File

@ -31,7 +31,7 @@ class ResourcePack : public Resource {
[[nodiscard]] QString description() const { return m_description; } [[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 resource pack, converted to a QPixmap for drawing, and scaled to size. */
[[nodiscard]] QPixmap image(QSize size); [[nodiscard]] QPixmap image(QSize size, Qt::AspectRatioMode mode = Qt::AspectRatioMode::IgnoreAspectRatio) const;
/** Thread-safe. */ /** Thread-safe. */
void setPackFormat(int new_format_id); void setPackFormat(int new_format_id);
@ -40,7 +40,7 @@ class ResourcePack : public Resource {
void setDescription(QString new_description); void setDescription(QString new_description);
/** Thread-safe. */ /** Thread-safe. */
void setImage(QImage new_image); void setImage(QImage new_image) const;
bool valid() const override; bool valid() const override;
@ -67,5 +67,5 @@ class ResourcePack : public Resource {
struct { struct {
QPixmapCache::Key key; QPixmapCache::Key key;
bool was_ever_used = false; bool was_ever_used = false;
} m_pack_image_cache_key; } mutable m_pack_image_cache_key;
}; };

View File

@ -35,6 +35,8 @@
*/ */
#include "ResourcePackFolderModel.h" #include "ResourcePackFolderModel.h"
#include <qnamespace.h>
#include <qsize.h>
#include <QIcon> #include <QIcon>
#include <QStyle> #include <QStyle>
@ -48,7 +50,11 @@
ResourcePackFolderModel::ResourcePackFolderModel(const QString& dir, BaseInstance* instance) ResourcePackFolderModel::ResourcePackFolderModel(const QString& dir, BaseInstance* instance)
: ResourceFolderModel(QDir(dir), instance) : ResourceFolderModel(QDir(dir), instance)
{ {
m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::PACK_FORMAT, SortType::DATE }; 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::ResizeToContents, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::ResizeToContents};
} }
QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const
@ -84,9 +90,11 @@ QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const
return {}; return {};
} }
case Qt::DecorationRole: { 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 APPLICATION->getThemedIcon("status-yellow");
if (column == ImageColumn) {
return at(row)->image({32, 32}, Qt::AspectRatioMode::KeepAspectRatioByExpanding);
}
return {}; return {};
} }
case Qt::ToolTipRole: { case Qt::ToolTipRole: {
@ -94,7 +102,7 @@ QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const
//: The string being explained by this is in the format: ID (Lower version - Upper version) //: The string being explained by this is in the format: ID (Lower version - Upper version)
return tr("The resource pack format ID, as well as the Minecraft versions it was designed for."); return tr("The resource pack format ID, as well as the Minecraft versions it was designed for.");
} }
if (column == NAME_COLUMN) { if (column == NameColumn) {
if (at(row)->isSymLinkUnder(instDirPath())) { if (at(row)->isSymLinkUnder(instDirPath())) {
return m_resources[row]->internal_id() + return m_resources[row]->internal_id() +
tr("\nWarning: This resource is symbolically linked from elsewhere. Editing it will also change the original." tr("\nWarning: This resource is symbolically linked from elsewhere. Editing it will also change the original."
@ -126,13 +134,11 @@ QVariant ResourcePackFolderModel::headerData(int section, Qt::Orientation orient
case Qt::DisplayRole: case Qt::DisplayRole:
switch (section) { switch (section) {
case ActiveColumn: case ActiveColumn:
return QString();
case NameColumn: case NameColumn:
return tr("Name");
case PackFormatColumn: case PackFormatColumn:
return tr("Pack Format");
case DateColumn: case DateColumn:
return tr("Last changed"); case ImageColumn:
return columnNames().at(section);
default: default:
return {}; return {};
} }
@ -151,6 +157,11 @@ QVariant ResourcePackFolderModel::headerData(int section, Qt::Orientation orient
default: default:
return {}; return {};
} }
case Qt::SizeHintRole:
if (section == ImageColumn) {
return QSize(64,0);
}
return {};
default: default:
return {}; return {};
} }

View File

@ -11,6 +11,7 @@ public:
enum Columns enum Columns
{ {
ActiveColumn = 0, ActiveColumn = 0,
ImageColumn,
NameColumn, NameColumn,
PackFormatColumn, PackFormatColumn,
DateColumn, DateColumn,
@ -19,6 +20,8 @@ public:
explicit ResourcePackFolderModel(const QString &dir, BaseInstance* instance); explicit ResourcePackFolderModel(const QString &dir, BaseInstance* instance);
virtual QString id() const override { return "resourcepacks"; }
[[nodiscard]] QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; [[nodiscard]] QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
[[nodiscard]] QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; [[nodiscard]] QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;

View File

@ -9,4 +9,6 @@ class ShaderPackFolderModel : public ResourceFolderModel {
explicit ShaderPackFolderModel(const QString& dir, BaseInstance* instance) explicit ShaderPackFolderModel(const QString& dir, BaseInstance* instance)
: ResourceFolderModel(QDir(dir), instance) : ResourceFolderModel(QDir(dir), instance)
{} {}
virtual QString id() const override { return "shaderpacks"; }
}; };

View File

@ -23,6 +23,8 @@
#include <QMap> #include <QMap>
#include <QRegularExpression> #include <QRegularExpression>
#include "MTPixmapCache.h"
#include "minecraft/mod/tasks/LocalTexturePackParseTask.h" #include "minecraft/mod/tasks/LocalTexturePackParseTask.h"
void TexturePack::setDescription(QString new_description) void TexturePack::setDescription(QString new_description)
@ -32,34 +34,41 @@ void TexturePack::setDescription(QString new_description)
m_description = new_description; m_description = new_description;
} }
void TexturePack::setImage(QImage new_image) void TexturePack::setImage(QImage new_image) const
{ {
QMutexLocker locker(&m_data_lock); QMutexLocker locker(&m_data_lock);
Q_ASSERT(!new_image.isNull()); Q_ASSERT(!new_image.isNull());
if (m_pack_image_cache_key.key.isValid()) if (m_pack_image_cache_key.key.isValid())
QPixmapCache::remove(m_pack_image_cache_key.key); PixmapCache::remove(m_pack_image_cache_key.key);
m_pack_image_cache_key.key = QPixmapCache::insert(QPixmap::fromImage(new_image)); // scale the image to avoid flooding the pixmapcache
auto pixmap = QPixmap::fromImage(new_image.scaled({64, 64}, Qt::AspectRatioMode::KeepAspectRatioByExpanding));
m_pack_image_cache_key.key = PixmapCache::insert(pixmap);
m_pack_image_cache_key.was_ever_used = true; m_pack_image_cache_key.was_ever_used = true;
} }
QPixmap TexturePack::image(QSize size) QPixmap TexturePack::image(QSize size, Qt::AspectRatioMode mode) const
{ {
QPixmap cached_image; QPixmap cached_image;
if (QPixmapCache::find(m_pack_image_cache_key.key, &cached_image)) { if (PixmapCache::find(m_pack_image_cache_key.key, &cached_image)) {
if (size.isNull()) if (size.isNull())
return cached_image; return cached_image;
return cached_image.scaled(size); return cached_image.scaled(size, mode);
} }
// No valid image we can get // No valid image we can get
if (!m_pack_image_cache_key.was_ever_used) if (!m_pack_image_cache_key.was_ever_used) {
return {}; return {};
} else {
qDebug() << "Texture Pack" << name() << "Had it's image evicted from the cache. reloading...";
PixmapCache::markCacheMissByEviciton();
}
// Imaged got evicted from the cache. Re-process it and retry. // Imaged got evicted from the cache. Re-process it and retry.
TexturePackUtils::process(*this); TexturePackUtils::processPackPNG(*this);
return image(size); return image(size);
} }

View File

@ -40,13 +40,13 @@ class TexturePack : public Resource {
[[nodiscard]] QString description() const { return m_description; } [[nodiscard]] QString description() const { return m_description; }
/** Gets the image of the texture pack, converted to a QPixmap for drawing, and scaled to size. */ /** Gets the image of the texture pack, converted to a QPixmap for drawing, and scaled to size. */
[[nodiscard]] QPixmap image(QSize size); [[nodiscard]] QPixmap image(QSize size, Qt::AspectRatioMode mode = Qt::AspectRatioMode::IgnoreAspectRatio) const;
/** Thread-safe. */ /** Thread-safe. */
void setDescription(QString new_description); void setDescription(QString new_description);
/** Thread-safe. */ /** Thread-safe. */
void setImage(QImage new_image); void setImage(QImage new_image) const;
bool valid() const override; bool valid() const override;
@ -65,5 +65,5 @@ class TexturePack : public Resource {
struct { struct {
QPixmapCache::Key key; QPixmapCache::Key key;
bool was_ever_used = false; bool was_ever_used = false;
} m_pack_image_cache_key; } mutable m_pack_image_cache_key;
}; };

View File

@ -33,6 +33,9 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
#include <QCoreApplication>
#include "Application.h"
#include "TexturePackFolderModel.h" #include "TexturePackFolderModel.h"
@ -41,7 +44,13 @@
TexturePackFolderModel::TexturePackFolderModel(const QString& dir, BaseInstance* instance) TexturePackFolderModel::TexturePackFolderModel(const QString& dir, BaseInstance* instance)
: ResourceFolderModel(QDir(dir), instance) : ResourceFolderModel(QDir(dir), instance)
{} {
m_column_names = QStringList({ "Enable", "Image", "Name", "Last Modified" });
m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Last Modified") });
m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::DATE };
m_column_resize_modes = { QHeaderView::ResizeToContents, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::ResizeToContents};
}
Task* TexturePackFolderModel::createUpdateTask() Task* TexturePackFolderModel::createUpdateTask()
{ {
@ -52,3 +61,96 @@ Task* TexturePackFolderModel::createParseTask(Resource& resource)
{ {
return new LocalTexturePackParseTask(m_next_resolution_ticket, static_cast<TexturePack&>(resource)); return new LocalTexturePackParseTask(m_next_resolution_ticket, static_cast<TexturePack&>(resource));
} }
QVariant TexturePackFolderModel::data(const QModelIndex& index, int role) const
{
if (!validateIndex(index))
return {};
int row = index.row();
int column = index.column();
switch (role) {
case Qt::DisplayRole:
switch (column) {
case NameColumn:
return m_resources[row]->name();
case DateColumn:
return m_resources[row]->dateTimeChanged();
default:
return {};
}
case Qt::ToolTipRole:
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."
"\nCanonical Path: %1")
.arg(at(row)->fileinfo().canonicalFilePath());;
}
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()))
return APPLICATION->getThemedIcon("status-yellow");
if (column == ImageColumn) {
return at(row)->image({32, 32}, Qt::AspectRatioMode::KeepAspectRatioByExpanding);
}
return {};
}
case Qt::CheckStateRole:
if (column == ActiveColumn) {
return m_resources[row]->enabled() ? Qt::Checked : Qt::Unchecked;
}
return {};
default:
return {};
}
}
QVariant TexturePackFolderModel::headerData(int section, Qt::Orientation orientation, int role) const
{
switch (role) {
case Qt::DisplayRole:
switch (section) {
case ActiveColumn:
case NameColumn:
case DateColumn:
case ImageColumn:
return columnNames().at(section);
default:
return {};
}
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 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).");
default:
return {};
}
}
default:
break;
}
return {};
}
int TexturePackFolderModel::columnCount(const QModelIndex& parent) const
{
return parent.isValid() ? 0 : NUM_COLUMNS;
}

View File

@ -38,12 +38,35 @@
#include "ResourceFolderModel.h" #include "ResourceFolderModel.h"
#include "TexturePack.h"
class TexturePackFolderModel : public ResourceFolderModel class TexturePackFolderModel : public ResourceFolderModel
{ {
Q_OBJECT Q_OBJECT
public: public:
enum Columns
{
ActiveColumn = 0,
ImageColumn,
NameColumn,
DateColumn,
NUM_COLUMNS
};
explicit TexturePackFolderModel(const QString &dir, std::shared_ptr<const BaseInstance> instance);
virtual QString id() const override { return "texturepacks"; }
[[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;
explicit TexturePackFolderModel(const QString &dir, BaseInstance* instance); explicit TexturePackFolderModel(const QString &dir, BaseInstance* instance);
[[nodiscard]] Task* createUpdateTask() override; [[nodiscard]] Task* createUpdateTask() override;
[[nodiscard]] Task* createParseTask(Resource&) override; [[nodiscard]] Task* createParseTask(Resource&) override;
RESOURCE_HELPERS(TexturePack)
}; };

View File

@ -135,7 +135,7 @@ QList<ModPlatform::Dependency> GetModDependenciesTask::getDependenciesForVersion
Task::Ptr GetModDependenciesTask::getProjectInfoTask(std::shared_ptr<PackDependency> pDep) Task::Ptr GetModDependenciesTask::getProjectInfoTask(std::shared_ptr<PackDependency> pDep)
{ {
auto provider = pDep->pack->provider == m_flame_provider.name ? m_flame_provider : m_modrinth_provider; auto provider = pDep->pack->provider == m_flame_provider.name ? m_flame_provider : m_modrinth_provider;
auto responseInfo = new QByteArray(); auto responseInfo = std::make_shared<QByteArray>();
auto info = provider.api->getProject(pDep->pack->addonId.toString(), responseInfo); auto info = provider.api->getProject(pDep->pack->addonId.toString(), responseInfo);
QObject::connect(info.get(), &NetJob::succeeded, [responseInfo, provider, pDep] { QObject::connect(info.get(), &NetJob::succeeded, [responseInfo, provider, pDep] {
QJsonParseError parse_error{}; QJsonParseError parse_error{};

View File

@ -52,6 +52,10 @@ ModDetails ReadMCModInfo(QByteArray contents)
authors = firstObj.value("authors").toArray(); authors = firstObj.value("authors").toArray();
} }
if (firstObj.contains("logoFile")) {
details.icon_file = firstObj.value("logoFile").toString();
}
for (auto author : authors) { for (auto author : authors) {
details.authors.append(author.toString()); details.authors.append(author.toString());
} }
@ -166,6 +170,31 @@ ModDetails ReadMCModTOML(QByteArray contents)
} }
details.homeurl = homeurl; details.homeurl = homeurl;
QString issueTrackerURL = "";
if (auto issueTrackerURLDatum = tomlData["issueTrackerURL"].as_string()) {
issueTrackerURL = QString::fromStdString(issueTrackerURLDatum->get());
} else if (auto issueTrackerURLDatum = (*modsTable)["issueTrackerURL"].as_string()) {
issueTrackerURL = QString::fromStdString(issueTrackerURLDatum->get());
}
details.issue_tracker = issueTrackerURL;
QString license = "";
if (auto licenseDatum = tomlData["license"].as_string()) {
license = QString::fromStdString(licenseDatum->get());
} else if (auto licenseDatum =(*modsTable)["license"].as_string()) {
license = QString::fromStdString(licenseDatum->get());
}
if (!license.isEmpty())
details.licenses.append(ModLicense(license));
QString logoFile = "";
if (auto logoFileDatum = tomlData["logoFile"].as_string()) {
logoFile = QString::fromStdString(logoFileDatum->get());
} else if (auto logoFileDatum =(*modsTable)["logoFile"].as_string()) {
logoFile = QString::fromStdString(logoFileDatum->get());
}
details.icon_file = logoFile;
return details; return details;
} }
@ -201,6 +230,57 @@ ModDetails ReadFabricModInfo(QByteArray contents)
if (contact.contains("homepage")) { if (contact.contains("homepage")) {
details.homeurl = contact.value("homepage").toString(); details.homeurl = contact.value("homepage").toString();
} }
if (contact.contains("issues")) {
details.issue_tracker = contact.value("issues").toString();
}
}
if (object.contains("license")) {
auto license = object.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 (object.contains("icon")) {
auto icon = object.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();
}
} }
} }
return details; return details;
@ -238,6 +318,58 @@ ModDetails ReadQuiltModInfo(QByteArray contents)
if (modContact.contains("homepage")) { if (modContact.contains("homepage")) {
details.homeurl = Json::requireString(modContact.value("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()));
}
}
} 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();
}
}
} }
return details; return details;
} }
@ -515,6 +647,85 @@ bool validate(QFileInfo file)
return ModUtils::process(mod, ProcessingLevel::BasicInfoOnly) && mod.valid(); return ModUtils::process(mod, ProcessingLevel::BasicInfoOnly) && mod.valid();
} }
bool processIconPNG(const Mod& mod, QByteArray&& raw_data)
{
auto img = QImage::fromData(raw_data);
if (!img.isNull()) {
mod.setIcon(img);
} else {
qWarning() << "Failed to parse mod logo:" << mod.iconPath() << "from" << mod.name();
return false;
}
return true;
}
bool loadIconFile(const Mod& mod) {
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";
return false;
};
switch (mod.type()) {
case ResourceType::FOLDER:
{
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;
auto data = icon.readAll();
bool icon_result = ModUtils::processIconPNG(mod, std::move(data));
icon.close();
if (!icon_result) {
return png_invalid(); // icon invalid
}
}
}
case ResourceType::ZIPFILE:
{
QuaZip zip(mod.fileinfo().filePath());
if (!zip.open(QuaZip::mdUnzip))
return false;
QuaZipFile file(&zip);
if (zip.setCurrentFile(mod.iconPath())) {
if (!file.open(QIODevice::ReadOnly)) {
qCritical() << "Failed to open file in zip.";
zip.close();
return png_invalid();
}
auto data = file.readAll();
bool icon_result = ModUtils::processIconPNG(mod, std::move(data));
file.close();
if (!icon_result) {
return png_invalid(); // icon png invalid
}
} else {
return png_invalid(); // could not set icon as current file.
}
}
case ResourceType::LITEMOD:
{
return false; // can lightmods even have icons?
}
default:
qWarning() << "Invalid type for mod, can not load icon.";
return false;
}
}
} // namespace ModUtils } // namespace ModUtils
LocalModParseTask::LocalModParseTask(int token, ResourceType type, const QFileInfo& modFile) LocalModParseTask::LocalModParseTask(int token, ResourceType type, const QFileInfo& modFile)

View File

@ -25,6 +25,9 @@ bool processLitemod(Mod& mod, ProcessingLevel level = ProcessingLevel::Full);
/** Checks whether a file is valid as a mod or not. */ /** Checks whether a file is valid as a mod or not. */
bool validate(QFileInfo file); bool validate(QFileInfo file);
bool processIconPNG(const Mod& mod, QByteArray&& raw_data);
bool loadIconFile(const Mod& mod);
} // namespace ModUtils } // namespace ModUtils
class LocalModParseTask : public Task { class LocalModParseTask : public Task {

View File

@ -165,15 +165,16 @@ bool processZIP(ResourcePack& pack, ProcessingLevel level)
bool pack_png_result = ResourcePackUtils::processPackPNG(pack, std::move(data)); bool pack_png_result = ResourcePackUtils::processPackPNG(pack, std::move(data));
file.close(); file.close();
zip.close();
if (!pack_png_result) { if (!pack_png_result) {
return png_invalid(); // pack.png invalid return png_invalid(); // pack.png invalid
} }
} else { } else {
zip.close();
return png_invalid(); // could not set pack.mcmeta as current file. return png_invalid(); // could not set pack.mcmeta as current file.
} }
zip.close(); zip.close();
return true; return true;
} }
@ -193,7 +194,7 @@ bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data)
return true; return true;
} }
bool processPackPNG(ResourcePack& pack, QByteArray&& raw_data) bool processPackPNG(const ResourcePack& pack, QByteArray&& raw_data)
{ {
auto img = QImage::fromData(raw_data); auto img = QImage::fromData(raw_data);
if (!img.isNull()) { if (!img.isNull()) {
@ -205,6 +206,68 @@ bool processPackPNG(ResourcePack& pack, QByteArray&& raw_data)
return true; return true;
} }
bool processPackPNG(const ResourcePack& pack)
{
auto png_invalid = [&pack]() {
qWarning() << "Resource pack at" << pack.fileinfo().filePath() << "does not have a valid pack.png";
return false;
};
switch (pack.type()) {
case ResourceType::FOLDER:
{
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.
}
}
case ResourceType::ZIPFILE:
{
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);
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();
if (!pack_png_result) {
return png_invalid(); // pack.png invalid
}
} else {
return png_invalid(); // could not set pack.mcmeta as current file.
}
}
default:
qWarning() << "Invalid type for resource pack parse task!";
return false;
}
}
bool validate(QFileInfo file) bool validate(QFileInfo file)
{ {
ResourcePack rp{ file }; ResourcePack rp{ file };

View File

@ -35,7 +35,10 @@ bool processZIP(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Ful
bool processFolder(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Full); bool processFolder(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Full);
bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data); bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data);
bool processPackPNG(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);
/** Checks whether a file is valid as a resource pack or not. */ /** Checks whether a file is valid as a resource pack or not. */
bool validate(QFileInfo file); bool validate(QFileInfo file);

View File

@ -131,6 +131,7 @@ bool processZIP(TexturePack& pack, ProcessingLevel level)
bool packPNG_result = TexturePackUtils::processPackPNG(pack, std::move(data)); bool packPNG_result = TexturePackUtils::processPackPNG(pack, std::move(data));
file.close(); file.close();
zip.close();
if (!packPNG_result) { if (!packPNG_result) {
return false; return false;
} }
@ -147,7 +148,7 @@ bool processPackTXT(TexturePack& pack, QByteArray&& raw_data)
return true; return true;
} }
bool processPackPNG(TexturePack& pack, QByteArray&& raw_data) bool processPackPNG(const TexturePack& pack, QByteArray&& raw_data)
{ {
auto img = QImage::fromData(raw_data); auto img = QImage::fromData(raw_data);
if (!img.isNull()) { if (!img.isNull()) {
@ -159,6 +160,70 @@ bool processPackPNG(TexturePack& pack, QByteArray&& raw_data)
return true; return true;
} }
bool processPackPNG(const TexturePack& pack)
{
auto png_invalid = [&pack]() {
qWarning() << "Texture pack at" << pack.fileinfo().filePath() << "does not have a valid pack.png";
return false;
};
switch (pack.type()) {
case ResourceType::FOLDER:
{
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 = TexturePackUtils::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.
}
}
case ResourceType::ZIPFILE:
{
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);
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 = TexturePackUtils::processPackPNG(pack, std::move(data));
file.close();
if (!pack_png_result) {
zip.close();
return png_invalid(); // pack.png invalid
}
} else {
zip.close();
return png_invalid(); // could not set pack.mcmeta as current file.
}
}
default:
qWarning() << "Invalid type for resource pack parse task!";
return false;
}
}
bool validate(QFileInfo file) bool validate(QFileInfo file)
{ {
TexturePack rp{ file }; TexturePack rp{ file };

View File

@ -36,7 +36,10 @@ bool processZIP(TexturePack& pack, ProcessingLevel level = ProcessingLevel::Full
bool processFolder(TexturePack& pack, ProcessingLevel level = ProcessingLevel::Full); bool processFolder(TexturePack& pack, ProcessingLevel level = ProcessingLevel::Full);
bool processPackTXT(TexturePack& pack, QByteArray&& raw_data); bool processPackTXT(TexturePack& pack, QByteArray&& raw_data);
bool processPackPNG(TexturePack& pack, QByteArray&& raw_data); bool processPackPNG(const TexturePack& pack, QByteArray&& raw_data);
/// processes ONLY the pack.png (rest of the pack may be invalid)
bool processPackPNG(const TexturePack& pack);
/** Checks whether a file is valid as a texture pack or not. */ /** Checks whether a file is valid as a texture pack or not. */
bool validate(QFileInfo file); bool validate(QFileInfo file);

View File

@ -10,6 +10,7 @@
#include "modplatform/flame/FlameAPI.h" #include "modplatform/flame/FlameAPI.h"
#include "modplatform/flame/FlameModIndex.h" #include "modplatform/flame/FlameModIndex.h"
#include "modplatform/helpers/HashUtils.h"
#include "modplatform/modrinth/ModrinthAPI.h" #include "modplatform/modrinth/ModrinthAPI.h"
#include "modplatform/modrinth/ModrinthPackIndex.h" #include "modplatform/modrinth/ModrinthPackIndex.h"
@ -24,8 +25,8 @@ EnsureMetadataTask::EnsureMetadataTask(Mod* mod, QDir dir, ModPlatform::Resource
auto hash_task = createNewHash(mod); auto hash_task = createNewHash(mod);
if (!hash_task) if (!hash_task)
return; return;
connect(hash_task.get(), &Task::succeeded, [this, hash_task, mod] { m_mods.insert(hash_task->getResult(), mod); }); connect(hash_task.get(), &Hashing::Hasher::resultsReady, [this, mod](QString hash) { m_mods.insert(hash, mod); });
connect(hash_task.get(), &Task::failed, [this, hash_task, mod] { emitFail(mod, "", RemoveFromList::No); }); connect(hash_task.get(), &Task::failed, [this, mod] { emitFail(mod, "", RemoveFromList::No); });
hash_task->start(); hash_task->start();
} }
@ -37,8 +38,8 @@ EnsureMetadataTask::EnsureMetadataTask(QList<Mod*>& mods, QDir dir, ModPlatform:
auto hash_task = createNewHash(mod); auto hash_task = createNewHash(mod);
if (!hash_task) if (!hash_task)
continue; continue;
connect(hash_task.get(), &Task::succeeded, [this, hash_task, mod] { m_mods.insert(hash_task->getResult(), mod); }); connect(hash_task.get(), &Hashing::Hasher::resultsReady, [this, mod](QString hash) { m_mods.insert(hash, mod); });
connect(hash_task.get(), &Task::failed, [this, hash_task, mod] { emitFail(mod, "", RemoveFromList::No); }); connect(hash_task.get(), &Task::failed, [this, mod] { emitFail(mod, "", RemoveFromList::No); });
m_hashing_task->addTask(hash_task); m_hashing_task->addTask(hash_task);
} }
} }
@ -212,12 +213,12 @@ Task::Ptr EnsureMetadataTask::modrinthVersionsTask()
{ {
auto hash_type = ProviderCaps.hashType(ModPlatform::ResourceProvider::MODRINTH).first(); auto hash_type = ProviderCaps.hashType(ModPlatform::ResourceProvider::MODRINTH).first();
auto* response = new QByteArray(); auto response = std::make_shared<QByteArray>();
auto ver_task = modrinth_api.currentVersions(m_mods.keys(), hash_type, response); auto ver_task = modrinth_api.currentVersions(m_mods.keys(), hash_type, response);
// Prevents unfortunate timings when aborting the task // Prevents unfortunate timings when aborting the task
if (!ver_task) if (!ver_task)
return Task::Ptr{nullptr}; return Task::Ptr{ nullptr };
connect(ver_task.get(), &Task::succeeded, this, [this, response] { connect(ver_task.get(), &Task::succeeded, this, [this, response] {
QJsonParseError parse_error{}; QJsonParseError parse_error{};
@ -264,7 +265,7 @@ Task::Ptr EnsureMetadataTask::modrinthProjectsTask()
for (auto const& data : m_temp_versions) for (auto const& data : m_temp_versions)
addonIds.insert(data.addonId.toString(), data.hash); addonIds.insert(data.addonId.toString(), data.hash);
auto response = new QByteArray(); auto response = std::make_shared<QByteArray>();
Task::Ptr proj_task; Task::Ptr proj_task;
if (addonIds.isEmpty()) { if (addonIds.isEmpty()) {
@ -277,7 +278,7 @@ Task::Ptr EnsureMetadataTask::modrinthProjectsTask()
// Prevents unfortunate timings when aborting the task // Prevents unfortunate timings when aborting the task
if (!proj_task) if (!proj_task)
return Task::Ptr{nullptr}; return Task::Ptr{ nullptr };
connect(proj_task.get(), &Task::succeeded, this, [this, response, addonIds] { connect(proj_task.get(), &Task::succeeded, this, [this, response, addonIds] {
QJsonParseError parse_error{}; QJsonParseError parse_error{};
@ -345,7 +346,7 @@ Task::Ptr EnsureMetadataTask::modrinthProjectsTask()
// Flame // Flame
Task::Ptr EnsureMetadataTask::flameVersionsTask() Task::Ptr EnsureMetadataTask::flameVersionsTask()
{ {
auto* response = new QByteArray(); auto response = std::make_shared<QByteArray>();
QList<uint> fingerprints; QList<uint> fingerprints;
for (auto& murmur : m_mods.keys()) { for (auto& murmur : m_mods.keys()) {
@ -413,7 +414,7 @@ Task::Ptr EnsureMetadataTask::flameProjectsTask()
QHash<QString, QString> addonIds; QHash<QString, QString> addonIds;
for (auto const& hash : m_mods.keys()) { for (auto const& hash : m_mods.keys()) {
if (m_temp_versions.contains(hash)) { if (m_temp_versions.contains(hash)) {
auto const& data = m_temp_versions.find(hash).value(); auto data = m_temp_versions.find(hash).value();
auto id_str = data.addonId.toString(); auto id_str = data.addonId.toString();
if (!id_str.isEmpty()) if (!id_str.isEmpty())
@ -421,7 +422,7 @@ Task::Ptr EnsureMetadataTask::flameProjectsTask()
} }
} }
auto response = new QByteArray(); auto response = std::make_shared<QByteArray>();
Task::Ptr proj_task; Task::Ptr proj_task;
if (addonIds.isEmpty()) { if (addonIds.isEmpty()) {
@ -434,7 +435,7 @@ Task::Ptr EnsureMetadataTask::flameProjectsTask()
// Prevents unfortunate timings when aborting the task // Prevents unfortunate timings when aborting the task
if (!proj_task) if (!proj_task)
return Task::Ptr{nullptr}; return Task::Ptr{ nullptr };
connect(proj_task.get(), &Task::succeeded, this, [this, response, addonIds] { connect(proj_task.get(), &Task::succeeded, this, [this, response, addonIds] {
QJsonParseError parse_error{}; QJsonParseError parse_error{};

View File

@ -131,12 +131,12 @@ class ResourceAPI {
qWarning() << "TODO"; qWarning() << "TODO";
return nullptr; return nullptr;
} }
[[nodiscard]] virtual Task::Ptr getProject(QString addonId, QByteArray* response) const [[nodiscard]] virtual Task::Ptr getProject(QString addonId, std::shared_ptr<QByteArray> response) const
{ {
qWarning() << "TODO"; qWarning() << "TODO";
return nullptr; return nullptr;
} }
[[nodiscard]] virtual Task::Ptr getProjects(QStringList addonIds, QByteArray* response) const [[nodiscard]] virtual Task::Ptr getProjects(QStringList addonIds, std::shared_ptr<QByteArray> response) const
{ {
qWarning() << "TODO"; qWarning() << "TODO";
return nullptr; return nullptr;

View File

@ -82,9 +82,9 @@ void PackInstallTask::executeTask()
{ {
qDebug() << "PackInstallTask::executeTask: " << QThread::currentThreadId(); qDebug() << "PackInstallTask::executeTask: " << QThread::currentThreadId();
NetJob::Ptr netJob{ new NetJob("ATLauncher::VersionFetch", APPLICATION->network()) }; NetJob::Ptr netJob{ new NetJob("ATLauncher::VersionFetch", APPLICATION->network()) };
auto searchUrl = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "packs/%1/versions/%2/Configs.json") auto searchUrl =
.arg(m_pack_safe_name).arg(m_version_name); QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "packs/%1/versions/%2/Configs.json").arg(m_pack_safe_name).arg(m_version_name);
netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), &response)); netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), response));
QObject::connect(netJob.get(), &NetJob::succeeded, this, &PackInstallTask::onDownloadSucceeded); QObject::connect(netJob.get(), &NetJob::succeeded, this, &PackInstallTask::onDownloadSucceeded);
QObject::connect(netJob.get(), &NetJob::failed, this, &PackInstallTask::onDownloadFailed); QObject::connect(netJob.get(), &NetJob::failed, this, &PackInstallTask::onDownloadFailed);
@ -99,11 +99,12 @@ void PackInstallTask::onDownloadSucceeded()
qDebug() << "PackInstallTask::onDownloadSucceeded: " << QThread::currentThreadId(); qDebug() << "PackInstallTask::onDownloadSucceeded: " << QThread::currentThreadId();
jobPtr.reset(); jobPtr.reset();
QJsonParseError parse_error {}; QJsonParseError parse_error{};
QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error); QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
if(parse_error.error != QJsonParseError::NoError) { if (parse_error.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response from ATLauncher at " << parse_error.offset << " reason: " << parse_error.errorString(); qWarning() << "Error while parsing JSON response from ATLauncher at " << parse_error.offset
qWarning() << response; << " reason: " << parse_error.errorString();
qWarning() << *response.get();
return; return;
} }
auto obj = doc.object(); auto obj = doc.object();

View File

@ -40,12 +40,13 @@
#include "ATLPackManifest.h" #include "ATLPackManifest.h"
#include "InstanceTask.h" #include "InstanceTask.h"
#include "net/NetJob.h" #include "meta/Version.h"
#include "settings/INISettingsObject.h"
#include "minecraft/MinecraftInstance.h" #include "minecraft/MinecraftInstance.h"
#include "minecraft/PackProfile.h" #include "minecraft/PackProfile.h"
#include "meta/Version.h" #include "net/NetJob.h"
#include "settings/INISettingsObject.h"
#include <memory>
#include <optional> #include <optional>
namespace ATLauncher { namespace ATLauncher {
@ -57,8 +58,7 @@ enum class InstallMode {
}; };
class UserInteractionSupport { class UserInteractionSupport {
public:
public:
/** /**
* Requests a user interaction to select which optional mods should be installed. * Requests a user interaction to select which optional mods should be installed.
*/ */
@ -74,23 +74,27 @@ public:
* Requests a user interaction to display a message. * Requests a user interaction to display a message.
*/ */
virtual void displayMessage(QString message) = 0; virtual void displayMessage(QString message) = 0;
virtual ~UserInteractionSupport() = default;
}; };
class PackInstallTask : public InstanceTask class PackInstallTask : public InstanceTask {
{ Q_OBJECT
Q_OBJECT
public: public:
explicit PackInstallTask(UserInteractionSupport *support, QString packName, QString version, InstallMode installMode = InstallMode::Install); explicit PackInstallTask(UserInteractionSupport* support,
virtual ~PackInstallTask(){} QString packName,
QString version,
InstallMode installMode = InstallMode::Install);
virtual ~PackInstallTask() { delete m_support; }
bool canAbort() const override { return true; } bool canAbort() const override { return true; }
bool abort() override; bool abort() override;
protected: protected:
virtual void executeTask() override; virtual void executeTask() override;
private slots: private slots:
void onDownloadSucceeded(); void onDownloadSucceeded();
void onDownloadFailed(QString reason); void onDownloadFailed(QString reason);
void onDownloadAborted(); void onDownloadAborted();
@ -98,7 +102,7 @@ private slots:
void onModsDownloaded(); void onModsDownloaded();
void onModsExtracted(); void onModsExtracted();
private: private:
QString getDirForModType(ModType type, QString raw); QString getDirForModType(ModType type, QString raw);
QString getVersionForLoader(QString uid); QString getVersionForLoader(QString uid);
QString detectLibrary(VersionLibrary library); QString detectLibrary(VersionLibrary library);
@ -110,20 +114,18 @@ private:
void installConfigs(); void installConfigs();
void extractConfigs(); void extractConfigs();
void downloadMods(); void downloadMods();
bool extractMods( bool extractMods(const QMap<QString, VersionMod>& toExtract,
const QMap<QString, VersionMod> &toExtract, const QMap<QString, VersionMod>& toDecomp,
const QMap<QString, VersionMod> &toDecomp, const QMap<QString, QString>& toCopy);
const QMap<QString, QString> &toCopy
);
void install(); void install();
private: private:
UserInteractionSupport *m_support; UserInteractionSupport* m_support;
bool abortable = false; bool abortable = false;
NetJob::Ptr jobPtr; NetJob::Ptr jobPtr;
QByteArray response; std::shared_ptr<QByteArray> response = std::make_shared<QByteArray>();
InstallMode m_install_mode; InstallMode m_install_mode;
QString m_pack_name; QString m_pack_name;
@ -145,7 +147,6 @@ private:
QFuture<bool> m_modExtractFuture; QFuture<bool> m_modExtractFuture;
QFutureWatcher<bool> m_modExtractFutureWatcher; QFutureWatcher<bool> m_modExtractFutureWatcher;
}; };
} } // namespace ATLauncher

View File

@ -25,15 +25,16 @@ void Flame::FileResolvingTask::executeTask()
setProgress(0, 3); setProgress(0, 3);
m_dljob.reset(new NetJob("Mod id resolver", m_network)); m_dljob.reset(new NetJob("Mod id resolver", m_network));
result.reset(new QByteArray()); result.reset(new QByteArray());
//build json data to send // build json data to send
QJsonObject object; QJsonObject object;
object["fileIds"] = QJsonArray::fromVariantList(std::accumulate(m_toProcess.files.begin(), m_toProcess.files.end(), QVariantList(), [](QVariantList& l, const File& s) { object["fileIds"] = QJsonArray::fromVariantList(
l.push_back(s.fileId); std::accumulate(m_toProcess.files.begin(), m_toProcess.files.end(), QVariantList(), [](QVariantList& l, const File& s) {
return l; l.push_back(s.fileId);
})); return l;
}));
QByteArray data = Json::toText(object); QByteArray data = Json::toText(object);
auto dl = Net::Upload::makeByteArray(QUrl("https://api.curseforge.com/v1/mods/files"), result.get(), data); auto dl = Net::Upload::makeByteArray(QUrl("https://api.curseforge.com/v1/mods/files"), result, data);
m_dljob->addNetAction(dl); m_dljob->addNetAction(dl);
auto step_progress = std::make_shared<TaskStepProgress>(); auto step_progress = std::make_shared<TaskStepProgress>();
@ -87,17 +88,15 @@ void Flame::FileResolvingTask::netJobFinished()
auto fileid = Json::requireInteger(Json::requireObject(file)["id"]); auto fileid = Json::requireInteger(Json::requireObject(file)["id"]);
auto& out = m_toProcess.files[fileid]; auto& out = m_toProcess.files[fileid];
try { try {
out.parseFromObject(Json::requireObject(file)); out.parseFromObject(Json::requireObject(file));
} catch (const JSONValidationError& e) { } catch (const JSONValidationError& e) {
qDebug() << "Blocked mod on curseforge" << out.fileName; qDebug() << "Blocked mod on curseforge" << out.fileName;
auto hash = out.hash; auto hash = out.hash;
if(!hash.isEmpty()) { if (!hash.isEmpty()) {
auto url = QString("https://api.modrinth.com/v2/version_file/%1?algorithm=sha1").arg(hash); auto url = QString("https://api.modrinth.com/v2/version_file/%1?algorithm=sha1").arg(hash);
auto output = std::make_shared<QByteArray>(); auto output = std::make_shared<QByteArray>();
auto dl = Net::Download::makeByteArray(QUrl(url), output.get()); auto dl = Net::Download::makeByteArray(QUrl(url), output);
QObject::connect(dl.get(), &Net::Download::succeeded, [&out]() { QObject::connect(dl.get(), &Net::Download::succeeded, [&out]() { out.resolved = true; });
out.resolved = true;
});
m_checkJob->addNetAction(dl); m_checkJob->addNetAction(dl);
blockedProjects.insert(&out, output); blockedProjects.insert(&out, output);
@ -169,7 +168,7 @@ void Flame::FileResolvingTask::modrinthCheckFinished() {
auto projectId = mod->projectId; auto projectId = mod->projectId;
auto output = std::make_shared<QByteArray>(); auto output = std::make_shared<QByteArray>();
auto url = QString("https://api.curseforge.com/v1/mods/%1").arg(projectId); auto url = QString("https://api.curseforge.com/v1/mods/%1").arg(projectId);
auto dl = Net::Download::makeByteArray(url, output.get()); auto dl = Net::Download::makeByteArray(url, output);
qDebug() << "Fetching url slug for file:" << mod->fileName; qDebug() << "Fetching url slug for file:" << mod->fileName;
QObject::connect(dl.get(), &Net::Download::succeeded, [block, index, output]() { QObject::connect(dl.get(), &Net::Download::succeeded, [block, index, output]() {
auto mod = block->at(index); // use the shared_ptr so it is captured and only freed when we are done auto mod = block->at(index); // use the shared_ptr so it is captured and only freed when we are done

View File

@ -11,7 +11,7 @@
#include "net/NetJob.h" #include "net/NetJob.h"
#include "net/Upload.h" #include "net/Upload.h"
Task::Ptr FlameAPI::matchFingerprints(const QList<uint>& fingerprints, QByteArray* response) Task::Ptr FlameAPI::matchFingerprints(const QList<uint>& fingerprints, std::shared_ptr<QByteArray> response)
{ {
auto netJob = makeShared<NetJob>(QString("Flame::MatchFingerprints"), APPLICATION->network()); auto netJob = makeShared<NetJob>(QString("Flame::MatchFingerprints"), APPLICATION->network());
@ -28,8 +28,6 @@ Task::Ptr FlameAPI::matchFingerprints(const QList<uint>& fingerprints, QByteArra
netJob->addNetAction(Net::Upload::makeByteArray(QString("https://api.curseforge.com/v1/fingerprints"), response, body_raw)); netJob->addNetAction(Net::Upload::makeByteArray(QString("https://api.curseforge.com/v1/fingerprints"), response, body_raw));
QObject::connect(netJob.get(), &NetJob::finished, [response] { delete response; });
return netJob; return netJob;
} }
@ -43,7 +41,7 @@ auto FlameAPI::getModFileChangelog(int modId, int fileId) -> QString
netJob->addNetAction(Net::Download::makeByteArray( netJob->addNetAction(Net::Download::makeByteArray(
QString("https://api.curseforge.com/v1/mods/%1/files/%2/changelog") QString("https://api.curseforge.com/v1/mods/%1/files/%2/changelog")
.arg(QString::fromStdString(std::to_string(modId)), QString::fromStdString(std::to_string(fileId))), .arg(QString::fromStdString(std::to_string(modId)), QString::fromStdString(std::to_string(fileId))),
response.get())); response));
QObject::connect(netJob.get(), &NetJob::succeeded, [&netJob, response, &changelog] { QObject::connect(netJob.get(), &NetJob::succeeded, [&netJob, response, &changelog] {
QJsonParseError parse_error{}; QJsonParseError parse_error{};
@ -75,8 +73,8 @@ auto FlameAPI::getModDescription(int modId) -> QString
auto netJob = makeShared<NetJob>(QString("Flame::ModDescription"), APPLICATION->network()); auto netJob = makeShared<NetJob>(QString("Flame::ModDescription"), APPLICATION->network());
auto response = std::make_shared<QByteArray>(); auto response = std::make_shared<QByteArray>();
netJob->addNetAction(Net::Download::makeByteArray( netJob->addNetAction(
QString("https://api.curseforge.com/v1/mods/%1/description").arg(QString::number(modId)), response.get())); Net::Download::makeByteArray(QString("https://api.curseforge.com/v1/mods/%1/description").arg(QString::number(modId)), response));
QObject::connect(netJob.get(), &NetJob::succeeded, [&netJob, response, &description] { QObject::connect(netJob.get(), &NetJob::succeeded, [&netJob, response, &description] {
QJsonParseError parse_error{}; QJsonParseError parse_error{};
@ -115,7 +113,7 @@ auto FlameAPI::getLatestVersion(VersionSearchArgs&& args) -> ModPlatform::Indexe
auto response = std::make_shared<QByteArray>(); auto response = std::make_shared<QByteArray>();
ModPlatform::IndexedVersion ver; ModPlatform::IndexedVersion ver;
netJob->addNetAction(Net::Download::makeByteArray(versions_url, response.get())); netJob->addNetAction(Net::Download::makeByteArray(versions_url, response));
QObject::connect(netJob.get(), &NetJob::succeeded, [response, args, &ver] { QObject::connect(netJob.get(), &NetJob::succeeded, [response, args, &ver] {
QJsonParseError parse_error{}; QJsonParseError parse_error{};
@ -137,7 +135,7 @@ auto FlameAPI::getLatestVersion(VersionSearchArgs&& args) -> ModPlatform::Indexe
for (auto file : arr) { for (auto file : arr) {
auto file_obj = Json::requireObject(file); auto file_obj = Json::requireObject(file);
auto file_tmp = FlameMod::loadIndexedPackVersion(file_obj); auto file_tmp = FlameMod::loadIndexedPackVersion(file_obj);
if(file_tmp.date > ver_tmp.date) { if (file_tmp.date > ver_tmp.date) {
ver_tmp = file_tmp; ver_tmp = file_tmp;
latest_file_obj = file_obj; latest_file_obj = file_obj;
} }
@ -160,7 +158,7 @@ auto FlameAPI::getLatestVersion(VersionSearchArgs&& args) -> ModPlatform::Indexe
return ver; return ver;
} }
Task::Ptr FlameAPI::getProjects(QStringList addonIds, QByteArray* response) const Task::Ptr FlameAPI::getProjects(QStringList addonIds, std::shared_ptr<QByteArray> response) const
{ {
auto netJob = makeShared<NetJob>(QString("Flame::GetProjects"), APPLICATION->network()); auto netJob = makeShared<NetJob>(QString("Flame::GetProjects"), APPLICATION->network());
@ -177,13 +175,12 @@ Task::Ptr FlameAPI::getProjects(QStringList addonIds, QByteArray* response) cons
netJob->addNetAction(Net::Upload::makeByteArray(QString("https://api.curseforge.com/v1/mods"), response, body_raw)); netJob->addNetAction(Net::Upload::makeByteArray(QString("https://api.curseforge.com/v1/mods"), response, body_raw));
QObject::connect(netJob.get(), &NetJob::finished, [response] { delete response; });
QObject::connect(netJob.get(), &NetJob::failed, [body_raw] { qDebug() << body_raw; }); QObject::connect(netJob.get(), &NetJob::failed, [body_raw] { qDebug() << body_raw; });
return netJob; return netJob;
} }
Task::Ptr FlameAPI::getFiles(const QStringList& fileIds, QByteArray* response) const Task::Ptr FlameAPI::getFiles(const QStringList& fileIds, std::shared_ptr<QByteArray> response) const
{ {
auto netJob = makeShared<NetJob>(QString("Flame::GetFiles"), APPLICATION->network()); auto netJob = makeShared<NetJob>(QString("Flame::GetFiles"), APPLICATION->network());
@ -200,7 +197,6 @@ Task::Ptr FlameAPI::getFiles(const QStringList& fileIds, QByteArray* response) c
netJob->addNetAction(Net::Upload::makeByteArray(QString("https://api.curseforge.com/v1/mods/files"), response, body_raw)); netJob->addNetAction(Net::Upload::makeByteArray(QString("https://api.curseforge.com/v1/mods/files"), response, body_raw));
QObject::connect(netJob.get(), &NetJob::finished, [response] { delete response; });
QObject::connect(netJob.get(), &NetJob::failed, [body_raw] { qDebug() << body_raw; }); QObject::connect(netJob.get(), &NetJob::failed, [body_raw] { qDebug() << body_raw; });
return netJob; return netJob;

View File

@ -5,6 +5,7 @@
#pragma once #pragma once
#include <algorithm> #include <algorithm>
#include <memory>
#include "modplatform/ModIndex.h" #include "modplatform/ModIndex.h"
#include "modplatform/ResourceAPI.h" #include "modplatform/ResourceAPI.h"
#include "modplatform/helpers/NetworkResourceAPI.h" #include "modplatform/helpers/NetworkResourceAPI.h"
@ -16,9 +17,9 @@ class FlameAPI : public NetworkResourceAPI {
auto getLatestVersion(VersionSearchArgs&& args) -> ModPlatform::IndexedVersion; auto getLatestVersion(VersionSearchArgs&& args) -> ModPlatform::IndexedVersion;
Task::Ptr getProjects(QStringList addonIds, QByteArray* response) const override; Task::Ptr getProjects(QStringList addonIds, std::shared_ptr<QByteArray> response) const override;
Task::Ptr matchFingerprints(const QList<uint>& fingerprints, QByteArray* response); Task::Ptr matchFingerprints(const QList<uint>& fingerprints, std::shared_ptr<QByteArray> response);
Task::Ptr getFiles(const QStringList& fileIds, QByteArray* response) const; Task::Ptr getFiles(const QStringList& fileIds, std::shared_ptr<QByteArray> response) const;
[[nodiscard]] auto getSortingMethods() const -> QList<ResourceAPI::SortingMethod> override; [[nodiscard]] auto getSortingMethods() const -> QList<ResourceAPI::SortingMethod> override;

View File

@ -31,7 +31,7 @@ ModPlatform::IndexedPack getProjectInfo(ModPlatform::IndexedVersion& ver_info)
auto get_project_job = new NetJob("Flame::GetProjectJob", APPLICATION->network()); auto get_project_job = new NetJob("Flame::GetProjectJob", APPLICATION->network());
auto response = new QByteArray(); auto response = std::make_shared<QByteArray>();
auto url = QString("https://api.curseforge.com/v1/mods/%1").arg(ver_info.addonId.toString()); auto url = QString("https://api.curseforge.com/v1/mods/%1").arg(ver_info.addonId.toString());
auto dl = Net::Download::makeByteArray(url, response); auto dl = Net::Download::makeByteArray(url, response);
get_project_job->addNetAction(dl); get_project_job->addNetAction(dl);
@ -75,7 +75,7 @@ ModPlatform::IndexedVersion getFileInfo(int addonId, int fileId)
auto get_file_info_job = new NetJob("Flame::GetFileInfoJob", APPLICATION->network()); auto get_file_info_job = new NetJob("Flame::GetFileInfoJob", APPLICATION->network());
auto response = new QByteArray(); auto response = std::make_shared<QByteArray>();
auto url = QString("https://api.curseforge.com/v1/mods/%1/files/%2").arg(QString::number(addonId), QString::number(fileId)); auto url = QString("https://api.curseforge.com/v1/mods/%1/files/%2").arg(QString::number(addonId), QString::number(fileId));
auto dl = Net::Download::makeByteArray(url, response); auto dl = Net::Download::makeByteArray(url, response);
get_file_info_job->addNetAction(dl); get_file_info_job->addNetAction(dl);

View File

@ -153,6 +153,9 @@ bool FlameCreationTask::updateInstance()
old_files.remove(file.key()); old_files.remove(file.key());
files_iterator = files.erase(files_iterator); files_iterator = files.erase(files_iterator);
if (files_iterator != files.begin())
files_iterator--;
} }
} }
@ -179,7 +182,7 @@ bool FlameCreationTask::updateInstance()
fileIds.append(QString::number(file.fileId)); fileIds.append(QString::number(file.fileId));
} }
auto* raw_response = new QByteArray; auto raw_response = std::make_shared<QByteArray>();
auto job = api.getFiles(fileIds, raw_response); auto job = api.getFiles(fileIds, raw_response);
QEventLoop loop; QEventLoop loop;

View File

@ -71,6 +71,7 @@ void ModrinthHasher::executeTask()
emitFailed("Empty hash!"); emitFailed("Empty hash!");
} else { } else {
emitSucceeded(); emitSucceeded();
emit resultsReady(m_hash);
} }
} }
@ -91,10 +92,9 @@ void FlameHasher::executeTask()
} }
} }
BlockedModHasher::BlockedModHasher(QString file_path, ModPlatform::ResourceProvider provider) : Hasher(file_path), provider(provider)
BlockedModHasher::BlockedModHasher(QString file_path, ModPlatform::ResourceProvider provider) {
: Hasher(file_path), provider(provider) { setObjectName(QString("BlockedModHasher: %1").arg(file_path));
setObjectName(QString("BlockedModHasher: %1").arg(file_path));
hash_type = ProviderCaps.hashType(provider).first(); hash_type = ProviderCaps.hashType(provider).first();
} }
@ -123,11 +123,13 @@ void BlockedModHasher::executeTask()
} }
} }
QStringList BlockedModHasher::getHashTypes() { QStringList BlockedModHasher::getHashTypes()
{
return ProviderCaps.hashType(provider); return ProviderCaps.hashType(provider);
} }
bool BlockedModHasher::useHashType(QString type) { bool BlockedModHasher::useHashType(QString type)
{
auto types = ProviderCaps.hashType(provider); auto types = ProviderCaps.hashType(provider);
if (types.contains(type)) { if (types.contains(type)) {
hash_type = type; hash_type = type;

View File

@ -8,6 +8,7 @@
namespace Hashing { namespace Hashing {
class Hasher : public Task { class Hasher : public Task {
Q_OBJECT
public: public:
using Ptr = shared_qobject_ptr<Hasher>; using Ptr = shared_qobject_ptr<Hasher>;
@ -21,6 +22,9 @@ class Hasher : public Task {
QString getResult() const { return m_hash; }; QString getResult() const { return m_hash; };
QString getPath() const { return m_path; }; QString getPath() const { return m_path; };
signals:
void resultsReady(QString hash);
protected: protected:
QString m_hash; QString m_hash;
QString m_path; QString m_path;
@ -48,6 +52,7 @@ class BlockedModHasher : public Hasher {
QStringList getHashTypes(); QStringList getHashTypes();
bool useHashType(QString type); bool useHashType(QString type);
private: private:
ModPlatform::ResourceProvider provider; ModPlatform::ResourceProvider provider;
QString hash_type; QString hash_type;

View File

@ -3,6 +3,7 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
#include "NetworkResourceAPI.h" #include "NetworkResourceAPI.h"
#include <memory>
#include "Application.h" #include "Application.h"
#include "net/NetJob.h" #include "net/NetJob.h"
@ -19,7 +20,7 @@ Task::Ptr NetworkResourceAPI::searchProjects(SearchArgs&& args, SearchCallbacks&
auto search_url = search_url_optional.value(); auto search_url = search_url_optional.value();
auto response = new QByteArray(); auto response = std::make_shared<QByteArray>();
auto netJob = makeShared<NetJob>(QString("%1::Search").arg(debugName()), APPLICATION->network()); auto netJob = makeShared<NetJob>(QString("%1::Search").arg(debugName()), APPLICATION->network());
netJob->addNetAction(Net::Download::makeByteArray(QUrl(search_url), response)); netJob->addNetAction(Net::Download::makeByteArray(QUrl(search_url), response));
@ -48,14 +49,13 @@ Task::Ptr NetworkResourceAPI::searchProjects(SearchArgs&& args, SearchCallbacks&
callbacks.on_fail(reason, network_error_code); callbacks.on_fail(reason, network_error_code);
}); });
QObject::connect(netJob.get(), &NetJob::aborted, [callbacks] { callbacks.on_abort(); }); QObject::connect(netJob.get(), &NetJob::aborted, [callbacks] { callbacks.on_abort(); });
QObject::connect(netJob.get(), &NetJob::finished, [response] { delete response; });
return netJob; return netJob;
} }
Task::Ptr NetworkResourceAPI::getProjectInfo(ProjectInfoArgs&& args, ProjectInfoCallbacks&& callbacks) const Task::Ptr NetworkResourceAPI::getProjectInfo(ProjectInfoArgs&& args, ProjectInfoCallbacks&& callbacks) const
{ {
auto response = new QByteArray(); auto response = std::make_shared<QByteArray>();
auto job = getProject(args.pack.addonId.toString(), response); auto job = getProject(args.pack.addonId.toString(), response);
QObject::connect(job.get(), &NetJob::succeeded, [response, callbacks, args] { QObject::connect(job.get(), &NetJob::succeeded, [response, callbacks, args] {
@ -83,7 +83,7 @@ Task::Ptr NetworkResourceAPI::getProjectVersions(VersionSearchArgs&& args, Versi
auto versions_url = versions_url_optional.value(); auto versions_url = versions_url_optional.value();
auto netJob = makeShared<NetJob>(QString("%1::Versions").arg(args.pack.name), APPLICATION->network()); auto netJob = makeShared<NetJob>(QString("%1::Versions").arg(args.pack.name), APPLICATION->network());
auto response = new QByteArray(); auto response = std::make_shared<QByteArray>();
netJob->addNetAction(Net::Download::makeByteArray(versions_url, response)); netJob->addNetAction(Net::Download::makeByteArray(versions_url, response));
@ -100,12 +100,10 @@ Task::Ptr NetworkResourceAPI::getProjectVersions(VersionSearchArgs&& args, Versi
callbacks.on_succeed(doc, args.pack); callbacks.on_succeed(doc, args.pack);
}); });
QObject::connect(netJob.get(), &NetJob::finished, [response] { delete response; });
return netJob; return netJob;
} }
Task::Ptr NetworkResourceAPI::getProject(QString addonId, QByteArray* response) const Task::Ptr NetworkResourceAPI::getProject(QString addonId, std::shared_ptr<QByteArray> response) const
{ {
auto project_url_optional = getInfoURL(addonId); auto project_url_optional = getInfoURL(addonId);
if (!project_url_optional.has_value()) if (!project_url_optional.has_value())
@ -117,8 +115,6 @@ Task::Ptr NetworkResourceAPI::getProject(QString addonId, QByteArray* response)
netJob->addNetAction(Net::Download::makeByteArray(QUrl(project_url), response)); netJob->addNetAction(Net::Download::makeByteArray(QUrl(project_url), response));
QObject::connect(netJob.get(), &NetJob::finished, [response] { delete response; });
return netJob; return netJob;
} }
@ -131,7 +127,7 @@ Task::Ptr NetworkResourceAPI::getDependencyVersion(DependencySearchArgs&& args,
auto versions_url = versions_url_optional.value(); auto versions_url = versions_url_optional.value();
auto netJob = makeShared<NetJob>(QString("%1::Dependency").arg(args.dependency.addonId.toString()), APPLICATION->network()); auto netJob = makeShared<NetJob>(QString("%1::Dependency").arg(args.dependency.addonId.toString()), APPLICATION->network());
auto response = new QByteArray(); auto response = std::make_shared<QByteArray>();
netJob->addNetAction(Net::Download::makeByteArray(versions_url, response)); netJob->addNetAction(Net::Download::makeByteArray(versions_url, response));
@ -148,7 +144,5 @@ Task::Ptr NetworkResourceAPI::getDependencyVersion(DependencySearchArgs&& args,
callbacks.on_succeed(doc, args.dependency); callbacks.on_succeed(doc, args.dependency);
}); });
QObject::connect(netJob.get(), &NetJob::finished, [response] { delete response; });
return netJob; return netJob;
}; };

View File

@ -4,13 +4,14 @@
#pragma once #pragma once
#include <memory>
#include "modplatform/ResourceAPI.h" #include "modplatform/ResourceAPI.h"
class NetworkResourceAPI : public ResourceAPI { class NetworkResourceAPI : public ResourceAPI {
public: public:
Task::Ptr searchProjects(SearchArgs&&, SearchCallbacks&&) const override; Task::Ptr searchProjects(SearchArgs&&, SearchCallbacks&&) const override;
Task::Ptr getProject(QString addonId, QByteArray* response) const override; Task::Ptr getProject(QString addonId, std::shared_ptr<QByteArray> response) const override;
Task::Ptr getProjectInfo(ProjectInfoArgs&&, ProjectInfoCallbacks&&) const override; Task::Ptr getProjectInfo(ProjectInfoArgs&&, ProjectInfoCallbacks&&) const override;
Task::Ptr getProjectVersions(VersionSearchArgs&&, VersionSearchCallbacks&&) const override; Task::Ptr getProjectVersions(VersionSearchArgs&&, VersionSearchCallbacks&&) const override;

View File

@ -51,11 +51,11 @@ void PackFetchTask::fetch()
QUrl publicPacksUrl = QUrl(BuildConfig.LEGACY_FTB_CDN_BASE_URL + "static/modpacks.xml"); QUrl publicPacksUrl = QUrl(BuildConfig.LEGACY_FTB_CDN_BASE_URL + "static/modpacks.xml");
qDebug() << "Downloading public version info from" << publicPacksUrl.toString(); qDebug() << "Downloading public version info from" << publicPacksUrl.toString();
jobPtr->addNetAction(Net::Download::makeByteArray(publicPacksUrl, &publicModpacksXmlFileData)); jobPtr->addNetAction(Net::Download::makeByteArray(publicPacksUrl, publicModpacksXmlFileData));
QUrl thirdPartyUrl = QUrl(BuildConfig.LEGACY_FTB_CDN_BASE_URL + "static/thirdparty.xml"); QUrl thirdPartyUrl = QUrl(BuildConfig.LEGACY_FTB_CDN_BASE_URL + "static/thirdparty.xml");
qDebug() << "Downloading thirdparty version info from" << thirdPartyUrl.toString(); qDebug() << "Downloading thirdparty version info from" << thirdPartyUrl.toString();
jobPtr->addNetAction(Net::Download::makeByteArray(thirdPartyUrl, &thirdPartyModpacksXmlFileData)); jobPtr->addNetAction(Net::Download::makeByteArray(thirdPartyUrl, thirdPartyModpacksXmlFileData));
QObject::connect(jobPtr.get(), &NetJob::succeeded, this, &PackFetchTask::fileDownloadFinished); QObject::connect(jobPtr.get(), &NetJob::succeeded, this, &PackFetchTask::fileDownloadFinished);
QObject::connect(jobPtr.get(), &NetJob::failed, this, &PackFetchTask::fileDownloadFailed); QObject::connect(jobPtr.get(), &NetJob::failed, this, &PackFetchTask::fileDownloadFailed);
@ -64,22 +64,19 @@ void PackFetchTask::fetch()
jobPtr->start(); jobPtr->start();
} }
void PackFetchTask::fetchPrivate(const QStringList & toFetch) void PackFetchTask::fetchPrivate(const QStringList& toFetch)
{ {
QString privatePackBaseUrl = BuildConfig.LEGACY_FTB_CDN_BASE_URL + "static/%1.xml"; QString privatePackBaseUrl = BuildConfig.LEGACY_FTB_CDN_BASE_URL + "static/%1.xml";
for (auto &packCode: toFetch) for (auto& packCode : toFetch) {
{ auto data = std::make_shared<QByteArray>();
QByteArray *data = new QByteArray(); NetJob* job = new NetJob("Fetching private pack", m_network);
NetJob *job = new NetJob("Fetching private pack", m_network);
job->addNetAction(Net::Download::makeByteArray(privatePackBaseUrl.arg(packCode), data)); job->addNetAction(Net::Download::makeByteArray(privatePackBaseUrl.arg(packCode), data));
QObject::connect(job, &NetJob::succeeded, this, [this, job, data, packCode] QObject::connect(job, &NetJob::succeeded, this, [this, job, data, packCode] {
{
ModpackList packs; ModpackList packs;
parseAndAddPacks(*data, PackType::Private, packs); parseAndAddPacks(*data, PackType::Private, packs);
foreach(Modpack currentPack, packs) foreach (Modpack currentPack, packs) {
{
currentPack.packCode = packCode; currentPack.packCode = packCode;
emit privateFileDownloadFinished(currentPack); emit privateFileDownloadFinished(currentPack);
} }
@ -87,24 +84,20 @@ void PackFetchTask::fetchPrivate(const QStringList & toFetch)
job->deleteLater(); job->deleteLater();
data->clear(); data->clear();
delete data;
}); });
QObject::connect(job, &NetJob::failed, this, [this, job, packCode, data](QString reason) QObject::connect(job, &NetJob::failed, this, [this, job, packCode, data](QString reason) {
{
emit privateFileDownloadFailed(reason, packCode); emit privateFileDownloadFailed(reason, packCode);
job->deleteLater(); job->deleteLater();
data->clear(); data->clear();
delete data;
}); });
QObject::connect(job, &NetJob::aborted, this, [this, job, data]{ QObject::connect(job, &NetJob::aborted, this, [this, job, data] {
emit aborted(); emit aborted();
job->deleteLater(); job->deleteLater();
data->clear(); data->clear();
delete data;
}); });
job->start(); job->start();
@ -117,27 +110,22 @@ void PackFetchTask::fileDownloadFinished()
QStringList failedLists; QStringList failedLists;
if(!parseAndAddPacks(publicModpacksXmlFileData, PackType::Public, publicPacks)) if (!parseAndAddPacks(*publicModpacksXmlFileData, PackType::Public, publicPacks)) {
{
failedLists.append(tr("Public Packs")); failedLists.append(tr("Public Packs"));
} }
if(!parseAndAddPacks(thirdPartyModpacksXmlFileData, PackType::ThirdParty, thirdPartyPacks)) if (!parseAndAddPacks(*thirdPartyModpacksXmlFileData, PackType::ThirdParty, thirdPartyPacks)) {
{
failedLists.append(tr("Third Party Packs")); failedLists.append(tr("Third Party Packs"));
} }
if(failedLists.size() > 0) if (failedLists.size() > 0) {
{
emit failed(tr("Failed to download some pack lists: %1").arg(failedLists.join("\n- "))); emit failed(tr("Failed to download some pack lists: %1").arg(failedLists.join("\n- ")));
} } else {
else
{
emit finished(publicPacks, thirdPartyPacks); emit finished(publicPacks, thirdPartyPacks);
} }
} }
bool PackFetchTask::parseAndAddPacks(QByteArray &data, PackType packType, ModpackList &list) bool PackFetchTask::parseAndAddPacks(QByteArray& data, PackType packType, ModpackList& list)
{ {
QDomDocument doc; QDomDocument doc;
@ -145,8 +133,7 @@ bool PackFetchTask::parseAndAddPacks(QByteArray &data, PackType packType, Modpac
int errorLine = -1; int errorLine = -1;
int errorCol = -1; int errorCol = -1;
if(!doc.setContent(data, false, &errorMsg, &errorLine, &errorCol)) if (!doc.setContent(data, false, &errorMsg, &errorLine, &errorCol)) {
{
auto fullErrMsg = QString("Failed to fetch modpack data: %1 %2:%3!").arg(errorMsg).arg(errorLine).arg(errorCol); auto fullErrMsg = QString("Failed to fetch modpack data: %1 %2:%3!").arg(errorMsg).arg(errorLine).arg(errorCol);
qWarning() << fullErrMsg; qWarning() << fullErrMsg;
data.clear(); data.clear();
@ -154,8 +141,7 @@ bool PackFetchTask::parseAndAddPacks(QByteArray &data, PackType packType, Modpac
} }
QDomNodeList nodes = doc.elementsByTagName("modpack"); QDomNodeList nodes = doc.elementsByTagName("modpack");
for(int i = 0; i < nodes.length(); i++) for (int i = 0; i < nodes.length(); i++) {
{
QDomElement element = nodes.at(i).toElement(); QDomElement element = nodes.at(i).toElement();
Modpack modpack; Modpack modpack;
@ -169,26 +155,20 @@ bool PackFetchTask::parseAndAddPacks(QByteArray &data, PackType packType, Modpac
modpack.broken = false; modpack.broken = false;
modpack.bugged = false; modpack.bugged = false;
//remove empty if the xml is bugged // remove empty if the xml is bugged
for(QString curr : modpack.oldVersions) for (QString curr : modpack.oldVersions) {
{ if (curr.isNull() || curr.isEmpty()) {
if(curr.isNull() || curr.isEmpty())
{
modpack.oldVersions.removeAll(curr); modpack.oldVersions.removeAll(curr);
modpack.bugged = true; modpack.bugged = true;
qWarning() << "Removed some empty versions from" << modpack.name; qWarning() << "Removed some empty versions from" << modpack.name;
} }
} }
if(modpack.oldVersions.size() < 1) if (modpack.oldVersions.size() < 1) {
{ if (!modpack.currentVersion.isNull() && !modpack.currentVersion.isEmpty()) {
if(!modpack.currentVersion.isNull() && !modpack.currentVersion.isEmpty())
{
modpack.oldVersions.append(modpack.currentVersion); modpack.oldVersions.append(modpack.currentVersion);
qWarning() << "Added current version to oldVersions because oldVersions was empty! (" + modpack.name + ")"; qWarning() << "Added current version to oldVersions because oldVersions was empty! (" + modpack.name + ")";
} } else {
else
{
modpack.broken = true; modpack.broken = true;
qWarning() << "Broken pack:" << modpack.name << " => No valid version!"; qWarning() << "Broken pack:" << modpack.name << " => No valid version!";
} }
@ -218,4 +198,4 @@ void PackFetchTask::fileDownloadAborted()
emit aborted(); emit aborted();
} }
} } // namespace LegacyFTB

View File

@ -1,41 +1,41 @@
#pragma once #pragma once
#include "net/NetJob.h"
#include <QTemporaryDir>
#include <QByteArray> #include <QByteArray>
#include <QObject> #include <QObject>
#include <QTemporaryDir>
#include <memory>
#include "PackHelpers.h" #include "PackHelpers.h"
#include "net/NetJob.h"
namespace LegacyFTB { namespace LegacyFTB {
class PackFetchTask : public QObject { class PackFetchTask : public QObject {
Q_OBJECT Q_OBJECT
public: public:
PackFetchTask(shared_qobject_ptr<QNetworkAccessManager> network) : QObject(nullptr), m_network(network) {}; PackFetchTask(shared_qobject_ptr<QNetworkAccessManager> network) : QObject(nullptr), m_network(network){};
virtual ~PackFetchTask() = default; virtual ~PackFetchTask() = default;
void fetch(); void fetch();
void fetchPrivate(const QStringList &toFetch); void fetchPrivate(const QStringList& toFetch);
private: private:
shared_qobject_ptr<QNetworkAccessManager> m_network; shared_qobject_ptr<QNetworkAccessManager> m_network;
NetJob::Ptr jobPtr; NetJob::Ptr jobPtr;
QByteArray publicModpacksXmlFileData; std::shared_ptr<QByteArray> publicModpacksXmlFileData = std::make_shared<QByteArray>();
QByteArray thirdPartyModpacksXmlFileData; std::shared_ptr<QByteArray> thirdPartyModpacksXmlFileData = std::make_shared<QByteArray>();
bool parseAndAddPacks(QByteArray &data, PackType packType, ModpackList &list); bool parseAndAddPacks(QByteArray& data, PackType packType, ModpackList& list);
ModpackList publicPacks; ModpackList publicPacks;
ModpackList thirdPartyPacks; ModpackList thirdPartyPacks;
protected slots: protected slots:
void fileDownloadFinished(); void fileDownloadFinished();
void fileDownloadFailed(QString reason); void fileDownloadFailed(QString reason);
void fileDownloadAborted(); void fileDownloadAborted();
signals: signals:
void finished(ModpackList publicPacks, ModpackList thirdPartyPacks); void finished(ModpackList publicPacks, ModpackList thirdPartyPacks);
void failed(QString reason); void failed(QString reason);
void aborted(); void aborted();
@ -44,4 +44,4 @@ signals:
void privateFileDownloadFailed(QString reason, QString packCode); void privateFileDownloadFailed(QString reason, QString packCode);
}; };
} } // namespace LegacyFTB

View File

@ -9,19 +9,17 @@
#include "net/NetJob.h" #include "net/NetJob.h"
#include "net/Upload.h" #include "net/Upload.h"
Task::Ptr ModrinthAPI::currentVersion(QString hash, QString hash_format, QByteArray* response) Task::Ptr ModrinthAPI::currentVersion(QString hash, QString hash_format, std::shared_ptr<QByteArray> response)
{ {
auto netJob = makeShared<NetJob>(QString("Modrinth::GetCurrentVersion"), APPLICATION->network()); auto netJob = makeShared<NetJob>(QString("Modrinth::GetCurrentVersion"), APPLICATION->network());
netJob->addNetAction(Net::Download::makeByteArray( netJob->addNetAction(Net::Download::makeByteArray(
QString(BuildConfig.MODRINTH_PROD_URL + "/version_file/%1?algorithm=%2").arg(hash, hash_format), response)); QString(BuildConfig.MODRINTH_PROD_URL + "/version_file/%1?algorithm=%2").arg(hash, hash_format), response));
QObject::connect(netJob.get(), &NetJob::finished, [response] { delete response; });
return netJob; return netJob;
} }
Task::Ptr ModrinthAPI::currentVersions(const QStringList& hashes, QString hash_format, QByteArray* response) Task::Ptr ModrinthAPI::currentVersions(const QStringList& hashes, QString hash_format, std::shared_ptr<QByteArray> response)
{ {
auto netJob = makeShared<NetJob>(QString("Modrinth::GetCurrentVersions"), APPLICATION->network()); auto netJob = makeShared<NetJob>(QString("Modrinth::GetCurrentVersions"), APPLICATION->network());
@ -35,8 +33,6 @@ Task::Ptr ModrinthAPI::currentVersions(const QStringList& hashes, QString hash_f
netJob->addNetAction(Net::Upload::makeByteArray(QString(BuildConfig.MODRINTH_PROD_URL + "/version_files"), response, body_raw)); netJob->addNetAction(Net::Upload::makeByteArray(QString(BuildConfig.MODRINTH_PROD_URL + "/version_files"), response, body_raw));
QObject::connect(netJob.get(), &NetJob::finished, [response] { delete response; });
return netJob; return netJob;
} }
@ -44,7 +40,7 @@ Task::Ptr ModrinthAPI::latestVersion(QString hash,
QString hash_format, QString hash_format,
std::optional<std::list<Version>> mcVersions, std::optional<std::list<Version>> mcVersions,
std::optional<ModLoaderTypes> loaders, std::optional<ModLoaderTypes> loaders,
QByteArray* response) std::shared_ptr<QByteArray> response)
{ {
auto netJob = makeShared<NetJob>(QString("Modrinth::GetLatestVersion"), APPLICATION->network()); auto netJob = makeShared<NetJob>(QString("Modrinth::GetLatestVersion"), APPLICATION->network());
@ -67,8 +63,6 @@ Task::Ptr ModrinthAPI::latestVersion(QString hash,
netJob->addNetAction(Net::Upload::makeByteArray( netJob->addNetAction(Net::Upload::makeByteArray(
QString(BuildConfig.MODRINTH_PROD_URL + "/version_file/%1/update?algorithm=%2").arg(hash, hash_format), response, body_raw)); QString(BuildConfig.MODRINTH_PROD_URL + "/version_file/%1/update?algorithm=%2").arg(hash, hash_format), response, body_raw));
QObject::connect(netJob.get(), &NetJob::finished, [response] { delete response; });
return netJob; return netJob;
} }
@ -76,7 +70,7 @@ Task::Ptr ModrinthAPI::latestVersions(const QStringList& hashes,
QString hash_format, QString hash_format,
std::optional<std::list<Version>> mcVersions, std::optional<std::list<Version>> mcVersions,
std::optional<ModLoaderTypes> loaders, std::optional<ModLoaderTypes> loaders,
QByteArray* response) std::shared_ptr<QByteArray> response)
{ {
auto netJob = makeShared<NetJob>(QString("Modrinth::GetLatestVersions"), APPLICATION->network()); auto netJob = makeShared<NetJob>(QString("Modrinth::GetLatestVersions"), APPLICATION->network());
@ -101,22 +95,16 @@ Task::Ptr ModrinthAPI::latestVersions(const QStringList& hashes,
netJob->addNetAction(Net::Upload::makeByteArray(QString(BuildConfig.MODRINTH_PROD_URL + "/version_files/update"), response, body_raw)); netJob->addNetAction(Net::Upload::makeByteArray(QString(BuildConfig.MODRINTH_PROD_URL + "/version_files/update"), response, body_raw));
QObject::connect(netJob.get(), &NetJob::finished, [response] { delete response; });
return netJob; return netJob;
} }
Task::Ptr ModrinthAPI::getProjects(QStringList addonIds, QByteArray* response) const Task::Ptr ModrinthAPI::getProjects(QStringList addonIds, std::shared_ptr<QByteArray> response) const
{ {
auto netJob = makeShared<NetJob>(QString("Modrinth::GetProjects"), APPLICATION->network()); auto netJob = makeShared<NetJob>(QString("Modrinth::GetProjects"), APPLICATION->network());
auto searchUrl = getMultipleModInfoURL(addonIds); auto searchUrl = getMultipleModInfoURL(addonIds);
netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), response)); netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), response));
QObject::connect(netJob.get(), &NetJob::finished, [response, netJob] {
delete response;
});
return netJob; return netJob;
} }

View File

@ -12,23 +12,23 @@
class ModrinthAPI : public NetworkResourceAPI { class ModrinthAPI : public NetworkResourceAPI {
public: public:
auto currentVersion(QString hash, QString hash_format, QByteArray* response) -> Task::Ptr; auto currentVersion(QString hash, QString hash_format, std::shared_ptr<QByteArray> response) -> Task::Ptr;
auto currentVersions(const QStringList& hashes, QString hash_format, QByteArray* response) -> Task::Ptr; auto currentVersions(const QStringList& hashes, QString hash_format, std::shared_ptr<QByteArray> response) -> Task::Ptr;
auto latestVersion(QString hash, auto latestVersion(QString hash,
QString hash_format, QString hash_format,
std::optional<std::list<Version>> mcVersions, std::optional<std::list<Version>> mcVersions,
std::optional<ModLoaderTypes> loaders, std::optional<ModLoaderTypes> loaders,
QByteArray* response) -> Task::Ptr; std::shared_ptr<QByteArray> response) -> Task::Ptr;
auto latestVersions(const QStringList& hashes, auto latestVersions(const QStringList& hashes,
QString hash_format, QString hash_format,
std::optional<std::list<Version>> mcVersions, std::optional<std::list<Version>> mcVersions,
std::optional<ModLoaderTypes> loaders, std::optional<ModLoaderTypes> loaders,
QByteArray* response) -> Task::Ptr; std::shared_ptr<QByteArray> response) -> Task::Ptr;
Task::Ptr getProjects(QStringList addonIds, QByteArray* response) const override; Task::Ptr getProjects(QStringList addonIds, std::shared_ptr<QByteArray> response) const override;
public: public:
[[nodiscard]] auto getSortingMethods() const -> QList<ResourceAPI::SortingMethod> override; [[nodiscard]] auto getSortingMethods() const -> QList<ResourceAPI::SortingMethod> override;

View File

@ -53,12 +53,11 @@ void ModrinthCheckUpdate::executeTask()
// (though it will rarely happen, if at all) // (though it will rarely happen, if at all)
if (mod->metadata()->hash_format != best_hash_type) { if (mod->metadata()->hash_format != best_hash_type) {
auto hash_task = Hashing::createModrinthHasher(mod->fileinfo().absoluteFilePath()); auto hash_task = Hashing::createModrinthHasher(mod->fileinfo().absoluteFilePath());
connect(hash_task.get(), &Task::succeeded, [&] { connect(hash_task.get(), &Hashing::Hasher::resultsReady, [&hashes, &mappings, mod](QString hash) {
QString hash(hash_task->getResult());
hashes.append(hash); hashes.append(hash);
mappings.insert(hash, mod); mappings.insert(hash, mod);
}); });
connect(hash_task.get(), &Task::failed, [this, hash_task] { failed("Failed to generate hash"); }); connect(hash_task.get(), &Task::failed, [this] { failed("Failed to generate hash"); });
hashing_task.addTask(hash_task); hashing_task.addTask(hash_task);
} else { } else {
hashes.append(hash); hashes.append(hash);
@ -71,7 +70,7 @@ void ModrinthCheckUpdate::executeTask()
hashing_task.start(); hashing_task.start();
loop.exec(); loop.exec();
auto* response = new QByteArray(); auto response = std::make_shared<QByteArray>();
auto job = api.latestVersions(hashes, best_hash_type, m_game_versions, m_loaders, response); auto job = api.latestVersions(hashes, best_hash_type, m_game_versions, m_loaders, response);
QEventLoop lock; QEventLoop lock;

View File

@ -214,7 +214,7 @@ bool ModrinthCreationTask::createInstance()
if (m_instIcon != "default") { if (m_instIcon != "default") {
instance.setIconKey(m_instIcon); instance.setIconKey(m_instIcon);
} else { } else if (!m_managed_id.isEmpty()) {
instance.setIconKey("modrinth"); instance.setIconKey("modrinth");
} }

View File

@ -157,7 +157,7 @@ void ModrinthPackExportTask::makeApiRequest()
if (pendingHashes.isEmpty()) if (pendingHashes.isEmpty())
buildZip(); buildZip();
else { else {
QByteArray* response = new QByteArray; auto response = std::make_shared<QByteArray>();
task = api.currentVersions(pendingHashes.values(), "sha512", response); task = api.currentVersions(pendingHashes.values(), "sha512", response);
connect(task.get(), &NetJob::succeeded, [this, response]() { parseApiResponse(response); }); connect(task.get(), &NetJob::succeeded, [this, response]() { parseApiResponse(response); });
connect(task.get(), &NetJob::failed, this, &ModrinthPackExportTask::emitFailed); connect(task.get(), &NetJob::failed, this, &ModrinthPackExportTask::emitFailed);
@ -165,7 +165,7 @@ void ModrinthPackExportTask::makeApiRequest()
} }
} }
void ModrinthPackExportTask::parseApiResponse(const QByteArray* response) void ModrinthPackExportTask::parseApiResponse(const std::shared_ptr<QByteArray> response)
{ {
task = nullptr; task = nullptr;

View File

@ -69,7 +69,7 @@ class ModrinthPackExportTask : public Task {
void collectFiles(); void collectFiles();
void collectHashes(); void collectHashes();
void makeApiRequest(); void makeApiRequest();
void parseApiResponse(const QByteArray* response); void parseApiResponse(const std::shared_ptr<QByteArray> response);
void buildZip(); void buildZip();
void finish(); void finish();

View File

@ -37,20 +37,19 @@
#include <FileSystem.h> #include <FileSystem.h>
#include <Json.h> #include <Json.h>
#include <QtConcurrentRun>
#include <MMCZip.h> #include <MMCZip.h>
#include <QtConcurrentRun>
#include "TechnicPackProcessor.h"
#include "SolderPackManifest.h" #include "SolderPackManifest.h"
#include "TechnicPackProcessor.h"
#include "net/ChecksumValidator.h" #include "net/ChecksumValidator.h"
Technic::SolderPackInstallTask::SolderPackInstallTask( Technic::SolderPackInstallTask::SolderPackInstallTask(shared_qobject_ptr<QNetworkAccessManager> network,
shared_qobject_ptr<QNetworkAccessManager> network, const QUrl& solderUrl,
const QUrl &solderUrl, const QString& pack,
const QString &pack, const QString& version,
const QString &version, const QString& minecraftVersion)
const QString &minecraftVersion {
) {
m_solderUrl = solderUrl; m_solderUrl = solderUrl;
m_pack = pack; m_pack = pack;
m_version = version; m_version = version;
@ -58,9 +57,9 @@ Technic::SolderPackInstallTask::SolderPackInstallTask(
m_minecraftVersion = minecraftVersion; m_minecraftVersion = minecraftVersion;
} }
bool Technic::SolderPackInstallTask::abort() { bool Technic::SolderPackInstallTask::abort()
if(m_abortable) {
{ if (m_abortable) {
return m_filesNetJob->abort(); return m_filesNetJob->abort();
} }
return false; return false;
@ -72,7 +71,7 @@ void Technic::SolderPackInstallTask::executeTask()
m_filesNetJob.reset(new NetJob(tr("Resolving modpack files"), m_network)); m_filesNetJob.reset(new NetJob(tr("Resolving modpack files"), m_network));
auto sourceUrl = QString("%1/modpack/%2/%3").arg(m_solderUrl.toString(), m_pack, m_version); auto sourceUrl = QString("%1/modpack/%2/%3").arg(m_solderUrl.toString(), m_pack, m_version);
m_filesNetJob->addNetAction(Net::Download::makeByteArray(sourceUrl, &m_response)); m_filesNetJob->addNetAction(Net::Download::makeByteArray(sourceUrl, m_response));
auto job = m_filesNetJob.get(); auto job = m_filesNetJob.get();
connect(job, &NetJob::succeeded, this, &Technic::SolderPackInstallTask::fileListSucceeded); connect(job, &NetJob::succeeded, this, &Technic::SolderPackInstallTask::fileListSucceeded);
@ -85,11 +84,11 @@ void Technic::SolderPackInstallTask::fileListSucceeded()
{ {
setStatus(tr("Downloading modpack")); setStatus(tr("Downloading modpack"));
QJsonParseError parse_error {}; QJsonParseError parse_error{};
QJsonDocument doc = QJsonDocument::fromJson(m_response, &parse_error); QJsonDocument doc = QJsonDocument::fromJson(*m_response, &parse_error);
if (parse_error.error != QJsonParseError::NoError) { if (parse_error.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response from Solder at " << parse_error.offset << " reason: " << parse_error.errorString(); qWarning() << "Error while parsing JSON response from Solder at " << parse_error.offset << " reason: " << parse_error.errorString();
qWarning() << m_response; qWarning() << *m_response;
return; return;
} }
auto obj = doc.object(); auto obj = doc.object();
@ -110,7 +109,7 @@ void Technic::SolderPackInstallTask::fileListSucceeded()
m_filesNetJob.reset(new NetJob(tr("Downloading modpack"), m_network)); m_filesNetJob.reset(new NetJob(tr("Downloading modpack"), m_network));
int i = 0; int i = 0;
for (const auto &mod : build.mods) { for (const auto& mod : build.mods) {
auto path = FS::PathCombine(m_outputDir.path(), QString("%1").arg(i)); auto path = FS::PathCombine(m_outputDir.path(), QString("%1").arg(i));
auto dl = Net::Download::makeFile(mod.url, path); auto dl = Net::Download::makeFile(mod.url, path);

View File

@ -40,45 +40,48 @@
#include <tasks/Task.h> #include <tasks/Task.h>
#include <QUrl> #include <QUrl>
#include <memory>
namespace Technic namespace Technic {
{ class SolderPackInstallTask : public InstanceTask {
class SolderPackInstallTask : public InstanceTask Q_OBJECT
{ public:
Q_OBJECT explicit SolderPackInstallTask(shared_qobject_ptr<QNetworkAccessManager> network,
public: const QUrl& solderUrl,
explicit SolderPackInstallTask(shared_qobject_ptr<QNetworkAccessManager> network, const QUrl &solderUrl, const QString& pack, const QString& version, const QString &minecraftVersion); const QString& pack,
const QString& version,
const QString& minecraftVersion);
bool canAbort() const override { return true; } bool canAbort() const override { return true; }
bool abort() override; bool abort() override;
protected: protected:
//! Entry point for tasks. //! Entry point for tasks.
virtual void executeTask() override; virtual void executeTask() override;
private slots: private slots:
void fileListSucceeded(); void fileListSucceeded();
void downloadSucceeded(); void downloadSucceeded();
void downloadFailed(QString reason); void downloadFailed(QString reason);
void downloadProgressChanged(qint64 current, qint64 total); void downloadProgressChanged(qint64 current, qint64 total);
void downloadAborted(); void downloadAborted();
void extractFinished(); void extractFinished();
void extractAborted(); void extractAborted();
private: private:
bool m_abortable = false; bool m_abortable = false;
shared_qobject_ptr<QNetworkAccessManager> m_network; shared_qobject_ptr<QNetworkAccessManager> m_network;
NetJob::Ptr m_filesNetJob; NetJob::Ptr m_filesNetJob;
QUrl m_solderUrl; QUrl m_solderUrl;
QString m_pack; QString m_pack;
QString m_version; QString m_version;
QString m_minecraftVersion; QString m_minecraftVersion;
QByteArray m_response; std::shared_ptr<QByteArray> m_response = std::make_shared<QByteArray>();
QTemporaryDir m_outputDir; QTemporaryDir m_outputDir;
int m_modCount; int m_modCount;
QFuture<bool> m_extractFuture; QFuture<bool> m_extractFuture;
QFutureWatcher<bool> m_extractFutureWatcher; QFutureWatcher<bool> m_extractFutureWatcher;
}; };
} } // namespace Technic

View File

@ -1,7 +1,8 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
/* /*
* PolyMC - Minecraft Launcher * Prism Launcher - Minecraft Launcher
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com> * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -46,7 +47,7 @@ namespace Net {
*/ */
class ByteArraySink : public Sink { class ByteArraySink : public Sink {
public: public:
ByteArraySink(QByteArray* output) : m_output(output){}; ByteArraySink(std::shared_ptr<QByteArray> output) : m_output(output){};
virtual ~ByteArraySink() = default; virtual ~ByteArraySink() = default;
@ -93,6 +94,6 @@ class ByteArraySink : public Sink {
auto hasLocalData() -> bool override { return false; } auto hasLocalData() -> bool override { return false; }
private: private:
QByteArray* m_output; std::shared_ptr<QByteArray> m_output;
}; };
} // namespace Net } // namespace Net

View File

@ -41,6 +41,7 @@
#include <QDateTime> #include <QDateTime>
#include <QFileInfo> #include <QFileInfo>
#include <memory>
#include "ByteArraySink.h" #include "ByteArraySink.h"
#include "ChecksumValidator.h" #include "ChecksumValidator.h"
@ -69,7 +70,7 @@ auto Download::makeCached(QUrl url, MetaEntryPtr entry, Options options) -> Down
return dl; return dl;
} }
auto Download::makeByteArray(QUrl url, QByteArray* output, Options options) -> Download::Ptr auto Download::makeByteArray(QUrl url, std::shared_ptr<QByteArray> output, Options options) -> Download::Ptr
{ {
auto dl = makeShared<Download>(); auto dl = makeShared<Download>();
dl->m_url = url; dl->m_url = url;

View File

@ -60,7 +60,7 @@ class Download : public NetAction {
~Download() override = default; ~Download() override = default;
static auto makeCached(QUrl url, MetaEntryPtr entry, Options options = Option::NoOptions) -> Download::Ptr; static auto makeCached(QUrl url, MetaEntryPtr entry, Options options = Option::NoOptions) -> Download::Ptr;
static auto makeByteArray(QUrl url, QByteArray* output, Options options = Option::NoOptions) -> Download::Ptr; static auto makeByteArray(QUrl url, std::shared_ptr<QByteArray> output, Options options = Option::NoOptions) -> Download::Ptr;
static auto makeFile(QUrl url, QString path, Options options = Option::NoOptions) -> Download::Ptr; static auto makeFile(QUrl url, QString path, Options options = Option::NoOptions) -> Download::Ptr;
public: public:

View File

@ -39,218 +39,226 @@
#include "Upload.h" #include "Upload.h"
#include <utility> #include <utility>
#include "ByteArraySink.h"
#include "BuildConfig.h"
#include "Application.h" #include "Application.h"
#include "BuildConfig.h"
#include "ByteArraySink.h"
#include "net/Logging.h" #include "net/Logging.h"
namespace Net { namespace Net {
bool Upload::abort() bool Upload::abort()
{ {
if (m_reply) { if (m_reply) {
m_reply->abort(); m_reply->abort();
} else { } else {
m_state = State::AbortedByUser; m_state = State::AbortedByUser;
}
return true;
}
void Upload::downloadProgress(qint64 bytesReceived, qint64 bytesTotal)
{
setProgress(bytesReceived, bytesTotal);
}
void Upload::downloadError(QNetworkReply::NetworkError error)
{
if (error == QNetworkReply::OperationCanceledError) {
qCCritical(taskUploadLogC) << getUid().toString() << "Aborted " << m_url.toString();
m_state = State::AbortedByUser;
} else {
// error happened during download.
qCCritical(taskUploadLogC) << getUid().toString() << "Failed " << m_url.toString() << " with reason " << error;
m_state = State::Failed;
}
}
void Upload::sslErrors(const QList<QSslError>& errors)
{
int i = 1;
for (const auto& error : errors) {
qCCritical(taskUploadLogC) << getUid().toString() << "Upload" << m_url.toString() << "SSL Error #" << i << " : "
<< error.errorString();
auto cert = error.certificate();
qCCritical(taskUploadLogC) << getUid().toString() << "Certificate in question:\n" << cert.toText();
i++;
}
}
bool Upload::handleRedirect()
{
QUrl redirect = m_reply->header(QNetworkRequest::LocationHeader).toUrl();
if (!redirect.isValid()) {
if (!m_reply->hasRawHeader("Location")) {
// no redirect -> it's fine to continue
return false;
} }
return true; // there is a Location header, but it's not correct. we need to apply some workarounds...
} QByteArray redirectBA = m_reply->rawHeader("Location");
if (redirectBA.size() == 0) {
void Upload::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) { // empty, yet present redirect header? WTF?
setProgress(bytesReceived, bytesTotal); return false;
}
void Upload::downloadError(QNetworkReply::NetworkError error) {
if (error == QNetworkReply::OperationCanceledError) {
qCCritical(taskUploadLogC) << getUid().toString() << "Aborted " << m_url.toString();
m_state = State::AbortedByUser;
} else {
// error happened during download.
qCCritical(taskUploadLogC) << getUid().toString() << "Failed " << m_url.toString() << " with reason " << error;
m_state = State::Failed;
} }
} QString redirectStr = QString::fromUtf8(redirectBA);
void Upload::sslErrors(const QList<QSslError> &errors) {
int i = 1;
for (const auto& error : errors) {
qCCritical(taskUploadLogC) << getUid().toString() << "Upload" << m_url.toString() << "SSL Error #" << i << " : " << error.errorString();
auto cert = error.certificate();
qCCritical(taskUploadLogC) << getUid().toString() << "Certificate in question:\n" << cert.toText();
i++;
}
}
bool Upload::handleRedirect()
{
QUrl redirect = m_reply->header(QNetworkRequest::LocationHeader).toUrl();
if (!redirect.isValid()) {
if (!m_reply->hasRawHeader("Location")) {
// no redirect -> it's fine to continue
return false;
}
// there is a Location header, but it's not correct. we need to apply some workarounds...
QByteArray redirectBA = m_reply->rawHeader("Location");
if (redirectBA.size() == 0) {
// empty, yet present redirect header? WTF?
return false;
}
QString redirectStr = QString::fromUtf8(redirectBA);
if (redirectStr.startsWith("//")) {
/*
* IF the URL begins with //, we need to insert the URL scheme.
* See: https://bugreports.qt.io/browse/QTBUG-41061
* See: http://tools.ietf.org/html/rfc3986#section-4.2
*/
redirectStr = m_reply->url().scheme() + ":" + redirectStr;
} else if (redirectStr.startsWith("/")) {
/*
* IF the URL begins with /, we need to process it as a relative URL
*/
auto url = m_reply->url();
url.setPath(redirectStr, QUrl::TolerantMode);
redirectStr = url.toString();
}
if (redirectStr.startsWith("//")) {
/* /*
* Next, make sure the URL is parsed in tolerant mode. Qt doesn't parse the location header in tolerant mode, which causes issues. * IF the URL begins with //, we need to insert the URL scheme.
* FIXME: report Qt bug for this * See: https://bugreports.qt.io/browse/QTBUG-41061
* See: http://tools.ietf.org/html/rfc3986#section-4.2
*/ */
redirect = QUrl(redirectStr, QUrl::TolerantMode); redirectStr = m_reply->url().scheme() + ":" + redirectStr;
if (!redirect.isValid()) { } else if (redirectStr.startsWith("/")) {
qCWarning(taskUploadLogC) << getUid().toString() << "Failed to parse redirect URL:" << redirectStr; /*
downloadError(QNetworkReply::ProtocolFailure); * IF the URL begins with /, we need to process it as a relative URL
return false; */
} auto url = m_reply->url();
qCDebug(taskUploadLogC) << getUid().toString() << "Fixed location header:" << redirect; url.setPath(redirectStr, QUrl::TolerantMode);
} else { redirectStr = url.toString();
qCDebug(taskUploadLogC) << getUid().toString() << "Location header:" << redirect;
} }
m_url = QUrl(redirect.toString()); /*
qCDebug(taskUploadLogC) << getUid().toString() << "Following redirect to " << m_url.toString(); * Next, make sure the URL is parsed in tolerant mode. Qt doesn't parse the location header in tolerant mode, which causes issues.
startAction(m_network); * FIXME: report Qt bug for this
return true; */
redirect = QUrl(redirectStr, QUrl::TolerantMode);
if (!redirect.isValid()) {
qCWarning(taskUploadLogC) << getUid().toString() << "Failed to parse redirect URL:" << redirectStr;
downloadError(QNetworkReply::ProtocolFailure);
return false;
}
qCDebug(taskUploadLogC) << getUid().toString() << "Fixed location header:" << redirect;
} else {
qCDebug(taskUploadLogC) << getUid().toString() << "Location header:" << redirect;
} }
void Upload::downloadFinished() { m_url = QUrl(redirect.toString());
// handle HTTP redirection first qCDebug(taskUploadLogC) << getUid().toString() << "Following redirect to " << m_url.toString();
// very unlikely for post requests, still can happen startAction(m_network);
if (handleRedirect()) { return true;
qCDebug(taskUploadLogC) << getUid().toString() << "Upload redirected:" << m_url.toString(); }
return;
}
// if the download failed before this point ... void Upload::downloadFinished()
if (m_state == State::Succeeded) { {
qCDebug(taskUploadLogC) << getUid().toString() << "Upload failed but we are allowed to proceed:" << m_url.toString(); // handle HTTP redirection first
m_sink->abort(); // very unlikely for post requests, still can happen
m_reply.reset(); if (handleRedirect()) {
emit succeeded(); qCDebug(taskUploadLogC) << getUid().toString() << "Upload redirected:" << m_url.toString();
return; return;
} else if (m_state == State::Failed) { }
qCDebug(taskUploadLogC) << getUid().toString() << "Upload failed in previous step:" << m_url.toString();
m_sink->abort();
m_reply.reset();
emit failed("");
return;
} else if (m_state == State::AbortedByUser) {
qCDebug(taskUploadLogC) << getUid().toString() << "Upload aborted in previous step:" << m_url.toString();
m_sink->abort();
m_reply.reset();
emit aborted();
return;
}
// make sure we got all the remaining data, if any // if the download failed before this point ...
auto data = m_reply->readAll(); if (m_state == State::Succeeded) {
if (data.size()) { qCDebug(taskUploadLogC) << getUid().toString() << "Upload failed but we are allowed to proceed:" << m_url.toString();
qCDebug(taskUploadLogC) << getUid().toString() << "Writing extra" << data.size() << "bytes"; m_sink->abort();
m_state = m_sink->write(data);
}
// otherwise, finalize the whole graph
m_state = m_sink->finalize(*m_reply.get());
if (m_state != State::Succeeded) {
qCDebug(taskUploadLogC) << getUid().toString() << "Upload failed to finalize:" << m_url.toString();
m_sink->abort();
m_reply.reset();
emit failed("");
return;
}
m_reply.reset(); m_reply.reset();
qCDebug(taskUploadLogC) << getUid().toString() << "Upload succeeded:" << m_url.toString();
emit succeeded(); emit succeeded();
return;
} else if (m_state == State::Failed) {
qCDebug(taskUploadLogC) << getUid().toString() << "Upload failed in previous step:" << m_url.toString();
m_sink->abort();
m_reply.reset();
emit failed("");
return;
} else if (m_state == State::AbortedByUser) {
qCDebug(taskUploadLogC) << getUid().toString() << "Upload aborted in previous step:" << m_url.toString();
m_sink->abort();
m_reply.reset();
emit aborted();
return;
} }
void Upload::downloadReadyRead() { // make sure we got all the remaining data, if any
if (m_state == State::Running) { auto data = m_reply->readAll();
auto data = m_reply->readAll(); if (data.size()) {
m_state = m_sink->write(data); qCDebug(taskUploadLogC) << getUid().toString() << "Writing extra" << data.size() << "bytes";
} m_state = m_sink->write(data);
} }
void Upload::executeTask() { // otherwise, finalize the whole graph
setStatus(tr("Uploading %1").arg(m_url.toString())); m_state = m_sink->finalize(*m_reply.get());
if (m_state != State::Succeeded) {
qCDebug(taskUploadLogC) << getUid().toString() << "Upload failed to finalize:" << m_url.toString();
m_sink->abort();
m_reply.reset();
emit failed("");
return;
}
m_reply.reset();
qCDebug(taskUploadLogC) << getUid().toString() << "Upload succeeded:" << m_url.toString();
emit succeeded();
}
if (m_state == State::AbortedByUser) { void Upload::downloadReadyRead()
qCWarning(taskUploadLogC) << getUid().toString() << "Attempt to start an aborted Upload:" << m_url.toString(); {
emit aborted(); if (m_state == State::Running) {
auto data = m_reply->readAll();
m_state = m_sink->write(data);
}
}
void Upload::executeTask()
{
setStatus(tr("Uploading %1").arg(m_url.toString()));
if (m_state == State::AbortedByUser) {
qCWarning(taskUploadLogC) << getUid().toString() << "Attempt to start an aborted Upload:" << m_url.toString();
emit aborted();
return;
}
QNetworkRequest request(m_url);
m_state = m_sink->init(request);
switch (m_state) {
case State::Succeeded:
emitSucceeded();
qCDebug(taskUploadLogC) << getUid().toString() << "Upload cache hit " << m_url.toString();
return; return;
} case State::Running:
QNetworkRequest request(m_url); qCDebug(taskUploadLogC) << getUid().toString() << "Uploading " << m_url.toString();
m_state = m_sink->init(request); break;
switch (m_state) { case State::Inactive:
case State::Succeeded: case State::Failed:
emitSucceeded(); emitFailed("");
qCDebug(taskUploadLogC) << getUid().toString() << "Upload cache hit " << m_url.toString(); return;
return; case State::AbortedByUser:
case State::Running: emitAborted();
qCDebug(taskUploadLogC) << getUid().toString() << "Uploading " << m_url.toString(); return;
break; }
case State::Inactive:
case State::Failed:
emitFailed("");
return;
case State::AbortedByUser:
emitAborted();
return;
}
request.setHeader(QNetworkRequest::UserAgentHeader, APPLICATION->getUserAgent().toUtf8()); request.setHeader(QNetworkRequest::UserAgentHeader, APPLICATION->getUserAgent().toUtf8());
// TODO remove duplication // TODO remove duplication
if (APPLICATION->capabilities() & Application::SupportsFlame && request.url().host() == QUrl(BuildConfig.FLAME_BASE_URL).host()) { if (APPLICATION->capabilities() & Application::SupportsFlame && request.url().host() == QUrl(BuildConfig.FLAME_BASE_URL).host()) {
request.setRawHeader("x-api-key", APPLICATION->getFlameAPIKey().toUtf8()); request.setRawHeader("x-api-key", APPLICATION->getFlameAPIKey().toUtf8());
} else if (request.url().host() == QUrl(BuildConfig.MODRINTH_PROD_URL).host() || } else if (request.url().host() == QUrl(BuildConfig.MODRINTH_PROD_URL).host() ||
request.url().host() == QUrl(BuildConfig.MODRINTH_STAGING_URL).host()) { request.url().host() == QUrl(BuildConfig.MODRINTH_STAGING_URL).host()) {
QString token = APPLICATION->getModrinthAPIToken(); QString token = APPLICATION->getModrinthAPIToken();
if (!token.isNull()) if (!token.isNull())
request.setRawHeader("Authorization", token.toUtf8()); request.setRawHeader("Authorization", token.toUtf8());
} }
//TODO other types of post requests ? // TODO other types of post requests ?
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
QNetworkReply* rep = m_network->post(request, m_post_data); QNetworkReply* rep = m_network->post(request, m_post_data);
m_reply.reset(rep); m_reply.reset(rep);
connect(rep, &QNetworkReply::downloadProgress, this, &Upload::downloadProgress); connect(rep, &QNetworkReply::downloadProgress, this, &Upload::downloadProgress);
connect(rep, &QNetworkReply::finished, this, &Upload::downloadFinished); connect(rep, &QNetworkReply::finished, this, &Upload::downloadFinished);
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15 #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15
connect(rep, &QNetworkReply::errorOccurred, this, &Upload::downloadError); connect(rep, &QNetworkReply::errorOccurred, this, &Upload::downloadError);
#else #else
connect(rep, QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::error), this, &Upload::downloadError); connect(rep, QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::error), this, &Upload::downloadError);
#endif #endif
connect(rep, &QNetworkReply::sslErrors, this, &Upload::sslErrors); connect(rep, &QNetworkReply::sslErrors, this, &Upload::sslErrors);
connect(rep, &QNetworkReply::readyRead, this, &Upload::downloadReadyRead); connect(rep, &QNetworkReply::readyRead, this, &Upload::downloadReadyRead);
} }
Upload::Ptr Upload::makeByteArray(QUrl url, QByteArray *output, QByteArray m_post_data) { Upload::Ptr Upload::makeByteArray(QUrl url, std::shared_ptr<QByteArray> output, QByteArray m_post_data)
auto up = makeShared<Upload>(); {
up->m_url = std::move(url); auto up = makeShared<Upload>();
up->m_sink.reset(new ByteArraySink(output)); up->m_url = std::move(url);
up->m_post_data = std::move(m_post_data); up->m_sink.reset(new ByteArraySink(output));
return up; up->m_post_data = std::move(m_post_data);
} return up;
} // Net }
} // namespace Net

View File

@ -42,31 +42,31 @@
namespace Net { namespace Net {
class Upload : public NetAction { class Upload : public NetAction {
Q_OBJECT Q_OBJECT
public: public:
using Ptr = shared_qobject_ptr<Upload>; using Ptr = shared_qobject_ptr<Upload>;
static Upload::Ptr makeByteArray(QUrl url, QByteArray *output, QByteArray m_post_data); static Upload::Ptr makeByteArray(QUrl url, std::shared_ptr<QByteArray> output, QByteArray m_post_data);
auto abort() -> bool override; auto abort() -> bool override;
auto canAbort() const -> bool override { return true; }; auto canAbort() const -> bool override { return true; };
protected slots: protected slots:
void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) override; void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) override;
void downloadError(QNetworkReply::NetworkError error) override; void downloadError(QNetworkReply::NetworkError error) override;
void sslErrors(const QList<QSslError> & errors) override; void sslErrors(const QList<QSslError>& errors) override;
void downloadFinished() override; void downloadFinished() override;
void downloadReadyRead() override; void downloadReadyRead() override;
public slots: public slots:
void executeTask() override; void executeTask() override;
private:
std::unique_ptr<Sink> m_sink;
QByteArray m_post_data;
bool handleRedirect(); private:
}; std::unique_ptr<Sink> m_sink;
QByteArray m_post_data;
} // Net bool handleRedirect();
};
} // namespace Net

View File

@ -58,7 +58,7 @@ void NewsChecker::reloadNews()
qDebug() << "Reloading news."; qDebug() << "Reloading news.";
NetJob::Ptr job{ new NetJob("News RSS Feed", m_network) }; NetJob::Ptr job{ new NetJob("News RSS Feed", m_network) };
job->addNetAction(Net::Download::makeByteArray(m_feedUrl, &newsData)); job->addNetAction(Net::Download::makeByteArray(m_feedUrl, newsData));
QObject::connect(job.get(), &NetJob::succeeded, this, &NewsChecker::rssDownloadFinished); QObject::connect(job.get(), &NetJob::succeeded, this, &NewsChecker::rssDownloadFinished);
QObject::connect(job.get(), &NetJob::failed, this, &NewsChecker::rssDownloadFailed); QObject::connect(job.get(), &NetJob::failed, this, &NewsChecker::rssDownloadFailed);
m_newsNetJob.reset(job); m_newsNetJob.reset(job);
@ -79,32 +79,27 @@ void NewsChecker::rssDownloadFinished()
int errorCol = -1; int errorCol = -1;
// Parse the XML. // Parse the XML.
if (!doc.setContent(newsData, false, &errorMsg, &errorLine, &errorCol)) if (!doc.setContent(*newsData, false, &errorMsg, &errorLine, &errorCol)) {
{
QString fullErrorMsg = QString("Error parsing RSS feed XML. %1 at %2:%3.").arg(errorMsg).arg(errorLine).arg(errorCol); QString fullErrorMsg = QString("Error parsing RSS feed XML. %1 at %2:%3.").arg(errorMsg).arg(errorLine).arg(errorCol);
fail(fullErrorMsg); fail(fullErrorMsg);
newsData.clear(); newsData->clear();
return; return;
} }
newsData.clear(); newsData->clear();
} }
// If the parsing succeeded, read it. // If the parsing succeeded, read it.
QDomNodeList items = doc.elementsByTagName("entry"); QDomNodeList items = doc.elementsByTagName("entry");
m_newsEntries.clear(); m_newsEntries.clear();
for (int i = 0; i < items.length(); i++) for (int i = 0; i < items.length(); i++) {
{
QDomElement element = items.at(i).toElement(); QDomElement element = items.at(i).toElement();
NewsEntryPtr entry; NewsEntryPtr entry;
entry.reset(new NewsEntry()); entry.reset(new NewsEntry());
QString errorMsg = "An unknown error occurred."; QString errorMsg = "An unknown error occurred.";
if (NewsEntry::fromXmlElement(element, entry.get(), &errorMsg)) if (NewsEntry::fromXmlElement(element, entry.get(), &errorMsg)) {
{
qDebug() << "Loaded news entry" << entry->title; qDebug() << "Loaded news entry" << entry->title;
m_newsEntries.append(entry); m_newsEntries.append(entry);
} } else {
else
{
qWarning() << "Failed to load news entry at index" << i << ":" << errorMsg; qWarning() << "Failed to load news entry at index" << i << ":" << errorMsg;
} }
} }

View File

@ -85,7 +85,7 @@ protected: /* data */
//! True if news has been loaded. //! True if news has been loaded.
bool m_loadedNews; bool m_loadedNews;
QByteArray newsData; std::shared_ptr<QByteArray> newsData = std::make_shared<QByteArray>();
/*! /*!
* Gets the error message that was given last time the news was loaded. * Gets the error message that was given last time the news was loaded.

View File

@ -577,7 +577,7 @@
<normaloff>.</normaloff>.</iconset> <normaloff>.</normaloff>.</iconset>
</property> </property>
<property name="text"> <property name="text">
<string>Report a &amp;Bug...</string> <string>Report a Bug or Suggest a Feature</string>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>Open the bug tracker to report a bug with %1.</string> <string>Open the bug tracker to report a bug with %1.</string>

View File

@ -53,6 +53,7 @@ ExportMrPackDialog::ExportMrPackDialog(InstancePtr instance, QWidget* parent)
const QDir root(instance->gameRoot()); const QDir root(instance->gameRoot());
proxy = new FileIgnoreProxy(instance->gameRoot(), this); proxy = new FileIgnoreProxy(instance->gameRoot(), this);
proxy->setSourceModel(model); proxy->setSourceModel(model);
proxy->setFilterRegularExpression("^(?!\\.DS_Store).+$");
const QDir::Filters filter(QDir::AllEntries | QDir::NoDotAndDotDot | QDir::AllDirs | QDir::Hidden); const QDir::Filters filter(QDir::AllEntries | QDir::NoDotAndDotDot | QDir::AllDirs | QDir::Hidden);

View File

@ -32,7 +32,7 @@ NewsDialog::~NewsDialog()
void NewsDialog::selectedArticleChanged(const QString& new_title) void NewsDialog::selectedArticleChanged(const QString& new_title)
{ {
auto const& article_entry = m_entries.constFind(new_title).value(); auto article_entry = m_entries.constFind(new_title).value();
ui->articleTitleLabel->setText(QString("<a href='%1'>%2</a>").arg(article_entry->link, new_title)); ui->articleTitleLabel->setText(QString("<a href='%1'>%2</a>").arg(article_entry->link, new_title));

View File

@ -35,15 +35,16 @@
#pragma once #pragma once
#include <QString>
#include <QIcon> #include <QIcon>
#include <QString>
#include <functional>
#include <memory> #include <memory>
#include "BasePageContainer.h" #include "BasePageContainer.h"
class BasePage class BasePage {
{ public:
public: using updateExtraInfoFunc = std::function<void(QString)>;
virtual ~BasePage() {} virtual ~BasePage() {}
virtual QString id() const = 0; virtual QString id() const = 0;
virtual QString displayName() const = 0; virtual QString displayName() const = 0;
@ -63,17 +64,16 @@ public:
} }
virtual void openedImpl() {} virtual void openedImpl() {}
virtual void closedImpl() {} virtual void closedImpl() {}
virtual void setParentContainer(BasePageContainer * container) virtual void setParentContainer(BasePageContainer* container) { m_container = container; };
{ virtual void retranslate() {}
m_container = container;
};
virtual void retranslate() { }
public: public:
int stackIndex = -1; int stackIndex = -1;
int listIndex = -1; int listIndex = -1;
protected: updateExtraInfoFunc updateExtraInfo;
BasePageContainer * m_container = nullptr;
protected:
BasePageContainer* m_container = nullptr;
bool isOpened = false; bool isOpened = false;
}; };

View File

@ -172,7 +172,7 @@
<string>Disable using metadata provided by mod providers (like Modrinth or Curseforge) for mods.</string> <string>Disable using metadata provided by mod providers (like Modrinth or Curseforge) for mods.</string>
</property> </property>
<property name="text"> <property name="text">
<string>Disable using metadata for mods?</string> <string>Disable using metadata for mods</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -307,21 +307,21 @@
<item> <item>
<widget class="QCheckBox" name="showConsoleCheck"> <widget class="QCheckBox" name="showConsoleCheck">
<property name="text"> <property name="text">
<string>Show console while the game is &amp;running?</string> <string>Show console while the game is &amp;running</string>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QCheckBox" name="autoCloseConsoleCheck"> <widget class="QCheckBox" name="autoCloseConsoleCheck">
<property name="text"> <property name="text">
<string>&amp;Automatically close console when the game quits?</string> <string>&amp;Automatically close console when the game quits</string>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QCheckBox" name="showConsoleErrorCheck"> <widget class="QCheckBox" name="showConsoleErrorCheck">
<property name="text"> <property name="text">
<string>Show console when the game &amp;crashes?</string> <string>Show console when the game &amp;crashes</string>
</property> </property>
</widget> </widget>
</item> </item>

View File

@ -51,7 +51,7 @@
<item> <item>
<widget class="QCheckBox" name="maximizedCheckBox"> <widget class="QCheckBox" name="maximizedCheckBox">
<property name="text"> <property name="text">
<string>Start Minecraft &amp;maximized?</string> <string>Start Minecraft &amp;maximized</string>
</property> </property>
</widget> </widget>
</item> </item>

View File

@ -1,3 +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/>.
*
* 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 "ExternalResourcesPage.h" #include "ExternalResourcesPage.h"
#include "ui/dialogs/CustomMessageBox.h" #include "ui/dialogs/CustomMessageBox.h"
#include "ui_ExternalResourcesPage.h" #include "ui_ExternalResourcesPage.h"
@ -9,6 +44,7 @@
#include <QKeyEvent> #include <QKeyEvent>
#include <QMenu> #include <QMenu>
#include <algorithm>
ExternalResourcesPage::ExternalResourcesPage(BaseInstance* instance, std::shared_ptr<ResourceFolderModel> model, QWidget* parent) ExternalResourcesPage::ExternalResourcesPage(BaseInstance* instance, std::shared_ptr<ResourceFolderModel> model, QWidget* parent)
: QMainWindow(parent), m_instance(instance), ui(new Ui::ExternalResourcesPage), m_model(model) : QMainWindow(parent), m_instance(instance), ui(new Ui::ExternalResourcesPage), m_model(model)
@ -24,6 +60,8 @@ ExternalResourcesPage::ExternalResourcesPage(BaseInstance* instance, std::shared
m_filterModel->setSourceModel(m_model.get()); m_filterModel->setSourceModel(m_model.get());
m_filterModel->setFilterKeyColumn(-1); m_filterModel->setFilterKeyColumn(-1);
ui->treeView->setModel(m_filterModel); ui->treeView->setModel(m_filterModel);
// must come after setModel
ui->treeView->setResizeModes(m_model->columnResizeModes());
ui->treeView->installEventFilter(this); ui->treeView->installEventFilter(this);
ui->treeView->sortByColumn(1, Qt::AscendingOrder); ui->treeView->sortByColumn(1, Qt::AscendingOrder);
@ -43,7 +81,21 @@ ExternalResourcesPage::ExternalResourcesPage(BaseInstance* instance, std::shared
auto selection_model = ui->treeView->selectionModel(); auto selection_model = ui->treeView->selectionModel();
connect(selection_model, &QItemSelectionModel::currentChanged, this, &ExternalResourcesPage::current); connect(selection_model, &QItemSelectionModel::currentChanged, this, &ExternalResourcesPage::current);
auto updateExtra = [this]() {
if (updateExtraInfo)
updateExtraInfo(extraHeaderInfoString());
};
connect(selection_model, &QItemSelectionModel::selectionChanged, this, updateExtra);
connect(model.get(), &ResourceFolderModel::updateFinished, this, updateExtra);
connect(ui->filterEdit, &QLineEdit::textChanged, this, &ExternalResourcesPage::filterTextChanged); connect(ui->filterEdit, &QLineEdit::textChanged, this, &ExternalResourcesPage::filterTextChanged);
auto viewHeader = ui->treeView->header();
viewHeader->setContextMenuPolicy(Qt::CustomContextMenu);
connect(viewHeader, &QHeaderView::customContextMenuRequested, this, &ExternalResourcesPage::ShowHeaderContextMenu);
m_model->loadHiddenColumns(ui->treeView);
} }
ExternalResourcesPage::~ExternalResourcesPage() ExternalResourcesPage::~ExternalResourcesPage()
@ -65,6 +117,13 @@ void ExternalResourcesPage::ShowContextMenu(const QPoint& pos)
delete menu; delete menu;
} }
void ExternalResourcesPage::ShowHeaderContextMenu(const QPoint& pos)
{
auto menu = m_model->createHeaderContextMenu(ui->treeView);
menu->exec(ui->treeView->mapToGlobal(pos));
menu->deleteLater();
}
void ExternalResourcesPage::openedImpl() void ExternalResourcesPage::openedImpl()
{ {
m_model->startWatching(); m_model->startWatching();
@ -96,7 +155,6 @@ void ExternalResourcesPage::itemActivated(const QModelIndex&)
return; return;
auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()); auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection());
m_model->setResourceEnabled(selection.indexes(), EnableAction::TOGGLE);
} }
void ExternalResourcesPage::filterTextChanged(const QString& newContents) void ExternalResourcesPage::filterTextChanged(const QString& newContents)
@ -248,6 +306,15 @@ bool ExternalResourcesPage::onSelectionChanged(const QModelIndex& current, const
int row = sourceCurrent.row(); int row = sourceCurrent.row();
Resource const& resource = m_model->at(row); Resource const& resource = m_model->at(row);
ui->frame->updateWithResource(resource); ui->frame->updateWithResource(resource);
return true; return true;
} }
QString ExternalResourcesPage::extraHeaderInfoString()
{
if (ui && ui->treeView && ui->treeView->selectionModel()) {
auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes();
if (auto count = std::count_if(selection.cbegin(), selection.cend(), [](auto v) { return v.column() == 0; }); count != 0)
return tr(" (%1 installed, %2 selected)").arg(m_model->size()).arg(count);
}
return tr(" (%1 installed)").arg(m_model->size());
}

View File

@ -29,6 +29,7 @@ class ExternalResourcesPage : public QMainWindow, public BasePage {
virtual QString helpPage() const override = 0; virtual QString helpPage() const override = 0;
virtual bool shouldDisplay() const override = 0; virtual bool shouldDisplay() const override = 0;
QString extraHeaderInfoString();
void openedImpl() override; void openedImpl() override;
void closedImpl() override; void closedImpl() override;
@ -60,6 +61,7 @@ class ExternalResourcesPage : public QMainWindow, public BasePage {
virtual void viewConfigs(); virtual void viewConfigs();
void ShowContextMenu(const QPoint& pos); void ShowContextMenu(const QPoint& pos);
void ShowHeaderContextMenu(const QPoint& pos);
protected: protected:
BaseInstance* m_instance = nullptr; BaseInstance* m_instance = nullptr;

View File

@ -62,6 +62,9 @@
<property name="dragDropMode"> <property name="dragDropMode">
<enum>QAbstractItemView::DropOnly</enum> <enum>QAbstractItemView::DropOnly</enum>
</property> </property>
<property name="uniformRowHeights">
<bool>true</bool>
</property>
</widget> </widget>
</item> </item>
</layout> </layout>

View File

@ -269,7 +269,7 @@
<item> <item>
<widget class="QCheckBox" name="maximizedCheckBox"> <widget class="QCheckBox" name="maximizedCheckBox">
<property name="text"> <property name="text">
<string>Start Minecraft maximized?</string> <string>Start Minecraft maximized</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -341,21 +341,21 @@
<item> <item>
<widget class="QCheckBox" name="showConsoleCheck"> <widget class="QCheckBox" name="showConsoleCheck">
<property name="text"> <property name="text">
<string>Show console while the game is running?</string> <string>Show console while the game is running</string>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QCheckBox" name="autoCloseConsoleCheck"> <widget class="QCheckBox" name="autoCloseConsoleCheck">
<property name="text"> <property name="text">
<string>Automatically close console when the game quits?</string> <string>Automatically close console when the game quits</string>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QCheckBox" name="showConsoleErrorCheck"> <widget class="QCheckBox" name="showConsoleErrorCheck">
<property name="text"> <property name="text">
<string>Show console when the game crashes?</string> <string>Show console when the game crashes</string>
</property> </property>
</widget> </widget>
</item> </item>

View File

@ -226,7 +226,7 @@ void ModrinthManagedPackPage::parseManagedPack()
QString id = m_inst->getManagedPackID(); QString id = m_inst->getManagedPackID();
m_fetch_job->addNetAction(Net::Download::makeByteArray(QString("%1/project/%2/version").arg(BuildConfig.MODRINTH_PROD_URL, id), response.get())); m_fetch_job->addNetAction(Net::Download::makeByteArray(QString("%1/project/%2/version").arg(BuildConfig.MODRINTH_PROD_URL, id), response));
QObject::connect(m_fetch_job.get(), &NetJob::succeeded, this, [this, response, id] { QObject::connect(m_fetch_job.get(), &NetJob::succeeded, this, [this, response, id] {
QJsonParseError parse_error{}; QJsonParseError parse_error{};
@ -369,7 +369,7 @@ void FlameManagedPackPage::parseManagedPack()
QString id = m_inst->getManagedPackID(); QString id = m_inst->getManagedPackID();
m_fetch_job->addNetAction(Net::Download::makeByteArray(QString("%1/mods/%2/files").arg(BuildConfig.FLAME_BASE_URL, id), response.get())); m_fetch_job->addNetAction(Net::Download::makeByteArray(QString("%1/mods/%2/files").arg(BuildConfig.FLAME_BASE_URL, id), response));
QObject::connect(m_fetch_job.get(), &NetJob::succeeded, this, [this, response, id] { QObject::connect(m_fetch_job.get(), &NetJob::succeeded, this, [this, response, id] {
QJsonParseError parse_error{}; QJsonParseError parse_error{};

View File

@ -4,6 +4,7 @@
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org> * Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me> * Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -86,28 +87,20 @@ ModFolderPage::ModFolderPage(BaseInstance* inst, std::shared_ptr<ModFolderModel>
connect(ui->actionUpdateItem, &QAction::triggered, this, &ModFolderPage::updateMods); connect(ui->actionUpdateItem, &QAction::triggered, this, &ModFolderPage::updateMods);
auto check_allow_update = [this] { auto check_allow_update = [this] {
return (!m_instance || !m_instance->isRunning()) && return (!m_instance || !m_instance->isRunning()) && (ui->treeView->selectionModel()->hasSelection() || !m_model->empty());
(ui->treeView->selectionModel()->hasSelection() || !m_model->empty());
}; };
connect(ui->treeView->selectionModel(), &QItemSelectionModel::selectionChanged, this, [this, check_allow_update] { connect(ui->treeView->selectionModel(), &QItemSelectionModel::selectionChanged, this,
ui->actionUpdateItem->setEnabled(check_allow_update()); [this, check_allow_update] { ui->actionUpdateItem->setEnabled(check_allow_update()); });
});
connect(mods.get(), &ModFolderModel::rowsInserted, this, [this, check_allow_update] { connect(mods.get(), &ModFolderModel::rowsInserted, this,
ui->actionUpdateItem->setEnabled(check_allow_update()); [this, check_allow_update] { ui->actionUpdateItem->setEnabled(check_allow_update()); });
});
connect(mods.get(), &ModFolderModel::rowsRemoved, this, [this, check_allow_update] { connect(mods.get(), &ModFolderModel::rowsRemoved, this,
ui->actionUpdateItem->setEnabled(check_allow_update()); [this, check_allow_update] { ui->actionUpdateItem->setEnabled(check_allow_update()); });
});
connect(mods.get(), &ModFolderModel::updateFinished, this, [this, check_allow_update, mods] { connect(mods.get(), &ModFolderModel::updateFinished, this,
ui->actionUpdateItem->setEnabled(check_allow_update()); [this, check_allow_update] { ui->actionUpdateItem->setEnabled(check_allow_update()); });
// Prevent a weird crash when trying to open the mods page twice in a session o.O
disconnect(mods.get(), &ModFolderModel::updateFinished, this, 0);
});
connect(m_instance, &BaseInstance::runningStatusChanged, this, &ModFolderPage::runningStateChanged); connect(m_instance, &BaseInstance::runningStatusChanged, this, &ModFolderPage::runningStateChanged);
ModFolderPage::runningStateChanged(m_instance && m_instance->isRunning()); ModFolderPage::runningStateChanged(m_instance && m_instance->isRunning());

View File

@ -57,7 +57,7 @@ public:
virtual ~ImportPage(); virtual ~ImportPage();
virtual QString displayName() const override virtual QString displayName() const override
{ {
return tr("Import from zip"); return tr("Import");
} }
virtual QIcon icon() const override virtual QIcon icon() const override
{ {

View File

@ -89,17 +89,13 @@ void ModPage::filterMods()
void ModPage::triggerSearch() void ModPage::triggerSearch()
{ {
auto changed = m_filter_widget->changed();
m_filter = m_filter_widget->getFilter(); m_filter = m_filter_widget->getFilter();
m_ui->packView->clearSelection();
m_ui->packDescription->clear();
m_ui->versionSelectionBox->clear();
updateSelectionButton();
if (changed) { static_cast<ModModel*>(m_model)->searchWithTerm(getSearchTerm(), m_ui->sortByBox->currentData().toUInt(), m_filter_widget->changed());
m_ui->packView->clearSelection();
m_ui->packDescription->clear();
m_ui->versionSelectionBox->clear();
updateSelectionButton();
}
static_cast<ModModel*>(m_model)->searchWithTerm(getSearchTerm(), m_ui->sortByBox->currentData().toUInt(), changed);
m_fetch_progress.watch(m_model->activeSearchJob().get()); m_fetch_progress.watch(m_model->activeSearchJob().get());
} }
@ -122,6 +118,8 @@ void ModPage::updateVersionList()
QString mcVersion = packProfile->getComponentVersion("net.minecraft"); QString mcVersion = packProfile->getComponentVersion("net.minecraft");
auto current_pack = getCurrentPack(); auto current_pack = getCurrentPack();
if (!current_pack)
return;
for (int i = 0; i < current_pack->versions.size(); i++) { for (int i = 0; i < current_pack->versions.size(); i++) {
auto version = current_pack->versions[i]; auto version = current_pack->versions[i];
bool valid = false; bool valid = false;

View File

@ -174,7 +174,11 @@ ModPlatform::IndexedPack::Ptr ResourcePage::getCurrentPack() const
void ResourcePage::updateUi() void ResourcePage::updateUi()
{ {
auto current_pack = getCurrentPack(); auto current_pack = getCurrentPack();
if (!current_pack) {
m_ui->packDescription->setHtml({});
m_ui->packDescription->flush();
return;
}
QString text = ""; QString text = "";
QString name = current_pack->name; QString name = current_pack->name;
@ -240,8 +244,8 @@ void ResourcePage::updateSelectionButton()
} }
m_ui->resourceSelectionButton->setEnabled(true); m_ui->resourceSelectionButton->setEnabled(true);
if (getCurrentPack()) { if (auto current_pack = getCurrentPack(); current_pack) {
if (!getCurrentPack()->isVersionSelected(m_selected_version_index)) if (!current_pack->isVersionSelected(m_selected_version_index))
m_ui->resourceSelectionButton->setText(tr("Select %1 for download").arg(resourceString())); m_ui->resourceSelectionButton->setText(tr("Select %1 for download").arg(resourceString()));
else else
m_ui->resourceSelectionButton->setText(tr("Deselect %1 for download").arg(resourceString())); m_ui->resourceSelectionButton->setText(tr("Deselect %1 for download").arg(resourceString()));
@ -258,13 +262,14 @@ void ResourcePage::updateVersionList()
m_ui->versionSelectionBox->clear(); m_ui->versionSelectionBox->clear();
m_ui->versionSelectionBox->blockSignals(false); m_ui->versionSelectionBox->blockSignals(false);
for (int i = 0; i < current_pack->versions.size(); i++) { if (current_pack)
auto& version = current_pack->versions[i]; for (int i = 0; i < current_pack->versions.size(); i++) {
if (optedOut(version)) auto& version = current_pack->versions[i];
continue; if (optedOut(version))
continue;
m_ui->versionSelectionBox->addItem(current_pack->versions[i].version, QVariant(i)); m_ui->versionSelectionBox->addItem(current_pack->versions[i].version, QVariant(i));
} }
if (m_ui->versionSelectionBox->count() == 0) { if (m_ui->versionSelectionBox->count() == 0) {
m_ui->versionSelectionBox->addItem(tr("No valid version found."), QVariant(-1)); m_ui->versionSelectionBox->addItem(tr("No valid version found."), QVariant(-1));
@ -283,7 +288,7 @@ void ResourcePage::onSelectionChanged(QModelIndex curr, QModelIndex prev)
auto current_pack = getCurrentPack(); auto current_pack = getCurrentPack();
bool request_load = false; bool request_load = false;
if (!current_pack->versionsLoaded) { if (!current_pack || !current_pack->versionsLoaded) {
m_ui->resourceSelectionButton->setText(tr("Loading versions...")); m_ui->resourceSelectionButton->setText(tr("Loading versions..."));
m_ui->resourceSelectionButton->setEnabled(false); m_ui->resourceSelectionButton->setEnabled(false);
@ -292,7 +297,7 @@ void ResourcePage::onSelectionChanged(QModelIndex curr, QModelIndex prev)
updateVersionList(); updateVersionList();
} }
if (!current_pack->extraDataLoaded) if (current_pack && !current_pack->extraDataLoaded)
request_load = true; request_load = true;
if (request_load) if (request_load)
@ -340,7 +345,7 @@ void ResourcePage::onResourceSelected()
return; return;
auto current_pack = getCurrentPack(); auto current_pack = getCurrentPack();
if (!current_pack->versionsLoaded) if (!current_pack || !current_pack->versionsLoaded)
return; return;
auto& version = current_pack->versions[m_selected_version_index]; auto& version = current_pack->versions[m_selected_version_index];
@ -386,7 +391,7 @@ void ResourcePage::openUrl(const QUrl& url)
const QString slug = match.captured(1); const QString slug = match.captured(1);
// ensure the user isn't opening the same mod // ensure the user isn't opening the same mod
if (slug != getCurrentPack()->slug) { if (auto current_pack = getCurrentPack(); current_pack && slug != current_pack->slug) {
m_parent_dialog->selectPage(page); m_parent_dialog->selectPage(page);
auto newPage = m_parent_dialog->getSelectedPage(); auto newPage = m_parent_dialog->getSelectedPage();

View File

@ -16,62 +16,49 @@
#include "AtlListModel.h" #include "AtlListModel.h"
#include <BuildConfig.h>
#include <Application.h> #include <Application.h>
#include <BuildConfig.h>
#include <Json.h> #include <Json.h>
namespace Atl { namespace Atl {
ListModel::ListModel(QObject *parent) : QAbstractListModel(parent) ListModel::ListModel(QObject* parent) : QAbstractListModel(parent) {}
{
}
ListModel::~ListModel() ListModel::~ListModel() {}
{
}
int ListModel::rowCount(const QModelIndex &parent) const int ListModel::rowCount(const QModelIndex& parent) const
{ {
return parent.isValid() ? 0 : modpacks.size(); return parent.isValid() ? 0 : modpacks.size();
} }
int ListModel::columnCount(const QModelIndex &parent) const int ListModel::columnCount(const QModelIndex& parent) const
{ {
return parent.isValid() ? 0 : 1; return parent.isValid() ? 0 : 1;
} }
QVariant ListModel::data(const QModelIndex &index, int role) const QVariant ListModel::data(const QModelIndex& index, int role) const
{ {
int pos = index.row(); int pos = index.row();
if(pos >= modpacks.size() || pos < 0 || !index.isValid()) if (pos >= modpacks.size() || pos < 0 || !index.isValid()) {
{
return QString("INVALID INDEX %1").arg(pos); return QString("INVALID INDEX %1").arg(pos);
} }
ATLauncher::IndexedPack pack = modpacks.at(pos); ATLauncher::IndexedPack pack = modpacks.at(pos);
if(role == Qt::DisplayRole) if (role == Qt::DisplayRole) {
{
return pack.name; return pack.name;
} } else if (role == Qt::ToolTipRole) {
else if (role == Qt::ToolTipRole)
{
return pack.name; return pack.name;
} } else if (role == Qt::DecorationRole) {
else if(role == Qt::DecorationRole) if (m_logoMap.contains(pack.safeName)) {
{
if(m_logoMap.contains(pack.safeName))
{
return (m_logoMap.value(pack.safeName)); return (m_logoMap.value(pack.safeName));
} }
auto icon = APPLICATION->getThemedIcon("atlauncher-placeholder"); auto icon = APPLICATION->getThemedIcon("atlauncher-placeholder");
auto url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "launcher/images/%1.png").arg(pack.safeName.toLower()); auto url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "launcher/images/%1.png").arg(pack.safeName.toLower());
((ListModel *)this)->requestLogo(pack.safeName, url); ((ListModel*)this)->requestLogo(pack.safeName, url);
return icon; return icon;
} } else if (role == Qt::UserRole) {
else if(role == Qt::UserRole)
{
QVariant v; QVariant v;
v.setValue(pack); v.setValue(pack);
return v; return v;
@ -88,7 +75,7 @@ void ListModel::request()
auto netJob = makeShared<NetJob>("Atl::Request", APPLICATION->network()); auto netJob = makeShared<NetJob>("Atl::Request", APPLICATION->network());
auto url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "launcher/json/packsnew.json"); auto url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "launcher/json/packsnew.json");
netJob->addNetAction(Net::Download::makeByteArray(QUrl(url), &response)); netJob->addNetAction(Net::Download::makeByteArray(QUrl(url), response));
jobPtr = netJob; jobPtr = netJob;
jobPtr->start(); jobPtr->start();
@ -101,36 +88,38 @@ void ListModel::requestFinished()
jobPtr.reset(); jobPtr.reset();
QJsonParseError parse_error; QJsonParseError parse_error;
QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error); QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
if(parse_error.error != QJsonParseError::NoError) { if (parse_error.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response from ATL at " << parse_error.offset << " reason: " << parse_error.errorString(); qWarning() << "Error while parsing JSON response from ATL at " << parse_error.offset << " reason: " << parse_error.errorString();
qWarning() << response; qWarning() << *response;
return; return;
} }
QList<ATLauncher::IndexedPack> newList; QList<ATLauncher::IndexedPack> newList;
auto packs = doc.array(); auto packs = doc.array();
for(auto packRaw : packs) { for (auto packRaw : packs) {
auto packObj = packRaw.toObject(); auto packObj = packRaw.toObject();
ATLauncher::IndexedPack pack; ATLauncher::IndexedPack pack;
try { try {
ATLauncher::loadIndexedPack(pack, packObj); ATLauncher::loadIndexedPack(pack, packObj);
} } catch (const JSONValidationError& e) {
catch (const JSONValidationError &e) { qDebug() << QString::fromUtf8(*response);
qDebug() << QString::fromUtf8(response);
qWarning() << "Error while reading pack manifest from ATLauncher: " << e.cause(); qWarning() << "Error while reading pack manifest from ATLauncher: " << e.cause();
return; return;
} }
// ignore packs without a published version // ignore packs without a published version
if(pack.versions.length() == 0) continue; if (pack.versions.length() == 0)
continue;
// only display public packs (for now) // only display public packs (for now)
if(pack.type != ATLauncher::PackType::Public) continue; if (pack.type != ATLauncher::PackType::Public)
continue;
// ignore "system" packs (Vanilla, Vanilla with Forge, etc) // ignore "system" packs (Vanilla, Vanilla with Forge, etc)
if(pack.system) continue; if (pack.system)
continue;
newList.append(pack); newList.append(pack);
} }
@ -145,14 +134,12 @@ void ListModel::requestFailed(QString reason)
jobPtr.reset(); jobPtr.reset();
} }
void ListModel::getLogo(const QString &logo, const QString &logoUrl, LogoCallback callback) void ListModel::getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback)
{ {
if(m_logoMap.contains(logo)) if (m_logoMap.contains(logo)) {
{ callback(
callback(APPLICATION->metacache()->resolveEntry("ATLauncherPacks", QString("logos/%1").arg(logo.section(".", 0, 0)))->getFullPath()); APPLICATION->metacache()->resolveEntry("ATLauncherPacks", QString("logos/%1").arg(logo.section(".", 0, 0)))->getFullPath());
} } else {
else
{
requestLogo(logo, logoUrl); requestLogo(logo, logoUrl);
} }
} }
@ -168,36 +155,34 @@ void ListModel::logoLoaded(QString logo, QIcon out)
m_loadingLogos.removeAll(logo); m_loadingLogos.removeAll(logo);
m_logoMap.insert(logo, out); m_logoMap.insert(logo, out);
for(int i = 0; i < modpacks.size(); i++) { for (int i = 0; i < modpacks.size(); i++) {
if(modpacks[i].safeName == logo) { if (modpacks[i].safeName == logo) {
emit dataChanged(createIndex(i, 0), createIndex(i, 0), {Qt::DecorationRole}); emit dataChanged(createIndex(i, 0), createIndex(i, 0), { Qt::DecorationRole });
} }
} }
} }
void ListModel::requestLogo(QString file, QString url) void ListModel::requestLogo(QString file, QString url)
{ {
if(m_loadingLogos.contains(file) || m_failedLogos.contains(file)) if (m_loadingLogos.contains(file) || m_failedLogos.contains(file)) {
{
return; return;
} }
MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("ATLauncherPacks", QString("logos/%1").arg(file.section(".", 0, 0))); MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("ATLauncherPacks", QString("logos/%1").arg(file.section(".", 0, 0)));
NetJob *job = new NetJob(QString("ATLauncher Icon Download %1").arg(file), APPLICATION->network()); auto job = new NetJob(QString("ATLauncher Icon Download %1").arg(file), APPLICATION->network());
job->addNetAction(Net::Download::makeCached(QUrl(url), entry)); job->addNetAction(Net::Download::makeCached(QUrl(url), entry));
auto fullPath = entry->getFullPath(); auto fullPath = entry->getFullPath();
QObject::connect(job, &NetJob::succeeded, this, [this, file, fullPath] QObject::connect(job, &NetJob::succeeded, this, [this, file, fullPath, job] {
{ job->deleteLater();
emit logoLoaded(file, QIcon(fullPath)); emit logoLoaded(file, QIcon(fullPath));
if(waitingCallbacks.contains(file)) if (waitingCallbacks.contains(file)) {
{
waitingCallbacks.value(file)(fullPath); waitingCallbacks.value(file)(fullPath);
} }
}); });
QObject::connect(job, &NetJob::failed, this, [this, file] QObject::connect(job, &NetJob::failed, this, [this, file, job] {
{ job->deleteLater();
emit logoFailed(file); emit logoFailed(file);
}); });
@ -206,4 +191,4 @@ void ListModel::requestLogo(QString file, QString url)
m_loadingLogos.append(file); m_loadingLogos.append(file);
} }
} } // namespace Atl

View File

@ -18,42 +18,41 @@
#include <QAbstractListModel> #include <QAbstractListModel>
#include "net/NetJob.h"
#include <QIcon>
#include <modplatform/atlauncher/ATLPackIndex.h> #include <modplatform/atlauncher/ATLPackIndex.h>
#include <QIcon>
#include "net/NetJob.h"
namespace Atl { namespace Atl {
typedef QMap<QString, QIcon> LogoMap; typedef QMap<QString, QIcon> LogoMap;
typedef std::function<void(QString)> LogoCallback; typedef std::function<void(QString)> LogoCallback;
class ListModel : public QAbstractListModel class ListModel : public QAbstractListModel {
{
Q_OBJECT Q_OBJECT
public: public:
ListModel(QObject *parent); ListModel(QObject* parent);
virtual ~ListModel(); virtual ~ListModel();
int rowCount(const QModelIndex &parent) const override; int rowCount(const QModelIndex& parent) const override;
int columnCount(const QModelIndex &parent) const override; int columnCount(const QModelIndex& parent) const override;
QVariant data(const QModelIndex &index, int role) const override; QVariant data(const QModelIndex& index, int role) const override;
void request(); void request();
void getLogo(const QString &logo, const QString &logoUrl, LogoCallback callback); void getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback);
private slots: private slots:
void requestFinished(); void requestFinished();
void requestFailed(QString reason); void requestFailed(QString reason);
void logoFailed(QString logo); void logoFailed(QString logo);
void logoLoaded(QString logo, QIcon out); void logoLoaded(QString logo, QIcon out);
private: private:
void requestLogo(QString file, QString url); void requestLogo(QString file, QString url);
private: private:
QList<ATLauncher::IndexedPack> modpacks; QList<ATLauncher::IndexedPack> modpacks;
QStringList m_failedLogos; QStringList m_failedLogos;
@ -62,7 +61,7 @@ private:
QMap<QString, LogoCallback> waitingCallbacks; QMap<QString, LogoCallback> waitingCallbacks;
NetJob::Ptr jobPtr; NetJob::Ptr jobPtr;
QByteArray response; std::shared_ptr<QByteArray> response = std::make_shared<QByteArray>();
}; };
} } // namespace Atl

View File

@ -152,7 +152,7 @@ Qt::ItemFlags AtlOptionalModListModel::flags(const QModelIndex &index) const {
void AtlOptionalModListModel::useShareCode(const QString& code) { void AtlOptionalModListModel::useShareCode(const QString& code) {
m_jobPtr.reset(new NetJob("Atl::Request", APPLICATION->network())); m_jobPtr.reset(new NetJob("Atl::Request", APPLICATION->network()));
auto url = QString(BuildConfig.ATL_API_BASE_URL + "share-codes/" + code); auto url = QString(BuildConfig.ATL_API_BASE_URL + "share-codes/" + code);
m_jobPtr->addNetAction(Net::Download::makeByteArray(QUrl(url), &m_response)); m_jobPtr->addNetAction(Net::Download::makeByteArray(QUrl(url), m_response));
connect(m_jobPtr.get(), &NetJob::succeeded, connect(m_jobPtr.get(), &NetJob::succeeded,
this, &AtlOptionalModListModel::shareCodeSuccess); this, &AtlOptionalModListModel::shareCodeSuccess);
@ -166,10 +166,10 @@ void AtlOptionalModListModel::shareCodeSuccess() {
m_jobPtr.reset(); m_jobPtr.reset();
QJsonParseError parse_error {}; QJsonParseError parse_error {};
auto doc = QJsonDocument::fromJson(m_response, &parse_error); auto doc = QJsonDocument::fromJson(*m_response, &parse_error);
if (parse_error.error != QJsonParseError::NoError) { if (parse_error.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response from ATL at " << parse_error.offset << " reason: " << parse_error.errorString(); qWarning() << "Error while parsing JSON response from ATL at " << parse_error.offset << " reason: " << parse_error.errorString();
qWarning() << m_response; qWarning() << *m_response;
return; return;
} }
auto obj = doc.object(); auto obj = doc.object();
@ -179,7 +179,7 @@ void AtlOptionalModListModel::shareCodeSuccess() {
ATLauncher::loadShareCodeResponse(response, obj); ATLauncher::loadShareCodeResponse(response, obj);
} }
catch (const JSONValidationError& e) { catch (const JSONValidationError& e) {
qDebug() << QString::fromUtf8(m_response); qDebug() << QString::fromUtf8(*m_response);
qWarning() << "Error while reading response from ATLauncher: " << e.cause(); qWarning() << "Error while reading response from ATLauncher: " << e.cause();
return; return;
} }

View File

@ -82,9 +82,9 @@ private:
void toggleMod(ATLauncher::VersionMod mod, int index); void toggleMod(ATLauncher::VersionMod mod, int index);
void setMod(ATLauncher::VersionMod mod, int index, bool enable, bool shouldEmit = true); void setMod(ATLauncher::VersionMod mod, int index, bool enable, bool shouldEmit = true);
private: private:
NetJob::Ptr m_jobPtr; NetJob::Ptr m_jobPtr;
QByteArray m_response; std::shared_ptr<QByteArray> m_response = std::make_shared<QByteArray>();
ATLauncher::PackVersion m_version; ATLauncher::PackVersion m_version;
QVector<ATLauncher::VersionMod> m_mods; QVector<ATLauncher::VersionMod> m_mods;

View File

@ -42,15 +42,15 @@
class AtlUserInteractionSupportImpl : public QObject, public ATLauncher::UserInteractionSupport { class AtlUserInteractionSupportImpl : public QObject, public ATLauncher::UserInteractionSupport {
Q_OBJECT Q_OBJECT
public: public:
AtlUserInteractionSupportImpl(QWidget* parent); AtlUserInteractionSupportImpl(QWidget* parent);
virtual ~AtlUserInteractionSupportImpl() = default;
private: private:
QString chooseVersion(Meta::VersionList::Ptr vlist, QString minecraftVersion) override; QString chooseVersion(Meta::VersionList::Ptr vlist, QString minecraftVersion) override;
std::optional<QVector<QString>> chooseOptionalMods(ATLauncher::PackVersion version, QVector<ATLauncher::VersionMod> mods) override; std::optional<QVector<QString>> chooseOptionalMods(ATLauncher::PackVersion version, QVector<ATLauncher::VersionMod> mods) override;
void displayMessage(QString message) override; void displayMessage(QString message) override;
private: private:
QWidget* m_parent; QWidget* m_parent;
}; };

View File

@ -171,7 +171,7 @@ void ListModel::performPaginatedSearch()
.arg(currentSearchTerm) .arg(currentSearchTerm)
.arg(currentSort + 1); .arg(currentSort + 1);
netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), &response)); netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), response));
jobPtr = netJob; jobPtr = netJob;
jobPtr->start(); jobPtr->start();
QObject::connect(netJob.get(), &NetJob::succeeded, this, &ListModel::searchRequestFinished); QObject::connect(netJob.get(), &NetJob::succeeded, this, &ListModel::searchRequestFinished);
@ -204,11 +204,11 @@ void Flame::ListModel::searchRequestFinished()
jobPtr.reset(); jobPtr.reset();
QJsonParseError parse_error; QJsonParseError parse_error;
QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error); QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
if (parse_error.error != QJsonParseError::NoError) { if (parse_error.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response from CurseForge at " << parse_error.offset qWarning() << "Error while parsing JSON response from CurseForge at " << parse_error.offset
<< " reason: " << parse_error.errorString(); << " reason: " << parse_error.errorString();
qWarning() << response; qWarning() << *response;
return; return;
} }

View File

@ -3,46 +3,44 @@
#include <RWStorage.h> #include <RWStorage.h>
#include <QAbstractListModel> #include <QAbstractListModel>
#include <QSortFilterProxyModel>
#include <QThreadPool>
#include <QIcon> #include <QIcon>
#include <QStyledItemDelegate>
#include <QList> #include <QList>
#include <QMetaType>
#include <QSortFilterProxyModel>
#include <QString> #include <QString>
#include <QStringList> #include <QStringList>
#include <QMetaType> #include <QStyledItemDelegate>
#include <QThreadPool>
#include <functional>
#include <net/NetJob.h> #include <net/NetJob.h>
#include <functional>
#include <modplatform/flame/FlamePackIndex.h> #include <modplatform/flame/FlamePackIndex.h>
namespace Flame { namespace Flame {
typedef QMap<QString, QIcon> LogoMap; typedef QMap<QString, QIcon> LogoMap;
typedef std::function<void(QString)> LogoCallback; typedef std::function<void(QString)> LogoCallback;
class ListModel : public QAbstractListModel class ListModel : public QAbstractListModel {
{
Q_OBJECT Q_OBJECT
public: public:
ListModel(QObject *parent); ListModel(QObject* parent);
virtual ~ListModel(); virtual ~ListModel();
int rowCount(const QModelIndex &parent) const override; int rowCount(const QModelIndex& parent) const override;
int columnCount(const QModelIndex &parent) const override; int columnCount(const QModelIndex& parent) const override;
QVariant data(const QModelIndex &index, int role) const override; QVariant data(const QModelIndex& index, int role) const override;
bool setData(const QModelIndex &index, const QVariant &value, int role) override; bool setData(const QModelIndex& index, const QVariant& value, int role) override;
Qt::ItemFlags flags(const QModelIndex &index) const override; Qt::ItemFlags flags(const QModelIndex& index) const override;
bool canFetchMore(const QModelIndex & parent) const override; bool canFetchMore(const QModelIndex& parent) const override;
void fetchMore(const QModelIndex & parent) override; void fetchMore(const QModelIndex& parent) override;
void getLogo(const QString &logo, const QString &logoUrl, LogoCallback callback); void getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback);
void searchWithTerm(const QString & term, const int sort); void searchWithTerm(const QString& term, const int sort);
private slots: private slots:
void performPaginatedSearch(); void performPaginatedSearch();
void logoFailed(QString logo); void logoFailed(QString logo);
@ -51,10 +49,10 @@ private slots:
void searchRequestFinished(); void searchRequestFinished();
void searchRequestFailed(QString reason); void searchRequestFailed(QString reason);
private: private:
void requestLogo(QString file, QString url); void requestLogo(QString file, QString url);
private: private:
QList<IndexedPack> modpacks; QList<IndexedPack> modpacks;
QStringList m_failedLogos; QStringList m_failedLogos;
QStringList m_loadingLogos; QStringList m_loadingLogos;
@ -64,14 +62,9 @@ private:
QString currentSearchTerm; QString currentSearchTerm;
int currentSort = 0; int currentSort = 0;
int nextSearchOffset = 0; int nextSearchOffset = 0;
enum SearchState { enum SearchState { None, CanPossiblyFetchMore, ResetRequested, Finished } searchState = None;
None,
CanPossiblyFetchMore,
ResetRequested,
Finished
} searchState = None;
NetJob::Ptr jobPtr; NetJob::Ptr jobPtr;
QByteArray response; std::shared_ptr<QByteArray> response = std::make_shared<QByteArray>();
}; };
} } // namespace Flame

View File

@ -130,7 +130,7 @@ void FlamePage::onSelectionChanged(QModelIndex curr, QModelIndex prev)
if (current.versionsLoaded == false) { if (current.versionsLoaded == false) {
qDebug() << "Loading flame modpack versions"; qDebug() << "Loading flame modpack versions";
auto netJob = new NetJob(QString("Flame::PackVersions(%1)").arg(current.name), APPLICATION->network()); auto netJob = new NetJob(QString("Flame::PackVersions(%1)").arg(current.name), APPLICATION->network());
auto response = new QByteArray(); auto response = std::make_shared<QByteArray>();
int addonId = current.addonId; int addonId = current.addonId;
netJob->addNetAction(Net::Download::makeByteArray(QString("https://api.curseforge.com/v1/mods/%1/files").arg(addonId), response)); netJob->addNetAction(Net::Download::makeByteArray(QString("https://api.curseforge.com/v1/mods/%1/files").arg(addonId), response));
@ -170,10 +170,7 @@ void FlamePage::onSelectionChanged(QModelIndex curr, QModelIndex prev)
} }
suggestCurrent(); suggestCurrent();
}); });
QObject::connect(netJob, &NetJob::finished, this, [response, netJob] { QObject::connect(netJob, &NetJob::finished, this, [response, netJob] { netJob->deleteLater(); });
netJob->deleteLater();
delete response;
});
netJob->start(); netJob->start();
} else { } else {
for (auto version : current.versions) { for (auto version : current.versions) {

View File

@ -38,11 +38,11 @@
#include "net/HttpMetaCache.h" #include "net/HttpMetaCache.h"
#include "net/NetJob.h" #include "net/NetJob.h"
#include "StringUtils.h"
#include <Version.h> #include <Version.h>
#include "StringUtils.h"
#include <QtMath>
#include <QLabel> #include <QLabel>
#include <QtMath>
#include <RWStorage.h> #include <RWStorage.h>
@ -50,33 +50,33 @@
namespace LegacyFTB { namespace LegacyFTB {
FilterModel::FilterModel(QObject *parent) : QSortFilterProxyModel(parent) FilterModel::FilterModel(QObject* parent) : QSortFilterProxyModel(parent)
{ {
currentSorting = Sorting::ByGameVersion; currentSorting = Sorting::ByGameVersion;
sortings.insert(tr("Sort by Name"), Sorting::ByName); sortings.insert(tr("Sort by Name"), Sorting::ByName);
sortings.insert(tr("Sort by Game Version"), Sorting::ByGameVersion); sortings.insert(tr("Sort by Game Version"), Sorting::ByGameVersion);
} }
bool FilterModel::lessThan(const QModelIndex &left, const QModelIndex &right) const bool FilterModel::lessThan(const QModelIndex& left, const QModelIndex& right) const
{ {
Modpack leftPack = sourceModel()->data(left, Qt::UserRole).value<Modpack>(); Modpack leftPack = sourceModel()->data(left, Qt::UserRole).value<Modpack>();
Modpack rightPack = sourceModel()->data(right, Qt::UserRole).value<Modpack>(); Modpack rightPack = sourceModel()->data(right, Qt::UserRole).value<Modpack>();
if(currentSorting == Sorting::ByGameVersion) { if (currentSorting == Sorting::ByGameVersion) {
Version lv(leftPack.mcVersion); Version lv(leftPack.mcVersion);
Version rv(rightPack.mcVersion); Version rv(rightPack.mcVersion);
return lv < rv; return lv < rv;
} else if(currentSorting == Sorting::ByName) { } else if (currentSorting == Sorting::ByName) {
return StringUtils::naturalCompare(leftPack.name, rightPack.name, Qt::CaseSensitive) >= 0; return StringUtils::naturalCompare(leftPack.name, rightPack.name, Qt::CaseSensitive) >= 0;
} }
//UHM, some inavlid value set?! // UHM, some inavlid value set?!
qWarning() << "Invalid sorting set!"; qWarning() << "Invalid sorting set!";
return true; return true;
} }
bool FilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const bool FilterModel::filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const
{ {
return true; return true;
} }
@ -102,18 +102,13 @@ FilterModel::Sorting FilterModel::getCurrentSorting()
return currentSorting; return currentSorting;
} }
ListModel::ListModel(QObject *parent) : QAbstractListModel(parent) ListModel::ListModel(QObject* parent) : QAbstractListModel(parent) {}
{
}
ListModel::~ListModel() ListModel::~ListModel() {}
{
}
QString ListModel::translatePackType(PackType type) const QString ListModel::translatePackType(PackType type) const
{ {
switch(type) switch (type) {
{
case PackType::Public: case PackType::Public:
return tr("Public Modpack"); return tr("Public Modpack");
case PackType::ThirdParty: case PackType::ThirdParty:
@ -125,67 +120,51 @@ QString ListModel::translatePackType(PackType type) const
return QString(); return QString();
} }
int ListModel::rowCount(const QModelIndex &parent) const int ListModel::rowCount(const QModelIndex& parent) const
{ {
return parent.isValid() ? 0 : modpacks.size(); return parent.isValid() ? 0 : modpacks.size();
} }
int ListModel::columnCount(const QModelIndex &parent) const int ListModel::columnCount(const QModelIndex& parent) const
{ {
return parent.isValid() ? 0 : 1; return parent.isValid() ? 0 : 1;
} }
QVariant ListModel::data(const QModelIndex &index, int role) const QVariant ListModel::data(const QModelIndex& index, int role) const
{ {
int pos = index.row(); int pos = index.row();
if(pos >= modpacks.size() || pos < 0 || !index.isValid()) if (pos >= modpacks.size() || pos < 0 || !index.isValid()) {
{
return QString("INVALID INDEX %1").arg(pos); return QString("INVALID INDEX %1").arg(pos);
} }
Modpack pack = modpacks.at(pos); Modpack pack = modpacks.at(pos);
if(role == Qt::DisplayRole) if (role == Qt::DisplayRole) {
{
return pack.name + "\n" + translatePackType(pack.type); return pack.name + "\n" + translatePackType(pack.type);
} } else if (role == Qt::ToolTipRole) {
else if (role == Qt::ToolTipRole) if (pack.description.length() > 100) {
{ // some magic to prevent to long tooltips and replace html linebreaks
if(pack.description.length() > 100)
{
//some magic to prevent to long tooltips and replace html linebreaks
QString edit = pack.description.left(97); QString edit = pack.description.left(97);
edit = edit.left(edit.lastIndexOf("<br>")).left(edit.lastIndexOf(" ")).append("..."); edit = edit.left(edit.lastIndexOf("<br>")).left(edit.lastIndexOf(" ")).append("...");
return edit; return edit;
} }
return pack.description; return pack.description;
} } else if (role == Qt::DecorationRole) {
else if(role == Qt::DecorationRole) if (m_logoMap.contains(pack.logo)) {
{
if(m_logoMap.contains(pack.logo))
{
return (m_logoMap.value(pack.logo)); return (m_logoMap.value(pack.logo));
} }
QIcon icon = APPLICATION->getThemedIcon("screenshot-placeholder"); QIcon icon = APPLICATION->getThemedIcon("screenshot-placeholder");
((ListModel *)this)->requestLogo(pack.logo); ((ListModel*)this)->requestLogo(pack.logo);
return icon; return icon;
} } else if (role == Qt::ForegroundRole) {
else if(role == Qt::ForegroundRole) if (pack.broken) {
{ // FIXME: Hardcoded color
if(pack.broken)
{
//FIXME: Hardcoded color
return QColor(255, 0, 50); return QColor(255, 0, 50);
} } else if (pack.bugged) {
else if(pack.bugged) // FIXME: Hardcoded color
{ // bugged pack, currently only indicates bugged xml
//FIXME: Hardcoded color
//bugged pack, currently only indicates bugged xml
return QColor(244, 229, 66); return QColor(244, 229, 66);
} }
} } else if (role == Qt::UserRole) {
else if(role == Qt::UserRole)
{
QVariant v; QVariant v;
v.setValue(pack); v.setValue(pack);
return v; return v;
@ -222,8 +201,7 @@ Modpack ListModel::at(int row)
void ListModel::remove(int row) void ListModel::remove(int row)
{ {
if(row < 0 || row >= modpacks.size()) if (row < 0 || row >= modpacks.size()) {
{
qWarning() << "Attempt to remove FTB modpacks with invalid row" << row; qWarning() << "Attempt to remove FTB modpacks with invalid row" << row;
return; return;
} }
@ -247,27 +225,25 @@ void ListModel::logoFailed(QString logo)
void ListModel::requestLogo(QString file) void ListModel::requestLogo(QString file)
{ {
if(m_loadingLogos.contains(file) || m_failedLogos.contains(file)) if (m_loadingLogos.contains(file) || m_failedLogos.contains(file)) {
{
return; return;
} }
MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("FTBPacks", QString("logos/%1").arg(file.section(".", 0, 0))); MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("FTBPacks", QString("logos/%1").arg(file.section(".", 0, 0)));
NetJob *job = new NetJob(QString("FTB Icon Download for %1").arg(file), APPLICATION->network()); NetJob* job = new NetJob(QString("FTB Icon Download for %1").arg(file), APPLICATION->network());
job->addNetAction(Net::Download::makeCached(QUrl(QString(BuildConfig.LEGACY_FTB_CDN_BASE_URL + "static/%1").arg(file)), entry)); job->addNetAction(Net::Download::makeCached(QUrl(QString(BuildConfig.LEGACY_FTB_CDN_BASE_URL + "static/%1").arg(file)), entry));
auto fullPath = entry->getFullPath(); auto fullPath = entry->getFullPath();
QObject::connect(job, &NetJob::finished, this, [this, file, fullPath] QObject::connect(job, &NetJob::finished, this, [this, file, fullPath, job] {
{ job->deleteLater();
emit logoLoaded(file, QIcon(fullPath)); emit logoLoaded(file, QIcon(fullPath));
if(waitingCallbacks.contains(file)) if (waitingCallbacks.contains(file)) {
{
waitingCallbacks.value(file)(fullPath); waitingCallbacks.value(file)(fullPath);
} }
}); });
QObject::connect(job, &NetJob::failed, this, [this, file] QObject::connect(job, &NetJob::failed, this, [this, file, job] {
{ job->deleteLater();
emit logoFailed(file); emit logoFailed(file);
}); });
@ -276,21 +252,18 @@ void ListModel::requestLogo(QString file)
m_loadingLogos.append(file); m_loadingLogos.append(file);
} }
void ListModel::getLogo(const QString &logo, LogoCallback callback) void ListModel::getLogo(const QString& logo, LogoCallback callback)
{ {
if(m_logoMap.contains(logo)) if (m_logoMap.contains(logo)) {
{
callback(APPLICATION->metacache()->resolveEntry("FTBPacks", QString("logos/%1").arg(logo.section(".", 0, 0)))->getFullPath()); callback(APPLICATION->metacache()->resolveEntry("FTBPacks", QString("logos/%1").arg(logo.section(".", 0, 0)))->getFullPath());
} } else {
else
{
requestLogo(logo); requestLogo(logo);
} }
} }
Qt::ItemFlags ListModel::flags(const QModelIndex &index) const Qt::ItemFlags ListModel::flags(const QModelIndex& index) const
{ {
return QAbstractListModel::flags(index); return QAbstractListModel::flags(index);
} }
} } // namespace LegacyFTB

View File

@ -131,27 +131,27 @@ void ModpackListModel::performPaginatedSearch()
// TODO: Move to standalone API // TODO: Move to standalone API
auto netJob = makeShared<NetJob>("Modrinth::SearchModpack", APPLICATION->network()); auto netJob = makeShared<NetJob>("Modrinth::SearchModpack", APPLICATION->network());
auto searchAllUrl = QString(BuildConfig.MODRINTH_PROD_URL + auto searchAllUrl = QString(BuildConfig.MODRINTH_PROD_URL +
"/search?" "/search?"
"offset=%1&" "offset=%1&"
"limit=%2&" "limit=%2&"
"query=%3&" "query=%3&"
"index=%4&" "index=%4&"
"facets=[[\"project_type:modpack\"]]") "facets=[[\"project_type:modpack\"]]")
.arg(nextSearchOffset) .arg(nextSearchOffset)
.arg(m_modpacks_per_page) .arg(m_modpacks_per_page)
.arg(currentSearchTerm) .arg(currentSearchTerm)
.arg(currentSort); .arg(currentSort);
netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchAllUrl), &m_all_response)); netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchAllUrl), m_all_response));
QObject::connect(netJob.get(), &NetJob::succeeded, this, [this] { QObject::connect(netJob.get(), &NetJob::succeeded, this, [this] {
QJsonParseError parse_error_all{}; QJsonParseError parse_error_all{};
QJsonDocument doc_all = QJsonDocument::fromJson(m_all_response, &parse_error_all); QJsonDocument doc_all = QJsonDocument::fromJson(*m_all_response, &parse_error_all);
if (parse_error_all.error != QJsonParseError::NoError) { if (parse_error_all.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response from " << debugName() << " at " << parse_error_all.offset qWarning() << "Error while parsing JSON response from " << debugName() << " at " << parse_error_all.offset
<< " reason: " << parse_error_all.errorString(); << " reason: " << parse_error_all.errorString();
qWarning() << m_all_response; qWarning() << *m_all_response;
return; return;
} }

View File

@ -110,9 +110,9 @@ class ModpackListModel : public QAbstractListModel {
NetJob::Ptr jobPtr; NetJob::Ptr jobPtr;
QByteArray m_all_response; std::shared_ptr<QByteArray> m_all_response = std::make_shared<QByteArray>();
QByteArray m_specific_response; QByteArray m_specific_response;
int m_modpacks_per_page = 20; int m_modpacks_per_page = 20;
}; };
} // namespace ModPlatform } // namespace Modrinth

View File

@ -123,7 +123,7 @@ void ModrinthPage::onSelectionChanged(QModelIndex curr, QModelIndex prev)
qDebug() << "Loading modrinth modpack information"; qDebug() << "Loading modrinth modpack information";
auto netJob = new NetJob(QString("Modrinth::PackInformation(%1)").arg(current.name), APPLICATION->network()); auto netJob = new NetJob(QString("Modrinth::PackInformation(%1)").arg(current.name), APPLICATION->network());
auto response = new QByteArray(); auto response = std::make_shared<QByteArray>();
QString id = current.id; QString id = current.id;
@ -162,10 +162,7 @@ void ModrinthPage::onSelectionChanged(QModelIndex curr, QModelIndex prev)
suggestCurrent(); suggestCurrent();
}); });
QObject::connect(netJob, &NetJob::finished, this, [response, netJob] { QObject::connect(netJob, &NetJob::finished, this, [response, netJob] { netJob->deleteLater(); });
netJob->deleteLater();
delete response;
});
netJob->start(); netJob->start();
} else } else
updateUI(); updateUI();
@ -174,7 +171,7 @@ void ModrinthPage::onSelectionChanged(QModelIndex curr, QModelIndex prev)
qDebug() << "Loading modrinth modpack versions"; qDebug() << "Loading modrinth modpack versions";
auto netJob = new NetJob(QString("Modrinth::PackVersions(%1)").arg(current.name), APPLICATION->network()); auto netJob = new NetJob(QString("Modrinth::PackVersions(%1)").arg(current.name), APPLICATION->network());
auto response = new QByteArray(); auto response = std::make_shared<QByteArray>();
QString id = current.id; QString id = current.id;
@ -217,10 +214,7 @@ void ModrinthPage::onSelectionChanged(QModelIndex curr, QModelIndex prev)
suggestCurrent(); suggestCurrent();
}); });
QObject::connect(netJob, &NetJob::finished, this, [response, netJob] { QObject::connect(netJob, &NetJob::finished, this, [response, netJob] { netJob->deleteLater(); });
netJob->deleteLater();
delete response;
});
netJob->start(); netJob->start();
} else { } else {
@ -260,10 +254,8 @@ void ModrinthPage::updateUI()
text += donates.join(", "); text += donates.join(", ");
} }
if (!current.extra.issuesUrl.isEmpty() if (!current.extra.issuesUrl.isEmpty() || !current.extra.sourceUrl.isEmpty() || !current.extra.wikiUrl.isEmpty() ||
|| !current.extra.sourceUrl.isEmpty() !current.extra.discordUrl.isEmpty()) {
|| !current.extra.wikiUrl.isEmpty()
|| !current.extra.discordUrl.isEmpty()) {
text += "<br><br>" + tr("External links:") + "<br>"; text += "<br><br>" + tr("External links:") + "<br>";
} }

View File

@ -40,39 +40,28 @@
#include <QIcon> #include <QIcon>
Technic::ListModel::ListModel(QObject *parent) : QAbstractListModel(parent) Technic::ListModel::ListModel(QObject* parent) : QAbstractListModel(parent) {}
{
}
Technic::ListModel::~ListModel() Technic::ListModel::~ListModel() {}
{
}
QVariant Technic::ListModel::data(const QModelIndex& index, int role) const QVariant Technic::ListModel::data(const QModelIndex& index, int role) const
{ {
int pos = index.row(); int pos = index.row();
if(pos >= modpacks.size() || pos < 0 || !index.isValid()) if (pos >= modpacks.size() || pos < 0 || !index.isValid()) {
{
return QString("INVALID INDEX %1").arg(pos); return QString("INVALID INDEX %1").arg(pos);
} }
Modpack pack = modpacks.at(pos); Modpack pack = modpacks.at(pos);
if(role == Qt::DisplayRole) if (role == Qt::DisplayRole) {
{
return pack.name; return pack.name;
} } else if (role == Qt::DecorationRole) {
else if(role == Qt::DecorationRole) if (m_logoMap.contains(pack.logoName)) {
{
if(m_logoMap.contains(pack.logoName))
{
return (m_logoMap.value(pack.logoName)); return (m_logoMap.value(pack.logoName));
} }
QIcon icon = APPLICATION->getThemedIcon("screenshot-placeholder"); QIcon icon = APPLICATION->getThemedIcon("screenshot-placeholder");
((ListModel *)this)->requestLogo(pack.logoName, pack.logoUrl); ((ListModel*)this)->requestLogo(pack.logoName, pack.logoUrl);
return icon; return icon;
} } else if (role == Qt::UserRole) {
else if(role == Qt::UserRole)
{
QVariant v; QVariant v;
v.setValue(pack); v.setValue(pack);
return v; return v;
@ -92,16 +81,15 @@ int Technic::ListModel::rowCount(const QModelIndex& parent) const
void Technic::ListModel::searchWithTerm(const QString& term) void Technic::ListModel::searchWithTerm(const QString& term)
{ {
if(currentSearchTerm == term && currentSearchTerm.isNull() == term.isNull()) { if (currentSearchTerm == term && currentSearchTerm.isNull() == term.isNull()) {
return; return;
} }
currentSearchTerm = term; currentSearchTerm = term;
if(jobPtr) { if (jobPtr) {
jobPtr->abort(); jobPtr->abort();
searchState = ResetRequested; searchState = ResetRequested;
return; return;
} } else {
else {
beginResetModel(); beginResetModel();
modpacks.clear(); modpacks.clear();
endResetModel(); endResetModel();
@ -115,26 +103,20 @@ void Technic::ListModel::performSearch()
auto netJob = makeShared<NetJob>("Technic::Search", APPLICATION->network()); auto netJob = makeShared<NetJob>("Technic::Search", APPLICATION->network());
QString searchUrl = ""; QString searchUrl = "";
if (currentSearchTerm.isEmpty()) { if (currentSearchTerm.isEmpty()) {
searchUrl = QString("%1trending?build=%2") searchUrl = QString("%1trending?build=%2").arg(BuildConfig.TECHNIC_API_BASE_URL, BuildConfig.TECHNIC_API_BUILD);
.arg(BuildConfig.TECHNIC_API_BASE_URL, BuildConfig.TECHNIC_API_BUILD);
searchMode = List; searchMode = List;
} } else if (currentSearchTerm.startsWith("http://api.technicpack.net/modpack/")) {
else if (currentSearchTerm.startsWith("http://api.technicpack.net/modpack/")) { searchUrl = QString("https://%1?build=%2").arg(currentSearchTerm.mid(7), BuildConfig.TECHNIC_API_BUILD);
searchUrl = QString("https://%1?build=%2")
.arg(currentSearchTerm.mid(7), BuildConfig.TECHNIC_API_BUILD);
searchMode = Single; searchMode = Single;
} } else if (currentSearchTerm.startsWith("https://api.technicpack.net/modpack/")) {
else if (currentSearchTerm.startsWith("https://api.technicpack.net/modpack/")) {
searchUrl = QString("%1?build=%2").arg(currentSearchTerm, BuildConfig.TECHNIC_API_BUILD); searchUrl = QString("%1?build=%2").arg(currentSearchTerm, BuildConfig.TECHNIC_API_BUILD);
searchMode = Single; searchMode = Single;
} } else {
else { searchUrl =
searchUrl = QString( QString("%1search?build=%2&q=%3").arg(BuildConfig.TECHNIC_API_BASE_URL, BuildConfig.TECHNIC_API_BUILD, currentSearchTerm);
"%1search?build=%2&q=%3"
).arg(BuildConfig.TECHNIC_API_BASE_URL, BuildConfig.TECHNIC_API_BUILD, currentSearchTerm);
searchMode = List; searchMode = List;
} }
netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), &response)); netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), response));
jobPtr = netJob; jobPtr = netJob;
jobPtr->start(); jobPtr->start();
QObject::connect(netJob.get(), &NetJob::succeeded, this, &ListModel::searchRequestFinished); QObject::connect(netJob.get(), &NetJob::succeeded, this, &ListModel::searchRequestFinished);
@ -146,11 +128,11 @@ void Technic::ListModel::searchRequestFinished()
jobPtr.reset(); jobPtr.reset();
QJsonParseError parse_error; QJsonParseError parse_error;
QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error); QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
if(parse_error.error != QJsonParseError::NoError) if (parse_error.error != QJsonParseError::NoError) {
{ qWarning() << "Error while parsing JSON response from Technic at " << parse_error.offset
qWarning() << "Error while parsing JSON response from Technic at " << parse_error.offset << " reason: " << parse_error.errorString(); << " reason: " << parse_error.errorString();
qWarning() << response; qWarning() << *response;
return; return;
} }
@ -161,7 +143,7 @@ void Technic::ListModel::searchRequestFinished()
switch (searchMode) { switch (searchMode) {
case List: { case List: {
auto objs = Json::requireArray(root, "modpacks"); auto objs = Json::requireArray(root, "modpacks");
for (auto technicPack: objs) { for (auto technicPack : objs) {
Modpack pack; Modpack pack;
auto technicPackObject = Json::requireObject(technicPack); auto technicPackObject = Json::requireObject(technicPack);
pack.name = Json::requireString(technicPackObject, "name"); pack.name = Json::requireString(technicPackObject, "name");
@ -170,11 +152,10 @@ void Technic::ListModel::searchRequestFinished()
continue; continue;
auto rawURL = Json::ensureString(technicPackObject, "iconUrl", "null"); auto rawURL = Json::ensureString(technicPackObject, "iconUrl", "null");
if(rawURL == "null") { if (rawURL == "null") {
pack.logoUrl = "null"; pack.logoUrl = "null";
pack.logoName = "null"; pack.logoName = "null";
} } else {
else {
pack.logoUrl = rawURL; pack.logoUrl = rawURL;
pack.logoName = rawURL.section(QLatin1Char('/'), -1).section(QLatin1Char('.'), 0, 0); pack.logoName = rawURL.section(QLatin1Char('/'), -1).section(QLatin1Char('.'), 0, 0);
} }
@ -199,8 +180,7 @@ void Technic::ListModel::searchRequestFinished()
pack.logoUrl = iconUrl; pack.logoUrl = iconUrl;
pack.logoName = iconUrl.section(QLatin1Char('/'), -1).section(QLatin1Char('.'), 0, 0); pack.logoName = iconUrl.section(QLatin1Char('/'), -1).section(QLatin1Char('.'), 0, 0);
} } else {
else {
pack.logoUrl = "null"; pack.logoUrl = "null";
pack.logoName = "null"; pack.logoName = "null";
} }
@ -210,10 +190,8 @@ void Technic::ListModel::searchRequestFinished()
break; break;
} }
} }
} } catch (const JSONValidationError& err) {
catch (const JSONValidationError &err) qCritical() << "Couldn't parse technic search results:" << err.cause();
{
qCritical() << "Couldn't parse technic search results:" << err.cause() ;
return; return;
} }
searchState = Finished; searchState = Finished;
@ -229,12 +207,9 @@ void Technic::ListModel::searchRequestFinished()
void Technic::ListModel::getLogo(const QString& logo, const QString& logoUrl, Technic::LogoCallback callback) void Technic::ListModel::getLogo(const QString& logo, const QString& logoUrl, Technic::LogoCallback callback)
{ {
if(m_logoMap.contains(logo)) if (m_logoMap.contains(logo)) {
{
callback(APPLICATION->metacache()->resolveEntry("TechnicPacks", QString("logos/%1").arg(logo))->getFullPath()); callback(APPLICATION->metacache()->resolveEntry("TechnicPacks", QString("logos/%1").arg(logo))->getFullPath());
} } else {
else
{
requestLogo(logo, logoUrl); requestLogo(logo, logoUrl);
} }
} }
@ -243,30 +218,24 @@ void Technic::ListModel::searchRequestFailed()
{ {
jobPtr.reset(); jobPtr.reset();
if(searchState == ResetRequested) if (searchState == ResetRequested) {
{
beginResetModel(); beginResetModel();
modpacks.clear(); modpacks.clear();
endResetModel(); endResetModel();
performSearch(); performSearch();
} } else {
else
{
searchState = Finished; searchState = Finished;
} }
} }
void Technic::ListModel::logoLoaded(QString logo, QString out) void Technic::ListModel::logoLoaded(QString logo, QString out)
{ {
m_loadingLogos.removeAll(logo); m_loadingLogos.removeAll(logo);
m_logoMap.insert(logo, QIcon(out)); m_logoMap.insert(logo, QIcon(out));
for(int i = 0; i < modpacks.size(); i++) for (int i = 0; i < modpacks.size(); i++) {
{ if (modpacks[i].logoName == logo) {
if(modpacks[i].logoName == logo) emit dataChanged(createIndex(i, 0), createIndex(i, 0), { Qt::DecorationRole });
{
emit dataChanged(createIndex(i, 0), createIndex(i, 0), {Qt::DecorationRole});
} }
} }
} }
@ -279,24 +248,23 @@ void Technic::ListModel::logoFailed(QString logo)
void Technic::ListModel::requestLogo(QString logo, QString url) void Technic::ListModel::requestLogo(QString logo, QString url)
{ {
if(m_loadingLogos.contains(logo) || m_failedLogos.contains(logo) || logo == "null") if (m_loadingLogos.contains(logo) || m_failedLogos.contains(logo) || logo == "null") {
{
return; return;
} }
MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("TechnicPacks", QString("logos/%1").arg(logo)); MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("TechnicPacks", QString("logos/%1").arg(logo));
NetJob *job = new NetJob(QString("Technic Icon Download %1").arg(logo), APPLICATION->network()); auto job = new NetJob(QString("Technic Icon Download %1").arg(logo), APPLICATION->network());
job->addNetAction(Net::Download::makeCached(QUrl(url), entry)); job->addNetAction(Net::Download::makeCached(QUrl(url), entry));
auto fullPath = entry->getFullPath(); auto fullPath = entry->getFullPath();
QObject::connect(job, &NetJob::succeeded, this, [this, logo, fullPath] QObject::connect(job, &NetJob::succeeded, this, [this, logo, fullPath, job] {
{ job->deleteLater();
logoLoaded(logo, fullPath); logoLoaded(logo, fullPath);
}); });
QObject::connect(job, &NetJob::failed, this, [this, logo] QObject::connect(job, &NetJob::failed, this, [this, logo, job] {
{ job->deleteLater();
logoFailed(logo); logoFailed(logo);
}); });

View File

@ -44,33 +44,32 @@ namespace Technic {
typedef std::function<void(QString)> LogoCallback; typedef std::function<void(QString)> LogoCallback;
class ListModel : public QAbstractListModel class ListModel : public QAbstractListModel {
{
Q_OBJECT Q_OBJECT
public: public:
ListModel(QObject *parent); ListModel(QObject* parent);
virtual ~ListModel(); virtual ~ListModel();
virtual QVariant data(const QModelIndex& index, int role) const; virtual QVariant data(const QModelIndex& index, int role) const;
virtual int columnCount(const QModelIndex& parent) const; virtual int columnCount(const QModelIndex& parent) const;
virtual int rowCount(const QModelIndex& parent) const; virtual int rowCount(const QModelIndex& parent) const;
void getLogo(const QString &logo, const QString &logoUrl, LogoCallback callback); void getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback);
void searchWithTerm(const QString & term); void searchWithTerm(const QString& term);
private slots: private slots:
void searchRequestFinished(); void searchRequestFinished();
void searchRequestFailed(); void searchRequestFailed();
void logoFailed(QString logo); void logoFailed(QString logo);
void logoLoaded(QString logo, QString out); void logoLoaded(QString logo, QString out);
private: private:
void performSearch(); void performSearch();
void requestLogo(QString logo, QString url); void requestLogo(QString logo, QString url);
private: private:
QList<Modpack> modpacks; QList<Modpack> modpacks;
QStringList m_failedLogos; QStringList m_failedLogos;
QStringList m_loadingLogos; QStringList m_loadingLogos;
@ -78,17 +77,13 @@ private:
QMap<QString, LogoCallback> waitingCallbacks; QMap<QString, LogoCallback> waitingCallbacks;
QString currentSearchTerm; QString currentSearchTerm;
enum SearchState { enum SearchState { None, ResetRequested, Finished } searchState = None;
None,
ResetRequested,
Finished
} searchState = None;
enum SearchMode { enum SearchMode {
List, List,
Single, Single,
} searchMode = List; } searchMode = List;
NetJob::Ptr jobPtr; NetJob::Ptr jobPtr;
QByteArray response; std::shared_ptr<QByteArray> response = std::make_shared<QByteArray>();
}; };
} } // namespace Technic

View File

@ -143,7 +143,7 @@ void TechnicPage::suggestCurrent()
auto netJob = makeShared<NetJob>(QString("Technic::PackMeta(%1)").arg(current.name), APPLICATION->network()); auto netJob = makeShared<NetJob>(QString("Technic::PackMeta(%1)").arg(current.name), APPLICATION->network());
QString slug = current.slug; QString slug = current.slug;
netJob->addNetAction(Net::Download::makeByteArray(QString("%1modpack/%2?build=%3").arg(BuildConfig.TECHNIC_API_BASE_URL, slug, BuildConfig.TECHNIC_API_BUILD), &response)); netJob->addNetAction(Net::Download::makeByteArray(QString("%1modpack/%2?build=%3").arg(BuildConfig.TECHNIC_API_BASE_URL, slug, BuildConfig.TECHNIC_API_BUILD), response));
QObject::connect(netJob.get(), &NetJob::succeeded, this, [this, slug] QObject::connect(netJob.get(), &NetJob::succeeded, this, [this, slug]
{ {
jobPtr.reset(); jobPtr.reset();
@ -154,7 +154,7 @@ void TechnicPage::suggestCurrent()
} }
QJsonParseError parse_error {}; QJsonParseError parse_error {};
QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error); QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
QJsonObject obj = doc.object(); QJsonObject obj = doc.object();
if(parse_error.error != QJsonParseError::NoError) if(parse_error.error != QJsonParseError::NoError)
{ {
@ -249,7 +249,7 @@ void TechnicPage::metadataLoaded()
auto netJob = makeShared<NetJob>(QString("Technic::SolderMeta(%1)").arg(current.name), APPLICATION->network()); auto netJob = makeShared<NetJob>(QString("Technic::SolderMeta(%1)").arg(current.name), APPLICATION->network());
auto url = QString("%1/modpack/%2").arg(current.url, current.slug); auto url = QString("%1/modpack/%2").arg(current.url, current.slug);
netJob->addNetAction(Net::Download::makeByteArray(QUrl(url), &response)); netJob->addNetAction(Net::Download::makeByteArray(QUrl(url), response));
QObject::connect(netJob.get(), &NetJob::succeeded, this, &TechnicPage::onSolderLoaded); QObject::connect(netJob.get(), &NetJob::succeeded, this, &TechnicPage::onSolderLoaded);
@ -291,11 +291,11 @@ void TechnicPage::onSolderLoaded() {
current.versions.clear(); current.versions.clear();
QJsonParseError parse_error {}; QJsonParseError parse_error{};
auto doc = QJsonDocument::fromJson(response, &parse_error); auto doc = QJsonDocument::fromJson(*response, &parse_error);
if (parse_error.error != QJsonParseError::NoError) { if (parse_error.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response from Solder at " << parse_error.offset << " reason: " << parse_error.errorString(); qWarning() << "Error while parsing JSON response from Solder at " << parse_error.offset << " reason: " << parse_error.errorString();
qWarning() << response; qWarning() << *response;
fallback(); fallback();
return; return;
} }
@ -304,8 +304,7 @@ void TechnicPage::onSolderLoaded() {
TechnicSolder::Pack pack; TechnicSolder::Pack pack;
try { try {
TechnicSolder::loadPack(pack, obj); TechnicSolder::loadPack(pack, obj);
} } catch (const JSONValidationError& err) {
catch (const JSONValidationError& err) {
qCritical() << "Couldn't parse Solder pack metadata:" << err.cause(); qCritical() << "Couldn't parse Solder pack metadata:" << err.cause();
fallback(); fallback();
return; return;

View File

@ -104,5 +104,5 @@ private:
QString selectedVersion; QString selectedVersion;
NetJob::Ptr jobPtr; NetJob::Ptr jobPtr;
QByteArray response; std::shared_ptr<QByteArray> response = std::make_shared<QByteArray>();
}; };

View File

@ -47,6 +47,8 @@ InfoFrame::InfoFrame(QWidget *parent) :
ui->setupUi(this); ui->setupUi(this);
ui->descriptionLabel->setHidden(true); ui->descriptionLabel->setHidden(true);
ui->nameLabel->setHidden(true); ui->nameLabel->setHidden(true);
ui->licenseLabel->setHidden(true);
ui->issueTrackerLabel->setHidden(true);
updateHiddenState(); updateHiddenState();
} }
@ -88,7 +90,41 @@ void InfoFrame::updateWithMod(Mod const& m)
setDescription(m.description()); setDescription(m.description());
} }
setImage(); setImage(m.icon({64,64}));
auto licenses = m.licenses();
QString licenseText = "";
if (!licenses.empty()) {
for (auto l : licenses) {
if (!licenseText.isEmpty()) {
licenseText += "\n"; // add newline between licenses
}
if (!l.name.isEmpty()) {
if (l.url.isEmpty()) {
licenseText += l.name;
} else {
licenseText += "<a href=\"" + l.url + "\">" + l.name + "</a>";
}
} else if (!l.url.isEmpty()) {
licenseText += "<a href=\"" + l.url + "\">" + l.url + "</a>";
}
if (!l.description.isEmpty() && l.description != l.name) {
licenseText += " " + l.description;
}
}
}
if (!licenseText.isEmpty()) {
setLicense(tr("License: %1").arg(licenseText));
} else {
setLicense();
}
QString issueTracker = "";
if (!m.issueTracker().isEmpty()) {
issueTracker += tr("Report issues to: ");
issueTracker += "<a href=\"" + m.issueTracker() + "\">" + m.issueTracker() + "</a>";
}
setIssueTracker(issueTracker);
} }
void InfoFrame::updateWithResource(const Resource& resource) void InfoFrame::updateWithResource(const Resource& resource)
@ -177,16 +213,16 @@ void InfoFrame::clear()
setName(); setName();
setDescription(); setDescription();
setImage(); setImage();
setLicense();
setIssueTracker();
} }
void InfoFrame::updateHiddenState() void InfoFrame::updateHiddenState()
{ {
if(ui->descriptionLabel->isHidden() && ui->nameLabel->isHidden()) if (ui->descriptionLabel->isHidden() && ui->nameLabel->isHidden() && ui->licenseLabel->isHidden() &&
{ ui->issueTrackerLabel->isHidden()) {
setHidden(true); setHidden(true);
} } else {
else
{
setHidden(false); setHidden(false);
} }
} }
@ -251,6 +287,66 @@ void InfoFrame::setDescription(QString text)
ui->descriptionLabel->setText(labeltext); ui->descriptionLabel->setText(labeltext);
} }
void InfoFrame::setLicense(QString text)
{
if(text.isEmpty())
{
ui->licenseLabel->setHidden(true);
updateHiddenState();
return;
}
else
{
ui->licenseLabel->setHidden(false);
updateHiddenState();
}
ui->licenseLabel->setToolTip("");
QString intermediatetext = text.trimmed();
bool prev(false);
QChar rem('\n');
QString finaltext;
finaltext.reserve(intermediatetext.size());
foreach(const QChar& c, intermediatetext)
{
if(c == rem && prev){
continue;
}
prev = c == rem;
finaltext += c;
}
QString labeltext;
labeltext.reserve(300);
if(finaltext.length() > 290)
{
ui->licenseLabel->setOpenExternalLinks(false);
ui->licenseLabel->setTextFormat(Qt::TextFormat::RichText);
m_description = text;
// This allows injecting HTML here.
labeltext.append("<html><body>" + finaltext.left(287) + "<a href=\"#mod_desc\">...</a></body></html>");
QObject::connect(ui->licenseLabel, &QLabel::linkActivated, this, &InfoFrame::licenseEllipsisHandler);
}
else
{
ui->licenseLabel->setTextFormat(Qt::TextFormat::AutoText);
labeltext.append(finaltext);
}
ui->licenseLabel->setText(labeltext);
}
void InfoFrame::setIssueTracker(QString text)
{
if(text.isEmpty())
{
ui->issueTrackerLabel->setHidden(true);
}
else
{
ui->issueTrackerLabel->setText(text);
ui->issueTrackerLabel->setHidden(false);
}
updateHiddenState();
}
void InfoFrame::setImage(QPixmap img) void InfoFrame::setImage(QPixmap img)
{ {
if (img.isNull()) { if (img.isNull()) {
@ -275,6 +371,20 @@ void InfoFrame::descriptionEllipsisHandler(QString link)
} }
} }
void InfoFrame::licenseEllipsisHandler(QString link)
{
if(!m_current_box)
{
m_current_box = CustomMessageBox::selectable(this, "", m_license);
connect(m_current_box, &QMessageBox::finished, this, &InfoFrame::boxClosed);
m_current_box->show();
}
else
{
m_current_box->setText(m_license);
}
}
void InfoFrame::boxClosed(int result) void InfoFrame::boxClosed(int result)
{ {
m_current_box = nullptr; m_current_box = nullptr;

View File

@ -36,6 +36,8 @@ class InfoFrame : public QFrame {
void setName(QString text = {}); void setName(QString text = {});
void setDescription(QString text = {}); void setDescription(QString text = {});
void setImage(QPixmap img = {}); void setImage(QPixmap img = {});
void setLicense(QString text = {});
void setIssueTracker(QString text = {});
void clear(); void clear();
@ -48,6 +50,7 @@ class InfoFrame : public QFrame {
public slots: public slots:
void descriptionEllipsisHandler(QString link); void descriptionEllipsisHandler(QString link);
void licenseEllipsisHandler(QString link);
void boxClosed(int result); void boxClosed(int result);
private: private:
@ -56,5 +59,6 @@ class InfoFrame : public QFrame {
private: private:
Ui::InfoFrame* ui; Ui::InfoFrame* ui;
QString m_description; QString m_description;
QString m_license;
class QMessageBox* m_current_box = nullptr; class QMessageBox* m_current_box = nullptr;
}; };

View File

@ -35,25 +35,28 @@
<property name="bottomMargin"> <property name="bottomMargin">
<number>0</number> <number>0</number>
</property> </property>
<item row="0" column="1"> <item row="0" column="0" rowspan="2">
<widget class="QLabel" name="nameLabel"> <widget class="QLabel" name="iconLabel">
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>64</width>
<height>64</height>
</size>
</property>
<property name="text"> <property name="text">
<string notr="true"/> <string notr="true"/>
</property> </property>
<property name="textFormat"> <property name="scaledContents">
<enum>Qt::RichText</enum> <bool>false</bool>
</property> </property>
<property name="alignment"> <property name="margin">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set> <number>0</number>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property> </property>
</widget> </widget>
</item> </item>
@ -82,28 +85,69 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="0" rowspan="2"> <item row="0" column="1">
<widget class="QLabel" name="iconLabel"> <widget class="QLabel" name="nameLabel">
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>64</width>
<height>64</height>
</size>
</property>
<property name="text"> <property name="text">
<string notr="true"/> <string notr="true"/>
</property> </property>
<property name="scaledContents"> <property name="textFormat">
<bool>false</bool> <enum>Qt::RichText</enum>
</property> </property>
<property name="margin"> <property name="alignment">
<number>0</number> <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLabel" name="licenseLabel">
<property name="text">
<string/>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLabel" name="issueTrackerLabel">
<property name="text">
<string/>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property> </property>
</widget> </widget>
</item> </item>

View File

@ -79,3 +79,12 @@ void ModListView::setModel ( QAbstractItemModel* model )
}); });
} }
} }
void ModListView::setResizeModes(const QList<QHeaderView::ResizeMode> &modes)
{
auto head = header();
for(int i = 0; i < modes.count(); i++) {
head->setSectionResizeMode(i, modes[i]);
}
}

View File

@ -14,6 +14,7 @@
*/ */
#pragma once #pragma once
#include <QHeaderView>
#include <QTreeView> #include <QTreeView>
class ModListView: public QTreeView class ModListView: public QTreeView
@ -22,4 +23,5 @@ class ModListView: public QTreeView
public: public:
explicit ModListView ( QWidget* parent = 0 ); explicit ModListView ( QWidget* parent = 0 );
virtual void setModel ( QAbstractItemModel* model ); virtual void setModel ( QAbstractItemModel* model );
virtual void setResizeModes (const QList<QHeaderView::ResizeMode>& modes);
}; };

View File

@ -93,6 +93,10 @@ PageContainer::PageContainer(BasePageProvider *pageProvider, QString defaultId,
page->listIndex = counter; page->listIndex = counter;
page->setParentContainer(this); page->setParentContainer(this);
counter++; counter++;
page->updateExtraInfo = [this](QString info) {
if (m_currentPage)
m_header->setText(m_currentPage->displayName() + info);
};
} }
m_model->setPages(pages); m_model->setPages(pages);