GH-1053 move launch related things and rename them

This commit is contained in:
Petr Mrázek
2015-07-10 01:11:06 +02:00
parent 5dd48e89f5
commit 8e7caf4e25
24 changed files with 91 additions and 89 deletions

641
logic/launch/LaunchTask.cpp Normal file
View File

@ -0,0 +1,641 @@
/* Copyright 2013-2015 MultiMC Contributors
*
* Authors: Orochimarufan <orochimarufan.x3@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "launch/LaunchTask.h"
#include "MessageLevel.h"
#include "MMCStrings.h"
#include "java/JavaChecker.h"
#include "tasks/Task.h"
#include <pathutils.h>
#include <QDebug>
#include <QDir>
#include <QEventLoop>
#include <QRegularExpression>
#include <QCoreApplication>
#include <QStandardPaths>
#define IBUS "@im=ibus"
void LaunchTask::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 LaunchTask::init()
{
initializeEnvironment();
m_process.setProcessEnvironment(m_env);
connect(&m_process, &LoggedProcess::log, this, &LaunchTask::on_log);
connect(&m_process, &LoggedProcess::stateChanged, this, &LaunchTask::on_state);
m_prelaunchprocess.setProcessEnvironment(m_env);
connect(&m_prelaunchprocess, &LoggedProcess::log, this, &LaunchTask::on_log);
connect(&m_prelaunchprocess, &LoggedProcess::stateChanged, this, &LaunchTask::on_pre_state);
m_postlaunchprocess.setProcessEnvironment(m_env);
connect(&m_postlaunchprocess, &LoggedProcess::log, this, &LaunchTask::on_log);
connect(&m_postlaunchprocess, &LoggedProcess::stateChanged, this, &LaunchTask::on_post_state);
m_instance->setRunning(true);
}
std::shared_ptr<LaunchTask> LaunchTask::create(MinecraftInstancePtr inst)
{
std::shared_ptr<LaunchTask> proc(new LaunchTask(inst));
proc->init();
return proc;
}
LaunchTask::LaunchTask(InstancePtr instance): m_instance(instance)
{
}
QString LaunchTask::censorPrivateInfo(QString in)
{
if (!m_session)
return in;
if (m_session->session != "-")
in.replace(m_session->session, "<SESSION ID>");
in.replace(m_session->access_token, "<ACCESS TOKEN>");
in.replace(m_session->client_token, "<CLIENT TOKEN>");
in.replace(m_session->uuid, "<PROFILE ID>");
in.replace(m_session->player_name, "<PROFILE NAME>");
auto i = m_session->u.properties.begin();
while (i != m_session->u.properties.end())
{
in.replace(i.value(), "<" + i.key().toUpper() + ">");
++i;
}
return in;
}
// console window
MessageLevel::Enum LaunchTask::guessLevel(const QString &line, MessageLevel::Enum level)
{
QRegularExpression re("\\[(?<timestamp>[0-9:]+)\\] \\[[^/]+/(?<level>[^\\]]+)\\]");
auto match = re.match(line);
if(match.hasMatch())
{
// New style logs from log4j
QString timestamp = match.captured("timestamp");
QString levelStr = match.captured("level");
if(levelStr == "INFO")
level = MessageLevel::Message;
if(levelStr == "WARN")
level = MessageLevel::Warning;
if(levelStr == "ERROR")
level = MessageLevel::Error;
if(levelStr == "FATAL")
level = MessageLevel::Fatal;
if(levelStr == "TRACE" || levelStr == "DEBUG")
level = MessageLevel::Debug;
}
else
{
// Old style forge logs
if (line.contains("[INFO]") || line.contains("[CONFIG]") || line.contains("[FINE]") ||
line.contains("[FINER]") || line.contains("[FINEST]"))
level = MessageLevel::Message;
if (line.contains("[SEVERE]") || line.contains("[STDERR]"))
level = MessageLevel::Error;
if (line.contains("[WARNING]"))
level = MessageLevel::Warning;
if (line.contains("[DEBUG]"))
level = MessageLevel::Debug;
}
if (line.contains("overwriting existing"))
return MessageLevel::Fatal;
if (line.contains("Exception in thread") || line.contains(QRegularExpression("\\s+at ")))
return MessageLevel::Error;
return level;
}
QMap<QString, QString> LaunchTask::getVariables() const
{
auto mcInstance = std::dynamic_pointer_cast<MinecraftInstance>(m_instance);
QMap<QString, QString> out;
out.insert("INST_NAME", mcInstance->name());
out.insert("INST_ID", mcInstance->id());
out.insert("INST_DIR", QDir(mcInstance->instanceRoot()).absolutePath());
out.insert("INST_MC_DIR", QDir(mcInstance->minecraftRoot()).absolutePath());
out.insert("INST_JAVA", mcInstance->settings()->get("JavaPath").toString());
out.insert("INST_JAVA_ARGS", javaArguments().join(' '));
return out;
}
QStringList LaunchTask::javaArguments() const
{
QStringList args;
// custom args go first. we want to override them if we have our own here.
args.append(m_instance->extraArguments());
// OSX dock icon and name
#ifdef Q_OS_MAC
args << "-Xdock:icon=icon.png";
args << QString("-Xdock:name=\"%1\"").arg(m_instance->windowTitle());
#endif
// HACK: Stupid hack for Intel drivers. See: https://mojang.atlassian.net/browse/MCL-767
#ifdef Q_OS_WIN32
args << QString("-XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_"
"minecraft.exe.heapdump");
#endif
args << QString("-Xms%1m").arg(m_instance->settings()->get("MinMemAlloc").toInt());
args << QString("-Xmx%1m").arg(m_instance->settings()->get("MaxMemAlloc").toInt());
// No PermGen in newer java.
auto javaVersion = m_instance->settings()->get("JavaVersion");
if(Strings::naturalCompare(javaVersion.toString(), "1.8.0", Qt::CaseInsensitive) < 0)
{
auto permgen = m_instance->settings()->get("PermGen").toInt();
if (permgen != 64)
{
args << QString("-XX:PermSize=%1m").arg(permgen);
}
}
args << "-Duser.language=en";
if (!m_nativeFolder.isEmpty())
args << QString("-Djava.library.path=%1").arg(m_nativeFolder);
args << "-jar" << PathCombine(QCoreApplication::applicationDirPath(), "jars", "NewLaunch.jar");
return args;
}
void LaunchTask::checkJava()
{
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(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)
{
m_JavaChecker = std::make_shared<JavaChecker>();
bool successful = false;
QString errorLog;
QString version;
emit log(tr("Checking Java version..."), MessageLevel::MultiMC);
connect(m_JavaChecker.get(), &JavaChecker::checkFinished, this, &LaunchTask::checkJavaFinished);
m_JavaChecker->m_path = realJavaPath;
m_JavaChecker->performCheck();
}
preLaunch();
}
void LaunchTask::checkJavaFinished(JavaCheckResult result)
{
if(!result.valid)
{
// 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(line, MessageLevel::Error);
}
emit log("\nCheck your MultiMC Java settings.", MessageLevel::MultiMC);
emitFailed(tr("Could not start java!"));
}
else
{
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();
}
}
void LaunchTask::executeTask()
{
printHeader();
emit log("Minecraft folder is:\n" + m_process.workingDirectory() + "\n\n");
checkJava();
}
void LaunchTask::launch()
{
QString launchString("launch\n");
m_process.write(launchString.toUtf8());
}
void LaunchTask::abort()
{
QString launchString("abort\n");
m_process.write(launchString.toUtf8());
}
void LaunchTask::setWorkdir(QString path)
{
QDir mcDir(path);
m_process.setWorkingDirectory(mcDir.absolutePath());
m_prelaunchprocess.setWorkingDirectory(mcDir.absolutePath());
m_postlaunchprocess.setWorkingDirectory(mcDir.absolutePath());
}
void LaunchTask::printHeader()
{
emit log(m_header);
}
void LaunchTask::on_log(QStringList lines, MessageLevel::Enum level)
{
logOutput(lines, level);
}
void LaunchTask::logOutput(const QStringList &lines, MessageLevel::Enum defaultLevel)
{
for (auto & line: lines)
{
logOutput(line, defaultLevel);
}
}
void LaunchTask::logOutput(QString line, MessageLevel::Enum level)
{
// if the launcher part set a log level, use it
auto innerLevel = MessageLevel::fromLine(line);
if(innerLevel != MessageLevel::Unknown)
{
level = innerLevel;
}
// If the level is still undetermined, guess level
if (level == MessageLevel::StdErr || level == MessageLevel::StdOut || level == MessageLevel::Unknown)
{
level = this->guessLevel(line, level);
}
// censor private user info
line = censorPrivateInfo(line);
emit log(line, level);
}
void LaunchTask::preLaunch()
{
QString prelaunch_cmd = m_instance->settings()->get("PreLaunchCommand").toString();
if (!prelaunch_cmd.isEmpty())
{
prelaunch_cmd = substituteVariables(prelaunch_cmd);
// Launch
emit log(tr("Running Pre-Launch command: %1").arg(prelaunch_cmd));
m_prelaunchprocess.start(prelaunch_cmd);
}
else
{
on_pre_state(LoggedProcess::Skipped);
}
}
void LaunchTask::on_pre_state(LoggedProcess::State state)
{
switch(state)
{
case LoggedProcess::Aborted:
case LoggedProcess::Crashed:
case LoggedProcess::FailedToStart:
{
QString error = tr("Pre-Launch command failed with code %1.\n\n").arg(m_prelaunchprocess.exitCode());
emit log(error, MessageLevel::Fatal);
emitFailed(error);
return;
}
case LoggedProcess::Finished:
{
emit log(tr("Pre-Launch command ran successfully.\n\n"));
}
case LoggedProcess::Skipped:
{
m_instance->reload();
updateInstance();
}
default:
break;
}
}
void LaunchTask::updateInstance()
{
m_updateTask = m_instance->createUpdateTask();
if(m_updateTask)
{
connect(m_updateTask.get(), SIGNAL(finished()), this, SLOT(updateFinished()));
m_updateTask->start();
return;
}
makeReady();
}
void LaunchTask::updateFinished()
{
if(m_updateTask->successful())
{
doJarModding();
}
else
{
QString reason = tr("Instance update failed because: %1.\n\n").arg(m_updateTask->failReason());
emit log(reason, MessageLevel::Fatal);
emitFailed(reason);
}
}
void LaunchTask::doJarModding()
{
m_jarModTask = m_instance->createJarModdingTask();
if(!m_jarModTask)
{
jarModdingSucceeded();
}
connect(m_jarModTask.get(), SIGNAL(succeeded()), this, SLOT(jarModdingSucceeded()));
connect(m_jarModTask.get(), SIGNAL(failed(QString)), this, SLOT(jarModdingFailed(QString)));
m_jarModTask->start();
}
void LaunchTask::jarModdingSucceeded()
{
makeReady();
}
void LaunchTask::jarModdingFailed(QString reason)
{
emitFailed(reason);
}
void LaunchTask::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())
{
QString reason = tr("The wrapper command \"%1\" couldn't be found.").arg(wrapperCommand);
emit log(reason, MessageLevel::Fatal);
emitFailed(reason);
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
QString reason = tr("Could not launch minecraft!");
emit log(reason, MessageLevel::Fatal);
emitFailed(reason);
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();
}
void LaunchTask::on_state(LoggedProcess::State state)
{
QProcess::ExitStatus estat = QProcess::NormalExit;
switch(state)
{
case LoggedProcess::Aborted:
case LoggedProcess::Crashed:
case LoggedProcess::FailedToStart:
{
estat = QProcess::CrashExit;
emitFailed("Game crashed.");
return;
}
case LoggedProcess::Finished:
{
auto exitCode = m_process.exitCode();
m_postlaunchprocess.processEnvironment().insert("INST_EXITCODE", QString(exitCode));
// run post-exit
postLaunch();
break;
}
case LoggedProcess::Skipped:
qWarning() << "Illegal game state: Skipped";
break;
case LoggedProcess::Running:
m_instance->setLastLaunch();
break;
default:
break;
}
}
void LaunchTask::killProcess()
{
killed = true;
if (m_prelaunchprocess.state() == LoggedProcess::Running)
{
m_prelaunchprocess.kill();
}
else if(m_process.state() == LoggedProcess::Running)
{
m_process.kill();
}
else if(m_postlaunchprocess.state() == LoggedProcess::Running)
{
m_postlaunchprocess.kill();
}
}
void LaunchTask::postLaunch()
{
if(killed)
return;
QString postlaunch_cmd = m_instance->settings()->get("PostExitCommand").toString();
if (!postlaunch_cmd.isEmpty())
{
postlaunch_cmd = substituteVariables(postlaunch_cmd);
emit log(tr("Running Post-Launch command: %1").arg(postlaunch_cmd));
m_postlaunchprocess.start(postlaunch_cmd);
return;
}
emitSucceeded();
}
void LaunchTask::on_post_state(LoggedProcess::State state)
{
switch(state)
{
case LoggedProcess::Aborted:
case LoggedProcess::Crashed:
case LoggedProcess::FailedToStart:
{
QString error = tr("Post-Launch command failed with code %1.\n\n").arg(m_postlaunchprocess.exitCode());
emit log(error, MessageLevel::Error);
emitFailed(error);
}
case LoggedProcess::Finished:
{
emit log(tr("Post-Launch command ran successfully.\n\n"));
}
case LoggedProcess::Skipped:
{
emitSucceeded();
}
default:
break;
}
}
void LaunchTask::emitSucceeded()
{
m_instance->cleanupAfterRun();
m_instance->setRunning(false);
Task::emitSucceeded();
}
void LaunchTask::emitFailed(QString reason)
{
m_instance->cleanupAfterRun();
m_instance->setRunning(false);
Task::emitFailed(reason);
}
QString LaunchTask::substituteVariables(const QString &cmd) const
{
QString out = cmd;
auto variables = getVariables();
for (auto it = variables.begin(); it != variables.end(); ++it)
{
out.replace("$" + it.key(), it.value());
}
auto env = QProcessEnvironment::systemEnvironment();
for (auto var : env.keys())
{
out.replace("$" + var, env.value(var));
}
return out;
}
qint64 LaunchTask::pid()
{
#ifdef Q_OS_WIN
struct _PROCESS_INFORMATION *procinfo = m_process.pid();
return procinfo->dwProcessId;
#else
return m_process.pid();
#endif
}

178
logic/launch/LaunchTask.h Normal file
View File

@ -0,0 +1,178 @@
/* Copyright 2013-2015 MultiMC Contributors
*
* Authors: Orochimarufan <orochimarufan.x3@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <QProcess>
#include "BaseInstance.h"
#include "MessageLevel.h"
#include "LoggedProcess.h"
/* HACK: MINECRAFT: split! */
#include "minecraft/MinecraftInstance.h"
#include "java/JavaChecker.h"
#include "QObjectPtr.h"
#include "tasks/Task.h"
class ProcessTask
{
};
class BaseProfilerFactory;
class LaunchTask: public Task
{
Q_OBJECT
protected:
explicit LaunchTask(InstancePtr instance);
void init();
public: /* methods */
static std::shared_ptr<LaunchTask> create(MinecraftInstancePtr inst);
virtual ~LaunchTask() {};
InstancePtr instance()
{
return m_instance;
}
/// Set the text printed on top of the log
void setHeader(QString header)
{
m_header = header;
}
void setWorkdir(QString path);
void killProcess();
qint64 pid();
/**
* @brief prepare the process for launch (for multi-stage launch)
*/
virtual void executeTask() override;
/**
* @brief launch the armed instance
*/
virtual void launch();
/**
* @brief abort launch
*/
virtual void abort();
public: /* HACK: MINECRAFT: split! */
void setLaunchScript(QString script)
{
launchScript = script;
}
void setNativeFolder(QString natives)
{
m_nativeFolder = natives;
}
inline void setLogin(AuthSessionPtr session)
{
m_session = session;
}
protected: /* methods */
void preLaunch();
void updateInstance();
void doJarModding();
void makeReady();
void postLaunch();
virtual void emitFailed(QString reason);
virtual void emitSucceeded();
QString substituteVariables(const QString &cmd) const;
void initializeEnvironment();
void printHeader();
virtual QMap<QString, QString> getVariables() const;
virtual QString censorPrivateInfo(QString in);
virtual MessageLevel::Enum guessLevel(const QString &message, MessageLevel::Enum defaultLevel);
protected slots:
void jarModdingSucceeded();
void jarModdingFailed(QString reason);
signals:
/**
* @brief emitted when the launch preparations are done
*/
void readyForLaunch();
/**
* @brief emitted when we want to log something
* @param text the text to log
* @param level the level to log at
*/
void log(QString text, MessageLevel::Enum level = MessageLevel::MultiMC);
protected slots:
void on_log(QStringList lines, MessageLevel::Enum level);
void logOutput(const QStringList& lines, MessageLevel::Enum defaultLevel = MessageLevel::Message);
void logOutput(QString line, MessageLevel::Enum defaultLevel = MessageLevel::Message);
void on_pre_state(LoggedProcess::State state);
void on_state(LoggedProcess::State state);
void on_post_state(LoggedProcess::State state);
protected:
InstancePtr m_instance;
LoggedProcess m_prelaunchprocess;
LoggedProcess m_postlaunchprocess;
LoggedProcess m_process;
QProcessEnvironment m_env;
BaseProfilerFactory * m_profiler = nullptr;
bool killed = false;
QString m_header;
/**
* java check step
*/
protected slots:
void checkJavaFinished(JavaCheckResult result);
protected:
// for java checker and launch
QString m_javaPath;
qlonglong m_javaUnixTime;
std::shared_ptr<JavaChecker> m_JavaChecker;
protected: /* HACK: MINECRAFT: split! */
AuthSessionPtr m_session;
QString launchScript;
QString m_nativeFolder;
std::shared_ptr<Task> m_updateTask;
std::shared_ptr<Task> m_jarModTask;
protected: /* HACK: MINECRAFT: split! */
void checkJava();
QStringList javaArguments() const;
private slots:
void updateFinished();
};
class BaseProfilerFactory;

