NOISSUE Granular instance reload

This commit is contained in:
Petr Mrázek 2016-10-03 00:55:54 +02:00
parent bbe139dce5
commit d66fdcd4cc
28 changed files with 1507 additions and 1005 deletions

View File

@ -243,7 +243,7 @@ int IconList::rowCount(const QModelIndex &parent) const
return icons.size();
}
void IconList::installIcons(QStringList iconFiles)
void IconList::installIcons(const QStringList &iconFiles)
{
for (QString file : iconFiles)
{
@ -261,7 +261,7 @@ void IconList::installIcons(QStringList iconFiles)
}
}
bool IconList::iconFileExists(QString key)
bool IconList::iconFileExists(const QString &key) const
{
auto iconEntry = icon(key);
if(!iconEntry)
@ -271,7 +271,7 @@ bool IconList::iconFileExists(QString key)
return iconEntry->has(IconType::FileBased);
}
const MMCIcon *IconList::icon(QString key)
const MMCIcon *IconList::icon(const QString &key) const
{
int iconIdx = getIconIndex(key);
if (iconIdx == -1)
@ -279,7 +279,7 @@ const MMCIcon *IconList::icon(QString key)
return &icons[iconIdx];
}
bool IconList::deleteIcon(QString key)
bool IconList::deleteIcon(const QString &key)
{
int iconIdx = getIconIndex(key);
if (iconIdx == -1)

View File

@ -44,18 +44,19 @@ public:
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override;
virtual bool addIcon(QString key, QString name, QString path, IconType type) override;
bool deleteIcon(QString key);
bool iconFileExists(QString key);
bool addIcon(const QString &key, const QString &name, const QString &path, const IconType type) override;
void saveIcon(const QString &key, const QString &path, const char * format) const override;
bool deleteIcon(const QString &key) override;
bool iconFileExists(const QString &key) const override;
virtual QStringList mimeTypes() const override;
virtual Qt::DropActions supportedDropActions() const override;
virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override;
virtual Qt::ItemFlags flags(const QModelIndex &index) const override;
void installIcons(QStringList iconFiles);
void installIcons(const QStringList &iconFiles) override;
const MMCIcon * icon(QString key);
const MMCIcon * icon(const QString &key) const;
void startWatching();
void stopWatching();

View File

@ -17,6 +17,7 @@
#include <QFileInfo>
#include <QDir>
#include <QDebug>
#include "settings/INISettingsObject.h"
#include "settings/Setting.h"
@ -74,10 +75,32 @@ void BaseInstance::iconUpdated(QString key)
}
}
void BaseInstance::invalidate()
{
changeStatus(Status::Gone);
qDebug() << "Instance" << id() << "has been invalidated.";
}
void BaseInstance::nuke()
{
changeStatus(Status::Gone);
qDebug() << "Instance" << id() << "has been deleted by MultiMC.";
FS::deletePath(instanceRoot());
emit nuked(this);
}
void BaseInstance::changeStatus(BaseInstance::Status newStatus)
{
Status status = currentStatus();
if(status != newStatus)
{
m_status = newStatus;
emit statusChanged(status, newStatus);
}
}
BaseInstance::Status BaseInstance::currentStatus() const
{
return m_status;
}
QString BaseInstance::id() const
@ -278,3 +301,19 @@ std::shared_ptr<LaunchTask> BaseInstance::getLaunchTask()
{
return m_launchProcess;
}
void BaseInstance::setProvider(BaseInstanceProvider* provider)
{
// only once.
assert(!m_provider);
if(m_provider)
{
qWarning() << "Provider set more than once for instance" << id();
}
m_provider = provider;
}
BaseInstanceProvider* BaseInstance::provider() const
{
return m_provider;
}

View File

@ -14,6 +14,7 @@
*/
#pragma once
#include <cassert>
#include <QObject>
#include "QObjectPtr.h"
@ -35,6 +36,7 @@ class QDir;
class Task;
class LaunchTask;
class BaseInstance;
class BaseInstanceProvider;
// pointer for lazy people
typedef std::shared_ptr<BaseInstance> InstancePtr;
@ -54,6 +56,13 @@ protected:
/// no-touchy!
BaseInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir);
public: /* types */
enum class Status
{
Present,
Gone // either nuked or invalidated
};
public:
/// virtual destructor to make sure the destruction is COMPLETE
virtual ~BaseInstance() {};
@ -66,6 +75,14 @@ public:
/// responsible of cleaning up the husk
void nuke();
/***
* the instance has been invalidated - it is no longer tracked by MultiMC for some reason,
* but it has not necessarily been deleted.
*
* Happens when the instance folder changes to some other location, or the instance is removed by external means.
*/
void invalidate();
/// The instance's ID. The ID SHALL be determined by MMC internally. The ID IS guaranteed to
/// be unique.
virtual QString id() const;
@ -75,6 +92,9 @@ public:
int64_t totalTimePlayed() const;
void resetTimePlayed();
void setProvider(BaseInstanceProvider * provider);
BaseInstanceProvider * provider() const;
/// get the type of this instance
QString instanceType() const;
@ -219,6 +239,11 @@ public:
*/
virtual QStringList verboseDescription(AuthSessionPtr session) = 0;
Status currentStatus() const;
protected:
void changeStatus(Status newStatus);
signals:
/*!
* \brief Signal emitted when properties relevant to the instance view change
@ -228,10 +253,6 @@ signals:
* \brief Signal emitted when groups are affected in any way
*/
void groupChanged();
/*!
* \brief The instance just got nuked. Hurray!
*/
void nuked(BaseInstance *inst);
void flagsChanged();
@ -239,10 +260,12 @@ signals:
void runningStatusChanged(bool running);
void statusChanged(Status from, Status to);
protected slots:
void iconUpdated(QString key);
protected:
protected: /* data */
QString m_rootDir;
QString m_group;
SettingsObjectPtr m_settings;
@ -250,6 +273,10 @@ protected:
bool m_isRunning = false;
std::shared_ptr<LaunchTask> m_launchProcess;
QDateTime m_timeStarted;
BaseInstanceProvider * m_provider = nullptr;
private: /* data */
Status m_status = Status::Present;
};
Q_DECLARE_METATYPE(std::shared_ptr<BaseInstance>)

View File

@ -0,0 +1,57 @@
#pragma once
#include <QObject>
#include <QString>
#include "BaseInstance.h"
#include "settings/SettingsObject.h"
#include "multimc_logic_export.h"
using InstanceId = QString;
using InstanceLocator = std::pair<InstancePtr, int>;
enum class InstCreateError
{
NoCreateError = 0,
NoSuchVersion,
UnknownCreateError,
InstExists,
CantCreateDir
};
class MULTIMC_LOGIC_EXPORT BaseInstanceProvider : public QObject
{
Q_OBJECT
public:
BaseInstanceProvider(SettingsObjectPtr settings) : m_globalSettings(settings)
{
// nil
}
public:
virtual QList<InstanceId> discoverInstances() = 0;
virtual InstancePtr loadInstance(const InstanceId &id) = 0;
virtual void loadGroupList() = 0;
virtual void saveGroupList() = 0;
virtual QString getStagedInstancePath()
{
return QString();
}
virtual bool commitStagedInstance(const QString & keyPath, const QString & path, const QString& instanceName, const QString & groupName)
{
return false;
}
virtual bool destroyStagingPath(const QString & path)
{
return true;
}
signals:
// Emit this when the list of provided instances changed
void instancesChanged();
// Emit when the set of groups your provider supplies changes.
void groupsChanged(QSet<QString> groups);
protected:
SettingsObjectPtr m_globalSettings;
};

View File

@ -8,8 +8,17 @@ set(CORE_SOURCES
BaseInstaller.cpp
BaseVersionList.h
BaseVersionList.cpp
InstanceCreationTask.h
InstanceCreationTask.cpp
InstanceCopyTask.h
InstanceCopyTask.cpp
InstanceImportTask.h
InstanceImportTask.cpp
InstanceList.h
InstanceList.cpp
BaseInstanceProvider.h
FolderInstanceProvider.h
FolderInstanceProvider.cpp
BaseVersion.h
BaseInstance.h
BaseInstance.cpp
@ -276,6 +285,8 @@ set(MINECRAFT_SOURCES
minecraft/ftb/LegacyFTBInstance.cpp
minecraft/ftb/FTBProfileStrategy.h
minecraft/ftb/FTBProfileStrategy.cpp
minecraft/ftb/FTBInstanceProvider.cpp
minecraft/ftb/FTBInstanceProvider.h
minecraft/ftb/FTBPlugin.h
minecraft/ftb/FTBPlugin.cpp

View File

@ -0,0 +1,356 @@
#include "FolderInstanceProvider.h"
#include "settings/INISettingsObject.h"
#include "FileSystem.h"
#include "minecraft/onesix/OneSixInstance.h"
#include "minecraft/legacy/LegacyInstance.h"
#include "NullInstance.h"
#include <QDir>
#include <QDirIterator>
#include <QFileSystemWatcher>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QUuid>
const static int GROUP_FILE_FORMAT_VERSION = 1;
struct WatchLock
{
WatchLock(QFileSystemWatcher * watcher, const QString& instDir)
: m_watcher(watcher), m_instDir(instDir)
{
m_watcher->removePath(m_instDir);
}
~WatchLock()
{
m_watcher->addPath(m_instDir);
}
QFileSystemWatcher * m_watcher;
QString m_instDir;
};
FolderInstanceProvider::FolderInstanceProvider(SettingsObjectPtr settings, const QString& instDir)
: BaseInstanceProvider(settings)
{
m_instDir = instDir;
if (!QDir::current().exists(m_instDir))
{
QDir::current().mkpath(m_instDir);
}
m_watcher = new QFileSystemWatcher(this);
connect(m_watcher, &QFileSystemWatcher::directoryChanged, this, &FolderInstanceProvider::instanceDirContentsChanged);
m_watcher->addPath(m_instDir);
}
QList< InstanceId > FolderInstanceProvider::discoverInstances()
{
QList<InstanceId> out;
QDirIterator iter(m_instDir, QDir::Dirs | QDir::NoDot | QDir::NoDotDot | QDir::Readable, QDirIterator::FollowSymlinks);
while (iter.hasNext())
{
QString subDir = iter.next();
QFileInfo dirInfo(subDir);
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());
QFileInfo instDirInfo(m_instDir);
if(targetInfo.canonicalPath() == instDirInfo.canonicalFilePath())
{
qDebug() << "Ignoring symlink" << subDir << "that leads into the instances folder";
continue;
}
}
auto id = dirInfo.fileName();
out.append(id);
qDebug() << "Found instance ID" << id;
}
return out;
}
InstancePtr FolderInstanceProvider::loadInstance(const InstanceId& id)
{
if(!m_groupsLoaded)
{
loadGroupList();
}
auto instanceRoot = FS::PathCombine(m_instDir, id);
auto instanceSettings = std::make_shared<INISettingsObject>(FS::PathCombine(instanceRoot, "instance.cfg"));
InstancePtr inst;
instanceSettings->registerSetting("InstanceType", "Legacy");
QString inst_type = instanceSettings->get("InstanceType").toString();
if (inst_type == "OneSix" || inst_type == "Nostalgia")
{
inst.reset(new OneSixInstance(m_globalSettings, instanceSettings, instanceRoot));
}
else if (inst_type == "Legacy")
{
inst.reset(new LegacyInstance(m_globalSettings, instanceSettings, instanceRoot));
}
else
{
inst.reset(new NullInstance(m_globalSettings, instanceSettings, instanceRoot));
}
inst->init();
inst->setProvider(this);
auto iter = groupMap.find(id);
if (iter != groupMap.end())
{
inst->setGroupInitial((*iter));
}
connect(inst.get(), &BaseInstance::groupChanged, this, &FolderInstanceProvider::groupChanged);
qDebug() << "Loaded instance " << inst->name() << " from " << inst->instanceRoot();
return inst;
}
#include "InstanceImportTask.h"
Task * FolderInstanceProvider::zipImportTask(const QUrl sourceUrl, const QString& instName, const QString& instGroup, const QString& instIcon)
{
return new InstanceImportTask(m_globalSettings, sourceUrl, this, instName, instGroup, instIcon);
}
#include "InstanceCreationTask.h"
Task * FolderInstanceProvider::creationTask(BaseVersionPtr version, const QString& instName, const QString& instGroup, const QString& instIcon)
{
return new InstanceCreationTask(m_globalSettings, this, version, instName, instIcon, instGroup);
}
#include "InstanceCopyTask.h"
Task * FolderInstanceProvider::copyTask(const InstancePtr& oldInstance, const QString& instName, const QString& instGroup, const QString& instIcon, bool copySaves)
{
return new InstanceCopyTask(m_globalSettings, this, oldInstance, instName, instIcon, instGroup, copySaves);
}
void FolderInstanceProvider::saveGroupList()
{
WatchLock foo(m_watcher, m_instDir);
QString groupFileName = m_instDir + "/instgroups.json";
QMap<QString, QSet<QString>> reverseGroupMap;
for (auto iter = groupMap.begin(); iter != groupMap.end(); iter++)
{
QString id = iter.key();
QString group = iter.value();
if (group.isEmpty())
continue;
if (!reverseGroupMap.count(group))
{
QSet<QString> set;
set.insert(id);
reverseGroupMap[group] = set;
}
else
{
QSet<QString> &set = reverseGroupMap[group];
set.insert(id);
}
}
QJsonObject toplevel;
toplevel.insert("formatVersion", QJsonValue(QString("1")));
QJsonObject groupsArr;
for (auto iter = reverseGroupMap.begin(); iter != reverseGroupMap.end(); iter++)
{
auto list = iter.value();
auto name = iter.key();
QJsonObject groupObj;
QJsonArray instanceArr;
groupObj.insert("hidden", QJsonValue(QString("false")));
for (auto item : list)
{
instanceArr.append(QJsonValue(item));
}
groupObj.insert("instances", instanceArr);
groupsArr.insert(name, groupObj);
}
toplevel.insert("groups", groupsArr);
QJsonDocument doc(toplevel);
try
{
FS::write(groupFileName, doc.toJson());
}
catch(FS::FileSystemException & e)
{
qCritical() << "Failed to write instance group file :" << e.cause();
}
}
void FolderInstanceProvider::loadGroupList()
{
QSet<QString> groupSet;
QString groupFileName = m_instDir + "/instgroups.json";
// if there's no group file, fail
if (!QFileInfo(groupFileName).exists())
return;
QByteArray jsonData;
try
{
jsonData = FS::read(groupFileName);
}
catch (FS::FileSystemException & e)
{
qCritical() << "Failed to read instance group file :" << e.cause();
return;
}
QJsonParseError error;
QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData, &error);
// if the json was bad, fail
if (error.error != QJsonParseError::NoError)
{
qCritical() << QString("Failed to parse instance group file: %1 at offset %2")
.arg(error.errorString(), QString::number(error.offset))
.toUtf8();
return;
}
// if the root of the json wasn't an object, fail
if (!jsonDoc.isObject())
{
qWarning() << "Invalid group file. Root entry should be an object.";
return;
}
QJsonObject rootObj = jsonDoc.object();
// Make sure the format version matches, otherwise fail.
if (rootObj.value("formatVersion").toVariant().toInt() != GROUP_FILE_FORMAT_VERSION)
return;
// Get the groups. if it's not an object, fail
if (!rootObj.value("groups").isObject())
{
qWarning() << "Invalid group list JSON: 'groups' should be an object.";
return;
}
groupMap.clear();
// Iterate through all the groups.
QJsonObject groupMapping = rootObj.value("groups").toObject();
for (QJsonObject::iterator iter = groupMapping.begin(); iter != groupMapping.end(); iter++)
{
QString groupName = iter.key();
// If not an object, complain and skip to the next one.
if (!iter.value().isObject())
{
qWarning() << QString("Group '%1' in the group list should "
"be an object.")
.arg(groupName)
.toUtf8();
continue;
}
QJsonObject groupObj = iter.value().toObject();
if (!groupObj.value("instances").isArray())
{
qWarning() << QString("Group '%1' in the group list is invalid. "
"It should contain an array "
"called 'instances'.")
.arg(groupName)
.toUtf8();
continue;
}
// keep a list/set of groups for choosing
groupSet.insert(groupName);
// Iterate through the list of instances in the group.
QJsonArray instancesArray = groupObj.value("instances").toArray();
for (QJsonArray::iterator iter2 = instancesArray.begin(); iter2 != instancesArray.end();
iter2++)
{
groupMap[(*iter2).toString()] = groupName;
}
}
m_groupsLoaded = true;
emit groupsChanged(groupSet);
}
void FolderInstanceProvider::groupChanged()
{
// save the groups. save all of them.
auto instance = (BaseInstance *) QObject::sender();
auto id = instance->id();
groupMap[id] = instance->group();
emit groupsChanged({instance->group()});
saveGroupList();
}
void FolderInstanceProvider::instanceDirContentsChanged(const QString& path)
{
Q_UNUSED(path);
emit instancesChanged();
}
void FolderInstanceProvider::on_InstFolderChanged(const Setting &setting, QVariant value)
{
QString newInstDir = value.toString();
if(newInstDir != m_instDir)
{
if(m_groupsLoaded)
{
saveGroupList();
}
m_instDir = newInstDir;
m_groupsLoaded = false;
emit instancesChanged();
}
}
QString FolderInstanceProvider::getStagedInstancePath()
{
QString key = QUuid::createUuid().toString();
QString relPath = FS::PathCombine("_MMC_TEMP/" , key);
QDir rootPath(m_instDir);
auto path = FS::PathCombine(m_instDir, relPath);
if(!rootPath.mkpath(relPath))
{
return QString();
}
return path;
}
bool FolderInstanceProvider::commitStagedInstance(const QString& keyPath, const QString& path, const QString& instanceName,
const QString& groupName)
{
if(!path.contains(keyPath))
{
qWarning() << "It is not possible to commit" << path << "because it is not in" << keyPath;
return false;
}
QDir dir;
QString instID = FS::DirNameFromString(instanceName, m_instDir);
{
WatchLock lock(m_watcher, m_instDir);
if(!dir.rename(path, FS::PathCombine(m_instDir, instID)))
{
destroyStagingPath(keyPath);
return false;
}
groupMap[instID] = groupName;
emit groupsChanged({groupName});
emit instancesChanged();
}
saveGroupList();
return destroyStagingPath(keyPath);
}
bool FolderInstanceProvider::destroyStagingPath(const QString& keyPath)
{
return FS::deletePath(keyPath);
}

View File

@ -0,0 +1,63 @@
#pragma once
#include "BaseInstanceProvider.h"
#include <QMap>
class QFileSystemWatcher;
class MULTIMC_LOGIC_EXPORT FolderInstanceProvider : public BaseInstanceProvider
{
Q_OBJECT
public:
FolderInstanceProvider(SettingsObjectPtr settings, const QString & instDir);
public:
/// used by InstanceList to @return a list of plausible IDs to probe for
QList<InstanceId> discoverInstances() override;
/// used by InstanceList to (re)load an instance with the given @id.
InstancePtr loadInstance(const InstanceId& id) override;
// create instance in this provider
Task * creationTask(BaseVersionPtr version, const QString &instName, const QString &instGroup, const QString &instIcon);
// copy instance to this provider
Task * copyTask(const InstancePtr &oldInstance, const QString& instName, const QString& instGroup, const QString& instIcon, bool copySaves);
// import zipped instance into this provider
Task * zipImportTask(const QUrl sourceUrl, const QString &instName, const QString &instGroup, const QString &instIcon);
/**
* Create a new empty staging area for instance creation and @return a path/key top commit it later.
* Used by instance manipulation tasks.
*/
QString getStagedInstancePath() override;
/**
* Commit the staging area given by @keyPath to the provider - used when creation succeeds.
* Used by instance manipulation tasks.
*/
bool commitStagedInstance(const QString & keyPath, const QString & path, const QString& instanceName, const QString & groupName) override;
/**
* Destroy a previously created staging area given by @keyPath - used when creation fails.
* Used by instance manipulation tasks.
*/
bool destroyStagingPath(const QString & keyPath) override;
public slots:
void on_InstFolderChanged(const Setting &setting, QVariant value);
private slots:
void instanceDirContentsChanged(const QString &path);
void groupChanged();
private: /* methods */
void loadGroupList() override;
void saveGroupList() override;
private: /* data */
QString m_instDir;
QFileSystemWatcher * m_watcher;
QMap<QString, QString> groupMap;
bool m_groupsLoaded = false;
};

View File

@ -0,0 +1,53 @@
#include "InstanceCopyTask.h"
#include "BaseInstanceProvider.h"
#include "settings/INISettingsObject.h"
#include "FileSystem.h"
#include "NullInstance.h"
#include "pathmatcher/RegexpMatcher.h"
InstanceCopyTask::InstanceCopyTask(SettingsObjectPtr settings, BaseInstanceProvider* target, InstancePtr origInstance, const QString& instName, const QString& instIcon, const QString& instGroup, bool copySaves)
{
m_globalSettings = settings;
m_target = target;
m_origInstance = origInstance;
m_instName = instName;
m_instIcon = instIcon;
m_instGroup = instGroup;
m_copySaves = copySaves;
}
void InstanceCopyTask::executeTask()
{
setStatus(tr("Copying instance %1").arg(m_origInstance->name()));
std::unique_ptr<IPathMatcher> matcher;
if(!m_copySaves)
{
// FIXME: get this from the original instance type...
auto matcherReal = new RegexpMatcher("[.]?minecraft/saves");
matcherReal->caseSensitive(false);
matcher.reset(matcherReal);
}
QString stagingPath = m_target->getStagedInstancePath();
FS::copy folderCopy(m_origInstance->instanceRoot(), stagingPath);
folderCopy.followSymlinks(false).blacklist(matcher.get());
if (!folderCopy())
{
m_target->destroyStagingPath(stagingPath);
emitFailed(tr("Instance folder copy failed."));
return;
}
// FIXME: shouldn't this be able to report errors?
auto instanceSettings = std::make_shared<INISettingsObject>(FS::PathCombine(stagingPath, "instance.cfg"));
instanceSettings->registerSetting("InstanceType", "Legacy");
// FIXME: and this too? errors???
m_origInstance->copy(stagingPath);
InstancePtr inst(new NullInstance(m_globalSettings, instanceSettings, stagingPath));
inst->setName(m_instName);
inst->setIconKey(m_instIcon);
m_target->commitStagedInstance(stagingPath, stagingPath, m_instName, m_instGroup);
emitSucceeded();
}

View File

@ -0,0 +1,34 @@
#pragma once
#include "tasks/Task.h"
#include "multimc_logic_export.h"
#include "net/NetJob.h"
#include <QUrl>
#include "settings/SettingsObject.h"
#include "BaseVersion.h"
#include "BaseInstance.h"
class BaseInstanceProvider;
class MULTIMC_LOGIC_EXPORT InstanceCopyTask : public Task
{
Q_OBJECT
public:
explicit InstanceCopyTask(SettingsObjectPtr settings, BaseInstanceProvider * target, InstancePtr origInstance, const QString &instName,
const QString &instIcon, const QString &instGroup, bool copySaves);
protected:
//! Entry point for tasks.
virtual void executeTask() override;
private: /* data */
SettingsObjectPtr m_globalSettings;
BaseInstanceProvider * m_target = nullptr;
InstancePtr m_origInstance;
QString m_instName;
QString m_instIcon;
QString m_instGroup;
bool m_copySaves = false;
};

View File

@ -0,0 +1,46 @@
#include "InstanceCreationTask.h"
#include "BaseInstanceProvider.h"
#include "settings/INISettingsObject.h"
#include "FileSystem.h"
//FIXME: remove this
#include "minecraft/MinecraftVersion.h"
#include "minecraft/onesix/OneSixInstance.h"
InstanceCreationTask::InstanceCreationTask(SettingsObjectPtr settings, BaseInstanceProvider* target, BaseVersionPtr version,
const QString& instName, const QString& instIcon, const QString& instGroup)
{
m_globalSettings = settings;
m_target = target;
m_instName = instName;
m_instIcon = instIcon;
m_instGroup = instGroup;
m_version = version;
}
void InstanceCreationTask::executeTask()
{
setStatus(tr("Creating instance from version %1").arg(m_version->name()));
auto minecraftVersion = std::dynamic_pointer_cast<MinecraftVersion>(m_version);
if(!minecraftVersion)
{
emitFailed(tr("The supplied version is not a Minecraft version."));
return ;
}
QString stagingPath = m_target->getStagedInstancePath();
QDir rootDir(stagingPath);
auto instanceSettings = std::make_shared<INISettingsObject>(FS::PathCombine(stagingPath, "instance.cfg"));
instanceSettings->registerSetting("InstanceType", "Legacy");
auto mcVer = std::dynamic_pointer_cast<MinecraftVersion>(m_version);
instanceSettings->set("InstanceType", "OneSix");
InstancePtr inst(new OneSixInstance(m_globalSettings, instanceSettings, stagingPath));
inst->setIntendedVersionId(m_version->descriptor());
inst->setName(m_instName);
inst->setIconKey(m_instIcon);
inst->init();
m_target->commitStagedInstance(stagingPath, stagingPath, m_instName, m_instGroup);
emitSucceeded();
}

View File

@ -0,0 +1,31 @@
#pragma once
#include "tasks/Task.h"
#include "multimc_logic_export.h"
#include "net/NetJob.h"
#include <QUrl>
#include "settings/SettingsObject.h"
#include "BaseVersion.h"
class BaseInstanceProvider;
class MULTIMC_LOGIC_EXPORT InstanceCreationTask : public Task
{
Q_OBJECT
public:
explicit InstanceCreationTask(SettingsObjectPtr settings, BaseInstanceProvider * target, BaseVersionPtr version, const QString &instName,
const QString &instIcon, const QString &instGroup);
protected:
//! Entry point for tasks.
virtual void executeTask() override;
private: /* data */
SettingsObjectPtr m_globalSettings;
BaseInstanceProvider * m_target;
BaseVersionPtr m_version;
QString m_instName;
QString m_instIcon;
QString m_instGroup;
};

View File

@ -0,0 +1,146 @@
#include "InstanceImportTask.h"
#include "BaseInstance.h"
#include "BaseInstanceProvider.h"
#include "FileSystem.h"
#include "Env.h"
#include "MMCZip.h"
#include "NullInstance.h"
#include "settings/INISettingsObject.h"
#include "icons/IIconList.h"
InstanceImportTask::InstanceImportTask(SettingsObjectPtr settings, const QUrl sourceUrl, BaseInstanceProvider * target,
const QString &instName, const QString &instIcon, const QString &instGroup)
{
m_globalSettings = settings;
m_sourceUrl = sourceUrl;
m_target = target;
m_instName = instName;
m_instIcon = instIcon;
m_instGroup = instGroup;
}
void InstanceImportTask::executeTask()
{
InstancePtr newInstance;
if (m_sourceUrl.isLocalFile())
{
m_archivePath = m_sourceUrl.toLocalFile();
extractAndTweak();
}
else
{
setStatus(tr("Downloading modpack:\n%1").arg(m_sourceUrl.toString()));
m_downloadRequired = true;
const QString path = m_sourceUrl.host() + '/' + m_sourceUrl.path();
auto entry = ENV.metacache()->resolveEntry("general", path);
entry->setStale(true);
m_filesNetJob.reset(new NetJob(tr("Modpack download")));
m_filesNetJob->addNetAction(Net::Download::makeCached(m_sourceUrl, entry));
m_archivePath = entry->getFullPath();
auto job = m_filesNetJob.get();
connect(job, &NetJob::succeeded, this, &InstanceImportTask::downloadSucceeded);
connect(job, &NetJob::progress, this, &InstanceImportTask::downloadProgressChanged);
connect(job, &NetJob::failed, this, &InstanceImportTask::downloadFailed);
m_filesNetJob->start();
}
}
void InstanceImportTask::downloadSucceeded()
{
extractAndTweak();
}
void InstanceImportTask::downloadFailed(QString reason)
{
emitFailed(reason);
}
void InstanceImportTask::downloadProgressChanged(qint64 current, qint64 total)
{
setProgress(current / 2, total);
}
static QFileInfo findRecursive(const QString &dir, const QString &name)
{
for (const auto info : QDir(dir).entryInfoList(QDir::NoDotAndDotDot | QDir::Dirs | QDir::Files, QDir::DirsLast))
{
if (info.isFile() && info.fileName() == name)
{
return info;
}
else if (info.isDir())
{
const QFileInfo res = findRecursive(info.absoluteFilePath(), name);
if (res.isFile() && res.exists())
{
return res;
}
}
}
return QFileInfo();
}
void InstanceImportTask::extractAndTweak()
{
setStatus(tr("Extracting modpack"));
QString stagingPath = m_target->getStagedInstancePath();
QDir extractDir(stagingPath);
qDebug() << "Attempting to create instance from" << m_archivePath;
if (MMCZip::extractDir(m_archivePath, extractDir.absolutePath()).isEmpty())
{
m_target->destroyStagingPath(stagingPath);
emitFailed(tr("Failed to extract modpack"));
return;
}
const QFileInfo instanceCfgFile = findRecursive(extractDir.absolutePath(), "instance.cfg");
if (!instanceCfgFile.isFile() || !instanceCfgFile.exists())
{
m_target->destroyStagingPath(stagingPath);
emitFailed(tr("Archive does not contain instance.cfg"));
return;
}
// FIXME: copy from FolderInstanceProvider!!! FIX IT!!!
auto instanceSettings = std::make_shared<INISettingsObject>(instanceCfgFile.absoluteFilePath());
instanceSettings->registerSetting("InstanceType", "Legacy");
QString actualDir = instanceCfgFile.absolutePath();
NullInstance instance(m_globalSettings, instanceSettings, actualDir);
// reset time played on import... because packs.
instance.resetTimePlayed();
// set a new nice name
instance.setName(m_instName);
// if the icon was specified by user, use that. otherwise pull icon from the pack
if (m_instIcon != "default")
{
instance.setIconKey(m_instIcon);
}
else
{
m_instIcon = instance.iconKey();
auto importIconPath = FS::PathCombine(instance.instanceRoot(), m_instIcon + ".png");
if (QFile::exists(importIconPath))
{
// import icon
auto iconList = ENV.icons();
if (iconList->iconFileExists(m_instIcon))
{
iconList->deleteIcon(m_instIcon);
}
iconList->installIcons({importIconPath});
}
}
if (!m_target->commitStagedInstance(stagingPath, actualDir, m_instName, m_instGroup))
{
m_target->destroyStagingPath(stagingPath);
emitFailed(tr("Unable to commit instance"));
return;
}
emitSucceeded();
}

View File

@ -0,0 +1,40 @@
#pragma once
#include "tasks/Task.h"
#include "multimc_logic_export.h"
#include "net/NetJob.h"
#include <QUrl>
#include "settings/SettingsObject.h"
class BaseInstanceProvider;
class MULTIMC_LOGIC_EXPORT InstanceImportTask : public Task
{
Q_OBJECT
public:
explicit InstanceImportTask(SettingsObjectPtr settings, const QUrl sourceUrl, BaseInstanceProvider * target, const QString &instName,
const QString &instIcon, const QString &instGroup);
protected:
//! Entry point for tasks.
virtual void executeTask() override;
private:
void extractAndTweak();
private slots:
void downloadSucceeded();
void downloadFailed(QString reason);
void downloadProgressChanged(qint64 current, qint64 total);
private: /* data */
SettingsObjectPtr m_globalSettings;
NetJobPtr m_filesNetJob;
QUrl m_sourceUrl;
BaseInstanceProvider * m_target;
QString m_archivePath;
bool m_downloadRequired = false;
QString m_instName;
QString m_instIcon;
QString m_instGroup;
};

View File

@ -16,39 +16,21 @@
#include <QDir>
#include <QSet>
#include <QFile>
#include <QDirIterator>
#include <QThread>
#include <QTextStream>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QXmlStreamReader>
#include <QRegularExpression>
#include <QDebug>
#include "InstanceList.h"
#include "BaseInstance.h"
//FIXME: this really doesn't belong *here*
#include "minecraft/onesix/OneSixInstance.h"
#include "minecraft/legacy/LegacyInstance.h"
#include "minecraft/ftb/FTBPlugin.h"
#include "minecraft/MinecraftVersion.h"
#include "settings/INISettingsObject.h"
#include "NullInstance.h"
#include "FileSystem.h"
#include "pathmatcher/RegexpMatcher.h"
const static int GROUP_FILE_FORMAT_VERSION = 1;
#include "FolderInstanceProvider.h"
InstanceList::InstanceList(SettingsObjectPtr globalSettings, const QString &instDir, QObject *parent)
: QAbstractListModel(parent), m_instDir(instDir)
{
m_globalSettings = globalSettings;
if (!QDir::current().exists(m_instDir))
{
QDir::current().mkpath(m_instDir);
}
resumeWatch();
}
InstanceList::~InstanceList()
@ -120,34 +102,11 @@ Qt::ItemFlags InstanceList::flags(const QModelIndex &index) const
return f;
}
void InstanceList::groupChanged()
{
// save the groups. save all of them.
saveGroupList();
}
QStringList InstanceList::getGroups()
{
return m_groups.toList();
}
void InstanceList::suspendGroupSaving()
{
suspendedGroupSave = true;
}
void InstanceList::resumeGroupSaving()
{
if(suspendedGroupSave)
{
suspendedGroupSave = false;
if(queuedGroupSave)
{
saveGroupList();
}
}
}
void InstanceList::deleteGroup(const QString& name)
{
for(auto & instance: m_instances)
@ -160,230 +119,180 @@ void InstanceList::deleteGroup(const QString& name)
}
}
void InstanceList::saveGroupList()
static QMap<InstanceId, InstanceLocator> getIdMapping(const QList<InstancePtr> &list)
{
if(suspendedGroupSave)
QMap<InstanceId, InstanceLocator> out;
int i = 0;
for(auto & item: list)
{
queuedGroupSave = true;
return;
}
QString groupFileName = m_instDir + "/instgroups.json";
QMap<QString, QSet<QString>> groupMap;
for (auto instance : m_instances)
{
QString id = instance->id();
QString group = instance->group();
if (group.isEmpty())
continue;
// keep a list/set of groups for choosing
m_groups.insert(group);
if (!groupMap.count(group))
auto id = item->id();
if(out.contains(id))
{
QSet<QString> set;
set.insert(id);
groupMap[group] = set;
}
else
{
QSet<QString> &set = groupMap[group];
set.insert(id);
qWarning() << "Duplicate ID" << id << "in instance list";
}
out[id] = std::make_pair(item, i);
i++;
}
QJsonObject toplevel;
toplevel.insert("formatVersion", QJsonValue(QString("1")));
QJsonObject groupsArr;
for (auto iter = groupMap.begin(); iter != groupMap.end(); iter++)
{
auto list = iter.value();
auto name = iter.key();
QJsonObject groupObj;
QJsonArray instanceArr;
groupObj.insert("hidden", QJsonValue(QString("false")));
for (auto item : list)
{
instanceArr.append(QJsonValue(item));
}
groupObj.insert("instances", instanceArr);
groupsArr.insert(name, groupObj);
}
toplevel.insert("groups", groupsArr);
QJsonDocument doc(toplevel);
try
{
FS::write(groupFileName, doc.toJson());
}
catch(FS::FileSystemException & e)
{
qCritical() << "Failed to write instance group file :" << e.cause();
}
return out;
}
void InstanceList::loadGroupList(QMap<QString, QString> &groupMap)
InstanceList::InstListError InstanceList::loadList(bool complete)
{
QString groupFileName = m_instDir + "/instgroups.json";
auto existingIds = getIdMapping(m_instances);
// if there's no group file, fail
if (!QFileInfo(groupFileName).exists())
return;
QList<InstancePtr> newList;
QByteArray jsonData;
try
auto processIds = [&](BaseInstanceProvider * provider, QList<InstanceId> ids)
{
jsonData = FS::read(groupFileName);
}
catch (FS::FileSystemException & e)
{
qCritical() << "Failed to read instance group file :" << e.cause();
return;
}
QJsonParseError error;
QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData, &error);
// if the json was bad, fail
if (error.error != QJsonParseError::NoError)
{
qCritical() << QString("Failed to parse instance group file: %1 at offset %2")
.arg(error.errorString(), QString::number(error.offset))
.toUtf8();
return;
}
// if the root of the json wasn't an object, fail
if (!jsonDoc.isObject())
{
qWarning() << "Invalid group file. Root entry should be an object.";
return;
}
QJsonObject rootObj = jsonDoc.object();
// Make sure the format version matches, otherwise fail.
if (rootObj.value("formatVersion").toVariant().toInt() != GROUP_FILE_FORMAT_VERSION)
return;
// Get the groups. if it's not an object, fail
if (!rootObj.value("groups").isObject())
{
qWarning() << "Invalid group list JSON: 'groups' should be an object.";
return;
}
// Iterate through all the groups.
QJsonObject groupMapping = rootObj.value("groups").toObject();
for (QJsonObject::iterator iter = groupMapping.begin(); iter != groupMapping.end(); iter++)
{
QString groupName = iter.key();
// If not an object, complain and skip to the next one.
if (!iter.value().isObject())
for(auto & id: ids)
{
qWarning() << QString("Group '%1' in the group list should "
"be an object.")
.arg(groupName)
.toUtf8();
continue;
if(existingIds.contains(id))
{
auto instPair = existingIds[id];
/*
auto & instPtr = instPair.first;
auto & instIdx = instPair.second;
*/
existingIds.remove(id);
qDebug() << "Should keep and soft-reload" << id;
}
else
{
InstancePtr instPtr = provider->loadInstance(id);
newList.append(instPtr);
}
}
QJsonObject groupObj = iter.value().toObject();
if (!groupObj.value("instances").isArray())
};
if(complete)
{
for(auto & item: m_providers)
{
qWarning() << QString("Group '%1' in the group list is invalid. "
"It should contain an array "
"called 'instances'.")
.arg(groupName)
.toUtf8();
continue;
}
// keep a list/set of groups for choosing
m_groups.insert(groupName);
// Iterate through the list of instances in the group.
QJsonArray instancesArray = groupObj.value("instances").toArray();
for (QJsonArray::iterator iter2 = instancesArray.begin(); iter2 != instancesArray.end();
iter2++)
{
groupMap[(*iter2).toString()] = groupName;
processIds(item.get(), item->discoverInstances());
}
}
}
InstanceList::InstListError InstanceList::loadList()
{
// load the instance groups
QMap<QString, QString> groupMap;
loadGroupList(groupMap);
QList<InstancePtr> tempList;
else
{
QDirIterator iter(m_instDir, QDir::Dirs | QDir::NoDot | QDir::NoDotDot | QDir::Readable,
QDirIterator::FollowSymlinks);
while (iter.hasNext())
for (auto & item: m_updatedProviders)
{
QString subDir = iter.next();
if (!QFileInfo(FS::PathCombine(subDir, "instance.cfg")).exists())
processIds(item, item->discoverInstances());
}
}
// TODO: looks like a general algorithm with a few specifics inserted. Do something about it.
if(!existingIds.isEmpty())
{
// 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);
// remove the contiguous ranges of rows
int front_bookmark = -1;
int back_bookmark = -1;
int currentItem = -1;
auto removeNow = [&]()
{
beginRemoveRows(QModelIndex(), front_bookmark, back_bookmark);
m_instances.erase(m_instances.begin() + front_bookmark, m_instances.begin() + back_bookmark + 1);
endRemoveRows();
front_bookmark = -1;
back_bookmark = currentItem;
};
for(auto & removedItem: deadList)
{
auto instPtr = removedItem.first;
if(!complete && !m_updatedProviders.contains(instPtr->provider()))
{
continue;
qDebug() << "Loading MultiMC instance from " << subDir;
InstancePtr instPtr;
auto error = loadInstance(instPtr, subDir);
if(!continueProcessInstance(instPtr, error, subDir, groupMap))
continue;
tempList.append(instPtr);
}
instPtr->invalidate();
currentItem = removedItem.second;
if(back_bookmark == -1)
{
// no bookmark yet
back_bookmark = currentItem;
}
else if(currentItem == front_bookmark - 1)
{
// part of contiguous sequence, continue
}
else
{
// seam between previous and current item
removeNow();
}
front_bookmark = currentItem;
}
if(back_bookmark != -1)
{
removeNow();
}
}
// FIXME: generalize
FTBPlugin::loadInstances(m_globalSettings, groupMap, tempList);
beginResetModel();
m_instances.clear();
for(auto inst: tempList)
if(newList.size())
{
connect(inst.get(), SIGNAL(propertiesChanged(BaseInstance *)), this,
SLOT(propertiesChanged(BaseInstance *)));
connect(inst.get(), SIGNAL(groupChanged()), this, SLOT(groupChanged()));
connect(inst.get(), SIGNAL(nuked(BaseInstance *)), this,
SLOT(instanceNuked(BaseInstance *)));
m_instances.append(inst);
add(newList);
}
endResetModel();
emit dataIsInvalid();
m_updatedProviders.clear();
return NoError;
}
/// Clear all instances. Triggers notifications.
void InstanceList::clear()
void InstanceList::add(const QList<InstancePtr> &t)
{
beginResetModel();
saveGroupList();
m_instances.clear();
endResetModel();
emit dataIsInvalid();
}
void InstanceList::on_InstFolderChanged(const Setting &setting, QVariant value)
{
m_instDir = value.toString();
loadList();
}
/// Add an instance. Triggers notifications, returns the new index
int InstanceList::add(InstancePtr t)
{
beginInsertRows(QModelIndex(), m_instances.size(), m_instances.size());
beginInsertRows(QModelIndex(), m_instances.count(), m_instances.count() + t.size() - 1);
m_instances.append(t);
t->setParent(this);
connect(t.get(), SIGNAL(propertiesChanged(BaseInstance *)), this,
SLOT(propertiesChanged(BaseInstance *)));
connect(t.get(), SIGNAL(groupChanged()), this, SLOT(groupChanged()));
connect(t.get(), SIGNAL(nuked(BaseInstance *)), this, SLOT(instanceNuked(BaseInstance *)));
for(auto & ptr : t)
{
connect(ptr.get(), &BaseInstance::propertiesChanged, this, &InstanceList::propertiesChanged);
}
endInsertRows();
return count() - 1;
}
void InstanceList::resumeWatch()
{
if(m_watchLevel > 0)
{
qWarning() << "Bad suspend level resume in instance list";
return;
}
m_watchLevel++;
if(m_watchLevel > 0 && !m_updatedProviders.isEmpty())
{
loadList();
}
}
void InstanceList::suspendWatch()
{
m_watchLevel --;
}
void InstanceList::providerUpdated()
{
auto provider = dynamic_cast<BaseInstanceProvider *>(QObject::sender());
if(!provider)
{
qWarning() << "InstanceList::providerUpdated triggered by a non-provider";
return;
}
m_updatedProviders.insert(provider);
if(m_watchLevel == 1)
{
loadList();
}
}
void InstanceList::groupsPublished(QSet<QString> newGroups)
{
m_groups.unite(newGroups);
}
void InstanceList::addInstanceProvider(BaseInstanceProvider* provider)
{
connect(provider, &BaseInstanceProvider::instancesChanged, this, &InstanceList::providerUpdated);
connect(provider, &BaseInstanceProvider::groupsChanged, this, &InstanceList::groupsPublished);
m_providers.append(provider);
}
InstancePtr InstanceList::getInstanceById(QString instId) const
@ -418,157 +327,6 @@ int InstanceList::getInstIndex(BaseInstance *inst) const
return -1;
}
bool InstanceList::continueProcessInstance(InstancePtr instPtr, const int error,
const QDir &dir, QMap<QString, QString> &groupMap)
{
if (error != InstanceList::NoLoadError && error != InstanceList::NotAnInstance)
{
QString errorMsg = QString("Failed to load instance %1: ")
.arg(QFileInfo(dir.absolutePath()).baseName())
.toUtf8();
switch (error)
{
default:
errorMsg += QString("Unknown instance loader error %1").arg(error);
break;
}
qCritical() << errorMsg.toUtf8();
return false;
}
else if (!instPtr)
{
qCritical() << QString("Error loading instance %1. Instance loader returned null.")
.arg(QFileInfo(dir.absolutePath()).baseName())
.toUtf8();
return false;
}
else
{
auto iter = groupMap.find(instPtr->id());
if (iter != groupMap.end())
{
instPtr->setGroupInitial((*iter));
}
qDebug() << "Loaded instance " << instPtr->name() << " from " << dir.absolutePath();
return true;
}
}
InstanceList::InstLoadError
InstanceList::loadInstance(InstancePtr &inst, const QString &instDir)
{
auto instanceSettings = std::make_shared<INISettingsObject>(FS::PathCombine(instDir, "instance.cfg"));
instanceSettings->registerSetting("InstanceType", "Legacy");
QString inst_type = instanceSettings->get("InstanceType").toString();
// FIXME: replace with a map lookup, where instance classes register their types
if (inst_type == "OneSix" || inst_type == "Nostalgia")
{
inst.reset(new OneSixInstance(m_globalSettings, instanceSettings, instDir));
}
else if (inst_type == "Legacy")
{
inst.reset(new LegacyInstance(m_globalSettings, instanceSettings, instDir));
}
else
{
inst.reset(new NullInstance(m_globalSettings, instanceSettings, instDir));
}
inst->init();
return NoLoadError;
}
InstanceList::InstCreateError
InstanceList::createInstance(InstancePtr &inst, BaseVersionPtr version, const QString &instDir)
{
QDir rootDir(instDir);
qDebug() << instDir.toUtf8();
if (!rootDir.exists() && !rootDir.mkpath("."))
{
qCritical() << "Can't create instance folder" << instDir;
return InstanceList::CantCreateDir;
}
if (!version)
{
qCritical() << "Can't create instance for non-existing MC version";
return InstanceList::NoSuchVersion;
}
auto instanceSettings = std::make_shared<INISettingsObject>(FS::PathCombine(instDir, "instance.cfg"));
instanceSettings->registerSetting("InstanceType", "Legacy");
auto minecraftVersion = std::dynamic_pointer_cast<MinecraftVersion>(version);
if(minecraftVersion)
{
auto mcVer = std::dynamic_pointer_cast<MinecraftVersion>(version);
instanceSettings->set("InstanceType", "OneSix");
inst.reset(new OneSixInstance(m_globalSettings, instanceSettings, instDir));
inst->setIntendedVersionId(version->descriptor());
inst->init();
return InstanceList::NoCreateError;
}
return InstanceList::NoSuchVersion;
}
InstanceList::InstCreateError
InstanceList::copyInstance(InstancePtr &newInstance, InstancePtr &oldInstance, const QString &instDir, bool copySaves)
{
QDir rootDir(instDir);
std::unique_ptr<IPathMatcher> matcher;
if(!copySaves)
{
auto matcherReal = new RegexpMatcher("[.]?minecraft/saves");
matcherReal->caseSensitive(false);
matcher.reset(matcherReal);
}
qDebug() << instDir.toUtf8();
FS::copy folderCopy(oldInstance->instanceRoot(), instDir);
folderCopy.followSymlinks(false).blacklist(matcher.get());
if (!folderCopy())
{
FS::deletePath(instDir);
return InstanceList::CantCreateDir;
}
INISettingsObject settings_obj(FS::PathCombine(instDir, "instance.cfg"));
settings_obj.registerSetting("InstanceType", "Legacy");
QString inst_type = settings_obj.get("InstanceType").toString();
oldInstance->copy(instDir);
auto error = loadInstance(newInstance, instDir);
switch (error)
{
case NoLoadError:
return NoCreateError;
case NotAnInstance:
rootDir.removeRecursively();
return CantCreateDir;
default:
case UnknownLoadError:
rootDir.removeRecursively();
return UnknownCreateError;
}
}
void InstanceList::instanceNuked(BaseInstance *inst)
{
int i = getInstIndex(inst);
if (i != -1)
{
beginRemoveRows(QModelIndex(), i, i);
m_instances.removeAt(i);
endRemoveRows();
}
}
void InstanceList::propertiesChanged(BaseInstance *inst)
{
int i = getInstIndex(inst);
@ -577,3 +335,5 @@ void InstanceList::propertiesChanged(BaseInstance *inst)
emit dataChanged(index(i), index(i));
}
}
#include "InstanceList.moc"

