Files
BloomPatched/src/Application.cpp

459 lines
17 KiB
C++
Raw Normal View History

2021-08-22 20:46:52 +01:00
#include "Application.hpp"
2021-04-04 21:04:12 +01:00
#include <iostream>
#include <csignal>
#include <thread>
#include <QJsonDocument>
#include <unistd.h>
#include <filesystem>
#include "src/Logger/Logger.hpp"
#include "src/Helpers/Paths.hpp"
2021-04-04 21:04:12 +01:00
#include "Exceptions/InvalidConfig.hpp"
namespace Bloom
{
using namespace Exceptions;
2021-04-04 21:04:12 +01:00
int Application::run(const std::vector<std::string>& arguments) {
try {
this->setName("Bloom");
2021-04-04 21:04:12 +01:00
if (!arguments.empty()) {
auto firstArg = arguments.front();
auto commandsToCallbackMapping = this->getCommandToHandlerMapping();
2021-04-04 21:04:12 +01:00
if (commandsToCallbackMapping.contains(firstArg)) {
// User has passed an argument that maps to a command callback - invoke the callback and shutdown
auto returnValue = commandsToCallbackMapping.at(firstArg)();
2021-04-04 21:04:12 +01:00
this->shutdown();
return returnValue;
}
2022-01-02 20:45:14 +00:00
// If the first argument didn't map to a command, we assume it's an environment name
this->selectedEnvironmentName = firstArg;
}
2021-04-04 21:04:12 +01:00
2021-04-24 16:22:04 +01:00
#ifdef BLOOM_DEBUG_BUILD
Logger::warning("This is a debug build - some functions may not work as expected.");
2021-04-24 16:22:04 +01:00
#endif
this->startup();
2021-04-04 21:04:12 +01:00
if (this->insightConfig->insightEnabled) {
this->insight = std::make_unique<Insight>(
this->eventManager,
this->projectConfig.value(),
this->environmentConfig.value(),
this->insightConfig.value(),
this->projectSettings.value().insightSettings
);
/*
* Before letting Insight occupy the main thread, process any pending events that accumulated
* during startup.
*/
this->applicationEventListener->dispatchCurrentEvents();
if (Thread::getThreadState() == ThreadState::READY) {
this->insight->run();
Logger::debug("Insight closed");
}
this->shutdown();
return EXIT_SUCCESS;
}
// Main event loop
while (Thread::getThreadState() == ThreadState::READY) {
this->applicationEventListener->waitAndDispatch();
}
2021-04-04 21:04:12 +01:00
}
catch (const InvalidConfig& exception) {
Logger::error(exception.getMessage());
2021-04-04 21:04:12 +01:00
}
catch (const Exception& exception) {
Logger::error(exception.getMessage());
2021-04-04 21:04:12 +01:00
}
this->shutdown();
return EXIT_SUCCESS;
}
2021-04-04 21:04:12 +01:00
bool Application::isRunningAsRoot() {
return geteuid() == 0;
2021-04-04 21:04:12 +01:00
}
void Application::startup() {
auto applicationEventListener = this->applicationEventListener;
this->eventManager.registerListener(applicationEventListener);
applicationEventListener->registerCallbackForEventType<Events::ShutdownApplication>(
std::bind(&Application::onShutdownApplicationRequest, this, std::placeholders::_1)
);
2021-04-04 21:04:12 +01:00
this->loadProjectSettings();
this->loadProjectConfiguration();
Logger::configure(this->projectConfig.value());
Logger::debug("Bloom version: " + Application::VERSION.toString());
2021-04-04 21:04:12 +01:00
this->blockAllSignalsOnCurrentThread();
this->startSignalHandler();
2021-04-04 21:04:12 +01:00
Logger::info("Selected environment: \"" + this->selectedEnvironmentName + "\"");
Logger::debug("Number of environments extracted from config: "
+ std::to_string(this->projectConfig->environments.size()));
applicationEventListener->registerCallbackForEventType<Events::TargetControllerThreadStateChanged>(
std::bind(&Application::onTargetControllerThreadStateChanged, this, std::placeholders::_1)
);
2021-04-04 21:04:12 +01:00
applicationEventListener->registerCallbackForEventType<Events::DebugServerThreadStateChanged>(
std::bind(&Application::onDebugServerThreadStateChanged, this, std::placeholders::_1)
);
2021-04-04 21:04:12 +01:00
this->startTargetController();
this->startDebugServer();
2021-04-04 21:04:12 +01:00
Thread::setThreadState(ThreadState::READY);
}
2021-04-04 21:04:12 +01:00
void Application::shutdown() {
auto appState = Thread::getThreadState();
if (appState == ThreadState::STOPPED || appState == ThreadState::SHUTDOWN_INITIATED) {
return;
}
2021-04-04 21:04:12 +01:00
Thread::setThreadState(ThreadState::SHUTDOWN_INITIATED);
Logger::info("Shutting down Bloom");
this->stopDebugServer();
this->stopTargetController();
this->stopSignalHandler();
2021-04-04 21:04:12 +01:00
this->saveProjectSettings();
Thread::setThreadState(ThreadState::STOPPED);
}
void Application::loadProjectSettings() {
const auto projectSettingsPath = Paths::projectSettingsPath();
auto jsonSettingsFile = QFile(QString::fromStdString(projectSettingsPath));
if (jsonSettingsFile.exists()) {
try {
if (!jsonSettingsFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
throw Exception("Failed to open settings file.");
}
const auto jsonObject = QJsonDocument::fromJson(jsonSettingsFile.readAll()).object();
jsonSettingsFile.close();
this->projectSettings = ProjectSettings(jsonObject);
return;
2022-01-22 16:14:29 +00:00
}
catch (const std::exception& exception) {
Logger::error(
"Failed to load project settings from " + projectSettingsPath + " - " + exception.what()
);
}
}
2022-01-22 16:14:29 +00:00
this->projectSettings = ProjectSettings();
}
2022-01-22 16:14:29 +00:00
void Application::saveProjectSettings() {
if (!this->projectSettings.has_value()) {
return;
}
const auto projectSettingsPath = Paths::projectSettingsPath();
auto jsonSettingsFile = QFile(QString::fromStdString(projectSettingsPath));
Logger::debug("Saving project settings to " + projectSettingsPath);
QDir().mkpath(QString::fromStdString(Paths::projectSettingsDirPath()));
2022-01-22 16:14:29 +00:00
try {
const auto jsonDocument = QJsonDocument(this->projectSettings->toJson());
2022-01-22 16:14:29 +00:00
if (!jsonSettingsFile.open(QIODevice::ReadWrite | QIODevice::Truncate | QIODevice::Text)) {
throw Exception(
"failed to open/create settings file (" + projectSettingsPath + "). Check file permissions."
);
}
2022-01-22 16:14:29 +00:00
jsonSettingsFile.write(jsonDocument.toJson());
jsonSettingsFile.close();
2022-01-22 16:14:29 +00:00
}
catch (const Exception& exception) {
Logger::error(
"Failed to save project settings - " + exception.getMessage()
2022-01-22 16:14:29 +00:00
);
}
}
void Application::loadProjectConfiguration() {
auto jsonConfigFile = QFile(QString::fromStdString(Paths::projectConfigPath()));
2021-04-04 21:04:12 +01:00
if (!jsonConfigFile.exists()) {
throw InvalidConfig("Bloom configuration file (bloom.json) not found. Working directory: "
+ Paths::projectDirPath());
}
2021-04-04 21:04:12 +01:00
if (!jsonConfigFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
throw InvalidConfig("Failed to load Bloom configuration file (bloom.json) Working directory: "
+ Paths::projectDirPath());
}
2021-04-04 21:04:12 +01:00
auto jsonObject = QJsonDocument::fromJson(jsonConfigFile.readAll()).object();
jsonConfigFile.close();
this->projectConfig = ProjectConfig(jsonObject);
// Validate the selected environment
if (!this->projectConfig->environments.contains(this->selectedEnvironmentName)) {
throw InvalidConfig(
"Environment (\"" + this->selectedEnvironmentName + "\") not found in configuration."
);
}
this->environmentConfig = this->projectConfig->environments.at(this->selectedEnvironmentName);
if (this->environmentConfig->insightConfig.has_value()) {
this->insightConfig = this->environmentConfig->insightConfig.value();
}
else if (this->projectConfig->insightConfig.has_value()) {
this->insightConfig = this->projectConfig->insightConfig.value();
}
else {
throw InvalidConfig("Insight configuration missing.");
}
if (this->environmentConfig->debugServerConfig.has_value()) {
this->debugServerConfig = this->environmentConfig->debugServerConfig.value();
}
else if (this->projectConfig->debugServerConfig.has_value()) {
this->debugServerConfig = this->projectConfig->debugServerConfig.value();
}
else {
throw InvalidConfig("Debug server configuration missing.");
}
}
2021-04-04 21:04:12 +01:00
int Application::presentHelpText() {
/*
* Silence all logging here, as we're just to display the help text and then exit the application. Any
* further logging will just be noise.
*/
Logger::silence();
2021-04-04 21:04:12 +01:00
// The file help.txt is included in the binary image as a resource. See src/resource.qrc
auto helpFile = QFile(QString::fromStdString(
Paths::compiledResourcesPath()
+ "/resources/help.txt"
)
);
2021-04-04 21:04:12 +01:00
if (!helpFile.open(QIODevice::ReadOnly)) {
// This should never happen - if it does, something has gone very wrong
throw Exception(
"Failed to open help file - please report this issue at " + Paths::homeDomainName()
+ "/report-issue"
);
}
2021-04-04 21:04:12 +01:00
std::cout << "Bloom v" << Application::VERSION.toString() << "\n";
std::cout << QTextStream(&helpFile).readAll().toUtf8().constData() << "\n";
return EXIT_SUCCESS;
}
2021-04-04 21:04:12 +01:00
int Application::presentVersionText() {
Logger::silence();
std::cout << "Bloom v" << Application::VERSION.toString() << "\n";
2021-04-24 16:22:04 +01:00
#ifdef BLOOM_DEBUG_BUILD
std::cout << "DEBUG BUILD - Compilation timestamp: " << __DATE__ << " " << __TIME__ << "\n";
2021-04-24 16:22:04 +01:00
#endif
std::cout << Paths::homeDomainName() + "/\n";
std::cout << "Nav Mohammed\n";
return EXIT_SUCCESS;
2021-04-04 21:04:12 +01:00
}
int Application::initProject() {
auto configFile = QFile(
QString::fromStdString(std::filesystem::current_path().string() + "/bloom.json")
);
2021-04-04 21:04:12 +01:00
if (configFile.exists()) {
throw Exception("Bloom configuration file (bloom.json) already exists in working directory.");
}
2021-04-04 21:04:12 +01:00
/*
* The file bloom.template.json is just a template Bloom config file that is included in the binary image as
* a resource. See src/resource.qrc
*
* We simply copy the template file into the user's working directory.
*/
auto templateConfigFile = QFile(
QString::fromStdString(Paths::compiledResourcesPath()+ "/resources/bloom.template.json")
);
2021-04-04 21:04:12 +01:00
if (!templateConfigFile.open(QIODevice::ReadOnly)) {
throw Exception(
"Failed to open template configuration file - please report this issue at "
+ Paths::homeDomainName() + "/report-issue"
);
}
2021-04-04 21:04:12 +01:00
if (!configFile.open(QIODevice::ReadWrite)) {
throw Exception("Failed to create Bloom configuration file (bloom.json)");
}
2021-04-04 21:04:12 +01:00
configFile.write(templateConfigFile.readAll());
configFile.close();
templateConfigFile.close();
Logger::info("Bloom configuration file (bloom.json) created in working directory.");
return EXIT_SUCCESS;
}
void Application::startSignalHandler() {
this->signalHandlerThread = std::thread(&SignalHandler::run, std::ref(this->signalHandler));
}
2021-04-04 21:04:12 +01:00
void Application::stopSignalHandler() {
if (this->signalHandler.getThreadState() != ThreadState::STOPPED
&& this->signalHandler.getThreadState() != ThreadState::UNINITIALISED
) {
this->signalHandler.triggerShutdown();
2021-04-04 21:04:12 +01:00
/*
* Send meaningless signal to the SignalHandler thread to have it shutdown. The signal will pull it out of
* a blocking state and allow it to action the shutdown. See SignalHandler::run() for more.
*/
pthread_kill(this->signalHandlerThread.native_handle(), SIGUSR1);
}
2021-04-04 21:04:12 +01:00
if (this->signalHandlerThread.joinable()) {
Logger::debug("Joining SignalHandler thread");
this->signalHandlerThread.join();
Logger::debug("SignalHandler thread joined");
}
2021-04-04 21:04:12 +01:00
}
void Application::startTargetController() {
this->targetController = std::make_unique<TargetController>(
this->eventManager,
this->projectConfig.value(),
this->environmentConfig.value()
);
this->targetControllerThread = std::thread(
&TargetController::run,
this->targetController.get()
2021-04-04 21:04:12 +01:00
);
auto tcStateChangeEvent =
this->applicationEventListener->waitForEvent<Events::TargetControllerThreadStateChanged>();
if (!tcStateChangeEvent.has_value() || tcStateChangeEvent->get()->getState() != ThreadState::READY) {
throw Exception("TargetController failed to startup.");
}
2021-04-04 21:04:12 +01:00
}
void Application::stopTargetController() {
if (this->targetController == nullptr) {
return;
}
auto targetControllerState = this->targetController->getThreadState();
if (targetControllerState == ThreadState::STARTING || targetControllerState == ThreadState::READY) {
this->eventManager.triggerEvent(std::make_shared<Events::ShutdownTargetController>());
this->applicationEventListener->waitForEvent<Events::TargetControllerThreadStateChanged>(
std::chrono::milliseconds(10000)
);
}
if (this->targetControllerThread.joinable()) {
Logger::debug("Joining TargetController thread");
this->targetControllerThread.join();
Logger::debug("TargetController thread joined");
}
2021-04-04 21:04:12 +01:00
}
void Application::startDebugServer() {
auto supportedDebugServers = this->getSupportedDebugServers();
if (!supportedDebugServers.contains(this->debugServerConfig->name)) {
throw Exceptions::InvalidConfig(
"DebugServer \"" + this->debugServerConfig->name + "\" not found."
);
}
2021-04-04 21:04:12 +01:00
this->debugServer = supportedDebugServers.at(this->debugServerConfig->name)();
Logger::info("Selected DebugServer: " + this->debugServer->getName());
2021-04-04 21:04:12 +01:00
this->debugServerThread = std::thread(
&DebugServers::DebugServer::run,
this->debugServer.get()
);
2021-04-04 21:04:12 +01:00
auto dsStateChangeEvent = this->applicationEventListener->waitForEvent<Events::DebugServerThreadStateChanged>();
2021-04-04 21:04:12 +01:00
if (!dsStateChangeEvent.has_value() || dsStateChangeEvent->get()->getState() != ThreadState::READY) {
throw Exception("DebugServer failed to startup.");
}
2021-04-04 21:04:12 +01:00
}
void Application::stopDebugServer() {
if (this->debugServer == nullptr) {
// DebugServer hasn't been resolved yet.
return;
}
auto debugServerState = this->debugServer->getThreadState();
if (debugServerState == ThreadState::STARTING || debugServerState == ThreadState::READY) {
this->eventManager.triggerEvent(std::make_shared<Events::ShutdownDebugServer>());
this->applicationEventListener->waitForEvent<Events::DebugServerThreadStateChanged>(
std::chrono::milliseconds(5000)
);
}
if (this->debugServerThread.joinable()) {
Logger::debug("Joining DebugServer thread");
this->debugServerThread.join();
Logger::debug("DebugServer thread joined");
}
2021-04-04 21:04:12 +01:00
}
void Application::onTargetControllerThreadStateChanged(const Events::TargetControllerThreadStateChanged& event) {
if (event.getState() == ThreadState::STOPPED || event.getState() == ThreadState::SHUTDOWN_INITIATED) {
// TargetController has unexpectedly shutdown - it must have encountered a fatal error.
this->shutdown();
}
2021-04-04 21:04:12 +01:00
}
void Application::onShutdownApplicationRequest(const Events::ShutdownApplication&) {
Logger::debug("ShutdownApplication event received.");
2021-04-04 21:04:12 +01:00
this->shutdown();
}
void Application::onDebugServerThreadStateChanged(const Events::DebugServerThreadStateChanged& event) {
if (event.getState() == ThreadState::STOPPED || event.getState() == ThreadState::SHUTDOWN_INITIATED) {
// DebugServer has unexpectedly shutdown - it must have encountered a fatal error.
this->shutdown();
}
2021-04-04 21:04:12 +01:00
}
}