View File

@ -0,0 +1,147 @@
#include "LoggedProcess.h"
#include "MessageLevel.h"
#include <QDebug>
LoggedProcess::LoggedProcess(QObject *parent) : QProcess(parent)
{
// QProcess has a strange interface... let's map a lot of those into a few.
connect(this, &QProcess::readyReadStandardOutput, this, &LoggedProcess::on_stdOut);
connect(this, &QProcess::readyReadStandardError, this, &LoggedProcess::on_stdErr);
connect(this, SIGNAL(finished(int,QProcess::ExitStatus)), SLOT(on_exit(int,QProcess::ExitStatus)));
connect(this, SIGNAL(error(QProcess::ProcessError)), this, SLOT(on_error(QProcess::ProcessError)));
connect(this, &QProcess::stateChanged, this, &LoggedProcess::on_stateChange);
}
QStringList reprocess(const QByteArray & data, QString & leftover)
{
QString str = leftover + QString::fromLocal8Bit(data);
str.remove('\r');
QStringList lines = str.split("\n");
leftover = lines.takeLast();
return lines;
}
void LoggedProcess::on_stdErr()
{
auto lines = reprocess(readAllStandardError(), m_err_leftover);
emit log(lines, MessageLevel::StdErr);
}
void LoggedProcess::on_stdOut()
{
auto lines = reprocess(readAllStandardOutput(), m_out_leftover);
emit log(lines, MessageLevel::StdOut);
}
void LoggedProcess::on_exit(int exit_code, QProcess::ExitStatus status)
{
// Flush console window
if (!m_err_leftover.isEmpty())
{
emit log({m_err_leftover}, MessageLevel::StdErr);
m_err_leftover.clear();
}
if (!m_out_leftover.isEmpty())
{
emit log({m_err_leftover}, MessageLevel::StdOut);
m_out_leftover.clear();
}
// based on state, send signals
if (!m_is_aborting)
{
if (status == QProcess::NormalExit)
{
//: Message displayed on instance exit
emit log({tr("Process exited with code %1.").arg(exit_code)}, MessageLevel::MultiMC);
changeState(LoggedProcess::Finished);
}
else
{
//: Message displayed on instance crashed
if(exit_code == -1)
emit log({tr("Process crashed.").arg(exit_code)}, MessageLevel::MultiMC);
else
emit log({tr("Process crashed with exitcode %1.").arg(exit_code)}, MessageLevel::MultiMC);
changeState(LoggedProcess::Crashed);
}
}
else
{
//: Message displayed after the instance exits due to kill request
emit log({tr("Process was killed by user.")}, MessageLevel::Error);
changeState(LoggedProcess::Aborted);
}
}
void LoggedProcess::on_error(QProcess::ProcessError error)
{
switch(error)
{
case QProcess::FailedToStart:
{
emit log({tr("The process failed to start.")}, MessageLevel::Fatal);
changeState(LoggedProcess::FailedToStart);
break;
}
// we'll just ignore those... never needed them
case QProcess::Crashed:
case QProcess::ReadError:
case QProcess::Timedout:
case QProcess::UnknownError:
case QProcess::WriteError:
break;
}
}
void LoggedProcess::kill()
{
m_is_aborting = true;
QProcess::kill();
}
int LoggedProcess::exitCode() const
{
return m_exit_code;
}
void LoggedProcess::changeState(LoggedProcess::State state)
{
if(state == m_state)
return;
m_state = state;
emit stateChanged(m_state);
}
LoggedProcess::State LoggedProcess::state() const
{
return m_state;
}
void LoggedProcess::on_stateChange(QProcess::ProcessState state)
{
switch(state)
{
case QProcess::NotRunning:
break; // let's not - there are too many that handle this already.
case QProcess::Starting:
{
if(m_state != LoggedProcess::NotRunning)
{
qWarning() << "Wrong state change for process from state" << m_state << "to" << (int) LoggedProcess::Starting;
}
changeState(LoggedProcess::Starting);
return;
}
case QProcess::Running:
{
if(m_state != LoggedProcess::Starting)
{
qWarning() << "Wrong state change for process from state" << m_state << "to" << (int) LoggedProcess::Running;
}
changeState(LoggedProcess::Running);
return;
}
}
}

