fix: move file deletion to the end of the instance update

This makes it harder for problems in the updating process to affect the
current instance. Network issues, for instance, will no longer put the
instance in an invalid state.

Still, a possible improvement to this would be passing that logic to
InstanceStaging instead, to be handled with the instance commiting
directly. However, as it is now, the code would become very spaguetti-y,
and given that the override operation in the commiting could also put
the instance into an invalid state, it seems to me that, in order to
fully error-proof this, we would need to do a copy operation on the
whole instance, in order to modify the copy, and only in the end
override everything an once with a rename. That also has the possibility
of corrupting the instance if done without super care, however, so I
think we may need to instead create an automatic backup system, with an
undo command of sorts, or something like that. This doesn't seem very
trivial though, so it'll probably need to wait until another PR. In the
meantime, the user is advised to always backup their instances before
doing this kind of action, as always.

What a long commit message o.O

Signed-off-by: flow <flowlnlnln@gmail.com>
This commit is contained in:
flow 2022-08-05 21:26:02 -03:00
parent 2dd372600c
commit d2fdbec41d
No known key found for this signature in database
GPG Key ID: 8D0F221F0A59F469
4 changed files with 61 additions and 29 deletions

View File

@ -1,6 +1,7 @@
#include "InstanceCreationTask.h" #include "InstanceCreationTask.h"
#include <QDebug> #include <QDebug>
#include <QFile>
InstanceCreationTask::InstanceCreationTask() = default; InstanceCreationTask::InstanceCreationTask() = default;
@ -19,19 +20,37 @@ void InstanceCreationTask::executeTask()
return; return;
} }
// If this is set, it means we're updating an instance. Since the previous step likely if (!createInstance()) {
// removed some old files, we'd better not let the user abort the next task, since it'd if (m_abort)
// put the instance in an invalid state.
// TODO: Figure out an unexpensive way of making such file removal a recoverable transaction.
setAbortStatus(!shouldOverride());
if (createInstance()) {
emitSucceeded();
return; return;
}
qWarning() << "Instance creation failed!"; qWarning() << "Instance creation failed!";
if (!m_error_message.isEmpty()) if (!m_error_message.isEmpty())
qWarning() << "Reason: " << m_error_message; qWarning() << "Reason: " << m_error_message;
emitFailed(tr("Error while creating new instance.")); 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()) {
setAbortStatus(false);
setStatus(tr("Removing old conflicting files..."));
qDebug() << "Removing old files";
for (auto path : m_files_to_remove) {
if (!QFile::exists(path))
continue;
qDebug() << "Removing" << path;
if (!QFile::remove(path)) {
qCritical() << "Couldn't remove the old conflicting files.";
emitFailed(tr("Failed to remove old conflicting files."));
return;
}
}
}
emitSucceeded();
return;
} }

View File

@ -39,6 +39,8 @@ class InstanceCreationTask : public InstanceTask {
protected: protected:
bool m_abort = false; bool m_abort = false;
QStringList m_files_to_remove;
private: private:
QString m_error_message; QString m_error_message;
}; };

View File

