feat: add early modrinth pack updating
Still some FIXMEs and TODOs to consider, but the general thing is here! Signed-off-by: flow <flowlnlnln@gmail.com>
This commit is contained in:
parent
4441b37338
commit
208ed73e59
@ -593,15 +593,16 @@ void InstanceImportTask::processModrinth()
|
|||||||
inst_creation_task->setIcon(m_instIcon);
|
inst_creation_task->setIcon(m_instIcon);
|
||||||
inst_creation_task->setGroup(m_instGroup);
|
inst_creation_task->setGroup(m_instGroup);
|
||||||
|
|
||||||
connect(inst_creation_task, &Task::succeeded, this, &InstanceImportTask::emitSucceeded);
|
connect(inst_creation_task, &Task::succeeded, this, [this, inst_creation_task] {
|
||||||
|
setOverride(inst_creation_task->shouldOverride());
|
||||||
|
emitSucceeded();
|
||||||
|
});
|
||||||
connect(inst_creation_task, &Task::failed, this, &InstanceImportTask::emitFailed);
|
connect(inst_creation_task, &Task::failed, this, &InstanceImportTask::emitFailed);
|
||||||
connect(inst_creation_task, &Task::progress, this, &InstanceImportTask::setProgress);
|
connect(inst_creation_task, &Task::progress, this, &InstanceImportTask::setProgress);
|
||||||
connect(inst_creation_task, &Task::status, this, &InstanceImportTask::setStatus);
|
connect(inst_creation_task, &Task::status, this, &InstanceImportTask::setStatus);
|
||||||
connect(inst_creation_task, &Task::finished, this, [inst_creation_task]{ inst_creation_task->deleteLater(); });
|
connect(inst_creation_task, &Task::finished, inst_creation_task, &InstanceCreationTask::deleteLater);
|
||||||
|
|
||||||
connect(this, &Task::aborted, inst_creation_task, [inst_creation_task] {
|
connect(this, &Task::aborted, inst_creation_task, &InstanceCreationTask::abort);
|
||||||
inst_creation_task->abort();
|
|
||||||
});
|
|
||||||
|
|
||||||
inst_creation_task->start();
|
inst_creation_task->start();
|
||||||
}
|
}
|
||||||
|
@ -535,7 +535,20 @@ InstancePtr InstanceList::getInstanceById(QString instId) const
|
|||||||
return InstancePtr();
|
return InstancePtr();
|
||||||
}
|
}
|
||||||
|
|
||||||
QModelIndex InstanceList::getInstanceIndexById(const QString& id) const
|
InstancePtr InstanceList::getInstanceByManagedName(QString managed_name) const
|
||||||
|
{
|
||||||
|
if (managed_name.isEmpty())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
for (auto instance : m_instances) {
|
||||||
|
if (instance->getManagedPackName() == managed_name)
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
QModelIndex InstanceList::getInstanceIndexById(const QString &id) const
|
||||||
{
|
{
|
||||||
return index(getInstIndex(getInstanceById(id).get()));
|
return index(getInstIndex(getInstanceById(id).get()));
|
||||||
}
|
}
|
||||||
@ -764,9 +777,8 @@ class InstanceStaging : public Task {
|
|||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
const unsigned minBackoff = 1;
|
const unsigned minBackoff = 1;
|
||||||
const unsigned maxBackoff = 16;
|
const unsigned maxBackoff = 16;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
InstanceStaging(InstanceList* parent, Task* child, const QString& stagingPath, const QString& instanceName, const QString& groupName)
|
InstanceStaging(InstanceList* parent, InstanceTask* child, const QString& stagingPath, const QString& instanceName, const QString& groupName)
|
||||||
: backoff(minBackoff, maxBackoff)
|
: backoff(minBackoff, maxBackoff)
|
||||||
{
|
{
|
||||||
m_parent = parent;
|
m_parent = parent;
|
||||||
@ -808,7 +820,8 @@ class InstanceStaging : public Task {
|
|||||||
void childSucceded()
|
void childSucceded()
|
||||||
{
|
{
|
||||||
unsigned sleepTime = backoff();
|
unsigned sleepTime = backoff();
|
||||||
if (m_parent->commitStagedInstance(m_stagingPath, m_instanceName, m_groupName)) {
|
if (m_parent->commitStagedInstance(m_stagingPath, m_instanceName, m_groupName, m_child->shouldOverride()))
|
||||||
|
{
|
||||||
emitSucceeded();
|
emitSucceeded();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -834,8 +847,8 @@ class InstanceStaging : public Task {
|
|||||||
*/
|
*/
|
||||||
ExponentialSeries backoff;
|
ExponentialSeries backoff;
|
||||||
QString m_stagingPath;
|
QString m_stagingPath;
|
||||||
InstanceList* m_parent;
|
InstanceList * m_parent;
|
||||||
unique_qobject_ptr<Task> m_child;
|
unique_qobject_ptr<InstanceTask> m_child;
|
||||||
QString m_instanceName;
|
QString m_instanceName;
|
||||||
QString m_groupName;
|
QString m_groupName;
|
||||||
QTimer m_backoffTimer;
|
QTimer m_backoffTimer;
|
||||||
@ -866,23 +879,52 @@ QString InstanceList::getStagedInstancePath()
|
|||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool InstanceList::commitStagedInstance(const QString& path, const QString& instanceName, const QString& groupName)
|
bool InstanceList::commitStagedInstance(const QString& path, const QString& instanceName, const QString& groupName, bool should_override)
|
||||||
{
|
{
|
||||||
QDir dir;
|
QDir dir;
|
||||||
QString instID = FS::DirNameFromString(instanceName, m_instDir);
|
QString instID;
|
||||||
|
InstancePtr inst;
|
||||||
|
|
||||||
|
QString raw_inst_name = instanceName.section(' ', 0, -2);
|
||||||
|
if (should_override) {
|
||||||
|
// This is to avoid problems when the instance folder gets manually renamed
|
||||||
|
if ((inst = getInstanceByManagedName(raw_inst_name))) {
|
||||||
|
instID = QFileInfo(inst->instanceRoot()).fileName();
|
||||||
|
} else {
|
||||||
|
instID = FS::RemoveInvalidFilenameChars(raw_inst_name, '-');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
instID = FS::DirNameFromString(raw_inst_name, m_instDir);
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
WatchLock lock(m_watcher, m_instDir);
|
WatchLock lock(m_watcher, m_instDir);
|
||||||
QString destination = FS::PathCombine(m_instDir, instID);
|
QString destination = FS::PathCombine(m_instDir, instID);
|
||||||
if (!dir.rename(path, destination)) {
|
|
||||||
qWarning() << "Failed to move" << path << "to" << destination;
|
if (should_override) {
|
||||||
return false;
|
if (!FS::overrideFolder(destination, path)) {
|
||||||
|
qWarning() << "Failed to override" << path << "to" << destination;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!inst)
|
||||||
|
inst = getInstanceById(instID);
|
||||||
|
if (inst)
|
||||||
|
inst->setName(instanceName);
|
||||||
|
} else {
|
||||||
|
if (!dir.rename(path, destination)) {
|
||||||
|
qWarning() << "Failed to move" << path << "to" << destination;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
m_instanceGroupIndex[instID] = groupName;
|
m_instanceGroupIndex[instID] = groupName;
|
||||||
instanceSet.insert(instID);
|
|
||||||
m_groupNameCache.insert(groupName);
|
m_groupNameCache.insert(groupName);
|
||||||
|
instanceSet.insert(instID);
|
||||||
|
|
||||||
emit instancesChanged();
|
emit instancesChanged();
|
||||||
emit instanceSelectRequest(instID);
|
emit instanceSelectRequest(instID);
|
||||||
}
|
}
|
||||||
|
|
||||||
saveGroupList();
|
saveGroupList();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -101,7 +101,10 @@ public:
|
|||||||
InstListError loadList();
|
InstListError loadList();
|
||||||
void saveNow();
|
void saveNow();
|
||||||
|
|
||||||
|
/* O(n) */
|
||||||
InstancePtr getInstanceById(QString id) const;
|
InstancePtr getInstanceById(QString id) const;
|
||||||
|
/* O(n) */
|
||||||
|
InstancePtr getInstanceByManagedName(QString managed_name) const;
|
||||||
QModelIndex getInstanceIndexById(const QString &id) const;
|
QModelIndex getInstanceIndexById(const QString &id) const;
|
||||||
QStringList getGroups();
|
QStringList getGroups();
|
||||||
bool isGroupCollapsed(const QString &groupName);
|
bool isGroupCollapsed(const QString &groupName);
|
||||||
@ -127,8 +130,10 @@ public:
|
|||||||
/**
|
/**
|
||||||
* Commit the staging area given by @keyPath to the provider - used when creation succeeds.
|
* Commit the staging area given by @keyPath to the provider - used when creation succeeds.
|
||||||
* Used by instance manipulation tasks.
|
* Used by instance manipulation tasks.
|
||||||
|
* 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 QString& instanceName, const QString & groupName);
|
bool commitStagedInstance(const QString & keyPath, const QString& instanceName, const QString & groupName, bool should_override);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Destroy a previously created staging area given by @keyPath - used when creation fails.
|
* Destroy a previously created staging area given by @keyPath - used when creation fails.
|
||||||
|
@ -43,10 +43,17 @@ public:
|
|||||||
return m_instGroup;
|
return m_instGroup;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool shouldOverride() const { return m_override_existing; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void setOverride(bool override) { m_override_existing = override; }
|
||||||
|
|
||||||
protected: /* data */
|
protected: /* data */
|
||||||
SettingsObjectPtr m_globalSettings;
|
SettingsObjectPtr m_globalSettings;
|
||||||
QString m_instName;
|
QString m_instName;
|
||||||
QString m_instIcon;
|
QString m_instIcon;
|
||||||
QString m_instGroup;
|
QString m_instGroup;
|
||||||
QString m_stagingPath;
|
QString m_stagingPath;
|
||||||
|
|
||||||
|
bool m_override_existing = false;
|
||||||
};
|
};
|
||||||
|
@ -2,25 +2,128 @@
|
|||||||
|
|
||||||
#include "Application.h"
|
#include "Application.h"
|
||||||
#include "FileSystem.h"
|
#include "FileSystem.h"
|
||||||
|
#include "InstanceList.h"
|
||||||
#include "Json.h"
|
#include "Json.h"
|
||||||
|
|
||||||
#include "minecraft/MinecraftInstance.h"
|
#include "minecraft/MinecraftInstance.h"
|
||||||
#include "minecraft/PackProfile.h"
|
#include "minecraft/PackProfile.h"
|
||||||
|
|
||||||
#include "net/NetJob.h"
|
#include "modplatform/ModIndex.h"
|
||||||
|
|
||||||
#include "net/ChecksumValidator.h"
|
#include "net/ChecksumValidator.h"
|
||||||
|
|
||||||
#include "settings/INISettingsObject.h"
|
#include "settings/INISettingsObject.h"
|
||||||
|
|
||||||
#include "ui/dialogs/CustomMessageBox.h"
|
#include "ui/dialogs/CustomMessageBox.h"
|
||||||
|
|
||||||
|
#include <QAbstractButton>
|
||||||
|
|
||||||
|
bool ModrinthCreationTask::abort()
|
||||||
|
{
|
||||||
|
if (m_files_job)
|
||||||
|
return m_files_job->abort();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ModrinthCreationTask::updateInstance()
|
||||||
|
{
|
||||||
|
auto instance_list = APPLICATION->instances();
|
||||||
|
|
||||||
|
// FIXME: How to handle situations when there's more than one install already for a given modpack?
|
||||||
|
// Based on the way we create the instance name (name + " " + version). Is there a better way?
|
||||||
|
auto inst = instance_list->getInstanceByManagedName(m_instName.section(' ', 0, -2));
|
||||||
|
|
||||||
|
if (!inst) {
|
||||||
|
inst = instance_list->getInstanceById(m_instName);
|
||||||
|
|
||||||
|
if (!inst)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString index_path = FS::PathCombine(m_stagingPath, "modrinth.index.json");
|
||||||
|
if (!parseManifest(index_path, m_files))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
auto version_id = inst->getManagedPackVersionID();
|
||||||
|
auto version_str = !version_id.isEmpty() ? tr(" (version %1)").arg(version_id) : "";
|
||||||
|
|
||||||
|
auto info = CustomMessageBox::selectable(m_parent, tr("Similar modpack was found!"),
|
||||||
|
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?")
|
||||||
|
.arg(version_str),
|
||||||
|
QMessageBox::Information, QMessageBox::Ok | QMessageBox::Abort);
|
||||||
|
info->setButtonText(QMessageBox::Ok, tr("Update existing instance"));
|
||||||
|
info->setButtonText(QMessageBox::Abort, tr("Create new instance"));
|
||||||
|
|
||||||
|
if (info->exec() && info->clickedButton() == info->button(QMessageBox::Abort))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Remove repeated files, we don't need to download them!
|
||||||
|
QDir old_inst_dir(inst->instanceRoot());
|
||||||
|
|
||||||
|
QString old_index_path(FS::PathCombine(old_inst_dir.absolutePath(), "mrpack", "modrinth.index.json"));
|
||||||
|
QFileInfo old_index_file(old_index_path);
|
||||||
|
if (old_index_file.exists()) {
|
||||||
|
std::vector<Modrinth::File> old_files;
|
||||||
|
parseManifest(old_index_path, old_files);
|
||||||
|
|
||||||
|
// Let's remove all duplicated, identical resources!
|
||||||
|
auto files_iterator = m_files.begin();
|
||||||
|
begin:
|
||||||
|
while (files_iterator != m_files.end()) {
|
||||||
|
auto const& file = *files_iterator;
|
||||||
|
|
||||||
|
auto old_files_iterator = old_files.begin();
|
||||||
|
while (old_files_iterator != old_files.end()) {
|
||||||
|
auto const& old_file = *old_files_iterator;
|
||||||
|
|
||||||
|
if (old_file.hash == file.hash) {
|
||||||
|
qDebug() << "Removed file at" << file.path << "from list of downloads";
|
||||||
|
files_iterator = m_files.erase(files_iterator);
|
||||||
|
old_files_iterator = old_files.erase(old_files_iterator);
|
||||||
|
goto begin; // Sorry :c
|
||||||
|
}
|
||||||
|
|
||||||
|
old_files_iterator++;
|
||||||
|
}
|
||||||
|
|
||||||
|
files_iterator++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Some files were removed from the old version, and some will be downloaded in an updated version,
|
||||||
|
// so we're fine removing them!
|
||||||
|
if (!old_files.empty()) {
|
||||||
|
QDir old_minecraft_dir(inst->gameRoot());
|
||||||
|
for (auto const& file : old_files) {
|
||||||
|
qWarning() << "Removing" << file.path;
|
||||||
|
old_minecraft_dir.remove(file.path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Currently 'overrides' will always override the stuff on update. How do we preserve unchanged overrides?
|
||||||
|
|
||||||
|
setOverride(true);
|
||||||
|
qDebug() << "Will override instance!";
|
||||||
|
|
||||||
|
// We let it go through the createInstance() stage, just with a couple modifications for updating
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://docs.modrinth.com/docs/modpacks/format_definition/
|
||||||
bool ModrinthCreationTask::createInstance()
|
bool ModrinthCreationTask::createInstance()
|
||||||
{
|
{
|
||||||
QEventLoop loop;
|
QEventLoop loop;
|
||||||
|
|
||||||
if (m_files.empty() && !parseManifest())
|
QString index_path = FS::PathCombine(m_stagingPath, "modrinth.index.json");
|
||||||
|
if (m_files.empty() && !parseManifest(index_path, m_files))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
// Keep index file in case we need it some other time (like when changing versions)
|
||||||
|
QString new_index_place(FS::PathCombine(m_stagingPath, "mrpack", "modrinth.index.json"));
|
||||||
|
FS::ensureFilePathExists(new_index_place);
|
||||||
|
QFile::rename(index_path, new_index_place);
|
||||||
|
|
||||||
auto mcPath = FS::PathCombine(m_stagingPath, ".minecraft");
|
auto mcPath = FS::PathCombine(m_stagingPath, ".minecraft");
|
||||||
|
|
||||||
auto override_path = FS::PathCombine(m_stagingPath, "overrides");
|
auto override_path = FS::PathCombine(m_stagingPath, "overrides");
|
||||||
@ -43,6 +146,7 @@ bool ModrinthCreationTask::createInstance()
|
|||||||
QString configPath = FS::PathCombine(m_stagingPath, "instance.cfg");
|
QString configPath = FS::PathCombine(m_stagingPath, "instance.cfg");
|
||||||
auto instanceSettings = std::make_shared<INISettingsObject>(configPath);
|
auto instanceSettings = std::make_shared<INISettingsObject>(configPath);
|
||||||
MinecraftInstance instance(m_globalSettings, instanceSettings, m_stagingPath);
|
MinecraftInstance instance(m_globalSettings, instanceSettings, m_stagingPath);
|
||||||
|
|
||||||
auto components = instance.getPackProfile();
|
auto components = instance.getPackProfile();
|
||||||
components->buildingFromScratch();
|
components->buildingFromScratch();
|
||||||
components->setComponentVersion("net.minecraft", minecraftVersion, true);
|
components->setComponentVersion("net.minecraft", minecraftVersion, true);
|
||||||
@ -53,6 +157,7 @@ bool ModrinthCreationTask::createInstance()
|
|||||||
components->setComponentVersion("org.quiltmc.quilt-loader", quiltVersion);
|
components->setComponentVersion("org.quiltmc.quilt-loader", quiltVersion);
|
||||||
if (!forgeVersion.isEmpty())
|
if (!forgeVersion.isEmpty())
|
||||||
components->setComponentVersion("net.minecraftforge", forgeVersion);
|
components->setComponentVersion("net.minecraftforge", forgeVersion);
|
||||||
|
|
||||||
if (m_instIcon != "default") {
|
if (m_instIcon != "default") {
|
||||||
instance.setIconKey(m_instIcon);
|
instance.setIconKey(m_instIcon);
|
||||||
} else {
|
} else {
|
||||||
@ -101,11 +206,10 @@ bool ModrinthCreationTask::createInstance()
|
|||||||
return ended_well;
|
return ended_well;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ModrinthCreationTask::parseManifest()
|
bool ModrinthCreationTask::parseManifest(QString index_path, std::vector<Modrinth::File>& files)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
QString indexPath = FS::PathCombine(m_stagingPath, "modrinth.index.json");
|
auto doc = Json::requireDocument(index_path);
|
||||||
auto doc = Json::requireDocument(indexPath);
|
|
||||||
auto obj = Json::requireObject(doc, "modrinth.index.json");
|
auto obj = Json::requireObject(doc, "modrinth.index.json");
|
||||||
int formatVersion = Json::requireInteger(obj, "formatVersion", "modrinth.index.json");
|
int formatVersion = Json::requireInteger(obj, "formatVersion", "modrinth.index.json");
|
||||||
if (formatVersion == 1) {
|
if (formatVersion == 1) {
|
||||||
@ -184,7 +288,7 @@ bool ModrinthCreationTask::parseManifest()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
m_files.push_back(file);
|
files.push_back(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto dependencies = Json::requireObject(obj, "dependencies", "modrinth.index.json");
|
auto dependencies = Json::requireObject(obj, "dependencies", "modrinth.index.json");
|
||||||
@ -205,7 +309,7 @@ bool ModrinthCreationTask::parseManifest()
|
|||||||
} else {
|
} else {
|
||||||
throw JSONValidationError(QStringLiteral("Unknown format version: %s").arg(formatVersion));
|
throw JSONValidationError(QStringLiteral("Unknown format version: %s").arg(formatVersion));
|
||||||
}
|
}
|
||||||
QFile::remove(indexPath);
|
|
||||||
} catch (const JSONValidationError& e) {
|
} catch (const JSONValidationError& e) {
|
||||||
setError(tr("Could not understand pack index:\n") + e.cause());
|
setError(tr("Could not understand pack index:\n") + e.cause());
|
||||||
return false;
|
return false;
|
||||||
|
@ -24,7 +24,7 @@ class ModrinthCreationTask final : public InstanceCreationTask {
|
|||||||
bool createInstance() override;
|
bool createInstance() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool parseManifest();
|
bool parseManifest(QString, std::vector<Modrinth::File>&);
|
||||||
QString getManagedPackID() const;
|
QString getManagedPackID() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
Loading…
Reference in New Issue
Block a user