View File

@ -0,0 +1,60 @@
#pragma once
#include <QProcess>
#include "MessageLevel.h"
/*
* This is a basic process.
* It has line-based logging support and hides some of the nasty bits.
*/
class LoggedProcess : public QProcess
{
Q_OBJECT
public:
enum State
{
NotRunning,
Starting,
FailedToStart,
Running,
Finished,
Crashed,
Aborted,
Skipped
};
public:
explicit LoggedProcess(QObject* parent = 0);
virtual ~LoggedProcess() {};
State state() const;
int exitCode() const;
signals:
void log(QStringList lines, MessageLevel::Enum level);
void stateChanged(LoggedProcess::State state);
public slots:
/**
* @brief kill the process - equivalent to kill -9
*/
void kill();
private slots:
void on_stdErr();
void on_stdOut();
void on_exit(int exit_code, QProcess::ExitStatus status);
void on_error(QProcess::ProcessError error);
void on_stateChange(QProcess::ProcessState);
private:
void changeState(LoggedProcess::State state);
private:
QString m_err_leftover;
QString m_out_leftover;
bool m_killed = false;
State m_state = NotRunning;
int m_exit_code = 0;
bool m_is_aborting = false;
};

View File

@ -0,0 +1,36 @@
#include "MessageLevel.h"
MessageLevel::Enum MessageLevel::getLevel(const QString& levelName)
{
if (levelName == "MultiMC")
return MessageLevel::MultiMC;
else if (levelName == "Debug")
return MessageLevel::Debug;
else if (levelName == "Info")
return MessageLevel::Info;
else if (levelName == "Message")
return MessageLevel::Message;
else if (levelName == "Warning")
return MessageLevel::Warning;
else if (levelName == "Error")
return MessageLevel::Error;
else if (levelName == "Fatal")
return MessageLevel::Fatal;
// Skip PrePost, it's not exposed to !![]!
// Also skip StdErr and StdOut
else
return MessageLevel::Unknown;
}
MessageLevel::Enum MessageLevel::fromLine(QString &line)
{
// Level prefix
int endmark = line.indexOf("]!");
if (line.startsWith("!![") && endmark != -1)
{
auto level = MessageLevel::getLevel(line.left(endmark).mid(3));
line = line.mid(endmark + 2);
return level;
}
return MessageLevel::Unknown;
}

View File

@ -0,0 +1,28 @@
#pragma once
#include <QString>
/**
* @brief the MessageLevel Enum
* defines what level a log message is
*/
namespace MessageLevel
{
enum Enum
{
Unknown, /**< No idea what this is or where it came from */
StdOut, /**< Undetermined stderr messages */
StdErr, /**< Undetermined stdout messages */
MultiMC, /**< MultiMC Messages */
Debug, /**< Debug Messages */
Info, /**< Info Messages */
Message, /**< Standard Messages */
Warning, /**< Warnings */
Error, /**< Errors */
Fatal, /**< Fatal Errors */
};
MessageLevel::Enum getLevel(const QString &levelName);
/* Get message level from a line. Line is modified if it was successful. */
MessageLevel::Enum fromLine(QString &line);
}