GH-1053 move instance update into the launch task (BaseLauncher)

This commit is contained in:
Petr Mrázek
2015-07-04 20:02:43 +02:00
parent 5628d3d379
commit 526a511f45
18 changed files with 303 additions and 262 deletions

View File

@ -138,7 +138,7 @@ public:
virtual std::shared_ptr<Task> doUpdate() = 0;
/// returns a valid process, ready for launch with the given account.
virtual BaseLauncher *prepareForLaunch(AuthSessionPtr account) = 0;
virtual std::shared_ptr<BaseLauncher> prepareForLaunch(AuthSessionPtr account) = 0;
/// do any necessary cleanups after the instance finishes. also runs before
/// 'prepareForLaunch'

View File

@ -19,6 +19,7 @@
#include "MessageLevel.h"
#include "MMCStrings.h"
#include "java/JavaChecker.h"
#include "tasks/Task.h"
#include <pathutils.h>
#include <QDebug>
#include <QDir>
@ -29,14 +30,108 @@
#define IBUS "@im=ibus"
BaseLauncher* BaseLauncher::create(MinecraftInstancePtr inst)
void BaseLauncher::initializeEnvironment()
{
auto proc = new BaseLauncher(inst);
// prepare the process environment
QProcessEnvironment rawenv = QProcessEnvironment::systemEnvironment();
QStringList ignored =
{
"JAVA_ARGS",
"CLASSPATH",
"CONFIGPATH",
"JAVA_HOME",
"JRE_HOME",
"_JAVA_OPTIONS",
"JAVA_OPTIONS",
"JAVA_TOOL_OPTIONS"
};
for(auto key: rawenv.keys())
{
auto value = rawenv.value(key);
// filter out dangerous java crap
if(ignored.contains(key))
{
qDebug() << "Env: ignoring" << key << value;
continue;
}
// filter MultiMC-related things
if(key.startsWith("QT_"))
{
qDebug() << "Env: ignoring" << key << value;
continue;
}
#ifdef Q_OS_LINUX
// Do not pass LD_* variables to java. They were intended for MultiMC
if(key.startsWith("LD_"))
{
qDebug() << "Env: ignoring" << key << value;
continue;
}
// Strip IBus
// IBus is a Linux IME framework. For some reason, it breaks MC?
if (key == "XMODIFIERS" && value.contains(IBUS))
{
QString save = value;
value.replace(IBUS, "");
qDebug() << "Env: stripped" << IBUS << "from" << save << ":" << value;
}
if(key == "GAME_PRELOAD")
{
m_env.insert("LD_PRELOAD", value);
continue;
}
if(key == "GAME_LIBRARY_PATH")
{
m_env.insert("LD_LIBRARY_PATH", value);
continue;
}
#endif
qDebug() << "Env: " << key << value;
m_env.insert(key, value);
}
#ifdef Q_OS_LINUX
// HACK: Workaround for QTBUG42500
if(!m_env.contains("LD_LIBRARY_PATH"))
{
m_env.insert("LD_LIBRARY_PATH", "");
}
#endif
// export some infos
auto variables = getVariables();
for (auto it = variables.begin(); it != variables.end(); ++it)
{
m_env.insert(it.key(), it.value());
}
}
void BaseLauncher::init()
{
initializeEnvironment();
m_process.setProcessEnvironment(m_env);
connect(&m_process, &LoggedProcess::log, this, &BaseLauncher::on_log);
connect(&m_process, &LoggedProcess::stateChanged, this, &BaseLauncher::on_state);
m_prelaunchprocess.setProcessEnvironment(m_env);
connect(&m_prelaunchprocess, &LoggedProcess::log, this, &BaseLauncher::on_log);
connect(&m_prelaunchprocess, &LoggedProcess::stateChanged, this, &BaseLauncher::on_pre_state);
m_postlaunchprocess.setProcessEnvironment(m_env);
connect(&m_postlaunchprocess, &LoggedProcess::log, this, &BaseLauncher::on_log);
connect(&m_postlaunchprocess, &LoggedProcess::stateChanged, this, &BaseLauncher::on_post_state);
m_instance->setRunning(true);
}
std::shared_ptr<BaseLauncher> BaseLauncher::create(MinecraftInstancePtr inst)
{
std::shared_ptr<BaseLauncher> proc(new BaseLauncher(inst));
proc->init();
return proc;
}
BaseLauncher::BaseLauncher(InstancePtr instance): m_instance(instance)
{
}
@ -158,126 +253,68 @@ QStringList BaseLauncher::javaArguments() const
return args;
}
bool BaseLauncher::checkJava(QString JavaPath)
void BaseLauncher::checkJava()
{
auto realJavaPath = QStandardPaths::findExecutable(JavaPath);
m_javaPath = m_instance->settings()->get("JavaPath").toString();
emit log("Java path is:\n" + m_javaPath + "\n\n");
auto realJavaPath = QStandardPaths::findExecutable(m_javaPath);
if (realJavaPath.isEmpty())
{
emit log(tr("The java binary \"%1\" couldn't be found. You may have to set up java "
"if Minecraft fails to launch.").arg(JavaPath),
"if Minecraft fails to launch.").arg(m_javaPath),
MessageLevel::Warning);
}
QFileInfo javaInfo(realJavaPath);
qlonglong javaUnixTime = javaInfo.lastModified().toMSecsSinceEpoch();
auto storedUnixTime = m_instance->settings()->get("JavaTimestamp").toLongLong();
this->m_javaUnixTime = javaUnixTime;
// if they are not the same, check!
if(javaUnixTime != storedUnixTime)
{
QEventLoop ev;
auto checker = std::make_shared<JavaChecker>();
m_JavaChecker = std::make_shared<JavaChecker>();
bool successful = false;
QString errorLog;
QString version;
emit log(tr("Checking Java version..."), MessageLevel::MultiMC);
connect(checker.get(), &JavaChecker::checkFinished,
[&](JavaCheckResult result)
{
successful = result.valid;
errorLog = result.errorLog;
version = result.javaVersion;
ev.exit();
});
checker->m_path = realJavaPath;
checker->performCheck();
ev.exec();
if(!successful)
{
// Error message displayed if java can't start
emit log(tr("Could not start java:"), MessageLevel::Error);
auto lines = errorLog.split('\n');
for(auto line: lines)
{
emit log(line, MessageLevel::Error);
}
emit log("\nCheck your MultiMC Java settings.", MessageLevel::MultiMC);
m_instance->cleanupAfterRun();
emit launch_failed(m_instance);
// not running, failed
m_instance->setRunning(false);
return false;
}
emit log(tr("Java version is %1!\n").arg(version), MessageLevel::MultiMC);
m_instance->settings()->set("JavaVersion", version);
m_instance->settings()->set("JavaTimestamp", javaUnixTime);
connect(m_JavaChecker.get(), &JavaChecker::checkFinished, this, &BaseLauncher::checkJavaFinished);
m_JavaChecker->m_path = realJavaPath;
m_JavaChecker->performCheck();
}
return true;
preLaunch();
}
void BaseLauncher::arm()
void BaseLauncher::checkJavaFinished(JavaCheckResult result)
{
printHeader();
emit log("Minecraft folder is:\n" + m_process.workingDirectory() + "\n\n");
/*
if (!preLaunch())
if(!result.valid)
{
emit ended(m_instance, 1, QProcess::CrashExit);
return;
}
*/
m_instance->setLastLaunch();
QString JavaPath = m_instance->settings()->get("JavaPath").toString();
emit log("Java path is:\n" + JavaPath + "\n\n");
if(!checkJava(JavaPath))
{
return;
}
QStringList args = javaArguments();
QString allArgs = args.join(", ");
emit log("Java Arguments:\n[" + censorPrivateInfo(allArgs) + "]\n\n");
QString wrapperCommand = m_instance->settings()->get("WrapperCommand").toString();
if(!wrapperCommand.isEmpty())
{
auto realWrapperCommand = QStandardPaths::findExecutable(wrapperCommand);
if (realWrapperCommand.isEmpty())
// Error message displayed if java can't start
emit log(tr("Could not start java:"), MessageLevel::Error);
auto lines = result.errorLog.split('\n');
for(auto line: lines)
{
emit log(tr("The wrapper command \"%1\" couldn't be found.").arg(wrapperCommand), MessageLevel::Warning);
m_instance->cleanupAfterRun();
emit launch_failed(m_instance);
m_instance->setRunning(false);
return;
emit log(line, MessageLevel::Error);
}
emit log("Wrapper command is:\n" + wrapperCommand + "\n\n");
args.prepend(JavaPath);
m_process.start(wrapperCommand, args);
}
else
{
m_process.start(JavaPath, args);
}
// instantiate the launcher part
if (!m_process.waitForStarted())
{
//: Error message displayed if instace can't start
emit log(tr("Could not launch minecraft!"), MessageLevel::Error);
emit log("\nCheck your MultiMC Java settings.", MessageLevel::MultiMC);
m_instance->cleanupAfterRun();
emit launch_failed(m_instance);
// not running, failed
m_instance->setRunning(false);
return;
}
emit log(tr("Java version is %1!\n").arg(result.javaVersion), MessageLevel::MultiMC);
m_instance->settings()->set("JavaVersion", result.javaVersion);
m_instance->settings()->set("JavaTimestamp", m_javaUnixTime);
preLaunch();
}
emit log(tr("Minecraft process ID: %1\n\n").arg(m_process.processId()), MessageLevel::MultiMC);
void BaseLauncher::executeTask()
{
printHeader();
emit log("Minecraft folder is:\n" + m_process.workingDirectory() + "\n\n");
// send the launch script to the launcher part
m_process.write(launchScript.toUtf8());
checkJava();
}
void BaseLauncher::launch()
@ -293,103 +330,6 @@ void BaseLauncher::abort()
}
void BaseLauncher::initializeEnvironment()
{
// prepare the process environment
QProcessEnvironment rawenv = QProcessEnvironment::systemEnvironment();
QStringList ignored =
{
"JAVA_ARGS",
"CLASSPATH",
"CONFIGPATH",
"JAVA_HOME",
"JRE_HOME",
"_JAVA_OPTIONS",
"JAVA_OPTIONS",
"JAVA_TOOL_OPTIONS"
};
for(auto key: rawenv.keys())
{
auto value = rawenv.value(key);
// filter out dangerous java crap
if(ignored.contains(key))
{
qDebug() << "Env: ignoring" << key << value;
continue;
}
// filter MultiMC-related things
if(key.startsWith("QT_"))
{
qDebug() << "Env: ignoring" << key << value;
continue;
}
#ifdef Q_OS_LINUX
// Do not pass LD_* variables to java. They were intended for MultiMC
if(key.startsWith("LD_"))
{
qDebug() << "Env: ignoring" << key << value;
continue;
}
// Strip IBus
// IBus is a Linux IME framework. For some reason, it breaks MC?
if (key == "XMODIFIERS" && value.contains(IBUS))
{
QString save = value;
value.replace(IBUS, "");
qDebug() << "Env: stripped" << IBUS << "from" << save << ":" << value;
}
if(key == "GAME_PRELOAD")
{
m_env.insert("LD_PRELOAD", value);
continue;
}
if(key == "GAME_LIBRARY_PATH")
{
m_env.insert("LD_LIBRARY_PATH", value);
continue;
}
#endif
qDebug() << "Env: " << key << value;
m_env.insert(key, value);
}
#ifdef Q_OS_LINUX
// HACK: Workaround for QTBUG42500
if(!m_env.contains("LD_LIBRARY_PATH"))
{
m_env.insert("LD_LIBRARY_PATH", "");
}
#endif
// export some infos
auto variables = getVariables();
for (auto it = variables.begin(); it != variables.end(); ++it)
{
m_env.insert(it.key(), it.value());
}
}
void BaseLauncher::init()
{
initializeEnvironment();
m_process.setProcessEnvironment(m_env);
connect(&m_process, &LoggedProcess::log, this, &BaseLauncher::on_log);
connect(&m_process, &LoggedProcess::stateChanged, this, &BaseLauncher::on_state);
m_prelaunchprocess.setProcessEnvironment(m_env);
connect(&m_prelaunchprocess, &LoggedProcess::log, this, &BaseLauncher::on_log);
connect(&m_prelaunchprocess, &LoggedProcess::stateChanged, this, &BaseLauncher::on_pre_state);
m_postlaunchprocess.setProcessEnvironment(m_env);
connect(&m_postlaunchprocess, &LoggedProcess::log, this, &BaseLauncher::on_log);
connect(&m_postlaunchprocess, &LoggedProcess::stateChanged, this, &BaseLauncher::on_post_state);
// a process has been constructed for the instance. It is running from MultiMC POV
m_instance->setRunning(true);
}
void BaseLauncher::setWorkdir(QString path)
{
QDir mcDir(path);
@ -468,18 +408,98 @@ void BaseLauncher::on_pre_state(LoggedProcess::State state)
emit prelaunch_failed(m_instance, m_prelaunchprocess.exitCode(), m_prelaunchprocess.exitStatus());
// not running, failed
m_instance->setRunning(false);
return;
}
case LoggedProcess::Finished:
{
emit log(tr("Pre-Launch command ran successfully.\n\n"));
m_instance->reload();
}
case LoggedProcess::Skipped:
{
m_instance->reload();
updateInstance();
}
default:
break;
}
}
void BaseLauncher::updateInstance()
{
m_updateTask = m_instance->doUpdate();
if(m_updateTask)
{
connect(m_updateTask.get(), SIGNAL(finished()), this, SLOT(updateFinished()));
m_updateTask->start();
return;
}
makeReady();
}
void BaseLauncher::updateFinished()
{
if(m_updateTask->successful())
{
makeReady();
}
else
{
QString reason = tr("Instance update failed because: %1.\n\n").arg(m_updateTask->failReason());
emit log(reason, MessageLevel::Fatal);
m_instance->cleanupAfterRun();
emit update_failed(m_instance);
emitFailed(reason);
m_instance->setRunning(false);
}
}
void BaseLauncher::makeReady()
{
QStringList args = javaArguments();
QString allArgs = args.join(", ");
emit log("Java Arguments:\n[" + censorPrivateInfo(allArgs) + "]\n\n");
QString wrapperCommand = m_instance->settings()->get("WrapperCommand").toString();
if(!wrapperCommand.isEmpty())
{
auto realWrapperCommand = QStandardPaths::findExecutable(wrapperCommand);
if (realWrapperCommand.isEmpty())
{
emit log(tr("The wrapper command \"%1\" couldn't be found.").arg(wrapperCommand), MessageLevel::Warning);
m_instance->cleanupAfterRun();
emit launch_failed(m_instance);
m_instance->setRunning(false);
return;
}
emit log("Wrapper command is:\n" + wrapperCommand + "\n\n");
args.prepend(m_javaPath);
m_process.start(wrapperCommand, args);
}
else
{
m_process.start(m_javaPath, args);
}
// instantiate the launcher part
if (!m_process.waitForStarted())
{
//: Error message displayed if instace can't start
emit log(tr("Could not launch minecraft!"), MessageLevel::Error);
m_instance->cleanupAfterRun();
emit launch_failed(m_instance);
// not running, failed
m_instance->setRunning(false);
return;
}
emit log(tr("Minecraft process ID: %1\n\n").arg(m_process.processId()), MessageLevel::MultiMC);
// send the launch script to the launcher part
m_process.write(launchScript.toUtf8());
emit readyForLaunch(shared_from_this());
}
void BaseLauncher::on_state(LoggedProcess::State state)
{
QProcess::ExitStatus estat = QProcess::NormalExit;
@ -500,9 +520,14 @@ void BaseLauncher::on_state(LoggedProcess::State state)
// no longer running...
m_instance->setRunning(false);
emit ended(m_instance, exitCode, estat);
break;
}
case LoggedProcess::Skipped:
qWarning() << "Illegal game state: Skipped";
break;
case LoggedProcess::Running:
m_instance->setLastLaunch();
break;
default:
break;
}

View File

@ -22,8 +22,12 @@
#include "LoggedProcess.h"
/* HACK: MINECRAFT: split! */
#include "minecraft/MinecraftInstance.h"
#include "java/JavaChecker.h"
#include "QObjectPtr.h"
#include "tasks/Task.h"
class BaseLauncher: public QObject
class BaseProfilerFactory;
class BaseLauncher: public Task, public std::enable_shared_from_this<BaseLauncher>
{
Q_OBJECT
protected:
@ -31,7 +35,7 @@ protected:
void init();
public: /* methods */
static BaseLauncher *create(MinecraftInstancePtr inst);
static std::shared_ptr<BaseLauncher> create(MinecraftInstancePtr inst);
virtual ~BaseLauncher() {};
InstancePtr instance()
@ -47,6 +51,16 @@ public: /* methods */
void setWorkdir(QString path);
BaseProfilerFactory * getProfiler()
{
return m_profiler;
}
void setProfiler(BaseProfilerFactory * profiler)
{
m_profiler = profiler;
}
void killProcess();
qint64 pid();
@ -54,7 +68,7 @@ public: /* methods */
/**
* @brief prepare the process for launch (for multi-stage launch)
*/
virtual void arm();
virtual void executeTask() override;
/**
* @brief launch the armed instance
@ -85,6 +99,8 @@ public: /* HACK: MINECRAFT: split! */
protected: /* methods */
void preLaunch();
void updateInstance();
void makeReady();
void postLaunch();
QString substituteVariables(const QString &cmd) const;
void initializeEnvironment();
@ -106,6 +122,11 @@ signals:
*/
void prelaunch_failed(InstancePtr, int code, QProcess::ExitStatus status);
/**
* @brief emitted when the instance update fails
*/
void update_failed(InstancePtr);
/**
* @brief emitted when the PostLaunchCommand fails
*/
@ -116,6 +137,11 @@ signals:
*/
void ended(InstancePtr, int code, QProcess::ExitStatus status);
/**
* @brief emitted when the launch preparations are done
*/
void readyForLaunch(std::shared_ptr<BaseLauncher> launcher);
/**
* @brief emitted when we want to log something
* @param text the text to log
@ -132,6 +158,8 @@ protected slots:
void on_state(LoggedProcess::State state);
void on_post_state(LoggedProcess::State state);
void checkJavaFinished(JavaCheckResult result);
protected:
InstancePtr m_instance;
@ -139,16 +167,27 @@ protected:
LoggedProcess m_postlaunchprocess;
LoggedProcess m_process;
QProcessEnvironment m_env;
BaseProfilerFactory * m_profiler = nullptr;
bool killed = false;
QString m_header;
// for java checker and launch
QString m_javaPath;
qlonglong m_javaUnixTime;
protected: /* HACK: MINECRAFT: split! */
AuthSessionPtr m_session;
QString launchScript;
QString m_nativeFolder;
std::shared_ptr<JavaChecker> m_JavaChecker;
std::shared_ptr<Task> m_updateTask;
protected: /* HACK: MINECRAFT: split! */
bool checkJava(QString path);
void checkJava();
QStringList javaArguments() const;
private slots:
void updateFinished();
};
class BaseProfilerFactory;

