/*
Copyright (C) 2016 Sebastian Herbord. All rights reserved.
This file is part of Mod Organizer.
Mod Organizer is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Mod Organizer is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Mod Organizer. If not, see .
*/
#include "instancemanager.h"
#include "selectiondialog.h"
#include
#include
#include
#include
#include
#include
#include
#include
static const char COMPANY_NAME[] = "Tannin";
static const char APPLICATION_NAME[] = "Mod Organizer";
static const char INSTANCE_KEY[] = "CurrentInstance";
InstanceManager::InstanceManager() : m_AppSettings(COMPANY_NAME, APPLICATION_NAME) {}
InstanceManager& InstanceManager::instance() {
static InstanceManager s_Instance;
return s_Instance;
}
void InstanceManager::overrideInstance(const QString& instanceName) {
m_overrideInstanceName = instanceName;
m_overrideInstance = true;
}
QString InstanceManager::currentInstance() const {
if (m_overrideInstance)
return m_overrideInstanceName;
else
return m_AppSettings.value(INSTANCE_KEY, "").toString();
}
void InstanceManager::clearCurrentInstance() {
setCurrentInstance("");
m_Reset = true;
}
void InstanceManager::setCurrentInstance(const QString& name) { m_AppSettings.setValue(INSTANCE_KEY, name); }
bool InstanceManager::deleteLocalInstance(const QString& instanceId) const {
bool result = true;
QString instancePath =
QDir::fromNativeSeparators(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/" + instanceId);
if (QMessageBox::warning(
nullptr, QObject::tr("Deleting folder"),
QObject::tr("I'm about to delete the following folder: \"%1\". Proceed?").arg(instancePath),
QMessageBox::No | QMessageBox::Yes, QMessageBox::No) == QMessageBox::No) {
return false;
}
if (!MOBase::shellDelete(QStringList(instancePath), true)) {
qWarning("Failed to shell-delete \"%s\" (errorcode %lu), trying regular delete", qPrintable(instancePath),
::GetLastError());
if (!MOBase::removeDir(instancePath)) {
qWarning("regular delete failed too");
result = false;
}
}
return result;
}
QString InstanceManager::manageInstances(const QStringList& instanceList) const {
SelectionDialog selection(
QString("%1
%2")
.arg(QObject::tr("Choose Instance to Delete"))
.arg(QObject::tr("Be Carefull! Deleting an Instance will remove all your files for that Instance (mods, "
"downloads, profiles, configuration, ...). Custom paths outside of the instance folder "
"for downloads, mods, etc. will be left untoched.")),
nullptr);
for (const QString& instance : instanceList) {
selection.addChoice(instance, "", instance);
}
if (selection.exec() == QDialog::Rejected) {
return (chooseInstance(instances()));
} else {
QString choice = selection.getChoiceData().toString();
{
if (QMessageBox::warning(
nullptr, QObject::tr("Are you sure?"),
QObject::tr("Are you really sure you want to delete the Instance \"%1\" with all its files?")
.arg(choice),
QMessageBox::No | QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes) {
if (!deleteLocalInstance(choice)) {
QMessageBox::warning(nullptr, QObject::tr("Failed to delete Instance"),
QObject::tr("Could not delete Instance \"%1\"").arg(choice), QMessageBox::Ok);
}
}
}
}
return (manageInstances(instances()));
}
QString InstanceManager::queryInstanceName(const QStringList& instanceList) const {
QString instanceId;
while (instanceId.isEmpty()) {
QInputDialog dialog;
dialog.setWindowTitle(QObject::tr("Enter a Name for the new Instance"));
dialog.setLabelText(
QObject::tr("Enter a new name or select one from the sugested list (only letters and numbers allowed):"));
// would be neat if we could take the names from the game plugins but
// the required initialization order requires the ini file to be
// available *before* we load plugins
dialog.setComboBoxItems(
{"NewName", "Fallout 4", "SkyrimSE", "Skyrim", "Fallout 3", "Fallout NV", "FO4VR", "Oblivion"});
dialog.setComboBoxEditable(true);
if (dialog.exec() == QDialog::Rejected) {
throw MOBase::MyException(QObject::tr("Canceled"));
}
instanceId = dialog.textValue().replace(QRegExp("[^0-9a-zA-Z ]"), "");
bool alreadyExists = false;
for (const QString& instance : instanceList) {
if (instanceId == instance)
alreadyExists = true;
}
if (alreadyExists) {
QMessageBox msgBox;
msgBox.setText(QObject::tr("The instance \"%1\" already exists.").arg(instanceId));
msgBox.setInformativeText(
QObject::tr("Please choose a different instance name, like: \"%1 1\" .").arg(instanceId));
msgBox.exec();
instanceId = "";
}
}
return instanceId;
}
QString InstanceManager::chooseInstance(const QStringList& instanceList) const {
enum class Special : uint8_t { NewInstance, Portable, Manage };
SelectionDialog selection(
QString("%1
%2")
.arg(QObject::tr("Choose Instance"))
.arg(QObject::tr("Each Instance is a full set of MO data files (mods, "
"downloads, profiles, configuration, ...). You can use multiple "
"instances for different games. Instances are stored in Appdata and can be accessed by "
"all MO installations. "
"If your MO folder is writable, you can also store a single instance locally (called "
"a Portable install, and all the MO data files will be inside the installation folder).")),
nullptr);
selection.disableCancel();
for (const QString& instance : instanceList) {
selection.addChoice(instance, "", instance);
}
selection.addChoice(QIcon(":/MO/gui/add"), QObject::tr("New"), QObject::tr("Create a new instance."),
static_cast(Special::NewInstance));
if (QFileInfo(qApp->applicationDirPath()).isWritable()) {
selection.addChoice(QIcon(":/MO/gui/package"), QObject::tr("Portable"), QObject::tr("Use MO folder for data."),
static_cast(Special::Portable));
}
selection.addChoice(QIcon(":/MO/gui/remove"), QObject::tr("Manage Instances"), QObject::tr("Delete an Instance."),
static_cast(Special::Manage));
if (selection.exec() == QDialog::Rejected) {
qDebug("rejected");
throw MOBase::MyException(QObject::tr("Canceled"));
}
QVariant choice = selection.getChoiceData();
if (choice.type() == QVariant::String) {
return choice.toString();
} else {
switch (choice.value()) {
case Special::NewInstance:
return queryInstanceName(instanceList);
case Special::Portable:
return QString();
case Special::Manage: {
return (manageInstances(instances()));
}
default:
throw std::runtime_error("invalid selection");
}
}
}
QString InstanceManager::instancePath() const {
return QDir::fromNativeSeparators(QStandardPaths::writableLocation(QStandardPaths::DataLocation));
}
QStringList InstanceManager::instances() const {
return QDir(instancePath()).entryList(QDir::Dirs | QDir::NoDotAndDotDot);
}
bool InstanceManager::portableInstall() const {
return QFile::exists(qApp->applicationDirPath() + "/" + QString::fromStdWString(AppConfig::iniFileName()));
}
void InstanceManager::createDataPath(const QString& dataPath) const {
if (!QDir(dataPath).exists()) {
if (!QDir().mkpath(dataPath)) {
throw MOBase::MyException(QObject::tr("failed to create %1").arg(dataPath));
} else {
QMessageBox::information(nullptr, QObject::tr("Data directory created"),
QObject::tr("New data directory created at %1. If you don't want to "
"store a lot of data there, reconfigure the storage "
"directories via settings.")
.arg(dataPath));
}
}
}
QString InstanceManager::determineDataPath() {
QString instanceId = currentInstance();
if (instanceId.isEmpty() && !m_Reset && (m_overrideInstance || portableInstall())) {
// startup, apparently using portable mode before
return qApp->applicationDirPath();
}
QString dataPath =
QDir::fromNativeSeparators(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/" + instanceId);
if (!m_overrideInstance && (instanceId.isEmpty() || !QFileInfo::exists(dataPath))) {
instanceId = chooseInstance(instances());
setCurrentInstance(instanceId);
if (!instanceId.isEmpty()) {
dataPath = QDir::fromNativeSeparators(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/" +
instanceId);
}
}
if (instanceId.isEmpty()) {
return qApp->applicationDirPath();
} else {
createDataPath(dataPath);
return dataPath;
}
}