diff options
Diffstat (limited to '')
-rw-r--r-- | contrib/epee/include/console_handler.h | 389 |
1 files changed, 276 insertions, 113 deletions
diff --git a/contrib/epee/include/console_handler.h b/contrib/epee/include/console_handler.h index 238abc80c..778679e75 100644 --- a/contrib/epee/include/console_handler.h +++ b/contrib/epee/include/console_handler.h @@ -1,6 +1,6 @@ // Copyright (c) 2006-2013, Andrey N. Sabelnikov, www.sabelnikov.net // All rights reserved. -// +// // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // * Redistributions of source code must retain the above copyright @@ -11,7 +11,7 @@ // * Neither the name of the Andrey N. Sabelnikov nor the // names of its contributors may be used to endorse or promote products // derived from this software without specific prior written permission. -// +// // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE @@ -22,141 +22,301 @@ // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// - - +// #pragma once +#include <atomic> +#include <condition_variable> +#include <functional> +#include <mutex> +#include <thread> + namespace epee { + class async_stdin_reader + { + public: + async_stdin_reader() + : m_run(true) + , m_has_read_request(false) + , m_read_status(state_init) + { + m_reader_thread = std::thread(std::bind(&async_stdin_reader::reader_thread_func, this)); + } + ~async_stdin_reader() + { + stop(); + } + // Not thread safe. Only one thread can call this method at once. + bool get_line(std::string& line) + { + if (!start_read()) + return false; + std::unique_lock<std::mutex> lock(m_response_mutex); + while (state_init == m_read_status) + { + m_response_cv.wait(lock); + } - template<class t_server> - bool empty_commands_handler(t_server* psrv, const std::string& command) - { - return true; - } - + bool res = false; + if (state_success == m_read_status) + { + line = m_line; + res = true; + } - template<class t_server, class chain_handler> - bool default_console_handler(t_server* psrv, chain_handler ch_handler, const std::string usage = "") - { - TRY_ENTRY(); - bool continue_handle = true; - while(continue_handle) + m_read_status = state_init; + + return res; + } + + void stop() + { + if (m_run) + { + m_run.store(false, std::memory_order_relaxed); + +#if defined(WIN32) + ::CloseHandle(::GetStdHandle(STD_INPUT_HANDLE)); +#endif + + m_request_cv.notify_one(); + m_reader_thread.join(); + } + } + + private: + bool start_read() + { + std::unique_lock<std::mutex> lock(m_request_mutex); + if (!m_run.load(std::memory_order_relaxed) || m_has_read_request) + return false; + + m_has_read_request = true; + m_request_cv.notify_one(); + return true; + } + + bool wait_read() { - char command_buff[400] = {0}; - std::string command; - std::cin.getline(command_buff, 399); - if(std::cin.eof() || std::cin.fail()) + std::unique_lock<std::mutex> lock(m_request_mutex); + while (m_run.load(std::memory_order_relaxed) && !m_has_read_request) { - LOG_PRINT("std::cin.eof() or std::cin.fail(), stopping...", LOG_LEVEL_0); - continue_handle = false; - break; + m_request_cv.wait(lock); } - command = command_buff; - if(!command.compare("exit") || !command.compare("q") ) + if (m_has_read_request) { - psrv->send_stop_signal(); - continue_handle = false; - }else if ( !command.compare(0, 7, "set_log")) + m_has_read_request = false; + return true; + } + + return false; + } + + bool wait_stdin_data() + { +#if !defined(WIN32) + int stdin_fileno = ::fileno(stdin); + + while (m_run.load(std::memory_order_relaxed)) { - //parse set_log command - if(command.size() != 9) + fd_set read_set; + FD_ZERO(&read_set); + FD_SET(stdin_fileno, &read_set); + + struct timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 100 * 1000; + + int retval = ::select(stdin_fileno + 1, &read_set, NULL, NULL, &tv); + if (retval < 0) + return false; + else if (0 < retval) + return true; + } +#endif + + return true; + } + + void reader_thread_func() + { + while (true) + { + if (!wait_read()) + break; + + std::string line; + bool read_ok = true; + if (wait_stdin_data()) { - std::cout << "wrong syntax: " << command << std::endl << "use set_log n" << std::endl; - continue; + if (m_run.load(std::memory_order_relaxed)) + { + std::getline(std::cin, line); + read_ok = !std::cin.eof() && !std::cin.fail(); + } } - int n = 0; - if(!string_tools::get_xtype_from_string(n, command.substr(8, 1))) + else { - std::cout << "wrong syntax: " << command << std::endl << "use set_log n" << std::endl; - continue; + read_ok = false; + } + + { + std::unique_lock<std::mutex> lock(m_response_mutex); + if (m_run.load(std::memory_order_relaxed)) + { + m_line = std::move(line); + m_read_status = read_ok ? state_success : state_error; + } + else + { + m_read_status = state_cancelled; + } + m_response_cv.notify_one(); } - log_space::get_set_log_detalisation_level(true, n); - LOG_PRINT_L0("New log level set " << n); - } - else if(ch_handler(psrv, command)) - continue; - else - { - std::cout << "unknown command: " << command << std::endl; - std::cout << usage; } } + + enum t_state + { + state_init, + state_success, + state_error, + state_cancelled + }; + + private: + std::thread m_reader_thread; + std::atomic<bool> m_run; + + std::string m_line; + bool m_has_read_request; + t_state m_read_status; + + std::mutex m_request_mutex; + std::mutex m_response_mutex; + std::condition_variable m_request_cv; + std::condition_variable m_response_cv; + }; + + + template<class t_server> + bool empty_commands_handler(t_server* psrv, const std::string& command) + { return true; - CATCH_ENTRY_L0("console_handler", false); } - template<class chain_handler> - bool default_console_handler2(chain_handler ch_handler, const std::string usage) + + class async_console_handler { - TRY_ENTRY(); - bool continue_handle = true; - while(continue_handle) + public: + async_console_handler() { - char command_buff[400] = {0}; - std::string command; - std::cin.getline(command_buff, 399); - if(std::cin.eof() || std::cin.fail()) - { + } - LOG_PRINT("std::cin.eof() or std::cin.fail(), stopping...", LOG_LEVEL_0); - continue_handle = false; - break; - } - command = command_buff; + template<class t_server, class chain_handler> + bool run(t_server* psrv, chain_handler ch_handler, const std::string& prompt = "#", const std::string& usage = "") + { + return run(prompt, usage, [&](const std::string& cmd) { return ch_handler(psrv, cmd); }, [&] { psrv->send_stop_signal(); }); + } - if(!command.compare("exit") || !command.compare("q") ) - { - continue_handle = false; - }else if ( !command.compare(0, 7, "set_log")) + template<class chain_handler> + bool run(chain_handler ch_handler, const std::string& prompt = "#", const std::string& usage = "") + { + return run(prompt, usage, [&](const std::string& cmd) { return ch_handler(cmd); }, [] { }); + } + + void stop() + { + m_stdin_reader.stop(); + } + + private: + template<typename t_cmd_handler, typename t_exit_handler> + bool run(const std::string& prompt, const std::string& usage, const t_cmd_handler& cmd_handler, const t_exit_handler& exit_handler) + { + TRY_ENTRY(); + bool continue_handle = true; + while(continue_handle) { - //parse set_log command - if(command.size() != 9) + if (!prompt.empty()) + { + epee::log_space::set_console_color(epee::log_space::console_color_yellow, true); + std::cout << prompt; + if (' ' != prompt.back()) + std::cout << ' '; + epee::log_space::reset_console_color(); + std::cout.flush(); + } + + std::string command; + if(!m_stdin_reader.get_line(command)) + { + LOG_PRINT("Failed to read line. Stopping...", LOG_LEVEL_0); + continue_handle = false; + break; + } + + LOG_PRINT_L2("Read command: " << command); + if(0 == command.compare("exit") || 0 == command.compare("q")) + { + continue_handle = false; + }else if (!command.compare(0, 7, "set_log")) + { + //parse set_log command + if(command.size() != 9) + { + std::cout << "wrong syntax: " << command << std::endl << "use set_log n" << std::endl; + continue; + } + uint16_t n = 0; + if(!string_tools::get_xtype_from_string(n, command.substr(8, 1))) + { + std::cout << "wrong syntax: " << command << std::endl << "use set_log n" << std::endl; + continue; + } + log_space::get_set_log_detalisation_level(true, n); + LOG_PRINT_L0("New log level set " << n); + }else if (command.empty()) { - std::cout << "wrong syntax: " << command << std::endl << "use set_log n" << std::endl; continue; } - int n = 0; - if(!string_tools::get_xtype_from_string(n, command.substr(8, 1))) + else if(cmd_handler(command)) { - std::cout << "wrong syntax: " << command << std::endl << "use set_log n" << std::endl; continue; + } else + { + std::cout << "unknown command: " << command << std::endl; + std::cout << usage; } - log_space::get_set_log_detalisation_level(true, n); - LOG_PRINT_L0("New log level set " << n); - } - else if(ch_handler(command)) - continue; - else - { - std::cout << "unknown command: " << command << std::endl; - std::cout << usage; } + exit_handler(); + return true; + CATCH_ENTRY_L0("console_handler", false); } - return true; - CATCH_ENTRY_L0("console_handler", false); - } - + private: + async_stdin_reader m_stdin_reader; + }; template<class t_server, class t_handler> - bool start_default_console(t_server* ptsrv, t_handler handlr, const std::string& usage = "") + bool start_default_console(t_server* ptsrv, t_handler handlr, const std::string& prompt, const std::string& usage = "") { - boost::thread( boost::bind(default_console_handler<t_server, t_handler>, ptsrv, handlr, usage) ); + std::shared_ptr<async_console_handler> console_handler = std::make_shared<async_console_handler>(); + boost::thread(boost::bind(&async_console_handler::run<t_server, t_handler>, console_handler, ptsrv, handlr, prompt, usage)).detach(); return true; } template<class t_server> - bool start_default_console(t_server* ptsrv, const std::string& usage = "") + bool start_default_console(t_server* ptsrv, const std::string& prompt, const std::string& usage = "") { - return start_default_console(ptsrv, empty_commands_handler<t_server>, usage); + return start_default_console(ptsrv, empty_commands_handler<t_server>, prompt, usage); } template<class t_server, class t_handler> @@ -166,15 +326,16 @@ namespace epee } template<class t_server, class t_handler> - bool run_default_console_handler_no_srv_param(t_server* ptsrv, t_handler handlr, const std::string& usage = "") + bool run_default_console_handler_no_srv_param(t_server* ptsrv, t_handler handlr, const std::string& prompt, const std::string& usage = "") { - return default_console_handler(ptsrv, boost::bind<bool>(no_srv_param_adapter<t_server, t_handler>, _1, _2, handlr), usage); + async_console_handler console_handler; + return console_handler.run(ptsrv, boost::bind<bool>(no_srv_param_adapter<t_server, t_handler>, _1, _2, handlr), prompt, usage); } template<class t_server, class t_handler> - bool start_default_console_handler_no_srv_param(t_server* ptsrv, t_handler handlr, const std::string& usage = "") + bool start_default_console_handler_no_srv_param(t_server* ptsrv, t_handler handlr, const std::string& prompt, const std::string& usage = "") { - boost::thread( boost::bind(run_default_console_handler_no_srv_param<t_server, t_handler>, ptsrv, handlr, usage) ); + boost::thread( boost::bind(run_default_console_handler_no_srv_param<t_server, t_handler>, ptsrv, handlr, prompt, usage) ); return true; } @@ -209,7 +370,8 @@ namespace epee typedef std::map<std::string, std::pair<console_command_handler, std::string> > command_handlers_map; std::unique_ptr<boost::thread> m_console_thread; command_handlers_map m_command_handlers; - public: + async_console_handler m_console_handler; + public: std::string get_usage() { std::stringstream ss; @@ -217,7 +379,7 @@ namespace epee for(auto& x:m_command_handlers) if(x.first.size() > max_command_len) max_command_len = x.first.size(); - + for(auto& x:m_command_handlers) { ss.width(max_command_len + 3); @@ -255,24 +417,22 @@ namespace epee start_default_console_handler_no_srv_param(&srv, boost::bind(&console_handlers_binder::process_command_str, this, _1)); return true; }*/ - - bool start_handling(const std::string& usage_string = "") + + bool start_handling(const std::string& prompt, const std::string& usage_string = "") { - m_console_thread.reset(new boost::thread(boost::bind(&console_handlers_binder::run_handling, this, usage_string) )); + m_console_thread.reset(new boost::thread(boost::bind(&console_handlers_binder::run_handling, this, prompt, usage_string))); + m_console_thread->detach(); return true; } - bool stop_handling() + void stop_handling() { - if(m_console_thread.get()) - m_console_thread->interrupt(); - return true; + m_console_handler.stop(); } - - bool run_handling(const std::string usage_string) + bool run_handling(const std::string& prompt, const std::string& usage_string) { - return default_console_handler2(boost::bind(&console_handlers_binder::process_command_str, this, _1), usage_string); + return m_console_handler.run(boost::bind(&console_handlers_binder::process_command_str, this, _1), prompt, usage_string); } /*template<class t_srv> @@ -280,7 +440,6 @@ namespace epee { return run_default_console_handler_no_srv_param(&srv, boost::bind<bool>(&console_handlers_binder::process_command_str, this, _1), usage_string); }*/ - }; /* work around because of broken boost bind */ @@ -292,19 +451,23 @@ namespace epee return console_handlers_binder::process_command_str(cmd); } public: - bool start_handling(t_server* psrv, const std::string& usage_string = "") + bool start_handling(t_server* psrv, const std::string& prompt, const std::string& usage_string = "") { - boost::thread(boost::bind(&srv_console_handlers_binder<t_server>::run_handling, this, psrv, usage_string) ); + boost::thread(boost::bind(&srv_console_handlers_binder<t_server>::run_handling, this, psrv, prompt, usage_string)).detach(); return true; } - bool run_handling(t_server* psrv, const std::string usage_string) + bool run_handling(t_server* psrv, const std::string& prompt, const std::string& usage_string) { - return default_console_handler(psrv, boost::bind(&srv_console_handlers_binder<t_server>::process_command_str, this, _1, _2), usage_string); + return m_console_handler.run(psrv, boost::bind(&srv_console_handlers_binder<t_server>::process_command_str, this, _1, _2), prompt, usage_string); } - }; + void stop_handling() + { + m_console_handler.stop(); + } + private: + async_console_handler m_console_handler; + }; } - - |