View File

@ -43,7 +43,7 @@ public:
{
return instanceRoot();
};
virtual BaseLauncher* prepareForLaunch(AuthSessionPtr)
virtual std::shared_ptr<BaseLauncher> prepareForLaunch(AuthSessionPtr)
{
return nullptr;
}

View File

@ -95,7 +95,7 @@ std::shared_ptr<Task> LegacyInstance::doUpdate()
return std::shared_ptr<Task>(new LegacyUpdate(this, this));
}
BaseLauncher *LegacyInstance::prepareForLaunch(AuthSessionPtr account)
std::shared_ptr<BaseLauncher> LegacyInstance::prepareForLaunch(AuthSessionPtr account)
{
QString launchScript;
QIcon icon = ENV.icons()->getIcon(iconKey());

View File

@ -111,7 +111,7 @@ public:
virtual void setShouldUpdate(bool val) override;
virtual std::shared_ptr<Task> doUpdate() override;
virtual BaseLauncher *prepareForLaunch(AuthSessionPtr account) override;
virtual std::shared_ptr<BaseLauncher> prepareForLaunch(AuthSessionPtr account) override;
virtual void cleanupAfterRun() override;
virtual QString getStatusbarDescription() override;

View File

@ -123,7 +123,7 @@ QStringList OneSixInstance::processMinecraftArgs(AuthSessionPtr session)
return parts;
}
BaseLauncher *OneSixInstance::prepareForLaunch(AuthSessionPtr session)
std::shared_ptr<BaseLauncher> OneSixInstance::prepareForLaunch(AuthSessionPtr session)
{
QString launchScript;
QIcon icon = ENV.icons()->getIcon(iconKey());

View File

@ -49,7 +49,7 @@ public:
virtual QString instanceConfigFolder() const override;
virtual std::shared_ptr<Task> doUpdate() override;
virtual BaseLauncher *prepareForLaunch(AuthSessionPtr account) override;
virtual std::shared_ptr<BaseLauncher> prepareForLaunch(AuthSessionPtr account) override;
virtual void cleanupAfterRun() override;

View File

@ -7,7 +7,7 @@ BaseProfiler::BaseProfiler(SettingsObjectPtr settings, InstancePtr instance, QOb
{
}
void BaseProfiler::beginProfiling(BaseLauncher *process)
void BaseProfiler::beginProfiling(std::shared_ptr<BaseLauncher> process)
{
beginProfilingImpl(process);
}

View File

@ -15,13 +15,13 @@ public:
public
slots:
void beginProfiling(BaseLauncher *process);
void beginProfiling(std::shared_ptr<BaseLauncher> process);
void abortProfiling();
protected:
QProcess *m_profilerProcess;
virtual void beginProfilingImpl(BaseLauncher *process) = 0;
virtual void beginProfilingImpl(std::shared_ptr<BaseLauncher> process) = 0;
virtual void abortProfilingImpl();
signals:

View File

@ -18,7 +18,7 @@ private slots:
void profilerFinished(int exit, QProcess::ExitStatus status);
protected:
void beginProfilingImpl(BaseLauncher *process);
void beginProfilingImpl(std::shared_ptr<BaseLauncher> process);
private:
int listeningPort = 0;
@ -48,7 +48,7 @@ void JProfiler::profilerFinished(int exit, QProcess::ExitStatus status)
}
}
void JProfiler::beginProfilingImpl(BaseLauncher *process)
void JProfiler::beginProfilingImpl(std::shared_ptr<BaseLauncher> process)
{
listeningPort = globalSettings->get("JProfilerPort").toInt();
QProcess *profiler = new QProcess(this);

View File

@ -18,7 +18,7 @@ private slots:
void profilerFinished(int exit, QProcess::ExitStatus status);
protected:
void beginProfilingImpl(BaseLauncher *process);
void beginProfilingImpl(std::shared_ptr<BaseLauncher> process);
};
@ -45,7 +45,7 @@ void JVisualVM::profilerFinished(int exit, QProcess::ExitStatus status)
}
}
void JVisualVM::beginProfilingImpl(BaseLauncher *process)
void JVisualVM::beginProfilingImpl(std::shared_ptr<BaseLauncher> process)
{
QProcess *profiler = new QProcess(this);
QStringList profilerArgs =