aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorflow <flowlnlnln@gmail.com>2022-07-08 13:00:44 -0300
committerflow <flowlnlnln@gmail.com>2022-09-20 18:36:06 -0300
commit208ed73e59c46ee7966f463558c07805a9b541e6 (patch)
treeb47f092c3bd29fcad15b102dfca4248f1fb5737c
parent4441b373385f9b7f77deed2a27751337951f38f6 (diff)
downloadPrismLauncher-208ed73e59c46ee7966f463558c07805a9b541e6.tar.gz
PrismLauncher-208ed73e59c46ee7966f463558c07805a9b541e6.tar.bz2
PrismLauncher-208ed73e59c46ee7966f463558c07805a9b541e6.zip
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>
-rw-r--r--launcher/InstanceImportTask.cpp11
-rw-r--r--launcher/InstanceList.cpp66
-rw-r--r--launcher/InstanceList.h7
-rw-r--r--launcher/InstanceTask.h7
-rw-r--r--launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp118
-rw-r--r--launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h2
6 files changed, 185 insertions, 26 deletions
diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp
index b19b5fa6..4bdf9cd2 100644
--- a/launcher/InstanceImportTask.cpp
+++ b/launcher/InstanceImportTask.cpp
@@ -593,15 +593,16 @@ void InstanceImportTask::processModrinth()
inst_creation_task->setIcon(m_instIcon);
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::progress, this, &InstanceImportTask::setProgress);
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] {
- inst_creation_task->abort();
- });
+ connect(this, &Task::aborted, inst_creation_task, &InstanceCreationTask::abort);
inst_creation_task->start();
}
diff --git a/launcher/InstanceList.cpp b/launcher/InstanceList.cpp
index 4447a17c..698aa24e 100644
--- a/launcher/InstanceList.cpp
+++ b/launcher/InstanceList.cpp
@@ -535,7 +535,20 @@ InstancePtr InstanceList::getInstanceById(QString instId) const
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()));
}
@@ -764,9 +777,8 @@ class InstanceStaging : public Task {
Q_OBJECT
const unsigned minBackoff = 1;
const unsigned maxBackoff = 16;
-
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)
{
m_parent = parent;
@@ -808,7 +820,8 @@ class InstanceStaging : public Task {
void childSucceded()
{
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();
return;
}
@@ -834,8 +847,8 @@ class InstanceStaging : public Task {
*/
ExponentialSeries backoff;
QString m_stagingPath;
- InstanceList* m_parent;
- unique_qobject_ptr<Task> m_child;
+ InstanceList * m_parent;
+ unique_qobject_ptr<InstanceTask> m_child;
QString m_instanceName;
QString m_groupName;
QTimer m_backoffTimer;
@@ -866,23 +879,52 @@ QString InstanceList::getStagedInstancePath()
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;
- 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);
QString destination = FS::PathCombine(m_instDir, instID);
- if (!dir.rename(path, destination)) {
- qWarning() << "Failed to move" << path << "to" << destination;
- return false;
+
+ if (should_override) {
+ 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;
- instanceSet.insert(instID);
m_groupNameCache.insert(groupName);
+ instanceSet.insert(instID);
+
emit instancesChanged();
emit instanceSelectRequest(instID);
}
+
saveGroupList();
return true;
}
diff --git a/launcher/InstanceList.h b/launcher/InstanceList.h
index 62282f04..6b4dcfa4 100644
--- a/launcher/InstanceList.h
+++ b/launcher/InstanceList.h
@@ -101,7 +101,10 @@ public:
InstListError loadList();
void saveNow();
+ /* O(n) */
InstancePtr getInstanceById(QString id) const;
+ /* O(n) */
+ InstancePtr getInstanceByManagedName(QString managed_name) const;
QModelIndex getInstanceIndexById(const QString &id) const;
QStringList getGroups();
bool isGroupCollapsed(const QString &groupName);
@@ -127,8 +130,10 @@ public:
/**
* Commit the staging area given by @keyPath to the provider - used when creation succeeds.
* 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.
diff --git a/launcher/InstanceTask.h b/launcher/InstanceTask.h
index 82e23f11..02810a52 100644
--- a/launcher/InstanceTask.h
+++ b/launcher/InstanceTask.h
@@ -43,10 +43,17 @@ public:
return m_instGroup;
}
+ bool shouldOverride() const { return m_override_existing; }
+
+protected:
+ void setOverride(bool override) { m_override_existing = override; }
+
protected: /* data */
SettingsObjectPtr m_globalSettings;
QString m_instName;
QString m_instIcon;
QString m_instGroup;
QString m_stagingPath;
+
+ bool m_override_existing = false;
};
diff --git a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp
index 7eb6cc8f..efb8c99b 100644
--- a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp
+++ b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp
@@ -2,25 +2,128 @@
#include "Application.h"
#include "FileSystem.h"
+#include "InstanceList.h"
#include "Json.h"
#include "minecraft/MinecraftInstance.h"
#include "minecraft/PackProfile.h"
-#include "net/NetJob.h"
+#include "modplatform/ModIndex.h"
+
#include "net/ChecksumValidator.h"
#include "settings/INISettingsObject.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()
{
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;
+ // 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 override_path = FS::PathCombine(m_stagingPath, "overrides");
@@ -43,6 +146,7 @@ bool ModrinthCreationTask::createInstance()
QString configPath = FS::PathCombine(m_stagingPath, "instance.cfg");
auto instanceSettings = std::make_shared<INISettingsObject>(configPath);
MinecraftInstance instance(m_globalSettings, instanceSettings, m_stagingPath);
+
auto components = instance.getPackProfile();
components->buildingFromScratch();
components->setComponentVersion("net.minecraft", minecraftVersion, true);
@@ -53,6 +157,7 @@ bool ModrinthCreationTask::createInstance()
components->setComponentVersion("org.quiltmc.quilt-loader", quiltVersion);
if (!forgeVersion.isEmpty())
components->setComponentVersion("net.minecraftforge", forgeVersion);
+
if (m_instIcon != "default") {
instance.setIconKey(m_instIcon);
} else {
@@ -101,11 +206,10 @@ bool ModrinthCreationTask::createInstance()
return ended_well;
}
-bool ModrinthCreationTask::parseManifest()
+bool ModrinthCreationTask::parseManifest(QString index_path, std::vector<Modrinth::File>& files)
{
try {
- QString indexPath = FS::PathCombine(m_stagingPath, "modrinth.index.json");
- auto doc = Json::requireDocument(indexPath);
+ auto doc = Json::requireDocument(index_path);
auto obj = Json::requireObject(doc, "modrinth.index.json");
int formatVersion = Json::requireInteger(obj, "formatVersion", "modrinth.index.json");
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");
@@ -205,7 +309,7 @@ bool ModrinthCreationTask::parseManifest()
} else {
throw JSONValidationError(QStringLiteral("Unknown format version: %s").arg(formatVersion));
}
- QFile::remove(indexPath);
+
} catch (const JSONValidationError& e) {
setError(tr("Could not understand pack index:\n") + e.cause());
return false;
diff --git a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h
index 61f7dd5c..4e804e58 100644
--- a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h
+++ b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h
@@ -24,7 +24,7 @@ class ModrinthCreationTask final : public InstanceCreationTask {
bool createInstance() override;
private:
- bool parseManifest();
+ bool parseManifest(QString, std::vector<Modrinth::File>&);
QString getManagedPackID() const;
private: