NOISSUE Granular instance reload
This commit is contained in:
parent
bbe139dce5
commit
d66fdcd4cc
@ -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)
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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>)
|
||||
|
57
api/logic/BaseInstanceProvider.h
Normal file
57
api/logic/BaseInstanceProvider.h
Normal 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;
|
||||
};
|
@ -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
|
||||
|
||||
|
356
api/logic/FolderInstanceProvider.cpp
Normal file
356
api/logic/FolderInstanceProvider.cpp
Normal 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);
|
||||
}
|
||||
|
63
api/logic/FolderInstanceProvider.h
Normal file
63
api/logic/FolderInstanceProvider.h
Normal 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;
|
||||
};
|
53
api/logic/InstanceCopyTask.cpp
Normal file
53
api/logic/InstanceCopyTask.cpp
Normal 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();
|
||||
}
|
34
api/logic/InstanceCopyTask.h
Normal file
34
api/logic/InstanceCopyTask.h
Normal 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;
|
||||
};
|
||||
|
||||
|
46
api/logic/InstanceCreationTask.cpp
Normal file
46
api/logic/InstanceCreationTask.cpp
Normal 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();
|
||||
}
|
31
api/logic/InstanceCreationTask.h
Normal file
31
api/logic/InstanceCreationTask.h
Normal 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;
|
||||
};
|
||||
|
146
api/logic/InstanceImportTask.cpp
Normal file
146
api/logic/InstanceImportTask.cpp
Normal 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();
|
||||
}
|
40
api/logic/InstanceImportTask.h
Normal file
40
api/logic/InstanceImportTask.h
Normal 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;
|
||||
};
|
@ -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;
|
||||
auto id = item->id();
|
||||
if(out.contains(id))
|
||||
{
|
||||
qWarning() << "Duplicate ID" << id << "in instance list";
|
||||
}
|
||||
out[id] = std::make_pair(item, i);
|
||||
i++;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
QString groupFileName = m_instDir + "/instgroups.json";
|
||||
QMap<QString, QSet<QString>> groupMap;
|
||||
for (auto instance : m_instances)
|
||||
InstanceList::InstListError InstanceList::loadList(bool complete)
|
||||
{
|
||||
QString id = instance->id();
|
||||
QString group = instance->group();
|
||||
if (group.isEmpty())
|
||||
continue;
|
||||
auto existingIds = getIdMapping(m_instances);
|
||||
|
||||
// keep a list/set of groups for choosing
|
||||
m_groups.insert(group);
|
||||
QList<InstancePtr> newList;
|
||||
|
||||
if (!groupMap.count(group))
|
||||
auto processIds = [&](BaseInstanceProvider * provider, QList<InstanceId> ids)
|
||||
{
|
||||
QSet<QString> set;
|
||||
set.insert(id);
|
||||
groupMap[group] = set;
|
||||
for(auto & id: ids)
|
||||
{
|
||||
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
|
||||
{
|
||||
QSet<QString> &set = groupMap[group];
|
||||
set.insert(id);
|
||||
InstancePtr instPtr = provider->loadInstance(id);
|
||||
newList.append(instPtr);
|
||||
}
|
||||
}
|
||||
QJsonObject toplevel;
|
||||
toplevel.insert("formatVersion", QJsonValue(QString("1")));
|
||||
QJsonObject groupsArr;
|
||||
for (auto iter = groupMap.begin(); iter != groupMap.end(); iter++)
|
||||
};
|
||||
if(complete)
|
||||
{
|
||||
auto list = iter.value();
|
||||
auto name = iter.key();
|
||||
QJsonObject groupObj;
|
||||
QJsonArray instanceArr;
|
||||
groupObj.insert("hidden", QJsonValue(QString("false")));
|
||||
for (auto item : list)
|
||||
for(auto & item: m_providers)
|
||||
{
|
||||
instanceArr.append(QJsonValue(item));
|
||||
processIds(item.get(), item->discoverInstances());
|
||||
}
|
||||
groupObj.insert("instances", instanceArr);
|
||||
groupsArr.insert(name, groupObj);
|
||||
}
|
||||
toplevel.insert("groups", groupsArr);
|
||||
QJsonDocument doc(toplevel);
|
||||
try
|
||||
else
|
||||
{
|
||||
FS::write(groupFileName, doc.toJson());
|
||||
}
|
||||
catch(FS::FileSystemException & e)
|
||||
for (auto & item: m_updatedProviders)
|
||||
{
|
||||
qCritical() << "Failed to write instance group file :" << e.cause();
|
||||
processIds(item, item->discoverInstances());
|
||||
}
|
||||
}
|
||||
|
||||
void InstanceList::loadGroupList(QMap<QString, QString> &groupMap)
|
||||
// TODO: looks like a general algorithm with a few specifics inserted. Do something about it.
|
||||
if(!existingIds.isEmpty())
|
||||
{
|
||||
QString groupFileName = m_instDir + "/instgroups.json";
|
||||
|
||||
// if there's no group file, fail
|
||||
if (!QFileInfo(groupFileName).exists())
|
||||
return;
|
||||
|
||||
QByteArray jsonData;
|
||||
try
|
||||
// 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
|
||||
{
|
||||
jsonData = FS::read(groupFileName);
|
||||
}
|
||||
catch (FS::FileSystemException & e)
|
||||
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 = [&]()
|
||||
{
|
||||
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)
|
||||
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)
|
||||
{
|
||||
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())
|
||||
auto instPtr = removedItem.first;
|
||||
if(!complete && !m_updatedProviders.contains(instPtr->provider()))
|
||||
{
|
||||
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())
|
||||
{
|
||||
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())
|
||||
instPtr->invalidate();
|
||||
currentItem = removedItem.second;
|
||||
if(back_bookmark == -1)
|
||||
{
|
||||
qWarning() << QString("Group '%1' in the group list is invalid. "
|
||||
"It should contain an array "
|
||||
"called 'instances'.")
|
||||
.arg(groupName)
|
||||
.toUtf8();
|
||||
continue;
|
||||
// no bookmark yet
|
||||
back_bookmark = currentItem;
|
||||
}
|
||||
|
||||
// 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++)
|
||||
else if(currentItem == front_bookmark - 1)
|
||||
{
|
||||
groupMap[(*iter2).toString()] = groupName;
|
||||
// part of contiguous sequence, continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
InstanceList::InstListError InstanceList::loadList()
|
||||
else
|
||||
{
|
||||
// load the instance groups
|
||||
QMap<QString, QString> groupMap;
|
||||
loadGroupList(groupMap);
|
||||
|
||||
QList<InstancePtr> tempList;
|
||||
// seam between previous and current item
|
||||
removeNow();
|
||||
}
|
||||
front_bookmark = currentItem;
|
||||
}
|
||||
if(back_bookmark != -1)
|
||||
{
|
||||
QDirIterator iter(m_instDir, QDir::Dirs | QDir::NoDot | QDir::NoDotDot | QDir::Readable,
|
||||
QDirIterator::FollowSymlinks);
|
||||
while (iter.hasNext())
|
||||
{
|
||||
QString subDir = iter.next();
|
||||
if (!QFileInfo(FS::PathCombine(subDir, "instance.cfg")).exists())
|
||||
continue;
|
||||
qDebug() << "Loading MultiMC instance from " << subDir;
|
||||
InstancePtr instPtr;
|
||||
auto error = loadInstance(instPtr, subDir);
|
||||
if(!continueProcessInstance(instPtr, error, subDir, groupMap))
|
||||
continue;
|
||||
tempList.append(instPtr);
|
||||
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();
|
||||
beginInsertRows(QModelIndex(), m_instances.count(), m_instances.count() + t.size() - 1);
|
||||
m_instances.append(t);
|
||||
for(auto & ptr : t)
|
||||
{
|
||||
connect(ptr.get(), &BaseInstance::propertiesChanged, this, &InstanceList::propertiesChanged);
|
||||
}
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
void InstanceList::on_InstFolderChanged(const Setting &setting, QVariant value)
|
||||
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())
|
||||
{
|
||||
m_instDir = value.toString();
|
||||
loadList();
|
||||
}
|
||||
}
|
||||
|
||||
/// Add an instance. Triggers notifications, returns the new index
|
||||
int InstanceList::add(InstancePtr t)
|
||||
void InstanceList::suspendWatch()
|
||||
{
|
||||
beginInsertRows(QModelIndex(), m_instances.size(), m_instances.size());
|
||||
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 *)));
|
||||
endInsertRows();
|
||||
return count() - 1;
|
||||
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"
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
278
api/logic/minecraft/ftb/FTBInstanceProvider.cpp
Normal file
278
api/logic/minecraft/ftb/FTBInstanceProvider.cpp
Normal 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;
|
||||
}
|
45
api/logic/minecraft/ftb/FTBInstanceProvider.h
Normal file
45
api/logic/minecraft/ftb/FTBInstanceProvider.h
Normal 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;
|
||||
};
|
@ -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;
|
||||
|
@ -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);
|
||||
};
|
||||
|
@ -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:
|
||||
|
@ -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,11 +159,19 @@ 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)
|
||||
@ -158,7 +180,6 @@ void InstanceWindow::closeEvent(QCloseEvent *event)
|
||||
QMetaObject::invokeMethod(MMC, "quit", Qt::QueuedConnection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void InstanceWindow::on_btnKillMinecraft_clicked()
|
||||
{
|
||||
|
@ -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;
|
||||
|
@ -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())
|
||||
connect(task, &Task::failed, [this](QString reason)
|
||||
{
|
||||
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)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
archivePath = entry->getFullPath();
|
||||
CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Warning)->show();
|
||||
});
|
||||
ProgressDialog loadDialog(this);
|
||||
loadDialog.setSkipButton(true, tr("Abort"));
|
||||
loadDialog.execWithTask(task);
|
||||
}
|
||||
|
||||
QTemporaryDir extractTmpDir;
|
||||
QDir extractDir(extractTmpDir.path());
|
||||
qDebug() << "Attempting to create instance from" << archivePath;
|
||||
if (MMCZip::extractDir(archivePath, extractDir.absolutePath()).isEmpty())
|
||||
void MainWindow::instanceFromZipPack(QString instName, QString instGroup, QString instIcon, QUrl url)
|
||||
{
|
||||
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;
|
||||
std::unique_ptr<Task> task(MMC->folderProvider()->zipImportTask(url, instName, instGroup, instIcon));
|
||||
runModalTask(task.get());
|
||||
|
||||
// FIXME: handle instance selection after creation
|
||||
// finalizeInstance(newInstance);
|
||||
}
|
||||
|
||||
auto error = MMC->instances()->loadInstance(newInstance, instDir);
|
||||
QString errorMsg = tr("Failed to load instance %1: ").arg(instDirName);
|
||||
switch (error)
|
||||
void MainWindow::instanceFromVersion(QString instName, QString instGroup, QString instIcon, BaseVersionPtr version)
|
||||
{
|
||||
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;
|
||||
std::unique_ptr<Task> task(MMC->folderProvider()->creationTask(version, instName, instGroup, instIcon));
|
||||
runModalTask(task.get());
|
||||
|
||||
// FIXME: handle instance selection after creation
|
||||
// finalizeInstance(newInstance);
|
||||
}
|
||||
|
||||
newInstance->setName(instName);
|
||||
if (instIcon != "default")
|
||||
void MainWindow::on_actionCopyInstance_triggered()
|
||||
{
|
||||
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();
|
||||
if (!m_selectedInstance)
|
||||
return;
|
||||
|
||||
finalizeInstance(newInstance);
|
||||
return newInstance;
|
||||
}
|
||||
CopyInstanceDialog copyInstDlg(m_selectedInstance, this);
|
||||
if (!copyInstDlg.exec())
|
||||
return;
|
||||
|
||||
InstancePtr MainWindow::instanceFromVersion(QString instName, QString instGroup, QString instIcon, BaseVersionPtr version)
|
||||
{
|
||||
InstancePtr newInstance;
|
||||
std::unique_ptr<Task> task(MMC->folderProvider()->copyTask(m_selectedInstance, copyInstDlg.instName(), copyInstDlg.instGroup(),
|
||||
copyInstDlg.iconKey(), copyInstDlg.shouldCopySaves()));
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
case InstanceList::CantCreateDir:
|
||||
{
|
||||
errorMsg += tr("Failed to create the instance directory.");
|
||||
CustomMessageBox::selectable(this, tr("Error"), errorMsg, QMessageBox::Warning)->show();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
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;
|
||||
// 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()
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user