diff --git a/HttpProxy/CMakeLists.txt b/HttpProxy/CMakeLists.txt index fc9cb8c..3874393 100644 --- a/HttpProxy/CMakeLists.txt +++ b/HttpProxy/CMakeLists.txt @@ -1,3 +1,5 @@ +find_package(Boost COMPONENTS url REQUIRED) + add_library(HttpProxy NetworkUtility.h NetworkUtility.cpp ProxyHttpSession.h ProxyHttpSession.cpp @@ -11,8 +13,11 @@ add_library(HttpProxy target_include_directories(HttpProxy INTERFACE ${CMAKE_CURRENT_SOURCE_DIR} + PRIVATE ${OPENSSL_INCLUDE_DIR} ) target_link_libraries(HttpProxy + PUBLIC ${Boost_LIBRARIES} PUBLIC Universal + PRIVATE ${OpenSSL_LIBRARY} ) \ No newline at end of file diff --git a/Universal/BufferUtility.cpp b/Universal/BufferUtility.cpp new file mode 100644 index 0000000..0e27965 --- /dev/null +++ b/Universal/BufferUtility.cpp @@ -0,0 +1,98 @@ +#include "BufferUtility.h" +#include +#include +#include + +namespace Amass { +const char CRLF[] = "\r\n"; +const char CRLFCRLF[] = "\r\n\r\n"; + +Buffer::Buffer(size_t initialSize) : m_buffer(initialSize) { +} + +void Buffer::swap(Buffer &buffer) { + m_buffer.swap(buffer.m_buffer); + std::swap(m_readerIndex, buffer.m_readerIndex); + std::swap(m_writerIndex, buffer.m_writerIndex); +} + +int Buffer::lastError() { + return m_lastError; +} + +void Buffer::append(const char *data, size_t len) { + if (writableBytes() < len + 1) + makeSpace(len + 1); // 剩下的可写空间如果小于需要的空间len+1,则增加len+1长度个空间,[len+1]用来加上\0进行截断 + std::memcpy(writableAddress(), data, len); + std::copy(data, data + len, writableAddress()); // 其实相当于把已有数据往前挪动 + m_writerIndex += len; + // 当Buffer以readableAddress()开始直接作为字符串直接使用时,在此加上\0截断,否则随机会打印出writableAddress()后的内容 + m_buffer[m_writerIndex] = '\0'; +} + +void Buffer::appendFileContent(const std::string_view &path) { + if (path.empty() || !std::filesystem::exists(path)) return; + std::ifstream ifs(path.data(), std::ifstream::binary); + char buffer[512]; + while (ifs.read(buffer, sizeof(buffer))) { + append(buffer, ifs.gcount()); + } + auto size = ifs.gcount(); + if (size > 0) append(buffer, size); +} + +void Buffer::makeSpace(size_t needSize) { + if ((writableBytes() + m_readerIndex) < needSize) { // 即使数据前移,空间还是不足 + auto remainSize = readableBytes(); + auto newSize = remainSize + needSize + 1024; + std::vector newBuffer(newSize); + memcpy(newBuffer.data(), readableAddress(), remainSize); + m_buffer.swap(newBuffer); + m_readerIndex = 0; + m_writerIndex = remainSize; + } else { // 将数据前移,空间满足要求 + auto remainSize = readableBytes(); + auto dst = m_buffer.data(); + auto src = readableAddress(); + for (size_t i = 0; i < remainSize; i++) { + *dst++ = *src++; + } + m_readerIndex = 0; + m_writerIndex = remainSize; + } +} +const char *Buffer::findCRLF() const { + const char *crlf = std::search(readableAddress(), writableAddress(), CRLF, CRLF + 2); + return crlf == writableAddress() ? nullptr : crlf; +} + +const char *Buffer::findCRLFCRLF() const { + auto crlf = std::search(readableAddress(), writableAddress(), CRLFCRLF, CRLFCRLF + 4); + return crlf == writableAddress() ? nullptr : crlf; +} + +const char *Buffer::findEOL(const char *start) const { + if (start == nullptr) start = readableAddress(); + if ((readableAddress() > start) || (start >= writableAddress())) return nullptr; + + const void *eol = memchr(start, '\n', static_cast(writableAddress() - start)); + return static_cast(eol); +} + +bool Buffer::retrieve(size_t len) { + if (len > readableBytes()) return false; + if (len < readableBytes()) { + m_readerIndex += len; + } else { + retrieveAll(); + } + + return true; +} + +bool Buffer::retrieveUntil(const char *end) { + if (((uint64_t)readableAddress() > (uint64_t)end) || ((uint64_t)end > (uint64_t)writableAddress())) return false; + return retrieve(static_cast(end - readableAddress())); +} + +} // namespace Amass diff --git a/Universal/BufferUtility.h b/Universal/BufferUtility.h new file mode 100644 index 0000000..5e7d2c5 --- /dev/null +++ b/Universal/BufferUtility.h @@ -0,0 +1,213 @@ +#ifndef __BUFFERUTILITY_H__ +#define __BUFFERUTILITY_H__ + +#include +#include +#include +#include + +namespace Amass { + +/** + * @brief The Buffer class A buffer class modeled after org.jboss.netty.buffer.ChannelBuffer + * +------------------+------------------+ + * | readable bytes | writable bytes | + * | (CONTENT) | | + * +------------------+------------------+ + * | | | + * 0 <=readerIndex <= writerIndex <= size + */ +class Buffer { +public: + Buffer(size_t initialSize = 1024); + void swap(Buffer &buffer); + + int lastError(); + + inline const char *readableAddress() const { + return m_buffer.data() + m_readerIndex; + } + inline size_t readableBytes() const { + return m_writerIndex - m_readerIndex; + } + + void append(const char *data, size_t len); + inline void append(std::string_view data) { + append(data.data(), data.size()); + } + + void appendFileContent(const std::string_view &path); + + /** + * @brief peek must POD struct + * @param fd + * @return [errno,size] + */ + template + T peek() { + T t; + memcpy(&t, readableAddress(), sizeof(T)); + return t; + } + + inline std::string peek(size_t size, bool retrieved = false) { + std::string ret(readableAddress(), size); + if (retrieved) retrieve(size); + return ret; + } + + /** + * @brief findCRLF + * @param start if(start == nullptr),find CRLF from readableAddress. + * @return + */ + const char *findCRLF() const; + + const char *findCRLFCRLF() const; + + template + const char *find(const T &data, size_t startPos = 0) { + if (startPos >= readableBytes()) return nullptr; + const char *pos = nullptr; + if constexpr (!std::is_array_v) { + if (startPos + sizeof(T) > readableBytes()) return nullptr; + pos = std::search(readableAddress() + startPos, (const char *)writableAddress(), (const char *)(&data), + (const char *)(&data)); + } else { + if (startPos + sizeof(T) * std::extent_v < T >> readableBytes()) return nullptr; + pos = std::search(readableAddress() + startPos, (const char *)writableAddress(), (const char *)(&data[0]), + (const char *)(&data[std::extent_v])); + } + + return pos == writableAddress() ? nullptr : pos; + } + + /** + * @brief findEOL + * @param start if(start == nullptr),find EOL from readableAddress. + * @return + */ + const char *findEOL(const char *start = nullptr) const; + + /** + * @brief retrieve retrieve returns void, to prevent string str(retrieve(readableBytes()), + * readableBytes()); the evaluation of two functions are unspecified + * @param len + * @return + */ + bool retrieve(size_t len); + + /** + * @brief retrieveUntil retrive [begin,end) + * @param end + * @return + */ + bool retrieveUntil(const char *end); + + /** + * @brief retrieveAll + * @param Return[template parameter] void,std::string,std::vector + * @return + */ + template + typename std::conditional_t, void, ReturnType> retrieveAll() { + if constexpr (std::is_same_v) { + std::string data(m_buffer.cbegin() + static_cast(m_readerIndex), + m_buffer.cbegin() + static_cast(m_writerIndex)); + m_readerIndex = 0; + m_writerIndex = 0; + m_buffer[m_readerIndex] = '\0'; + return data; + } else if constexpr (std::is_same_v>) { + std::vector data(m_buffer.cbegin() + static_cast(m_readerIndex), + m_buffer.cbegin() + static_cast(m_writerIndex)); + m_readerIndex = 0; + m_writerIndex = 0; + m_buffer[m_readerIndex] = '\0'; + return data; + } else if constexpr (std::is_same_v) { + m_readerIndex = 0; + m_writerIndex = 0; + m_buffer[m_readerIndex] = '\0'; + } + } + +protected: + /** + * @brief makeSpace 增加m_buffer的writeable区域容量,或者将可读数据前移 + * @param len + */ + void makeSpace(size_t len); + + inline size_t writableBytes() const { + return m_buffer.size() - m_writerIndex; + } + inline const char *writableAddress() const { + return m_buffer.data() + m_writerIndex; + } + inline char *writableAddress() { + return m_buffer.data() + m_writerIndex; + } + +private: + std::vector m_buffer; + size_t m_readerIndex = 0; + size_t m_writerIndex = 0; + int m_lastError; +}; + +template +class VectorView { +public: + using size_type = std::size_t; + using const_reference = const T &; + VectorView(const T *ptr, size_type length) : m_data(ptr), m_length(length) { + } + constexpr size_type size() const noexcept { + return m_length; + } + constexpr size_type length() const noexcept { + return m_length; + } + constexpr const_reference front() const { + return m_data[0]; + } + constexpr const_reference back() const { + return m_data[size() - 1]; + } + constexpr const_reference at(size_type pos) const { + return m_data[pos]; + } + +private: + const T *m_data; + size_type m_length; +}; + +template +class BufferView { +public: + BufferView() { + } + BufferView(const T *data, size_t size) : m_data(data), m_length(size), m_current(data) { + } + bool empty() const { + return m_data == nullptr || (m_current > (m_data + m_length)); + } + size_t size() const { + return m_data + m_length - m_current; + } + void read(T *buffer, size_t size) { + std::copy(m_current, m_current + size, buffer); + m_current += size; + } + +private: + const T *m_data = nullptr; + size_t m_length = 0; + const T *m_current = nullptr; +}; + +} // namespace Amass + +#endif // __BUFFERUTILITY_H__ diff --git a/Universal/CMakeLists.txt b/Universal/CMakeLists.txt index b50ee15..ae600a8 100644 --- a/Universal/CMakeLists.txt +++ b/Universal/CMakeLists.txt @@ -1,12 +1,16 @@ -find_package(Boost REQUIRED COMPONENTS log log_setup) +find_package(Boost REQUIRED COMPONENTS log log_setup program_options) add_library(Universal BoostLog.h BoostLog.inl BoostLog.cpp + BufferUtility.h BufferUtility.cpp + CommandLineInterpreter.h CommandLineInterpreter.cpp DateTime.h DateTime.cpp FunctionTraits.h IoContext.h IoContext.cpp MessageManager.h MessageManager.inl MessageManager.cpp + ProcessUtility.h ProcessUtility.inl ProcessUtility.cpp Singleton.h + SingletonProcess.h SingletonProcess.cpp StreamFormat.h StreamFormat.inl StreamFormat.cpp StringUtility.h StringUtility.cpp ) diff --git a/Universal/CommandLineInterpreter.cpp b/Universal/CommandLineInterpreter.cpp new file mode 100644 index 0000000..ee51d09 --- /dev/null +++ b/Universal/CommandLineInterpreter.cpp @@ -0,0 +1,101 @@ +#include "CommandLineInterpreter.h" +#include "BoostLog.h" +#include +#include +#include + +CommandLineInterpreter::CommandLineInterpreter(const DescriptionPointer &description, const std::string &prompt) + : m_description(description), m_prompt(prompt) {} + +void CommandLineInterpreter::interpret(std::istream &inputStream) { + std::string command; + std::cout << m_prompt << std::flush; + + while (std::getline(inputStream, command, '\n')) { + handleReadLine(command); + std::cout << m_prompt << std::flush; + } +} + +void CommandLineInterpreter::handleReadLine(std::string line) { + using namespace boost::program_options; + if (m_description.expired()) { + LOG(error) << "description has expired."; + return; + } + auto description = m_description.lock(); + std::vector args; + + // huu, ugly... + args = splitCommandLine(std::string("--") + line); + + try { + variables_map vm; + store(command_line_parser(args).options(*description).run(), vm); + notify(vm); + } catch (boost::program_options::unknown_option &e) { + std::cerr << "error: " << e.what() << std::endl; + } catch (boost::program_options::invalid_command_line_syntax &e) { + std::cerr << "error: " << e.what() << std::endl; + } catch (boost::program_options::validation_error &e) { + std::cerr << "error: " << e.what() << std::endl; + } +} + +std::vector CommandLineInterpreter::splitCommandLine(const std::string &input) { + std::vector result; + + std::string::const_iterator i = input.begin(), e = input.end(); + for (; i != e; ++i) + if (!isspace((unsigned char)*i)) break; + + if (i != e) { + std::string current; + bool inside_quoted = false; + int backslash_count = 0; + + for (; i != e; ++i) { + if (*i == '"') { + // '"' preceded by even number (n) of backslashes generates + // n/2 backslashes and is a quoted block delimiter + if (backslash_count % 2 == 0) { + current.append(backslash_count / 2, '\\'); + inside_quoted = !inside_quoted; + // '"' preceded by odd number (n) of backslashes generates + // (n-1)/2 backslashes and is literal quote. + } else { + current.append(backslash_count / 2, '\\'); + current += '"'; + } + backslash_count = 0; + } else if (*i == '\\') { + ++backslash_count; + } else { + // Not quote or backslash. All accumulated backslashes should be + // added + if (backslash_count) { + current.append(backslash_count, '\\'); + backslash_count = 0; + } + if (isspace((unsigned char)*i) && !inside_quoted) { + // Space outside quoted section terminate the current argument + result.push_back(current); + current.resize(0); + for (; i != e && isspace((unsigned char)*i); ++i) + ; + --i; + } else { + current += *i; + } + } + } + + // If we have trailing backslashes, add them + if (backslash_count) current.append(backslash_count, '\\'); + + // If we have non-empty 'current' or we're still in quoted + // section (even if 'current' is empty), add the last token. + if (!current.empty() || inside_quoted) result.push_back(current); + } + return result; +} diff --git a/Universal/CommandLineInterpreter.h b/Universal/CommandLineInterpreter.h new file mode 100644 index 0000000..2c42080 --- /dev/null +++ b/Universal/CommandLineInterpreter.h @@ -0,0 +1,23 @@ +#ifndef COMMANDLINEINTERPRETER_H +#define COMMANDLINEINTERPRETER_H + +#include +#include + +class CommandLineInterpreter { +public: + using DescriptionPointer = std::shared_ptr; + using DescriptionWeakPointer = std::weak_ptr; + CommandLineInterpreter(const DescriptionPointer &description, const std::string &prompt); + void interpret(std::istream &inputStream); + +protected: + void handleReadLine(std::string line); + static std::vector splitCommandLine(const std::string &input); + +private: + const DescriptionWeakPointer m_description; + std::string m_prompt; +}; + +#endif // COMMANDLINEINTERPRETER_H diff --git a/Universal/ProcessUtility.cpp b/Universal/ProcessUtility.cpp new file mode 100644 index 0000000..81bb17b --- /dev/null +++ b/Universal/ProcessUtility.cpp @@ -0,0 +1,200 @@ +#include "ProcessUtility.h" +#include "BoostLog.h" +#include "boost/algorithm/string.hpp" +#include +#include +#ifdef __linux__ +#include +#elif defined(_WIN32) +#include +#endif + +bool DockerUtility::supervisorStatus(std::string_view container, const std::string &module) { + using namespace boost::process; + boost::process::ipstream ips; + auto docker = search_path("docker"); + auto ctl = boost::process::search_path("supervisorctl"); + auto env = boost::this_process::environment(); + if (env.count("USER")) { + auto user = env["USER"].to_string(); + system(docker, args = {"exec", "-u", user, container.data(), "supervisorctl", "status", module}, std_out > ips); + std::string line; + if (!ips) return false; + std::getline(ips, line); + if (line.empty()) return false; + boost::algorithm::trim(line); + std::vector s; + boost::algorithm::split(s, line, boost::algorithm::is_any_of(" "), boost::algorithm::token_compress_on); + if (s.size() < 3) return false; + return s.at(2) == "RUNNING"; + } else { + LOG(error) << "this_process did not contain USER env value."; + return false; + } +} + +bool DockerUtility::running(std::string_view container, std::error_code &error) { +#ifdef __linux__ + using namespace boost::process; + boost::process::ipstream stream; + std::ostringstream oss; + oss << "docker ps -q --filter \"name=" << container << "\" --filter \"status=running\""; + system(oss.str(), boost::process::std_out > stream, error); + if (error) return false; + bool ret = false; + std::string line; + while (std::getline(stream, line) && !line.empty()) { + ret = true; + break; + } + return ret; +#else + return false; +#endif +} + +std::string NativeUtility::currentExecutable() { +// win32 GetModuleFileNameA +#ifdef __linux__ + char buffer[512] = {0}; + auto status = readlink("/proc/self/exe", buffer, sizeof(buffer)); + return status == -1 ? std::string() : std::string(buffer); +#else + char buffer[MAX_PATH]; + boost::winapi::get_module_file_name(nullptr, buffer, MAX_PATH); + return buffer; +#endif +} + +std::string NativeUtility::executableDirectory() { + auto path = currentExecutable(); +#ifdef __linux__ + auto slashPos = path.find_last_of("/"); +#else + auto slashPos = path.find_last_of("\\"); +#endif + return path.substr(0, slashPos); +} + +std::string NativeUtility::applicationDataDirectory(bool create) { + std::string directory; + +#ifdef WIN32 + DWORD bufferSize = GetEnvironmentVariable("APPDATA", 0, 0); + char *buffer = new char[bufferSize + 2]; + GetEnvironmentVariable("APPDATA", buffer, bufferSize); + directory.assign(buffer); + delete[] buffer; +#else + const char *result = std::getenv("XDG_CONFIG_HOME"); + if (result && strlen(result)) { + directory = std::string(result); + } else { + directory = std::string(std::getenv("HOME")) + std::string("/.config/"); + } +#endif + auto exePath = currentExecutable(); + auto slashPos = exePath.find_last_of("\\"); + auto dotPos = exePath.find_last_of("."); + auto appName = exePath.substr(slashPos + 1, dotPos - slashPos - 1); + std::ostringstream oss; + oss << directory << "/" << appName << "/"; + directory = oss.str(); + if (create) { + try { + std::filesystem::path path(std::filesystem::u8path(directory)); + if (!std::filesystem::exists(path)) { + std::filesystem::create_directories(path); + } + } catch (...) { + } + } + return directory; +} + +std::string NativeUtility::homeDirectory() { + std::string directory; +#ifdef WIN32 + DWORD bufferSize = GetEnvironmentVariable("USERPROFILE", 0, 0); + char *buffer = new char[bufferSize + 2]; + GetEnvironmentVariable("USERPROFILE", buffer, bufferSize); + directory.assign(buffer); + delete[] buffer; +#else + directory = std::string(std::getenv("HOME")); +#endif + return directory; +} + +bool NativeUtility::processRunning(const std::string &nameFilter) { + boost::process::ipstream ips; + // for unknow reason,on gdb debug mode,the columns is very short. + boost::process::system("ps -eF --columns 999", boost::process::std_out > ips); + std::string line; + std::regex regex(nameFilter); + while (std::getline(ips, line)) { + if (line.empty()) continue; + // LOG(debug) << "processRunning: " << line; + if (std::regex_search(line, regex)) { + return true; + } + } + return false; +} + +void DockerUtility::executeBashCommand(const std::string &command) { + std::ostringstream oss; + oss << "bash -c \"" << command << "\""; + boost::process::system("bash -c \" \""); + + // ps -aux | grep rosbridge | awk '{print $2}' | xargs kill -s 9 2>/dev/null +} + +void DockerUtility::execute(const std::string_view &container, const std::string_view &command) { + std::ostringstream oss; + auto env = boost::this_process::environment(); + auto user = env["USER"].to_string(); + oss << "docker exec -u " << user << " -d " << container << " " << command; + using namespace boost::process; + system(oss.str()); +} + +void DockerUtility::kill(const std::string &nameFilter) { + std::ostringstream oss; + oss << "ps -aux | grep " << nameFilter << " | awk '{print $2}' | xargs kill -s 9 2>/dev/null"; + executeBashCommand(oss.str()); +} + +bool DockerUtility::containerExisted(const std::string_view &name) { + boost::process::ipstream ips; + std::ostringstream oss; + oss << "docker ps -aq --filter name=" << name; + boost::process::system(oss.str(), boost::process::std_out > ips); + std::string line; + while (std::getline(ips, line)) { + if (line.empty()) continue; + return true; + } + return false; +} + +void DockerUtility::supervisorctl(const std::string_view &container, const std::string &module, bool isStart) { + using namespace boost::process; + auto docker = search_path("docker"); + auto env = boost::this_process::environment(); + auto user = env["USER"].to_string(); + system(docker, args = {"exec", "-u", user, container.data(), "supervisorctl", isStart ? "start" : "stop", module}); +} + +void DockerUtility::stop(const std::string &nameFilter) { + using namespace boost::process; + ipstream ips; + std::ostringstream oss; + oss << "docker ps -q --filter \"name=" << nameFilter << "\" --filter \"status=running\" "; + system(oss.str(), std_out > ips); + std::string id; + auto docker = search_path("docker"); + while (std::getline(ips, id) && !id.empty()) { + system(docker, "stop", args += {id}); + } +} diff --git a/Universal/ProcessUtility.h b/Universal/ProcessUtility.h new file mode 100644 index 0000000..9b080bf --- /dev/null +++ b/Universal/ProcessUtility.h @@ -0,0 +1,54 @@ +#ifndef DOCKERUTILITY_H +#define DOCKERUTILITY_H + +#include +#include +#include +#include + +/** + * @brief used when app run on the native machine + */ +class DockerUtility { +public: + void stop(const std::string &nameFilter); + static bool supervisorStatus(std::string_view container, const std::string &module); + + template + static void runScript(std::string_view container, const std::string &script, Args &&...scriptArgs); + + static bool running(std::string_view container, std::error_code &error); + static void executeBashCommand(const std::string &command); + static void execute(const std::string_view &container, const std::string_view &command); + static void kill(const std::string &nameFilter); + static bool containerExisted(const std::string_view &name); + + /** + * @brief supervisorctl + * @param module + * @param isStart true:start,false:stop + */ + static void supervisorctl(const std::string_view &container, const std::string &module, bool isStart); + +private: + DockerUtility() = default; +}; + +class NativeUtility { +public: + using ShellCommand = std::true_type; + template + static void runScript(const std::string &script, Args &&...scriptArgs); + static bool processRunning(const std::string &nameFilter); + static std::string currentExecutable(); + static std::string executableDirectory(); + static std::string applicationDataDirectory(bool create = true); + static std::string homeDirectory(); + +private: + NativeUtility() = default; +}; + +#include "ProcessUtility.inl" + +#endif // DOCKERUTILITY_H diff --git a/Universal/ProcessUtility.inl b/Universal/ProcessUtility.inl new file mode 100644 index 0000000..329080d --- /dev/null +++ b/Universal/ProcessUtility.inl @@ -0,0 +1,44 @@ +#ifndef __PROCESSUTILITY_INL__ +#define __PROCESSUTILITY_INL__ + +#include "BoostLog.h" +#include "ProcessUtility.h" +#include +#include +#if (defined __arm__) || (defined __aarch64__) +#include +#else +#include +#endif + +template +void DockerUtility::runScript(std::string_view container, const std::string &script, Args &&...scriptArgs) { + using namespace boost::process; + auto env = boost::this_process::environment(); + auto user = env["USER"].to_string(); + auto docker = search_path("docker"); + system(docker, + args = {"exec", "-d", "-u", user, container.data(), "bash", script, std::forward(scriptArgs)...}); +} + +template +void NativeUtility::runScript(const std::string &script, Args &&...scriptArgs) { +#if (defined __arm__) || (defined __aarch64__) + using namespace std::experimental; +#else + using namespace std; +#endif + +#if defined(WIN32) || defined(ANDROID) + LOG(info) << "DockerUtility::runScript() not supported on windows or android."; +#else + auto askpass = filesystem::current_path().parent_path() / "Askpass/Askpassd.AppImage"; + auto env = boost::this_process::environment(); + env["SUDO_ASKPASS"] = askpass; + + auto bash = boost::process::search_path("bash"); + boost::process::system(bash, boost::process::args = {script, std::forward(scriptArgs)...}, env); +#endif +} + +#endif // __PROCESSUTILITY_INL__ diff --git a/Universal/SingletonProcess.cpp b/Universal/SingletonProcess.cpp new file mode 100644 index 0000000..7cf9314 --- /dev/null +++ b/Universal/SingletonProcess.cpp @@ -0,0 +1,66 @@ +#include "SingletonProcess.h" +#include "BoostLog.h" +#ifdef WIN32 +#include "Windows.h" +#else +#include +#endif + +SingletonProcess::SingletonProcess(const std::string_view &name) { +#ifdef WIN32 + m_handle = CreateMutex(NULL, FALSE, name.data()); + if (m_handle) { + if (ERROR_ALREADY_EXISTS == GetLastError()) { + m_alreadyRun = true; + } + } +#else + std::ostringstream oss; + oss << "/tmp/" << name; + int pidFile = open(oss.str().data(), O_CREAT | O_RDWR, 0666); + m_handle = flock(pidFile, LOCK_EX | LOCK_NB); + if (m_handle) { + if (EWOULDBLOCK == errno) m_alreadyRun = true; + } +#endif + + if (!m_alreadyRun) { + boost::interprocess::message_queue::remove(name.data()); + m_messageQueue = std::make_unique(boost::interprocess::create_only, + name.data(), 64, 1024); + m_thread = std::make_unique(&SingletonProcess::run, this); + } else { + m_messageQueue = + std::make_unique(boost::interprocess::open_only, name.data()); + } +} + +bool SingletonProcess::alreadyRun() const { + return m_alreadyRun; +} + +SingletonProcess::~SingletonProcess() { + m_exit = true; + m_conditionVariable.notify_all(); + if (m_thread && m_thread->joinable()) m_thread->join(); +} + +bool SingletonProcess::sendMessage(const char *data, size_t size) { + if (!m_alreadyRun) return false; + return m_messageQueue->try_send(data, size, 0); +} + +void SingletonProcess::run() { + char buffer[1024]; + while (!m_exit) { + unsigned int priority; + boost::interprocess::message_queue::size_type receivedSize; + bool status = m_messageQueue->try_receive(buffer, sizeof(buffer), receivedSize, priority); + if (status) { + messageHandler(buffer, receivedSize); + } else { + std::unique_lock locker(m_mutex); + m_conditionVariable.wait_for(locker, std::chrono::milliseconds(500)); + } + } +} diff --git a/Universal/SingletonProcess.h b/Universal/SingletonProcess.h new file mode 100644 index 0000000..ff9f250 --- /dev/null +++ b/Universal/SingletonProcess.h @@ -0,0 +1,39 @@ +#ifndef SINGLETONPROCESS_H +#define SINGLETONPROCESS_H + +#include +#include +#include +#include +#include + +class SingletonProcess +{ +public: + using MessageHandler = boost::signals2::signal; + using MessageHandlerSlot = MessageHandler::slot_type; + SingletonProcess(const std::string_view &name); + bool alreadyRun() const; + MessageHandler messageHandler; + ~SingletonProcess(); + bool sendMessage(const char *data, size_t size); + +protected: + void run(); + +private: +#if WIN32 + void *m_handle{nullptr}; // windows HANDLE +#else + int m_handle{-1}; +#endif + bool m_alreadyRun{false}; + std::unique_ptr m_messageQueue; + + std::unique_ptr m_thread; + std::condition_variable m_conditionVariable; + std::mutex m_mutex; + bool m_exit{false}; +}; + +#endif // SINGLETONPROCESS_H