Merge branch 'develop' into better-component-installation
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
4
.git-blame-ignore-revs
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# .git-blame-ignore-revs
|
||||||
|
|
||||||
|
# tabs -> spaces
|
||||||
|
bbb3b3e6f6e3c0f95873f22e6d0a4aaf350f49d9
|
@ -594,7 +594,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
|
|||||||
|
|
||||||
// Java Settings
|
// Java Settings
|
||||||
m_settings->registerSetting("JavaPath", "");
|
m_settings->registerSetting("JavaPath", "");
|
||||||
m_settings->registerSetting("JavaTimestamp", 0);
|
m_settings->registerSetting("JavaSignature", "");
|
||||||
m_settings->registerSetting("JavaArchitecture", "");
|
m_settings->registerSetting("JavaArchitecture", "");
|
||||||
m_settings->registerSetting("JavaRealArchitecture", "");
|
m_settings->registerSetting("JavaRealArchitecture", "");
|
||||||
m_settings->registerSetting("JavaVersion", "");
|
m_settings->registerSetting("JavaVersion", "");
|
||||||
|
@ -362,6 +362,8 @@ set(MINECRAFT_SOURCES
|
|||||||
minecraft/mod/tasks/LocalWorldSaveParseTask.cpp
|
minecraft/mod/tasks/LocalWorldSaveParseTask.cpp
|
||||||
minecraft/mod/tasks/LocalResourceParse.h
|
minecraft/mod/tasks/LocalResourceParse.h
|
||||||
minecraft/mod/tasks/LocalResourceParse.cpp
|
minecraft/mod/tasks/LocalResourceParse.cpp
|
||||||
|
minecraft/mod/tasks/GetModDependenciesTask.h
|
||||||
|
minecraft/mod/tasks/GetModDependenciesTask.cpp
|
||||||
|
|
||||||
# Assets
|
# Assets
|
||||||
minecraft/AssetsUtils.h
|
minecraft/AssetsUtils.h
|
||||||
@ -375,8 +377,6 @@ set(MINECRAFT_SOURCES
|
|||||||
minecraft/services/SkinDelete.cpp
|
minecraft/services/SkinDelete.cpp
|
||||||
minecraft/services/SkinDelete.h
|
minecraft/services/SkinDelete.h
|
||||||
|
|
||||||
mojang/PackageManifest.h
|
|
||||||
mojang/PackageManifest.cpp
|
|
||||||
minecraft/Agent.h)
|
minecraft/Agent.h)
|
||||||
|
|
||||||
# the screenshots feature
|
# the screenshots feature
|
||||||
@ -682,6 +682,7 @@ SET(LAUNCHER_SOURCES
|
|||||||
VersionProxyModel.h
|
VersionProxyModel.h
|
||||||
VersionProxyModel.cpp
|
VersionProxyModel.cpp
|
||||||
Markdown.h
|
Markdown.h
|
||||||
|
Markdown.cpp
|
||||||
|
|
||||||
# Super secret!
|
# Super secret!
|
||||||
KonamiCode.h
|
KonamiCode.h
|
||||||
@ -825,8 +826,8 @@ SET(LAUNCHER_SOURCES
|
|||||||
ui/pages/global/APIPage.h
|
ui/pages/global/APIPage.h
|
||||||
|
|
||||||
# GUI - platform pages
|
# GUI - platform pages
|
||||||
ui/pages/modplatform/VanillaPage.cpp
|
ui/pages/modplatform/CustomPage.cpp
|
||||||
ui/pages/modplatform/VanillaPage.h
|
ui/pages/modplatform/CustomPage.h
|
||||||
|
|
||||||
ui/pages/modplatform/ResourcePage.cpp
|
ui/pages/modplatform/ResourcePage.cpp
|
||||||
ui/pages/modplatform/ResourcePage.h
|
ui/pages/modplatform/ResourcePage.h
|
||||||
@ -1034,7 +1035,7 @@ qt_wrap_ui(LAUNCHER_UI
|
|||||||
ui/pages/instance/ScreenshotsPage.ui
|
ui/pages/instance/ScreenshotsPage.ui
|
||||||
ui/pages/modplatform/atlauncher/AtlOptionalModDialog.ui
|
ui/pages/modplatform/atlauncher/AtlOptionalModDialog.ui
|
||||||
ui/pages/modplatform/atlauncher/AtlPage.ui
|
ui/pages/modplatform/atlauncher/AtlPage.ui
|
||||||
ui/pages/modplatform/VanillaPage.ui
|
ui/pages/modplatform/CustomPage.ui
|
||||||
ui/pages/modplatform/ResourcePage.ui
|
ui/pages/modplatform/ResourcePage.ui
|
||||||
ui/pages/modplatform/flame/FlamePage.ui
|
ui/pages/modplatform/flame/FlamePage.ui
|
||||||
ui/pages/modplatform/legacy_ftb/Page.ui
|
ui/pages/modplatform/legacy_ftb/Page.ui
|
||||||
|
@ -40,6 +40,7 @@
|
|||||||
#include <QFileSystemModel>
|
#include <QFileSystemModel>
|
||||||
#include <QSortFilterProxyModel>
|
#include <QSortFilterProxyModel>
|
||||||
#include <QStack>
|
#include <QStack>
|
||||||
|
#include <algorithm>
|
||||||
#include "FileSystem.h"
|
#include "FileSystem.h"
|
||||||
#include "SeparatorPrefixTree.h"
|
#include "SeparatorPrefixTree.h"
|
||||||
#include "StringUtils.h"
|
#include "StringUtils.h"
|
||||||
@ -254,3 +255,25 @@ bool FileIgnoreProxy::filterAcceptsColumn(int source_column, const QModelIndex&
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool FileIgnoreProxy::filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const
|
||||||
|
{
|
||||||
|
QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
|
||||||
|
QFileSystemModel* fsm = qobject_cast<QFileSystemModel*>(sourceModel());
|
||||||
|
|
||||||
|
auto fileInfo = fsm->fileInfo(index);
|
||||||
|
return !ignoreFile(fileInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FileIgnoreProxy::ignoreFile(QFileInfo fileInfo) const
|
||||||
|
{
|
||||||
|
auto fileName = fileInfo.fileName();
|
||||||
|
auto path = relPath(fileInfo.absoluteFilePath());
|
||||||
|
return std::any_of(m_ignoreFiles.cbegin(), m_ignoreFiles.cend(), [fileName](auto iFileName) { return fileName == iFileName; }) ||
|
||||||
|
m_ignoreFilePaths.covers(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FileIgnoreProxy::filterFile(const QString& fileName) const
|
||||||
|
{
|
||||||
|
return blocked.covers(fileName) || ignoreFile(QFileInfo(QDir(root), fileName));
|
||||||
|
}
|
||||||
|
@ -36,6 +36,7 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <QFileInfo>
|
||||||
#include <QSortFilterProxyModel>
|
#include <QSortFilterProxyModel>
|
||||||
#include "SeparatorPrefixTree.h"
|
#include "SeparatorPrefixTree.h"
|
||||||
|
|
||||||
@ -63,10 +64,22 @@ class FileIgnoreProxy : public QSortFilterProxyModel {
|
|||||||
inline const SeparatorPrefixTree<'/'>& blockedPaths() const { return blocked; }
|
inline const SeparatorPrefixTree<'/'>& blockedPaths() const { return blocked; }
|
||||||
inline SeparatorPrefixTree<'/'>& blockedPaths() { return blocked; }
|
inline SeparatorPrefixTree<'/'>& blockedPaths() { return blocked; }
|
||||||
|
|
||||||
|
// list of file names that need to be removed completely from model
|
||||||
|
inline QStringList& ignoreFilesWithName() { return m_ignoreFiles; }
|
||||||
|
// list of relative paths that need to be removed completely from model
|
||||||
|
inline SeparatorPrefixTree<'/'>& ignoreFilesWithPath() { return m_ignoreFilePaths; }
|
||||||
|
|
||||||
|
bool filterFile(const QString& fileName) const;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool filterAcceptsColumn(int source_column, const QModelIndex& source_parent) const;
|
bool filterAcceptsColumn(int source_column, const QModelIndex& source_parent) const;
|
||||||
|
bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const;
|
||||||
|
|
||||||
|
bool ignoreFile(QFileInfo file) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const QString root;
|
const QString root;
|
||||||
SeparatorPrefixTree<'/'> blocked;
|
SeparatorPrefixTree<'/'> blocked;
|
||||||
|
QStringList m_ignoreFiles;
|
||||||
|
SeparatorPrefixTree<'/'> m_ignoreFilePaths;
|
||||||
};
|
};
|
||||||
|
@ -372,7 +372,7 @@ void create_link::make_link_list(const QString& offset)
|
|||||||
auto src_path = source_it.next();
|
auto src_path = source_it.next();
|
||||||
auto relative_path = src_dir.relativeFilePath(src_path);
|
auto relative_path = src_dir.relativeFilePath(src_path);
|
||||||
|
|
||||||
if (m_max_depth >= 0 && pathDepth(relative_path) > m_max_depth){
|
if (m_max_depth >= 0 && pathDepth(relative_path) > m_max_depth) {
|
||||||
relative_path = pathTruncate(relative_path, m_max_depth);
|
relative_path = pathTruncate(relative_path, m_max_depth);
|
||||||
src_path = src_dir.filePath(relative_path);
|
src_path = src_dir.filePath(relative_path);
|
||||||
if (linkedPaths.contains(src_path)) {
|
if (linkedPaths.contains(src_path)) {
|
||||||
@ -663,7 +663,7 @@ QString pathTruncate(const QString& path, int depth)
|
|||||||
|
|
||||||
QString trunc = QFileInfo(path).path();
|
QString trunc = QFileInfo(path).path();
|
||||||
|
|
||||||
if (pathDepth(trunc) > depth ) {
|
if (pathDepth(trunc) > depth) {
|
||||||
return pathTruncate(trunc, depth);
|
return pathTruncate(trunc, depth);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -769,6 +769,9 @@ QString getDesktopDir()
|
|||||||
// Cross-platform Shortcut creation
|
// Cross-platform Shortcut creation
|
||||||
bool createShortcut(QString destination, QString target, QStringList args, QString name, QString icon)
|
bool createShortcut(QString destination, QString target, QStringList args, QString name, QString icon)
|
||||||
{
|
{
|
||||||
|
if (destination.isEmpty()) {
|
||||||
|
destination = PathCombine(getDesktopDir(), RemoveInvalidFilenameChars(name));
|
||||||
|
}
|
||||||
#if defined(Q_OS_MACOS)
|
#if defined(Q_OS_MACOS)
|
||||||
destination += ".command";
|
destination += ".command";
|
||||||
|
|
||||||
@ -791,6 +794,8 @@ bool createShortcut(QString destination, QString target, QStringList args, QStri
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
#elif defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
|
#elif defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
|
||||||
|
if (!destination.endsWith(".desktop")) // in case of isFlatpak destination is already populated
|
||||||
|
destination += ".desktop";
|
||||||
QFile f(destination);
|
QFile f(destination);
|
||||||
f.open(QIODevice::WriteOnly | QIODevice::Text);
|
f.open(QIODevice::WriteOnly | QIODevice::Text);
|
||||||
QTextStream stream(&f);
|
QTextStream stream(&f);
|
||||||
@ -974,7 +979,7 @@ FilesystemType getFilesystemType(const QString& name)
|
|||||||
{
|
{
|
||||||
for (auto iter = s_filesystem_type_names.constBegin(); iter != s_filesystem_type_names.constEnd(); ++iter) {
|
for (auto iter = s_filesystem_type_names.constBegin(); iter != s_filesystem_type_names.constEnd(); ++iter) {
|
||||||
auto fs_names = iter.value();
|
auto fs_names = iter.value();
|
||||||
if(fs_names.contains(name.toUpper()))
|
if (fs_names.contains(name.toUpper()))
|
||||||
return iter.key();
|
return iter.key();
|
||||||
}
|
}
|
||||||
return FilesystemType::UNKNOWN;
|
return FilesystemType::UNKNOWN;
|
||||||
|
@ -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;
|
||||||
};
|
};
|
||||||
|
31
launcher/Markdown.cpp
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* Prism Launcher - Minecraft Launcher
|
||||||
|
* Copyright (C) 2023 Joshua Goins <josh@redstrate.com>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, version 3.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "Markdown.h"
|
||||||
|
|
||||||
|
QString markdownToHTML(const QString& markdown)
|
||||||
|
{
|
||||||
|
const QByteArray markdownData = markdown.toUtf8();
|
||||||
|
char* buffer = cmark_markdown_to_html(markdownData.constData(), markdownData.length(), CMARK_OPT_NOBREAKS | CMARK_OPT_UNSAFE);
|
||||||
|
|
||||||
|
QString htmlStr(buffer);
|
||||||
|
|
||||||
|
free(buffer);
|
||||||
|
|
||||||
|
return htmlStr;
|
||||||
|
}
|
@ -21,14 +21,4 @@
|
|||||||
#include <QString>
|
#include <QString>
|
||||||
#include <cmark.h>
|
#include <cmark.h>
|
||||||
|
|
||||||
static QString markdownToHTML(const QString& markdown)
|
QString markdownToHTML(const QString& markdown);
|
||||||
{
|
|
||||||
const QByteArray markdownData = markdown.toUtf8();
|
|
||||||
char* buffer = cmark_markdown_to_html(markdownData.constData(), markdownData.length(), CMARK_OPT_NOBREAKS | CMARK_OPT_UNSAFE);
|
|
||||||
|
|
||||||
QString htmlStr(buffer);
|
|
||||||
|
|
||||||
free(buffer);
|
|
||||||
|
|
||||||
return htmlStr;
|
|
||||||
}
|
|
@ -38,6 +38,8 @@ class ResourceDownloadTask : public SequentialTask {
|
|||||||
const QString& getFilename() const { return m_pack_version.fileName; }
|
const QString& getFilename() const { return m_pack_version.fileName; }
|
||||||
const QString& getCustomPath() const { return m_custom_target_folder; }
|
const QString& getCustomPath() const { return m_custom_target_folder; }
|
||||||
const QVariant& getVersionID() const { return m_pack_version.fileId; }
|
const QVariant& getVersionID() const { return m_pack_version.fileId; }
|
||||||
|
const ModPlatform::IndexedVersion& getVersion() const { return m_pack_version; }
|
||||||
|
const ModPlatform::ResourceProvider& getProvider() const { return m_pack->provider; }
|
||||||
const QString& getName() const { return m_pack->name; }
|
const QString& getName() const { return m_pack->name; }
|
||||||
ModPlatform::IndexedPack::Ptr getPack() { return m_pack; }
|
ModPlatform::IndexedPack::Ptr getPack() { return m_pack; }
|
||||||
|
|
||||||
|
@ -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)
|
||||||
{
|
{
|
||||||
|
@ -81,15 +81,20 @@ void CheckJava::executeTask()
|
|||||||
}
|
}
|
||||||
|
|
||||||
QFileInfo javaInfo(realJavaPath);
|
QFileInfo javaInfo(realJavaPath);
|
||||||
qlonglong javaUnixTime = javaInfo.lastModified().toMSecsSinceEpoch();
|
qint64 javaUnixTime = javaInfo.lastModified().toMSecsSinceEpoch();
|
||||||
auto storedUnixTime = settings->get("JavaTimestamp").toLongLong();
|
auto storedSignature = settings->get("JavaSignature").toString();
|
||||||
auto storedArchitecture = settings->get("JavaArchitecture").toString();
|
auto storedArchitecture = settings->get("JavaArchitecture").toString();
|
||||||
auto storedRealArchitecture = settings->get("JavaRealArchitecture").toString();
|
auto storedRealArchitecture = settings->get("JavaRealArchitecture").toString();
|
||||||
auto storedVersion = settings->get("JavaVersion").toString();
|
auto storedVersion = settings->get("JavaVersion").toString();
|
||||||
auto storedVendor = settings->get("JavaVendor").toString();
|
auto storedVendor = settings->get("JavaVendor").toString();
|
||||||
m_javaUnixTime = javaUnixTime;
|
|
||||||
|
QCryptographicHash hash(QCryptographicHash::Sha1);
|
||||||
|
hash.addData(QByteArray::number(javaUnixTime));
|
||||||
|
hash.addData(m_javaPath.toUtf8());
|
||||||
|
m_javaSignature = hash.result().toHex();
|
||||||
|
|
||||||
// if timestamps are not the same, or something is missing, check!
|
// if timestamps are not the same, or something is missing, check!
|
||||||
if (javaUnixTime != storedUnixTime || storedVersion.size() == 0
|
if (m_javaSignature != storedSignature || storedVersion.size() == 0
|
||||||
|| storedArchitecture.size() == 0 || storedRealArchitecture.size() == 0
|
|| storedArchitecture.size() == 0 || storedRealArchitecture.size() == 0
|
||||||
|| storedVendor.size() == 0)
|
|| storedVendor.size() == 0)
|
||||||
{
|
{
|
||||||
@ -140,7 +145,7 @@ void CheckJava::checkJavaFinished(JavaCheckResult result)
|
|||||||
instance->settings()->set("JavaArchitecture", result.mojangPlatform);
|
instance->settings()->set("JavaArchitecture", result.mojangPlatform);
|
||||||
instance->settings()->set("JavaRealArchitecture", result.realPlatform);
|
instance->settings()->set("JavaRealArchitecture", result.realPlatform);
|
||||||
instance->settings()->set("JavaVendor", result.javaVendor);
|
instance->settings()->set("JavaVendor", result.javaVendor);
|
||||||
instance->settings()->set("JavaTimestamp", m_javaUnixTime);
|
instance->settings()->set("JavaSignature", m_javaSignature);
|
||||||
emitSucceeded();
|
emitSucceeded();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -40,6 +40,6 @@ private:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
QString m_javaPath;
|
QString m_javaPath;
|
||||||
qlonglong m_javaUnixTime;
|
QString m_javaSignature;
|
||||||
JavaCheckerPtr m_JavaChecker;
|
JavaCheckerPtr m_JavaChecker;
|
||||||
};
|
};
|
||||||
|
@ -148,7 +148,7 @@ void MinecraftInstance::loadSpecificSettings()
|
|||||||
m_settings->registerOverride(global_settings->getSetting("IgnoreJavaCompatibility"), javaOrLocation);
|
m_settings->registerOverride(global_settings->getSetting("IgnoreJavaCompatibility"), javaOrLocation);
|
||||||
|
|
||||||
// special!
|
// special!
|
||||||
m_settings->registerPassthrough(global_settings->getSetting("JavaTimestamp"), javaOrLocation);
|
m_settings->registerPassthrough(global_settings->getSetting("JavaSignature"), javaOrLocation);
|
||||||
m_settings->registerPassthrough(global_settings->getSetting("JavaArchitecture"), javaOrLocation);
|
m_settings->registerPassthrough(global_settings->getSetting("JavaArchitecture"), javaOrLocation);
|
||||||
m_settings->registerPassthrough(global_settings->getSetting("JavaRealArchitecture"), javaOrLocation);
|
m_settings->registerPassthrough(global_settings->getSetting("JavaRealArchitecture"), javaOrLocation);
|
||||||
m_settings->registerPassthrough(global_settings->getSetting("JavaVersion"), javaOrLocation);
|
m_settings->registerPassthrough(global_settings->getSetting("JavaVersion"), javaOrLocation);
|
||||||
|
@ -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();
|
||||||
|
@ -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;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
@ -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;
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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);
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
};
|
};
|
||||||
|
@ -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, 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 {};
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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"; }
|
||||||
};
|
};
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
};
|
};
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -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)
|
||||||
};
|
};
|
||||||
|
252
launcher/minecraft/mod/tasks/GetModDependenciesTask.cpp
Normal file
@ -0,0 +1,252 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* Prism Launcher - Minecraft Launcher
|
||||||
|
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, version 3.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "GetModDependenciesTask.h"
|
||||||
|
|
||||||
|
#include <QDebug>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <memory>
|
||||||
|
#include "Json.h"
|
||||||
|
#include "QObjectPtr.h"
|
||||||
|
#include "minecraft/mod/MetadataHandler.h"
|
||||||
|
#include "modplatform/ModIndex.h"
|
||||||
|
#include "modplatform/ResourceAPI.h"
|
||||||
|
#include "modplatform/flame/FlameAPI.h"
|
||||||
|
#include "modplatform/modrinth/ModrinthAPI.h"
|
||||||
|
#include "tasks/ConcurrentTask.h"
|
||||||
|
#include "tasks/SequentialTask.h"
|
||||||
|
#include "ui/pages/modplatform/ModModel.h"
|
||||||
|
#include "ui/pages/modplatform/flame/FlameResourceModels.h"
|
||||||
|
#include "ui/pages/modplatform/modrinth/ModrinthResourceModels.h"
|
||||||
|
|
||||||
|
static Version mcVersion(BaseInstance* inst)
|
||||||
|
{
|
||||||
|
return static_cast<MinecraftInstance*>(inst)->getPackProfile()->getComponent("net.minecraft")->getVersion();
|
||||||
|
}
|
||||||
|
|
||||||
|
static ResourceAPI::ModLoaderTypes mcLoaders(BaseInstance* inst)
|
||||||
|
{
|
||||||
|
return static_cast<MinecraftInstance*>(inst)->getPackProfile()->getModLoaders().value();
|
||||||
|
}
|
||||||
|
|
||||||
|
GetModDependenciesTask::GetModDependenciesTask(QObject* parent,
|
||||||
|
BaseInstance* instance,
|
||||||
|
ModFolderModel* folder,
|
||||||
|
QList<std::shared_ptr<PackDependency>> selected)
|
||||||
|
: SequentialTask(parent, tr("Get dependencies"))
|
||||||
|
, m_selected(selected)
|
||||||
|
, m_flame_provider{ ModPlatform::ResourceProvider::FLAME, std::make_shared<ResourceDownload::FlameModModel>(*instance),
|
||||||
|
std::make_shared<FlameAPI>() }
|
||||||
|
, m_modrinth_provider{ ModPlatform::ResourceProvider::MODRINTH, std::make_shared<ResourceDownload::ModrinthModModel>(*instance),
|
||||||
|
std::make_shared<ModrinthAPI>() }
|
||||||
|
, m_version(mcVersion(instance))
|
||||||
|
, m_loaderType(mcLoaders(instance))
|
||||||
|
{
|
||||||
|
for (auto mod : folder->allMods())
|
||||||
|
if (auto meta = mod->metadata(); meta)
|
||||||
|
m_mods.append(meta);
|
||||||
|
prepare();
|
||||||
|
};
|
||||||
|
|
||||||
|
void GetModDependenciesTask::prepare()
|
||||||
|
{
|
||||||
|
for (auto sel : m_selected) {
|
||||||
|
for (auto dep : getDependenciesForVersion(sel->version, sel->pack->provider)) {
|
||||||
|
addTask(prepareDependencyTask(dep, sel->pack->provider, 20));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ModPlatform::Dependency GetModDependenciesTask::getOverride(const ModPlatform::Dependency& dep,
|
||||||
|
const ModPlatform::ResourceProvider providerName)
|
||||||
|
{
|
||||||
|
if (auto isQuilt = m_loaderType & ResourceAPI::Quilt; isQuilt || m_loaderType & ResourceAPI::Fabric) {
|
||||||
|
auto overide = ModPlatform::getOverrideDeps();
|
||||||
|
auto over = std::find_if(overide.cbegin(), overide.cend(), [dep, providerName, isQuilt](auto o) {
|
||||||
|
return o.provider == providerName && dep.addonId == (isQuilt ? o.fabric : o.quilt);
|
||||||
|
});
|
||||||
|
if (over != overide.cend()) {
|
||||||
|
return { isQuilt ? over->quilt : over->fabric, dep.type };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dep;
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<ModPlatform::Dependency> GetModDependenciesTask::getDependenciesForVersion(const ModPlatform::IndexedVersion& version,
|
||||||
|
const ModPlatform::ResourceProvider providerName)
|
||||||
|
{
|
||||||
|
QList<ModPlatform::Dependency> c_dependencies;
|
||||||
|
for (auto ver_dep : version.dependencies) {
|
||||||
|
if (ver_dep.type != ModPlatform::DependencyType::REQUIRED)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
auto isOnlyVersion = providerName == ModPlatform::ResourceProvider::MODRINTH && ver_dep.addonId.toString().isEmpty();
|
||||||
|
if (auto dep = std::find_if(c_dependencies.begin(), c_dependencies.end(),
|
||||||
|
[&ver_dep, isOnlyVersion](const ModPlatform::Dependency& i) {
|
||||||
|
return isOnlyVersion ? i.version == ver_dep.version : i.addonId == ver_dep.addonId;
|
||||||
|
});
|
||||||
|
dep != c_dependencies.end())
|
||||||
|
continue; // check the current dependency list
|
||||||
|
|
||||||
|
if (auto dep = std::find_if(m_selected.begin(), m_selected.end(),
|
||||||
|
[&ver_dep, providerName, isOnlyVersion](std::shared_ptr<PackDependency> i) {
|
||||||
|
return i->pack->provider == providerName && (isOnlyVersion ? i->version.version == ver_dep.version
|
||||||
|
: i->pack->addonId == ver_dep.addonId);
|
||||||
|
});
|
||||||
|
dep != m_selected.end())
|
||||||
|
continue; // check the selected versions
|
||||||
|
|
||||||
|
if (auto dep = std::find_if(m_mods.begin(), m_mods.end(),
|
||||||
|
[&ver_dep, providerName, isOnlyVersion](std::shared_ptr<Metadata::ModStruct> i) {
|
||||||
|
return i->provider == providerName &&
|
||||||
|
(isOnlyVersion ? i->file_id == ver_dep.version : i->project_id == ver_dep.addonId);
|
||||||
|
});
|
||||||
|
dep != m_mods.end())
|
||||||
|
continue; // check the existing mods
|
||||||
|
|
||||||
|
if (auto dep = std::find_if(m_pack_dependencies.begin(), m_pack_dependencies.end(),
|
||||||
|
[&ver_dep, providerName, isOnlyVersion](std::shared_ptr<PackDependency> i) {
|
||||||
|
return i->pack->provider == providerName && (isOnlyVersion ? i->version.version == ver_dep.addonId
|
||||||
|
: i->pack->addonId == ver_dep.addonId);
|
||||||
|
});
|
||||||
|
dep != m_pack_dependencies.end()) // check loaded dependencies
|
||||||
|
continue;
|
||||||
|
|
||||||
|
c_dependencies.append(getOverride(ver_dep, providerName));
|
||||||
|
}
|
||||||
|
return c_dependencies;
|
||||||
|
};
|
||||||
|
|
||||||
|
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 responseInfo = std::make_shared<QByteArray>();
|
||||||
|
auto info = provider.api->getProject(pDep->pack->addonId.toString(), responseInfo);
|
||||||
|
QObject::connect(info.get(), &NetJob::succeeded, [responseInfo, provider, pDep] {
|
||||||
|
QJsonParseError parse_error{};
|
||||||
|
QJsonDocument doc = QJsonDocument::fromJson(*responseInfo, &parse_error);
|
||||||
|
if (parse_error.error != QJsonParseError::NoError) {
|
||||||
|
qWarning() << "Error while parsing JSON response for mod info at " << parse_error.offset
|
||||||
|
<< " reason: " << parse_error.errorString();
|
||||||
|
qDebug() << *responseInfo;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
auto obj = provider.name == ModPlatform::ResourceProvider::FLAME ? Json::requireObject(Json::requireObject(doc), "data")
|
||||||
|
: Json::requireObject(doc);
|
||||||
|
provider.mod->loadIndexedPack(*pDep->pack, obj);
|
||||||
|
} catch (const JSONValidationError& e) {
|
||||||
|
qDebug() << doc;
|
||||||
|
qWarning() << "Error while reading mod info: " << e.cause();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
Task::Ptr GetModDependenciesTask::prepareDependencyTask(const ModPlatform::Dependency& dep,
|
||||||
|
const ModPlatform::ResourceProvider providerName,
|
||||||
|
int level)
|
||||||
|
{
|
||||||
|
auto pDep = std::make_shared<PackDependency>();
|
||||||
|
pDep->dependency = dep;
|
||||||
|
pDep->pack = std::make_shared<ModPlatform::IndexedPack>();
|
||||||
|
pDep->pack->addonId = dep.addonId;
|
||||||
|
pDep->pack->provider = providerName;
|
||||||
|
|
||||||
|
m_pack_dependencies.append(pDep);
|
||||||
|
auto provider = providerName == m_flame_provider.name ? m_flame_provider : m_modrinth_provider;
|
||||||
|
|
||||||
|
auto tasks = makeShared<SequentialTask>(
|
||||||
|
this, QString("DependencyInfo: %1").arg(dep.addonId.toString().isEmpty() ? dep.version : dep.addonId.toString()));
|
||||||
|
|
||||||
|
if (!dep.addonId.toString().isEmpty()) {
|
||||||
|
tasks->addTask(getProjectInfoTask(pDep));
|
||||||
|
}
|
||||||
|
|
||||||
|
ResourceAPI::DependencySearchArgs args = { dep, m_version, m_loaderType };
|
||||||
|
ResourceAPI::DependencySearchCallbacks callbacks;
|
||||||
|
|
||||||
|
callbacks.on_succeed = [dep, provider, pDep, level, this](auto& doc, auto& pack) {
|
||||||
|
try {
|
||||||
|
QJsonArray arr;
|
||||||
|
if (dep.version.length() != 0 && doc.isObject()) {
|
||||||
|
arr.append(doc.object());
|
||||||
|
} else {
|
||||||
|
arr = doc.isObject() ? Json::ensureArray(doc.object(), "data") : doc.array();
|
||||||
|
}
|
||||||
|
pDep->version = provider.mod->loadDependencyVersions(dep, arr);
|
||||||
|
if (!pDep->version.addonId.isValid()) {
|
||||||
|
if (m_loaderType & ResourceAPI::Quilt) { // falback for quilt
|
||||||
|
auto overide = ModPlatform::getOverrideDeps();
|
||||||
|
auto over = std::find_if(overide.cbegin(), overide.cend(),
|
||||||
|
[dep, provider](auto o) { return o.provider == provider.name && dep.addonId == o.quilt; });
|
||||||
|
if (over != overide.cend()) {
|
||||||
|
removePack(dep.addonId);
|
||||||
|
addTask(prepareDependencyTask({ over->fabric, dep.type }, provider.name, level));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
qWarning() << "Error while reading mod version empty ";
|
||||||
|
qDebug() << doc;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
pDep->version.is_currently_selected = true;
|
||||||
|
pDep->pack->versions = { pDep->version };
|
||||||
|
pDep->pack->versionsLoaded = true;
|
||||||
|
|
||||||
|
} catch (const JSONValidationError& e) {
|
||||||
|
qDebug() << doc;
|
||||||
|
qWarning() << "Error while reading mod version: " << e.cause();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (level == 0) {
|
||||||
|
qWarning() << "Dependency cycle exeeded";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (dep.addonId.toString().isEmpty() && !pDep->version.addonId.toString().isEmpty()) {
|
||||||
|
pDep->pack->addonId = pDep->version.addonId;
|
||||||
|
auto dep = getOverride({ pDep->version.addonId, pDep->dependency.type }, provider.name);
|
||||||
|
if (dep.addonId != pDep->version.addonId) {
|
||||||
|
removePack(pDep->version.addonId);
|
||||||
|
addTask(prepareDependencyTask(dep, provider.name, level));
|
||||||
|
} else
|
||||||
|
addTask(getProjectInfoTask(pDep));
|
||||||
|
}
|
||||||
|
for (auto dep : getDependenciesForVersion(pDep->version, provider.name)) {
|
||||||
|
addTask(prepareDependencyTask(dep, provider.name, level - 1));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
auto version = provider.api->getDependencyVersion(std::move(args), std::move(callbacks));
|
||||||
|
tasks->addTask(version);
|
||||||
|
return tasks;
|
||||||
|
};
|
||||||
|
|
||||||
|
void GetModDependenciesTask::removePack(const QVariant addonId)
|
||||||
|
{
|
||||||
|
auto pred = [addonId](const std::shared_ptr<PackDependency>& v) { return v->pack->addonId == addonId; };
|
||||||
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 1, 0)
|
||||||
|
m_pack_dependencies.removeIf(pred);
|
||||||
|
#else
|
||||||
|
for (auto it = m_pack_dependencies.begin(); it != m_pack_dependencies.end();)
|
||||||
|
if (pred(*it))
|
||||||
|
it = m_pack_dependencies.erase(it);
|
||||||
|
else
|
||||||
|
++it;
|
||||||
|
#endif
|
||||||
|
}
|
84
launcher/minecraft/mod/tasks/GetModDependenciesTask.h
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* Prism Launcher - Minecraft Launcher
|
||||||
|
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, version 3.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QDir>
|
||||||
|
#include <QEventLoop>
|
||||||
|
#include <QList>
|
||||||
|
#include <QVariant>
|
||||||
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "minecraft/mod/MetadataHandler.h"
|
||||||
|
#include "minecraft/mod/ModFolderModel.h"
|
||||||
|
#include "modplatform/ModIndex.h"
|
||||||
|
#include "modplatform/ResourceAPI.h"
|
||||||
|
#include "tasks/SequentialTask.h"
|
||||||
|
#include "tasks/Task.h"
|
||||||
|
#include "ui/pages/modplatform/ModModel.h"
|
||||||
|
|
||||||
|
class GetModDependenciesTask : public SequentialTask {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
using Ptr = shared_qobject_ptr<GetModDependenciesTask>;
|
||||||
|
|
||||||
|
struct PackDependency {
|
||||||
|
ModPlatform::Dependency dependency;
|
||||||
|
ModPlatform::IndexedPack::Ptr pack;
|
||||||
|
ModPlatform::IndexedVersion version;
|
||||||
|
PackDependency() = default;
|
||||||
|
PackDependency(const ModPlatform::IndexedPack::Ptr p, const ModPlatform::IndexedVersion& v)
|
||||||
|
{
|
||||||
|
pack = p;
|
||||||
|
version = v;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Provider {
|
||||||
|
ModPlatform::ResourceProvider name;
|
||||||
|
std::shared_ptr<ResourceDownload::ModModel> mod;
|
||||||
|
std::shared_ptr<ResourceAPI> api;
|
||||||
|
};
|
||||||
|
|
||||||
|
explicit GetModDependenciesTask(QObject* parent,
|
||||||
|
BaseInstance* instance,
|
||||||
|
ModFolderModel* folder,
|
||||||
|
QList<std::shared_ptr<PackDependency>> selected);
|
||||||
|
|
||||||
|
auto getDependecies() const -> QList<std::shared_ptr<PackDependency>> { return m_pack_dependencies; }
|
||||||
|
|
||||||
|
protected slots:
|
||||||
|
Task::Ptr prepareDependencyTask(const ModPlatform::Dependency&, const ModPlatform::ResourceProvider, int);
|
||||||
|
QList<ModPlatform::Dependency> getDependenciesForVersion(const ModPlatform::IndexedVersion&,
|
||||||
|
const ModPlatform::ResourceProvider providerName);
|
||||||
|
void prepare();
|
||||||
|
Task::Ptr getProjectInfoTask(std::shared_ptr<PackDependency> pDep);
|
||||||
|
ModPlatform::Dependency getOverride(const ModPlatform::Dependency&, const ModPlatform::ResourceProvider providerName);
|
||||||
|
void removePack(const QVariant addonId);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QList<std::shared_ptr<PackDependency>> m_pack_dependencies;
|
||||||
|
QList<std::shared_ptr<Metadata::ModStruct>> m_mods;
|
||||||
|
QList<std::shared_ptr<PackDependency>> m_selected;
|
||||||
|
Provider m_flame_provider;
|
||||||
|
Provider m_modrinth_provider;
|
||||||
|
|
||||||
|
Version m_version;
|
||||||
|
ResourceAPI::ModLoaderTypes m_loaderType;
|
||||||
|
};
|
@ -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)
|
||||||
|
@ -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 {
|
||||||
|
@ -1,25 +1,24 @@
|
|||||||
// 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) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||||
*
|
*
|
||||||
* 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
|
||||||
* the Free Software Foundation, version 3.
|
* the Free Software Foundation, version 3.
|
||||||
*
|
*
|
||||||
* This program is distributed in the hope that it will be useful,
|
* This program is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "LocalModUpdateTask.h"
|
#include "LocalModUpdateTask.h"
|
||||||
|
|
||||||
#include "Application.h"
|
|
||||||
#include "FileSystem.h"
|
#include "FileSystem.h"
|
||||||
#include "minecraft/mod/MetadataHandler.h"
|
#include "minecraft/mod/MetadataHandler.h"
|
||||||
|
|
||||||
|
@ -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 };
|
||||||
|
@ -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);
|
||||||
|
@ -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 };
|
||||||
|
@ -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);
|
||||||
|
@ -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{};
|
||||||
|
@ -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
|
||||||
@ -33,6 +34,8 @@ enum class ResourceProvider { MODRINTH, FLAME };
|
|||||||
|
|
||||||
enum class ResourceType { MOD, RESOURCE_PACK, SHADER_PACK };
|
enum class ResourceType { MOD, RESOURCE_PACK, SHADER_PACK };
|
||||||
|
|
||||||
|
enum class DependencyType { REQUIRED, OPTIONAL, INCOMPATIBLE, EMBEDDED, TOOL, INCLUDE, UNKNOWN };
|
||||||
|
|
||||||
class ProviderCapabilities {
|
class ProviderCapabilities {
|
||||||
public:
|
public:
|
||||||
auto name(ResourceProvider) -> const char*;
|
auto name(ResourceProvider) -> const char*;
|
||||||
@ -52,6 +55,12 @@ struct DonationData {
|
|||||||
QString url;
|
QString url;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct Dependency {
|
||||||
|
QVariant addonId;
|
||||||
|
DependencyType type;
|
||||||
|
QString version;
|
||||||
|
};
|
||||||
|
|
||||||
struct IndexedVersion {
|
struct IndexedVersion {
|
||||||
QVariant addonId;
|
QVariant addonId;
|
||||||
QVariant fileId;
|
QVariant fileId;
|
||||||
@ -66,6 +75,7 @@ struct IndexedVersion {
|
|||||||
QString hash;
|
QString hash;
|
||||||
bool is_preferred = true;
|
bool is_preferred = true;
|
||||||
QString changelog;
|
QString changelog;
|
||||||
|
QList<Dependency> dependencies;
|
||||||
|
|
||||||
// For internal use, not provided by APIs
|
// For internal use, not provided by APIs
|
||||||
bool is_currently_selected = false;
|
bool is_currently_selected = false;
|
||||||
@ -119,6 +129,22 @@ struct IndexedPack {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct OverrideDep {
|
||||||
|
QString quilt;
|
||||||
|
QString fabric;
|
||||||
|
QString slug;
|
||||||
|
ModPlatform::ResourceProvider provider;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline auto getOverrideDeps() -> QList<OverrideDep>
|
||||||
|
{
|
||||||
|
return { { "634179", "306612", "API", ModPlatform::ResourceProvider::FLAME },
|
||||||
|
{ "720410", "308769", "KotlinLibraries", ModPlatform::ResourceProvider::FLAME },
|
||||||
|
|
||||||
|
{ "qvIfYCYJ", "P7dR8mSH", "API", ModPlatform::ResourceProvider::MODRINTH },
|
||||||
|
{ "lwVhp9o5", "Ha28R6CL", "KotlinLibraries", ModPlatform::ResourceProvider::MODRINTH } };
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace ModPlatform
|
} // namespace ModPlatform
|
||||||
|
|
||||||
Q_DECLARE_METATYPE(ModPlatform::IndexedPack)
|
Q_DECLARE_METATYPE(ModPlatform::IndexedPack)
|
||||||
|
@ -111,6 +111,16 @@ class ResourceAPI {
|
|||||||
std::function<void(QJsonDocument&, ModPlatform::IndexedPack)> on_succeed;
|
std::function<void(QJsonDocument&, ModPlatform::IndexedPack)> on_succeed;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct DependencySearchArgs {
|
||||||
|
ModPlatform::Dependency dependency;
|
||||||
|
Version mcVersion;
|
||||||
|
ModLoaderTypes loader;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DependencySearchCallbacks {
|
||||||
|
std::function<void(QJsonDocument&, const ModPlatform::Dependency&)> on_succeed;
|
||||||
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/** Gets a list of available sorting methods for this API. */
|
/** Gets a list of available sorting methods for this API. */
|
||||||
[[nodiscard]] virtual auto getSortingMethods() const -> QList<SortingMethod> = 0;
|
[[nodiscard]] virtual auto getSortingMethods() const -> QList<SortingMethod> = 0;
|
||||||
@ -121,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;
|
||||||
@ -143,6 +153,12 @@ class ResourceAPI {
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] virtual Task::Ptr getDependencyVersion(DependencySearchArgs&&, DependencySearchCallbacks&&) const
|
||||||
|
{
|
||||||
|
qWarning() << "TODO";
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
static auto getModLoaderString(ModLoaderType type) -> const QString
|
static auto getModLoaderString(ModLoaderType type) -> const QString
|
||||||
{
|
{
|
||||||
switch (type) {
|
switch (type) {
|
||||||
|
@ -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();
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
|
@ -4,7 +4,10 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <memory>
|
||||||
#include "modplatform/ModIndex.h"
|
#include "modplatform/ModIndex.h"
|
||||||
|
#include "modplatform/ResourceAPI.h"
|
||||||
#include "modplatform/helpers/NetworkResourceAPI.h"
|
#include "modplatform/helpers/NetworkResourceAPI.h"
|
||||||
|
|
||||||
class FlameAPI : public NetworkResourceAPI {
|
class FlameAPI : public NetworkResourceAPI {
|
||||||
@ -14,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;
|
||||||
|
|
||||||
@ -41,14 +44,15 @@ class FlameAPI : public NetworkResourceAPI {
|
|||||||
return 4;
|
return 4;
|
||||||
// TODO: remove this once Quilt drops official Fabric support
|
// TODO: remove this once Quilt drops official Fabric support
|
||||||
if (loaders & Quilt) // NOTE: Most if not all Fabric mods should work *currently*
|
if (loaders & Quilt) // NOTE: Most if not all Fabric mods should work *currently*
|
||||||
return 4; // Quilt would probably be 5
|
return 4; // Quilt would probably be 5
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
[[nodiscard]] std::optional<QString> getSearchURL(SearchArgs const& args) const override
|
[[nodiscard]] std::optional<QString> getSearchURL(SearchArgs const& args) const override
|
||||||
{
|
{
|
||||||
auto gameVersionStr = args.versions.has_value() ? QString("gameVersion=%1").arg(args.versions.value().front().toString()) : QString();
|
auto gameVersionStr =
|
||||||
|
args.versions.has_value() ? QString("gameVersion=%1").arg(args.versions.value().front().toString()) : QString();
|
||||||
|
|
||||||
QStringList get_arguments;
|
QStringList get_arguments;
|
||||||
get_arguments.append(QString("classId=%1").arg(getClassId(args.type)));
|
get_arguments.append(QString("classId=%1").arg(getClassId(args.type)));
|
||||||
@ -73,14 +77,48 @@ class FlameAPI : public NetworkResourceAPI {
|
|||||||
|
|
||||||
[[nodiscard]] std::optional<QString> getVersionsURL(VersionSearchArgs const& args) const override
|
[[nodiscard]] std::optional<QString> getVersionsURL(VersionSearchArgs const& args) const override
|
||||||
{
|
{
|
||||||
QString url{QString("https://api.curseforge.com/v1/mods/%1/files?pageSize=10000&").arg(args.pack.addonId.toString())};
|
auto addonId = args.pack.addonId.toString();
|
||||||
|
QString url{ QString("https://api.curseforge.com/v1/mods/%1/files?pageSize=10000&").arg(addonId) };
|
||||||
|
|
||||||
QStringList get_parameters;
|
QStringList get_parameters;
|
||||||
if (args.mcVersions.has_value())
|
if (args.mcVersions.has_value())
|
||||||
get_parameters.append(QString("gameVersion=%1").arg(args.mcVersions.value().front().toString()));
|
get_parameters.append(QString("gameVersion=%1").arg(args.mcVersions.value().front().toString()));
|
||||||
if (args.loaders.has_value())
|
|
||||||
get_parameters.append(QString("modLoaderType=%1").arg(getMappedModLoader(args.loaders.value())));
|
if (args.loaders.has_value()) {
|
||||||
|
int mappedModLoader = getMappedModLoader(args.loaders.value());
|
||||||
|
|
||||||
|
if (args.loaders.value() & Quilt) {
|
||||||
|
auto overide = ModPlatform::getOverrideDeps();
|
||||||
|
auto over = std::find_if(overide.cbegin(), overide.cend(), [addonId](auto dep) {
|
||||||
|
return dep.provider == ModPlatform::ResourceProvider::FLAME && addonId == dep.quilt;
|
||||||
|
});
|
||||||
|
if (over != overide.cend()) {
|
||||||
|
mappedModLoader = 5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get_parameters.append(QString("modLoaderType=%1").arg(mappedModLoader));
|
||||||
|
}
|
||||||
|
|
||||||
return url + get_parameters.join('&');
|
return url + get_parameters.join('&');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] std::optional<QString> getDependencyURL(DependencySearchArgs const& args) const override
|
||||||
|
{
|
||||||
|
auto mappedModLoader = getMappedModLoader(args.loader);
|
||||||
|
auto addonId = args.dependency.addonId.toString();
|
||||||
|
if (args.loader & Quilt) {
|
||||||
|
auto overide = ModPlatform::getOverrideDeps();
|
||||||
|
auto over = std::find_if(overide.cbegin(), overide.cend(), [addonId](auto dep) {
|
||||||
|
return dep.provider == ModPlatform::ResourceProvider::FLAME && addonId == dep.quilt;
|
||||||
|
});
|
||||||
|
if (over != overide.cend()) {
|
||||||
|
mappedModLoader = 5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return QString("https://api.curseforge.com/v1/mods/%1/files?pageSize=10000&gameVersion=%2&modLoaderType=%3")
|
||||||
|
.arg(addonId)
|
||||||
|
.arg(args.mcVersion.toString())
|
||||||
|
.arg(mappedModLoader);
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
@ -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);
|
||||||
|
@ -182,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;
|
||||||
|
@ -39,15 +39,15 @@ void FlameMod::loadURLs(ModPlatform::IndexedPack& pack, QJsonObject& obj)
|
|||||||
auto links_obj = Json::ensureObject(obj, "links");
|
auto links_obj = Json::ensureObject(obj, "links");
|
||||||
|
|
||||||
pack.extraData.issuesUrl = Json::ensureString(links_obj, "issuesUrl");
|
pack.extraData.issuesUrl = Json::ensureString(links_obj, "issuesUrl");
|
||||||
if(pack.extraData.issuesUrl.endsWith('/'))
|
if (pack.extraData.issuesUrl.endsWith('/'))
|
||||||
pack.extraData.issuesUrl.chop(1);
|
pack.extraData.issuesUrl.chop(1);
|
||||||
|
|
||||||
pack.extraData.sourceUrl = Json::ensureString(links_obj, "sourceUrl");
|
pack.extraData.sourceUrl = Json::ensureString(links_obj, "sourceUrl");
|
||||||
if(pack.extraData.sourceUrl.endsWith('/'))
|
if (pack.extraData.sourceUrl.endsWith('/'))
|
||||||
pack.extraData.sourceUrl.chop(1);
|
pack.extraData.sourceUrl.chop(1);
|
||||||
|
|
||||||
pack.extraData.wikiUrl = Json::ensureString(links_obj, "wikiUrl");
|
pack.extraData.wikiUrl = Json::ensureString(links_obj, "wikiUrl");
|
||||||
if(pack.extraData.wikiUrl.endsWith('/'))
|
if (pack.extraData.wikiUrl.endsWith('/'))
|
||||||
pack.extraData.wikiUrl.chop(1);
|
pack.extraData.wikiUrl.chop(1);
|
||||||
|
|
||||||
if (!pack.extraData.body.isEmpty())
|
if (!pack.extraData.body.isEmpty())
|
||||||
@ -56,7 +56,7 @@ void FlameMod::loadURLs(ModPlatform::IndexedPack& pack, QJsonObject& obj)
|
|||||||
|
|
||||||
void FlameMod::loadBody(ModPlatform::IndexedPack& pack, QJsonObject& obj)
|
void FlameMod::loadBody(ModPlatform::IndexedPack& pack, QJsonObject& obj)
|
||||||
{
|
{
|
||||||
pack.extraData.body = api.getModDescription(pack.addonId.toInt());
|
pack.extraData.body = api.getModDescription(pack.addonId.toInt());
|
||||||
|
|
||||||
if (!pack.extraData.issuesUrl.isEmpty() || !pack.extraData.sourceUrl.isEmpty() || !pack.extraData.wikiUrl.isEmpty())
|
if (!pack.extraData.issuesUrl.isEmpty() || !pack.extraData.sourceUrl.isEmpty() || !pack.extraData.wikiUrl.isEmpty())
|
||||||
pack.extraDataLoaded = true;
|
pack.extraDataLoaded = true;
|
||||||
@ -64,12 +64,12 @@ void FlameMod::loadBody(ModPlatform::IndexedPack& pack, QJsonObject& obj)
|
|||||||
|
|
||||||
static QString enumToString(int hash_algorithm)
|
static QString enumToString(int hash_algorithm)
|
||||||
{
|
{
|
||||||
switch(hash_algorithm){
|
switch (hash_algorithm) {
|
||||||
default:
|
default:
|
||||||
case 1:
|
case 1:
|
||||||
return "sha1";
|
return "sha1";
|
||||||
case 2:
|
case 2:
|
||||||
return "md5";
|
return "md5";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,10 +86,10 @@ void FlameMod::loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
|
|||||||
auto obj = versionIter.toObject();
|
auto obj = versionIter.toObject();
|
||||||
|
|
||||||
auto file = loadIndexedPackVersion(obj);
|
auto file = loadIndexedPackVersion(obj);
|
||||||
if(!file.addonId.isValid())
|
if (!file.addonId.isValid())
|
||||||
file.addonId = pack.addonId;
|
file.addonId = pack.addonId;
|
||||||
|
|
||||||
if(file.fileId.isValid()) // Heuristic to check if the returned value is valid
|
if (file.fileId.isValid()) // Heuristic to check if the returned value is valid
|
||||||
unsortedVersions.append(file);
|
unsortedVersions.append(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,8 +136,61 @@ auto FlameMod::loadIndexedPackVersion(QJsonObject& obj, bool load_changelog) ->
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(load_changelog)
|
auto dependencies = Json::ensureArray(obj, "dependencies");
|
||||||
|
for (auto d : dependencies) {
|
||||||
|
auto dep = Json::ensureObject(d);
|
||||||
|
ModPlatform::Dependency dependency;
|
||||||
|
dependency.addonId = Json::requireInteger(dep, "modId");
|
||||||
|
switch (Json::requireInteger(dep, "relationType")) {
|
||||||
|
case 1: // EmbeddedLibrary
|
||||||
|
dependency.type = ModPlatform::DependencyType::EMBEDDED;
|
||||||
|
break;
|
||||||
|
case 2: // OptionalDependency
|
||||||
|
dependency.type = ModPlatform::DependencyType::OPTIONAL;
|
||||||
|
break;
|
||||||
|
case 3: // RequiredDependency
|
||||||
|
dependency.type = ModPlatform::DependencyType::REQUIRED;
|
||||||
|
break;
|
||||||
|
case 4: // Tool
|
||||||
|
dependency.type = ModPlatform::DependencyType::TOOL;
|
||||||
|
break;
|
||||||
|
case 5: // Incompatible
|
||||||
|
dependency.type = ModPlatform::DependencyType::INCOMPATIBLE;
|
||||||
|
break;
|
||||||
|
case 6: // Include
|
||||||
|
dependency.type = ModPlatform::DependencyType::INCLUDE;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
dependency.type = ModPlatform::DependencyType::UNKNOWN;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
file.dependencies.append(dependency);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (load_changelog)
|
||||||
file.changelog = api.getModFileChangelog(file.addonId.toInt(), file.fileId.toInt());
|
file.changelog = api.getModFileChangelog(file.addonId.toInt(), file.fileId.toInt());
|
||||||
|
|
||||||
return file;
|
return file;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ModPlatform::IndexedVersion FlameMod::loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr)
|
||||||
|
{
|
||||||
|
QVector<ModPlatform::IndexedVersion> versions;
|
||||||
|
for (auto versionIter : arr) {
|
||||||
|
auto obj = versionIter.toObject();
|
||||||
|
|
||||||
|
auto file = loadIndexedPackVersion(obj);
|
||||||
|
if (!file.addonId.isValid())
|
||||||
|
file.addonId = m.addonId;
|
||||||
|
|
||||||
|
if (file.fileId.isValid()) // Heuristic to check if the returned value is valid
|
||||||
|
versions.append(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto orderSortPredicate = [](const ModPlatform::IndexedVersion& a, const ModPlatform::IndexedVersion& b) -> bool {
|
||||||
|
// dates are in RFC 3339 format
|
||||||
|
return a.date > b.date;
|
||||||
|
};
|
||||||
|
std::sort(versions.begin(), versions.end(), orderSortPredicate);
|
||||||
|
return versions.front();
|
||||||
|
};
|
||||||
|
@ -6,8 +6,8 @@
|
|||||||
|
|
||||||
#include "modplatform/ModIndex.h"
|
#include "modplatform/ModIndex.h"
|
||||||
|
|
||||||
#include "BaseInstance.h"
|
|
||||||
#include <QNetworkAccessManager>
|
#include <QNetworkAccessManager>
|
||||||
|
#include "BaseInstance.h"
|
||||||
|
|
||||||
namespace FlameMod {
|
namespace FlameMod {
|
||||||
|
|
||||||
@ -19,5 +19,5 @@ void loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
|
|||||||
const shared_qobject_ptr<QNetworkAccessManager>& network,
|
const shared_qobject_ptr<QNetworkAccessManager>& network,
|
||||||
const BaseInstance* inst);
|
const BaseInstance* inst);
|
||||||
auto loadIndexedPackVersion(QJsonObject& obj, bool load_changelog = false) -> ModPlatform::IndexedVersion;
|
auto loadIndexedPackVersion(QJsonObject& obj, bool load_changelog = false) -> ModPlatform::IndexedVersion;
|
||||||
|
auto loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr) -> ModPlatform::IndexedVersion;
|
||||||
} // namespace FlameMod
|
} // namespace FlameMod
|
@ -4,6 +4,7 @@
|
|||||||
#include <QMetaType>
|
#include <QMetaType>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QVector>
|
#include <QVector>
|
||||||
|
#include "modplatform/ModIndex.h"
|
||||||
|
|
||||||
namespace Flame {
|
namespace Flame {
|
||||||
|
|
||||||
@ -27,8 +28,7 @@ struct ModpackExtra {
|
|||||||
QString sourceUrl;
|
QString sourceUrl;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct IndexedPack
|
struct IndexedPack {
|
||||||
{
|
|
||||||
int addonId;
|
int addonId;
|
||||||
QString name;
|
QString name;
|
||||||
QString description;
|
QString description;
|
||||||
@ -43,9 +43,9 @@ struct IndexedPack
|
|||||||
ModpackExtra extra;
|
ModpackExtra extra;
|
||||||
};
|
};
|
||||||
|
|
||||||
void loadIndexedPack(IndexedPack & m, QJsonObject & obj);
|
void loadIndexedPack(IndexedPack& m, QJsonObject& obj);
|
||||||
void loadIndexedInfo(IndexedPack&, QJsonObject&);
|
void loadIndexedInfo(IndexedPack&, QJsonObject&);
|
||||||
void loadIndexedPackVersions(IndexedPack & m, QJsonArray & arr);
|
void loadIndexedPackVersions(IndexedPack& m, QJsonArray& arr);
|
||||||
}
|
} // namespace Flame
|
||||||
|
|
||||||
Q_DECLARE_METATYPE(Flame::IndexedPack)
|
Q_DECLARE_METATYPE(Flame::IndexedPack)
|
||||||
|
@ -71,6 +71,7 @@ void ModrinthHasher::executeTask()
|
|||||||
emitFailed("Empty hash!");
|
emitFailed("Empty hash!");
|
||||||
} else {
|
} else {
|
||||||
emitSucceeded();
|
emitSucceeded();
|
||||||
|
emit resultsReady(m_hash);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,12 +89,12 @@ void FlameHasher::executeTask()
|
|||||||
emitFailed("Empty hash!");
|
emitFailed("Empty hash!");
|
||||||
} else {
|
} else {
|
||||||
emitSucceeded();
|
emitSucceeded();
|
||||||
|
emit resultsReady(m_hash);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
}
|
}
|
||||||
@ -120,14 +121,17 @@ void BlockedModHasher::executeTask()
|
|||||||
emitFailed("Empty hash!");
|
emitFailed("Empty hash!");
|
||||||
} else {
|
} else {
|
||||||
emitSucceeded();
|
emitSucceeded();
|
||||||
|
emit resultsReady(m_hash);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
|
@ -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;
|
||||||
|
@ -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,12 +20,12 @@ 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));
|
||||||
|
|
||||||
QObject::connect(netJob.get(), &NetJob::succeeded, [this, response, callbacks]{
|
QObject::connect(netJob.get(), &NetJob::succeeded, [this, response, callbacks] {
|
||||||
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) {
|
||||||
@ -40,27 +41,21 @@ Task::Ptr NetworkResourceAPI::searchProjects(SearchArgs&& args, SearchCallbacks&
|
|||||||
callbacks.on_succeed(doc);
|
callbacks.on_succeed(doc);
|
||||||
});
|
});
|
||||||
|
|
||||||
QObject::connect(netJob.get(), &NetJob::failed, [&netJob, callbacks](QString reason){
|
QObject::connect(netJob.get(), &NetJob::failed, [&netJob, callbacks](QString reason) {
|
||||||
int network_error_code = -1;
|
int network_error_code = -1;
|
||||||
if (auto* failed_action = netJob->getFailedActions().at(0); failed_action && failed_action->m_reply)
|
if (auto* failed_action = netJob->getFailedActions().at(0); failed_action && failed_action->m_reply)
|
||||||
network_error_code = failed_action->m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
network_error_code = failed_action->m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||||
|
|
||||||
callbacks.on_fail(reason, network_error_code);
|
callbacks.on_fail(reason, network_error_code);
|
||||||
});
|
});
|
||||||
QObject::connect(netJob.get(), &NetJob::aborted, [callbacks]{
|
QObject::connect(netJob.get(), &NetJob::aborted, [callbacks] { callbacks.on_abort(); });
|
||||||
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] {
|
||||||
@ -88,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));
|
||||||
|
|
||||||
@ -105,14 +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())
|
||||||
@ -124,9 +115,34 @@ 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] {
|
return netJob;
|
||||||
delete response;
|
}
|
||||||
|
|
||||||
|
Task::Ptr NetworkResourceAPI::getDependencyVersion(DependencySearchArgs&& args, DependencySearchCallbacks&& callbacks) const
|
||||||
|
{
|
||||||
|
auto versions_url_optional = getDependencyURL(args);
|
||||||
|
if (!versions_url_optional.has_value())
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
auto versions_url = versions_url_optional.value();
|
||||||
|
|
||||||
|
auto netJob = makeShared<NetJob>(QString("%1::Dependency").arg(args.dependency.addonId.toString()), APPLICATION->network());
|
||||||
|
auto response = std::make_shared<QByteArray>();
|
||||||
|
|
||||||
|
netJob->addNetAction(Net::Download::makeByteArray(versions_url, response));
|
||||||
|
|
||||||
|
QObject::connect(netJob.get(), &NetJob::succeeded, [=] {
|
||||||
|
QJsonParseError parse_error{};
|
||||||
|
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
|
||||||
|
if (parse_error.error != QJsonParseError::NoError) {
|
||||||
|
qWarning() << "Error while parsing JSON response for getting versions at " << parse_error.offset
|
||||||
|
<< " reason: " << parse_error.errorString();
|
||||||
|
qWarning() << *response;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
callbacks.on_succeed(doc, args.dependency);
|
||||||
});
|
});
|
||||||
|
|
||||||
return netJob;
|
return netJob;
|
||||||
}
|
};
|
||||||
|
@ -4,19 +4,22 @@
|
|||||||
|
|
||||||
#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;
|
||||||
|
Task::Ptr getDependencyVersion(DependencySearchArgs&&, DependencySearchCallbacks&&) const override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
[[nodiscard]] virtual auto getSearchURL(SearchArgs const& args) const -> std::optional<QString> = 0;
|
[[nodiscard]] virtual auto getSearchURL(SearchArgs const& args) const -> std::optional<QString> = 0;
|
||||||
[[nodiscard]] virtual auto getInfoURL(QString const& id) const -> std::optional<QString> = 0;
|
[[nodiscard]] virtual auto getInfoURL(QString const& id) const -> std::optional<QString> = 0;
|
||||||
[[nodiscard]] virtual auto getVersionsURL(VersionSearchArgs const& args) const -> std::optional<QString> = 0;
|
[[nodiscard]] virtual auto getVersionsURL(VersionSearchArgs const& args) const -> std::optional<QString> = 0;
|
||||||
|
[[nodiscard]] virtual auto getDependencyURL(DependencySearchArgs const& args) const -> std::optional<QString> = 0;
|
||||||
};
|
};
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,27 +12,23 @@
|
|||||||
|
|
||||||
class ModrinthAPI : public NetworkResourceAPI {
|
class ModrinthAPI : public NetworkResourceAPI {
|
||||||
public:
|
public:
|
||||||
auto currentVersion(QString hash,
|
auto currentVersion(QString hash, QString hash_format, std::shared_ptr<QByteArray> response) -> Task::Ptr;
|
||||||
QString hash_format,
|
|
||||||
QByteArray* response) -> Task::Ptr;
|
|
||||||
|
|
||||||
auto currentVersions(const QStringList& hashes,
|
auto currentVersions(const QStringList& hashes, QString hash_format, std::shared_ptr<QByteArray> response) -> Task::Ptr;
|
||||||
QString hash_format,
|
|
||||||
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;
|
||||||
@ -42,7 +38,7 @@ class ModrinthAPI : public NetworkResourceAPI {
|
|||||||
static auto getModLoaderStrings(const ModLoaderTypes types) -> const QStringList
|
static auto getModLoaderStrings(const ModLoaderTypes types) -> const QStringList
|
||||||
{
|
{
|
||||||
QStringList l;
|
QStringList l;
|
||||||
for (auto loader : {Forge, Fabric, Quilt}) {
|
for (auto loader : { Forge, Fabric, Quilt }) {
|
||||||
if (types & loader) {
|
if (types & loader) {
|
||||||
l << getModLoaderString(loader);
|
l << getModLoaderString(loader);
|
||||||
}
|
}
|
||||||
@ -55,8 +51,7 @@ class ModrinthAPI : public NetworkResourceAPI {
|
|||||||
static auto getModLoaderFilters(ModLoaderTypes types) -> const QString
|
static auto getModLoaderFilters(ModLoaderTypes types) -> const QString
|
||||||
{
|
{
|
||||||
QStringList l;
|
QStringList l;
|
||||||
for (auto loader : getModLoaderStrings(types))
|
for (auto loader : getModLoaderStrings(types)) {
|
||||||
{
|
|
||||||
l << QString("\"categories:%1\"").arg(loader);
|
l << QString("\"categories:%1\"").arg(loader);
|
||||||
}
|
}
|
||||||
return l.join(',');
|
return l.join(',');
|
||||||
@ -139,16 +134,22 @@ class ModrinthAPI : public NetworkResourceAPI {
|
|||||||
auto getGameVersionsArray(std::list<Version> mcVersions) const -> QString
|
auto getGameVersionsArray(std::list<Version> mcVersions) const -> QString
|
||||||
{
|
{
|
||||||
QString s;
|
QString s;
|
||||||
for(auto& ver : mcVersions){
|
for (auto& ver : mcVersions) {
|
||||||
s += QString("\"versions:%1\",").arg(ver.toString());
|
s += QString("\"versions:%1\",").arg(ver.toString());
|
||||||
}
|
}
|
||||||
s.remove(s.length() - 1, 1); //remove last comma
|
s.remove(s.length() - 1, 1); // remove last comma
|
||||||
return s.isEmpty() ? QString() : s;
|
return s.isEmpty() ? QString() : s;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline auto validateModLoaders(ModLoaderTypes loaders) const -> bool
|
inline auto validateModLoaders(ModLoaderTypes loaders) const -> bool { return loaders & (Forge | Fabric | Quilt); }
|
||||||
{
|
|
||||||
return loaders & (Forge | Fabric | Quilt);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
[[nodiscard]] std::optional<QString> getDependencyURL(DependencySearchArgs const& args) const override
|
||||||
|
{
|
||||||
|
return args.dependency.version.length() != 0 ? QString("%1/version/%2").arg(BuildConfig.MODRINTH_PROD_URL, args.dependency.version)
|
||||||
|
: QString("%1/project/%2/version?game_versions=[\"%3\"]&loaders=[\"%4\"]")
|
||||||
|
.arg(BuildConfig.MODRINTH_PROD_URL)
|
||||||
|
.arg(args.dependency.addonId.toString())
|
||||||
|
.arg(args.mcVersion.toString())
|
||||||
|
.arg(getModLoaderStrings(args.loader).join("\",\""));
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
@ -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;
|
||||||
|
@ -134,8 +134,8 @@ void ModrinthPackExportTask::collectHashes()
|
|||||||
QCryptographicHash sha1(QCryptographicHash::Algorithm::Sha1);
|
QCryptographicHash sha1(QCryptographicHash::Algorithm::Sha1);
|
||||||
sha1.addData(data);
|
sha1.addData(data);
|
||||||
|
|
||||||
ResolvedFile file{ sha1.result().toHex(), sha512.result().toHex(), url.toString(), openFile.size() };
|
ResolvedFile resolvedFile{ sha1.result().toHex(), sha512.result().toHex(), url.toEncoded(), openFile.size() };
|
||||||
resolvedFiles[relative] = file;
|
resolvedFiles[relative] = resolvedFile;
|
||||||
|
|
||||||
// nice! we've managed to resolve based on local metadata!
|
// nice! we've managed to resolve based on local metadata!
|
||||||
// no need to enqueue it
|
// no need to enqueue it
|
||||||
@ -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;
|
||||||
|
|
||||||
|
@ -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();
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
#include "Json.h"
|
#include "Json.h"
|
||||||
#include "minecraft/MinecraftInstance.h"
|
#include "minecraft/MinecraftInstance.h"
|
||||||
#include "minecraft/PackProfile.h"
|
#include "minecraft/PackProfile.h"
|
||||||
#include "net/NetJob.h"
|
#include "modplatform/ModIndex.h"
|
||||||
|
|
||||||
static ModrinthAPI api;
|
static ModrinthAPI api;
|
||||||
static ModPlatform::ProviderCapabilities ProviderCaps;
|
static ModPlatform::ProviderCapabilities ProviderCaps;
|
||||||
@ -140,6 +140,28 @@ auto Modrinth::loadIndexedPackVersion(QJsonObject& obj, QString preferred_hash_t
|
|||||||
file.version_number = Json::requireString(obj, "version_number");
|
file.version_number = Json::requireString(obj, "version_number");
|
||||||
file.changelog = Json::requireString(obj, "changelog");
|
file.changelog = Json::requireString(obj, "changelog");
|
||||||
|
|
||||||
|
auto dependencies = Json::ensureArray(obj, "dependencies");
|
||||||
|
for (auto d : dependencies) {
|
||||||
|
auto dep = Json::ensureObject(d);
|
||||||
|
ModPlatform::Dependency dependency;
|
||||||
|
dependency.addonId = Json::ensureString(dep, "project_id");
|
||||||
|
dependency.version = Json::ensureString(dep, "version_id");
|
||||||
|
auto depType = Json::requireString(dep, "dependency_type");
|
||||||
|
|
||||||
|
if (depType == "required")
|
||||||
|
dependency.type = ModPlatform::DependencyType::REQUIRED;
|
||||||
|
else if (depType == "optional")
|
||||||
|
dependency.type = ModPlatform::DependencyType::OPTIONAL;
|
||||||
|
else if (depType == "incompatible")
|
||||||
|
dependency.type = ModPlatform::DependencyType::INCOMPATIBLE;
|
||||||
|
else if (depType == "embedded")
|
||||||
|
dependency.type = ModPlatform::DependencyType::EMBEDDED;
|
||||||
|
else
|
||||||
|
dependency.type = ModPlatform::DependencyType::UNKNOWN;
|
||||||
|
|
||||||
|
file.dependencies.append(dependency);
|
||||||
|
}
|
||||||
|
|
||||||
auto files = Json::requireArray(obj, "files");
|
auto files = Json::requireArray(obj, "files");
|
||||||
int i = 0;
|
int i = 0;
|
||||||
|
|
||||||
@ -195,3 +217,22 @@ auto Modrinth::loadIndexedPackVersion(QJsonObject& obj, QString preferred_hash_t
|
|||||||
|
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto Modrinth::loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr) -> ModPlatform::IndexedVersion
|
||||||
|
{
|
||||||
|
QVector<ModPlatform::IndexedVersion> versions;
|
||||||
|
|
||||||
|
for (auto versionIter : arr) {
|
||||||
|
auto obj = versionIter.toObject();
|
||||||
|
auto file = loadIndexedPackVersion(obj);
|
||||||
|
|
||||||
|
if (file.fileId.isValid()) // Heuristic to check if the returned value is valid
|
||||||
|
versions.append(file);
|
||||||
|
}
|
||||||
|
auto orderSortPredicate = [](const ModPlatform::IndexedVersion& a, const ModPlatform::IndexedVersion& b) -> bool {
|
||||||
|
// dates are in RFC 3339 format
|
||||||
|
return a.date > b.date;
|
||||||
|
};
|
||||||
|
std::sort(versions.begin(), versions.end(), orderSortPredicate);
|
||||||
|
return versions.length() != 0 ? versions.front() : ModPlatform::IndexedVersion();
|
||||||
|
}
|
@ -19,8 +19,8 @@
|
|||||||
|
|
||||||
#include "modplatform/ModIndex.h"
|
#include "modplatform/ModIndex.h"
|
||||||
|
|
||||||
#include "BaseInstance.h"
|
|
||||||
#include <QNetworkAccessManager>
|
#include <QNetworkAccessManager>
|
||||||
|
#include "BaseInstance.h"
|
||||||
|
|
||||||
namespace Modrinth {
|
namespace Modrinth {
|
||||||
|
|
||||||
@ -31,5 +31,6 @@ void loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
|
|||||||
const shared_qobject_ptr<QNetworkAccessManager>& network,
|
const shared_qobject_ptr<QNetworkAccessManager>& network,
|
||||||
const BaseInstance* inst);
|
const BaseInstance* inst);
|
||||||
auto loadIndexedPackVersion(QJsonObject& obj, QString hash_type = "sha512", QString filename_prefer = "") -> ModPlatform::IndexedVersion;
|
auto loadIndexedPackVersion(QJsonObject& obj, QString hash_type = "sha512", QString filename_prefer = "") -> ModPlatform::IndexedVersion;
|
||||||
|
auto loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr) -> ModPlatform::IndexedVersion;
|
||||||
|
|
||||||
} // namespace Modrinth
|
} // namespace Modrinth
|
||||||
|
@ -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);
|
||||||
|
@ -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
|
||||||
|
@ -1,427 +0,0 @@
|
|||||||
#include "PackageManifest.h"
|
|
||||||
#include <Json.h>
|
|
||||||
#include <QDir>
|
|
||||||
#include <QDirIterator>
|
|
||||||
#include <QCryptographicHash>
|
|
||||||
#include <QDebug>
|
|
||||||
|
|
||||||
#ifndef Q_OS_WIN32
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <sys/types.h>
|
|
||||||
#include <sys/stat.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace mojang_files {
|
|
||||||
|
|
||||||
const Hash hash_of_empty_string = "da39a3ee5e6b4b0d3255bfef95601890afd80709";
|
|
||||||
|
|
||||||
int Path::compare(const Path& rhs) const
|
|
||||||
{
|
|
||||||
auto left_cursor = begin();
|
|
||||||
auto left_end = end();
|
|
||||||
auto right_cursor = rhs.begin();
|
|
||||||
auto right_end = rhs.end();
|
|
||||||
|
|
||||||
while (left_cursor != left_end && right_cursor != right_end)
|
|
||||||
{
|
|
||||||
if(*left_cursor < *right_cursor)
|
|
||||||
{
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
else if(*left_cursor > *right_cursor)
|
|
||||||
{
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
left_cursor++;
|
|
||||||
right_cursor++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(left_cursor == left_end)
|
|
||||||
{
|
|
||||||
if(right_cursor == right_end)
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Package::addFile(const Path& path, const File& file) {
|
|
||||||
addFolder(path.parent_path());
|
|
||||||
files[path] = file;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Package::addFolder(Path folder) {
|
|
||||||
if(!folder.has_parent_path()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
do {
|
|
||||||
folders.insert(folder);
|
|
||||||
folder = folder.parent_path();
|
|
||||||
} while(folder.has_parent_path());
|
|
||||||
}
|
|
||||||
|
|
||||||
void Package::addLink(const Path& path, const Path& target) {
|
|
||||||
addFolder(path.parent_path());
|
|
||||||
symlinks[path] = target;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Package::addSource(const FileSource& source) {
|
|
||||||
sources[source.hash] = source;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
void fromJson(QJsonDocument & doc, Package & out) {
|
|
||||||
std::set<Path> seen_paths;
|
|
||||||
if (!doc.isObject())
|
|
||||||
{
|
|
||||||
throw JSONValidationError("file manifest is not an object");
|
|
||||||
}
|
|
||||||
QJsonObject root = doc.object();
|
|
||||||
|
|
||||||
auto filesObj = Json::ensureObject(root, "files");
|
|
||||||
auto iter = filesObj.begin();
|
|
||||||
while (iter != filesObj.end())
|
|
||||||
{
|
|
||||||
Path objectPath = Path(iter.key());
|
|
||||||
auto value = iter.value();
|
|
||||||
iter++;
|
|
||||||
if(seen_paths.count(objectPath)) {
|
|
||||||
throw JSONValidationError("duplicate path inside manifest, the manifest is invalid");
|
|
||||||
}
|
|
||||||
if (!value.isObject())
|
|
||||||
{
|
|
||||||
throw JSONValidationError("file entry inside manifest is not an an object");
|
|
||||||
}
|
|
||||||
seen_paths.insert(objectPath);
|
|
||||||
|
|
||||||
auto fileObject = value.toObject();
|
|
||||||
auto type = Json::requireString(fileObject, "type");
|
|
||||||
if(type == "directory") {
|
|
||||||
out.addFolder(objectPath);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
else if(type == "file") {
|
|
||||||
FileSource bestSource;
|
|
||||||
File file;
|
|
||||||
file.executable = Json::ensureBoolean(fileObject, QString("executable"), false);
|
|
||||||
auto downloads = Json::requireObject(fileObject, "downloads");
|
|
||||||
for(auto iter2 = downloads.begin(); iter2 != downloads.end(); iter2++) {
|
|
||||||
FileSource source;
|
|
||||||
|
|
||||||
auto downloadObject = Json::requireObject(iter2.value());
|
|
||||||
source.hash = Json::requireString(downloadObject, "sha1");
|
|
||||||
source.size = Json::requireInteger(downloadObject, "size");
|
|
||||||
source.url = Json::requireString(downloadObject, "url");
|
|
||||||
|
|
||||||
auto compression = iter2.key();
|
|
||||||
if(compression == "raw") {
|
|
||||||
file.hash = source.hash;
|
|
||||||
file.size = source.size;
|
|
||||||
source.compression = Compression::Raw;
|
|
||||||
}
|
|
||||||
else if (compression == "lzma") {
|
|
||||||
source.compression = Compression::Lzma;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
bestSource.upgrade(source);
|
|
||||||
}
|
|
||||||
if(bestSource.isBad()) {
|
|
||||||
throw JSONValidationError("No valid compression method for file " + iter.key());
|
|
||||||
}
|
|
||||||
out.addFile(objectPath, file);
|
|
||||||
out.addSource(bestSource);
|
|
||||||
}
|
|
||||||
else if(type == "link") {
|
|
||||||
auto target = Json::requireString(fileObject, "target");
|
|
||||||
out.symlinks[objectPath] = target;
|
|
||||||
out.addLink(objectPath, target);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
throw JSONValidationError("Invalid item type in manifest: " + type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// make sure the containing folder exists
|
|
||||||
out.folders.insert(Path());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Package Package::fromManifestContents(const QByteArray& contents)
|
|
||||||
{
|
|
||||||
Package out;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
auto doc = Json::requireDocument(contents, "Manifest");
|
|
||||||
fromJson(doc, out);
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
catch (const Exception &e)
|
|
||||||
{
|
|
||||||
qDebug() << QString("Unable to parse manifest: %1").arg(e.cause());
|
|
||||||
out.valid = false;
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Package Package::fromManifestFile(const QString & filename) {
|
|
||||||
Package out;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
auto doc = Json::requireDocument(filename, filename);
|
|
||||||
fromJson(doc, out);
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
catch (const Exception &e)
|
|
||||||
{
|
|
||||||
qDebug() << QString("Unable to parse manifest file %1: %2").arg(filename, e.cause());
|
|
||||||
out.valid = false;
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifndef Q_OS_WIN32
|
|
||||||
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <sys/types.h>
|
|
||||||
#include <sys/stat.h>
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
// FIXME: Qt obscures symlink targets by making them absolute. that is useless. this is the workaround - we do it ourselves
|
|
||||||
bool actually_read_symlink_target(const QString & filepath, Path & out)
|
|
||||||
{
|
|
||||||
struct ::stat st;
|
|
||||||
// FIXME: here, we assume the native filesystem encoding. May the Gods have mercy upon our Souls.
|
|
||||||
QByteArray nativePath = filepath.toUtf8();
|
|
||||||
const char * filepath_cstr = nativePath.data();
|
|
||||||
|
|
||||||
if (lstat(filepath_cstr, &st) != 0)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto size = st.st_size ? st.st_size + 1 : PATH_MAX;
|
|
||||||
std::string temp(size, '\0');
|
|
||||||
// because we don't realiably know how long the damn thing actually is, we loop and expand. POSIX is naff
|
|
||||||
do
|
|
||||||
{
|
|
||||||
auto link_length = ::readlink(filepath_cstr, &temp[0], temp.size());
|
|
||||||
if(link_length == -1)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if(std::string::size_type(link_length) < temp.size())
|
|
||||||
{
|
|
||||||
// buffer was long enough and we managed to read the link target. RETURN here.
|
|
||||||
temp.resize(link_length);
|
|
||||||
out = Path(QString::fromUtf8(temp.c_str()));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
temp.resize(temp.size() * 2);
|
|
||||||
} while (true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// FIXME: Qt filesystem abstraction is bad, but ... let's hope it doesn't break too much?
|
|
||||||
// FIXME: The error handling is just DEFICIENT
|
|
||||||
Package Package::fromInspectedFolder(const QString& folderPath)
|
|
||||||
{
|
|
||||||
QDir root(folderPath);
|
|
||||||
|
|
||||||
Package out;
|
|
||||||
QDirIterator iterator(folderPath, QDir::NoDotAndDotDot | QDir::AllEntries | QDir::System | QDir::Hidden, QDirIterator::Subdirectories);
|
|
||||||
while(iterator.hasNext()) {
|
|
||||||
iterator.next();
|
|
||||||
|
|
||||||
auto fileInfo = iterator.fileInfo();
|
|
||||||
auto relPath = root.relativeFilePath(fileInfo.filePath());
|
|
||||||
// FIXME: this is probably completely busted on Windows anyway, so just disable it.
|
|
||||||
// Qt makes shit up and doesn't understand the platform details
|
|
||||||
// TODO: Actually use a filesystem library that isn't terrible and has decen license.
|
|
||||||
// I only know one, and I wrote it. Sadly, currently proprietary. PAIN.
|
|
||||||
#ifndef Q_OS_WIN32
|
|
||||||
if(fileInfo.isSymLink()) {
|
|
||||||
Path targetPath;
|
|
||||||
if(!actually_read_symlink_target(fileInfo.filePath(), targetPath)) {
|
|
||||||
qCritical() << "Folder inspection: Unknown filesystem object:" << fileInfo.absoluteFilePath();
|
|
||||||
out.valid = false;
|
|
||||||
}
|
|
||||||
out.addLink(relPath, targetPath);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
#endif
|
|
||||||
if(fileInfo.isDir()) {
|
|
||||||
out.addFolder(relPath);
|
|
||||||
}
|
|
||||||
else if(fileInfo.isFile()) {
|
|
||||||
File f;
|
|
||||||
f.executable = fileInfo.isExecutable();
|
|
||||||
f.size = fileInfo.size();
|
|
||||||
// FIXME: async / optimize the hashing
|
|
||||||
QFile input(fileInfo.absoluteFilePath());
|
|
||||||
if(!input.open(QIODevice::ReadOnly)) {
|
|
||||||
qCritical() << "Folder inspection: Failed to open file:" << fileInfo.absoluteFilePath();
|
|
||||||
out.valid = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
f.hash = QCryptographicHash::hash(input.readAll(), QCryptographicHash::Sha1).toHex().constData();
|
|
||||||
out.addFile(relPath, f);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Something else... oh my
|
|
||||||
qCritical() << "Folder inspection: Unknown filesystem object:" << fileInfo.absoluteFilePath();
|
|
||||||
out.valid = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
out.folders.insert(Path("."));
|
|
||||||
out.valid = true;
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
struct shallow_first_sort
|
|
||||||
{
|
|
||||||
bool operator()(const Path &lhs, const Path &rhs) const
|
|
||||||
{
|
|
||||||
auto lhs_depth = lhs.length();
|
|
||||||
auto rhs_depth = rhs.length();
|
|
||||||
if(lhs_depth < rhs_depth)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else if(lhs_depth == rhs_depth)
|
|
||||||
{
|
|
||||||
if(lhs < rhs)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct deep_first_sort
|
|
||||||
{
|
|
||||||
bool operator()(const Path &lhs, const Path &rhs) const
|
|
||||||
{
|
|
||||||
auto lhs_depth = lhs.length();
|
|
||||||
auto rhs_depth = rhs.length();
|
|
||||||
if(lhs_depth > rhs_depth)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else if(lhs_depth == rhs_depth)
|
|
||||||
{
|
|
||||||
if(lhs < rhs)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
UpdateOperations UpdateOperations::resolve(const Package& from, const Package& to)
|
|
||||||
{
|
|
||||||
UpdateOperations out;
|
|
||||||
|
|
||||||
if(!from.valid || !to.valid) {
|
|
||||||
out.valid = false;
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Files
|
|
||||||
for(auto iter = from.files.begin(); iter != from.files.end(); iter++) {
|
|
||||||
const auto ¤t_hash = iter->second.hash;
|
|
||||||
const auto ¤t_executable = iter->second.executable;
|
|
||||||
const auto &path = iter->first;
|
|
||||||
|
|
||||||
auto iter2 = to.files.find(path);
|
|
||||||
if(iter2 == to.files.end()) {
|
|
||||||
// removed
|
|
||||||
out.deletes.push_back(path);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
auto new_hash = iter2->second.hash;
|
|
||||||
auto new_executable = iter2->second.executable;
|
|
||||||
if (current_hash != new_hash) {
|
|
||||||
out.deletes.push_back(path);
|
|
||||||
out.downloads.emplace(
|
|
||||||
std::pair<Path, FileDownload>{
|
|
||||||
path,
|
|
||||||
FileDownload(to.sources.at(iter2->second.hash), iter2->second.executable)
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
else if (current_executable != new_executable) {
|
|
||||||
out.executable_fixes[path] = new_executable;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for(auto iter = to.files.begin(); iter != to.files.end(); iter++) {
|
|
||||||
auto path = iter->first;
|
|
||||||
if(!from.files.count(path)) {
|
|
||||||
out.downloads.emplace(
|
|
||||||
std::pair<Path, FileDownload>{
|
|
||||||
path,
|
|
||||||
FileDownload(to.sources.at(iter->second.hash), iter->second.executable)
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Folders
|
|
||||||
std::set<Path, deep_first_sort> remove_folders;
|
|
||||||
std::set<Path, shallow_first_sort> make_folders;
|
|
||||||
for(auto from_path: from.folders) {
|
|
||||||
auto iter = to.folders.find(from_path);
|
|
||||||
if(iter == to.folders.end()) {
|
|
||||||
remove_folders.insert(from_path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for(auto & rmdir: remove_folders) {
|
|
||||||
out.rmdirs.push_back(rmdir);
|
|
||||||
}
|
|
||||||
for(auto to_path: to.folders) {
|
|
||||||
auto iter = from.folders.find(to_path);
|
|
||||||
if(iter == from.folders.end()) {
|
|
||||||
make_folders.insert(to_path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for(auto & mkdir: make_folders) {
|
|
||||||
out.mkdirs.push_back(mkdir);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Symlinks
|
|
||||||
for(auto iter = from.symlinks.begin(); iter != from.symlinks.end(); iter++) {
|
|
||||||
const auto ¤t_target = iter->second;
|
|
||||||
const auto &path = iter->first;
|
|
||||||
|
|
||||||
auto iter2 = to.symlinks.find(path);
|
|
||||||
if(iter2 == to.symlinks.end()) {
|
|
||||||
// removed
|
|
||||||
out.deletes.push_back(path);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const auto &new_target = iter2->second;
|
|
||||||
if (current_target != new_target) {
|
|
||||||
out.deletes.push_back(path);
|
|
||||||
out.mklinks[path] = iter2->second;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for(auto iter = to.symlinks.begin(); iter != to.symlinks.end(); iter++) {
|
|
||||||
auto path = iter->first;
|
|
||||||
if(!from.symlinks.count(path)) {
|
|
||||||
out.mklinks[path] = iter->second;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
out.valid = true;
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,171 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <QString>
|
|
||||||
#include <map>
|
|
||||||
#include <set>
|
|
||||||
#include <QStringList>
|
|
||||||
#include "tasks/Task.h"
|
|
||||||
|
|
||||||
namespace mojang_files {
|
|
||||||
|
|
||||||
using Hash = QString;
|
|
||||||
extern const Hash empty_hash;
|
|
||||||
|
|
||||||
// simple-ish path implementation. assumes always relative and does not allow '..' entries
|
|
||||||
class Path
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
using parts_type = QStringList;
|
|
||||||
|
|
||||||
Path() = default;
|
|
||||||
Path(QString string) {
|
|
||||||
auto parts_in = string.split('/');
|
|
||||||
for(auto & part: parts_in) {
|
|
||||||
if(part.isEmpty() || part == ".") {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if(part == "..") {
|
|
||||||
if(parts.size()) {
|
|
||||||
parts.pop_back();
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
parts.push_back(part);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool has_parent_path() const
|
|
||||||
{
|
|
||||||
return parts.size() > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
Path parent_path() const
|
|
||||||
{
|
|
||||||
if (parts.empty())
|
|
||||||
return Path();
|
|
||||||
return Path(parts.begin(), std::prev(parts.end()));
|
|
||||||
}
|
|
||||||
|
|
||||||
bool empty() const
|
|
||||||
{
|
|
||||||
return parts.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
int length() const
|
|
||||||
{
|
|
||||||
return parts.length();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool operator==(const Path & rhs) const {
|
|
||||||
return parts == rhs.parts;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool operator!=(const Path & rhs) const {
|
|
||||||
return parts != rhs.parts;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline bool operator<(const Path& rhs) const
|
|
||||||
{
|
|
||||||
return compare(rhs) < 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
parts_type::const_iterator begin() const
|
|
||||||
{
|
|
||||||
return parts.begin();
|
|
||||||
}
|
|
||||||
|
|
||||||
parts_type::const_iterator end() const
|
|
||||||
{
|
|
||||||
return parts.end();
|
|
||||||
}
|
|
||||||
|
|
||||||
QString toString() const {
|
|
||||||
return parts.join("/");
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
Path(const parts_type::const_iterator & start, const parts_type::const_iterator & end) {
|
|
||||||
auto cursor = start;
|
|
||||||
while(cursor != end) {
|
|
||||||
parts.push_back(*cursor);
|
|
||||||
cursor++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
int compare(const Path& p) const;
|
|
||||||
|
|
||||||
parts_type parts;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
enum class Compression {
|
|
||||||
Raw,
|
|
||||||
Lzma,
|
|
||||||
Unknown
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
struct FileSource
|
|
||||||
{
|
|
||||||
Compression compression = Compression::Unknown;
|
|
||||||
Hash hash;
|
|
||||||
QString url;
|
|
||||||
std::size_t size = 0;
|
|
||||||
void upgrade(const FileSource & other) {
|
|
||||||
if(compression == Compression::Unknown || other.size < size) {
|
|
||||||
*this = other;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bool isBad() const {
|
|
||||||
return compression == Compression::Unknown;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct File
|
|
||||||
{
|
|
||||||
Hash hash;
|
|
||||||
bool executable;
|
|
||||||
std::uint64_t size = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Package {
|
|
||||||
static Package fromInspectedFolder(const QString &folderPath);
|
|
||||||
static Package fromManifestFile(const QString &path);
|
|
||||||
static Package fromManifestContents(const QByteArray& contents);
|
|
||||||
|
|
||||||
explicit operator bool() const
|
|
||||||
{
|
|
||||||
return valid;
|
|
||||||
}
|
|
||||||
void addFolder(Path folder);
|
|
||||||
void addFile(const Path & path, const File & file);
|
|
||||||
void addLink(const Path & path, const Path & target);
|
|
||||||
void addSource(const FileSource & source);
|
|
||||||
|
|
||||||
std::map<Hash, FileSource> sources;
|
|
||||||
bool valid = true;
|
|
||||||
std::set<Path> folders;
|
|
||||||
std::map<Path, File> files;
|
|
||||||
std::map<Path, Path> symlinks;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct FileDownload : FileSource
|
|
||||||
{
|
|
||||||
FileDownload(const FileSource& source, bool executable) {
|
|
||||||
static_cast<FileSource &> (*this) = source;
|
|
||||||
this->executable = executable;
|
|
||||||
}
|
|
||||||
bool executable = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct UpdateOperations {
|
|
||||||
static UpdateOperations resolve(const Package & from, const Package & to);
|
|
||||||
bool valid = false;
|
|
||||||
std::vector<Path> deletes;
|
|
||||||
std::vector<Path> rmdirs;
|
|
||||||
std::vector<Path> mkdirs;
|
|
||||||
std::map<Path, FileDownload> downloads;
|
|
||||||
std::map<Path, Path> mklinks;
|
|
||||||
std::map<Path, bool> executable_fixes;
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
@ -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
|
||||||
|
@ -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;
|
||||||
|
@ -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:
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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.
|
||||||
|
@ -16,7 +16,6 @@
|
|||||||
<file>scalable/jarmods.svg</file>
|
<file>scalable/jarmods.svg</file>
|
||||||
<file>scalable/java.svg</file>
|
<file>scalable/java.svg</file>
|
||||||
<file>scalable/language.svg</file>
|
<file>scalable/language.svg</file>
|
||||||
<file>scalable/launcher.svg</file>
|
|
||||||
<file>scalable/loadermods.svg</file>
|
<file>scalable/loadermods.svg</file>
|
||||||
<file>scalable/log.svg</file>
|
<file>scalable/log.svg</file>
|
||||||
<file>scalable/minecraft.svg</file>
|
<file>scalable/minecraft.svg</file>
|
||||||
|
@ -1,57 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
|
||||||
<svg width="48" height="48" version="1.1" viewBox="0 0 12.7 12.7" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
|
||||||
<title>Prism Launcher Logo</title>
|
|
||||||
<g stroke-width=".26458">
|
|
||||||
<path d="m6.35 6.35" fill="#99cd61"/>
|
|
||||||
<path d="m6.35 0.52917-2.5208 4.3656 2.5208 1.4552 2.5203-1.4552 0.10955-3.0996c-1.1511-0.66459-2.3388-1.2661-2.6298-1.2661z" fill="#df6277"/>
|
|
||||||
<path d="m8.9798 1.7952-2.6298 4.5548 2.5203 1.4552 2.5208-4.3656c-0.14552-0.25205-1.2601-0.97975-2.4112-1.6443z" fill="#fb9168"/>
|
|
||||||
<path d="m11.391 3.4396-5.041 2.9104 2.5203 1.4552 2.7389-1.4552c0-1.3292-0.072554-2.6584-0.21808-2.9104z" fill="#f3db6c"/>
|
|
||||||
<path d="m6.35 6.35v2.9104h5.041c0.14552-0.25205 0.21807-1.5812 0.21808-2.9104h-5.2591z" fill="#7ab392"/>
|
|
||||||
<path d="m6.35 6.35v2.9104l2.6298 1.6443c1.1511-0.66459 2.2657-1.3923 2.4112-1.6443l-5.041-2.9104z" fill="#4b7cbc"/>
|
|
||||||
<path d="m6.35 6.35-2.5208 1.4552 2.5208 4.3656c0.29104 0 1.4787-0.60148 2.6298-1.2661l-2.6298-4.5548z" fill="#6f488c"/>
|
|
||||||
<path d="m3.8292 4.8948-2.5203 4.3656c0.29104 0.5041 4.459 2.9104 5.041 2.9104v-5.8208l-2.5208-1.4552z" fill="#4d3f33"/>
|
|
||||||
<path d="m1.309 3.4396c-0.29104 0.5041-0.29104 5.3167 0 5.8208l5.041-2.9104v-2.9104h-5.041z" fill="#7a573b"/>
|
|
||||||
<path d="m6.35 0.52917c-0.58208-2e-8 -4.75 2.4063-5.041 2.9104l5.041 2.9104v-5.8208z" fill="#99cd61"/>
|
|
||||||
</g>
|
|
||||||
<g transform="matrix(.88 0 0 .88 -10.906 -1.2421)">
|
|
||||||
<g transform="translate(13.26 2.2776)">
|
|
||||||
<path transform="matrix(.96975 0 0 .96975 .1921 .1921)" d="m6.3498 2.9393c-0.34105 0-2.7827 1.4099-2.9532 1.7052l2.9532 5.1157 2.9538-5.1157c-0.17052-0.29535-2.6127-1.7052-2.9538-1.7052z" fill="#fff" stroke-width=".26458"/>
|
|
||||||
</g>
|
|
||||||
<path d="m16.746 6.9737 2.8639 4.9609c0.33073 0 2.6991-1.3672 2.8644-1.6536 0.16536-0.28642 0.16536-3.0209 0-3.3073l-2.8644 1.6536z" fill="#dfdfdf" stroke-width=".26458"/>
|
|
||||||
</g>
|
|
||||||
<path d="m3.8299 4.8948c-0.14551 0.25205-0.14553 2.6584 0 2.9104 0.14553 0.25204 2.2292 1.4552 2.5203 1.4552v-2.9104z" fill="#d6d2d2" stroke-width=".26458"/>
|
|
||||||
<metadata>
|
|
||||||
<rdf:RDF>
|
|
||||||
<cc:Work rdf:about="">
|
|
||||||
<dc:title>Prism Launcher Logo</dc:title>
|
|
||||||
<dc:date>19/10/2022</dc:date>
|
|
||||||
<dc:creator>
|
|
||||||
<cc:Agent>
|
|
||||||
<dc:title>Prism Launcher</dc:title>
|
|
||||||
</cc:Agent>
|
|
||||||
</dc:creator>
|
|
||||||
<dc:contributor>
|
|
||||||
<cc:Agent>
|
|
||||||
<dc:title>AutiOne, Boba, ely, Fulmine, gon sawa, Pankakes, tobimori, Zeke</dc:title>
|
|
||||||
</cc:Agent>
|
|
||||||
</dc:contributor>
|
|
||||||
<dc:source>https://github.com/PrismLauncher/PrismLauncher</dc:source>
|
|
||||||
<dc:publisher>
|
|
||||||
<cc:Agent>
|
|
||||||
<dc:title>Prism Launcher</dc:title>
|
|
||||||
</cc:Agent>
|
|
||||||
</dc:publisher>
|
|
||||||
<cc:license rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/"/>
|
|
||||||
</cc:Work>
|
|
||||||
<cc:License rdf:about="http://creativecommons.org/licenses/by-sa/4.0/">
|
|
||||||
<cc:permits rdf:resource="http://creativecommons.org/ns#Reproduction"/>
|
|
||||||
<cc:permits rdf:resource="http://creativecommons.org/ns#Distribution"/>
|
|
||||||
<cc:requires rdf:resource="http://creativecommons.org/ns#Notice"/>
|
|
||||||
<cc:requires rdf:resource="http://creativecommons.org/ns#Attribution"/>
|
|
||||||
<cc:permits rdf:resource="http://creativecommons.org/ns#DerivativeWorks"/>
|
|
||||||
<cc:requires rdf:resource="http://creativecommons.org/ns#ShareAlike"/>
|
|
||||||
</cc:License>
|
|
||||||
</rdf:RDF>
|
|
||||||
</metadata>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 3.4 KiB |
@ -1,57 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
|
||||||
<svg width="48" height="48" version="1.1" viewBox="0 0 12.7 12.7" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
|
||||||
<title>Prism Launcher Logo</title>
|
|
||||||
<g stroke-width=".26458">
|
|
||||||
<path d="m6.35 6.35" fill="#99cd61"/>
|
|
||||||
<path d="m6.35 0.52917-2.5208 4.3656 2.5208 1.4552 2.5203-1.4552 0.10955-3.0996c-1.1511-0.66459-2.3388-1.2661-2.6298-1.2661z" fill="#df6277"/>
|
|
||||||
<path d="m8.9798 1.7952-2.6298 4.5548 2.5203 1.4552 2.5208-4.3656c-0.14552-0.25205-1.2601-0.97975-2.4112-1.6443z" fill="#fb9168"/>
|
|
||||||
<path d="m11.391 3.4396-5.041 2.9104 2.5203 1.4552 2.7389-1.4552c0-1.3292-0.072554-2.6584-0.21808-2.9104z" fill="#f3db6c"/>
|
|
||||||
<path d="m6.35 6.35v2.9104h5.041c0.14552-0.25205 0.21807-1.5812 0.21808-2.9104h-5.2591z" fill="#7ab392"/>
|
|
||||||
<path d="m6.35 6.35v2.9104l2.6298 1.6443c1.1511-0.66459 2.2657-1.3923 2.4112-1.6443l-5.041-2.9104z" fill="#4b7cbc"/>
|
|
||||||
<path d="m6.35 6.35-2.5208 1.4552 2.5208 4.3656c0.29104 0 1.4787-0.60148 2.6298-1.2661l-2.6298-4.5548z" fill="#6f488c"/>
|
|
||||||
<path d="m3.8292 4.8948-2.5203 4.3656c0.29104 0.5041 4.459 2.9104 5.041 2.9104v-5.8208l-2.5208-1.4552z" fill="#4d3f33"/>
|
|
||||||
<path d="m1.309 3.4396c-0.29104 0.5041-0.29104 5.3167 0 5.8208l5.041-2.9104v-2.9104h-5.041z" fill="#7a573b"/>
|
|
||||||
<path d="m6.35 0.52917c-0.58208-2e-8 -4.75 2.4063-5.041 2.9104l5.041 2.9104v-5.8208z" fill="#99cd61"/>
|
|
||||||
</g>
|
|
||||||
<g transform="matrix(.88 0 0 .88 -10.906 -1.2421)">
|
|
||||||
<g transform="translate(13.26 2.2776)">
|
|
||||||
<path transform="matrix(.96975 0 0 .96975 .1921 .1921)" d="m6.3498 2.9393c-0.34105 0-2.7827 1.4099-2.9532 1.7052l2.9532 5.1157 2.9538-5.1157c-0.17052-0.29535-2.6127-1.7052-2.9538-1.7052z" fill="#fff" stroke-width=".26458"/>
|
|
||||||
</g>
|
|
||||||
<path d="m16.746 6.9737 2.8639 4.9609c0.33073 0 2.6991-1.3672 2.8644-1.6536 0.16536-0.28642 0.16536-3.0209 0-3.3073l-2.8644 1.6536z" fill="#dfdfdf" stroke-width=".26458"/>
|
|
||||||
</g>
|
|
||||||
<path d="m3.8299 4.8948c-0.14551 0.25205-0.14553 2.6584 0 2.9104 0.14553 0.25204 2.2292 1.4552 2.5203 1.4552v-2.9104z" fill="#d6d2d2" stroke-width=".26458"/>
|
|
||||||
<metadata>
|
|
||||||
<rdf:RDF>
|
|
||||||
<cc:Work rdf:about="">
|
|
||||||
<dc:title>Prism Launcher Logo</dc:title>
|
|
||||||
<dc:date>19/10/2022</dc:date>
|
|
||||||
<dc:creator>
|
|
||||||
<cc:Agent>
|
|
||||||
<dc:title>Prism Launcher</dc:title>
|
|
||||||
</cc:Agent>
|
|
||||||
</dc:creator>
|
|
||||||
<dc:contributor>
|
|
||||||
<cc:Agent>
|
|
||||||
<dc:title>AutiOne, Boba, ely, Fulmine, gon sawa, Pankakes, tobimori, Zeke</dc:title>
|
|
||||||
</cc:Agent>
|
|
||||||
</dc:contributor>
|
|
||||||
<dc:source>https://github.com/PrismLauncher/PrismLauncher</dc:source>
|
|
||||||
<dc:publisher>
|
|
||||||
<cc:Agent>
|
|
||||||
<dc:title>Prism Launcher</dc:title>
|
|
||||||
</cc:Agent>
|
|
||||||
</dc:publisher>
|
|
||||||
<cc:license rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/"/>
|
|
||||||
</cc:Work>
|
|
||||||
<cc:License rdf:about="http://creativecommons.org/licenses/by-sa/4.0/">
|
|
||||||
<cc:permits rdf:resource="http://creativecommons.org/ns#Reproduction"/>
|
|
||||||
<cc:permits rdf:resource="http://creativecommons.org/ns#Distribution"/>
|
|
||||||
<cc:requires rdf:resource="http://creativecommons.org/ns#Notice"/>
|
|
||||||
<cc:requires rdf:resource="http://creativecommons.org/ns#Attribution"/>
|
|
||||||
<cc:permits rdf:resource="http://creativecommons.org/ns#DerivativeWorks"/>
|
|
||||||
<cc:requires rdf:resource="http://creativecommons.org/ns#ShareAlike"/>
|
|
||||||
</cc:License>
|
|
||||||
</rdf:RDF>
|
|
||||||
</metadata>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 3.4 KiB |
@ -18,7 +18,6 @@
|
|||||||
<file>scalable/jarmods.svg</file>
|
<file>scalable/jarmods.svg</file>
|
||||||
<file>scalable/java.svg</file>
|
<file>scalable/java.svg</file>
|
||||||
<file>scalable/language.svg</file>
|
<file>scalable/language.svg</file>
|
||||||
<file>scalable/launcher.svg</file>
|
|
||||||
<file>scalable/loadermods.svg</file>
|
<file>scalable/loadermods.svg</file>
|
||||||
<file>scalable/log.svg</file>
|
<file>scalable/log.svg</file>
|
||||||
<file>scalable/minecraft.svg</file>
|
<file>scalable/minecraft.svg</file>
|
||||||
|
@ -1,57 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
|
||||||
<svg width="48" height="48" version="1.1" viewBox="0 0 12.7 12.7" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
|
||||||
<title>Prism Launcher Logo</title>
|
|
||||||
<g stroke-width=".26458">
|
|
||||||
<path d="m6.35 6.35" fill="#99cd61"/>
|
|
||||||
<path d="m6.35 0.52917-2.5208 4.3656 2.5208 1.4552 2.5203-1.4552 0.10955-3.0996c-1.1511-0.66459-2.3388-1.2661-2.6298-1.2661z" fill="#df6277"/>
|
|
||||||
<path d="m8.9798 1.7952-2.6298 4.5548 2.5203 1.4552 2.5208-4.3656c-0.14552-0.25205-1.2601-0.97975-2.4112-1.6443z" fill="#fb9168"/>
|
|
||||||
<path d="m11.391 3.4396-5.041 2.9104 2.5203 1.4552 2.7389-1.4552c0-1.3292-0.072554-2.6584-0.21808-2.9104z" fill="#f3db6c"/>
|
|
||||||
<path d="m6.35 6.35v2.9104h5.041c0.14552-0.25205 0.21807-1.5812 0.21808-2.9104h-5.2591z" fill="#7ab392"/>
|
|
||||||
<path d="m6.35 6.35v2.9104l2.6298 1.6443c1.1511-0.66459 2.2657-1.3923 2.4112-1.6443l-5.041-2.9104z" fill="#4b7cbc"/>
|
|
||||||
<path d="m6.35 6.35-2.5208 1.4552 2.5208 4.3656c0.29104 0 1.4787-0.60148 2.6298-1.2661l-2.6298-4.5548z" fill="#6f488c"/>
|
|
||||||
<path d="m3.8292 4.8948-2.5203 4.3656c0.29104 0.5041 4.459 2.9104 5.041 2.9104v-5.8208l-2.5208-1.4552z" fill="#4d3f33"/>
|
|
||||||
<path d="m1.309 3.4396c-0.29104 0.5041-0.29104 5.3167 0 5.8208l5.041-2.9104v-2.9104h-5.041z" fill="#7a573b"/>
|
|
||||||
<path d="m6.35 0.52917c-0.58208-2e-8 -4.75 2.4063-5.041 2.9104l5.041 2.9104v-5.8208z" fill="#99cd61"/>
|
|
||||||
</g>
|
|
||||||
<g transform="matrix(.88 0 0 .88 -10.906 -1.2421)">
|
|
||||||
<g transform="translate(13.26 2.2776)">
|
|
||||||
<path transform="matrix(.96975 0 0 .96975 .1921 .1921)" d="m6.3498 2.9393c-0.34105 0-2.7827 1.4099-2.9532 1.7052l2.9532 5.1157 2.9538-5.1157c-0.17052-0.29535-2.6127-1.7052-2.9538-1.7052z" fill="#fff" stroke-width=".26458"/>
|
|
||||||
</g>
|
|
||||||
<path d="m16.746 6.9737 2.8639 4.9609c0.33073 0 2.6991-1.3672 2.8644-1.6536 0.16536-0.28642 0.16536-3.0209 0-3.3073l-2.8644 1.6536z" fill="#dfdfdf" stroke-width=".26458"/>
|
|
||||||
</g>
|
|
||||||
<path d="m3.8299 4.8948c-0.14551 0.25205-0.14553 2.6584 0 2.9104 0.14553 0.25204 2.2292 1.4552 2.5203 1.4552v-2.9104z" fill="#d6d2d2" stroke-width=".26458"/>
|
|
||||||
<metadata>
|
|
||||||
<rdf:RDF>
|
|
||||||
<cc:Work rdf:about="">
|
|
||||||
<dc:title>Prism Launcher Logo</dc:title>
|
|
||||||
<dc:date>19/10/2022</dc:date>
|
|
||||||
<dc:creator>
|
|
||||||
<cc:Agent>
|
|
||||||
<dc:title>Prism Launcher</dc:title>
|
|
||||||
</cc:Agent>
|
|
||||||
</dc:creator>
|
|
||||||
<dc:contributor>
|
|
||||||
<cc:Agent>
|
|
||||||
<dc:title>AutiOne, Boba, ely, Fulmine, gon sawa, Pankakes, tobimori, Zeke</dc:title>
|
|
||||||
</cc:Agent>
|
|
||||||
</dc:contributor>
|
|
||||||
<dc:source>https://github.com/PrismLauncher/PrismLauncher</dc:source>
|
|
||||||
<dc:publisher>
|
|
||||||
<cc:Agent>
|
|
||||||
<dc:title>Prism Launcher</dc:title>
|
|
||||||
</cc:Agent>
|
|
||||||
</dc:publisher>
|
|
||||||
<cc:license rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/"/>
|
|
||||||
</cc:Work>
|
|
||||||
<cc:License rdf:about="http://creativecommons.org/licenses/by-sa/4.0/">
|
|
||||||
<cc:permits rdf:resource="http://creativecommons.org/ns#Reproduction"/>
|
|
||||||
<cc:permits rdf:resource="http://creativecommons.org/ns#Distribution"/>
|
|
||||||
<cc:requires rdf:resource="http://creativecommons.org/ns#Notice"/>
|
|
||||||
<cc:requires rdf:resource="http://creativecommons.org/ns#Attribution"/>
|
|
||||||
<cc:permits rdf:resource="http://creativecommons.org/ns#DerivativeWorks"/>
|
|
||||||
<cc:requires rdf:resource="http://creativecommons.org/ns#ShareAlike"/>
|
|
||||||
</cc:License>
|
|
||||||
</rdf:RDF>
|
|
||||||
</metadata>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 3.4 KiB |
@ -18,7 +18,6 @@
|
|||||||
<file>scalable/jarmods.svg</file>
|
<file>scalable/jarmods.svg</file>
|
||||||
<file>scalable/java.svg</file>
|
<file>scalable/java.svg</file>
|
||||||
<file>scalable/language.svg</file>
|
<file>scalable/language.svg</file>
|
||||||
<file>scalable/launcher.svg</file>
|
|
||||||
<file>scalable/loadermods.svg</file>
|
<file>scalable/loadermods.svg</file>
|
||||||
<file>scalable/log.svg</file>
|
<file>scalable/log.svg</file>
|
||||||
<file>scalable/minecraft.svg</file>
|
<file>scalable/minecraft.svg</file>
|
||||||
|
@ -1,2 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<svg width="24" height="24" fill="#eeeeee" version="1.1" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="m20 4h-16v16h16zm0 18h-16c-1.1046 0-2-0.89543-2-2v-16c0-1.1046 0.89543-2 2-2h16c1.1046 0 2 0.89543 2 2v16c0 1.1046-0.89543 2-2 2z"/><path d="m7.2 18c-0.225 0-0.45-0.075-0.6-0.15-0.375-0.225-0.6-0.6-0.6-1.05v-9.6c0-0.45 0.225-0.825 0.6-1.05 0.225-0.15 0.375-0.15 0.6-0.15 0.15 0 0.375 0.075 0.525 0.15l9.6 4.8c0.375 0.225 0.675 0.6 0.675 1.05 0 0.45-0.225 0.9-0.675 1.05l-9.6 4.8c-0.15 0.075-0.375 0.15-0.525 0.15z" clip-rule="evenodd" fill="#eeeeee" fill-rule="evenodd" stroke-width=".99999"/></svg>
|
|
Before Width: | Height: | Size: 660 B |
@ -16,7 +16,6 @@
|
|||||||
<file>scalable/jarmods.svg</file>
|
<file>scalable/jarmods.svg</file>
|
||||||
<file>scalable/java.svg</file>
|
<file>scalable/java.svg</file>
|
||||||
<file>scalable/language.svg</file>
|
<file>scalable/language.svg</file>
|
||||||
<file>scalable/launcher.svg</file>
|
|
||||||
<file>scalable/loadermods.svg</file>
|
<file>scalable/loadermods.svg</file>
|
||||||
<file>scalable/log.svg</file>
|
<file>scalable/log.svg</file>
|
||||||
<file>scalable/minecraft.svg</file>
|
<file>scalable/minecraft.svg</file>
|
||||||
|
@ -1,57 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
|
||||||
<svg width="48" height="48" version="1.1" viewBox="0 0 12.7 12.7" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
|
||||||
<title>Prism Launcher Logo</title>
|
|
||||||
<g stroke-width=".26458">
|
|
||||||
<path d="m6.35 6.35" fill="#99cd61"/>
|
|
||||||
<path d="m6.35 0.52917-2.5208 4.3656 2.5208 1.4552 2.5203-1.4552 0.10955-3.0996c-1.1511-0.66459-2.3388-1.2661-2.6298-1.2661z" fill="#df6277"/>
|
|
||||||
<path d="m8.9798 1.7952-2.6298 4.5548 2.5203 1.4552 2.5208-4.3656c-0.14552-0.25205-1.2601-0.97975-2.4112-1.6443z" fill="#fb9168"/>
|
|
||||||
<path d="m11.391 3.4396-5.041 2.9104 2.5203 1.4552 2.7389-1.4552c0-1.3292-0.072554-2.6584-0.21808-2.9104z" fill="#f3db6c"/>
|
|
||||||
<path d="m6.35 6.35v2.9104h5.041c0.14552-0.25205 0.21807-1.5812 0.21808-2.9104h-5.2591z" fill="#7ab392"/>
|
|
||||||
<path d="m6.35 6.35v2.9104l2.6298 1.6443c1.1511-0.66459 2.2657-1.3923 2.4112-1.6443l-5.041-2.9104z" fill="#4b7cbc"/>
|
|
||||||
<path d="m6.35 6.35-2.5208 1.4552 2.5208 4.3656c0.29104 0 1.4787-0.60148 2.6298-1.2661l-2.6298-4.5548z" fill="#6f488c"/>
|
|
||||||
<path d="m3.8292 4.8948-2.5203 4.3656c0.29104 0.5041 4.459 2.9104 5.041 2.9104v-5.8208l-2.5208-1.4552z" fill="#4d3f33"/>
|
|
||||||
<path d="m1.309 3.4396c-0.29104 0.5041-0.29104 5.3167 0 5.8208l5.041-2.9104v-2.9104h-5.041z" fill="#7a573b"/>
|
|
||||||
<path d="m6.35 0.52917c-0.58208-2e-8 -4.75 2.4063-5.041 2.9104l5.041 2.9104v-5.8208z" fill="#99cd61"/>
|
|
||||||
</g>
|
|
||||||
<g transform="matrix(.88 0 0 .88 -10.906 -1.2421)">
|
|
||||||
<g transform="translate(13.26 2.2776)">
|
|
||||||
<path transform="matrix(.96975 0 0 .96975 .1921 .1921)" d="m6.3498 2.9393c-0.34105 0-2.7827 1.4099-2.9532 1.7052l2.9532 5.1157 2.9538-5.1157c-0.17052-0.29535-2.6127-1.7052-2.9538-1.7052z" fill="#fff" stroke-width=".26458"/>
|
|
||||||
</g>
|
|
||||||
<path d="m16.746 6.9737 2.8639 4.9609c0.33073 0 2.6991-1.3672 2.8644-1.6536 0.16536-0.28642 0.16536-3.0209 0-3.3073l-2.8644 1.6536z" fill="#dfdfdf" stroke-width=".26458"/>
|
|
||||||
</g>
|
|
||||||
<path d="m3.8299 4.8948c-0.14551 0.25205-0.14553 2.6584 0 2.9104 0.14553 0.25204 2.2292 1.4552 2.5203 1.4552v-2.9104z" fill="#d6d2d2" stroke-width=".26458"/>
|
|
||||||
<metadata>
|
|
||||||
<rdf:RDF>
|
|
||||||
<cc:Work rdf:about="">
|
|
||||||
<dc:title>Prism Launcher Logo</dc:title>
|
|
||||||
<dc:date>19/10/2022</dc:date>
|
|
||||||
<dc:creator>
|
|
||||||
<cc:Agent>
|
|
||||||
<dc:title>Prism Launcher</dc:title>
|
|
||||||
</cc:Agent>
|
|
||||||
</dc:creator>
|
|
||||||
<dc:contributor>
|
|
||||||
<cc:Agent>
|
|
||||||
<dc:title>AutiOne, Boba, ely, Fulmine, gon sawa, Pankakes, tobimori, Zeke</dc:title>
|
|
||||||
</cc:Agent>
|
|
||||||
</dc:contributor>
|
|
||||||
<dc:source>https://github.com/PrismLauncher/PrismLauncher</dc:source>
|
|
||||||
<dc:publisher>
|
|
||||||
<cc:Agent>
|
|
||||||
<dc:title>Prism Launcher</dc:title>
|
|
||||||
</cc:Agent>
|
|
||||||
</dc:publisher>
|
|
||||||
<cc:license rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/"/>
|
|
||||||
</cc:Work>
|
|
||||||
<cc:License rdf:about="http://creativecommons.org/licenses/by-sa/4.0/">
|
|
||||||
<cc:permits rdf:resource="http://creativecommons.org/ns#Reproduction"/>
|
|
||||||
<cc:permits rdf:resource="http://creativecommons.org/ns#Distribution"/>
|
|
||||||
<cc:requires rdf:resource="http://creativecommons.org/ns#Notice"/>
|
|
||||||
<cc:requires rdf:resource="http://creativecommons.org/ns#Attribution"/>
|
|
||||||
<cc:permits rdf:resource="http://creativecommons.org/ns#DerivativeWorks"/>
|
|
||||||
<cc:requires rdf:resource="http://creativecommons.org/ns#ShareAlike"/>
|
|
||||||
</cc:License>
|
|
||||||
</rdf:RDF>
|
|
||||||
</metadata>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 3.4 KiB |
@ -16,7 +16,6 @@
|
|||||||
<file>scalable/jarmods.svg</file>
|
<file>scalable/jarmods.svg</file>
|
||||||
<file>scalable/java.svg</file>
|
<file>scalable/java.svg</file>
|
||||||
<file>scalable/language.svg</file>
|
<file>scalable/language.svg</file>
|
||||||
<file>scalable/launcher.svg</file>
|
|
||||||
<file>scalable/loadermods.svg</file>
|
<file>scalable/loadermods.svg</file>
|
||||||
<file>scalable/log.svg</file>
|
<file>scalable/log.svg</file>
|
||||||
<file>scalable/minecraft.svg</file>
|
<file>scalable/minecraft.svg</file>
|
||||||
|
@ -1,57 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
|
||||||
<svg width="48" height="48" version="1.1" viewBox="0 0 12.7 12.7" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
|
||||||
<title>Prism Launcher Logo</title>
|
|
||||||
<g stroke-width=".26458">
|
|
||||||
<path d="m6.35 6.35" fill="#99cd61"/>
|
|
||||||
<path d="m6.35 0.52917-2.5208 4.3656 2.5208 1.4552 2.5203-1.4552 0.10955-3.0996c-1.1511-0.66459-2.3388-1.2661-2.6298-1.2661z" fill="#df6277"/>
|
|
||||||
<path d="m8.9798 1.7952-2.6298 4.5548 2.5203 1.4552 2.5208-4.3656c-0.14552-0.25205-1.2601-0.97975-2.4112-1.6443z" fill="#fb9168"/>
|
|
||||||
<path d="m11.391 3.4396-5.041 2.9104 2.5203 1.4552 2.7389-1.4552c0-1.3292-0.072554-2.6584-0.21808-2.9104z" fill="#f3db6c"/>
|
|
||||||
<path d="m6.35 6.35v2.9104h5.041c0.14552-0.25205 0.21807-1.5812 0.21808-2.9104h-5.2591z" fill="#7ab392"/>
|
|
||||||
<path d="m6.35 6.35v2.9104l2.6298 1.6443c1.1511-0.66459 2.2657-1.3923 2.4112-1.6443l-5.041-2.9104z" fill="#4b7cbc"/>
|
|
||||||
<path d="m6.35 6.35-2.5208 1.4552 2.5208 4.3656c0.29104 0 1.4787-0.60148 2.6298-1.2661l-2.6298-4.5548z" fill="#6f488c"/>
|
|
||||||
<path d="m3.8292 4.8948-2.5203 4.3656c0.29104 0.5041 4.459 2.9104 5.041 2.9104v-5.8208l-2.5208-1.4552z" fill="#4d3f33"/>
|
|
||||||
<path d="m1.309 3.4396c-0.29104 0.5041-0.29104 5.3167 0 5.8208l5.041-2.9104v-2.9104h-5.041z" fill="#7a573b"/>
|
|
||||||
<path d="m6.35 0.52917c-0.58208-2e-8 -4.75 2.4063-5.041 2.9104l5.041 2.9104v-5.8208z" fill="#99cd61"/>
|
|
||||||
</g>
|
|
||||||
<g transform="matrix(.88 0 0 .88 -10.906 -1.2421)">
|
|
||||||
<g transform="translate(13.26 2.2776)">
|
|
||||||
<path transform="matrix(.96975 0 0 .96975 .1921 .1921)" d="m6.3498 2.9393c-0.34105 0-2.7827 1.4099-2.9532 1.7052l2.9532 5.1157 2.9538-5.1157c-0.17052-0.29535-2.6127-1.7052-2.9538-1.7052z" fill="#fff" stroke-width=".26458"/>
|
|
||||||
</g>
|
|
||||||
<path d="m16.746 6.9737 2.8639 4.9609c0.33073 0 2.6991-1.3672 2.8644-1.6536 0.16536-0.28642 0.16536-3.0209 0-3.3073l-2.8644 1.6536z" fill="#dfdfdf" stroke-width=".26458"/>
|
|
||||||
</g>
|
|
||||||
<path d="m3.8299 4.8948c-0.14551 0.25205-0.14553 2.6584 0 2.9104 0.14553 0.25204 2.2292 1.4552 2.5203 1.4552v-2.9104z" fill="#d6d2d2" stroke-width=".26458"/>
|
|
||||||
<metadata>
|
|
||||||
<rdf:RDF>
|
|
||||||
<cc:Work rdf:about="">
|
|
||||||
<dc:title>Prism Launcher Logo</dc:title>
|
|
||||||
<dc:date>19/10/2022</dc:date>
|
|
||||||
<dc:creator>
|
|
||||||
<cc:Agent>
|
|
||||||
<dc:title>Prism Launcher</dc:title>
|
|
||||||
</cc:Agent>
|
|
||||||
</dc:creator>
|
|
||||||
<dc:contributor>
|
|
||||||
<cc:Agent>
|
|
||||||
<dc:title>AutiOne, Boba, ely, Fulmine, gon sawa, Pankakes, tobimori, Zeke</dc:title>
|
|
||||||
</cc:Agent>
|
|
||||||
</dc:contributor>
|
|
||||||
<dc:source>https://github.com/PrismLauncher/PrismLauncher</dc:source>
|
|
||||||
<dc:publisher>
|
|
||||||
<cc:Agent>
|
|
||||||
<dc:title>Prism Launcher</dc:title>
|
|
||||||
</cc:Agent>
|
|
||||||
</dc:publisher>
|
|
||||||
<cc:license rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/"/>
|
|
||||||
</cc:Work>
|
|
||||||
<cc:License rdf:about="http://creativecommons.org/licenses/by-sa/4.0/">
|
|
||||||
<cc:permits rdf:resource="http://creativecommons.org/ns#Reproduction"/>
|
|
||||||
<cc:permits rdf:resource="http://creativecommons.org/ns#Distribution"/>
|
|
||||||
<cc:requires rdf:resource="http://creativecommons.org/ns#Notice"/>
|
|
||||||
<cc:requires rdf:resource="http://creativecommons.org/ns#Attribution"/>
|
|
||||||
<cc:permits rdf:resource="http://creativecommons.org/ns#DerivativeWorks"/>
|
|
||||||
<cc:requires rdf:resource="http://creativecommons.org/ns#ShareAlike"/>
|
|
||||||
</cc:License>
|
|
||||||
</rdf:RDF>
|
|
||||||
</metadata>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 3.4 KiB |
@ -16,7 +16,6 @@
|
|||||||
<file>scalable/jarmods.svg</file>
|
<file>scalable/jarmods.svg</file>
|
||||||
<file>scalable/java.svg</file>
|
<file>scalable/java.svg</file>
|
||||||
<file>scalable/language.svg</file>
|
<file>scalable/language.svg</file>
|
||||||
<file>scalable/launcher.svg</file>
|
|
||||||
<file>scalable/loadermods.svg</file>
|
<file>scalable/loadermods.svg</file>
|
||||||
<file>scalable/log.svg</file>
|
<file>scalable/log.svg</file>
|
||||||
<file>scalable/minecraft.svg</file>
|
<file>scalable/minecraft.svg</file>
|
||||||
|
@ -1,57 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
|
||||||
<svg width="48" height="48" version="1.1" viewBox="0 0 12.7 12.7" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
|
||||||
<title>Prism Launcher Logo</title>
|
|
||||||
<g stroke-width=".26458">
|
|
||||||
<path d="m6.35 6.35" fill="#99cd61"/>
|
|
||||||
<path d="m6.35 0.52917-2.5208 4.3656 2.5208 1.4552 2.5203-1.4552 0.10955-3.0996c-1.1511-0.66459-2.3388-1.2661-2.6298-1.2661z" fill="#df6277"/>
|
|
||||||
<path d="m8.9798 1.7952-2.6298 4.5548 2.5203 1.4552 2.5208-4.3656c-0.14552-0.25205-1.2601-0.97975-2.4112-1.6443z" fill="#fb9168"/>
|
|
||||||
<path d="m11.391 3.4396-5.041 2.9104 2.5203 1.4552 2.7389-1.4552c0-1.3292-0.072554-2.6584-0.21808-2.9104z" fill="#f3db6c"/>
|
|
||||||
<path d="m6.35 6.35v2.9104h5.041c0.14552-0.25205 0.21807-1.5812 0.21808-2.9104h-5.2591z" fill="#7ab392"/>
|
|
||||||
<path d="m6.35 6.35v2.9104l2.6298 1.6443c1.1511-0.66459 2.2657-1.3923 2.4112-1.6443l-5.041-2.9104z" fill="#4b7cbc"/>
|
|
||||||
<path d="m6.35 6.35-2.5208 1.4552 2.5208 4.3656c0.29104 0 1.4787-0.60148 2.6298-1.2661l-2.6298-4.5548z" fill="#6f488c"/>
|
|
||||||
<path d="m3.8292 4.8948-2.5203 4.3656c0.29104 0.5041 4.459 2.9104 5.041 2.9104v-5.8208l-2.5208-1.4552z" fill="#4d3f33"/>
|
|
||||||
<path d="m1.309 3.4396c-0.29104 0.5041-0.29104 5.3167 0 5.8208l5.041-2.9104v-2.9104h-5.041z" fill="#7a573b"/>
|
|
||||||
<path d="m6.35 0.52917c-0.58208-2e-8 -4.75 2.4063-5.041 2.9104l5.041 2.9104v-5.8208z" fill="#99cd61"/>
|
|
||||||
</g>
|
|
||||||
<g transform="matrix(.88 0 0 .88 -10.906 -1.2421)">
|
|
||||||
<g transform="translate(13.26 2.2776)">
|
|
||||||
<path transform="matrix(.96975 0 0 .96975 .1921 .1921)" d="m6.3498 2.9393c-0.34105 0-2.7827 1.4099-2.9532 1.7052l2.9532 5.1157 2.9538-5.1157c-0.17052-0.29535-2.6127-1.7052-2.9538-1.7052z" fill="#fff" stroke-width=".26458"/>
|
|
||||||
</g>
|
|
||||||
<path d="m16.746 6.9737 2.8639 4.9609c0.33073 0 2.6991-1.3672 2.8644-1.6536 0.16536-0.28642 0.16536-3.0209 0-3.3073l-2.8644 1.6536z" fill="#dfdfdf" stroke-width=".26458"/>
|
|
||||||
</g>
|
|
||||||
<path d="m3.8299 4.8948c-0.14551 0.25205-0.14553 2.6584 0 2.9104 0.14553 0.25204 2.2292 1.4552 2.5203 1.4552v-2.9104z" fill="#d6d2d2" stroke-width=".26458"/>
|
|
||||||
<metadata>
|
|
||||||
<rdf:RDF>
|
|
||||||
<cc:Work rdf:about="">
|
|
||||||
<dc:title>Prism Launcher Logo</dc:title>
|
|
||||||
<dc:date>19/10/2022</dc:date>
|
|
||||||
<dc:creator>
|
|
||||||
<cc:Agent>
|
|
||||||
<dc:title>Prism Launcher</dc:title>
|
|
||||||
</cc:Agent>
|
|
||||||
</dc:creator>
|
|
||||||
<dc:contributor>
|
|
||||||
<cc:Agent>
|
|
||||||
<dc:title>AutiOne, Boba, ely, Fulmine, gon sawa, Pankakes, tobimori, Zeke</dc:title>
|
|
||||||
</cc:Agent>
|
|
||||||
</dc:contributor>
|
|
||||||
<dc:source>https://github.com/PrismLauncher/PrismLauncher</dc:source>
|
|
||||||
<dc:publisher>
|
|
||||||
<cc:Agent>
|
|
||||||
<dc:title>Prism Launcher</dc:title>
|
|
||||||
</cc:Agent>
|
|
||||||
</dc:publisher>
|
|
||||||
<cc:license rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/"/>
|
|
||||||
</cc:Work>
|
|
||||||
<cc:License rdf:about="http://creativecommons.org/licenses/by-sa/4.0/">
|
|
||||||
<cc:permits rdf:resource="http://creativecommons.org/ns#Reproduction"/>
|
|
||||||
<cc:permits rdf:resource="http://creativecommons.org/ns#Distribution"/>
|
|
||||||
<cc:requires rdf:resource="http://creativecommons.org/ns#Notice"/>
|
|
||||||
<cc:requires rdf:resource="http://creativecommons.org/ns#Attribution"/>
|
|
||||||
<cc:permits rdf:resource="http://creativecommons.org/ns#DerivativeWorks"/>
|
|
||||||
<cc:requires rdf:resource="http://creativecommons.org/ns#ShareAlike"/>
|
|
||||||
</cc:License>
|
|
||||||
</rdf:RDF>
|
|
||||||
</metadata>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 3.4 KiB |
@ -16,7 +16,6 @@
|
|||||||
<file>scalable/jarmods.svg</file>
|
<file>scalable/jarmods.svg</file>
|
||||||
<file>scalable/java.svg</file>
|
<file>scalable/java.svg</file>
|
||||||
<file>scalable/language.svg</file>
|
<file>scalable/language.svg</file>
|
||||||
<file>scalable/launcher.svg</file>
|
|
||||||
<file>scalable/loadermods.svg</file>
|
<file>scalable/loadermods.svg</file>
|
||||||
<file>scalable/log.svg</file>
|
<file>scalable/log.svg</file>
|
||||||
<file>scalable/minecraft.svg</file>
|
<file>scalable/minecraft.svg</file>
|
||||||
|
@ -1,57 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
|
||||||
<svg width="48" height="48" version="1.1" viewBox="0 0 12.7 12.7" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
|
||||||
<title>Prism Launcher Logo</title>
|
|
||||||
<g stroke-width=".26458">
|
|
||||||
<path d="m6.35 6.35" fill="#99cd61"/>
|
|
||||||
<path d="m6.35 0.52917-2.5208 4.3656 2.5208 1.4552 2.5203-1.4552 0.10955-3.0996c-1.1511-0.66459-2.3388-1.2661-2.6298-1.2661z" fill="#df6277"/>
|
|
||||||
<path d="m8.9798 1.7952-2.6298 4.5548 2.5203 1.4552 2.5208-4.3656c-0.14552-0.25205-1.2601-0.97975-2.4112-1.6443z" fill="#fb9168"/>
|
|
||||||
<path d="m11.391 3.4396-5.041 2.9104 2.5203 1.4552 2.7389-1.4552c0-1.3292-0.072554-2.6584-0.21808-2.9104z" fill="#f3db6c"/>
|
|
||||||
<path d="m6.35 6.35v2.9104h5.041c0.14552-0.25205 0.21807-1.5812 0.21808-2.9104h-5.2591z" fill="#7ab392"/>
|
|
||||||
<path d="m6.35 6.35v2.9104l2.6298 1.6443c1.1511-0.66459 2.2657-1.3923 2.4112-1.6443l-5.041-2.9104z" fill="#4b7cbc"/>
|
|
||||||
<path d="m6.35 6.35-2.5208 1.4552 2.5208 4.3656c0.29104 0 1.4787-0.60148 2.6298-1.2661l-2.6298-4.5548z" fill="#6f488c"/>
|
|
||||||
<path d="m3.8292 4.8948-2.5203 4.3656c0.29104 0.5041 4.459 2.9104 5.041 2.9104v-5.8208l-2.5208-1.4552z" fill="#4d3f33"/>
|
|
||||||
<path d="m1.309 3.4396c-0.29104 0.5041-0.29104 5.3167 0 5.8208l5.041-2.9104v-2.9104h-5.041z" fill="#7a573b"/>
|
|
||||||
<path d="m6.35 0.52917c-0.58208-2e-8 -4.75 2.4063-5.041 2.9104l5.041 2.9104v-5.8208z" fill="#99cd61"/>
|
|
||||||
</g>
|
|
||||||
<g transform="matrix(.88 0 0 .88 -10.906 -1.2421)">
|
|
||||||
<g transform="translate(13.26 2.2776)">
|
|
||||||
<path transform="matrix(.96975 0 0 .96975 .1921 .1921)" d="m6.3498 2.9393c-0.34105 0-2.7827 1.4099-2.9532 1.7052l2.9532 5.1157 2.9538-5.1157c-0.17052-0.29535-2.6127-1.7052-2.9538-1.7052z" fill="#fff" stroke-width=".26458"/>
|
|
||||||
</g>
|
|
||||||
<path d="m16.746 6.9737 2.8639 4.9609c0.33073 0 2.6991-1.3672 2.8644-1.6536 0.16536-0.28642 0.16536-3.0209 0-3.3073l-2.8644 1.6536z" fill="#dfdfdf" stroke-width=".26458"/>
|
|
||||||
</g>
|
|
||||||
<path d="m3.8299 4.8948c-0.14551 0.25205-0.14553 2.6584 0 2.9104 0.14553 0.25204 2.2292 1.4552 2.5203 1.4552v-2.9104z" fill="#d6d2d2" stroke-width=".26458"/>
|
|
||||||
<metadata>
|
|
||||||
<rdf:RDF>
|
|
||||||
<cc:Work rdf:about="">
|
|
||||||
<dc:title>Prism Launcher Logo</dc:title>
|
|
||||||
<dc:date>19/10/2022</dc:date>
|
|
||||||
<dc:creator>
|
|
||||||
<cc:Agent>
|
|
||||||
<dc:title>Prism Launcher</dc:title>
|
|
||||||
</cc:Agent>
|
|
||||||
</dc:creator>
|
|
||||||
<dc:contributor>
|
|
||||||
<cc:Agent>
|
|
||||||
<dc:title>AutiOne, Boba, ely, Fulmine, gon sawa, Pankakes, tobimori, Zeke</dc:title>
|
|
||||||
</cc:Agent>
|
|
||||||
</dc:contributor>
|
|
||||||
<dc:source>https://github.com/PrismLauncher/PrismLauncher</dc:source>
|
|
||||||
<dc:publisher>
|
|
||||||
<cc:Agent>
|
|
||||||
<dc:title>Prism Launcher</dc:title>
|
|
||||||
</cc:Agent>
|
|
||||||
</dc:publisher>
|
|
||||||
<cc:license rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/"/>
|
|
||||||
</cc:Work>
|
|
||||||
<cc:License rdf:about="http://creativecommons.org/licenses/by-sa/4.0/">
|
|
||||||
<cc:permits rdf:resource="http://creativecommons.org/ns#Reproduction"/>
|
|
||||||
<cc:permits rdf:resource="http://creativecommons.org/ns#Distribution"/>
|
|
||||||
<cc:requires rdf:resource="http://creativecommons.org/ns#Notice"/>
|
|
||||||
<cc:requires rdf:resource="http://creativecommons.org/ns#Attribution"/>
|
|
||||||
<cc:permits rdf:resource="http://creativecommons.org/ns#DerivativeWorks"/>
|
|
||||||
<cc:requires rdf:resource="http://creativecommons.org/ns#ShareAlike"/>
|
|
||||||
</cc:License>
|
|
||||||
</rdf:RDF>
|
|
||||||
</metadata>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 3.4 KiB |
@ -16,7 +16,6 @@
|
|||||||
<file>scalable/jarmods.svg</file>
|
<file>scalable/jarmods.svg</file>
|
||||||
<file>scalable/java.svg</file>
|
<file>scalable/java.svg</file>
|
||||||
<file>scalable/language.svg</file>
|
<file>scalable/language.svg</file>
|
||||||
<file>scalable/launcher.svg</file>
|
|
||||||
<file>scalable/loadermods.svg</file>
|
<file>scalable/loadermods.svg</file>
|
||||||
<file>scalable/log.svg</file>
|
<file>scalable/log.svg</file>
|
||||||
<file>scalable/minecraft.svg</file>
|
<file>scalable/minecraft.svg</file>
|
||||||
|
@ -1,57 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
|
||||||
<svg width="48" height="48" version="1.1" viewBox="0 0 12.7 12.7" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
|
||||||
<title>Prism Launcher Logo</title>
|
|
||||||
<g stroke-width=".26458">
|
|
||||||
<path d="m6.35 6.35" fill="#99cd61"/>
|
|
||||||
<path d="m6.35 0.52917-2.5208 4.3656 2.5208 1.4552 2.5203-1.4552 0.10955-3.0996c-1.1511-0.66459-2.3388-1.2661-2.6298-1.2661z" fill="#df6277"/>
|
|
||||||
<path d="m8.9798 1.7952-2.6298 4.5548 2.5203 1.4552 2.5208-4.3656c-0.14552-0.25205-1.2601-0.97975-2.4112-1.6443z" fill="#fb9168"/>
|
|
||||||
<path d="m11.391 3.4396-5.041 2.9104 2.5203 1.4552 2.7389-1.4552c0-1.3292-0.072554-2.6584-0.21808-2.9104z" fill="#f3db6c"/>
|
|
||||||
<path d="m6.35 6.35v2.9104h5.041c0.14552-0.25205 0.21807-1.5812 0.21808-2.9104h-5.2591z" fill="#7ab392"/>
|
|
||||||
<path d="m6.35 6.35v2.9104l2.6298 1.6443c1.1511-0.66459 2.2657-1.3923 2.4112-1.6443l-5.041-2.9104z" fill="#4b7cbc"/>
|
|
||||||
<path d="m6.35 6.35-2.5208 1.4552 2.5208 4.3656c0.29104 0 1.4787-0.60148 2.6298-1.2661l-2.6298-4.5548z" fill="#6f488c"/>
|
|
||||||
<path d="m3.8292 4.8948-2.5203 4.3656c0.29104 0.5041 4.459 2.9104 5.041 2.9104v-5.8208l-2.5208-1.4552z" fill="#4d3f33"/>
|
|
||||||
<path d="m1.309 3.4396c-0.29104 0.5041-0.29104 5.3167 0 5.8208l5.041-2.9104v-2.9104h-5.041z" fill="#7a573b"/>
|
|
||||||
<path d="m6.35 0.52917c-0.58208-2e-8 -4.75 2.4063-5.041 2.9104l5.041 2.9104v-5.8208z" fill="#99cd61"/>
|
|
||||||
</g>
|
|
||||||
<g transform="matrix(.88 0 0 .88 -10.906 -1.2421)">
|
|
||||||
<g transform="translate(13.26 2.2776)">
|
|
||||||
<path transform="matrix(.96975 0 0 .96975 .1921 .1921)" d="m6.3498 2.9393c-0.34105 0-2.7827 1.4099-2.9532 1.7052l2.9532 5.1157 2.9538-5.1157c-0.17052-0.29535-2.6127-1.7052-2.9538-1.7052z" fill="#fff" stroke-width=".26458"/>
|
|
||||||
</g>
|
|
||||||
<path d="m16.746 6.9737 2.8639 4.9609c0.33073 0 2.6991-1.3672 2.8644-1.6536 0.16536-0.28642 0.16536-3.0209 0-3.3073l-2.8644 1.6536z" fill="#dfdfdf" stroke-width=".26458"/>
|
|
||||||
</g>
|
|
||||||
<path d="m3.8299 4.8948c-0.14551 0.25205-0.14553 2.6584 0 2.9104 0.14553 0.25204 2.2292 1.4552 2.5203 1.4552v-2.9104z" fill="#d6d2d2" stroke-width=".26458"/>
|
|
||||||
<metadata>
|
|
||||||
<rdf:RDF>
|
|
||||||
<cc:Work rdf:about="">
|
|
||||||
<dc:title>Prism Launcher Logo</dc:title>
|
|
||||||
<dc:date>19/10/2022</dc:date>
|
|
||||||
<dc:creator>
|
|
||||||
<cc:Agent>
|
|
||||||
<dc:title>Prism Launcher</dc:title>
|
|
||||||
</cc:Agent>
|
|
||||||
</dc:creator>
|
|
||||||
<dc:contributor>
|
|
||||||
<cc:Agent>
|
|
||||||
<dc:title>AutiOne, Boba, ely, Fulmine, gon sawa, Pankakes, tobimori, Zeke</dc:title>
|
|
||||||
</cc:Agent>
|
|
||||||
</dc:contributor>
|
|
||||||
<dc:source>https://github.com/PrismLauncher/PrismLauncher</dc:source>
|
|
||||||
<dc:publisher>
|
|
||||||
<cc:Agent>
|
|
||||||
<dc:title>Prism Launcher</dc:title>
|
|
||||||
</cc:Agent>
|
|
||||||
</dc:publisher>
|
|
||||||
<cc:license rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/"/>
|
|
||||||
</cc:Work>
|
|
||||||
<cc:License rdf:about="http://creativecommons.org/licenses/by-sa/4.0/">
|
|
||||||
<cc:permits rdf:resource="http://creativecommons.org/ns#Reproduction"/>
|
|
||||||
<cc:permits rdf:resource="http://creativecommons.org/ns#Distribution"/>
|
|
||||||
<cc:requires rdf:resource="http://creativecommons.org/ns#Notice"/>
|
|
||||||
<cc:requires rdf:resource="http://creativecommons.org/ns#Attribution"/>
|
|
||||||
<cc:permits rdf:resource="http://creativecommons.org/ns#DerivativeWorks"/>
|
|
||||||
<cc:requires rdf:resource="http://creativecommons.org/ns#ShareAlike"/>
|
|
||||||
</cc:License>
|
|
||||||
</rdf:RDF>
|
|
||||||
</metadata>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 3.4 KiB |
@ -224,6 +224,13 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi
|
|||||||
// disabled until we have an instance selected
|
// disabled until we have an instance selected
|
||||||
ui->instanceToolBar->setEnabled(false);
|
ui->instanceToolBar->setEnabled(false);
|
||||||
setInstanceActionsEnabled(false);
|
setInstanceActionsEnabled(false);
|
||||||
|
|
||||||
|
// add a close button at the end of the main toolbar when running on gamescope / steam deck
|
||||||
|
// FIXME: detect if we don't have server side decorations instead
|
||||||
|
if (qgetenv("XDG_CURRENT_DESKTOP") == "gamescope") {
|
||||||
|
ui->mainToolBar->addAction(ui->actionCloseWindow);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// add the toolbar toggles to the view menu
|
// add the toolbar toggles to the view menu
|
||||||
@ -1503,140 +1510,113 @@ void MainWindow::on_actionKillInstance_triggered()
|
|||||||
|
|
||||||
void MainWindow::on_actionCreateInstanceShortcut_triggered()
|
void MainWindow::on_actionCreateInstanceShortcut_triggered()
|
||||||
{
|
{
|
||||||
if (m_selectedInstance)
|
if (!m_selectedInstance)
|
||||||
{
|
return;
|
||||||
auto desktopPath = FS::getDesktopDir();
|
auto desktopPath = FS::getDesktopDir();
|
||||||
if (desktopPath.isEmpty()) {
|
if (desktopPath.isEmpty()) {
|
||||||
// TODO come up with an alternative solution (open "save file" dialog)
|
// TODO come up with an alternative solution (open "save file" dialog)
|
||||||
QMessageBox::critical(this, tr("Create instance shortcut"), tr("Couldn't find desktop?!"));
|
QMessageBox::critical(this, tr("Create instance shortcut"), tr("Couldn't find desktop?!"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString desktopFilePath;
|
||||||
|
QString appPath = QApplication::applicationFilePath();
|
||||||
|
QString iconPath;
|
||||||
|
QStringList args;
|
||||||
#if defined(Q_OS_MACOS)
|
#if defined(Q_OS_MACOS)
|
||||||
QString appPath = QApplication::applicationFilePath();
|
if (appPath.startsWith("/private/var/")) {
|
||||||
if (appPath.startsWith("/private/var/")) {
|
QMessageBox::critical(this, tr("Create instance shortcut"),
|
||||||
QMessageBox::critical(this, tr("Create instance shortcut"), tr("The launcher is in the folder it was extracted from, therefore it cannot create shortcuts."));
|
tr("The launcher is in the folder it was extracted from, therefore it cannot create shortcuts."));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (FS::createShortcut(FS::PathCombine(desktopPath, m_selectedInstance->name()),
|
|
||||||
appPath, { "--launch", m_selectedInstance->id() },
|
|
||||||
m_selectedInstance->name(), "")) {
|
|
||||||
QMessageBox::information(this, tr("Create instance shortcut"), tr("Created a shortcut to this instance on your desktop!"));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create instance shortcut!"));
|
|
||||||
}
|
|
||||||
#elif defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
|
#elif defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
|
||||||
QString appPath = QApplication::applicationFilePath();
|
if (appPath.startsWith("/tmp/.mount_")) {
|
||||||
if (appPath.startsWith("/tmp/.mount_")) {
|
// AppImage!
|
||||||
// AppImage!
|
appPath = QProcessEnvironment::systemEnvironment().value(QStringLiteral("APPIMAGE"));
|
||||||
appPath = QProcessEnvironment::systemEnvironment().value(QStringLiteral("APPIMAGE"));
|
if (appPath.isEmpty()) {
|
||||||
if (appPath.isEmpty())
|
QMessageBox::critical(this, tr("Create instance shortcut"),
|
||||||
{
|
tr("Launcher is running as misconfigured AppImage? ($APPIMAGE environment variable is missing)"));
|
||||||
QMessageBox::critical(this, tr("Create instance shortcut"), tr("Launcher is running as misconfigured AppImage? ($APPIMAGE environment variable is missing)"));
|
} else if (appPath.endsWith("/")) {
|
||||||
}
|
appPath.chop(1);
|
||||||
else if (appPath.endsWith("/"))
|
|
||||||
{
|
|
||||||
appPath.chop(1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
auto icon = APPLICATION->icons()->icon(m_selectedInstance->iconKey());
|
auto icon = APPLICATION->icons()->icon(m_selectedInstance->iconKey());
|
||||||
if (icon == nullptr)
|
if (icon == nullptr) {
|
||||||
{
|
icon = APPLICATION->icons()->icon("grass");
|
||||||
icon = APPLICATION->icons()->icon("grass");
|
}
|
||||||
}
|
|
||||||
|
|
||||||
QString iconPath = FS::PathCombine(m_selectedInstance->instanceRoot(), "icon.png");
|
iconPath = FS::PathCombine(m_selectedInstance->instanceRoot(), "icon.png");
|
||||||
|
|
||||||
QFile iconFile(iconPath);
|
QFile iconFile(iconPath);
|
||||||
if (!iconFile.open(QFile::WriteOnly))
|
if (!iconFile.open(QFile::WriteOnly)) {
|
||||||
{
|
QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create icon for shortcut."));
|
||||||
QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create icon for shortcut."));
|
return;
|
||||||
return;
|
}
|
||||||
}
|
bool success = icon->icon().pixmap(64, 64).save(&iconFile, "PNG");
|
||||||
bool success = icon->icon().pixmap(64, 64).save(&iconFile, "PNG");
|
iconFile.close();
|
||||||
iconFile.close();
|
|
||||||
|
|
||||||
if (!success)
|
if (!success) {
|
||||||
{
|
iconFile.remove();
|
||||||
iconFile.remove();
|
QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create icon for shortcut."));
|
||||||
QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create icon for shortcut."));
|
return;
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
|
if (DesktopServices::isFlatpak()) {
|
||||||
|
desktopFilePath = FS::PathCombine(desktopPath, FS::RemoveInvalidFilenameChars(m_selectedInstance->name()) + ".desktop");
|
||||||
|
QFileDialog fileDialog;
|
||||||
|
// workaround to make sure the portal file dialog opens in the desktop directory
|
||||||
|
fileDialog.setDirectoryUrl(desktopPath);
|
||||||
|
desktopFilePath = fileDialog.getSaveFileName(this, tr("Create Shortcut"), desktopFilePath, tr("Desktop Entries (*.desktop)"));
|
||||||
|
if (desktopFilePath.isEmpty())
|
||||||
|
return; // file dialog canceled by user
|
||||||
|
appPath = "flatpak";
|
||||||
|
QString flatpakAppId = BuildConfig.LAUNCHER_DESKTOPFILENAME;
|
||||||
|
flatpakAppId.remove(".desktop");
|
||||||
|
args.append({ "run", flatpakAppId });
|
||||||
|
}
|
||||||
|
|
||||||
QString desktopFilePath = FS::PathCombine(desktopPath, m_selectedInstance->name() + ".desktop");
|
|
||||||
QStringList args;
|
|
||||||
if (DesktopServices::isFlatpak()) {
|
|
||||||
QFileDialog fileDialog;
|
|
||||||
// workaround to make sure the portal file dialog opens in the desktop directory
|
|
||||||
fileDialog.setDirectoryUrl(desktopPath);
|
|
||||||
desktopFilePath = fileDialog.getSaveFileName(
|
|
||||||
this, tr("Create Shortcut"), desktopFilePath,
|
|
||||||
tr("Desktop Entries (*.desktop)"));
|
|
||||||
if (desktopFilePath.isEmpty())
|
|
||||||
return; // file dialog canceled by user
|
|
||||||
appPath = "flatpak";
|
|
||||||
QString flatpakAppId = BuildConfig.LAUNCHER_DESKTOPFILENAME;
|
|
||||||
flatpakAppId.remove(".desktop");
|
|
||||||
args.append({ "run", flatpakAppId });
|
|
||||||
}
|
|
||||||
args.append({ "--launch", m_selectedInstance->id() });
|
|
||||||
if (FS::createShortcut(desktopFilePath, appPath, args, m_selectedInstance->name(), iconPath)) {
|
|
||||||
QMessageBox::information(this, tr("Create instance shortcut"), tr("Created a shortcut to this instance on your desktop!"));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
iconFile.remove();
|
|
||||||
QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create instance shortcut!"));
|
|
||||||
}
|
|
||||||
#elif defined(Q_OS_WIN)
|
#elif defined(Q_OS_WIN)
|
||||||
auto icon = APPLICATION->icons()->icon(m_selectedInstance->iconKey());
|
auto icon = APPLICATION->icons()->icon(m_selectedInstance->iconKey());
|
||||||
if (icon == nullptr)
|
if (icon == nullptr) {
|
||||||
{
|
icon = APPLICATION->icons()->icon("grass");
|
||||||
icon = APPLICATION->icons()->icon("grass");
|
}
|
||||||
}
|
|
||||||
|
|
||||||
QString iconPath = FS::PathCombine(m_selectedInstance->instanceRoot(), "icon.ico");
|
iconPath = FS::PathCombine(m_selectedInstance->instanceRoot(), "icon.ico");
|
||||||
|
|
||||||
// part of fix for weird bug involving the window icon being replaced
|
// part of fix for weird bug involving the window icon being replaced
|
||||||
// dunno why it happens, but this 2-line fix seems to be enough, so w/e
|
// dunno why it happens, but this 2-line fix seems to be enough, so w/e
|
||||||
auto appIcon = APPLICATION->getThemedIcon("logo");
|
auto appIcon = APPLICATION->getThemedIcon("logo");
|
||||||
|
|
||||||
QFile iconFile(iconPath);
|
QFile iconFile(iconPath);
|
||||||
if (!iconFile.open(QFile::WriteOnly))
|
if (!iconFile.open(QFile::WriteOnly)) {
|
||||||
{
|
QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create icon for shortcut."));
|
||||||
QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create icon for shortcut."));
|
return;
|
||||||
return;
|
}
|
||||||
}
|
bool success = icon->icon().pixmap(64, 64).save(&iconFile, "ICO");
|
||||||
bool success = icon->icon().pixmap(64, 64).save(&iconFile, "ICO");
|
iconFile.close();
|
||||||
iconFile.close();
|
|
||||||
|
|
||||||
// restore original window icon
|
// restore original window icon
|
||||||
QGuiApplication::setWindowIcon(appIcon);
|
QGuiApplication::setWindowIcon(appIcon);
|
||||||
|
|
||||||
if (!success)
|
if (!success) {
|
||||||
{
|
iconFile.remove();
|
||||||
iconFile.remove();
|
QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create icon for shortcut."));
|
||||||
QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create icon for shortcut."));
|
return;
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (FS::createShortcut(FS::PathCombine(desktopPath, m_selectedInstance->name()),
|
|
||||||
QApplication::applicationFilePath(), { "--launch", m_selectedInstance->id() },
|
|
||||||
m_selectedInstance->name(), iconPath)) {
|
|
||||||
QMessageBox::information(this, tr("Create instance shortcut"), tr("Created a shortcut to this instance on your desktop!"));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
iconFile.remove();
|
|
||||||
QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create instance shortcut!"));
|
|
||||||
}
|
|
||||||
#else
|
#else
|
||||||
QMessageBox::critical(this, tr("Create instance shortcut"), tr("Not supported on your platform!"));
|
QMessageBox::critical(this, tr("Create instance shortcut"), tr("Not supported on your platform!"));
|
||||||
|
return;
|
||||||
#endif
|
#endif
|
||||||
|
args.append({ "--launch", m_selectedInstance->id() });
|
||||||
|
if (FS::createShortcut(desktopFilePath, appPath, args, m_selectedInstance->name(), iconPath)) {
|
||||||
|
QMessageBox::information(this, tr("Create instance shortcut"), tr("Created a shortcut to this instance on your desktop!"));
|
||||||
|
} else {
|
||||||
|
#if not defined(Q_OS_MACOS)
|
||||||
|
iconFile.remove();
|
||||||
|
#endif
|
||||||
|
QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create instance shortcut!"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -577,7 +577,7 @@
|
|||||||
<normaloff>.</normaloff>.</iconset>
|
<normaloff>.</normaloff>.</iconset>
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Report a &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>
|
||||||
|
@ -71,13 +71,18 @@ QString getCreditsHtml()
|
|||||||
//: %1 is the name of the launcher, determined at build time, e.g. "Prism Launcher Developers"
|
//: %1 is the name of the launcher, determined at build time, e.g. "Prism Launcher Developers"
|
||||||
stream << "<h3>" << QObject::tr("%1 Developers", "About Credits").arg(BuildConfig.LAUNCHER_DISPLAYNAME) << "</h3>\n";
|
stream << "<h3>" << QObject::tr("%1 Developers", "About Credits").arg(BuildConfig.LAUNCHER_DISPLAYNAME) << "</h3>\n";
|
||||||
stream << QString("<p>Sefa Eyeoglu (Scrumplex) %1</p>\n") .arg(getWebsite("https://scrumplex.net"));
|
stream << QString("<p>Sefa Eyeoglu (Scrumplex) %1</p>\n") .arg(getWebsite("https://scrumplex.net"));
|
||||||
stream << QString("<p>dada513 %1</p>\n") .arg(getGitHub("dada513"));
|
stream << QString("<p>d-513 %1</p>\n") .arg(getGitHub("d-513"));
|
||||||
stream << QString("<p>txtsd %1</p>\n") .arg(getWebsite("https://ihavea.quest"));
|
stream << QString("<p>txtsd %1</p>\n") .arg(getWebsite("https://ihavea.quest"));
|
||||||
stream << QString("<p>timoreo %1</p>\n") .arg(getGitHub("timoreo22"));
|
stream << QString("<p>timoreo %1</p>\n") .arg(getGitHub("timoreo22"));
|
||||||
stream << QString("<p>Ezekiel Smith (ZekeSmith) %1</p>\n") .arg(getGitHub("ZekeSmith"));
|
stream << QString("<p>Ezekiel Smith (ZekeSmith) %1</p>\n") .arg(getGitHub("ZekeSmith"));
|
||||||
stream << QString("<p>cozyGalvinism %1</p>\n") .arg(getGitHub("cozyGalvinism"));
|
stream << QString("<p>cozyGalvinism %1</p>\n") .arg(getGitHub("cozyGalvinism"));
|
||||||
stream << QString("<p>DioEgizio %1</p>\n") .arg(getGitHub("DioEgizio"));
|
stream << QString("<p>DioEgizio %1</p>\n") .arg(getGitHub("DioEgizio"));
|
||||||
stream << QString("<p>flowln %1</p>\n") .arg(getGitHub("flowln"));
|
stream << QString("<p>flowln %1</p>\n") .arg(getGitHub("flowln"));
|
||||||
|
stream << QString("<p>ViRb3 %1</p>\n") .arg(getGitHub("ViRb3"));
|
||||||
|
stream << QString("<p>Rachel Powers (Ryex) %1</p>\n") .arg(getGitHub("Ryex"));
|
||||||
|
stream << QString("<p>TayouVR %1</p>\n") .arg(getGitHub("TayouVR"));
|
||||||
|
stream << QString("<p>TheKodeToad %1</p>\n") .arg(getGitHub("TheKodeToad"));
|
||||||
|
stream << QString("<p>getchoo %1</p>\n") .arg(getGitHub("getchoo"));
|
||||||
stream << "<br />\n";
|
stream << "<br />\n";
|
||||||
|
|
||||||
// TODO: possibly retrieve from git history at build time?
|
// TODO: possibly retrieve from git history at build time?
|
||||||
|
@ -35,24 +35,26 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "ExportInstanceDialog.h"
|
#include "ExportInstanceDialog.h"
|
||||||
#include "ui_ExportInstanceDialog.h"
|
|
||||||
#include <BaseInstance.h>
|
#include <BaseInstance.h>
|
||||||
#include <MMCZip.h>
|
#include <MMCZip.h>
|
||||||
#include <QFileDialog>
|
#include <QFileDialog>
|
||||||
#include <QMessageBox>
|
|
||||||
#include <QFileSystemModel>
|
#include <QFileSystemModel>
|
||||||
|
#include <QMessageBox>
|
||||||
|
#include "FileIgnoreProxy.h"
|
||||||
|
#include "ui_ExportInstanceDialog.h"
|
||||||
|
|
||||||
#include <QSortFilterProxyModel>
|
|
||||||
#include <QDebug>
|
|
||||||
#include <QSaveFile>
|
|
||||||
#include <QStack>
|
|
||||||
#include <QFileInfo>
|
|
||||||
#include "SeparatorPrefixTree.h"
|
|
||||||
#include "Application.h"
|
|
||||||
#include <icons/IconList.h>
|
|
||||||
#include <FileSystem.h>
|
#include <FileSystem.h>
|
||||||
|
#include <icons/IconList.h>
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include <QSaveFile>
|
||||||
|
#include <QSortFilterProxyModel>
|
||||||
|
#include <QStack>
|
||||||
|
#include <functional>
|
||||||
|
#include "Application.h"
|
||||||
|
#include "SeparatorPrefixTree.h"
|
||||||
|
|
||||||
ExportInstanceDialog::ExportInstanceDialog(InstancePtr instance, QWidget *parent)
|
ExportInstanceDialog::ExportInstanceDialog(InstancePtr instance, QWidget* parent)
|
||||||
: QDialog(parent), ui(new Ui::ExportInstanceDialog), m_instance(instance)
|
: QDialog(parent), ui(new Ui::ExportInstanceDialog), m_instance(instance)
|
||||||
{
|
{
|
||||||
ui->setupUi(this);
|
ui->setupUi(this);
|
||||||
@ -60,8 +62,12 @@ ExportInstanceDialog::ExportInstanceDialog(InstancePtr instance, QWidget *parent
|
|||||||
model->setIconProvider(&icons);
|
model->setIconProvider(&icons);
|
||||||
auto root = instance->instanceRoot();
|
auto root = instance->instanceRoot();
|
||||||
proxyModel = new FileIgnoreProxy(root, this);
|
proxyModel = new FileIgnoreProxy(root, this);
|
||||||
loadPackIgnore();
|
|
||||||
proxyModel->setSourceModel(model);
|
proxyModel->setSourceModel(model);
|
||||||
|
auto prefix = QDir(instance->instanceRoot()).relativeFilePath(instance->gameRoot());
|
||||||
|
proxyModel->ignoreFilesWithPath().insert({ FS::PathCombine(prefix, "logs"), FS::PathCombine(prefix, "crash-reports") });
|
||||||
|
proxyModel->ignoreFilesWithName().append({ ".DS_Store", "thumbs.db", "Thumbs.db" });
|
||||||
|
loadPackIgnore();
|
||||||
|
|
||||||
ui->treeView->setModel(proxyModel);
|
ui->treeView->setModel(proxyModel);
|
||||||
ui->treeView->setRootIndex(proxyModel->mapFromSource(model->index(root)));
|
ui->treeView->setRootIndex(proxyModel->mapFromSource(model->index(root)));
|
||||||
ui->treeView->sortByColumn(0, Qt::AscendingOrder);
|
ui->treeView->sortByColumn(0, Qt::AscendingOrder);
|
||||||
@ -133,11 +139,9 @@ bool ExportInstanceDialog::doExport()
|
|||||||
|
|
||||||
SaveIcon(m_instance);
|
SaveIcon(m_instance);
|
||||||
|
|
||||||
auto & blocked = proxyModel->blockedPaths();
|
|
||||||
using std::placeholders::_1;
|
|
||||||
auto files = QFileInfoList();
|
auto files = QFileInfoList();
|
||||||
if (!MMCZip::collectFileListRecursively(m_instance->instanceRoot(), nullptr, &files,
|
if (!MMCZip::collectFileListRecursively(m_instance->instanceRoot(), nullptr, &files,
|
||||||
std::bind(&SeparatorPrefixTree<'/'>::covers, blocked, _1))) {
|
std::bind(&FileIgnoreProxy::filterFile, proxyModel, std::placeholders::_1))) {
|
||||||
QMessageBox::warning(this, tr("Error"), tr("Unable to export instance"));
|
QMessageBox::warning(this, tr("Error"), tr("Unable to export instance"));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -52,8 +52,9 @@ ExportMrPackDialog::ExportMrPackDialog(InstancePtr instance, QWidget* parent)
|
|||||||
// use the game root - everything outside cannot be exported
|
// use the game root - everything outside cannot be exported
|
||||||
const QDir root(instance->gameRoot());
|
const QDir root(instance->gameRoot());
|
||||||
proxy = new FileIgnoreProxy(instance->gameRoot(), this);
|
proxy = new FileIgnoreProxy(instance->gameRoot(), this);
|
||||||
|
proxy->ignoreFilesWithPath().insert({ "logs", "crash-reports" });
|
||||||
|
proxy->ignoreFilesWithName().append({ ".DS_Store", "thumbs.db", "Thumbs.db" });
|
||||||
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);
|
||||||
|
|
||||||
@ -99,7 +100,7 @@ void ExportMrPackDialog::done(int result)
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
ModrinthPackExportTask task(ui->name->text(), ui->version->text(), ui->summary->text(), instance, output,
|
ModrinthPackExportTask task(ui->name->text(), ui->version->text(), ui->summary->text(), instance, output,
|
||||||
[this](const QString& path) { return proxy->blockedPaths().covers(path); });
|
std::bind(&FileIgnoreProxy::filterFile, proxy, std::placeholders::_1));
|
||||||
|
|
||||||
connect(&task, &Task::failed,
|
connect(&task, &Task::failed,
|
||||||
[this](const QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); });
|
[this](const QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); });
|
||||||
|
@ -54,7 +54,7 @@
|
|||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#include "ui/widgets/PageContainer.h"
|
#include "ui/widgets/PageContainer.h"
|
||||||
#include "ui/pages/modplatform/VanillaPage.h"
|
#include "ui/pages/modplatform/CustomPage.h"
|
||||||
#include "ui/pages/modplatform/atlauncher/AtlPage.h"
|
#include "ui/pages/modplatform/atlauncher/AtlPage.h"
|
||||||
#include "ui/pages/modplatform/legacy_ftb/Page.h"
|
#include "ui/pages/modplatform/legacy_ftb/Page.h"
|
||||||
#include "ui/pages/modplatform/flame/FlamePage.h"
|
#include "ui/pages/modplatform/flame/FlamePage.h"
|
||||||
@ -162,7 +162,7 @@ QList<BasePage *> NewInstanceDialog::getPages()
|
|||||||
|
|
||||||
importPage = new ImportPage(this);
|
importPage = new ImportPage(this);
|
||||||
|
|
||||||
pages.append(new VanillaPage(this));
|
pages.append(new CustomPage(this));
|
||||||
pages.append(importPage);
|
pages.append(importPage);
|
||||||
pages.append(new AtlPage(this));
|
pages.append(new AtlPage(this));
|
||||||
if (APPLICATION->capabilities() & Application::SupportsFlame)
|
if (APPLICATION->capabilities() & Application::SupportsFlame)
|
||||||
|
@ -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));
|
||||||
|
|
||||||
|