@ -120,15 +120,17 @@ bool FlameCreationTask::updateInstance()
files_iterator++; files_iterator++;
} }
QString old_minecraft_dir(inst->gameRoot()); QDir old_minecraft_dir(inst->gameRoot());
// We will remove all the previous overrides, to prevent duplicate files! // 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? // 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. // FIXME: We may want to do something about disabled mods.
auto old_overrides = Override::readOverrides("overrides", old_index_folder); auto old_overrides = Override::readOverrides("overrides", old_index_folder);
for (auto entry : old_overrides) { for (auto entry : old_overrides) {
qDebug() << "Removing" << entry; if (entry.isEmpty())
old_minecraft_dir.remove(entry); continue;
qDebug() << "Scheduling" << entry << "for removal";
m_files_to_remove.append(old_minecraft_dir.absoluteFilePath(entry));
} }
// Remove remaining old files (we need to do an API request to know which ids are which files...) // Remove remaining old files (we need to do an API request to know which ids are which files...)
@ -143,7 +145,7 @@ bool FlameCreationTask::updateInstance()
QEventLoop loop; QEventLoop loop;
connect(job, &NetJob::succeeded, this, [raw_response, fileIds, old_inst_dir, &old_files, old_minecraft_dir] { connect(job, &NetJob::succeeded, this, [this, raw_response, fileIds, old_inst_dir, &old_files, old_minecraft_dir] {
// Parse the API response // Parse the API response
QJsonParseError parse_error{}; QJsonParseError parse_error{};
auto doc = QJsonDocument::fromJson(*raw_response, &parse_error); auto doc = QJsonDocument::fromJson(*raw_response, &parse_error);
@ -180,10 +182,9 @@ bool FlameCreationTask::updateInstance()
if (file.fileName.isEmpty() || file.targetFolder.isEmpty()) if (file.fileName.isEmpty() || file.targetFolder.isEmpty())
continue; continue;
qDebug() << "Removing" << file.fileName << "at" << file.targetFolder; QString relative_path(FS::PathCombine(file.targetFolder, file.fileName));
QString path(FS::PathCombine(old_minecraft_dir, file.targetFolder, file.fileName)); qDebug() << "Scheduling" << relative_path << "for removal";
if (!QFile::remove(path)) m_files_to_remove.append(old_minecraft_dir.absoluteFilePath(relative_path));
qDebug() << "Failed to remove file at" << path;
} }
}); });
connect(job, &NetJob::finished, &loop, &QEventLoop::quit); connect(job, &NetJob::finished, &loop, &QEventLoop::quit);
@ -334,14 +335,17 @@ bool FlameCreationTask::createInstance()
loop.exec(); loop.exec();
if (m_instance) { bool did_succeed = getError().isEmpty();
if (m_instance && did_succeed) {
setAbortStatus(false);
auto inst = m_instance.value(); auto inst = m_instance.value();
inst->copyManagedPack(instance); inst->copyManagedPack(instance);
inst->setName(instance.name()); inst->setName(instance.name());
} }
return getError().isEmpty(); return did_succeed;
} }
void FlameCreationTask::idResolverSucceeded(QEventLoop& loop) void FlameCreationTask::idResolverSucceeded(QEventLoop& loop)
@ -421,7 +425,6 @@ void FlameCreationTask::setupDownloadJob(QEventLoop& loop)
m_mod_id_resolver.reset(); m_mod_id_resolver.reset();
connect(m_files_job.get(), &NetJob::succeeded, this, [&]() { connect(m_files_job.get(), &NetJob::succeeded, this, [&]() {
m_files_job.reset(); m_files_job.reset();
emitSucceeded();
}); });
connect(m_files_job.get(), &NetJob::failed, [&](QString reason) { connect(m_files_job.get(), &NetJob::failed, [&](QString reason) {
m_files_job.reset(); m_files_job.reset();

View File

@ -105,12 +105,15 @@ bool ModrinthCreationTask::updateInstance()
} }
QDir old_minecraft_dir(inst->gameRoot()); QDir old_minecraft_dir(inst->gameRoot());
// Some files were removed from the old version, and some will be downloaded in an updated version, // Some files were removed from the old version, and some will be downloaded in an updated version,
// so we're fine removing them! // so we're fine removing them!
if (!old_files.empty()) { if (!old_files.empty()) {
for (auto const& file : old_files) { for (auto const& file : old_files) {
qDebug() << "Removing" << file.path; if (file.path.isEmpty())
old_minecraft_dir.remove(file.path); continue;
qDebug() << "Scheduling" << file.path << "for removal";
m_files_to_remove.append(old_minecraft_dir.absoluteFilePath(file.path));
} }
} }
@ -119,14 +122,18 @@ bool ModrinthCreationTask::updateInstance()
// FIXME: We may want to do something about disabled mods. // FIXME: We may want to do something about disabled mods.
auto old_overrides = Override::readOverrides("overrides", old_index_folder); auto old_overrides = Override::readOverrides("overrides", old_index_folder);
for (auto entry : old_overrides) { for (auto entry : old_overrides) {
qDebug() << "Removing" << entry; if (entry.isEmpty())
old_minecraft_dir.remove(entry); continue;
qDebug() << "Scheduling" << entry << "for removal";
m_files_to_remove.append(old_minecraft_dir.absoluteFilePath(entry));
} }
auto old_client_overrides = Override::readOverrides("client-overrides", old_index_folder); auto old_client_overrides = Override::readOverrides("client-overrides", old_index_folder);
for (auto entry : old_overrides) { for (auto entry : old_overrides) {
qDebug() << "Removing" << entry; if (entry.isEmpty())
old_minecraft_dir.remove(entry); continue;
qDebug() << "Scheduling" << entry << "for removal";
m_files_to_remove.append(old_minecraft_dir.absoluteFilePath(entry));
} }
} }
@ -244,7 +251,8 @@ bool ModrinthCreationTask::createInstance()
loop.exec(); loop.exec();
if (m_instance) { if (m_instance && ended_well) {
setAbortStatus(false);
auto inst = m_instance.value(); auto inst = m_instance.value();
inst->copyManagedPack(instance); inst->copyManagedPack(instance);