Files
gsmartcontrol/src/applib/cmdex_sync.cpp
T

247 lines
5.6 KiB
C++

/******************************************************************************
License: GNU General Public License v3.0 only
Copyright:
(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>
******************************************************************************/
/// \file
/// \author Alexander Shaduri
/// \ingroup applib
/// \weakgroup applib
/// @{
#include <glib.h> // g_usleep()
#include <glibmm/i18n.h>
#include "cmdex_sync.h"
namespace {
/// "Execution finished" signal
sigc::signal<void, const CmdexSyncCommandInfo&> s_cmdex_sync_signal_execute_finish;
}
cmdex_signal_execute_finish_type& cmdex_sync_signal_execute_finish()
{
return s_cmdex_sync_signal_execute_finish;
}
CmdexSync::CmdexSync(std::string command_name, std::string command_args)
: CmdexSync()
{
this->set_command(std::move(command_name), std::move(command_args));
}
CmdexSync::CmdexSync()
{
/// Translators: {command} will be replaced by command name.
running_msg_ = _("Running {command}...");
set_error_header(std::string(_("An error occurred while executing command:")) + "\n\n");
}
void CmdexSync::set_command(std::string command_name, std::string command_args)
{
cmdex_.set_command(command_name, command_args);
// keep a copy locally to avoid locking on get() every time
command_name_ = std::move(command_name);
command_args_ = std::move(command_args);
}
std::string CmdexSync::get_command_name() const
{
return command_name_;
}
std::string CmdexSync::get_command_args() const
{
return command_args_;
}
bool CmdexSync::execute()
{
set_error_msg(""); // clear old error if present
bool slot_connected = !(signal_execute_tick.slots().begin() == signal_execute_tick.slots().end());
if (slot_connected && !signal_execute_tick.emit(TickStatus::starting))
return false;
if (!cmdex_.execute()) { // try to execute
debug_out_error("app", DBG_FUNC_MSG << "cmdex_.execute() failed.\n");
import_error(); // get error from cmdex and display warnings if needed
// emit this for execution loggers
cmdex_sync_signal_execute_finish().emit(CmdexSyncCommandInfo(get_command_name(),
get_command_args(), get_stdout_str(), get_stderr_str(), get_error_msg()));
if (slot_connected)
signal_execute_tick.emit(TickStatus::failed);
return false;
}
bool stop_requested = false; // stop requested from tick function
bool signals_sent = false; // stop signals sent
while(!cmdex_.stopped_cleanup_needed()) {
if (!stop_requested) { // running and no stop requested yet
// call the tick function with "running" periodically.
// if it returns false, try to stop.
if (slot_connected && !signal_execute_tick.emit(TickStatus::running)) {
debug_out_info("app", DBG_FUNC_MSG << "execute_tick slot returned false, trying to stop the program.\n");
stop_requested = true;
}
}
if (stop_requested && !signals_sent) { // stop request received
// send the stop request to the command
if (!cmdex_.try_stop()) { // try sigterm. this returns false if it can't be done (no permissions, zombie)
debug_out_warn("app", DBG_FUNC_MSG << "cmdex_.try_stop() returned false.\n");
}
// set sigkill timeout to 3 sec (in case sigterm fails); won't do anything if already exited.
cmdex_.set_stop_timeouts(std::chrono::milliseconds(0), forced_kill_timeout_msec_);
// import_error(); // don't need errors here - they will be available later anyway.
signals_sent = true;
}
// alert the tick function
if (stop_requested && slot_connected) {
signal_execute_tick.emit(TickStatus::stopping); // ignore returned value here
}
// Without this, no event sources will be processed and the program will
// hang waiting for the child to exit (the watch handler won't be called).
// Note: If you have an idle callback, g_main_context_pending() will
// always return true (until the idle callback returns false and unregisters itself).
while(g_main_context_pending(nullptr)) {
g_main_context_iteration(nullptr, false);
}
g_usleep(50*1000); // 50 msec. avoids 100% CPU usage.
}
// command exited, do a cleanup.
cmdex_.stopped_cleanup();
import_error(); // get error from cmdex and display warnings if needed
// emit this for execution loggers
cmdex_sync_signal_execute_finish().emit(CmdexSyncCommandInfo(get_command_name(),
get_command_args(), get_stdout_str(), get_stderr_str(), get_error_msg()));
if (slot_connected)
signal_execute_tick.emit(TickStatus::stopped); // last call
return true;
}
void CmdexSync::set_forced_kill_timeout(std::chrono::milliseconds timeout_msec)
{
forced_kill_timeout_msec_ = timeout_msec;
}
std::string CmdexSync::get_error_msg(bool with_header) const
{
if (with_header)
return error_header_ + error_msg_;
return error_msg_;
}
void CmdexSync::set_running_msg(const std::string& msg)
{
running_msg_ = msg;
}
void CmdexSync::set_error_header(const std::string& msg)
{
error_header_ = msg;
}
std::string CmdexSync::get_error_header()
{
return error_header_;
}
void CmdexSync::import_error()
{
Cmdex::error_list_t errors = cmdex_.get_errors(); // these are not clones
hz::ErrorBase* e = nullptr;
if (!errors.empty())
e = errors.back()->clone();
cmdex_.clear_errors(); // and clear them
if (e) { // if error is present, alert the user
on_error_warn(e);
}
}
void CmdexSync::on_error_warn(hz::ErrorBase* e)
{
if (e) {
set_error_msg(e->get_message()); // this message will be displayed
}
}
void CmdexSync::set_error_msg(const std::string& error_msg)
{
error_msg_ = error_msg;
}
std::string CmdexSync::get_running_msg() const
{
return running_msg_;
}
Cmdex& CmdexSync::get_command_executor()
{
return cmdex_;
}
/// @}