This commit is contained in:
Alexandru Ionut Tripon 2026-06-26 11:59:25 -04:00 committed by GitHub
commit 74af7592c8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
30 changed files with 1193 additions and 1181 deletions

View file

@ -46,13 +46,10 @@
#include "Application.h"
#include "Json.h"
#include "launch/LaunchTask.h"
#include "settings/INISettingsObject.h"
#include "settings/OverrideSetting.h"
#include "settings/Setting.h"
#include "BuildConfig.h"
#include "Commandline.h"
#include "FileSystem.h"
int getConsoleMaxLines(SettingsObject* settings)
{
@ -71,20 +68,18 @@ bool shouldStopOnConsoleOverflow(SettingsObject* settings)
return settings->get("ConsoleOverflowStop").toBool();
}
BaseInstance::BaseInstance(SettingsObject* globalSettings, std::unique_ptr<SettingsObject> settings, const QString& rootDir) : QObject()
BaseInstance::BaseInstance(SettingsObject* globalSettings, std::unique_ptr<SettingsObject> settings, QString rootDir)
: m_rootDir(std::move(rootDir)), m_settings(std::move(settings)), m_global_settings(globalSettings)
{
m_settings = std::move(settings);
m_global_settings = globalSettings;
m_rootDir = rootDir;
m_settings->registerSetting("name", "Unnamed Instance");
m_settings->registerSetting("iconKey", "default");
m_settings->registerSetting("notes", "");
m_settings->registerSetting("lastLaunchTime", 0);
m_settings->registerSetting("totalTimePlayed", 0);
if (m_settings->get("totalTimePlayed").toLongLong() < 0)
if (m_settings->get("totalTimePlayed").toLongLong() < 0) {
m_settings->reset("totalTimePlayed");
}
m_settings->registerSetting("lastTimePlayed", 0);
m_settings->registerSetting("linkedInstances", "[]");
@ -97,8 +92,9 @@ BaseInstance::BaseInstance(SettingsObject* globalSettings, std::unique_ptr<Setti
// NOTE: Sometimees InstanceType is already registered, as it was used to identify the type of
// a locally stored instance
if (!m_settings->getSetting("InstanceType"))
if (!m_settings->getSetting("InstanceType")) {
m_settings->registerSetting("InstanceType", "");
}
// Custom Commands
auto commandSetting = m_settings->registerSetting({ "OverrideCommands", "OverrideLaunchCmd" }, false);
@ -128,8 +124,6 @@ BaseInstance::BaseInstance(SettingsObject* globalSettings, std::unique_ptr<Setti
m_settings->registerSetting("Profiler", "");
}
BaseInstance::~BaseInstance() {}
QString BaseInstance::getPreLaunchCommand()
{
return settings()->get("PreLaunchCommand").toString();
@ -189,22 +183,6 @@ void BaseInstance::setManagedPack(const QString& type,
m_settings->set("ManagedPackVersionName", version);
}
void BaseInstance::copyManagedPack(BaseInstance& other)
{
m_settings->set("ManagedPack", other.isManagedPack());
m_settings->set("ManagedPackType", other.getManagedPackType());
m_settings->set("ManagedPackID", other.getManagedPackID());
m_settings->set("ManagedPackName", other.getManagedPackName());
m_settings->set("ManagedPackVersionID", other.getManagedPackVersionID());
m_settings->set("ManagedPackVersionName", other.getManagedPackVersionName());
if (APPLICATION->settings()->get("AutomaticJavaSwitch").toBool() && m_settings->get("AutomaticJava").toBool() &&
m_settings->get("OverrideJavaLocation").toBool()) {
m_settings->set("OverrideJavaLocation", false);
m_settings->set("JavaPath", "");
}
}
QStringList BaseInstance::getLinkedInstances() const
{
auto setting = m_settings->get("linkedInstances").toString();
@ -226,7 +204,7 @@ void BaseInstance::addLinkedInstanceId(const QString& id)
bool BaseInstance::removeLinkedInstanceId(const QString& id)
{
auto linkedInstances = getLinkedInstances();
int numRemoved = linkedInstances.removeAll(id);
auto numRemoved = linkedInstances.removeAll(id);
setLinkedInstances(linkedInstances);
return numRemoved > 0;
}
@ -237,7 +215,7 @@ bool BaseInstance::isLinkedToInstanceId(const QString& id) const
return linkedInstances.contains(id);
}
void BaseInstance::iconUpdated(QString key)
void BaseInstance::iconUpdated(const QString& key)
{
if (iconKey() == key) {
emit propertiesChanged(this);
@ -276,8 +254,9 @@ bool BaseInstance::isRunning() const
void BaseInstance::setRunning(bool running)
{
if (running == m_isRunning)
if (running == m_isRunning) {
return;
}
m_isRunning = running;
@ -368,7 +347,7 @@ void BaseInstance::setLastLaunch(qint64 val)
emit propertiesChanged(this);
}
void BaseInstance::setNotes(QString val)
void BaseInstance::setNotes(const QString& val)
{
// FIXME: if no change, do not set. setting involves saving a file.
m_settings->set("notes", val);
@ -379,7 +358,7 @@ QString BaseInstance::notes() const
return m_settings->get("notes").toString();
}
void BaseInstance::setIconKey(QString val)
void BaseInstance::setIconKey(const QString& val)
{
// FIXME: if no change, do not set. setting involves saving a file.
m_settings->set("iconKey", val);
@ -391,7 +370,7 @@ QString BaseInstance::iconKey() const
return m_settings->get("iconKey").toString();
}
void BaseInstance::setName(QString val)
void BaseInstance::setName(const QString& val)
{
// FIXME: if no change, do not set. setting involves saving a file.
m_settings->set("name", val);
@ -430,19 +409,23 @@ QList<ShortcutData> BaseInstance::shortcuts() const
auto data = m_settings->get("shortcuts").toString().toUtf8();
QJsonParseError parseError;
auto document = QJsonDocument::fromJson(data, &parseError);
if (parseError.error != QJsonParseError::NoError || !document.isArray())
if (parseError.error != QJsonParseError::NoError || !document.isArray()) {
return {};
}
QList<ShortcutData> results;
for (const auto& elem : document.array()) {
if (!elem.isObject())
if (!elem.isObject()) {
continue;
}
auto dict = elem.toObject();
if (!dict.contains("name") || !dict.contains("filePath") || !dict.contains("target"))
if (!dict.contains("name") || !dict.contains("filePath") || !dict.contains("target")) {
continue;
}
int value = dict["target"].toInt(-1);
if (!dict["name"].isString() || !dict["filePath"].isString() || value < 0 || value >= 3)
if (!dict.value("name").isString() || !dict.value("filePath").isString() || value < 0 || value >= 3) {
continue;
}
QString shortcutName = dict["name"].toString();
QString filePath = dict["filePath"].toString();
@ -481,7 +464,7 @@ void BaseInstance::updateRuntimeContext()
// NOOP
}
bool BaseInstance::isLegacy()
bool BaseInstance::isLegacy() const
{
return traits().contains("legacyLaunch") || traits().contains("alphaLaunch");
}

View file

@ -45,16 +45,12 @@
#include <QObject>
#include <QProcess>
#include <QSet>
#include <cstdint>
#include "QObjectPtr.h"
#include "settings/SettingsObject.h"
#include "BaseVersionList.h"
#include "MessageLevel.h"
#include "minecraft/auth/MinecraftAccount.h"
#include "settings/INIFile.h"
#include "net/Mode.h"
#include "RuntimeContext.h"
#include "minecraft/launch/MinecraftTarget.h"
@ -65,7 +61,7 @@ class LaunchTask;
class BaseInstance;
/// Shortcut saving target representations
enum class ShortcutTarget { Desktop, Applications, Other };
enum class ShortcutTarget : std::uint8_t { Desktop, Applications, Other };
/// Shortcut data representation
struct ShortcutData {
@ -90,17 +86,17 @@ class BaseInstance : public QObject {
Q_OBJECT
protected:
/// no-touchy!
BaseInstance(SettingsObject* globalSettings, std::unique_ptr<SettingsObject> settings, const QString& rootDir);
BaseInstance(SettingsObject* globalSettings, std::unique_ptr<SettingsObject> settings, QString rootDir);
public: /* types */
enum class Status {
enum class Status : std::uint8_t {
Present,
Gone // either nuked or invalidated
};
public:
/// virtual destructor to make sure the destruction is COMPLETE
virtual ~BaseInstance();
~BaseInstance() override = default;
virtual void saveNow() = 0;
@ -136,7 +132,7 @@ class BaseInstance : public QObject {
virtual QString modsRoot() const = 0;
QString name() const;
void setName(QString val);
void setName(const QString& val);
/// Sync name and rename instance dir accordingly; returns true if successful
bool syncInstanceDirName(const QString& newRoot) const;
@ -150,10 +146,10 @@ class BaseInstance : public QObject {
QString windowTitle() const;
QString iconKey() const;
void setIconKey(QString val);
void setIconKey(const QString& val);
QString notes() const;
void setNotes(QString val);
void setNotes(const QString& val);
QString getPreLaunchCommand();
QString getPostExitCommand();
@ -166,7 +162,6 @@ class BaseInstance : public QObject {
QString getManagedPackVersionID() const;
QString getManagedPackVersionName() const;
void setManagedPack(const QString& type, const QString& id, const QString& name, const QString& versionId, const QString& version);
void copyManagedPack(BaseInstance& other);
virtual QStringList extraArguments();
@ -278,7 +273,7 @@ class BaseInstance : public QObject {
bool removeLinkedInstanceId(const QString& id);
bool isLinkedToInstanceId(const QString& id) const;
bool isLegacy();
bool isLegacy() const;
protected:
void changeStatus(Status newStatus);
@ -303,7 +298,7 @@ class BaseInstance : public QObject {
void statusChanged(Status from, Status to);
protected slots:
void iconUpdated(QString key);
void iconUpdated(const QString& key);
protected: /* data */
QString m_rootDir;

View file

@ -41,8 +41,6 @@ set(CORE_SOURCES
PSaveFile.h
# Basic instance manipulation tasks (derived from InstanceTask)
InstanceCreationTask.h
InstanceCreationTask.cpp
InstanceCopyPrefs.h
InstanceCopyPrefs.cpp
InstanceCopyTask.h

View file

@ -1,142 +0,0 @@
#include "InstanceCreationTask.h"
#include <QDebug>
#include <QFile>
#include "Application.h"
#include "InstanceTask.h"
#include "minecraft/MinecraftLoadAndCheck.h"
#include "tasks/SequentialTask.h"
bool InstanceCreationTask::abort()
{
if (!canAbort()) {
return false;
}
m_abort = true;
if (m_gameFilesTask) {
return m_gameFilesTask->abort();
}
return InstanceTask::abort();
}
void InstanceCreationTask::executeTask()
{
setAbortable(true);
if (updateInstance()) {
emitSucceeded();
return;
}
// When the user aborted in the update stage.
if (m_abort) {
emitAborted();
return;
}
m_instance = createInstance();
if (!m_instance) {
if (m_abort) {
return;
}
qWarning() << "Instance creation failed!";
if (!m_error_message.isEmpty()) {
qWarning() << "Reason:" << m_error_message;
emitFailed(tr("Error while creating new instance:\n%1").arg(m_error_message));
} else {
emitFailed(tr("Error while creating new instance."));
}
return;
}
// If this is set, it means we're updating an instance. So, we now need to remove the
// files scheduled to, and we'd better not let the user abort in the middle of it, since it'd
// put the instance in an invalid state.
if (shouldOverride()) {
bool deleteFailed = false;
setAbortable(false);
setStatus(tr("Removing old conflicting files..."));
qDebug() << "Removing old files";
for (const QString& path : m_filesToRemove) {
if (!QFile::exists(path)) {
continue;
}
qDebug() << "Removing" << path;
if (!QFile::remove(path)) {
qCritical() << "Could not remove" << path;
deleteFailed = true;
}
}
if (deleteFailed) {
emitFailed(tr("Failed to remove old conflicting files."));
return;
}
}
if (!m_abort) {
if (!APPLICATION->settings()->get("DownloadGameFilesDuringInstanceCreation").toBool()) {
emitSucceeded();
return;
}
setAbortable(true);
setAbortButtonText(tr("Skip"));
qDebug() << "Downloading game files";
auto updateTasks = m_instance->createUpdateTask();
if (updateTasks.isEmpty()) {
emitSucceeded();
return;
}
auto task = makeShared<SequentialTask>();
task->addTask(makeShared<MinecraftLoadAndCheck>(m_instance.get(), Net::Mode::Online));
for (const auto& t : updateTasks) {
task->addTask(t);
}
connect(task.get(), &Task::finished, this, [this, task] {
if (task->wasSuccessful() || m_abort) {
emitSucceeded();
} else {
emitFailed(tr("Could not download game files: %1").arg(task->failReason()));
}
});
propagateFromOther(task.get());
setDetails(tr("Downloading game files"));
m_gameFilesTask = task;
m_gameFilesTask->start();
}
}
void InstanceCreationTask::scheduleToDelete(QWidget* parent, const QDir& dir, const QString& path, bool checkDisabled)
{
if (path.isEmpty()) {
return;
}
if (path.startsWith("saves/")) {
if (m_shouldDeleteSaves == ShouldDeleteSaves::NotAsked) {
m_shouldDeleteSaves = askIfShouldDeleteSaves(parent);
}
if (m_shouldDeleteSaves == ShouldDeleteSaves::No) {
return;
}
}
qDebug() << "Scheduling" << path << "for removal";
m_filesToRemove.append(dir.absoluteFilePath(path));
if (checkDisabled) {
if (path.endsWith(".disabled")) { // remove it if it was enabled/disabled by user
m_filesToRemove.append(dir.absoluteFilePath(path.chopped(9)));
} else {
m_filesToRemove.append(dir.absoluteFilePath(path + ".disabled"));
}
}
}

View file

@ -1,53 +0,0 @@
#pragma once
#include "BaseVersion.h"
#include "InstanceTask.h"
#include "minecraft/MinecraftInstance.h"
class InstanceCreationTask : public InstanceTask {
Q_OBJECT
public:
InstanceCreationTask() = default;
virtual ~InstanceCreationTask() = default;
bool abort() override;
protected:
void executeTask() final override;
/**
* Tries to update an already existing instance.
*
* This can be implemented by subclasses to provide a way of updating an already existing
* instance, according to that implementation's concept of 'identity' (i.e. instances that
* are updates / downgrades of one another).
*
* If this returns true, createInstance() will not run, so you should do all update steps in here.
* Otherwise, createInstance() is run as normal.
*/
virtual bool updateInstance() { return false; };
/**
* Creates a new instance.
*
* Returns the instance if it was created or nullptr otherwise.
*/
virtual std::unique_ptr<MinecraftInstance> createInstance() { return nullptr; }
QString getError() const { return m_error_message; }
protected:
void setError(const QString& message) { m_error_message = message; };
void scheduleToDelete(QWidget* parent, const QDir& dir, const QString& path, bool checkDisabled = false);
protected:
bool m_abort = false;
QStringList m_filesToRemove;
ShouldDeleteSaves m_shouldDeleteSaves;
private:
QString m_error_message;
std::unique_ptr<MinecraftInstance> m_instance;
Task::Ptr m_gameFilesTask;
};

View file

@ -58,19 +58,22 @@
#include <QFileInfo>
#include <QtConcurrentRun>
#include <memory>
#include <utility>
InstanceImportTask::InstanceImportTask(const QUrl& sourceUrl, QWidget* parent, QMap<QString, QString>&& extra_info)
: m_sourceUrl(sourceUrl), m_extra_info(extra_info), m_parent(parent)
InstanceImportTask::InstanceImportTask(QUrl sourceUrl, QWidget* parent, QMap<QString, QString> extraInfo)
: m_sourceUrl(std::move(sourceUrl)), m_extra_info(std::move(extraInfo)), m_parent(parent)
{}
bool InstanceImportTask::abort()
{
if (!canAbort())
if (!canAbort()) {
return false;
}
bool wasAborted = false;
if (m_task)
if (m_task) {
wasAborted = m_task->abort();
}
return wasAborted;
}
@ -107,16 +110,20 @@ void InstanceImportTask::downloadFromUrl()
m_task.reset(filesNetJob);
filesNetJob->start();
}
namespace {
QString cleanPath(QString path)
QString cleanPath(const QString& path)
{
if (path == ".")
if (path == ".") {
return QString();
}
QString result = path;
if (result.startsWith("./"))
if (result.startsWith("./")) {
result = result.mid(2);
}
return result;
}
} // namespace
void InstanceImportTask::processZipPack()
{
@ -187,7 +194,7 @@ void InstanceImportTask::processZipPack()
connect(zipTask.get(), &Task::failed, this, [this, progressStep](QString reason) {
progressStep->state = TaskStepState::Failed;
stepProgress(*progressStep);
emitFailed(reason);
emitFailed(std::move(reason));
});
connect(zipTask.get(), &Task::stepProgress, this, &InstanceImportTask::propagateStepProgress);
@ -196,7 +203,7 @@ void InstanceImportTask::processZipPack()
stepProgress(*progressStep);
});
connect(zipTask.get(), &Task::status, this, [this, progressStep](QString status) {
progressStep->status = status;
progressStep->status = std::move(status);
stepProgress(*progressStep);
});
connect(zipTask.get(), &Task::warningLogged, this, [this](const QString& line) { m_Warnings.append(line); });
@ -251,16 +258,20 @@ void InstanceImportTask::extractFinished()
}
}
bool installIcon(QString root, QString instIconKey)
namespace {
bool installIcon(const QString& root, const QString& instIconKey)
{
auto importIconPath = IconUtils::findBestIconIn(root, instIconKey);
if (importIconPath.isNull() || !QFile::exists(importIconPath))
if (importIconPath.isNull() || !QFile::exists(importIconPath)) {
importIconPath = IconUtils::findBestIconIn(root, "icon.png");
if (importIconPath.isNull() || !QFile::exists(importIconPath))
}
if (importIconPath.isNull() || !QFile::exists(importIconPath)) {
importIconPath = IconUtils::findBestIconIn(FS::PathCombine(root, "overrides"), "icon.png");
}
if (!importIconPath.isNull() && QFile::exists(importIconPath)) {
// import icon
auto iconList = APPLICATION->icons();
auto* iconList = APPLICATION->icons();
if (iconList->iconFileExists(instIconKey)) {
iconList->deleteIcon(instIconKey);
}
@ -269,32 +280,35 @@ bool installIcon(QString root, QString instIconKey)
}
return false;
}
} // namespace
void InstanceImportTask::processFlame()
{
shared_qobject_ptr<FlameCreationTask> inst_creation_task = nullptr;
shared_qobject_ptr<FlameCreationTask> instCreationTask = nullptr;
if (!m_extra_info.isEmpty()) {
auto pack_id_it = m_extra_info.constFind("pack_id");
Q_ASSERT(pack_id_it != m_extra_info.constEnd());
auto pack_id = pack_id_it.value();
auto packIdIt = m_extra_info.constFind("pack_id");
Q_ASSERT(packIdIt != m_extra_info.constEnd());
const auto& packId = packIdIt.value();
auto pack_version_id_it = m_extra_info.constFind("pack_version_id");
Q_ASSERT(pack_version_id_it != m_extra_info.constEnd());
auto pack_version_id = pack_version_id_it.value();
auto packVersionIdIt = m_extra_info.constFind("pack_version_id");
Q_ASSERT(packVersionIdIt != m_extra_info.constEnd());
const auto& packVersionId = packVersionIdIt.value();
QString original_instance_id;
auto original_instance_id_it = m_extra_info.constFind("original_instance_id");
if (original_instance_id_it != m_extra_info.constEnd())
original_instance_id = original_instance_id_it.value();
QString originalInstanceId;
auto originalInstanceIdIt = m_extra_info.constFind("original_instance_id");
if (originalInstanceIdIt != m_extra_info.constEnd()) {
originalInstanceId = originalInstanceIdIt.value();
}
inst_creation_task =
makeShared<FlameCreationTask>(m_stagingPath, m_globalSettings, m_parent, pack_id, pack_version_id, original_instance_id);
instCreationTask =
makeShared<FlameCreationTask>(m_stagingPath, m_globalSettings, m_parent, packId, packVersionId, originalInstanceId);
} else {
// FIXME: Find a way to get IDs in directly imported ZIPs
inst_creation_task = makeShared<FlameCreationTask>(m_stagingPath, m_globalSettings, m_parent, QString(), QString());
instCreationTask = makeShared<FlameCreationTask>(m_stagingPath, m_globalSettings, m_parent, QString(), QString());
}
inst_creation_task->setName(*this);
instCreationTask->setName(modifiedName());
instCreationTask->setOriginalName(originalName(), version());
// if the icon was specified by user, use that. otherwise pull icon from the pack
if (m_instIcon == "default") {
auto iconKey = QString("Flame_%1_Icon").arg(name());
@ -303,30 +317,30 @@ void InstanceImportTask::processFlame()
m_instIcon = iconKey;
}
}
inst_creation_task->setIcon(m_instIcon);
inst_creation_task->setGroup(m_instGroup);
inst_creation_task->setConfirmUpdate(shouldConfirmUpdate());
instCreationTask->setIcon(m_instIcon);
instCreationTask->setGroup(m_instGroup);
instCreationTask->setConfirmUpdate(shouldConfirmUpdate());
auto weak = inst_creation_task.toWeakRef();
connect(inst_creation_task.get(), &Task::succeeded, this, [this, weak] {
auto weak = instCreationTask.toWeakRef();
connect(instCreationTask.get(), &Task::succeeded, this, [this, weak] {
if (auto sp = weak.lock()) {
setOverride(sp->shouldOverride(), sp->originalInstanceID());
}
emitSucceeded();
});
connect(inst_creation_task.get(), &Task::failed, this, &InstanceImportTask::emitFailed);
connect(inst_creation_task.get(), &Task::progress, this, &InstanceImportTask::setProgress);
connect(inst_creation_task.get(), &Task::stepProgress, this, &InstanceImportTask::propagateStepProgress);
connect(inst_creation_task.get(), &Task::status, this, &InstanceImportTask::setStatus);
connect(inst_creation_task.get(), &Task::details, this, &InstanceImportTask::setDetails);
connect(instCreationTask.get(), &Task::failed, this, &InstanceImportTask::emitFailed);
connect(instCreationTask.get(), &Task::progress, this, &InstanceImportTask::setProgress);
connect(instCreationTask.get(), &Task::stepProgress, this, &InstanceImportTask::propagateStepProgress);
connect(instCreationTask.get(), &Task::status, this, &InstanceImportTask::setStatus);
connect(instCreationTask.get(), &Task::details, this, &InstanceImportTask::setDetails);
connect(inst_creation_task.get(), &Task::aborted, this, &InstanceImportTask::emitAborted);
connect(inst_creation_task.get(), &Task::abortStatusChanged, this, &Task::setAbortable);
connect(inst_creation_task.get(), &Task::abortButtonTextChanged, this, &Task::setAbortButtonText);
connect(instCreationTask.get(), &Task::aborted, this, &InstanceImportTask::emitAborted);
connect(instCreationTask.get(), &Task::abortStatusChanged, this, &Task::setAbortable);
connect(instCreationTask.get(), &Task::abortButtonTextChanged, this, &Task::setAbortButtonText);
connect(inst_creation_task.get(), &Task::warningLogged, this, [this](const QString& line) { m_Warnings.append(line); });
connect(instCreationTask.get(), &Task::warningLogged, this, [this](const QString& line) { m_Warnings.append(line); });
m_task.reset(inst_creation_task);
m_task.reset(instCreationTask);
setAbortable(true);
m_task->start();
}
@ -365,36 +379,39 @@ void InstanceImportTask::processMultiMC()
void InstanceImportTask::processModrinth()
{
shared_qobject_ptr<ModrinthCreationTask> inst_creation_task = nullptr;
shared_qobject_ptr<ModrinthCreationTask> instCreationTask = nullptr;
if (!m_extra_info.isEmpty()) {
auto pack_id_it = m_extra_info.constFind("pack_id");
Q_ASSERT(pack_id_it != m_extra_info.constEnd());
auto pack_id = pack_id_it.value();
auto packIdIt = m_extra_info.constFind("pack_id");
Q_ASSERT(packIdIt != m_extra_info.constEnd());
const auto& packId = packIdIt.value();
QString pack_version_id;
auto pack_version_id_it = m_extra_info.constFind("pack_version_id");
if (pack_version_id_it != m_extra_info.constEnd())
pack_version_id = pack_version_id_it.value();
QString packVersionId;
auto packVersionIdIt = m_extra_info.constFind("pack_version_id");
if (packVersionIdIt != m_extra_info.constEnd()) {
packVersionId = packVersionIdIt.value();
}
QString original_instance_id;
auto original_instance_id_it = m_extra_info.constFind("original_instance_id");
if (original_instance_id_it != m_extra_info.constEnd())
original_instance_id = original_instance_id_it.value();
QString originalInstanceId;
auto originalInstanceIdIt = m_extra_info.constFind("original_instance_id");
if (originalInstanceIdIt != m_extra_info.constEnd()) {
originalInstanceId = originalInstanceIdIt.value();
}
inst_creation_task =
makeShared<ModrinthCreationTask>(m_stagingPath, m_globalSettings, m_parent, pack_id, pack_version_id, original_instance_id);
instCreationTask =
makeShared<ModrinthCreationTask>(m_stagingPath, m_globalSettings, m_parent, packId, packVersionId, originalInstanceId);
} else {
QString pack_id;
QString packId;
if (!m_sourceUrl.isEmpty()) {
static const QRegularExpression s_regex(R"(data\/([^\/]*)\/versions)");
pack_id = s_regex.match(m_sourceUrl.toString()).captured(1);
packId = s_regex.match(m_sourceUrl.toString()).captured(1);
}
// FIXME: Find a way to get the ID in directly imported ZIPs
inst_creation_task = makeShared<ModrinthCreationTask>(m_stagingPath, m_globalSettings, m_parent, pack_id);
instCreationTask = makeShared<ModrinthCreationTask>(m_stagingPath, m_globalSettings, m_parent, packId);
}
inst_creation_task->setName(*this);
instCreationTask->setName(modifiedName());
instCreationTask->setOriginalName(originalName(), version());
// if the icon was specified by user, use that. otherwise pull icon from the pack
if (m_instIcon == "default") {
auto iconKey = QString("Modrinth_%1_Icon").arg(name());
@ -403,30 +420,30 @@ void InstanceImportTask::processModrinth()
m_instIcon = iconKey;
}
}
inst_creation_task->setIcon(m_instIcon);
inst_creation_task->setGroup(m_instGroup);
inst_creation_task->setConfirmUpdate(shouldConfirmUpdate());
instCreationTask->setIcon(m_instIcon);
instCreationTask->setGroup(m_instGroup);
instCreationTask->setConfirmUpdate(shouldConfirmUpdate());
auto weak = inst_creation_task.toWeakRef();
connect(inst_creation_task.get(), &Task::succeeded, this, [this, weak] {
auto weak = instCreationTask.toWeakRef();
connect(instCreationTask.get(), &Task::succeeded, this, [this, weak] {
if (auto sp = weak.lock()) {
setOverride(sp->shouldOverride(), sp->originalInstanceID());
}
emitSucceeded();
});
connect(inst_creation_task.get(), &Task::failed, this, &InstanceImportTask::emitFailed);
connect(inst_creation_task.get(), &Task::progress, this, &InstanceImportTask::setProgress);
connect(inst_creation_task.get(), &Task::stepProgress, this, &InstanceImportTask::propagateStepProgress);
connect(inst_creation_task.get(), &Task::status, this, &InstanceImportTask::setStatus);
connect(inst_creation_task.get(), &Task::details, this, &InstanceImportTask::setDetails);
connect(instCreationTask.get(), &Task::failed, this, &InstanceImportTask::emitFailed);
connect(instCreationTask.get(), &Task::progress, this, &InstanceImportTask::setProgress);
connect(instCreationTask.get(), &Task::stepProgress, this, &InstanceImportTask::propagateStepProgress);
connect(instCreationTask.get(), &Task::status, this, &InstanceImportTask::setStatus);
connect(instCreationTask.get(), &Task::details, this, &InstanceImportTask::setDetails);
connect(inst_creation_task.get(), &Task::aborted, this, &InstanceImportTask::emitAborted);
connect(inst_creation_task.get(), &Task::abortStatusChanged, this, &Task::setAbortable);
connect(inst_creation_task.get(), &Task::abortButtonTextChanged, this, &Task::setAbortButtonText);
connect(instCreationTask.get(), &Task::aborted, this, &InstanceImportTask::emitAborted);
connect(instCreationTask.get(), &Task::abortStatusChanged, this, &Task::setAbortable);
connect(instCreationTask.get(), &Task::abortButtonTextChanged, this, &Task::setAbortButtonText);
connect(inst_creation_task.get(), &Task::warningLogged, this, [this](const QString& line) { m_Warnings.append(line); });
connect(instCreationTask.get(), &Task::warningLogged, this, [this](const QString& line) { m_Warnings.append(line); });
m_task.reset(inst_creation_task);
m_task.reset(instCreationTask);
setAbortable(true);
m_task->start();
}

View file

@ -43,19 +43,20 @@
class InstanceImportTask : public InstanceTask {
Q_OBJECT
public:
explicit InstanceImportTask(const QUrl& sourceUrl, QWidget* parent = nullptr, QMap<QString, QString>&& extra_info = {});
virtual ~InstanceImportTask() = default;
explicit InstanceImportTask(QUrl sourceUrl, QWidget* parent = nullptr, QMap<QString, QString> extraInfo = {});
~InstanceImportTask() override = default;
bool abort() override;
protected:
//! Entry point for tasks.
virtual void executeTask() override;
void executeTask() override;
private:
void processMultiMC();
void processTechnic();
void processFlame();
void processModrinth();
void downloadFromUrl();
private slots:
void processZipPack();
@ -65,7 +66,7 @@ class InstanceImportTask : public InstanceTask {
QUrl m_sourceUrl;
QString m_archivePath;
Task::Ptr m_task;
enum class ModpackType {
enum class ModpackType : std::uint8_t {
Unknown,
MultiMC,
Technic,
@ -79,5 +80,4 @@ class InstanceImportTask : public InstanceTask {
// FIXME: nuke
QWidget* m_parent;
void downloadFromUrl();
};

View file

@ -47,6 +47,7 @@
#include <QStack>
#include <QTimer>
#include <QUuid>
#include <algorithm>
#include "BaseInstance.h"
#include "ExponentialSeries.h"
@ -62,7 +63,7 @@
#include <windows.h>
#endif
const static int GROUP_FILE_FORMAT_VERSION = 1;
const static int g_GROUP_FILE_FORMAT_VERSION = 1;
InstanceList::InstanceList(SettingsObject* settings, const QString& instDir, QObject* parent)
: QAbstractListModel(parent), m_globalSettings(settings)
@ -82,8 +83,6 @@ InstanceList::InstanceList(SettingsObject* settings, const QString& instDir, QOb
m_watcher->addPath(m_instDir);
}
InstanceList::~InstanceList() {}
Qt::DropActions InstanceList::supportedDragActions() const
{
return Qt::MoveAction;
@ -100,10 +99,7 @@ bool InstanceList::canDropMimeData(const QMimeData* data,
[[maybe_unused]] int column,
[[maybe_unused]] const QModelIndex& parent) const
{
if (data && data->hasFormat("application/x-instanceid")) {
return true;
}
return false;
return (data != nullptr) && data->hasFormat("application/x-instanceid");
}
bool InstanceList::dropMimeData(const QMimeData* data,
@ -112,10 +108,7 @@ bool InstanceList::dropMimeData(const QMimeData* data,
[[maybe_unused]] int column,
[[maybe_unused]] const QModelIndex& parent)
{
if (data && data->hasFormat("application/x-instanceid")) {
return true;
}
return false;
return (data != nullptr) && data->hasFormat("application/x-instanceid");
}
QStringList InstanceList::mimeTypes() const
@ -127,7 +120,7 @@ QStringList InstanceList::mimeTypes() const
QMimeData* InstanceList::mimeData(const QModelIndexList& indexes) const
{
auto mimeData = QAbstractListModel::mimeData(indexes);
auto* mimeData = QAbstractListModel::mimeData(indexes);
if (indexes.size() == 1) {
auto instanceId = data(indexes[0], InstanceIDRole).toString();
mimeData->setData("application/x-instanceid", instanceId.toUtf8());
@ -138,9 +131,10 @@ QMimeData* InstanceList::mimeData(const QModelIndexList& indexes) const
QStringList InstanceList::getLinkedInstancesById(const QString& id) const
{
QStringList linkedInstances;
for (auto& inst : m_instances) {
if (inst->isLinkedToInstanceId(id))
for (const auto& inst : m_instances) {
if (inst->isLinkedToInstanceId(id)) {
linkedInstances.append(inst->id());
}
}
return linkedInstances;
}
@ -154,8 +148,9 @@ int InstanceList::rowCount(const QModelIndex& parent) const
QModelIndex InstanceList::index(int row, int column, const QModelIndex& parent) const
{
Q_UNUSED(parent);
if (row < 0 || row >= count())
return QModelIndex();
if (row < 0 || row >= count()) {
return {};
}
return createIndex(row, column, m_instances.at(row).get());
}
@ -164,7 +159,7 @@ QVariant InstanceList::data(const QModelIndex& index, int role) const
if (!index.isValid()) {
return QVariant();
}
BaseInstance* pdata = static_cast<BaseInstance*>(index.internalPointer());
auto* pdata = static_cast<BaseInstance*>(index.internalPointer());
switch (role) {
case InstancePointerRole: {
QVariant v = QVariant::fromValue((void*)pdata);
@ -204,7 +199,7 @@ bool InstanceList::setData(const QModelIndex& index, const QVariant& value, int
if (role != Qt::EditRole) {
return false;
}
BaseInstance* pdata = static_cast<BaseInstance*>(index.internalPointer());
auto* pdata = static_cast<BaseInstance*>(index.internalPointer());
auto newName = value.toString();
if (pdata->name() == newName) {
return true;
@ -224,23 +219,24 @@ Qt::ItemFlags InstanceList::flags(const QModelIndex& index) const
GroupId InstanceList::getInstanceGroup(const InstanceId& id) const
{
auto inst = getInstanceById(id);
auto* inst = getInstanceById(id);
if (!inst) {
return GroupId();
return {};
}
auto iter = m_instanceGroupIndex.find(inst->id());
if (iter != m_instanceGroupIndex.end()) {
return *iter;
}
return GroupId();
return {};
}
void InstanceList::setInstanceGroup(const InstanceId& id, GroupId name)
{
if (name.isEmpty() && !name.isNull())
if (name.isEmpty() && !name.isNull()) {
name = QString();
}
auto inst = getInstanceById(id);
auto* inst = getInstanceById(id);
if (!inst) {
qDebug() << "Attempt to set a null instance's group";
return;
@ -287,19 +283,22 @@ void InstanceList::deleteGroup(const GroupId& name)
qDebug() << "Remove" << instID << "from group" << name;
removed = true;
auto idx = getInstIndex(instance.get());
if (idx >= 0)
if (idx >= 0) {
emit dataChanged(index(idx), index(idx), { GroupRole });
}
}
}
if (removed)
if (removed) {
saveGroupList();
}
}
void InstanceList::renameGroup(const QString& src, const QString& dst)
{
m_groupNameCache.remove(src);
if (m_collapsedGroups.remove(src))
if (m_collapsedGroups.remove(src)) {
m_collapsedGroups.insert(dst);
}
bool modified = false;
qDebug() << "Rename group" << src << "to" << dst;
@ -312,12 +311,14 @@ void InstanceList::renameGroup(const QString& src, const QString& dst)
qDebug() << "Set" << instID << "group to" << dst;
modified = true;
auto idx = getInstIndex(instance.get());
if (idx >= 0)
if (idx >= 0) {
emit dataChanged(index(idx), index(idx), { GroupRole });
}
}
}
if (modified)
if (modified) {
saveGroupList();
}
}
bool InstanceList::isGroupCollapsed(const QString& group)
@ -327,7 +328,7 @@ bool InstanceList::isGroupCollapsed(const QString& group)
bool InstanceList::trashInstance(const InstanceId& id)
{
auto inst = getInstanceById(id);
auto* inst = getInstanceById(id);
if (!inst) {
qWarning() << "Cannot trash instance" << id << ". No such instance is present (deleted externally?).";
return false;
@ -338,7 +339,7 @@ bool InstanceList::trashInstance(const InstanceId& id)
qDebug() << "Will trash instance" << id;
QString trashedLoc;
if (m_instanceGroupIndex.remove(id)) {
if (m_instanceGroupIndex.remove(id) != 0) {
decreaseGroupCount(cachedGroupId);
saveGroupList();
}
@ -422,7 +423,7 @@ bool InstanceList::undoTrashInstance()
void InstanceList::deleteInstance(const InstanceId& id)
{
auto inst = getInstanceById(id);
auto* inst = getInstanceById(id);
if (!inst) {
qWarning() << "Cannot delete instance" << id << ". No such instance is present (deleted externally?).";
return;
@ -430,7 +431,7 @@ void InstanceList::deleteInstance(const InstanceId& id)
QString cachedGroupId = m_instanceGroupIndex[id];
if (m_instanceGroupIndex.remove(id)) {
if (m_instanceGroupIndex.remove(id) != 0) {
decreaseGroupCount(cachedGroupId);
saveGroupList();
}
@ -452,11 +453,12 @@ void InstanceList::deleteInstance(const InstanceId& id)
}
}
static QMap<InstanceId, InstanceLocator> getIdMapping(const std::vector<std::unique_ptr<BaseInstance>>& list)
namespace {
QMap<InstanceId, InstanceLocator> getIdMapping(const std::vector<std::unique_ptr<BaseInstance>>& list)
{
QMap<InstanceId, InstanceLocator> out;
int i = 0;
for (auto& item : list) {
for (const auto& item : list) {
auto id = item->id();
if (out.contains(id)) {
qWarning() << "Duplicate ID" << id << "in instance list";
@ -466,6 +468,7 @@ static QMap<InstanceId, InstanceLocator> getIdMapping(const std::vector<std::uni
}
return out;
}
} // namespace
QList<InstanceId> InstanceList::discoverInstances()
{
@ -475,8 +478,9 @@ QList<InstanceId> InstanceList::discoverInstances()
while (iter.hasNext()) {
QString subDir = iter.next();
QFileInfo dirInfo(subDir);
if (!QFileInfo(FS::PathCombine(subDir, "instance.cfg")).exists())
if (!QFileInfo(FS::PathCombine(subDir, "instance.cfg")).exists()) {
continue;
}
// if it is a symlink, ignore it if it goes to the instance folder
if (dirInfo.isSymLink()) {
QFileInfo targetInfo(dirInfo.symLinkTarget());
@ -490,7 +494,7 @@ QList<InstanceId> InstanceList::discoverInstances()
out.append(id);
qInfo() << "Found instance ID" << id;
}
instanceSet = QSet<QString>(out.begin(), out.end());
m_instanceSet = QSet<QString>(out.begin(), out.end());
m_instancesProbed = true;
return out;
}
@ -518,38 +522,38 @@ InstanceList::InstListError InstanceList::loadList()
// get the list of removed instances and sort it by their original index, from last to first
auto deadList = existingIds.values();
auto orderSortPredicate = [](const InstanceLocator& a, const InstanceLocator& b) -> bool { return a.second > b.second; };
std::sort(deadList.begin(), deadList.end(), orderSortPredicate);
std::ranges::sort(deadList, orderSortPredicate);
// remove the contiguous ranges of rows
int front_bookmark = -1;
int back_bookmark = -1;
int frontBookmark = -1;
int backBookmark = -1;
int currentItem = -1;
auto removeNow = [this, &front_bookmark, &back_bookmark, &currentItem]() {
beginRemoveRows(QModelIndex(), front_bookmark, back_bookmark);
m_instances.erase(m_instances.begin() + front_bookmark, m_instances.begin() + back_bookmark + 1);
auto removeNow = [this, &frontBookmark, &backBookmark, &currentItem]() {
beginRemoveRows(QModelIndex(), frontBookmark, backBookmark);
m_instances.erase(m_instances.begin() + frontBookmark, m_instances.begin() + backBookmark + 1);
endRemoveRows();
front_bookmark = -1;
back_bookmark = currentItem;
frontBookmark = -1;
backBookmark = currentItem;
};
for (auto& removedItem : deadList) {
auto instPtr = removedItem.first;
auto* instPtr = removedItem.first;
instPtr->invalidate();
currentItem = removedItem.second;
if (back_bookmark == -1) {
if (backBookmark == -1) {
// no bookmark yet
back_bookmark = currentItem;
} else if (currentItem == front_bookmark - 1) {
backBookmark = currentItem;
} else if (currentItem == frontBookmark - 1) {
// part of contiguous sequence, continue
} else {
// seam between previous and current item
removeNow();
}
front_bookmark = currentItem;
frontBookmark = currentItem;
}
if (back_bookmark != -1) {
if (backBookmark != -1) {
removeNow();
}
}
if (newList.size()) {
if (!newList.empty()) {
add(newList);
}
m_dirty = false;
@ -559,9 +563,9 @@ InstanceList::InstListError InstanceList::loadList()
void InstanceList::updateTotalPlayTime()
{
totalPlayTime = 0;
m_totalPlayTime = 0;
for (const auto& itr : m_instances) {
totalPlayTime += itr->totalTimePlayed();
m_totalPlayTime += static_cast<int>(itr->totalTimePlayed());
}
}
@ -607,11 +611,12 @@ void InstanceList::providerUpdated()
}
}
BaseInstance* InstanceList::getInstanceById(QString instId) const
BaseInstance* InstanceList::getInstanceById(const QString& instId) const
{
if (instId.isEmpty())
if (instId.isEmpty()) {
return nullptr;
for (auto& inst : m_instances) {
}
for (const auto& inst : m_instances) {
if (inst->id() == instId) {
return inst.get();
}
@ -619,14 +624,16 @@ BaseInstance* InstanceList::getInstanceById(QString instId) const
return nullptr;
}
BaseInstance* InstanceList::getInstanceByManagedName(const QString& managed_name) const
BaseInstance* InstanceList::getInstanceByManagedName(const QString& managedName) const
{
if (managed_name.isEmpty())
if (managedName.isEmpty()) {
return {};
}
for (auto& instance : m_instances) {
if (instance->getManagedPackName() == managed_name)
for (const auto& instance : m_instances) {
if (instance->getManagedPackName() == managedName) {
return instance.get();
}
}
return {};
@ -669,11 +676,11 @@ std::unique_ptr<BaseInstance> InstanceList::loadInstance(const InstanceId& id)
instanceSettings->registerSetting("InstanceType", "");
QString inst_type = instanceSettings->get("InstanceType").toString();
const QString instType = instanceSettings->get("InstanceType").toString();
// NOTE: Some launcher versions didn't save the InstanceType properly. We will just bank on the probability that this is probably a
// OneSix instance
if (inst_type == "OneSix" || inst_type.isEmpty()) {
if (instType == "OneSix" || instType.isEmpty()) {
inst.reset(new MinecraftInstance(m_globalSettings, std::move(instanceSettings), instanceRoot));
} else {
inst.reset(new NullInstance(m_globalSettings, std::move(instanceSettings), instanceRoot));
@ -681,24 +688,27 @@ std::unique_ptr<BaseInstance> InstanceList::loadInstance(const InstanceId& id)
qDebug() << "Loaded instance" << inst->name() << "from" << inst->instanceRoot();
auto shortcut = inst->shortcuts();
if (!shortcut.isEmpty())
if (!shortcut.isEmpty()) {
qDebug() << "Loaded" << shortcut.size() << "shortcut(s) for instance" << inst->name();
}
return inst;
}
void InstanceList::increaseGroupCount(const QString& group)
{
if (group.isEmpty())
if (group.isEmpty()) {
return;
}
++m_groupNameCache[group];
}
void InstanceList::decreaseGroupCount(const QString& group)
{
if (group.isEmpty())
if (group.isEmpty()) {
return;
}
if (--m_groupNameCache[group] < 1) {
m_groupNameCache.remove(group);
@ -718,15 +728,16 @@ void InstanceList::saveGroupList()
QMap<QString, QSet<QString>> reverseGroupMap;
for (auto iter = m_instanceGroupIndex.begin(); iter != m_instanceGroupIndex.end(); iter++) {
const QString& id = iter.key();
QString group = iter.value();
if (group.isEmpty())
const QString& group = iter.value();
if (group.isEmpty()) {
continue;
if (!instanceSet.contains(id)) {
}
if (!m_instanceSet.contains(id)) {
qDebug() << "Skipping saving missing instance" << id << "to groups list.";
continue;
}
if (!reverseGroupMap.count(group)) {
if (!reverseGroupMap.contains(group)) {
QSet<QString> set;
set.insert(id);
reverseGroupMap[group] = set;
@ -740,11 +751,11 @@ void InstanceList::saveGroupList()
QJsonObject groupsArr;
for (auto iter = reverseGroupMap.begin(); iter != reverseGroupMap.end(); iter++) {
auto list = iter.value();
auto name = iter.key();
const auto& name = iter.key();
QJsonObject groupObj;
QJsonArray instanceArr;
groupObj.insert("hidden", QJsonValue(m_collapsedGroups.contains(name)));
for (auto item : list) {
for (const auto& item : list) {
instanceArr.append(QJsonValue(item));
}
groupObj.insert("instances", instanceArr);
@ -773,8 +784,9 @@ void InstanceList::loadGroupList()
QString groupFileName = m_instDir + "/instgroups.json";
// if there's no group file, fail
if (!QFileInfo(groupFileName).exists())
if (!QFileInfo(groupFileName).exists()) {
return;
}
QByteArray jsonData;
try {
@ -804,8 +816,9 @@ void InstanceList::loadGroupList()
QJsonObject rootObj = jsonDoc.object();
// Make sure the format version matches, otherwise fail.
if (rootObj.value("formatVersion").toVariant().toInt() != GROUP_FILE_FORMAT_VERSION)
if (rootObj.value("formatVersion").toVariant().toInt() != g_GROUP_FILE_FORMAT_VERSION) {
return;
}
// Get the groups. if it's not an object, fail
if (!rootObj.value("groups").isObject()) {
@ -841,8 +854,9 @@ void InstanceList::loadGroupList()
}
auto hidden = groupObj.value("hidden").toBool(false);
if (hidden)
if (hidden) {
m_collapsedGroups.insert(groupName);
}
// Iterate through the list of instances in the group.
QJsonArray instancesArray = groupObj.value("instances").toArray();
@ -872,7 +886,7 @@ void InstanceList::instanceDirContentsChanged(const QString& path)
emit instancesChanged();
}
void InstanceList::on_InstFolderChanged([[maybe_unused]] const Setting& setting, QVariant value)
void InstanceList::on_InstFolderChanged([[maybe_unused]] const Setting& setting, const QVariant& value)
{
QString newInstDir = QDir(value.toString()).canonicalPath();
if (newInstDir != m_instDir) {
@ -899,13 +913,16 @@ void InstanceList::on_GroupStateChanged(const QString& group, bool collapsed)
saveGroupList();
}
namespace {
class InstanceStaging : public Task {
Q_OBJECT
const unsigned minBackoff = 1;
const unsigned maxBackoff = 16;
public:
InstanceStaging(InstanceList* parent, InstanceTask* child, SettingsObject* settings) : m_parent(parent), backoff(minBackoff, maxBackoff)
InstanceStaging(InstanceList* parent, InstanceTask* child, SettingsObject* settings)
: m_parent(parent), m_backoff(minBackoff, maxBackoff)
{
m_stagingPath = parent->getStagedInstancePath();
@ -954,8 +971,8 @@ class InstanceStaging : public Task {
private slots:
void childSucceeded()
{
unsigned sleepTime = backoff();
if (m_parent->commitStagedInstance(m_stagingPath, *m_child, m_child->group(), *m_child)) {
const unsigned sleepTime = m_backoff();
if (m_parent->commitStagedInstance(m_stagingPath, *m_child, m_child->group())) {
m_backoffTimer.stop();
emitSucceeded();
return;
@ -972,14 +989,14 @@ class InstanceStaging : public Task {
void childFailed(const QString& reason)
{
m_backoffTimer.stop();
m_parent->destroyStagingPath(m_stagingPath);
FS::deletePath(m_stagingPath);
emitFailed(reason);
}
void childAborted()
{
m_backoffTimer.stop();
m_parent->destroyStagingPath(m_stagingPath);
FS::deletePath(m_stagingPath);
emitAborted();
}
@ -990,11 +1007,12 @@ class InstanceStaging : public Task {
* Basically, it starts messing things up while the launcher is extracting/creating instances
* and causes that horrible failure that is NTFS to lock files in place because they are open.
*/
ExponentialSeries backoff;
ExponentialSeries m_backoff;
QString m_stagingPath;
std::unique_ptr<InstanceTask> m_child;
QTimer m_backoffTimer;
};
} // namespace
Task* InstanceList::wrapInstanceTask(InstanceTask* task)
{
@ -1009,37 +1027,37 @@ QString InstanceList::getStagedInstancePath()
int tries = 0;
do {
if (++tries > 256)
if (++tries > 256) {
return {};
}
const QString key = QUuid::createUuid().toString(QUuid::Id128).left(6);
result = FS::PathCombine(tempRoot, key);
} while (QFileInfo::exists(result));
if (!QDir::current().mkpath(result))
if (!QDir::current().mkpath(result)) {
return {};
}
#ifdef Q_OS_WIN32
SetFileAttributesA(tempRoot.toStdString().c_str(), FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_NOT_CONTENT_INDEXED);
#endif
return result;
}
bool InstanceList::commitStagedInstance(const QString& path,
const InstanceName& instanceName,
QString groupName,
const InstanceTask& commiting)
bool InstanceList::commitStagedInstance(const QString& path, const InstanceTask& instanceTask, QString groupName)
{
if (groupName.isEmpty() && !groupName.isNull())
if (groupName.isEmpty() && !groupName.isNull()) {
groupName = QString();
}
QString instID;
auto should_override = commiting.shouldOverride();
auto shouldOverride = instanceTask.shouldOverride();
if (should_override) {
instID = commiting.originalInstanceID();
if (shouldOverride) {
instID = instanceTask.originalInstanceID();
} else {
instID = FS::DirNameFromString(instanceName.modifiedName(), m_instDir);
instID = FS::DirNameFromString(instanceTask.modifiedName(), m_instDir);
}
Q_ASSERT(!instID.isEmpty());
@ -1048,7 +1066,7 @@ bool InstanceList::commitStagedInstance(const QString& path,
WatchLock lock(m_watcher, m_instDir);
QString destination = FS::PathCombine(m_instDir, instID);
if (should_override) {
if (shouldOverride) {
if (!FS::overrideFolder(destination, path)) {
qWarning() << "Failed to override" << path << "to" << destination;
return false;
@ -1063,7 +1081,7 @@ bool InstanceList::commitStagedInstance(const QString& path,
increaseGroupCount(groupName);
}
instanceSet.insert(instID);
m_instanceSet.insert(instID);
emit instancesChanged();
emit instanceSelectRequest(instID);
@ -1073,15 +1091,10 @@ bool InstanceList::commitStagedInstance(const QString& path,
return true;
}
bool InstanceList::destroyStagingPath(const QString& keyPath)
{
return FS::deletePath(keyPath);
}
int InstanceList::getTotalPlayTime()
{
updateTotalPlayTime();
return totalPlayTime;
return m_totalPlayTime;
}
#include "InstanceList.moc"

View file

@ -41,20 +41,20 @@
#include <QPair>
#include <QSet>
#include <QStack>
#include <cstdint>
#include "BaseInstance.h"
class QFileSystemWatcher;
class InstanceTask;
struct InstanceName;
using InstanceId = QString;
using GroupId = QString;
using InstanceLocator = std::pair<BaseInstance*, int>;
enum class InstCreateError { NoCreateError = 0, NoSuchVersion, UnknownCreateError, InstExists, CantCreateDir };
enum class InstCreateError : std::uint8_t { NoCreateError = 0, NoSuchVersion, UnknownCreateError, InstExists, CantCreateDir };
enum class GroupsState { NotLoaded, Steady, Dirty };
enum class GroupsState : std::uint8_t { NotLoaded, Steady, Dirty };
struct TrashShortcutItem {
ShortcutData data;
@ -73,8 +73,8 @@ class InstanceList : public QAbstractListModel {
Q_OBJECT
public:
explicit InstanceList(SettingsObject* settings, const QString& instDir, QObject* parent = 0);
virtual ~InstanceList();
explicit InstanceList(SettingsObject* settings, const QString& instDir, QObject* parent = nullptr);
~InstanceList() override = default;
public:
QModelIndex index(int row, int column = 0, const QModelIndex& parent = QModelIndex()) const override;
@ -94,7 +94,7 @@ class InstanceList : public QAbstractListModel {
* NoError Indicates that no error occurred.
* UnknownError indicates that an unspecified error occurred.
*/
enum InstListError { NoError = 0, UnknownError };
enum InstListError : std::uint8_t { NoError = 0, UnknownError };
BaseInstance* at(int i) const { return m_instances.at(i).get(); }
@ -104,9 +104,9 @@ class InstanceList : public QAbstractListModel {
void saveNow();
/* O(n) */
BaseInstance* getInstanceById(QString id) const;
BaseInstance* getInstanceById(const QString& id) const;
/* O(n) */
BaseInstance* getInstanceByManagedName(const QString& managed_name) const;
BaseInstance* getInstanceByManagedName(const QString& managedName) const;
QModelIndex getInstanceIndexById(const QString& id) const;
QStringList getGroups();
bool isGroupCollapsed(const QString& groupName);
@ -136,13 +136,7 @@ class InstanceList : public QAbstractListModel {
* should_override is used when another similar instance already exists, and we want to override it
* - for instance, when updating it.
*/
bool commitStagedInstance(const QString& keyPath, const InstanceName& instanceName, QString groupName, const InstanceTask&);
/**
* Destroy a previously created staging area given by @keyPath - used when creation fails.
* Used by instance manipulation tasks.
*/
bool destroyStagingPath(const QString& keyPath);
bool commitStagedInstance(const QString& keyPath, const InstanceTask& instanceTask, QString groupName);
int getTotalPlayTime();
@ -166,7 +160,7 @@ class InstanceList : public QAbstractListModel {
void groupsChanged(QSet<QString> groups);
public slots:
void on_InstFolderChanged(const Setting& setting, QVariant value);
void on_InstFolderChanged(const Setting& setting, const QVariant& value);
void on_GroupStateChanged(const QString& group, bool collapsed);
private slots:
@ -190,7 +184,7 @@ class InstanceList : public QAbstractListModel {
private:
int m_watchLevel = 0;
int totalPlayTime = 0;
int m_totalPlayTime = 0;
bool m_dirty = false;
std::vector<std::unique_ptr<BaseInstance>> m_instances;
// id -> refs
@ -202,7 +196,7 @@ class InstanceList : public QAbstractListModel {
// FIXME: this is so inefficient that looking at it is almost painful.
QSet<QString> m_collapsedGroups;
QMap<InstanceId, GroupId> m_instanceGroupIndex;
QSet<InstanceId> instanceSet;
QSet<InstanceId> m_instanceSet;
bool m_groupsLoaded = false;
bool m_instancesProbed = false;

View file

@ -2,94 +2,182 @@
#include <QDir>
#include "Application.h"
#include "minecraft/MinecraftInstance.h"
#include "minecraft/MinecraftLoadAndCheck.h"
#include "settings/SettingsObject.h"
#include "tasks/SequentialTask.h"
#include "ui/dialogs/CustomMessageBox.h"
#include <QPushButton>
InstanceNameChange askForChangingInstanceName(QWidget* parent, const QString& old_name, const QString& new_name)
InstanceNameChange askForChangingInstanceName(QWidget* parent, const QString& oldName, const QString& newName)
{
auto dialog =
auto* dialog =
CustomMessageBox::selectable(parent, QObject::tr("Change instance name"),
QObject::tr("The instance's name seems to include the old version. Would you like to update it?\n\n"
"Old name: %1\n"
"New name: %2")
.arg(old_name, new_name),
.arg(oldName, newName),
QMessageBox::Question, QMessageBox::No | QMessageBox::Yes);
auto result = dialog->exec();
if (result == QMessageBox::Yes)
if (result == QMessageBox::Yes) {
return InstanceNameChange::ShouldChange;
}
return InstanceNameChange::ShouldKeep;
}
ShouldUpdate askIfShouldUpdate(QWidget* parent, QString original_version_name)
ShouldUpdate askIfShouldUpdate(QWidget* parent, const QString& originalVersionName)
{
if (APPLICATION->settings()->get("SkipModpackUpdatePrompt").toBool())
if (APPLICATION->settings()->get("SkipModpackUpdatePrompt").toBool()) {
return ShouldUpdate::SkipUpdating;
}
auto info = CustomMessageBox::selectable(
auto* info = CustomMessageBox::selectable(
parent, QObject::tr("Similar modpack was found!"),
QObject::tr(
"One or more of your instances are from this same modpack%1. Do you want to create a "
"separate instance, or update the existing one?\n\nNOTE: Make sure you made a backup of your important instance data before "
"updating, as worlds can be corrupted and some configuration may be lost (due to pack overrides).")
.arg(original_version_name),
.arg(originalVersionName),
QMessageBox::Information, QMessageBox::Cancel);
QAbstractButton* update = info->addButton(QObject::tr("Update existing instance"), QMessageBox::AcceptRole);
QAbstractButton* skip = info->addButton(QObject::tr("Create new instance"), QMessageBox::ResetRole);
info->exec();
if (info->clickedButton() == update)
if (info->clickedButton() == update) {
return ShouldUpdate::Update;
if (info->clickedButton() == skip)
}
if (info->clickedButton() == skip) {
return ShouldUpdate::SkipUpdating;
}
return ShouldUpdate::Cancel;
}
QString InstanceName::name() const
QString InstanceTask::name() const
{
if (!m_modified_name.isEmpty())
if (!m_modifiedName.isEmpty()) {
return modifiedName();
if (!m_original_version.isEmpty())
return QString("%1 %2").arg(m_original_name, m_original_version);
}
if (!m_originalVersion.isEmpty()) {
return QString("%1 %2").arg(m_originalName, m_originalVersion);
}
return m_original_name;
return m_originalName;
}
QString InstanceName::originalName() const
QString InstanceTask::originalName() const
{
return m_original_name;
return m_originalName;
}
QString InstanceName::modifiedName() const
QString InstanceTask::modifiedName() const
{
if (!m_modified_name.isEmpty())
return m_modified_name;
return m_original_name;
if (!m_modifiedName.isEmpty()) {
return m_modifiedName;
}
return m_originalName;
}
QString InstanceName::version() const
QString InstanceTask::version() const
{
return m_original_version;
return m_originalVersion;
}
void InstanceName::setName(InstanceName& other)
void InstanceTask::setOriginalName(const QString& name, const QString& version)
{
m_original_name = other.m_original_name;
m_original_version = other.m_original_version;
m_modified_name = other.m_modified_name;
m_originalName = name;
m_originalVersion = version;
}
void InstanceTask::setOverride(bool override, const QString& instanceIdToOverride)
{
m_overrideExisting = override;
if (!instanceIdToOverride.isEmpty()) {
m_originalInstanceId = instanceIdToOverride;
}
}
InstanceTask::InstanceTask() : Task(), InstanceName() {}
ShouldDeleteSaves askIfShouldDeleteSaves(QWidget* parent)
{
auto dialog = CustomMessageBox::selectable(parent, QObject::tr("Delete Existing Save Files"),
QObject::tr("An earlier version of this mod pack installed save files.\n"
"Would you like to remove those existing saves as part of this update?"),
QMessageBox::Question, QMessageBox::No | QMessageBox::Yes);
auto* dialog = CustomMessageBox::selectable(parent, QObject::tr("Delete Existing Save Files"),
QObject::tr("An earlier version of this mod pack installed save files.\n"
"Would you like to remove those existing saves as part of this update?"),
QMessageBox::Question, QMessageBox::No | QMessageBox::Yes);
auto result = dialog->exec();
return result == QMessageBox::Yes ? ShouldDeleteSaves::Yes : ShouldDeleteSaves::No;
}
void InstanceTask::scheduleToDelete(QWidget* parent, const QDir& dir, const QString& path, bool checkDisabled)
{
if (path.isEmpty()) {
return;
}
if (path.startsWith("saves/")) {
if (m_shouldDeleteSaves == ShouldDeleteSaves::NotAsked) {
m_shouldDeleteSaves = askIfShouldDeleteSaves(parent);
}
if (m_shouldDeleteSaves == ShouldDeleteSaves::No) {
return;
}
}
qDebug() << "Scheduling" << path << "for removal";
m_filesToRemove.append(dir.absoluteFilePath(path));
if (checkDisabled) {
if (path.endsWith(".disabled")) { // remove it if it was enabled/disabled by user
m_filesToRemove.append(dir.absoluteFilePath(path.chopped(9)));
} else {
m_filesToRemove.append(dir.absoluteFilePath(path + ".disabled"));
}
}
}
void InstanceTask::downloadFiles(MinecraftInstance* inst)
{
if (!APPLICATION->settings()->get("DownloadGameFilesDuringInstanceCreation").toBool()) {
emitSucceeded();
return;
}
setAbortable(true);
setAbortButtonText(tr("Skip"));
qDebug() << "Downloading game files";
auto updateTasks = inst->createUpdateTask();
if (updateTasks.isEmpty()) {
emitSucceeded();
return;
}
auto task = makeShared<SequentialTask>();
task->addTask(makeShared<MinecraftLoadAndCheck>(inst, Net::Mode::Online));
for (const auto& t : updateTasks) {
task->addTask(t);
}
connect(task.get(), &Task::finished, this, [this, task] {
if (isRunning()) {
return;
}
if (task->wasSuccessful()) {
emitSucceeded();
} else {
emitFailed(tr("Could not download game files: %1").arg(task->failReason()));
}
});
propagateFromOther(task.get());
setDetails(tr("Downloading game files"));
m_gameFilesTask = task;
m_gameFilesTask->start();
}
bool InstanceTask::abort()
{
if (!canAbort()) {
return false;
}
if (m_gameFilesTask) {
return m_gameFilesTask->abort();
}
return Task::abort();
}

View file

@ -1,40 +1,24 @@
#pragma once
#include <QDir>
#include <cstdint>
#include "settings/SettingsObject.h"
#include "tasks/Task.h"
class MinecraftInstance;
/* Helpers */
enum class InstanceNameChange { ShouldChange, ShouldKeep };
[[nodiscard]] InstanceNameChange askForChangingInstanceName(QWidget* parent, const QString& old_name, const QString& new_name);
enum class ShouldUpdate { Update, SkipUpdating, Cancel };
[[nodiscard]] ShouldUpdate askIfShouldUpdate(QWidget* parent, QString original_version_name);
enum class ShouldDeleteSaves { NotAsked, Yes, No };
enum class InstanceNameChange : std::uint8_t { ShouldChange, ShouldKeep };
[[nodiscard]] InstanceNameChange askForChangingInstanceName(QWidget* parent, const QString& oldName, const QString& newName);
enum class ShouldUpdate : std::uint8_t { Update, SkipUpdating, Cancel };
[[nodiscard]] ShouldUpdate askIfShouldUpdate(QWidget* parent, const QString& originalVersionName);
enum class ShouldDeleteSaves : std::uint8_t { NotAsked, Yes, No };
[[nodiscard]] ShouldDeleteSaves askIfShouldDeleteSaves(QWidget* parent);
struct InstanceName {
public:
InstanceName() = default;
InstanceName(QString name, QString version) : m_original_name(std::move(name)), m_original_version(std::move(version)) {}
QString modifiedName() const;
QString originalName() const;
QString name() const;
QString version() const;
void setName(QString name) { m_modified_name = name; }
void setName(InstanceName& other);
protected:
QString m_original_name;
QString m_original_version;
QString m_modified_name;
};
class InstanceTask : public Task, public InstanceName {
class InstanceTask : public Task {
Q_OBJECT
public:
InstanceTask();
InstanceTask() = default;
~InstanceTask() override = default;
void setParentSettings(SettingsObject* settings) { m_globalSettings = settings; }
@ -46,29 +30,48 @@ class InstanceTask : public Task, public InstanceName {
void setGroup(const QString& group) { m_instGroup = group; }
QString group() const { return m_instGroup; }
bool shouldConfirmUpdate() const { return m_confirm_update; }
void setConfirmUpdate(bool confirm) { m_confirm_update = confirm; }
bool shouldConfirmUpdate() const { return m_confirmUpdate; }
void setConfirmUpdate(bool confirm) { m_confirmUpdate = confirm; }
bool shouldOverride() const { return m_override_existing; }
bool shouldOverride() const { return m_overrideExisting; }
QString originalInstanceID() const { return m_original_instance_id; };
QString originalInstanceID() const { return m_originalInstanceId; };
QString modifiedName() const;
QString originalName() const;
QString name() const;
QString version() const;
void setName(const QString& name) { m_modifiedName = name; }
void setOriginalName(const QString& name, const QString& version);
protected:
void setOverride(bool override, QString instance_id_to_override = {})
{
m_override_existing = override;
if (!instance_id_to_override.isEmpty())
m_original_instance_id = instance_id_to_override;
}
void setOverride(bool override, const QString& instanceIdToOverride = {});
void scheduleToDelete(QWidget* parent, const QDir& dir, const QString& path, bool checkDisabled = false);
void downloadFiles(MinecraftInstance* inst);
public slots:
bool abort() override;
protected: /* data */
SettingsObject* m_globalSettings;
SettingsObject* m_globalSettings{};
QString m_instIcon;
QString m_instGroup;
QString m_stagingPath;
bool m_override_existing = false;
bool m_confirm_update = true;
bool m_overrideExisting = false;
bool m_confirmUpdate = true;
QString m_original_instance_id;
QString m_originalInstanceId;
QString m_originalName;
QString m_originalVersion;
QString m_modifiedName;
QStringList m_filesToRemove;
ShouldDeleteSaves m_shouldDeleteSaves{};
Task::Ptr m_gameFilesTask;
};

View file

@ -8,26 +8,30 @@
#include "settings/INISettingsObject.h"
VanillaCreationTask::VanillaCreationTask(BaseVersion::Ptr version, QString loader, BaseVersion::Ptr loaderVersion)
: m_version(std::move(version)), m_using_loader(true), m_loader(std::move(loader)), m_loader_version(std::move(loaderVersion))
: m_version(std::move(version)), m_usingLoader(true), m_loader(std::move(loader)), m_loaderVersion(std::move(loaderVersion))
{}
std::unique_ptr<MinecraftInstance> VanillaCreationTask::createInstance()
void VanillaCreationTask::executeTask()
{
setStatus(tr("Creating instance from version %1").arg(m_version->name()));
auto inst = std::make_unique<MinecraftInstance>(
m_instance = std::make_unique<MinecraftInstance>(
m_globalSettings, std::make_unique<INISettingsObject>(FS::PathCombine(m_stagingPath, "instance.cfg")), m_stagingPath);
SettingsObject::Lock lock(inst->settings());
{
SettingsObject::Lock const lock(m_instance->settings());
auto* components = inst->getPackProfile();
components->buildingFromScratch();
components->setComponentVersion("net.minecraft", m_version->descriptor(), true);
if (m_using_loader) {
components->setComponentVersion(m_loader, m_loader_version->descriptor());
auto* components = m_instance->getPackProfile();
components->buildingFromScratch();
components->setComponentVersion("net.minecraft", m_version->descriptor(), true);
if (m_usingLoader) {
components->setComponentVersion(m_loader, m_loaderVersion->descriptor());
}
m_instance->setName(name());
m_instance->setIconKey(m_instIcon);
components->saveNow();
}
inst->setName(name());
inst->setIconKey(m_instIcon);
components->saveNow();
return inst;
downloadFiles(m_instance.get());
}

View file

@ -1,22 +1,25 @@
#pragma once
#include "InstanceCreationTask.h"
#include <memory>
#include "BaseVersion.h"
#include "InstanceTask.h"
#include "minecraft/MinecraftInstance.h"
#include <utility>
class VanillaCreationTask final : public InstanceCreationTask {
class VanillaCreationTask final : public InstanceTask {
Q_OBJECT
public:
explicit VanillaCreationTask(BaseVersion::Ptr version) : m_version(std::move(version)) {}
VanillaCreationTask(BaseVersion::Ptr version, QString loader, BaseVersion::Ptr loaderVersion);
std::unique_ptr<MinecraftInstance> createInstance() override;
void executeTask() override;
private:
std::unique_ptr<MinecraftInstance> m_instance;
// Version to update to / create of the instance.
BaseVersion::Ptr m_version;
bool m_using_loader = false;
bool m_usingLoader = false;
QString m_loader;
BaseVersion::Ptr m_loader_version;
BaseVersion::Ptr m_loaderVersion;
};

View file

@ -1091,7 +1091,7 @@ void PackInstallTask::install()
jarmods.clear();
}
emitSucceeded();
downloadFiles(&instance);
}
} // namespace ATLauncher

View file

@ -35,7 +35,6 @@
#include "FlameInstanceCreationTask.h"
#include "InstanceTask.h"
#include "QObjectPtr.h"
#include "minecraft/mod/tasks/LocalResourceUpdateTask.h"
#include "modplatform/flame/FileResolvingTask.h"
@ -55,13 +54,13 @@
#include "settings/INISettingsObject.h"
#include "SysInfo.h"
#include "tasks/ConcurrentTask.h"
#include "ui/dialogs/BlockedModsDialog.h"
#include "ui/dialogs/CustomMessageBox.h"
#include <QDebug>
#include <QFileInfo>
#include <utility>
#include "HardwareInfo.h"
#include "meta/Index.h"
@ -70,199 +69,223 @@
#include "net/ApiDownload.h"
#include "ui/pages/modplatform/OptionalModDialog.h"
static const FlameAPI api;
bool FlameCreationTask::abort()
{
if (!canAbort())
if (!canAbort()) {
return false;
}
if (m_processUpdateFileInfoJob)
if (m_processUpdateFileInfoJob) {
m_processUpdateFileInfoJob->abort();
if (m_filesJob)
}
if (m_filesJob) {
m_filesJob->abort();
if (m_modIdResolver)
}
if (m_modIdResolver) {
m_modIdResolver->abort();
}
return InstanceCreationTask::abort();
return InstanceTask::abort();
}
bool FlameCreationTask::updateInstance()
void FlameCreationTask::executeTask()
{
auto instance_list = APPLICATION->instances();
auto* instanceList = APPLICATION->instances();
// FIXME: How to handle situations when there's more than one install already for a given modpack?
BaseInstance* inst;
if (auto original_id = originalInstanceID(); !original_id.isEmpty()) {
inst = instance_list->getInstanceById(original_id);
BaseInstance* inst = nullptr;
if (auto originalId = originalInstanceID(); !originalId.isEmpty()) {
inst = instanceList->getInstanceById(originalId);
Q_ASSERT(inst);
} else {
inst = instance_list->getInstanceByManagedName(originalName());
inst = instanceList->getInstanceByManagedName(originalName());
if (!inst) {
inst = instance_list->getInstanceById(originalName());
inst = instanceList->getInstanceById(originalName());
if (!inst)
return false;
if (!inst) {
createInstance();
return;
}
}
}
QString index_path(FS::PathCombine(m_stagingPath, "manifest.json"));
QString indexPath(FS::PathCombine(m_stagingPath, "manifest.json"));
try {
Flame::loadManifest(m_pack, index_path);
} catch (const JSONValidationError& e) {
setError(tr("Could not understand pack manifest:\n") + e.cause());
return false;
Flame::loadManifest(m_pack, indexPath);
} catch (const JSONValidationError&) {
// emitFailed(tr("Could not understand pack manifest:\n") + e.cause());
createInstance(); // to keep the backwards comatibility here just create the instance
return;
}
auto version_id = inst->getManagedPackVersionName();
auto version_str = !version_id.isEmpty() ? tr(" (version %1)").arg(version_id) : "";
auto versionId = inst->getManagedPackVersionName();
auto versionStr = !versionId.isEmpty() ? tr(" (version %1)").arg(versionId) : "";
if (shouldConfirmUpdate()) {
auto should_update = askIfShouldUpdate(m_parent, version_str);
if (should_update == ShouldUpdate::SkipUpdating)
return false;
if (should_update == ShouldUpdate::Cancel) {
m_abort = true;
return false;
auto shouldUpdate = askIfShouldUpdate(m_parent, versionStr);
if (shouldUpdate == ShouldUpdate::SkipUpdating) {
createInstance();
return;
}
if (shouldUpdate == ShouldUpdate::Cancel) {
emitAborted();
return;
}
}
QDir old_inst_dir(inst->instanceRoot());
QDir const oldInstDir(inst->instanceRoot());
QString old_index_folder(FS::PathCombine(old_inst_dir.absolutePath(), "flame"));
QString old_index_path(FS::PathCombine(old_index_folder, "manifest.json"));
QString const oldIndexFolder(FS::PathCombine(oldInstDir.absolutePath(), "flame"));
QString const oldIndexPath(FS::PathCombine(oldIndexFolder, "manifest.json"));
QFileInfo old_index_file(old_index_path);
if (old_index_file.exists()) {
Flame::Manifest old_pack;
Flame::loadManifest(old_pack, old_index_path);
QFileInfo const oldIndexFile(oldIndexPath);
auto createInst = [this, inst] {
setOverride(true, inst->id());
qDebug() << "Will override instance!";
auto& old_files = old_pack.files;
m_oldInstance = inst;
// We let it go through the createInstance() stage, just with a couple modifications for updating
createInstance();
};
auto warnUser = [this, createInst](const QString& title,
const QString& text) { // We don't have an old index file, so we may duplicate stuff!
auto* dialog = CustomMessageBox::selectable(m_parent, title, text, QMessageBox::Warning, QMessageBox::Ok | QMessageBox::Cancel);
if (dialog->exec() == QDialog::DialogCode::Rejected) {
emitAborted();
return;
}
createInst();
};
if (oldIndexFile.exists()) {
Flame::Manifest oldPack;
Flame::loadManifest(oldPack, oldIndexPath);
auto oldFiles = oldPack.files;
auto& files = m_pack.files;
// Remove repeated files, we don't need to download them!
auto files_iterator = files.begin();
while (files_iterator != files.end()) {
auto const& file = files_iterator;
auto filesIterator = files.begin();
while (filesIterator != files.end()) {
const auto& file = filesIterator;
auto old_file = old_files.find(file.key());
if (old_file != old_files.end()) {
auto oldFile = oldFiles.find(file.key());
if (oldFile != oldFiles.end()) {
// We found a match, but is it a different version?
if (old_file->fileId == file->fileId) {
if (oldFile->fileId == file->fileId) {
qDebug() << "Removed file at" << file->targetFolder << "with id" << file->fileId << "from list of downloads";
old_files.remove(file.key());
files_iterator = files.erase(files_iterator);
oldFiles.remove(file.key());
filesIterator = files.erase(filesIterator);
if (files_iterator != files.begin())
files_iterator--;
if (filesIterator != files.begin()) {
filesIterator--;
}
}
}
files_iterator++;
filesIterator++;
}
QDir old_minecraft_dir(inst->gameRoot());
QDir const oldMinecraftDir(inst->gameRoot());
// We will remove all the previous overrides, to prevent duplicate files!
// TODO: Currently 'overrides' will always override the stuff on update. How do we preserve unchanged overrides?
// FIXME: We may want to do something about disabled mods.
auto old_overrides = Override::readOverrides("overrides", old_index_folder);
for (const auto& entry : old_overrides) {
scheduleToDelete(m_parent, old_minecraft_dir, entry);
auto oldOverrides = Override::readOverrides("overrides", oldIndexFolder);
for (const auto& entry : oldOverrides) {
scheduleToDelete(m_parent, oldMinecraftDir, entry);
}
// Remove remaining old files (we need to do an API request to know which ids are which files...)
QStringList fileIds;
for (auto& file : old_files) {
for (auto& file : oldFiles) {
fileIds.append(QString::number(file.fileId));
}
auto [job, raw_response] = api.getFiles(fileIds);
auto [job, rawResponse] = FlameAPI().getFiles(fileIds);
QEventLoop loop;
connect(job.get(), &Task::succeeded, this,
[this, rawResponse, fileIds, oldInstDir, oldFiles, oldMinecraftDir, createInst]() mutable {
// Parse the API response
QJsonParseError parseError{};
auto doc = QJsonDocument::fromJson(*rawResponse, &parseError);
if (parseError.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response from Flame files task at" << parseError.offset
<< "reason:" << parseError.errorString();
qWarning() << *rawResponse;
return;
}
connect(job.get(), &Task::succeeded, this, [this, raw_response, fileIds, old_inst_dir, &old_files, old_minecraft_dir] {
// Parse the API response
QJsonParseError parse_error{};
auto doc = QJsonDocument::fromJson(*raw_response, &parse_error);
if (parse_error.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response from Flame files task at" << parse_error.offset
<< "reason:" << parse_error.errorString();
qWarning() << *raw_response;
return;
}
try {
QJsonArray entries;
if (fileIds.size() == 1) {
entries = { Json::requireObject(Json::requireObject(doc), "data") };
} else {
entries = Json::requireArray(Json::requireObject(doc), "data");
}
try {
QJsonArray entries;
if (fileIds.size() == 1)
entries = { Json::requireObject(Json::requireObject(doc), "data") };
else
entries = Json::requireArray(Json::requireObject(doc), "data");
for (auto entry : entries) {
auto entryObj = Json::requireObject(entry);
for (auto entry : entries) {
auto entry_obj = Json::requireObject(entry);
Flame::File file;
// We don't care about blocked mods, we just need local data to delete the file
file.version = FlameMod::loadIndexedPackVersion(entryObj);
auto id = Json::requireInteger(entryObj, "id");
oldFiles.insert(id, file);
}
} catch (Json::JsonException& e) {
qCritical() << e.cause() << e.what();
}
Flame::File file;
// We don't care about blocked mods, we just need local data to delete the file
file.version = FlameMod::loadIndexedPackVersion(entry_obj);
auto id = Json::requireInteger(entry_obj, "id");
old_files.insert(id, file);
}
} catch (Json::JsonException& e) {
qCritical() << e.cause() << e.what();
}
// Delete the files
for (const auto& file : oldFiles) {
if (file.version.fileName.isEmpty() || file.targetFolder.isEmpty()) {
continue;
}
// Delete the files
for (auto& file : old_files) {
if (file.version.fileName.isEmpty() || file.targetFolder.isEmpty())
continue;
QString const relativePath(FS::PathCombine(file.targetFolder, file.version.fileName));
scheduleToDelete(m_parent, oldMinecraftDir, relativePath, true);
}
QString relative_path(FS::PathCombine(file.targetFolder, file.version.fileName));
scheduleToDelete(m_parent, old_minecraft_dir, relative_path, true);
}
createInst();
});
connect(job.get(), &Task::aborted, this, [warnUser] {
warnUser(tr("Failed to fetch the old files."),
tr("We couldn't fetch the old files because the task was aborted. This may cause "
"some of the files to be duplicated. Do you want to continue?"));
});
connect(job.get(), &Task::failed, this, [warnUser](const QString& reason) {
warnUser(tr("Failed to fetch the old files."), tr("We couldn't fetch the old files because: %1. This may cause some of the "
"files to be duplicated. Do you want to continue?")
.arg(reason));
});
connect(job.get(), &Task::failed, this, [](QString reason) { qCritical() << "Failed to get files:" << reason; });
connect(job.get(), &Task::finished, &loop, &QEventLoop::quit);
m_processUpdateFileInfoJob = job;
job->start();
loop.exec();
m_processUpdateFileInfoJob = nullptr;
} else {
// We don't have an old index file, so we may duplicate stuff!
auto dialog = CustomMessageBox::selectable(m_parent, tr("No index file."),
tr("We couldn't find a suitable index file for the older version. This may cause some "
"of the files to be duplicated. Do you want to continue?"),
QMessageBox::Warning, QMessageBox::Ok | QMessageBox::Cancel);
if (dialog->exec() == QDialog::DialogCode::Rejected) {
m_abort = true;
return false;
}
return;
}
setOverride(true, inst->id());
qDebug() << "Will override instance!";
m_instance = inst;
// We let it go through the createInstance() stage, just with a couple modifications for updating
return false;
warnUser(tr("No index file."), tr("We couldn't find a suitable index file for the older version. This may cause some of the files to "
"be duplicated. Do you want to continue?"));
}
QString FlameCreationTask::getVersionForLoader(QString uid, QString loaderType, QString loaderVersion, QString mcVersion)
QString FlameCreationTask::getVersionForLoader(const QString& uid,
const QString& loaderType,
const QString& loaderVersion,
const QString& mcVersion)
{
if (loaderVersion == "recommended") {
auto vlist = APPLICATION->metadataIndex()->get(uid);
if (!vlist) {
setError(tr("Failed to get local metadata index for %1").arg(uid));
emitFailed(tr("Failed to get local metadata index for %1").arg(uid));
return {};
}
@ -270,16 +293,18 @@ QString FlameCreationTask::getVersionForLoader(QString uid, QString loaderType,
QEventLoop loadVersionLoop;
auto task = vlist->getLoadTask();
connect(task.get(), &Task::finished, &loadVersionLoop, &QEventLoop::quit);
if (!task->isRunning())
if (!task->isRunning()) {
task->start();
}
loadVersionLoop.exec();
}
for (auto version : vlist->versions()) {
for (const auto& version : vlist->versions()) {
// first recommended build we find, we use.
if (!version->isRecommended())
if (!version->isRecommended()) {
continue;
}
auto reqs = version->requiredSet();
// filter by minecraft version, if the loader depends on a certain version.
@ -289,13 +314,14 @@ QString FlameCreationTask::getVersionForLoader(QString uid, QString loaderType,
auto iter = std::find_if(reqs.begin(), reqs.end(), [mcVersion](const Meta::Require& req) {
return req.uid == "net.minecraft" && req.equalsVersion == mcVersion;
});
if (iter == reqs.end())
if (iter == reqs.end()) {
continue;
}
}
return version->descriptor();
}
setError(tr("Failed to find version for %1 loader").arg(loaderType));
emitFailed(tr("Failed to find version for %1 loader").arg(loaderType));
return {};
}
@ -307,37 +333,46 @@ QString FlameCreationTask::getVersionForLoader(QString uid, QString loaderType,
return loaderVersion;
}
std::unique_ptr<MinecraftInstance> FlameCreationTask::createInstance()
void FlameCreationTask::setManagedPack(BaseInstance* instance)
{
QEventLoop loop;
// Don't add managed info to packs without an ID (most likely imported from ZIP)
if (!m_managedId.isEmpty()) {
instance->setManagedPack("flame", m_managedId, m_pack.name, m_managedVersionId, m_pack.version);
} else {
instance->setManagedPack("flame", "", name(), "", "");
}
}
QString parent_folder(FS::PathCombine(m_stagingPath, "flame"));
void FlameCreationTask::createInstance()
{
QString const parentFolder(FS::PathCombine(m_stagingPath, "flame"));
try {
QString index_path(FS::PathCombine(m_stagingPath, "manifest.json"));
if (!m_pack.is_loaded)
Flame::loadManifest(m_pack, index_path);
QString const indexPath(FS::PathCombine(m_stagingPath, "manifest.json"));
if (!m_pack.isLoaded) {
Flame::loadManifest(m_pack, indexPath);
}
// Keep index file in case we need it some other time (like when changing versions)
QString new_index_place(FS::PathCombine(parent_folder, "manifest.json"));
FS::ensureFilePathExists(new_index_place);
FS::move(index_path, new_index_place);
QString const newIndexPlace(FS::PathCombine(parentFolder, "manifest.json"));
FS::ensureFilePathExists(newIndexPlace);
FS::move(indexPath, newIndexPlace);
} catch (const JSONValidationError& e) {
setError(tr("Could not understand pack manifest:\n") + e.cause());
return nullptr;
emitFailed(tr("Could not understand pack manifest:\n") + e.cause());
return;
}
if (!m_pack.overrides.isEmpty()) {
QString overridePath = FS::PathCombine(m_stagingPath, m_pack.overrides);
if (QFile::exists(overridePath)) {
// Create a list of overrides in "overrides.txt" inside flame/
Override::createOverrides("overrides", parent_folder, overridePath);
Override::createOverrides("overrides", parentFolder, overridePath);
QString mcPath = FS::PathCombine(m_stagingPath, "minecraft");
if (!FS::move(overridePath, mcPath)) {
setError(tr("Could not rename the overrides folder:\n") + m_pack.overrides);
return nullptr;
emitFailed(tr("Could not rename the overrides folder:\n") + m_pack.overrides);
return;
}
} else {
logWarning(
@ -353,8 +388,9 @@ std::unique_ptr<MinecraftInstance> FlameCreationTask::createInstance()
auto id = loader.id;
if (id.startsWith("neoforge-")) {
id.remove("neoforge-");
if (id.startsWith("1.20.1-"))
if (id.startsWith("1.20.1-")) {
id.remove("1.20.1-"); // this is a mess for curseforge
}
loaderType = "neoforge";
loaderUid = "net.neoforged";
} else if (id.startsWith("forge-")) {
@ -378,7 +414,7 @@ std::unique_ptr<MinecraftInstance> FlameCreationTask::createInstance()
QString configPath = FS::PathCombine(m_stagingPath, "instance.cfg");
auto instanceSettings = std::make_unique<INISettingsObject>(configPath);
auto instance = std::make_unique<MinecraftInstance>(m_globalSettings, std::move(instanceSettings), m_stagingPath);
m_newInstance = std::make_unique<MinecraftInstance>(m_globalSettings, std::move(instanceSettings), m_stagingPath);
auto mcVersion = m_pack.minecraft.version;
// Hack to correct some 'special sauce'...
@ -388,32 +424,33 @@ std::unique_ptr<MinecraftInstance> FlameCreationTask::createInstance()
logWarning(tr("Mysterious trailing dots removed from Minecraft version while importing pack."));
}
auto components = instance->getPackProfile();
auto* components = m_newInstance->getPackProfile();
components->buildingFromScratch();
components->setComponentVersion("net.minecraft", mcVersion, true);
if (!loaderType.isEmpty()) {
auto version = getVersionForLoader(loaderUid, loaderType, loaderVersion, mcVersion);
if (version.isEmpty())
return nullptr;
if (version.isEmpty()) { // because there are more info in getVersionForLoader the emitFailed is trigered inside it
return;
}
components->setComponentVersion(loaderUid, version);
}
if (m_instIcon != "default") {
instance->setIconKey(m_instIcon);
m_newInstance->setIconKey(m_instIcon);
} else {
if (m_pack.name.contains("Direwolf20")) {
instance->setIconKey("steve");
m_newInstance->setIconKey("steve");
} else if (m_pack.name.contains("FTB") || m_pack.name.contains("Feed The Beast")) {
instance->setIconKey("ftb_logo");
m_newInstance->setIconKey("ftb_logo");
} else {
instance->setIconKey("flame");
m_newInstance->setIconKey("flame");
}
}
int recommendedRAM = m_pack.minecraft.recommendedRAM;
// only set memory if this is a fresh instance
if (!m_instance && recommendedRAM > 0) {
if (!m_oldInstance && recommendedRAM > 0) {
const uint64_t sysMiB = HardwareInfo::totalRamMiB();
const uint64_t max = sysMiB * 0.9;
@ -424,8 +461,8 @@ std::unique_ptr<MinecraftInstance> FlameCreationTask::createInstance()
recommendedRAM = max;
}
instance->settings()->set("OverrideMemory", true);
instance->settings()->set("MaxMemAlloc", recommendedRAM);
m_newInstance->settings()->set("OverrideMemory", true);
m_newInstance->settings()->set("MaxMemAlloc", recommendedRAM);
}
QString jarmodsPath = FS::PathCombine(m_stagingPath, "minecraft", "jarmods");
@ -439,53 +476,31 @@ std::unique_ptr<MinecraftInstance> FlameCreationTask::createInstance()
qDebug() << info.fileName();
jarMods.push_back(info.absoluteFilePath());
}
auto profile = instance->getPackProfile();
auto* profile = m_newInstance->getPackProfile();
profile->installJarMods(jarMods);
// nuke the original files
FS::deletePath(jarmodsPath);
}
// Don't add managed info to packs without an ID (most likely imported from ZIP)
if (!m_managedId.isEmpty())
instance->setManagedPack("flame", m_managedId, m_pack.name, m_managedVersionId, m_pack.version);
else
instance->setManagedPack("flame", "", name(), "", "");
setManagedPack(m_newInstance.get());
instance->setName(name());
m_newInstance->setName(name());
m_modIdResolver.reset(new Flame::FileResolvingTask(m_pack));
connect(m_modIdResolver.get(), &Flame::FileResolvingTask::succeeded, this, [this, &loop] { idResolverSucceeded(loop); });
connect(m_modIdResolver.get(), &Flame::FileResolvingTask::failed, [this, &loop](QString reason) {
connect(m_modIdResolver.get(), &Flame::FileResolvingTask::succeeded, this, &FlameCreationTask::idResolverSucceeded);
connect(m_modIdResolver.get(), &Flame::FileResolvingTask::failed, [this](const QString& reason) {
m_modIdResolver.reset();
setError(tr("Unable to resolve mod IDs:\n") + reason);
loop.quit();
emitFailed(tr("Unable to resolve mod IDs:\n") + reason);
});
connect(m_modIdResolver.get(), &Flame::FileResolvingTask::aborted, &loop, &QEventLoop::quit);
connect(m_modIdResolver.get(), &Flame::FileResolvingTask::aborted, this, &FlameCreationTask::emitAborted);
connect(m_modIdResolver.get(), &Flame::FileResolvingTask::progress, this, &FlameCreationTask::setProgress);
connect(m_modIdResolver.get(), &Flame::FileResolvingTask::status, this, &FlameCreationTask::setStatus);
connect(m_modIdResolver.get(), &Flame::FileResolvingTask::stepProgress, this, &FlameCreationTask::propagateStepProgress);
connect(m_modIdResolver.get(), &Flame::FileResolvingTask::details, this, &FlameCreationTask::setDetails);
m_modIdResolver->start();
loop.exec();
bool did_succeed = getError().isEmpty();
// Update information of the already installed instance, if any.
if (m_instance && did_succeed) {
setAbortable(false);
auto inst = m_instance.value();
inst->copyManagedPack(*instance);
}
if (did_succeed) {
return instance;
}
return nullptr;
}
void FlameCreationTask::idResolverSucceeded(QEventLoop& loop)
void FlameCreationTask::idResolverSucceeded()
{
auto results = m_modIdResolver->getResults().files;
@ -500,7 +515,6 @@ void FlameCreationTask::idResolverSucceeded(QEventLoop& loop)
OptionalModDialog optionalModDialog(m_parent, optionalFiles);
if (optionalModDialog.exec() == QDialog::Rejected) {
emitAborted();
loop.quit();
return;
}
@ -508,7 +522,7 @@ void FlameCreationTask::idResolverSucceeded(QEventLoop& loop)
}
// first check for blocked mods
QList<BlockedMod> blocked_mods;
QList<BlockedMod> blockedMods;
auto anyBlocked = false;
for (const auto& result : results.values()) {
if (result.resourceType != ModPlatform::ResourceType::Mod) {
@ -517,19 +531,19 @@ void FlameCreationTask::idResolverSucceeded(QEventLoop& loop)
// skip optional mods that were not selected
if (result.version.downloadUrl.isEmpty()) {
BlockedMod blocked_mod;
blocked_mod.name = result.version.fileName;
blocked_mod.websiteUrl = QString("%1/download/%2").arg(result.pack.websiteUrl, QString::number(result.fileId));
blocked_mod.hash = result.version.hash;
blocked_mod.matched = false;
blocked_mod.localPath = "";
blocked_mod.targetFolder = result.targetFolder;
BlockedMod blockedMod;
blockedMod.name = result.version.fileName;
blockedMod.websiteUrl = QString("%1/download/%2").arg(result.pack.websiteUrl, QString::number(result.fileId));
blockedMod.hash = result.version.hash;
blockedMod.matched = false;
blockedMod.localPath = "";
blockedMod.targetFolder = result.targetFolder;
auto fileName = result.version.fileName;
fileName = FS::RemoveInvalidPathChars(fileName);
auto relpath = FS::PathCombine(result.targetFolder, fileName);
blocked_mod.disabled = !result.required && !m_selectedOptionalMods.contains(relpath);
blockedMod.disabled = !result.required && !m_selectedOptionalMods.contains(relpath);
blocked_mods.append(blocked_mod);
blockedMods.append(blockedMod);
anyBlocked = true;
}
@ -537,28 +551,28 @@ void FlameCreationTask::idResolverSucceeded(QEventLoop& loop)
if (anyBlocked) {
qWarning() << "Blocked mods found, displaying mod list";
BlockedModsDialog message_dialog(m_parent, tr("Blocked mods found"),
tr("The following files are not available for download in third party launchers.<br/>"
"You will need to manually download them and add them to the instance."),
blocked_mods);
BlockedModsDialog messageDialog(m_parent, tr("Blocked mods found"),
tr("The following files are not available for download in third party launchers.<br/>"
"You will need to manually download them and add them to the instance."),
blockedMods);
message_dialog.setModal(true);
messageDialog.setModal(true);
if (message_dialog.exec()) {
qDebug() << "Post dialog blocked mods list:" << blocked_mods;
copyBlockedMods(blocked_mods);
setupDownloadJob(loop);
if (messageDialog.exec() != 0) {
qDebug() << "Post dialog blocked mods list:" << blockedMods;
copyBlockedMods(blockedMods);
setupDownloadJob();
} else {
m_modIdResolver.reset();
setError("Canceled");
loop.quit();
emitAborted();
return;
}
} else {
setupDownloadJob(loop);
setupDownloadJob();
}
}
void FlameCreationTask::setupDownloadJob(QEventLoop& loop)
void FlameCreationTask::setupDownloadJob()
{
m_filesJob.reset(new NetJob(tr("Mod Download Flame"), APPLICATION->network()));
auto results = m_modIdResolver->getResults().files;
@ -582,13 +596,13 @@ void FlameCreationTask::setupDownloadJob(QEventLoop& loop)
}
}
connect(m_filesJob.get(), &NetJob::finished, this, [this, &loop]() {
connect(m_filesJob.get(), &NetJob::finished, this, [this]() {
m_filesJob.reset();
validateOtherResources(loop);
validateOtherResources();
});
connect(m_filesJob.get(), &NetJob::failed, [this](QString reason) {
m_filesJob.reset();
setError(reason);
emitFailed(std::move(reason));
});
connect(m_filesJob.get(), &NetJob::progress, this, [this](qint64 current, qint64 total) {
setDetails(tr("%1 out of %2 complete").arg(current).arg(total));
@ -601,23 +615,24 @@ void FlameCreationTask::setupDownloadJob(QEventLoop& loop)
}
/// @brief copy the matched blocked mods to the instance staging area
/// @param blocked_mods list of the blocked mods and their matched paths
void FlameCreationTask::copyBlockedMods(QList<BlockedMod> const& blocked_mods)
/// @param blockedMods list of the blocked mods and their matched paths
void FlameCreationTask::copyBlockedMods(const QList<BlockedMod>& blockedMods)
{
setStatus(tr("Copying Blocked Mods..."));
setAbortable(false);
int i = 0;
int total = blocked_mods.length();
setProgress(i, total);
for (auto const& mod : blocked_mods) {
auto const total = blockedMods.length();
setProgress(i, static_cast<int>(total));
for (const auto& mod : blockedMods) {
if (!mod.matched) {
qDebug() << mod.name << "was not matched to a local file, skipping copy";
continue;
}
auto destPath = FS::PathCombine(m_stagingPath, "minecraft", mod.targetFolder, mod.name);
if (mod.disabled)
if (mod.disabled) {
destPath += ".disabled";
}
setStatus(tr("Copying Blocked Mods (%1 out of %2 are done)").arg(QString::number(i), QString::number(total)));
@ -640,17 +655,17 @@ void FlameCreationTask::copyBlockedMods(QList<BlockedMod> const& blocked_mods)
setAbortable(true);
}
void FlameCreationTask::validateOtherResources(QEventLoop& loop)
void FlameCreationTask::validateOtherResources()
{
qDebug() << "Validating whether other resources are in the right place";
QStringList zipMods;
for (auto [fileName, targetFolder] : m_otherResources) {
for (const auto& [fileName, targetFolder] : m_otherResources) {
qDebug() << "Checking" << fileName << "...";
auto localPath = FS::PathCombine(m_stagingPath, "minecraft", targetFolder, fileName);
/// @brief check the target and move the the file
/// @return path where file can now be found
auto validatePath = [&localPath, this](QString fileName, QString targetFolder, QString realTarget) {
auto validatePath = [&localPath, this](const QString& fileName, const QString& targetFolder, const QString& realTarget) {
if (targetFolder != realTarget) {
qDebug() << "Target folder of" << fileName << "is incorrect, it belongs in" << realTarget;
auto destPath = FS::PathCombine(m_stagingPath, "minecraft", realTarget, fileName);
@ -664,7 +679,7 @@ void FlameCreationTask::validateOtherResources(QEventLoop& loop)
return localPath;
};
auto installWorld = [this](QString worldPath) {
auto installWorld = [this](const QString& worldPath) {
qDebug() << "Installing World from" << worldPath;
QFileInfo worldFileInfo(worldPath);
World w(worldFileInfo);
@ -714,13 +729,49 @@ void FlameCreationTask::validateOtherResources(QEventLoop& loop)
auto task = makeShared<ConcurrentTask>("CreateModMetadata", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt());
auto results = m_modIdResolver->getResults().files;
auto folder = FS::PathCombine(m_stagingPath, "minecraft", "mods", ".index");
for (auto file : results) {
for (const auto& file : results) {
if (file.targetFolder != "mods" || (file.version.fileName.endsWith(".zip") && !zipMods.contains(file.version.fileName))) {
continue;
}
task->addTask(makeShared<LocalResourceUpdateTask>(folder, file.pack, file.version));
}
connect(task.get(), &Task::finished, &loop, &QEventLoop::quit);
connect(task.get(), &Task::finished, this, &FlameCreationTask::finishInstall);
m_processUpdateFileInfoJob = task;
task->start();
}
void FlameCreationTask::finishInstall()
{
// Update information of the already installed instance, if any.
if (m_oldInstance) {
setAbortable(false);
setManagedPack(*m_oldInstance);
}
if (shouldOverride()) {
bool deleteFailed = false;
setAbortable(false);
setStatus(tr("Removing old conflicting files..."));
qDebug() << "Removing old files";
for (const QString& path : m_filesToRemove) {
if (!QFile::exists(path)) {
continue;
}
qDebug() << "Removing" << path;
if (!QFile::remove(path)) {
qCritical() << "Could not remove" << path;
deleteFailed = true;
}
}
if (deleteFailed) {
emitFailed(tr("Failed to remove old conflicting files."));
return;
}
}
downloadFiles(m_newInstance.get());
}

View file

@ -35,47 +35,51 @@
#pragma once
#include "InstanceCreationTask.h"
#include <memory>
#include <optional>
#include "BaseInstance.h"
#include "InstanceTask.h"
#include "minecraft/MinecraftInstance.h"
#include "modplatform/flame/FileResolvingTask.h"
#include "net/NetJob.h"
#include "ui/dialogs/BlockedModsDialog.h"
class FlameCreationTask final : public InstanceCreationTask {
class FlameCreationTask final : public InstanceTask {
Q_OBJECT
public:
FlameCreationTask(const QString& staging_path,
SettingsObject* global_settings,
FlameCreationTask(const QString& stagingPath,
SettingsObject* globalSettings,
QWidget* parent,
QString id,
QString version_id,
QString original_instance_id = {})
: InstanceCreationTask(), m_parent(parent), m_managedId(std::move(id)), m_managedVersionId(std::move(version_id))
QString versionId,
const QString& originalInstanceId = {})
: m_parent(parent), m_managedId(std::move(id)), m_managedVersionId(std::move(versionId))
{
setStagingPath(staging_path);
setParentSettings(global_settings);
setStagingPath(stagingPath);
setParentSettings(globalSettings);
m_original_instance_id = std::move(original_instance_id);
m_originalInstanceId = originalInstanceId;
}
bool abort() override;
bool updateInstance() override;
std::unique_ptr<MinecraftInstance> createInstance() override;
void createInstance();
void executeTask() override;
private slots:
void idResolverSucceeded(QEventLoop&);
void setupDownloadJob(QEventLoop&);
void copyBlockedMods(QList<BlockedMod> const& blocked_mods);
void validateOtherResources(QEventLoop& loop);
QString getVersionForLoader(QString uid, QString loaderType, QString version, QString mcVersion);
void idResolverSucceeded();
void setupDownloadJob();
void copyBlockedMods(const QList<BlockedMod>& blockedMods);
void validateOtherResources();
QString getVersionForLoader(const QString& uid, const QString& loaderType, const QString& version, const QString& mcVersion);
void finishInstall();
private:
void setManagedPack(BaseInstance* instance);
private:
QWidget* m_parent = nullptr;
@ -91,7 +95,8 @@ class FlameCreationTask final : public InstanceCreationTask {
QList<std::pair<QString, QString>> m_otherResources;
std::optional<BaseInstance*> m_instance;
std::optional<BaseInstance*> m_oldInstance{};
std::unique_ptr<MinecraftInstance> m_newInstance{};
QStringList m_selectedOptionalMods;
};

View file

@ -1,20 +1,20 @@
#include "PackManifest.h"
#include "Json.h"
static void loadFileV1(Flame::File& f, QJsonObject& file)
namespace {
void loadFileV1(Flame::File& f, QJsonObject& file)
{
f.projectId = Json::requireInteger(file, "projectID");
f.fileId = Json::requireInteger(file, "fileID");
f.required = file["required"].toBool(true);
}
static void loadModloaderV1(Flame::Modloader& m, QJsonObject& modLoader)
void loadModloaderV1(Flame::Modloader& m, QJsonObject& modLoader)
{
m.id = Json::requireString(modLoader, "id");
m.primary = modLoader["primary"].toBool();
}
static void loadMinecraftV1(Flame::Minecraft& m, QJsonObject& minecraft)
void loadMinecraftV1(Flame::Minecraft& m, QJsonObject& minecraft)
{
m.version = Json::requireString(minecraft, "version");
// extra libraries... apparently only used for a custom Minecraft launcher in the 1.2.5 FTB retro pack
@ -30,7 +30,7 @@ static void loadMinecraftV1(Flame::Minecraft& m, QJsonObject& minecraft)
m.recommendedRAM = minecraft["recommendedRam"].toInt();
}
static void loadManifestV1(Flame::Manifest& pack, QJsonObject& manifest)
void loadManifestV1(Flame::Manifest& pack, QJsonObject& manifest)
{
auto mc = Json::requireObject(manifest, "minecraft");
@ -52,8 +52,9 @@ static void loadManifestV1(Flame::Manifest& pack, QJsonObject& manifest)
pack.overrides = manifest["overrides"].toString("overrides");
pack.is_loaded = true;
pack.isLoaded = true;
}
} // namespace
void Flame::loadManifest(Flame::Manifest& m, const QString& filepath)
{

View file

@ -81,7 +81,7 @@ struct Manifest {
QMap<int, Flame::File> files;
QString overrides;
bool is_loaded = false;
bool isLoaded = false;
};
void loadManifest(Flame::Manifest& m, const QString& filepath);

View file

@ -38,6 +38,8 @@
#include "FTBPackInstallTask.h"
#include <utility>
#include "FileSystem.h"
#include "Json.h"
#include "minecraft/MinecraftInstance.h"
@ -59,15 +61,18 @@ PackInstallTask::PackInstallTask(Modpack pack, QString version, QWidget* parent)
bool PackInstallTask::abort()
{
if (!canAbort())
if (!canAbort()) {
return false;
}
bool aborted = true;
if (m_net_job)
if (m_net_job) {
aborted &= m_net_job->abort();
if (m_modIdResolverTask)
}
if (m_modIdResolverTask) {
aborted &= m_modIdResolverTask->abort();
}
return aborted ? InstanceTask::abort() : false;
}
@ -78,15 +83,15 @@ void PackInstallTask::executeTask()
setAbortable(false);
// Find pack version
auto version_it = std::find_if(m_pack.versions.constBegin(), m_pack.versions.constEnd(),
[this](const FTB::VersionInfo& a) { return a.name == m_versionName; });
auto versionIt = std::find_if(m_pack.versions.constBegin(), m_pack.versions.constEnd(),
[this](const FTB::VersionInfo& a) { return a.name == m_versionName; });
if (version_it == m_pack.versions.constEnd()) {
if (versionIt == m_pack.versions.constEnd()) {
emitFailed(tr("Failed to find pack version %1").arg(m_versionName));
return;
}
auto version = *version_it;
const auto& version = *versionIt;
auto netJob = makeShared<NetJob>("FTB::VersionFetch", APPLICATION->network());
@ -112,10 +117,10 @@ void PackInstallTask::onManifestDownloadSucceeded(QByteArray* responsePtr)
QByteArray response = std::move(*responsePtr);
m_net_job.reset();
QJsonParseError parse_error{};
QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error);
if (parse_error.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response from FTB at " << parse_error.offset << " reason: " << parse_error.errorString();
QJsonParseError parseError{};
QJsonDocument const doc = QJsonDocument::fromJson(response, &parseError);
if (parseError.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response from FTB at " << parseError.offset << " reason: " << parseError.errorString();
qWarning() << response;
return;
}
@ -179,24 +184,25 @@ void PackInstallTask::onResolveModsSucceeded()
Flame::Manifest results = m_modIdResolverTask->getResults();
for (int index = 0; index < m_fileIds.size(); index++) {
const auto file_id = m_fileIds.at(index);
if (file_id < 0)
const auto fileId = m_fileIds.at(index);
if (fileId < 0) {
continue;
}
Flame::File resultsFile = results.files[file_id];
Flame::File const resultsFile = results.files.value(fileId);
VersionFile& localFile = m_version.files[index];
// First check for blocked mods
if (resultsFile.version.downloadUrl.isEmpty()) {
BlockedMod blocked_mod;
blocked_mod.name = resultsFile.version.fileName;
blocked_mod.websiteUrl = QString("%1/download/%2").arg(resultsFile.pack.websiteUrl, QString::number(resultsFile.fileId));
blocked_mod.hash = resultsFile.version.hash;
blocked_mod.matched = false;
blocked_mod.localPath = "";
blocked_mod.targetFolder = resultsFile.targetFolder;
BlockedMod blockedMod;
blockedMod.name = resultsFile.version.fileName;
blockedMod.websiteUrl = QString("%1/download/%2").arg(resultsFile.pack.websiteUrl, QString::number(resultsFile.fileId));
blockedMod.hash = resultsFile.version.hash;
blockedMod.matched = false;
blockedMod.localPath = "";
blockedMod.targetFolder = resultsFile.targetFolder;
m_blockedMods.append(blocked_mod);
m_blockedMods.append(blockedMod);
anyBlocked = true;
} else {
@ -209,14 +215,14 @@ void PackInstallTask::onResolveModsSucceeded()
if (anyBlocked) {
qDebug() << "Blocked files found, displaying file list";
BlockedModsDialog message_dialog(m_parent, tr("Blocked files found"),
tr("The following files are not available for download in third party launchers.<br/>"
"You will need to manually download them and add them to the instance."),
m_blockedMods);
BlockedModsDialog messageDialog(m_parent, tr("Blocked files found"),
tr("The following files are not available for download in third party launchers.<br/>"
"You will need to manually download them and add them to the instance."),
m_blockedMods);
message_dialog.setModal(true);
messageDialog.setModal(true);
if (message_dialog.exec() == QDialog::Accepted) {
if (messageDialog.exec() == QDialog::Accepted) {
qDebug() << "Post dialog blocked mods list: " << m_blockedMods;
createInstance();
} else {
@ -238,20 +244,21 @@ void PackInstallTask::createInstance()
auto instanceConfigPath = FS::PathCombine(m_stagingPath, "instance.cfg");
auto instanceSettings = std::make_unique<INISettingsObject>(instanceConfigPath);
MinecraftInstance instance(m_globalSettings, std::move(instanceSettings), m_stagingPath);
auto components = instance.getPackProfile();
m_instance = std::make_unique<MinecraftInstance>(m_globalSettings, std::move(instanceSettings), m_stagingPath);
auto* components = m_instance->getPackProfile();
components->buildingFromScratch();
for (auto target : m_version.targets) {
for (const auto& target : m_version.targets) {
if (target.type == "game" && target.name == "minecraft") {
components->setComponentVersion("net.minecraft", target.version, true);
break;
}
}
for (auto target : m_version.targets) {
if (target.type != "modloader")
for (const auto& target : m_version.targets) {
if (target.type != "modloader") {
continue;
}
if (target.name == "forge") {
components->setComponentVersion("net.minecraftforge", target.version);
@ -278,11 +285,11 @@ void PackInstallTask::createInstance()
components->saveNow();
instance.setName(name());
instance.setIconKey(m_instIcon);
instance.setManagedPack("ftb", QString::number(m_pack.id), m_pack.name, QString::number(m_version.id), m_version.name);
m_instance->setName(name());
m_instance->setIconKey(m_instIcon);
m_instance->setManagedPack("ftb", QString::number(m_pack.id), m_pack.name, QString::number(m_version.id), m_version.name);
instance.saveNow();
m_instance->saveNow();
onCreateInstanceSucceeded();
}
@ -299,13 +306,14 @@ void PackInstallTask::downloadPack()
auto jobPtr = makeShared<NetJob>(tr("Mod download"), APPLICATION->network());
for (const auto& file : m_version.files) {
if (file.serverOnly || file.url.isEmpty())
if (file.serverOnly || file.url.isEmpty()) {
continue;
}
auto path = FS::PathCombine(m_stagingPath, ".minecraft", file.path, file.name);
qDebug() << "Will try to download" << file.url << "to" << path;
QFileInfo file_info(file.name);
QFileInfo const fileInfo(file.name);
auto dl = Net::Download::makeFile(file.url, path);
if (!file.sha1.isEmpty()) {
@ -333,27 +341,27 @@ void PackInstallTask::onModDownloadSucceeded()
if (!m_blockedMods.isEmpty()) {
copyBlockedMods();
}
emitSucceeded();
downloadFiles(m_instance.get());
}
void PackInstallTask::onManifestDownloadFailed(QString reason)
{
m_net_job.reset();
emitFailed(reason);
emitFailed(std::move(reason));
}
void PackInstallTask::onResolveModsFailed(QString reason)
{
m_net_job.reset();
emitFailed(reason);
emitFailed(std::move(reason));
}
void PackInstallTask::onCreateInstanceFailed(QString reason)
{
emitFailed(reason);
emitFailed(std::move(reason));
}
void PackInstallTask::onModDownloadFailed(QString reason)
{
m_net_job.reset();
emitFailed(reason);
emitFailed(std::move(reason));
}
/// @brief copy the matched blocked mods to the instance staging area
@ -362,7 +370,7 @@ void PackInstallTask::copyBlockedMods()
setStatus(tr("Copying Blocked Mods..."));
setAbortable(false);
int i = 0;
int total = m_blockedMods.length();
auto total = m_blockedMods.length();
setProgress(i, total);
for (const auto& mod : m_blockedMods) {
if (!mod.matched) {
@ -370,14 +378,14 @@ void PackInstallTask::copyBlockedMods()
continue;
}
auto dest_path = FS::PathCombine(m_stagingPath, ".minecraft", mod.targetFolder, mod.name);
auto destPath = FS::PathCombine(m_stagingPath, ".minecraft", mod.targetFolder, mod.name);
setStatus(tr("Copying Blocked Mods (%1 out of %2 are done)").arg(QString::number(i), QString::number(total)));
qDebug() << "Will try to copy" << mod.localPath << "to" << dest_path;
qDebug() << "Will try to copy" << mod.localPath << "to" << destPath;
if (!FS::copy(mod.localPath, dest_path)()) {
qDebug() << "Copy of" << mod.localPath << "to" << dest_path << "Failed";
if (!FS::copy(mod.localPath, destPath)()) {
qDebug() << "Copy of" << mod.localPath << "to" << destPath << "Failed";
}
i++;

View file

@ -46,6 +46,7 @@
#include "ui/dialogs/BlockedModsDialog.h"
#include <QWidget>
#include <memory>
namespace FTB {
@ -91,6 +92,8 @@ class PackInstallTask final : public InstanceTask {
QMap<QString, QString> m_filesToCopy;
QList<BlockedMod> m_blockedMods;
std::unique_ptr<MinecraftInstance> m_instance;
// FIXME: nuke
QWidget* m_parent;
};

View file

@ -20,11 +20,9 @@
#include <QtConcurrent>
#include "BaseInstance.h"
#include "FileSystem.h"
#include "minecraft/MinecraftInstance.h"
#include "minecraft/PackProfile.h"
#include "modplatform/ResourceAPI.h"
#include "modplatform/import_ftb/PackHelpers.h"
#include "settings/INISettingsObject.h"
@ -51,19 +49,20 @@ void PackInstallTask::copySettings()
progress(2, 2);
QString instanceConfigPath = FS::PathCombine(m_stagingPath, "instance.cfg");
MinecraftInstance instance(m_globalSettings, std::make_unique<INISettingsObject>(instanceConfigPath), m_stagingPath);
m_instance =
std::make_unique<MinecraftInstance>(m_globalSettings, std::make_unique<INISettingsObject>(instanceConfigPath), m_stagingPath);
{
SettingsObject::Lock lock(instance.settings());
instance.settings()->set("InstanceType", "OneSix");
instance.settings()->set("totalTimePlayed", m_pack.totalPlayTime / 1000);
SettingsObject::Lock const lock(m_instance->settings());
m_instance->settings()->set("InstanceType", "OneSix");
m_instance->settings()->set("totalTimePlayed", m_pack.totalPlayTime / 1000);
if (m_pack.jvmArgs.isValid() && !m_pack.jvmArgs.toString().isEmpty()) {
instance.settings()->set("OverrideJavaArgs", true);
instance.settings()->set("JvmArgs", m_pack.jvmArgs.toString());
m_instance->settings()->set("OverrideJavaArgs", true);
m_instance->settings()->set("JvmArgs", m_pack.jvmArgs.toString());
}
auto* components = instance.getPackProfile();
auto* components = m_instance->getPackProfile();
components->buildingFromScratch();
components->setComponentVersion("net.minecraft", m_pack.mcVersion, true);
@ -92,13 +91,13 @@ void PackInstallTask::copySettings()
}
components->saveNow();
instance.setName(name());
m_instance->setName(name());
if (m_instIcon == "default") {
m_instIcon = "ftb_logo";
}
instance.setIconKey(m_instIcon);
m_instance->setIconKey(m_instIcon);
}
emitSucceeded();
downloadFiles(m_instance.get());
}
} // namespace FTBImportAPP

View file

@ -21,8 +21,11 @@
#include <QFuture>
#include <QFutureWatcher>
#include <memory>
#include <utility>
#include "InstanceTask.h"
#include "PackHelpers.h"
#include "minecraft/MinecraftInstance.h"
namespace FTBImportAPP {
@ -30,11 +33,11 @@ class PackInstallTask : public InstanceTask {
Q_OBJECT
public:
explicit PackInstallTask(const Modpack& pack) : m_pack(pack) {}
virtual ~PackInstallTask() = default;
explicit PackInstallTask(Modpack pack) : m_pack(std::move(pack)) {}
~PackInstallTask() override = default;
protected:
virtual void executeTask() override;
void executeTask() override;
private slots:
void copySettings();
@ -43,6 +46,8 @@ class PackInstallTask : public InstanceTask {
QFuture<bool> m_copyFuture;
QFutureWatcher<bool> m_copyFutureWatcher;
std::unique_ptr<MinecraftInstance> m_instance;
const Modpack m_pack;
};

View file

@ -36,6 +36,7 @@
#include "PackInstallTask.h"
#include <QtConcurrent>
#include <utility>
#include "BaseInstance.h"
#include "FileSystem.h"
@ -52,12 +53,9 @@
namespace LegacyFTB {
PackInstallTask::PackInstallTask(QNetworkAccessManager* network, const Modpack& pack, QString version)
{
m_pack = pack;
m_version = version;
m_network = network;
}
PackInstallTask::PackInstallTask(QNetworkAccessManager* network, Modpack pack, QString version)
: m_network(network), m_pack(std::move(pack)), m_version(std::move(version))
{}
void PackInstallTask::executeTask()
{
@ -73,22 +71,22 @@ void PackInstallTask::downloadPack()
auto path = QString("%1/%2/%3").arg(m_pack.dir, m_version.replace(".", "_"), m_pack.file);
auto entry = APPLICATION->metacache()->resolveEntry("FTBPacks", path);
entry->setStale(true);
archivePath = entry->getFullPath();
netJobContainer.reset(new NetJob("Download FTB Pack", m_network));
m_archivePath = entry->getFullPath();
m_netJobContainer.reset(new NetJob("Download FTB Pack", m_network));
QString url;
if (m_pack.type == PackType::Private) {
url = QString(BuildConfig.LEGACY_FTB_CDN_BASE_URL + "privatepacks/%1").arg(path);
} else {
url = QString(BuildConfig.LEGACY_FTB_CDN_BASE_URL + "modpacks/%1").arg(path);
}
netJobContainer->addNetAction(Net::ApiDownload::makeCached(url, entry));
m_netJobContainer->addNetAction(Net::ApiDownload::makeCached(url, entry));
connect(netJobContainer.get(), &NetJob::succeeded, this, &PackInstallTask::unzip);
connect(netJobContainer.get(), &NetJob::failed, this, &PackInstallTask::emitFailed);
connect(netJobContainer.get(), &NetJob::stepProgress, this, &PackInstallTask::propagateStepProgress);
connect(netJobContainer.get(), &NetJob::aborted, this, &PackInstallTask::emitAborted);
connect(m_netJobContainer.get(), &NetJob::succeeded, this, &PackInstallTask::unzip);
connect(m_netJobContainer.get(), &NetJob::failed, this, &PackInstallTask::emitFailed);
connect(m_netJobContainer.get(), &NetJob::stepProgress, this, &PackInstallTask::propagateStepProgress);
connect(m_netJobContainer.get(), &NetJob::aborted, this, &PackInstallTask::emitAborted);
netJobContainer->start();
m_netJobContainer->start();
setAbortable(true);
progress(1, 4);
@ -102,7 +100,7 @@ void PackInstallTask::unzip()
QDir extractDir(m_stagingPath);
m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), QOverload<QString, QString>::of(MMCZip::extractDir), archivePath,
m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), QOverload<QString, QString>::of(MMCZip::extractDir), m_archivePath,
extractDir.absolutePath() + "/unzip");
connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::finished, this, &PackInstallTask::onUnzipFinished);
connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::canceled, this, &PackInstallTask::onUnzipCanceled);
@ -133,11 +131,12 @@ void PackInstallTask::install()
}
QString instanceConfigPath = FS::PathCombine(m_stagingPath, "instance.cfg");
MinecraftInstance instance(m_globalSettings, std::make_unique<INISettingsObject>(instanceConfigPath), m_stagingPath);
m_instance =
std::make_unique<MinecraftInstance>(m_globalSettings, std::make_unique<INISettingsObject>(instanceConfigPath), m_stagingPath);
{
SettingsObject::Lock lock(instance.settings());
SettingsObject::Lock const lock(m_instance->settings());
auto components = instance.getPackProfile();
auto* components = m_instance->getPackProfile();
components->buildingFromScratch();
components->setComponentVersion("net.minecraft", m_pack.mcVersion, true);
@ -177,7 +176,7 @@ void PackInstallTask::install()
qDebug() << "Found jarmods, installing...";
QStringList jarmods;
for (auto info : jarmodDir.entryInfoList(QDir::NoDotAndDotDot | QDir::Files)) {
for (const auto& info : jarmodDir.entryInfoList(QDir::NoDotAndDotDot | QDir::Files)) {
qDebug() << "Jarmod:" << info.fileName();
jarmods.push_back(info.absoluteFilePath());
}
@ -199,14 +198,14 @@ void PackInstallTask::install()
progress(4, 4);
instance.setName(name());
m_instance->setName(name());
if (m_instIcon == "default") {
m_instIcon = "ftb_logo";
}
instance.setIconKey(m_instIcon);
m_instance->setIconKey(m_instIcon);
}
emitSucceeded();
downloadFiles(m_instance.get());
}
bool PackInstallTask::abort()
@ -215,7 +214,7 @@ bool PackInstallTask::abort()
return false;
}
netJobContainer->abort();
m_netJobContainer->abort();
return InstanceTask::abort();
}

View file

@ -1,11 +1,10 @@
#pragma once
#include "InstanceTask.h"
#include "PackHelpers.h"
#include "meta/Index.h"
#include "meta/Version.h"
#include "meta/VersionList.h"
#include "minecraft/MinecraftInstance.h"
#include "net/NetJob.h"
#include <memory>
#include <optional>
namespace LegacyFTB {
@ -14,15 +13,15 @@ class PackInstallTask : public InstanceTask {
Q_OBJECT
public:
explicit PackInstallTask(QNetworkAccessManager* network, const Modpack& pack, QString version);
virtual ~PackInstallTask() {}
explicit PackInstallTask(QNetworkAccessManager* network, Modpack pack, QString version);
~PackInstallTask() override = default;
bool canAbort() const override { return true; }
bool abort() override;
protected:
//! Entry point for tasks.
virtual void executeTask() override;
void executeTask() override;
private:
void downloadPack();
@ -36,11 +35,13 @@ class PackInstallTask : public InstanceTask {
private: /* data */
QNetworkAccessManager* m_network;
bool abortable = false;
bool m_abortable = false;
QFuture<std::optional<QStringList>> m_extractFuture;
QFutureWatcher<std::optional<QStringList>> m_extractFutureWatcher;
NetJob::Ptr netJobContainer;
QString archivePath;
NetJob::Ptr m_netJobContainer;
QString m_archivePath;
std::unique_ptr<MinecraftInstance> m_instance;
Modpack m_pack;
QString m_version;

View file

@ -16,7 +16,6 @@
#include "net/ChecksumValidator.h"
#include "net/ApiDownload.h"
#include "net/ApiHeaderProxy.h"
#include "net/NetJob.h"
#include "modplatform/ModIndex.h"
@ -39,10 +38,10 @@ bool ModrinthCreationTask::abort()
if (m_task) {
m_task->abort();
}
return InstanceCreationTask::abort();
return InstanceTask::abort();
}
bool ModrinthCreationTask::updateInstance()
void ModrinthCreationTask::executeTask()
{
auto* instanceList = APPLICATION->instances();
@ -58,28 +57,30 @@ bool ModrinthCreationTask::updateInstance()
inst = instanceList->getInstanceById(originalName());
if (!inst) {
return false;
createInstance();
return;
}
}
}
QString indexPath = FS::PathCombine(m_stagingPath, "modrinth.index.json");
if (!parseManifest(indexPath, m_files, true, false)) {
return false;
return;
}
auto versionName = inst->getManagedPackVersionName();
m_root_path = QFileInfo(inst->gameRoot()).fileName();
m_rootPath = QFileInfo(inst->gameRoot()).fileName();
auto versionStr = !versionName.isEmpty() ? tr(" (version %1)").arg(versionName) : "";
if (shouldConfirmUpdate()) {
auto shouldUpdate = askIfShouldUpdate(m_parent, versionStr);
if (shouldUpdate == ShouldUpdate::SkipUpdating) {
return false;
createInstance();
return;
}
if (shouldUpdate == ShouldUpdate::Cancel) {
m_abort = true;
return false;
emitAborted();
return;
}
}
@ -147,30 +148,28 @@ bool ModrinthCreationTask::updateInstance()
QMessageBox::Warning, QMessageBox::Ok | QMessageBox::Cancel);
if (dialog->exec() == QDialog::DialogCode::Rejected) {
m_abort = true;
return false;
emitAborted();
return;
}
}
setOverride(true, inst->id());
qDebug() << "Will override instance!";
m_instance = inst;
m_oldInstance = inst;
// We let it go through the createInstance() stage, just with a couple modifications for updating
return false;
createInstance();
}
// https://docs.modrinth.com/docs/modpacks/format_definition/
std::unique_ptr<MinecraftInstance> ModrinthCreationTask::createInstance()
void ModrinthCreationTask::createInstance()
{
QEventLoop loop;
QString parentFolder(FS::PathCombine(m_stagingPath, "mrpack"));
QString indexPath = FS::PathCombine(m_stagingPath, "modrinth.index.json");
if (m_files.empty() && !parseManifest(indexPath, m_files, true, true)) {
return nullptr;
return;
}
// Keep index file in case we need it some other time (like when changing versions)
@ -178,7 +177,7 @@ std::unique_ptr<MinecraftInstance> ModrinthCreationTask::createInstance()
FS::ensureFilePathExists(newIndexPlace);
FS::move(indexPath, newIndexPlace);
auto mcPath = FS::PathCombine(m_stagingPath, m_root_path);
auto mcPath = FS::PathCombine(m_stagingPath, m_rootPath);
auto overridePath = FS::PathCombine(m_stagingPath, "overrides");
if (QFile::exists(overridePath)) {
@ -187,8 +186,8 @@ std::unique_ptr<MinecraftInstance> ModrinthCreationTask::createInstance()
// Apply the overrides
if (!FS::move(overridePath, mcPath)) {
setError(tr("Could not rename the overrides folder:\n") + "overrides");
return nullptr;
emitFailed(tr("Could not rename the overrides folder:\n") + "overrides");
return;
}
}
@ -200,85 +199,80 @@ std::unique_ptr<MinecraftInstance> ModrinthCreationTask::createInstance()
// Apply the overrides
if (!FS::overrideFolder(mcPath, clientOverridePath)) {
setError(tr("Could not rename the client overrides folder:\n") + "client overrides");
return nullptr;
emitFailed(tr("Could not rename the client overrides folder:\n") + "client overrides");
return;
}
}
QString configPath = FS::PathCombine(m_stagingPath, "instance.cfg");
auto instanceSettings = std::make_unique<INISettingsObject>(configPath);
auto instance = std::make_unique<MinecraftInstance>(m_globalSettings, std::move(instanceSettings), m_stagingPath);
m_newInstance = std::make_unique<MinecraftInstance>(m_globalSettings, std::move(instanceSettings), m_stagingPath);
auto* components = instance->getPackProfile();
auto* components = m_newInstance->getPackProfile();
components->buildingFromScratch();
components->setComponentVersion("net.minecraft", m_minecraft_version, true);
components->setComponentVersion("net.minecraft", m_minecraftVersion, true);
QString loader;
if (!m_fabric_version.isEmpty()) {
components->setComponentVersion("net.fabricmc.fabric-loader", m_fabric_version);
if (!m_fabricVersion.isEmpty()) {
components->setComponentVersion("net.fabricmc.fabric-loader", m_fabricVersion);
loader = ModPlatform::getModLoaderAsString(ModPlatform::ModLoaderType::Fabric);
}
if (!m_quilt_version.isEmpty()) {
components->setComponentVersion("org.quiltmc.quilt-loader", m_quilt_version);
if (!m_quiltVersion.isEmpty()) {
components->setComponentVersion("org.quiltmc.quilt-loader", m_quiltVersion);
loader = ModPlatform::getModLoaderAsString(ModPlatform::ModLoaderType::Quilt);
}
if (!m_forge_version.isEmpty()) {
components->setComponentVersion("net.minecraftforge", m_forge_version);
if (!m_forgeVersion.isEmpty()) {
components->setComponentVersion("net.minecraftforge", m_forgeVersion);
loader = ModPlatform::getModLoaderAsString(ModPlatform::ModLoaderType::Forge);
}
if (!m_neoForge_version.isEmpty()) {
components->setComponentVersion("net.neoforged", m_neoForge_version);
if (!m_neoForgeVersion.isEmpty()) {
components->setComponentVersion("net.neoforged", m_neoForgeVersion);
loader = ModPlatform::getModLoaderAsString(ModPlatform::ModLoaderType::NeoForge);
}
if (m_instIcon != "default") {
instance->setIconKey(m_instIcon);
} else if (!m_managed_id.isEmpty()) {
instance->setIconKey("modrinth");
m_newInstance->setIconKey(m_instIcon);
} else if (!m_managedId.isEmpty()) {
m_newInstance->setIconKey("modrinth");
}
// Don't add managed info to packs without an ID (most likely imported from ZIP)
if (!m_managed_id.isEmpty()) {
instance->setManagedPack("modrinth", m_managed_id, m_managed_name, m_managed_version_id, version());
} else {
instance->setManagedPack("modrinth", "", name(), "", "");
}
setManagedPack(m_newInstance.get());
instance->setName(name());
instance->saveNow();
m_newInstance->setName(name());
m_newInstance->saveNow();
auto downloadMods = makeShared<NetJob>(tr("Mod Download Modrinth"), APPLICATION->network());
auto rootModpackPath = FS::PathCombine(m_stagingPath, m_root_path);
auto rootModpackPath = FS::PathCombine(m_stagingPath, m_rootPath);
auto rootModpackUrl = QUrl::fromLocalFile(rootModpackPath);
// TODO make this work with other sorts of resource
QHash<QString, Resource*> resources;
for (auto& file : m_files) {
auto fileName = file.path;
fileName = FS::RemoveInvalidPathChars(fileName);
auto filePath = FS::PathCombine(rootModpackPath, fileName);
if (!rootModpackUrl.isParentOf(QUrl::fromLocalFile(filePath))) {
// This means we somehow got out of the root folder, so abort here to prevent exploits
setError(tr("One of the files has a path that leads to an arbitrary location (%1). This is a security risk and isn't allowed.")
.arg(fileName));
return nullptr;
emitFailed(
tr("One of the files has a path that leads to an arbitrary location (%1). This is a security risk and isn't allowed.")
.arg(fileName));
return;
}
if (fileName.startsWith("mods/")) {
auto* mod = new Mod(filePath);
ModDetails d;
d.mod_id = filePath;
mod->setDetails(d);
resources[file.hash.toHex()] = mod;
m_resources.insert(file.hash.toHex(), mod);
}
if (file.downloads.empty()) {
setError(tr("The file '%1' is missing a download link. This is invalid in the pack format.").arg(fileName));
return nullptr;
emitFailed(tr("The file '%1' is missing a download link. This is invalid in the pack format.").arg(fileName));
return;
}
qDebug() << "Will try to download" << file.downloads.front() << "to" << filePath;
Net::ModrinthDownloadMeta meta{
.reason = m_instance.has_value() ? "update" : "modpack",
.gameVersion = m_minecraft_version,
.reason = m_oldInstance.has_value() ? "update" : "modpack",
.gameVersion = m_minecraftVersion,
.loader = loader,
};
@ -302,14 +296,9 @@ std::unique_ptr<MinecraftInstance> ModrinthCreationTask::createInstance()
}
}
bool endedWell = false;
connect(downloadMods.get(), &NetJob::succeeded, this, [&endedWell]() { endedWell = true; });
connect(downloadMods.get(), &NetJob::failed, [this, &endedWell](const QString& reason) {
endedWell = false;
setError(reason);
});
connect(downloadMods.get(), &NetJob::finished, &loop, &QEventLoop::quit);
connect(downloadMods.get(), &NetJob::succeeded, this, &ModrinthCreationTask::ensureMetaLoop);
connect(downloadMods.get(), &NetJob::failed, this, &ModrinthCreationTask::emitFailed);
connect(downloadMods.get(), &NetJob::aborted, this, &ModrinthCreationTask::emitAborted);
connect(downloadMods.get(), &NetJob::progress, [this](qint64 current, qint64 total) {
setDetails(tr("%1 out of %2 complete").arg(current).arg(total));
setProgress(current, total);
@ -319,57 +308,6 @@ std::unique_ptr<MinecraftInstance> ModrinthCreationTask::createInstance()
setStatus(tr("Downloading mods..."));
downloadMods->start();
m_task = downloadMods;
loop.exec();
if (!endedWell) {
for (auto* resource : resources) {
delete resource;
}
return nullptr;
}
QEventLoop ensureMetaLoop;
QDir folder = FS::PathCombine(instance->modsRoot(), ".index");
auto ensureMetadataTask = makeShared<EnsureMetadataTask>(resources, folder, ModPlatform::ResourceProvider::MODRINTH);
connect(ensureMetadataTask.get(), &Task::succeeded, this, [&endedWell]() { endedWell = true; });
connect(ensureMetadataTask.get(), &Task::finished, &ensureMetaLoop, &QEventLoop::quit);
connect(ensureMetadataTask.get(), &Task::progress, [this](qint64 current, qint64 total) {
setDetails(tr("%1 out of %2 complete").arg(current).arg(total));
setProgress(current, total);
});
connect(ensureMetadataTask.get(), &Task::stepProgress, this, &ModrinthCreationTask::propagateStepProgress);
ensureMetadataTask->start();
m_task = ensureMetadataTask;
ensureMetaLoop.exec();
for (auto* resource : resources) {
delete resource;
}
resources.clear();
// Update information of the already installed instance, if any.
if (m_instance && endedWell) {
setAbortable(false);
auto* inst = m_instance.value();
// Only change the name if it didn't use a custom name, so that the previous custom name
// is preserved, but if we're using the original one, we update the version string.
// NOTE: This needs to come before the copyManagedPack call!
if (inst->name().contains(inst->getManagedPackVersionName()) && inst->name() != instance->name()) {
if (askForChangingInstanceName(m_parent, inst->name(), instance->name()) == InstanceNameChange::ShouldChange) {
inst->setName(instance->name());
}
}
inst->copyManagedPack(*instance);
}
if (endedWell) {
return instance;
}
return nullptr;
}
bool ModrinthCreationTask::parseManifest(const QString& indexPath, std::vector<File>& files, bool setInternalData, bool showOptionalDialog)
@ -385,10 +323,10 @@ bool ModrinthCreationTask::parseManifest(const QString& indexPath, std::vector<F
}
if (setInternalData) {
if (m_managed_version_id.isEmpty()) {
m_managed_version_id = obj["versionId"].toString();
if (m_managedVersionId.isEmpty()) {
m_managedVersionId = obj.value("versionId").toString();
}
m_managed_name = obj["name"].toString();
m_managedName = obj.value("name").toString();
}
auto jsonFiles = Json::requireIsArrayOf<QJsonObject>(obj, "files", "modrinth.index.json");
@ -470,15 +408,15 @@ bool ModrinthCreationTask::parseManifest(const QString& indexPath, std::vector<F
for (auto it = dependencies.begin(), end = dependencies.end(); it != end; ++it) {
QString name = it.key();
if (name == "minecraft") {
m_minecraft_version = Json::requireString(*it, "Minecraft version");
m_minecraftVersion = Json::requireString(*it, "Minecraft version");
} else if (name == "fabric-loader") {
m_fabric_version = Json::requireString(*it, "Fabric Loader version");
m_fabricVersion = Json::requireString(*it, "Fabric Loader version");
} else if (name == "quilt-loader") {
m_quilt_version = Json::requireString(*it, "Quilt Loader version");
m_quiltVersion = Json::requireString(*it, "Quilt Loader version");
} else if (name == "forge") {
m_forge_version = Json::requireString(*it, "Forge version");
m_forgeVersion = Json::requireString(*it, "Forge version");
} else if (name == "neoforge") {
m_neoForge_version = Json::requireString(*it, "NeoForge version");
m_neoForgeVersion = Json::requireString(*it, "NeoForge version");
} else {
throw JSONValidationError("Unknown dependency type: " + name);
}
@ -489,9 +427,91 @@ bool ModrinthCreationTask::parseManifest(const QString& indexPath, std::vector<F
}
} catch (const JSONValidationError& e) {
setError(tr("Could not understand pack index:\n") + e.cause());
emitFailed(tr("Could not understand pack index:\n") + e.cause());
return false;
}
return true;
}
void ModrinthCreationTask::ensureMetaLoop()
{
QDir const folder = FS::PathCombine(m_stagingPath, "minecraft", "jarmods");
auto ensureMetadataTask = makeShared<EnsureMetadataTask>(m_resources, folder, ModPlatform::ResourceProvider::MODRINTH);
connect(ensureMetadataTask.get(), &Task::succeeded, this, &ModrinthCreationTask::finishInstall);
connect(ensureMetadataTask.get(), &Task::failed, this, &ModrinthCreationTask::emitFailed);
connect(ensureMetadataTask.get(), &Task::aborted, this, &ModrinthCreationTask::emitAborted);
connect(ensureMetadataTask.get(), &Task::progress, [this](qint64 current, qint64 total) {
setDetails(tr("%1 out of %2 complete").arg(current).arg(total));
setProgress(current, total);
});
connect(ensureMetadataTask.get(), &Task::stepProgress, this, &ModrinthCreationTask::propagateStepProgress);
ensureMetadataTask->start();
m_task = ensureMetadataTask;
}
ModrinthCreationTask::~ModrinthCreationTask()
{
for (auto* resource : m_resources) {
delete resource;
}
m_resources.clear();
}
void ModrinthCreationTask::setManagedPack(BaseInstance* instance)
{
// Don't add managed info to packs without an ID (most likely imported from ZIP)
if (!m_managedId.isEmpty()) {
instance->setManagedPack("modrinth", m_managedId, m_managedName, m_managedVersionId, version());
} else {
instance->setManagedPack("modrinth", "", name(), "", "");
}
}
void ModrinthCreationTask::finishInstall()
{
// Update information of the already installed instance, if any.
if (m_oldInstance) {
setAbortable(false);
auto* inst = *m_oldInstance;
// Only change the name if it didn't use a custom name, so that the previous custom name
// is preserved, but if we're using the original one, we update the version string.
// NOTE: This needs to come before the setManagedPack call!
if (inst->name().contains(inst->getManagedPackVersionName()) && inst->name() != name()) {
if (askForChangingInstanceName(m_parent, inst->name(), name()) == InstanceNameChange::ShouldChange) {
inst->setName(name());
}
}
setManagedPack(*m_oldInstance);
}
if (shouldOverride()) {
bool deleteFailed = false;
setAbortable(false);
setStatus(tr("Removing old conflicting files..."));
qDebug() << "Removing old files";
for (const QString& path : m_filesToRemove) {
if (!QFile::exists(path)) {
continue;
}
qDebug() << "Removing" << path;
if (!QFile::remove(path)) {
qCritical() << "Could not remove" << path;
deleteFailed = true;
}
}
if (deleteFailed) {
emitFailed(tr("Failed to remove old conflicting files."));
return;
}
}
downloadFiles(m_newInstance.get());
}

View file

@ -10,9 +10,11 @@
#include <QVector>
#include "BaseInstance.h"
#include "InstanceCreationTask.h"
#include "InstanceTask.h"
class ModrinthCreationTask final : public InstanceCreationTask {
class Resource;
class ModrinthCreationTask final : public InstanceTask {
Q_OBJECT
struct File {
QString path;
@ -30,32 +32,42 @@ class ModrinthCreationTask final : public InstanceCreationTask {
QString id,
QString versionId = {},
QString originalInstanceId = {})
: m_parent(parent), m_managed_id(std::move(id)), m_managed_version_id(std::move(versionId))
: m_parent(parent), m_managedId(std::move(id)), m_managedVersionId(std::move(versionId))
{
setStagingPath(stagingPath);
setParentSettings(globalSettings);
m_original_instance_id = std::move(originalInstanceId);
m_originalInstanceId = std::move(originalInstanceId);
}
~ModrinthCreationTask() override;
bool abort() override;
bool updateInstance() override;
std::unique_ptr<MinecraftInstance> createInstance() override;
void createInstance();
void executeTask() override;
private slots:
void finishInstall();
private:
bool parseManifest(const QString&, std::vector<File>&, bool setInternalData = true, bool showOptionalDialog = true);
void ensureMetaLoop();
void setManagedPack(BaseInstance* instance);
private:
QWidget* m_parent = nullptr;
QString m_minecraft_version, m_fabric_version, m_quilt_version, m_forge_version, m_neoForge_version;
QString m_managed_id, m_managed_version_id, m_managed_name;
QString m_minecraftVersion, m_fabricVersion, m_quiltVersion, m_forgeVersion, m_neoForgeVersion;
QString m_managedId, m_managedVersionId, m_managedName;
std::vector<File> m_files;
Task::Ptr m_task;
std::optional<BaseInstance*> m_instance;
std::optional<BaseInstance*> m_oldInstance;
std::unique_ptr<MinecraftInstance> m_newInstance{};
QString m_root_path = "minecraft";
QString m_rootPath = "minecraft";
QHash<QString, Resource*> m_resources;
};

View file

@ -46,7 +46,6 @@
#include <tasks/Task.h>
#include "IconPickerDialog.h"
#include "ProgressDialog.h"
#include "VersionSelectDialog.h"
#include <QDialogButtonBox>
@ -69,20 +68,19 @@
NewInstanceDialog::NewInstanceDialog(const QString& initialGroup,
const QString& url,
const QMap<QString, QString>& extra_info,
const QMap<QString, QString>& extraInfo,
QWidget* parent)
: QDialog(parent), ui(new Ui::NewInstanceDialog)
: QDialog(parent), ui(new Ui::NewInstanceDialog), m_instIconKey("default")
{
ui->setupUi(this);
setWindowIcon(QIcon::fromTheme("new"));
InstIconKey = "default";
ui->iconButton->setIcon(APPLICATION->icons()->getIcon(InstIconKey));
ui->iconButton->setIcon(APPLICATION->icons()->getIcon(m_instIconKey));
QStringList groups = APPLICATION->instances()->getGroups();
groups.prepend("");
int index = groups.indexOf(initialGroup);
auto index = groups.indexOf(initialGroup);
if (index == -1) {
index = 1;
groups.insert(index, initialGroup);
@ -102,35 +100,35 @@ NewInstanceDialog::NewInstanceDialog(const QString& initialGroup,
ui->verticalLayout->insertWidget(2, m_container);
m_container->addButtons(m_buttons);
connect(m_container, &PageContainer::selectedPageChanged, this, [this](BasePage* previous, BasePage* selected) {
m_buttons->button(QDialogButtonBox::Ok)->setEnabled(creationTask && !instName().isEmpty());
connect(m_container, &PageContainer::selectedPageChanged, this, [this](BasePage* /*previous*/, BasePage* /*selected*/) {
m_buttons->button(QDialogButtonBox::Ok)->setEnabled(m_creationTask && !instName().isEmpty());
});
// Bonk Qt over its stupid head and make sure it understands which button is the default one...
// See: https://stackoverflow.com/questions/24556831/qbuttonbox-set-default-button
auto OkButton = m_buttons->button(QDialogButtonBox::Ok);
OkButton->setDefault(true);
OkButton->setAutoDefault(true);
OkButton->setText(tr("OK"));
connect(OkButton, &QPushButton::clicked, this, &NewInstanceDialog::accept);
auto* okButton = m_buttons->button(QDialogButtonBox::Ok);
okButton->setDefault(true);
okButton->setAutoDefault(true);
okButton->setText(tr("OK"));
connect(okButton, &QPushButton::clicked, this, &NewInstanceDialog::accept);
auto CancelButton = m_buttons->button(QDialogButtonBox::Cancel);
CancelButton->setDefault(false);
CancelButton->setAutoDefault(false);
CancelButton->setText(tr("Cancel"));
connect(CancelButton, &QPushButton::clicked, this, &NewInstanceDialog::reject);
auto* cancelButton = m_buttons->button(QDialogButtonBox::Cancel);
cancelButton->setDefault(false);
cancelButton->setAutoDefault(false);
cancelButton->setText(tr("Cancel"));
connect(cancelButton, &QPushButton::clicked, this, &NewInstanceDialog::reject);
auto HelpButton = m_buttons->button(QDialogButtonBox::Help);
HelpButton->setDefault(false);
HelpButton->setAutoDefault(false);
HelpButton->setText(tr("Help"));
connect(HelpButton, &QPushButton::clicked, m_container, &PageContainer::help);
auto* helpButton = m_buttons->button(QDialogButtonBox::Help);
helpButton->setDefault(false);
helpButton->setAutoDefault(false);
helpButton->setText(tr("Help"));
connect(helpButton, &QPushButton::clicked, m_container, &PageContainer::help);
if (!url.isEmpty()) {
QUrl actualUrl(url);
m_container->selectPage("import");
importPage->setUrl(url);
importPage->setExtraInfo(extra_info);
m_importPage->setUrl(url);
m_importPage->setExtraInfo(extraInfo);
}
updateDialogState();
@ -138,7 +136,7 @@ NewInstanceDialog::NewInstanceDialog(const QString& initialGroup,
if (APPLICATION->settings()->get("NewInstanceGeometry").isValid()) {
restoreGeometry(QByteArray::fromBase64(APPLICATION->settings()->get("NewInstanceGeometry").toString().toUtf8()));
} else {
auto screen = parent->screen();
auto* screen = parent->screen();
auto geometry = screen->availableSize();
resize(width(), qMin(geometry.height() - 50, 710));
}
@ -171,13 +169,14 @@ QList<BasePage*> NewInstanceDialog::getPages()
{
QList<BasePage*> pages;
importPage = new ImportPage(this);
m_importPage = new ImportPage(this);
pages.append(new CustomPage(this));
pages.append(importPage);
pages.append(m_importPage);
pages.append(new AtlPage(this));
if (APPLICATION->capabilities() & Application::SupportsFlame)
if (APPLICATION->capabilities() & Application::SupportsFlame) {
pages.append(new FlamePage(this));
}
pages.append(new FtbPage(this));
pages.append(new LegacyFTB::Page(this));
pages.append(new FTBImportAPP::ImportFTBPage(this));
@ -199,41 +198,41 @@ NewInstanceDialog::~NewInstanceDialog()
void NewInstanceDialog::setSuggestedPack(const QString& name, InstanceTask* task)
{
creationTask.reset(task);
m_creationTask.reset(task);
ui->instNameTextBox->setPlaceholderText(name);
importVersion.clear();
m_importVersion.clear();
if (!task) {
ui->iconButton->setIcon(APPLICATION->icons()->getIcon(InstIconKey));
importIcon = false;
ui->iconButton->setIcon(APPLICATION->icons()->getIcon(m_instIconKey));
m_importIcon = false;
}
auto allowOK = task && !instName().isEmpty();
auto allowOK = (task != nullptr) && !instName().isEmpty();
m_buttons->button(QDialogButtonBox::Ok)->setEnabled(allowOK);
}
void NewInstanceDialog::setSuggestedPack(const QString& name, QString version, InstanceTask* task)
{
creationTask.reset(task);
m_creationTask.reset(task);
ui->instNameTextBox->setPlaceholderText(name);
importVersion = std::move(version);
m_importVersion = std::move(version);
if (!task) {
ui->iconButton->setIcon(APPLICATION->icons()->getIcon(InstIconKey));
importIcon = false;
ui->iconButton->setIcon(APPLICATION->icons()->getIcon(m_instIconKey));
m_importIcon = false;
}
auto allowOK = task && !instName().isEmpty();
auto allowOK = (task != nullptr) && !instName().isEmpty();
m_buttons->button(QDialogButtonBox::Ok)->setEnabled(allowOK);
}
void NewInstanceDialog::setSuggestedIconFromFile(const QString& path, const QString& name)
{
importIcon = true;
importIconPath = path;
importIconName = name;
m_importIcon = true;
m_importIconPath = path;
m_importIconName = name;
// Hmm, for some reason they can be to small
ui->iconButton->setIcon(QIcon(path));
@ -241,22 +240,22 @@ void NewInstanceDialog::setSuggestedIconFromFile(const QString& path, const QStr
void NewInstanceDialog::setSuggestedIcon(const QString& key)
{
if (key == "default")
if (key == "default") {
return;
}
auto icon = APPLICATION->icons()->getIcon(key);
importIcon = false;
m_importIcon = false;
ui->iconButton->setIcon(icon);
}
InstanceTask* NewInstanceDialog::extractTask()
{
InstanceTask* extracted = creationTask.release();
InstanceTask* extracted = m_creationTask.release();
InstanceName inst_name(ui->instNameTextBox->placeholderText().trimmed(), importVersion);
inst_name.setName(ui->instNameTextBox->text().trimmed());
extracted->setName(inst_name);
extracted->setName(ui->instNameTextBox->text().trimmed());
extracted->setOriginalName(ui->instNameTextBox->placeholderText().trimmed(), m_importVersion);
extracted->setGroup(instGroup());
extracted->setIcon(iconKey());
@ -265,21 +264,21 @@ InstanceTask* NewInstanceDialog::extractTask()
void NewInstanceDialog::updateDialogState()
{
auto allowOK = creationTask && !instName().isEmpty();
auto OkButton = m_buttons->button(QDialogButtonBox::Ok);
if (OkButton->isEnabled() != allowOK) {
OkButton->setEnabled(allowOK);
auto allowOK = m_creationTask && !instName().isEmpty();
auto* okButton = m_buttons->button(QDialogButtonBox::Ok);
if (okButton->isEnabled() != allowOK) {
okButton->setEnabled(allowOK);
}
}
QString NewInstanceDialog::instName() const
{
auto result = ui->instNameTextBox->text().trimmed();
if (result.size()) {
if (result.size() != 0) {
return result;
}
result = ui->instNameTextBox->placeholderText().trimmed();
if (result.size()) {
if (result.size() != 0) {
return result;
}
return QString();
@ -291,19 +290,19 @@ QString NewInstanceDialog::instGroup() const
}
QString NewInstanceDialog::iconKey() const
{
return InstIconKey;
return m_instIconKey;
}
void NewInstanceDialog::on_iconButton_clicked()
{
importIconNow(); // so the user can switch back
IconPickerDialog dlg(this);
dlg.execWithSelection(InstIconKey);
dlg.execWithSelection(m_instIconKey);
if (dlg.result() == QDialog::Accepted) {
InstIconKey = dlg.selectedIconKey;
ui->iconButton->setIcon(APPLICATION->icons()->getIcon(InstIconKey));
importIcon = false;
m_instIconKey = dlg.selectedIconKey;
ui->iconButton->setIcon(APPLICATION->icons()->getIcon(m_instIconKey));
m_importIcon = false;
}
}
@ -314,22 +313,22 @@ void NewInstanceDialog::on_instNameTextBox_textChanged([[maybe_unused]] const QS
void NewInstanceDialog::importIconNow()
{
if (importIcon) {
APPLICATION->icons()->installIcon(importIconPath, importIconName);
InstIconKey = importIconName.mid(0, importIconName.lastIndexOf('.'));
importIcon = false;
if (m_importIcon) {
APPLICATION->icons()->installIcon(m_importIconPath, m_importIconName);
m_instIconKey = m_importIconName.mid(0, m_importIconName.lastIndexOf('.'));
m_importIcon = false;
}
APPLICATION->settings()->set("NewInstanceGeometry", QString::fromUtf8(saveGeometry().toBase64()));
}
void NewInstanceDialog::selectedPageChanged(BasePage* previous, BasePage* selected)
{
auto prevPage = dynamic_cast<ModpackProviderBasePage*>(previous);
auto* prevPage = dynamic_cast<ModpackProviderBasePage*>(previous);
if (prevPage) {
m_searchTerm = prevPage->getSerachTerm();
}
auto nextPage = dynamic_cast<ModpackProviderBasePage*>(selected);
auto* nextPage = dynamic_cast<ModpackProviderBasePage*>(selected);
if (nextPage) {
nextPage->setSearchTerm(m_searchTerm);
}

View file

@ -55,9 +55,9 @@ class NewInstanceDialog : public QDialog, public BasePageProvider {
public:
explicit NewInstanceDialog(const QString& initialGroup,
const QString& url = QString(),
const QMap<QString, QString>& extra_info = {},
QWidget* parent = 0);
~NewInstanceDialog();
const QMap<QString, QString>& extraInfo = {},
QWidget* parent = nullptr);
~NewInstanceDialog() override;
void updateDialogState();
@ -84,22 +84,23 @@ class NewInstanceDialog : public QDialog, public BasePageProvider {
void on_instNameTextBox_textChanged(const QString& arg1);
void selectedPageChanged(BasePage* previous, BasePage* selected);
private:
void importIconNow();
private:
Ui::NewInstanceDialog* ui = nullptr;
PageContainer* m_container = nullptr;
QDialogButtonBox* m_buttons = nullptr;
QString InstIconKey;
ImportPage* importPage = nullptr;
std::unique_ptr<InstanceTask> creationTask;
QString m_instIconKey;
ImportPage* m_importPage = nullptr;
std::unique_ptr<InstanceTask> m_creationTask;
bool importIcon = false;
QString importIconPath;
QString importIconName;
bool m_importIcon = false;
QString m_importIconPath;
QString m_importIconName;
QString importVersion;
QString m_importVersion;
QString m_searchTerm;
void importIconNow();
};

View file

@ -17,11 +17,9 @@
#include <memory>
#include "Application.h"
#include "BuildConfig.h"
#include "InstanceImportTask.h"
#include "InstanceList.h"
#include "InstanceTask.h"
#include "Json.h"
#include "Markdown.h"
#include "StringUtils.h"
@ -29,11 +27,10 @@
#include "ui/dialogs/CustomMessageBox.h"
#include "ui/dialogs/ProgressDialog.h"
#include "net/ApiDownload.h"
/** This is just to override the combo box popup behavior so that the combo box doesn't take the whole screen.
* ... thanks Qt.
*/
namespace {
class NoBigComboBoxStyle : public QProxyStyle {
Q_OBJECT
@ -41,8 +38,9 @@ class NoBigComboBoxStyle : public QProxyStyle {
// clang-format off
int styleHint(QStyle::StyleHint hint, const QStyleOption* option = nullptr, const QWidget* widget = nullptr, QStyleHintReturn* returnData = nullptr) const override
{
if (hint == QStyle::SH_ComboBox_Popup)
return false;
if (hint == QStyle::SH_ComboBox_Popup) {
return 0;
}
return QProxyStyle::styleHint(hint, option, widget, returnData);
}
@ -58,39 +56,42 @@ class NoBigComboBoxStyle : public QProxyStyle {
public:
static NoBigComboBoxStyle* getInstance(QStyle* style)
{
static QHash<QStyle*, NoBigComboBoxStyle*> s_singleton_instances_ = {};
static std::mutex s_singleton_instances_mutex_;
static QHash<QStyle*, NoBigComboBoxStyle*> s_singleton_instances = {};
static std::mutex s_singleton_instances_mutex;
std::lock_guard<std::mutex> lock(s_singleton_instances_mutex_);
auto inst_iter = s_singleton_instances_.constFind(style);
std::scoped_lock const lock(s_singleton_instances_mutex);
auto instIter = s_singleton_instances.constFind(style);
NoBigComboBoxStyle* inst = nullptr;
if (inst_iter == s_singleton_instances_.constEnd() || *inst_iter == nullptr) {
if (instIter == s_singleton_instances.constEnd() || *instIter == nullptr) {
inst = new NoBigComboBoxStyle(style);
inst->setParent(APPLICATION);
s_singleton_instances_.insert(style, inst);
s_singleton_instances.insert(style, inst);
qDebug() << "QProxyStyle NoBigComboBox created for" << style->objectName() << style;
} else {
inst = *inst_iter;
inst = *instIter;
}
return inst;
}
private:
NoBigComboBoxStyle(QStyle* style) : QProxyStyle(style) {}
explicit NoBigComboBoxStyle(QStyle* style) : QProxyStyle(style) {}
};
} // namespace
ManagedPackPage* ManagedPackPage::createPage(BaseInstance* inst, QString type, QWidget* parent)
ManagedPackPage* ManagedPackPage::createPage(BaseInstance* inst, const QString& type, QWidget* parent)
{
if (type == "modrinth")
if (type == "modrinth") {
return new ModrinthManagedPackPage(inst, nullptr, parent);
if (type == "flame" && (APPLICATION->capabilities() & Application::SupportsFlame))
}
if (type == "flame" && ((APPLICATION->capabilities() & Application::SupportsFlame) != 0U)) {
return new FlameManagedPackPage(inst, nullptr, parent);
}
return new GenericManagedPackPage(inst, nullptr, parent);
}
ManagedPackPage::ManagedPackPage(BaseInstance* inst, InstanceWindow* instance_window, QWidget* parent)
: QWidget(parent), m_instance_window(instance_window), ui(new Ui::ManagedPackPage), m_inst(inst)
ManagedPackPage::ManagedPackPage(BaseInstance* inst, InstanceWindow* instanceWindow, QWidget* parent)
: QWidget(parent), m_instanceWindow(instanceWindow), ui(new Ui::ManagedPackPage), m_inst(inst)
{
Q_ASSERT(inst);
@ -99,7 +100,7 @@ ManagedPackPage::ManagedPackPage(BaseInstance* inst, InstanceWindow* instance_wi
// NOTE: GTK2 themes crash with the proxy style.
// This seems like an upstream bug, so there's not much else that can be done.
if (!QStyleFactory::keys().contains("gtk2")) {
auto comboStyle = NoBigComboBoxStyle::getInstance(ui->versionsComboBox->style());
auto* comboStyle = NoBigComboBoxStyle::getInstance(ui->versionsComboBox->style());
ui->versionsComboBox->setStyle(comboStyle);
}
@ -115,20 +116,22 @@ ManagedPackPage::ManagedPackPage(BaseInstance* inst, InstanceWindow* instance_wi
openedImpl();
});
connect(ui->changelogTextBrowser, &QTextBrowser::anchorClicked, this, [](const QUrl url) {
connect(ui->changelogTextBrowser, &QTextBrowser::anchorClicked, this, [](const QUrl& url) {
if (url.scheme().isEmpty()) {
auto querry =
QUrlQuery(url.query()).queryItemValue("remoteUrl", QUrl::FullyDecoded); // curseforge workaround for linkout?remoteUrl=
auto decoded = QUrl::fromPercentEncoding(querry.toUtf8());
auto newUrl = QUrl(decoded);
if (newUrl.isValid() && (newUrl.scheme() == "http" || newUrl.scheme() == "https"))
if (newUrl.isValid() && (newUrl.scheme() == "http" || newUrl.scheme() == "https")) {
QDesktopServices ::openUrl(newUrl);
}
return;
}
QDesktopServices::openUrl(url);
});
connect(ui->urlLine, &QLineEdit::textChanged, this, [this](QString text) { m_inst->settings()->set("ManagedPackURL", text.trimmed()); });
connect(ui->urlLine, &QLineEdit::textChanged, this,
[this](const QString& text) { m_inst->settings()->set("ManagedPackURL", text.trimmed()); });
}
ManagedPackPage::~ManagedPackPage()
@ -169,10 +172,12 @@ void ManagedPackPage::openedImpl()
QString ManagedPackPage::displayName() const
{
auto type = m_inst->getManagedPackType();
if (type.isEmpty())
if (type.isEmpty()) {
return {};
if (type == "flame")
}
if (type == "flame") {
type = "CurseForge";
}
return type.replace(0, 1, type[0].toUpper());
}
@ -200,26 +205,26 @@ bool ManagedPackPage::runUpdateTask(InstanceTask* task)
{
Q_ASSERT(task);
unique_qobject_ptr<Task> wrapped_task(APPLICATION->instances()->wrapInstanceTask(task));
unique_qobject_ptr<Task> const wrappedTask(APPLICATION->instances()->wrapInstanceTask(task));
connect(wrapped_task.get(), &Task::failed,
connect(wrappedTask.get(), &Task::failed,
[this](const QString& reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); });
connect(wrapped_task.get(), &Task::succeeded, [this, task]() {
connect(wrappedTask.get(), &Task::succeeded, [this, task]() {
QStringList warnings = task->warnings();
if (warnings.count()) {
CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show();
}
});
connect(wrapped_task.get(), &Task::aborted, [this] {
connect(wrappedTask.get(), &Task::aborted, [this] {
CustomMessageBox::selectable(this, tr("Task aborted"), tr("The task has been aborted by the user."), QMessageBox::Information)
->show();
});
ProgressDialog loadDialog(this);
loadDialog.setSkipButton(true, tr("Abort"));
loadDialog.execWithTask(wrapped_task.get());
loadDialog.execWithTask(wrappedTask.get());
return wrapped_task->wasSuccessful();
return wrappedTask->wasSuccessful();
}
void ManagedPackPage::suggestVersion()
@ -246,8 +251,8 @@ void ManagedPackPage::setFailState()
ui->reloadButton->setVisible(true);
}
ModrinthManagedPackPage::ModrinthManagedPackPage(BaseInstance* inst, InstanceWindow* instance_window, QWidget* parent)
: ManagedPackPage(inst, instance_window, parent)
ModrinthManagedPackPage::ModrinthManagedPackPage(BaseInstance* inst, InstanceWindow* instanceWindow, QWidget* parent)
: ManagedPackPage(inst, instanceWindow, parent)
{
Q_ASSERT(inst->isManagedPack());
connect(ui->versionsComboBox, &QComboBox::currentIndexChanged, this, &ModrinthManagedPackPage::suggestVersion);
@ -265,8 +270,8 @@ void ModrinthManagedPackPage::parseManagedPack()
return;
}
if (m_fetch_job && m_fetch_job->isRunning()) {
m_fetch_job->abort();
if (m_fetchJob && m_fetchJob->isRunning()) {
m_fetchJob->abort();
}
ResourceAPI::Callback<QVector<ModPlatform::IndexedVersion>> callbacks{};
@ -300,16 +305,16 @@ void ModrinthManagedPackPage::parseManagedPack()
};
callbacks.on_fail = [this](const QString& /*reason*/, int) { setFailState(); };
callbacks.on_abort = [this]() { setFailState(); };
m_fetch_job = m_api.getProjectVersions({ .pack = std::make_shared<ModPlatform::IndexedPack>(m_pack),
.mcVersions = {},
.loaders = {},
.resourceType = ModPlatform::ResourceType::Modpack,
.includeChangelog = true },
std::move(callbacks));
m_fetchJob = m_api.getProjectVersions({ .pack = std::make_shared<ModPlatform::IndexedPack>(m_pack),
.mcVersions = {},
.loaders = {},
.resourceType = ModPlatform::ResourceType::Modpack,
.includeChangelog = true },
std::move(callbacks));
ui->changelogTextBrowser->setText(tr("Fetching changelogs..."));
m_fetch_job->start();
m_fetchJob->start();
}
QString ModrinthManagedPackPage::url() const
@ -334,12 +339,13 @@ void ModrinthManagedPackPage::suggestVersion()
/// @brief Called when the update task has completed.
/// Internally handles the closing of the instance window if the update was successful and shows a message box.
/// @param did_succeed Whether the update task was successful.
void ManagedPackPage::onUpdateTaskCompleted(bool did_succeed) const
void ManagedPackPage::onUpdateTaskCompleted(bool didSucceed) const
{
// Close the window if the update was successful
if (did_succeed) {
if (m_instance_window != nullptr)
m_instance_window->close();
if (didSucceed) {
if (m_instanceWindow != nullptr) {
m_instanceWindow->close();
}
CustomMessageBox::selectable(nullptr, tr("Update Successful"),
tr("The instance updated to pack version %1 successfully.").arg(m_inst->getManagedPackVersionName()),
@ -375,15 +381,16 @@ void ModrinthManagedPackPage::update()
void ModrinthManagedPackPage::updateFromFile()
{
auto output = QFileDialog::getOpenFileUrl(this, tr("Choose update file"), QDir::homePath(), tr("Modrinth pack") + " (*.mrpack *.zip)");
if (output.isEmpty())
if (output.isEmpty()) {
return;
}
updatePack(output);
}
// FLAME
FlameManagedPackPage::FlameManagedPackPage(BaseInstance* inst, InstanceWindow* instance_window, QWidget* parent)
: ManagedPackPage(inst, instance_window, parent)
FlameManagedPackPage::FlameManagedPackPage(BaseInstance* inst, InstanceWindow* instanceWindow, QWidget* parent)
: ManagedPackPage(inst, instanceWindow, parent)
{
Q_ASSERT(inst->isManagedPack());
connect(ui->versionsComboBox, &QComboBox::currentIndexChanged, this, &FlameManagedPackPage::suggestVersion);
@ -418,8 +425,8 @@ void FlameManagedPackPage::parseManagedPack()
return;
}
if (m_fetch_job && m_fetch_job->isRunning()) {
m_fetch_job->abort();
if (m_fetchJob && m_fetchJob->isRunning()) {
m_fetchJob->abort();
}
QString id = m_inst->getManagedPackID();
@ -453,14 +460,14 @@ void FlameManagedPackPage::parseManagedPack()
};
callbacks.on_fail = [this](const QString& /*reason*/, int) { setFailState(); };
callbacks.on_abort = [this]() { setFailState(); };
m_fetch_job = m_api.getProjectVersions({ .pack = std::make_shared<ModPlatform::IndexedPack>(m_pack),
.mcVersions = {},
.loaders = {},
.resourceType = ModPlatform::ResourceType::Modpack,
.includeChangelog = true },
std::move(callbacks));
m_fetchJob = m_api.getProjectVersions({ .pack = std::make_shared<ModPlatform::IndexedPack>(m_pack),
.mcVersions = {},
.loaders = {},
.resourceType = ModPlatform::ResourceType::Modpack,
.includeChangelog = true },
std::move(callbacks));
m_fetch_job->start();
m_fetchJob->start();
}
QString FlameManagedPackPage::url() const
@ -504,36 +511,36 @@ void FlameManagedPackPage::update()
void FlameManagedPackPage::updateFromFile()
{
auto output = QFileDialog::getOpenFileUrl(this, tr("Choose update file"), QDir::homePath(), tr("CurseForge pack") + " (*.zip)");
if (output.isEmpty())
if (output.isEmpty()) {
return;
}
updatePack(output);
}
void ManagedPackPage::updatePack(const QUrl& url, QString versionID, QString versionName)
void ManagedPackPage::updatePack(const QUrl& url, const QString& versionID, const QString& versionName)
{
QMap<QString, QString> extra_info;
QMap<QString, QString> extraInfo;
// NOTE: Don't use 'm_pack.id' here, since we didn't completely parse all the metadata for the pack, including this field.
extra_info.insert("pack_id", m_inst->getManagedPackID());
extra_info.insert("pack_version_id", versionID);
extra_info.insert("original_instance_id", m_inst->id());
extraInfo.insert("pack_id", m_inst->getManagedPackID());
extraInfo.insert("pack_version_id", versionID);
extraInfo.insert("original_instance_id", m_inst->id());
auto extracted = new InstanceImportTask(url, this, std::move(extra_info));
auto* extracted = new InstanceImportTask(url, this, std::move(extraInfo));
if (versionName.isEmpty()) {
extracted->setName(m_inst->name());
} else {
InstanceName inst_name(m_inst->getManagedPackName(), versionName);
inst_name.setName(m_inst->name().replace(m_inst->getManagedPackVersionName(), versionName));
extracted->setName(inst_name);
extracted->setOriginalName(m_inst->getManagedPackName(), versionName);
extracted->setName(m_inst->name().replace(m_inst->getManagedPackVersionName(), versionName));
}
extracted->setGroup(APPLICATION->instances()->getInstanceGroup(m_inst->id()));
extracted->setIcon(m_inst->iconKey());
extracted->setConfirmUpdate(false);
// Run our task then handle the result
auto did_succeed = runUpdateTask(extracted);
onUpdateTaskCompleted(did_succeed);
auto didSucceed = runUpdateTask(extracted);
onUpdateTaskCompleted(didSucceed);
}
#include "ManagedPackPage.moc"

View file

@ -11,8 +11,6 @@
#include "modplatform/flame/FlameAPI.h"
#include "net/NetJob.h"
#include "ui/pages/BasePage.h"
#include <QWidget>
@ -28,12 +26,12 @@ class ManagedPackPage : public QWidget, public BasePage {
Q_OBJECT
public:
inline static ManagedPackPage* createPage(BaseInstance* inst, QWidget* parent = nullptr)
static ManagedPackPage* createPage(BaseInstance* inst, QWidget* parent = nullptr)
{
return ManagedPackPage::createPage(inst, inst->getManagedPackType(), parent);
}
static ManagedPackPage* createPage(BaseInstance* inst, QString type, QWidget* parent = nullptr);
static ManagedPackPage* createPage(BaseInstance* inst, const QString& type, QWidget* parent = nullptr);
~ManagedPackPage() override;
QString displayName() const override;
@ -56,7 +54,7 @@ class ManagedPackPage : public QWidget, public BasePage {
*/
virtual QString url() const { return {}; };
void setInstanceWindow(InstanceWindow* window) { m_instance_window = window; }
void setInstanceWindow(InstanceWindow* window) { m_instanceWindow = window; }
public slots:
/** Gets the current version selection and update the UI, including the update button and the changelog.
@ -77,7 +75,7 @@ class ManagedPackPage : public QWidget, public BasePage {
void setFailState();
protected:
ManagedPackPage(BaseInstance* inst, InstanceWindow* instance_window, QWidget* parent = nullptr);
ManagedPackPage(BaseInstance* inst, InstanceWindow* instanceWindow, QWidget* parent = nullptr);
/** Run the InstanceTask, with a progress dialog and all.
* Similar to MainWindow::instanceFromInstanceTask
@ -86,17 +84,17 @@ class ManagedPackPage : public QWidget, public BasePage {
*/
bool runUpdateTask(InstanceTask*);
void updatePack(const QUrl& url, QString versionID = {}, QString versionName = {});
void updatePack(const QUrl& url, const QString& versionID = {}, const QString& versionName = {});
void onUpdateTaskCompleted(bool didSucceed) const;
protected:
InstanceWindow* m_instance_window = nullptr;
InstanceWindow* m_instanceWindow = nullptr;
Ui::ManagedPackPage* ui;
BaseInstance* m_inst;
bool m_loaded = false;
void onUpdateTaskCompleted(bool did_succeed) const;
};
/** Simple page for when we aren't a managed pack. */
@ -104,8 +102,8 @@ class GenericManagedPackPage final : public ManagedPackPage {
Q_OBJECT
public:
GenericManagedPackPage(BaseInstance* inst, InstanceWindow* instance_window, QWidget* parent = nullptr)
: ManagedPackPage(inst, instance_window, parent)
GenericManagedPackPage(BaseInstance* inst, InstanceWindow* instanceWindow, QWidget* parent = nullptr)
: ManagedPackPage(inst, instanceWindow, parent)
{}
~GenericManagedPackPage() override = default;
@ -117,7 +115,7 @@ class ModrinthManagedPackPage final : public ManagedPackPage {
Q_OBJECT
public:
ModrinthManagedPackPage(BaseInstance* inst, InstanceWindow* instance_window, QWidget* parent = nullptr);
ModrinthManagedPackPage(BaseInstance* inst, InstanceWindow* instanceWindow, QWidget* parent = nullptr);
~ModrinthManagedPackPage() override = default;
void parseManagedPack() override;
@ -131,7 +129,7 @@ class ModrinthManagedPackPage final : public ManagedPackPage {
void updateFromFile() override;
private:
Task::Ptr m_fetch_job = nullptr;
Task::Ptr m_fetchJob = nullptr;
ModPlatform::IndexedPack m_pack;
ModrinthAPI m_api;
@ -141,7 +139,7 @@ class FlameManagedPackPage final : public ManagedPackPage {
Q_OBJECT
public:
FlameManagedPackPage(BaseInstance* inst, InstanceWindow* instance_window, QWidget* parent = nullptr);
FlameManagedPackPage(BaseInstance* inst, InstanceWindow* instanceWindow, QWidget* parent = nullptr);
~FlameManagedPackPage() override = default;
void parseManagedPack() override;
@ -155,7 +153,7 @@ class FlameManagedPackPage final : public ManagedPackPage {
void updateFromFile() override;
private:
Task::Ptr m_fetch_job = nullptr;
Task::Ptr m_fetchJob = nullptr;
ModPlatform::IndexedPack m_pack;
FlameAPI m_api;