View File

@ -18,24 +18,22 @@
#include <QObject>
#include <QAbstractListModel>
#include <QSet>
#include <QList>
#include "BaseInstance.h"
#include "BaseInstanceProvider.h"
#include "multimc_logic_export.h"
#include "QObjectPtr.h"
class QFileSystemWatcher;
class BaseInstance;
class QDir;
class MULTIMC_LOGIC_EXPORT InstanceList : public QAbstractListModel
{
Q_OBJECT
private:
void loadGroupList(QMap<QString, QString> &groupList);
void suspendGroupSaving();
void resumeGroupSaving();
public slots:
void saveGroupList();
public:
explicit InstanceList(SettingsObjectPtr globalSettings, const QString &instDir, QObject *parent = 0);
@ -64,124 +62,47 @@ public:
UnknownError
};
enum InstLoadError
{
NoLoadError = 0,
UnknownLoadError,
NotAnInstance
};
enum InstCreateError
{
NoCreateError = 0,
NoSuchVersion,
UnknownCreateError,
InstExists,
CantCreateDir
};
QString instDir() const
{
return m_instDir;
}
/*!
* \brief Get the instance at index
*/
InstancePtr at(int i) const
{
return m_instances.at(i);
}
;
/*!
* \brief Get the count of loaded instances
*/
int count() const
{
return m_instances.count();
}
;
/// Clear all instances. Triggers notifications.
void clear();
InstListError loadList(bool complete = false);
/// Add an instance. Triggers notifications, returns the new index
int add(InstancePtr t);
/// Add an instance provider. Takes ownership of it. Should only be done before the first load.
void addInstanceProvider(BaseInstanceProvider * provider);
/// Get an instance by ID
InstancePtr getInstanceById(QString id) const;
QModelIndex getInstanceIndexById(const QString &id) const;
// FIXME: instead of iterating through all instances and forming a set, keep the set around
QStringList getGroups();
void deleteGroup(const QString & name);
/*!
* \brief Creates a stub instance
*
* \param inst Pointer to store the created instance in.
* \param version Game version to use for the instance
* \param instDir The new instance's directory.
* \return An InstCreateError error code.
* - InstExists if the given instance directory is already an instance.
* - CantCreateDir if the given instance directory cannot be created.
*/
InstCreateError createInstance(InstancePtr &inst, BaseVersionPtr version,
const QString &instDir);
/*!
* \brief Creates a copy of an existing instance with a new name
*
* \param newInstance Pointer to store the created instance in.
* \param oldInstance The instance to copy
* \param instDir The new instance's directory.
* \return An InstCreateError error code.
* - InstExists if the given instance directory is already an instance.
* - CantCreateDir if the given instance directory cannot be created.
*/
InstCreateError copyInstance(InstancePtr &newInstance, InstancePtr &oldInstance,
const QString &instDir, bool copySaves);
/*!
* \brief Loads an instance from the given directory.
* Checks the instance's INI file to figure out what the instance's type is first.
* \param inst Pointer to store the loaded instance in.
* \param instDir The instance's directory.
* \return An InstLoadError error code.
* - NotAnInstance if the given instance directory isn't a valid instance.
*/
InstLoadError loadInstance(InstancePtr &inst, const QString &instDir);
signals:
void dataIsInvalid();
public slots:
void on_InstFolderChanged(const Setting &setting, QVariant value);
/*!
* \brief Loads the instance list. Triggers notifications.
*/
InstListError loadList();
private slots:
void propertiesChanged(BaseInstance *inst);
void instanceNuked(BaseInstance *inst);
void groupChanged();
void groupsPublished(QSet<QString>);
void providerUpdated();
private:
int getInstIndex(BaseInstance *inst) const;
public:
static bool continueProcessInstance(InstancePtr instPtr, const int error, const QDir &dir, QMap<QString, QString> &groupMap);
void suspendWatch();
void resumeWatch();
void add(const QList<InstancePtr> &list);
protected:
int m_watchLevel = 0;
QSet<BaseInstanceProvider *> m_updatedProviders;
QString m_instDir;
QList<InstancePtr> m_instances;
QSet<QString> m_groups;
SettingsObjectPtr m_globalSettings;
bool suspendedGroupSave = false;
bool queuedGroupSave = false;
QVector<shared_qobject_ptr<BaseInstanceProvider>> m_providers;
};

View File

@ -1,6 +1,7 @@
#pragma once
#include <QString>
#include <QStringList>
#include "multimc_logic_export.h"
enum IconType : unsigned
@ -16,6 +17,9 @@ class MULTIMC_LOGIC_EXPORT IIconList
{
public:
virtual ~IIconList();
virtual bool addIcon(QString key, QString name, QString path, IconType type) = 0;
virtual bool addIcon(const QString &key, const QString &name, const QString &path, const IconType type) = 0;
virtual bool deleteIcon(const QString &key) = 0;
virtual void saveIcon(const QString &key, const QString &path, const char * format) const = 0;
virtual bool iconFileExists(const QString &key) const = 0;
virtual void installIcons(const QStringList &iconFiles) = 0;
};

View File

@ -0,0 +1,278 @@
#include "FTBInstanceProvider.h"
#include <QDir>
#include <QDebug>
#include <QXmlStreamReader>
#include <QRegularExpression>
#include <settings/INISettingsObject.h>
#include <FileSystem.h>
#include "Env.h"
#include "minecraft/MinecraftVersion.h"
#include "LegacyFTBInstance.h"
#include "OneSixFTBInstance.h"
inline uint qHash(FTBRecord record)
{
return qHash(record.instanceDir);
}
FTBInstanceProvider::FTBInstanceProvider(SettingsObjectPtr settings)
: BaseInstanceProvider(settings)
{
// nil
}
QList<InstanceId> FTBInstanceProvider::discoverInstances()
{
// nothing to load when we don't have
if (m_globalSettings->get("TrackFTBInstances").toBool() != true)
{
return {};
}
m_records.clear();
discoverFTBEntries();
return m_records.keys();
}
InstancePtr FTBInstanceProvider::loadInstance(const InstanceId& id)
{
// process the records we acquired.
auto iter = m_records.find(id);
if(iter == m_records.end())
{
qWarning() << "Cannot load instance" << id << "without a record";
return nullptr;
}
auto & record = m_records[id];
qDebug() << "Loading FTB instance from " << record.instanceDir;
QString iconKey = record.iconKey;
auto icons = ENV.icons();
if(icons)
{
icons->addIcon(iconKey, iconKey, FS::PathCombine(record.templateDir, record.logo), IconType::Transient);
}
auto settingsFilePath = FS::PathCombine(record.instanceDir, "instance.cfg");
qDebug() << "ICON get!";
if (QFileInfo(settingsFilePath).exists())
{
auto instPtr = loadInstance(record);
if (!instPtr)
{
qWarning() << "Couldn't load instance config:" << settingsFilePath;
if(!QFile::remove(settingsFilePath))
{
qWarning() << "Couldn't remove broken instance config!";
return nullptr;
}
// failed to load, but removed the poisonous file
}
else
{
return InstancePtr(instPtr);
}
}
auto instPtr = createInstance(record);
if (!instPtr)
{
qWarning() << "Couldn't create FTB instance!";
return nullptr;
}
return InstancePtr(instPtr);
}
void FTBInstanceProvider::discoverFTBEntries()
{
QDir dir = QDir(m_globalSettings->get("FTBLauncherLocal").toString());
QDir dataDir = QDir(m_globalSettings->get("FTBRoot").toString());
if (!dataDir.exists())
{
qDebug() << "The FTB directory specified does not exist. Please check your settings";
return;
}
else if (!dir.exists())
{
qDebug() << "The FTB launcher data directory specified does not exist. Please check "
"your settings";
return;
}
dir.cd("ModPacks");
auto allFiles = dir.entryList(QDir::Readable | QDir::Files, QDir::Name);
for (auto filename : allFiles)
{
if (!filename.endsWith(".xml"))
continue;
auto fpath = dir.absoluteFilePath(filename);
QFile f(fpath);
qDebug() << "Discovering FTB instances -- " << fpath;
if (!f.open(QFile::ReadOnly))
continue;
// read the FTB packs XML.
QXmlStreamReader reader(&f);
while (!reader.atEnd())
{
switch (reader.readNext())
{
case QXmlStreamReader::StartElement:
{
if (reader.name() == "modpack")
{
QXmlStreamAttributes attrs = reader.attributes();
FTBRecord record;
record.dirName = attrs.value("dir").toString();
record.instanceDir = dataDir.absoluteFilePath(record.dirName);
record.templateDir = dir.absoluteFilePath(record.dirName);
QDir test(record.instanceDir);
qDebug() << dataDir.absolutePath() << record.instanceDir << record.dirName;
if (!test.exists())
continue;
record.name = attrs.value("name").toString();
record.logo = attrs.value("logo").toString();
QString logo = record.logo;
record.iconKey = logo.remove(QRegularExpression("\\..*"));
auto customVersions = attrs.value("customMCVersions");
if (!customVersions.isNull())
{
QMap<QString, QString> versionMatcher;
QString customVersionsStr = customVersions.toString();
QStringList list = customVersionsStr.split(';');
for (auto item : list)
{
auto segment = item.split('^');
if (segment.size() != 2)
{
qCritical() << "FTB: Segment of size < 2 in "
<< customVersionsStr;
continue;
}
versionMatcher[segment[0]] = segment[1];
}
auto actualVersion = attrs.value("version").toString();
if (versionMatcher.contains(actualVersion))
{
record.mcVersion = versionMatcher[actualVersion];
}
else
{
record.mcVersion = attrs.value("mcVersion").toString();
}
}
else
{
record.mcVersion = attrs.value("mcVersion").toString();
}
record.description = attrs.value("description").toString();
auto id = "FTB/" + record.dirName;
m_records[id] = record;
}
break;
}
case QXmlStreamReader::EndElement:
break;
case QXmlStreamReader::Characters:
break;
default:
break;
}
}
f.close();
}
}
InstancePtr FTBInstanceProvider::loadInstance(const FTBRecord & record) const
{
InstancePtr inst;
auto m_settings = std::make_shared<INISettingsObject>(FS::PathCombine(record.instanceDir, "instance.cfg"));
m_settings->registerSetting("InstanceType", "Legacy");
qDebug() << "Loading existing " << record.name;
QString inst_type = m_settings->get("InstanceType").toString();
if (inst_type == "LegacyFTB")
{
inst.reset(new LegacyFTBInstance(m_globalSettings, m_settings, record.instanceDir));
}
else if (inst_type == "OneSixFTB")
{
inst.reset(new OneSixFTBInstance(m_globalSettings, m_settings, record.instanceDir));
}
else
{
return nullptr;
}
qDebug() << "Construction " << record.instanceDir;
SettingsObject::Lock lock(inst->settings());
inst->init();
qDebug() << "Init " << record.instanceDir;
inst->setGroupInitial("FTB");
/**
* FIXME: this does not respect the user's preferences. BUT, it would work nicely with the planned pack support
* -> instead of changing the user values, change pack values (defaults you can look at and revert to)
*/
/*
inst->setName(record.name);
inst->setIconKey(record.iconKey);
inst->setNotes(record.description);
*/
if (inst->intendedVersionId() != record.mcVersion)
{
inst->setIntendedVersionId(record.mcVersion);
}
qDebug() << "Loaded instance " << inst->name() << " from " << inst->instanceRoot();
return inst;
}
InstancePtr FTBInstanceProvider::createInstance(const FTBRecord & record) const
{
QDir rootDir(record.instanceDir);
InstancePtr inst;
qDebug() << "Converting " << record.name << " as new.";
auto mcVersion = std::dynamic_pointer_cast<MinecraftVersion>(ENV.getVersion("net.minecraft", record.mcVersion));
if (!mcVersion)
{
qCritical() << "Can't load instance " << record.instanceDir
<< " because minecraft version " << record.mcVersion
<< " can't be resolved.";
return nullptr;
}
if (!rootDir.exists() && !rootDir.mkpath("."))
{
qCritical() << "Can't create instance folder" << record.instanceDir;
return nullptr;
}
auto m_settings = std::make_shared<INISettingsObject>(FS::PathCombine(record.instanceDir, "instance.cfg"));
m_settings->registerSetting("InstanceType", "Legacy");
if (mcVersion->usesLegacyLauncher())
{
m_settings->set("InstanceType", "LegacyFTB");
inst.reset(new LegacyFTBInstance(m_globalSettings, m_settings, record.instanceDir));
}
else
{
m_settings->set("InstanceType", "OneSixFTB");
inst.reset(new OneSixFTBInstance(m_globalSettings, m_settings, record.instanceDir));
}
// initialize
{
SettingsObject::Lock lock(inst->settings());
inst->setIntendedVersionId(mcVersion->descriptor());
inst->init();
inst->setGroupInitial("FTB");
inst->setName(record.name);
inst->setIconKey(record.iconKey);
inst->setNotes(record.description);
}
return inst;
}

View File

@ -0,0 +1,45 @@
#pragma once
#include "BaseInstanceProvider.h"
#include <QMap>
class QFileSystemWatcher;
struct MULTIMC_LOGIC_EXPORT FTBRecord
{
QString dirName;
QString name;
QString logo;
QString iconKey;
QString mcVersion;
QString description;
QString instanceDir;
QString templateDir;
bool operator==(const FTBRecord other) const
{
return instanceDir == other.instanceDir;
}
};
class MULTIMC_LOGIC_EXPORT FTBInstanceProvider : public BaseInstanceProvider
{
Q_OBJECT
public:
FTBInstanceProvider (SettingsObjectPtr settings);
public:
QList<InstanceId> discoverInstances() override;
InstancePtr loadInstance(const InstanceId& id) override;
void loadGroupList() override {};
void saveGroupList() override {};
private: /* methods */
void discoverFTBEntries();
InstancePtr createInstance(const FTBRecord & record) const;
InstancePtr loadInstance(const FTBRecord & record) const;
private:
QMap<InstanceId, FTBRecord> m_records;
};

View File

@ -8,292 +8,10 @@
#include <minecraft/MinecraftVersionList.h>
#include <settings/INISettingsObject.h>
#include <FileSystem.h>
#include "QDebug"
#include <QXmlStreamReader>
#include <QDebug>
#include <QRegularExpression>
struct FTBRecord
{
QString dirName;
QString name;
QString logo;
QString iconKey;
QString mcVersion;
QString description;
QString instanceDir;
QString templateDir;
bool operator==(const FTBRecord other) const
{
return instanceDir == other.instanceDir;
}
};
inline uint qHash(FTBRecord record)
{
return qHash(record.instanceDir);
}
QSet<FTBRecord> discoverFTBInstances(SettingsObjectPtr globalSettings)
{
QSet<FTBRecord> records;
QDir dir = QDir(globalSettings->get("FTBLauncherLocal").toString());
QDir dataDir = QDir(globalSettings->get("FTBRoot").toString());
if (!dataDir.exists())
{
qDebug() << "The FTB directory specified does not exist. Please check your settings";
return records;
}
else if (!dir.exists())
{
qDebug() << "The FTB launcher data directory specified does not exist. Please check "
"your settings";
return records;
}
dir.cd("ModPacks");
auto allFiles = dir.entryList(QDir::Readable | QDir::Files, QDir::Name);
for (auto filename : allFiles)
{
if (!filename.endsWith(".xml"))
continue;
auto fpath = dir.absoluteFilePath(filename);
QFile f(fpath);
qDebug() << "Discovering FTB instances -- " << fpath;
if (!f.open(QFile::ReadOnly))
continue;
// read the FTB packs XML.
QXmlStreamReader reader(&f);
while (!reader.atEnd())
{
switch (reader.readNext())
{
case QXmlStreamReader::StartElement:
{
if (reader.name() == "modpack")
{
QXmlStreamAttributes attrs = reader.attributes();
FTBRecord record;
record.dirName = attrs.value("dir").toString();
record.instanceDir = dataDir.absoluteFilePath(record.dirName);
record.templateDir = dir.absoluteFilePath(record.dirName);
QDir test(record.instanceDir);
qDebug() << dataDir.absolutePath() << record.instanceDir << record.dirName;
if (!test.exists())
continue;
record.name = attrs.value("name").toString();
record.logo = attrs.value("logo").toString();
QString logo = record.logo;
record.iconKey = logo.remove(QRegularExpression("\\..*"));
auto customVersions = attrs.value("customMCVersions");
if (!customVersions.isNull())
{
QMap<QString, QString> versionMatcher;
QString customVersionsStr = customVersions.toString();
QStringList list = customVersionsStr.split(';');
for (auto item : list)
{
auto segment = item.split('^');
if (segment.size() != 2)
{
qCritical() << "FTB: Segment of size < 2 in "
<< customVersionsStr;
continue;
}
versionMatcher[segment[0]] = segment[1];
}
auto actualVersion = attrs.value("version").toString();
if (versionMatcher.contains(actualVersion))
{
record.mcVersion = versionMatcher[actualVersion];
}
else
{
record.mcVersion = attrs.value("mcVersion").toString();
}
}
else
{
record.mcVersion = attrs.value("mcVersion").toString();
}
record.description = attrs.value("description").toString();
records.insert(record);
}
break;
}
case QXmlStreamReader::EndElement:
break;
case QXmlStreamReader::Characters:
break;
default:
break;
}
}
f.close();
}
return records;
}
InstancePtr loadInstance(SettingsObjectPtr globalSettings, QMap<QString, QString> &groupMap, const FTBRecord & record)
{
InstancePtr inst;
auto m_settings = std::make_shared<INISettingsObject>(FS::PathCombine(record.instanceDir, "instance.cfg"));
m_settings->registerSetting("InstanceType", "Legacy");
qDebug() << "Loading existing " << record.name;
QString inst_type = m_settings->get("InstanceType").toString();
if (inst_type == "LegacyFTB")
{
inst.reset(new LegacyFTBInstance(globalSettings, m_settings, record.instanceDir));
}
else if (inst_type == "OneSixFTB")
{
inst.reset(new OneSixFTBInstance(globalSettings, m_settings, record.instanceDir));
}
else
{
return nullptr;
}
qDebug() << "Construction " << record.instanceDir;
SettingsObject::Lock lock(inst->settings());
inst->init();
qDebug() << "Init " << record.instanceDir;
inst->setGroupInitial("FTB");
/**
* FIXME: this does not respect the user's preferences. BUT, it would work nicely with the planned pack support
* -> instead of changing the user values, change pack values (defaults you can look at and revert to)
*/
/*
inst->setName(record.name);
inst->setIconKey(record.iconKey);
inst->setNotes(record.description);
*/
if (inst->intendedVersionId() != record.mcVersion)
{
inst->setIntendedVersionId(record.mcVersion);
}
qDebug() << "Post-Process " << record.instanceDir;
if (!InstanceList::continueProcessInstance(inst, InstanceList::NoCreateError, record.instanceDir, groupMap))
{
return nullptr;
}
qDebug() << "Final " << record.instanceDir;
return inst;
}
InstancePtr createInstance(SettingsObjectPtr globalSettings, QMap<QString, QString> &groupMap, const FTBRecord & record)
{
QDir rootDir(record.instanceDir);
InstancePtr inst;
qDebug() << "Converting " << record.name << " as new.";
auto mcVersion = std::dynamic_pointer_cast<MinecraftVersion>(ENV.getVersion("net.minecraft", record.mcVersion));
if (!mcVersion)
{
qCritical() << "Can't load instance " << record.instanceDir
<< " because minecraft version " << record.mcVersion
<< " can't be resolved.";
return nullptr;
}
if (!rootDir.exists() && !rootDir.mkpath("."))
{
qCritical() << "Can't create instance folder" << record.instanceDir;
return nullptr;
}
auto m_settings = std::make_shared<INISettingsObject>(FS::PathCombine(record.instanceDir, "instance.cfg"));
m_settings->registerSetting("InstanceType", "Legacy");
if (mcVersion->usesLegacyLauncher())
{
m_settings->set("InstanceType", "LegacyFTB");
inst.reset(new LegacyFTBInstance(globalSettings, m_settings, record.instanceDir));
}
else
{
m_settings->set("InstanceType", "OneSixFTB");
inst.reset(new OneSixFTBInstance(globalSettings, m_settings, record.instanceDir));
}
// initialize
{
SettingsObject::Lock lock(inst->settings());
inst->setIntendedVersionId(mcVersion->descriptor());
inst->init();
inst->setGroupInitial("FTB");
inst->setName(record.name);
inst->setIconKey(record.iconKey);
inst->setNotes(record.description);
qDebug() << "Post-Process " << record.instanceDir;
if (!InstanceList::continueProcessInstance(inst, InstanceList::NoCreateError, record.instanceDir, groupMap))
{
return nullptr;
}
}
return inst;
}
void FTBPlugin::loadInstances(SettingsObjectPtr globalSettings, QMap<QString, QString> &groupMap, QList<InstancePtr> &tempList)
{
// nothing to load when we don't have
if (globalSettings->get("TrackFTBInstances").toBool() != true)
{
return;
}
auto records = discoverFTBInstances(globalSettings);
if (!records.size())
{
qDebug() << "No FTB instances to load.";
return;
}
qDebug() << "Loading FTB instances! -- got " << records.size();
// process the records we acquired.
for (auto record : records)
{
qDebug() << "Loading FTB instance from " << record.instanceDir;
QString iconKey = record.iconKey;
auto icons = ENV.icons();
if(icons)
{
icons->addIcon(iconKey, iconKey, FS::PathCombine(record.templateDir, record.logo), IconType::Transient);
}
auto settingsFilePath = FS::PathCombine(record.instanceDir, "instance.cfg");
qDebug() << "ICON get!";
if (QFileInfo(settingsFilePath).exists())
{
auto instPtr = loadInstance(globalSettings, groupMap, record);
if (!instPtr)
{
qWarning() << "Couldn't load instance config:" << settingsFilePath;
if(!QFile::remove(settingsFilePath))
{
qWarning() << "Couldn't remove broken instance config!";
continue;
}
// failed to load, but removed the poisonous file
}
else
{
tempList.append(InstancePtr(instPtr));
continue;
}
}
auto instPtr = createInstance(globalSettings, groupMap, record);
if (!instPtr)
{
qWarning() << "Couldn't create FTB instance!";
continue;
}
tempList.append(InstancePtr(instPtr));
}
}
#ifdef Q_OS_WIN32
#include <windows.h>
static const int APPDATA_BUFFER_SIZE = 1024;

View File

@ -9,5 +9,4 @@ class MULTIMC_LOGIC_EXPORT FTBPlugin
{
public:
static void initialize(SettingsObjectPtr globalSettings);
static void loadInstances(SettingsObjectPtr globalSettings, QMap<QString, QString> &groupMap, QList<InstancePtr> &tempList);
};

View File

@ -1,8 +1,9 @@
#pragma once
#include "Task.h"
#include "multimc_logic_export.h"
class ThreadTask : public Task
class MULTIMC_LOGIC_EXPORT ThreadTask : public Task
{
Q_OBJECT
public:

View File

@ -95,9 +95,23 @@ InstanceWindow::InstanceWindow(InstancePtr instance, QWidget *parent)
connect(m_instance.get(), &BaseInstance::runningStatusChanged,
this, &InstanceWindow::on_RunningState_changed);
}
// set up instance destruction detection
{
connect(m_instance.get(), &BaseInstance::statusChanged, this, &InstanceWindow::on_instanceStatusChanged);
}
show();
}
void InstanceWindow::on_instanceStatusChanged(BaseInstance::Status, BaseInstance::Status newStatus)
{
if(newStatus == BaseInstance::Status::Gone)
{
m_doNotSave = true;
close();
}
}
void InstanceWindow::setKillButton(bool kill)
{
if(kill)
@ -145,18 +159,25 @@ void InstanceWindow::on_closeButton_clicked()
void InstanceWindow::closeEvent(QCloseEvent *event)
{
bool proceed = true;
if(!m_doNotSave)
{
proceed &= m_container->requestClose(event);
}
if(!proceed)
{
return;
}
MMC->settings()->set("ConsoleWindowState", saveState().toBase64());
MMC->settings()->set("ConsoleWindowGeometry", saveGeometry().toBase64());
if(m_container->requestClose(event))
emit isClosing();
event->accept();
if(m_shouldQuit)
{
emit isClosing();
event->accept();
if(m_shouldQuit)
{
// this needs to be delayed so we don't do horrible things
QMetaObject::invokeMethod(MMC, "quit", Qt::QueuedConnection);
}
// this needs to be delayed so we don't do horrible things
QMetaObject::invokeMethod(MMC, "quit", Qt::QueuedConnection);
}
}

View File

@ -55,6 +55,7 @@ slots:
void on_InstanceLaunchTask_changed(std::shared_ptr<LaunchTask> proc);
void on_RunningState_changed(bool running);
void on_instanceStatusChanged(BaseInstance::Status, BaseInstance::Status newStatus);
protected:
void closeEvent(QCloseEvent *) override;
@ -67,6 +68,7 @@ private:
unique_qobject_ptr<LaunchController> m_launchController;
InstancePtr m_instance;
bool m_shouldQuit = false;
bool m_doNotSave = false;
PageContainer *m_container = nullptr;
QPushButton *m_closeButton = nullptr;
QPushButton *m_killButton = nullptr;

View File

@ -88,6 +88,8 @@
#include "dialogs/EditAccountDialog.h"
#include "dialogs/NotificationDialog.h"
#include "dialogs/ExportInstanceDialog.h"
#include <FolderInstanceProvider.h>
#include <InstanceImportTask.h>
class MainWindow::Ui
{
@ -996,26 +998,6 @@ void MainWindow::setCatBackground(bool enabled)
}
}
static QFileInfo findRecursive(const QString &dir, const QString &name)
{
for (const auto info : QDir(dir).entryInfoList(QDir::NoDotAndDotDot | QDir::Dirs | QDir::Files, QDir::DirsLast))
{
if (info.isFile() && info.fileName() == name)
{
return info;
}
else if (info.isDir())
{
const QFileInfo res = findRecursive(info.absoluteFilePath(), name);
if (res.isFile() && res.exists())
{
return res;
}
}
}
return QFileInfo();
}
// FIXME: eliminate, should not be needed
void MainWindow::waitForMinecraftVersions()
{
@ -1028,147 +1010,50 @@ void MainWindow::waitForMinecraftVersions()
}
}
InstancePtr MainWindow::instanceFromZipPack(QString instName, QString instGroup, QString instIcon, QUrl url)
void MainWindow::runModalTask(Task *task)
{
InstancePtr newInstance;
QString instancesDir = MMC->settings()->get("InstanceDir").toString();
QString instDirName = FS::DirNameFromString(instName, instancesDir);
QString instDir = FS::PathCombine(instancesDir, instDirName);
QString archivePath;
if (url.isLocalFile())
{
archivePath = url.toLocalFile();
}
else
{
const QString path = url.host() + '/' + url.path();
auto entry = ENV.metacache()->resolveEntry("general", path);
entry->setStale(true);
NetJob job(tr("Modpack download"));
job.addNetAction(Net::Download::makeCached(url, entry));
// FIXME: possibly causes endless loop problems
ProgressDialog dlDialog(this);
job.setStatus(tr("Downloading modpack:\n%1").arg(url.toString()));
if (dlDialog.execWithTask(&job) != QDialog::Accepted)
connect(task, &Task::failed, [this](QString reason)
{
return nullptr;
}
archivePath = entry->getFullPath();
}
QTemporaryDir extractTmpDir;
QDir extractDir(extractTmpDir.path());
qDebug() << "Attempting to create instance from" << archivePath;
if (MMCZip::extractDir(archivePath, extractDir.absolutePath()).isEmpty())
{
CustomMessageBox::selectable(this, tr("Error"), tr("Failed to extract modpack"), QMessageBox::Warning)->show();
return nullptr;
}
const QFileInfo instanceCfgFile = findRecursive(extractDir.absolutePath(), "instance.cfg");
if (!instanceCfgFile.isFile() || !instanceCfgFile.exists())
{
CustomMessageBox::selectable(this, tr("Error"), tr("Archive does not contain instance.cfg"))->show();
return nullptr;
}
if (!FS::copy(instanceCfgFile.absoluteDir().absolutePath(), instDir)())
{
CustomMessageBox::selectable(this, tr("Error"), tr("Unable to copy instance"))->show();
return nullptr;
}
auto error = MMC->instances()->loadInstance(newInstance, instDir);
QString errorMsg = tr("Failed to load instance %1: ").arg(instDirName);
switch (error)
{
case InstanceList::UnknownLoadError:
errorMsg += tr("Unkown error");
CustomMessageBox::selectable(this, tr("Error"), errorMsg, QMessageBox::Warning)->show();
return nullptr;
case InstanceList::NotAnInstance:
errorMsg += tr("Not an instance");
CustomMessageBox::selectable(this, tr("Error"), errorMsg, QMessageBox::Warning)->show();
return nullptr;
default:
break;
}
newInstance->setName(instName);
if (instIcon != "default")
{
newInstance->setIconKey(instIcon);
}
else
{
instIcon = newInstance->iconKey();
auto importIconPath = FS::PathCombine(newInstance->instanceRoot(), instIcon + ".png");
if (QFile::exists(importIconPath))
{
// import icon
auto iconList = MMC->icons();
// FIXME: check if the file is OK before removing the existing one...
if (iconList->iconFileExists(instIcon))
{
// FIXME: ask if icon should be overwritten. Show difference in the question dialog.
iconList->deleteIcon(instIcon);
}
iconList->installIcons({importIconPath});
}
}
newInstance->setGroupInitial(instGroup);
// reset time played on import... because packs.
newInstance->resetTimePlayed();
MMC->instances()->add(InstancePtr(newInstance));
MMC->instances()->saveGroupList();
finalizeInstance(newInstance);
return newInstance;
CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Warning)->show();
});
ProgressDialog loadDialog(this);
loadDialog.setSkipButton(true, tr("Abort"));
loadDialog.execWithTask(task);
}
InstancePtr MainWindow::instanceFromVersion(QString instName, QString instGroup, QString instIcon, BaseVersionPtr version)
void MainWindow::instanceFromZipPack(QString instName, QString instGroup, QString instIcon, QUrl url)
{
InstancePtr newInstance;
std::unique_ptr<Task> task(MMC->folderProvider()->zipImportTask(url, instName, instGroup, instIcon));
runModalTask(task.get());
QString instancesDir = MMC->settings()->get("InstanceDir").toString();
QString instDirName = FS::DirNameFromString(instName, instancesDir);
QString instDir = FS::PathCombine(instancesDir, instDirName);
auto error = MMC->instances()->createInstance(newInstance, version, instDir);
QString errorMsg = tr("Failed to create instance %1: ").arg(instDirName);
switch (error)
{
case InstanceList::NoCreateError:
break;
// FIXME: handle instance selection after creation
// finalizeInstance(newInstance);
}
case InstanceList::InstExists:
{
errorMsg += tr("An instance with the given directory name already exists.");
CustomMessageBox::selectable(this, tr("Error"), errorMsg, QMessageBox::Warning)->show();
return nullptr;
}
void MainWindow::instanceFromVersion(QString instName, QString instGroup, QString instIcon, BaseVersionPtr version)
{
std::unique_ptr<Task> task(MMC->folderProvider()->creationTask(version, instName, instGroup, instIcon));
runModalTask(task.get());
case InstanceList::CantCreateDir:
{
errorMsg += tr("Failed to create the instance directory.");
CustomMessageBox::selectable(this, tr("Error"), errorMsg, QMessageBox::Warning)->show();
return nullptr;
}
// FIXME: handle instance selection after creation
// finalizeInstance(newInstance);
}
default:
{
errorMsg += tr("Unknown instance loader error %1").arg(error);
CustomMessageBox::selectable(this, tr("Error"), errorMsg, QMessageBox::Warning)->show();
return nullptr;
}
}
newInstance->setName(instName);
newInstance->setIconKey(instIcon);
newInstance->setGroupInitial(instGroup);
MMC->instances()->add(InstancePtr(newInstance));
MMC->instances()->saveGroupList();
finalizeInstance(newInstance);
return newInstance;
void MainWindow::on_actionCopyInstance_triggered()
{
if (!m_selectedInstance)
return;
CopyInstanceDialog copyInstDlg(m_selectedInstance, this);
if (!copyInstDlg.exec())
return;
std::unique_ptr<Task> task(MMC->folderProvider()->copyTask(m_selectedInstance, copyInstDlg.instName(), copyInstDlg.instGroup(),
copyInstDlg.iconKey(), copyInstDlg.shouldCopySaves()));
runModalTask(task.get());
// FIXME: handle instance selection after creation
// finalizeInstance(newInstance);
}
void MainWindow::finalizeInstance(InstancePtr inst)
@ -1251,56 +1136,6 @@ void MainWindow::on_actionDISCORD_triggered()
DesktopServices::openUrl(QUrl("https://discord.gg/0k2zsXGNHs0fE4Wm"));
}
void MainWindow::on_actionCopyInstance_triggered()
{
if (!m_selectedInstance)
return;
CopyInstanceDialog copyInstDlg(m_selectedInstance, this);
if (!copyInstDlg.exec())
return;
QString instancesDir = MMC->settings()->get("InstanceDir").toString();
QString instDirName = FS::DirNameFromString(copyInstDlg.instName(), instancesDir);
QString instDir = FS::PathCombine(instancesDir, instDirName);
bool copySaves = copyInstDlg.shouldCopySaves();
InstancePtr newInstance;
auto error = MMC->instances()->copyInstance(newInstance, m_selectedInstance, instDir, copySaves);
QString errorMsg = tr("Failed to create instance %1: ").arg(instDirName);
switch (error)
{
case InstanceList::NoCreateError:
newInstance->setName(copyInstDlg.instName());
newInstance->setIconKey(copyInstDlg.iconKey());
MMC->instances()->add(newInstance);
newInstance->setGroupPost(copyInstDlg.instGroup());
return;
case InstanceList::InstExists:
{
errorMsg += tr("An instance with the given directory name already exists.");
CustomMessageBox::selectable(this, tr("Error"), errorMsg, QMessageBox::Warning)->show();
break;
}
case InstanceList::CantCreateDir:
{
errorMsg += tr("Failed to create the instance directory.");
CustomMessageBox::selectable(this, tr("Error"), errorMsg, QMessageBox::Warning)->show();
break;
}
default:
{
errorMsg += tr("Unknown instance loader error %1").arg(error);
CustomMessageBox::selectable(this, tr("Error"), errorMsg, QMessageBox::Warning)->show();
break;
}
}
}
void MainWindow::on_actionChangeInstIcon_triggered()
{
if (!m_selectedInstance)
@ -1386,7 +1221,7 @@ void MainWindow::on_actionViewInstanceFolder_triggered()
void MainWindow::on_actionRefresh_triggered()
{
MMC->instances()->loadList();
MMC->instances()->loadList(true);
}
void MainWindow::on_actionViewCentralModsFolder_triggered()

View File

@ -170,8 +170,9 @@ private:
void setSelectedInstanceById(const QString &id);
void waitForMinecraftVersions();
InstancePtr instanceFromVersion(QString instName, QString instGroup, QString instIcon, BaseVersionPtr version);
InstancePtr instanceFromZipPack(QString instName, QString instGroup, QString instIcon, QUrl url);
void runModalTask(Task *task);
void instanceFromVersion(QString instName, QString instGroup, QString instIcon, BaseVersionPtr version);
void instanceFromZipPack(QString instName, QString instGroup, QString instIcon, QUrl url);
void finalizeInstance(InstancePtr inst);
void launch(InstancePtr instance, bool online = true, BaseProfilerFactory *profiler = nullptr);

View File

@ -25,6 +25,9 @@
#include <QStyleFactory>
#include "InstanceList.h"
#include "FolderInstanceProvider.h"
#include "minecraft/ftb/FTBInstanceProvider.h"
#include <minecraft/auth/MojangAccountList.h>
#include "icons/IconList.h"
//FIXME: get rid of this
@ -261,10 +264,13 @@ MultiMC::MultiMC(int &argc, char **argv, bool test_mode) : QApplication(argc, ar
<< "Your instance path contains \'!\' and this is known to cause java problems";
}
m_instances.reset(new InstanceList(m_settings, InstDirSetting->get().toString(), this));
m_instanceFolder = new FolderInstanceProvider(m_settings, instDir);
connect(InstDirSetting.get(), &Setting::SettingChanged, m_instanceFolder, &FolderInstanceProvider::on_InstFolderChanged);
m_instances->addInstanceProvider(m_instanceFolder);
m_instances->addInstanceProvider(new FTBInstanceProvider(m_settings));
qDebug() << "Loading Instances...";
m_instances->loadList();
connect(InstDirSetting.get(), SIGNAL(SettingChanged(const Setting &, QVariant)),
m_instances.get(), SLOT(on_InstFolderChanged(const Setting &, QVariant)));
m_instances->loadList(true);
// and accounts
m_accounts.reset(new MojangAccountList(this));
@ -1007,7 +1013,7 @@ void MultiMC::onExit()
{
if(m_instances)
{
m_instances->saveGroupList();
// m_instances->saveGroupList();
}
ENV.destroy();
if(logFile)

View File

@ -7,6 +7,7 @@
#include <QIcon>
#include <QDateTime>
#include <updater/GoUpdate.h>
class FolderInstanceProvider;
class GenericPageProvider;
class QFile;
@ -91,6 +92,11 @@ public:
return m_instances;
}
FolderInstanceProvider * folderProvider()
{
return m_instanceFolder;
}
std::shared_ptr<IconList> icons()
{
return m_icons;
@ -164,6 +170,7 @@ private:
std::shared_ptr<QTranslator> m_mmc_translator;
std::shared_ptr<SettingsObject> m_settings;
std::shared_ptr<InstanceList> m_instances;
FolderInstanceProvider * m_instanceFolder;
std::shared_ptr<IconList> m_icons;
std::shared_ptr<UpdateChecker> m_updateChecker;
std::shared_ptr<MojangAccountList> m_accounts;