From bf55a8e1510625fc3045dde4d90ccfae46419d1d Mon Sep 17 00:00:00 2001 From: fasiondog Date: Fri, 2 Aug 2024 18:15:43 +0800 Subject: [PATCH] =?UTF-8?q?=E5=90=8C=E6=AD=A5=20hku=5Futils?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config_utils.h.in | 9 +- copy_dependents.lua | 6 +- hikyuu_cpp/hikyuu/GlobalInitializer.cpp | 14 +- hikyuu_cpp/hikyuu/global/sysinfo.cpp | 7 +- hikyuu_cpp/hikyuu/utilities/FilterNode.h | 267 ++++++ hikyuu_cpp/hikyuu/utilities/Log.cpp | 118 +-- hikyuu_cpp/hikyuu/utilities/Log.h | 107 +-- hikyuu_cpp/hikyuu/utilities/ResourcePool.h | 636 +++++++++++++ hikyuu_cpp/hikyuu/utilities/base64.cpp | 108 +++ hikyuu_cpp/hikyuu/utilities/base64.h | 59 ++ hikyuu_cpp/hikyuu/utilities/exception.h | 1 + .../utilities/http_client/HttpClient.cpp | 208 +++++ .../hikyuu/utilities/http_client/HttpClient.h | 191 ++++ .../hikyuu/utilities/http_client/nng_wrap.h | 478 ++++++++++ .../hikyuu/utilities/http_client/url.cpp | 56 ++ hikyuu_cpp/hikyuu/utilities/http_client/url.h | 25 + hikyuu_cpp/hikyuu/utilities/mo/mo.cpp | 23 + hikyuu_cpp/hikyuu/utilities/mo/mo.h | 48 + hikyuu_cpp/hikyuu/utilities/mo/moFileReader.h | 836 ++++++++++++++++++ hikyuu_cpp/hikyuu/utilities/snowflake.h | 110 +++ hikyuu_cpp/hikyuu/xmake.lua | 2 +- setup.py | 5 +- xmake.lua | 148 +--- 23 files changed, 3155 insertions(+), 307 deletions(-) create mode 100644 hikyuu_cpp/hikyuu/utilities/FilterNode.h create mode 100644 hikyuu_cpp/hikyuu/utilities/ResourcePool.h create mode 100644 hikyuu_cpp/hikyuu/utilities/base64.cpp create mode 100644 hikyuu_cpp/hikyuu/utilities/base64.h create mode 100644 hikyuu_cpp/hikyuu/utilities/http_client/HttpClient.cpp create mode 100644 hikyuu_cpp/hikyuu/utilities/http_client/HttpClient.h create mode 100644 hikyuu_cpp/hikyuu/utilities/http_client/nng_wrap.h create mode 100644 hikyuu_cpp/hikyuu/utilities/http_client/url.cpp create mode 100644 hikyuu_cpp/hikyuu/utilities/http_client/url.h create mode 100644 hikyuu_cpp/hikyuu/utilities/mo/mo.cpp create mode 100644 hikyuu_cpp/hikyuu/utilities/mo/mo.h create mode 100644 hikyuu_cpp/hikyuu/utilities/mo/moFileReader.h create mode 100644 hikyuu_cpp/hikyuu/utilities/snowflake.h diff --git a/config_utils.h.in b/config_utils.h.in index f7437193..3479a6c5 100644 --- a/config_utils.h.in +++ b/config_utils.h.in @@ -25,10 +25,17 @@ ${define HKU_ENABLE_STACK_TRACE} ${define HKU_CLOSE_SPEND_TIME} -${define HKU_DEFAULT_LOG_NAME} ${define HKU_USE_SPDLOG_ASYNC_LOGGER} ${define HKU_LOG_ACTIVE_LEVEL} +${define HKU_ENABLE_MO} + +${define HKU_ENABLE_HTTP_CLIENT} +${define HKU_ENABLE_HTTP_CLIENT_SSL} +${define HKU_ENABLE_HTTP_CLIENT_ZIP} + +${define HKU_ENABLE_NODE} + // clang-format on #endif /* HKU_UTILS_CONFIG_H_ */ \ No newline at end of file diff --git a/copy_dependents.lua b/copy_dependents.lua index 48b6fc5d..f9e7e3e4 100644 --- a/copy_dependents.lua +++ b/copy_dependents.lua @@ -39,7 +39,7 @@ task("copy_dependents") end elseif type(pkg_path) == 'table' then for i=1, #pkg_path do - local pos = string.find(pkg_path[i], "yh_utils") + local pos = string.find(pkg_path[i], "hku_utils") if pos == nil then pos = string.find(pkg_path[i], "opencv") if pos == nil then @@ -49,11 +49,11 @@ task("copy_dependents") end else for _, filedir in ipairs(os.dirs(pkg_path[i] .. "/*")) do - local pos = string.find(filedir, "yihua") + local pos = string.find(filedir, "hikyuu") if pos == nil then os.trycp(filedir, destpath .. "/include") else - os.trycp(filedir .. "/utils", destpath .. "/include/yihua") + os.trycp(filedir .. "/utilities", destpath .. "/include/hikyuu") end end end diff --git a/hikyuu_cpp/hikyuu/GlobalInitializer.cpp b/hikyuu_cpp/hikyuu/GlobalInitializer.cpp index 39e22143..e3038991 100644 --- a/hikyuu_cpp/hikyuu/GlobalInitializer.cpp +++ b/hikyuu_cpp/hikyuu/GlobalInitializer.cpp @@ -21,6 +21,7 @@ #endif #include "utilities/Log.h" +#include "utilities/os.h" #include "hikyuu.h" #include "GlobalInitializer.h" #include "StockManager.h" @@ -56,12 +57,11 @@ void GlobalInitializer::init() { fmt::print("Initialize hikyuu_{} ...\n", getVersionWithBuild()); #endif - initLogger(); -#if defined(_DEBUG) || defined(DEBUG) - set_log_level(LOG_LEVEL::LOG_TRACE); -#else - set_log_level(LOG_LEVEL::LOG_INFO); -#endif + if (createDir(fmt::format("{}/.hikyuu", getUserDir()))) { + initLogger(false, fmt::format("{}/.hikyuu/hikyuu.log", getUserDir())); + } else { + initLogger(); + } #if HKU_ENABLE_SEND_FEEDBACK sendFeedback(); @@ -103,9 +103,7 @@ void GlobalInitializer::clean() { H5close(); #endif -#if USE_SPDLOG_LOGGER spdlog::drop_all(); -#endif #ifdef MSVC_LEAKER_DETECT // MSVC 内存泄露检测,输出至 VS 的输出窗口 diff --git a/hikyuu_cpp/hikyuu/global/sysinfo.cpp b/hikyuu_cpp/hikyuu/global/sysinfo.cpp index a47306e0..5c58a09c 100644 --- a/hikyuu_cpp/hikyuu/global/sysinfo.cpp +++ b/hikyuu_cpp/hikyuu/global/sysinfo.cpp @@ -10,7 +10,6 @@ #include #include #include -// #include #include #include "hikyuu/version.h" #include "hikyuu/DataType.h" @@ -51,7 +50,11 @@ bool HKU_API pythonInJupyter() { void HKU_API setPythonInJupyter(bool injupyter) { g_pythonInJupyter = injupyter; - initLogger(injupyter); + if (createDir(fmt::format("{}/.hikyuu", getUserDir()))) { + initLogger(injupyter, fmt::format("{}/.hikyuu/hikyuu.log", getUserDir())); + } else { + initLogger(injupyter); + } } bool HKU_API CanUpgrade() { diff --git a/hikyuu_cpp/hikyuu/utilities/FilterNode.h b/hikyuu_cpp/hikyuu/utilities/FilterNode.h new file mode 100644 index 00000000..8df20f36 --- /dev/null +++ b/hikyuu_cpp/hikyuu/utilities/FilterNode.h @@ -0,0 +1,267 @@ +/* + * Copyright (c) 2023 hikyuu.org + * + * Created on: 2023-01-13 + * Author: fasiondog + */ + +#pragma once + +#include +#include +#include +#include +#include "thread/ThreadPool.h" +#include "any_to_string.h" +#include "Log.h" + +namespace hku { + +/** + * @brief 过滤节点 + */ +class FilterNode { +public: + FilterNode() = default; + FilterNode(const FilterNode&) = default; + virtual ~FilterNode() = default; + + /** + * @brief 构造函数 + * @param exclusive 是否排他。为 true 时,只执行第一个遇到的满足过滤条件的子节点 + */ + explicit FilterNode(bool exclusive) : m_exclusive(exclusive) {} + + FilterNode(FilterNode&& rv) + : m_value(std::move(rv.m_value)), + m_children(std::move(rv.m_children)), + m_exclusive(rv.m_exclusive) {} + + FilterNode& operator=(const FilterNode& rv) { + if (this == &rv) + return *this; + m_value = rv.m_value; + m_children = rv.m_children; + m_exclusive = rv.m_exclusive; + return *this; + } + + FilterNode& operator=(FilterNode&& rv) { + if (this == &rv) + return *this; + m_value = std::move(rv.m_value); + m_children = std::move(rv.m_children); + m_exclusive = rv.m_exclusive; + return *this; + } + + using ptr_t = std::shared_ptr; + + ptr_t addChild(const ptr_t& child) { + HKU_CHECK(child, "Invalid input child! child is null!"); + m_children.push_front(child); + return child; + } + + bool exclusive() const { + return m_exclusive; + } + + void exclusive(bool exclusive) { + m_exclusive = exclusive; + } + + using const_iterator = std::forward_list::const_iterator; + using iterator = std::forward_list::iterator; + const_iterator cbegin() const { + return m_children.cbegin(); + } + + const_iterator cend() const { + return m_children.cend(); + } + + iterator begin() { + return m_children.begin(); + } + + iterator end() { + return m_children.end(); + } + + bool run(const any_t& data) noexcept { + if (_filter(data)) { + _process(data); + for (auto& node : m_children) { + if (node->run(data) && m_exclusive) { + return true; + } + } + return true; + } + return false; + } + + virtual bool filter(const any_t& data) { + return true; + } + + virtual void process(const any_t& data) {} + + template + ValueT value() const { + return any_cast(m_value); + } + + template + void value(const ValueT& value) { + m_value = value; + } + + bool has_value() const { +#if !HKU_OS_IOS && CPP_STANDARD >= CPP_STANDARD_17 + return m_value.has_value(); +#else + return !m_value.empty(); +#endif + } + +private: + bool _filter(const any_t& data) noexcept { + try { + return filter(data); + } catch (const std::exception& e) { + HKU_WARN("Node filter exist error! {}", e.what()); + } catch (...) { + HKU_WARN("Node filter exist unknown error!"); + } + return false; + } + + void _process(const any_t& data) noexcept { + try { + process(data); + } catch (const std::exception& e) { + HKU_WARN("Node process exist error! {}", e.what()); + } catch (...) { + HKU_WARN("Node process exist unknown error!"); + } + } + +protected: + any_t m_value; + +private: + std::forward_list m_children; + bool m_exclusive = false; +}; + +template <> +inline const any_t& FilterNode::value() const { + return m_value; +} + +typedef std::shared_ptr FilterNodePtr; + +/** + * @brief 绑定过滤节点,通过 std::function 绑定自定义的 filter 和 process 处理函数 + */ +class BindFilterNode : public FilterNode { +public: + BindFilterNode() = default; + virtual ~BindFilterNode() = default; + + using filter_func = std::function; + using process_func = std::function; + + explicit BindFilterNode(const process_func& process) : FilterNode(false), m_process(process) {} + explicit BindFilterNode(process_func&& process) + : FilterNode(false), m_process(std::move(process)) {} + + BindFilterNode(const filter_func& filter, const process_func& process, bool exclusive = false) + : FilterNode(exclusive), m_filter(filter), m_process(process) {} + + BindFilterNode(filter_func&& filter, process_func&& process, bool exclusive = false) + : FilterNode(exclusive), m_filter(std::move(filter)), m_process(std::move(process)) {} + + virtual bool filter(const any_t& data) { + return m_filter ? m_filter(this, data) : true; + } + + virtual void process(const any_t& data) { + if (m_process) { + m_process(this, data); + } + } + +private: + filter_func m_filter; + process_func m_process; +}; + +/** + * @brief 异步串行事件处理器 + * @tparam EventT + */ +template +class AsyncSerialEventProcessor { +public: + /** + * @brief 构造函数 + * @param quit_wait 退出时等待所有任务完成 + */ + explicit AsyncSerialEventProcessor(bool quit_wait = true) : m_quit_wait(quit_wait) { + m_tg = std::unique_ptr(new ThreadPool(1)); + } + + /** 析构函数 */ + virtual ~AsyncSerialEventProcessor() { + if (m_quit_wait) { + m_tg->join(); + } else { + m_tg->stop(); + } + } + + /** + * @brief 添加事件处理节点 + * + * @param event 事件 + * @param action 对应的处理节点 + * @return 返回加入的节点 + */ + FilterNodePtr addAction(const EventT& event, const FilterNodePtr& action) { + HKU_CHECK(action, "Input action is null!"); + std::lock_guard lock(m_mutex); + auto iter = m_trees.find(event); + if (iter != m_trees.end()) { + iter->second->addChild(action); + } else { + m_trees[event] = action; + } + return action; + } + + /** + * @brief 分派事件消息 + * + * @param event 事件 + * @param data 事件附加信息 + */ + void dispatch(const EventT& event, const any_t& data) { + m_tg->submit([=] { + auto iter = m_trees.find(event); + HKU_WARN_IF_RETURN(iter == m_trees.end(), void(), + "There is no matching handling method for the event({})!", event); + iter->second->run(data); + }); + } + +private: + mutable std::mutex m_mutex; + std::unordered_map m_trees; + std::unique_ptr m_tg; + bool m_quit_wait = true; +}; + +} // namespace hku \ No newline at end of file diff --git a/hikyuu_cpp/hikyuu/utilities/Log.cpp b/hikyuu_cpp/hikyuu/utilities/Log.cpp index 06f388da..4cce287a 100644 --- a/hikyuu_cpp/hikyuu/utilities/Log.cpp +++ b/hikyuu_cpp/hikyuu/utilities/Log.cpp @@ -10,7 +10,6 @@ #include "os.h" #include "Log.h" -#if USE_SPDLOG_LOGGER // 使用 stdout_color 将无法将日志输出重定向至 python #include #include @@ -20,11 +19,6 @@ #if HKU_USE_SPDLOG_ASYNC_LOGGER #include #endif /* HKU_USE_SPDLOG_ASYNC_LOGGER */ -#endif /* #if USE_SPDLOG_LOGGER */ - -#ifndef HKU_DEFAULT_LOG_NAME -#define HKU_DEFAULT_LOG_NAME "hikyuu" -#endif namespace hku { @@ -36,88 +30,51 @@ LOG_LEVEL get_log_level() { return g_log_level; } -/********************************************** - * Use SPDLOG for logging - *********************************************/ -#if USE_SPDLOG_LOGGER -std::shared_ptr getHikyuuLogger() { - return spdlog::get(HKU_DEFAULT_LOG_NAME); +void set_log_level(LOG_LEVEL level) { + g_log_level = level; + getHikyuuLogger()->set_level((spdlog::level::level_enum)level); } -#if HKU_USE_SPDLOG_ASYNC_LOGGER -void HKU_UTILS_API initLogger(bool inJupyter) { - std::string logname(HKU_DEFAULT_LOG_NAME); +std::shared_ptr getHikyuuLogger() { + auto logger = spdlog::get("hikyuu"); + return logger ? logger : spdlog::default_logger(); +} + +void HKU_UTILS_API initLogger(bool not_use_color, const std::string& filename) { + std::string logname("hikyuu"); spdlog::drop(logname); std::shared_ptr logger = spdlog::get(logname); if (logger) { spdlog::drop(logname); } + spdlog::sink_ptr stdout_sink; - if (inJupyter) { + if (not_use_color) { stdout_sink = std::make_shared(std::cout, true); } else { stdout_sink = std::make_shared(); } stdout_sink->set_level(spdlog::level::trace); - std::shared_ptr rotating_sink; - if (createDir(fmt::format("{}/.hikyuu", getUserDir()))) { - std::string log_filename = - fmt::format("{}/.hikyuu/{}.log", getUserDir(), HKU_DEFAULT_LOG_NAME); - rotating_sink = - std::make_shared(log_filename, 1024 * 1024 * 10, 3); - rotating_sink->set_level(spdlog::level::warn); - } + std::string logfile = filename.empty() ? "./hikyuu.log" : filename; + auto rotating_sink = + std::make_shared(logfile, 1024 * 1024 * 10, 3); + rotating_sink->set_level(spdlog::level::warn); std::vector sinks{stdout_sink}; if (rotating_sink) { sinks.emplace_back(rotating_sink); } +#if HKU_USE_SPDLOG_ASYNC_LOGGER spdlog::init_thread_pool(8192, 1); logger = std::make_shared(logname, sinks.begin(), sinks.end(), spdlog::thread_pool(), spdlog::async_overflow_policy::block); - - logger->set_level(spdlog::level::trace); - logger->flush_on(spdlog::level::trace); - logger->set_pattern("%Y-%m-%d %H:%M:%S.%e [%^HKU-%L%$] - %v [%!]"); - // logger->set_pattern("%^%Y-%m-%d %H:%M:%S.%e [HKU-%L] - %v (%s:%#)%$"); - // spdlog::register_logger(logger); - spdlog::set_default_logger(logger); -} - -#else /* #if HKU_USE_SPDLOG_ASYNC_LOGGER */ - -void HKU_UTILS_API initLogger(bool inJupyter) { - std::string logname(HKU_DEFAULT_LOG_NAME); - spdlog::drop(logname); - std::shared_ptr logger = spdlog::get(logname); - if (logger) { - spdlog::drop(logname); - } - spdlog::sink_ptr stdout_sink; - if (inJupyter) { - stdout_sink = std::make_shared(std::cout, true); - } else { - stdout_sink = std::make_shared(); - } - stdout_sink->set_level(spdlog::level::trace); - - std::shared_ptr rotating_sink; - if (createDir(fmt::format("{}/.hikyuu", getUserDir()))) { - std::string log_filename = - fmt::format("{}/.hikyuu/{}.log", getUserDir(), HKU_DEFAULT_LOG_NAME); - rotating_sink = - std::make_shared(log_filename, 1024 * 1024 * 10, 3); - rotating_sink->set_level(spdlog::level::warn); - } - - std::vector sinks{stdout_sink}; - if (rotating_sink) { - sinks.emplace_back(rotating_sink); - } +#else logger = std::make_shared(logname, sinks.begin(), sinks.end()); +#endif + logger->set_level(spdlog::level::trace); logger->flush_on(spdlog::level::trace); logger->set_pattern("%Y-%m-%d %H:%M:%S.%e [%^HKU-%L%$] - %v (%s:%#)"); @@ -126,39 +83,4 @@ void HKU_UTILS_API initLogger(bool inJupyter) { spdlog::set_default_logger(logger); } -#endif /* #if HKU_USE_SPDLOG_ASYNC_LOGGER */ - -void set_log_level(LOG_LEVEL level) { - g_log_level = level; - getHikyuuLogger()->set_level((spdlog::level::level_enum)level); -} - -#else /* #if USE_SPDLOG_LOGGER */ -/********************************************** - * Use SPDLOG for logging - *********************************************/ -void HKU_UTILS_API initLogger(bool inJupyter) {} - -void set_log_level(LOG_LEVEL level) { - g_log_level = level; -} - -std::string HKU_UTILS_API getLocalTime() { - auto now = std::chrono::system_clock::now(); - uint64_t dis_millseconds = - std::chrono::duration_cast(now.time_since_epoch()).count() - - std::chrono::duration_cast(now.time_since_epoch()).count() * 1000; - time_t tt = std::chrono::system_clock::to_time_t(now); -#ifdef _WIN32 - struct tm now_time; - localtime_s(&now_time, &tt); -#else - struct tm now_time; - localtime_r(&tt, &now_time); -#endif - return fmt::format("{:%Y-%m-%d %H:%M:%S}.{:<3d}", now_time, dis_millseconds); -} - -#endif /* #if USE_SPDLOG_LOGGER */ - } // namespace hku diff --git a/hikyuu_cpp/hikyuu/utilities/Log.h b/hikyuu_cpp/hikyuu/utilities/Log.h index 6fe26dd4..598d0353 100644 --- a/hikyuu_cpp/hikyuu/utilities/Log.h +++ b/hikyuu_cpp/hikyuu/utilities/Log.h @@ -20,18 +20,15 @@ #define HKU_LOG_ACTIVE_LEVEL 0 #endif -#if !defined(USE_SPDLOG_LOGGER) -#define USE_SPDLOG_LOGGER 1 +// clang-format off +#ifndef SPDLOG_ACTIVE_LEVEL +#define SPDLOG_ACTIVE_LEVEL HKU_LOG_ACTIVE_LEVEL #endif -// clang-format off -#if USE_SPDLOG_LOGGER - #define SPDLOG_ACTIVE_LEVEL HKU_LOG_ACTIVE_LEVEL - #include - #include - #if HKU_USE_SPDLOG_ASYNC_LOGGER - #include "spdlog/async.h" - #endif +#include +#include +#if HKU_USE_SPDLOG_ASYNC_LOGGER + #include "spdlog/async.h" #endif // clang-format on @@ -56,7 +53,7 @@ namespace hku { /********************************************** * Use SPDLOG for logging *********************************************/ -#if USE_SPDLOG_LOGGER + /** 日志级别 */ enum LOG_LEVEL { LOG_TRACE = SPDLOG_LEVEL_TRACE, ///< 跟踪 @@ -68,6 +65,14 @@ enum LOG_LEVEL { LOG_OFF = SPDLOG_LEVEL_OFF, ///< 关闭日志打印 }; +/** + * 初始化 logger + * @param not_use_color 不使用彩色输出 + * @param filename 日志文件名,为空时默认为当前目录下 "./hikyuu.log",需自行保存存放目录存在且可写入 + */ +void HKU_UTILS_API initLogger(bool not_use_color = false, + const std::string& filename = std::string()); + /** * 获取当前日志级别 * @return @@ -89,76 +94,6 @@ std::shared_ptr HKU_UTILS_API getHikyuuLogger(); #define HKU_ERROR(...) SPDLOG_LOGGER_ERROR(hku::getHikyuuLogger(), __VA_ARGS__) #define HKU_FATAL(...) SPDLOG_LOGGER_CRITICAL(hku::getHikyuuLogger(), __VA_ARGS__) -void HKU_UTILS_API initLogger(bool inJupyter = false); - -#else -enum LOG_LEVEL { - LOG_TRACE = 0, - LOG_DEBUG = 1, - LOG_INFO = 2, - LOG_WARN = 3, - LOG_ERROR = 4, - LOG_FATAL = 5, - LOG_OFF = 6, -}; - -LOG_LEVEL HKU_UTILS_API get_log_level(); -void HKU_UTILS_API set_log_level(LOG_LEVEL level); -void HKU_UTILS_API initLogger(bool inJupyter = false); - -/** 获取系统当前时间,精确到毫秒,如:2001-01-02 13:01:02.001 */ -std::string HKU_UTILS_API getLocalTime(); - -#if HKU_LOG_ACTIVE_LEVEL <= 0 -#define HKU_TRACE(...) \ - fmt::print("[{}] [HKU-T] - {} ({}:{})\n", getLocalTime(), fmt::format(__VA_ARGS__), __FILE__, \ - __LINE__); -#else -#define HKU_TRACE(...) -#endif - -#if HKU_LOG_ACTIVE_LEVEL <= 1 -#define HKU_DEBUG(...) \ - fmt::print("[{}] [HKU-D] - {} ({}:{})\n", getLocalTime(), fmt::format(__VA_ARGS__), __FILE__, \ - __LINE__); -#else -#define HKU_DEBUG(...) -#endif - -#if HKU_LOG_ACTIVE_LEVEL <= 2 -#define HKU_INFO(...) \ - fmt::print("[{}] [HKU-I] - {} ({}:{})\n", getLocalTime(), fmt::format(__VA_ARGS__), __FILE__, \ - __LINE__); -#else -#define HKU_INFO(...) -#endif - -#if HKU_LOG_ACTIVE_LEVEL <= 3 -#define HKU_WARN(...) \ - fmt::print("[{}] [HKU-W] - {} ({}:{})\n", getLocalTime(), fmt::format(__VA_ARGS__), __FILE__, \ - __LINE__); -#else -#define HKU_WARN(...) -#endif - -#if HKU_LOG_ACTIVE_LEVEL <= 4 -#define HKU_ERROR(...) \ - fmt::print("[{}] [HKU-E] - {} ({}:{})\n", getLocalTime(), fmt::format(__VA_ARGS__), __FILE__, \ - __LINE__); -#else -#define HKU_ERROR(...) -#endif - -#if HKU_LOG_ACTIVE_LEVEL <= 5 -#define HKU_FATAL(...) \ - fmt::print("[{}] [HKU-F] - {} ({}:{})\n", getLocalTime(), fmt::format(__VA_ARGS__), __FILE__, \ - __LINE__); -#else -#define HKU_FATAL(...) -#endif - -#endif /* USE_SPDLOG_LOGGER */ - /////////////////////////////////////////////////////////////////////////////// // // clang/gcc 下使用 __PRETTY_FUNCTION__ 会包含函数参数,可以在编译时指定 @@ -409,6 +344,7 @@ std::string HKU_UTILS_API getLocalTime(); /** 用于 catch (...) 中打印,减少编译后代码大小 */ extern std::string g_unknown_error_msg; +#define HKU_THROW_UNKNOWN HKU_THROW(g_unknown_error_msg); #define HKU_TRACE_UNKNOWN HKU_TRACE(g_unknown_error_msg) #define HKU_DEBUG_UNKNOWN HKU_DEBUG(g_unknown_error_msg) #define HKU_INFO_UNKNOWN HKU_INFO(g_unknown_error_msg) @@ -459,6 +395,15 @@ protected: \ #define CLS_FATAL_IF_RETURN(expr, ret, ...) \ HKU_FATAL_IF_RETURN(expr, ret, fmt::format("[{}] {}", ms_logger, fmt::format(__VA_ARGS__))) +#define CLS_ASSERT HKU_ASSERT +#define CLS_CHECK(expr, ...) \ + HKU_CHECK(expr, fmt::format("[{}] {}", ms_logger, fmt::format(__VA_ARGS__))) +#define CLS_CHECK_THROW(expr, except, ...) \ + HKU_CHECK_THROW(expr, except, fmt::format("[{}] {}", ms_logger, fmt::format(__VA_ARGS__))) +#define CLS_THROW(...) HKU_THROW(fmt::format("[{}] {}", ms_logger, fmt::format(__VA_ARGS__))) +#define CLS_THROW_EXCEPTION(except, ...) \ + HKU_THROW_EXCEPTION(except, fmt::format("[{}] {}", ms_logger, fmt::format(__VA_ARGS__))) + /** @} */ } /* namespace hku */ diff --git a/hikyuu_cpp/hikyuu/utilities/ResourcePool.h b/hikyuu_cpp/hikyuu/utilities/ResourcePool.h new file mode 100644 index 00000000..f246ceaf --- /dev/null +++ b/hikyuu_cpp/hikyuu/utilities/ResourcePool.h @@ -0,0 +1,636 @@ +/* + * ResourcePool.h + * + * Copyright (c) 2019, hikyuu.org + * + * Created on: 2019-8-5 + * Author: fasiondog + */ +#pragma once +#ifndef HKU_UTILS_RESOURCE_POOL_H +#define HKU_UTILS_RESOURCE_POOL_H + +#include +#include +#include +#include +#include +#include +#include "Parameter.h" +#include "Log.h" + +namespace hku { + +/** + * 资源获取超时异常 + */ +class GetResourceTimeoutException : public hku::exception { +public: + GetResourceTimeoutException(const char *msg) + : hku::exception(fmt::format("GetResourceTimeoutException {}", msg)) {} + + GetResourceTimeoutException(const std::string &msg) + : hku::exception(fmt::format("GetResourceTimeoutException {}", msg)) {} + + virtual ~GetResourceTimeoutException() {} +}; + +/** + * 新资源创建失败异常 + */ +class CreateResourceException : public hku::exception { +public: + CreateResourceException(const char *msg) + : hku::exception(fmt::format("CreateResourceException {}", msg)) {} + + CreateResourceException(const std::string &msg) + : hku::exception(fmt::format("CreateResourceException {}", msg)) {} + + virtual ~CreateResourceException() {} +}; + +/** + * 通用共享资源池 + * @ingroup Utilities + */ +template +class ResourcePool { +public: + ResourcePool() = delete; + ResourcePool(const ResourcePool &) = delete; + ResourcePool &operator=(const ResourcePool &) = delete; + + /** + * 构造函数 + * @param param 连接参数 + * @param maxPoolSize 允许的最大共享资源数,为 0 表示不限制 + * @param maxIdleNum 运行的最大空闲资源数,为 0 表示用完即刻释放,无缓存 + */ + explicit ResourcePool(const Parameter ¶m, size_t maxPoolSize = 0, size_t maxIdleNum = 100) + : m_maxPoolSize(maxPoolSize), m_maxIdelSize(maxIdleNum), m_count(0), m_param(param) {} + + /** + * 析构函数,释放所有缓存的资源 + */ + virtual ~ResourcePool() { + std::unique_lock lock(m_mutex); + + // 将所有已分配资源的 closer 和 pool 解绑 + for (auto iter = m_closer_set.begin(); iter != m_closer_set.end(); ++iter) { + (*iter)->unbind(); + } + + // 删除所有空闲资源 + while (!m_resourceList.empty()) { + ResourceType *p = m_resourceList.front(); + m_resourceList.pop(); + if (p) { + delete p; + } + } + } + + /** 获取当前允许的最大资源数 */ + size_t maxPoolSize() const { + return m_maxIdelSize; + } + + /** 获取当前允许的最大空闲资源数 */ + size_t maxIdleSize() const { + return m_maxIdelSize; + } + + /** 设置最大资源数 */ + void maxPoolSize(size_t num) { + std::lock_guard lock(m_mutex); + m_maxPoolSize = num; + } + + /** 设置允许的最大空闲资源数 */ + void maxIdleSize(size_t num) { + std::lock_guard lock(m_mutex); + m_maxIdelSize = num; + } + + /** 资源实例指针类型 */ + typedef std::shared_ptr ResourcePtr; + + /** + * 获取可用资源,如超出允许的最大资源数将返回空指针 + * @exception CreateResourceException 新资源创建可能抛出异常 + */ + ResourcePtr get() { + std::lock_guard lock(m_mutex); + ResourcePtr result; + ResourceType *p = nullptr; + if (m_resourceList.empty()) { + if (m_maxPoolSize > 0 && m_count >= m_maxPoolSize) { + return result; + } + try { + p = new ResourceType(m_param); + } catch (const std::exception &e) { + HKU_THROW_EXCEPTION(CreateResourceException, "Failed create a new Resource! {}", + e.what()); + } catch (...) { + HKU_THROW_EXCEPTION(CreateResourceException, + "Failed create a new Resource! Unknown error!"); + } + m_count++; + result = ResourcePtr(p, ResourceCloser(this)); + m_closer_set.insert(std::get_deleter(result)); + return result; + } + p = m_resourceList.front(); + m_resourceList.pop(); + result = ResourcePtr(p, ResourceCloser(this)); + m_closer_set.insert(std::get_deleter(result)); + return result; + } + + /** + * 在指定的超时时间内获取可用资源 + * @param ms_timeout 超时时间,单位毫秒 + * @exception GetResourceTimeoutException, CreateResourceException + */ + ResourcePtr getWaitFor(uint64_t ms_timeout) { // NOSONAR + std::unique_lock lock(m_mutex); + ResourcePtr result; + ResourceType *p = nullptr; + if (m_resourceList.empty()) { + if (m_maxPoolSize > 0 && m_count >= m_maxPoolSize) { + // HKU_TRACE("超出最大资源数,等待空闲资源"); + if (ms_timeout > 0) { + if (m_cond.wait_for(lock, + std::chrono::duration(ms_timeout), + [&] { return !m_resourceList.empty(); })) { + HKU_CHECK_THROW(!m_resourceList.empty(), GetResourceTimeoutException, + "Failed get resource!"); + } else { + HKU_THROW_EXCEPTION(GetResourceTimeoutException, "Failed get resource!"); + } + } else { + m_cond.wait(lock, [this] { return !m_resourceList.empty(); }); + } + } else { + try { + p = new ResourceType(m_param); + } catch (const std::exception &e) { + HKU_THROW_EXCEPTION(CreateResourceException, "Failed create a new Resource! {}", + e.what()); + } catch (...) { + HKU_THROW_EXCEPTION(CreateResourceException, + "Failed create a new Resource! Unknown error!"); + } + m_count++; + result = ResourcePtr(p, ResourceCloser(this)); + m_closer_set.insert(std::get_deleter(result)); + return result; + } + } + p = m_resourceList.front(); + m_resourceList.pop(); + result = ResourcePtr(p, ResourceCloser(this)); + m_closer_set.insert(std::get_deleter(result)); + return result; + } + + /** + * 获取可用资源,如超出允许的最大资源数,将阻塞等待直到获得空闲资源 + * @exception CreateResourceException 新资源创建可能抛出异常 + */ + ResourcePtr getAndWait() { + return getWaitFor(0); + } + + /** 当前活动的资源数, 即全部资源数(含空闲及被使用的资源) */ + size_t count() const { + return m_count; + } + + /** 当前空闲的资源数 */ + size_t idleCount() const { + return m_resourceList.size(); + } + + /** 释放当前所有的空闲资源 */ + void releaseIdleResource() { + std::lock_guard lock(m_mutex); + _releaseIdleResourceNoLock(); + } + +private: + void _releaseIdleResourceNoLock() { + while (!m_resourceList.empty()) { + ResourceType *p = m_resourceList.front(); + m_resourceList.pop(); + m_count--; + if (p) { + delete p; + } + } + } + +private: + size_t m_maxPoolSize; // 允许的最大共享资源数 + size_t m_maxIdelSize; // 允许的最大空闲资源数 + size_t m_count; // 当前活动的资源数 + Parameter m_param; + std::mutex m_mutex; + std::condition_variable m_cond; + std::queue m_resourceList; + + class ResourceCloser { + public: + explicit ResourceCloser(ResourcePool *pool) : m_pool(pool) { // NOSONAR + } + + void operator()(ResourceType *conn) { // NOSONAR + if (conn) { + // 如果绑定了 pool,则归还资源;否则删除 + if (m_pool) { + // HKU_DEBUG("retuan to pool"); + m_pool->returnResource(conn, this); + } else { + // HKU_DEBUG("delete resource not in pool"); + delete conn; + } + } + } + + // 解绑资源池 + void unbind() { + m_pool = nullptr; + } + + private: + ResourcePool *m_pool; + }; + + /** 归还至资源池 */ + void returnResource(ResourceType *p, ResourceCloser *closer) { + std::unique_lock lock(m_mutex); + if (p) { + if (m_resourceList.size() < m_maxIdelSize) { + m_resourceList.push(p); + m_cond.notify_all(); + } else { + delete p; + m_count--; + } + } else { + m_count--; + // HKU_WARN("Trying to return an empty pointer!"); + } + if (closer) { + m_closer_set.erase(closer); // 移除该 closer + } + } + + std::unordered_set m_closer_set; // 占用资源的 closer +}; + +/** + * @brief 带版本的资源接口,可由需要版本管理的资源继承 + * @details 自带的 getVersion 和 setVerion 方法由 ResourceVersionPool 调用,不建议带有其他用途 + */ +class ResourceWithVersion { +public: + /** 默认构造函数 */ + ResourceWithVersion() : m_version(0) {} + + /** 析构函数 */ + virtual ~ResourceWithVersion() {} + + /** 获取资源版本 */ + int getVersion() const { + return m_version; + } + + /** 设置资源版本 **/ + void setVersion(int version) { + m_version = version; + } + +protected: + int m_version; +}; + +/** + * 通用版本的共享资源池,当资源池参数变更时,保证新资源使用新参数,老版本的资源再使用完毕后被自动回收 + * @details 要求资源类具备 int getVersion() 和 void setVersion(int) 另个接口函数 + * @ingroup Utilities + */ +template +class ResourceVersionPool { +public: + ResourceVersionPool() = delete; + ResourceVersionPool(const ResourceVersionPool &) = delete; + ResourceVersionPool &operator=(const ResourceVersionPool &) = delete; + + /** + * 构造函数 + * @param param 连接参数 + * @param maxPoolSize 允许的最大共享资源数,为 0 表示不限制 + * @param maxIdleNum 运行的最大空闲资源数,为 0 表示用完即刻释放,无缓存 + */ + explicit ResourceVersionPool(const Parameter ¶m, size_t maxPoolSize = 0, + size_t maxIdleNum = 100) + : m_maxPoolSize(maxPoolSize), + m_maxIdelSize(maxIdleNum), + m_count(0), + m_param(param), + m_version(0) {} + + /** + * 析构函数,释放所有缓存的资源 + */ + virtual ~ResourceVersionPool() { + std::unique_lock lock(m_mutex); + + // 将所有已分配资源的 closer 和 pool 解绑 + for (auto iter = m_closer_set.begin(); iter != m_closer_set.end(); ++iter) { + (*iter)->unbind(); + } + + // 删除所有空闲资源 + while (!m_resourceList.empty()) { + ResourceType *p = m_resourceList.front(); + m_resourceList.pop(); + if (p) { + delete p; + } + } + } + + /** 获取当前允许的最大资源数 */ + size_t maxPoolSize() const { + return m_maxIdelSize; + } + + /** 获取当前允许的最大空闲资源数 */ + size_t maxIdleSize() const { + return m_maxIdelSize; + } + + /** 设置最大资源数 */ + void maxPoolSize(size_t num) { + std::lock_guard lock(m_mutex); + m_maxPoolSize = num; + } + + /** 设置允许的最大空闲资源数 */ + void maxIdleSize(size_t num) { + std::lock_guard lock(m_mutex); + m_maxIdelSize = num; + } + + /** 指定参数是否存在 */ + bool haveParam(const std::string &name) { + std::lock_guard lock(m_mutex); + return m_param.have(name); + } + + /** 获取指定参数的值,如参数不存在或类型不匹配抛出异常 */ + template + ValueType getParam(const std::string &name) { + std::lock_guard lock(m_mutex); + return m_param.get(name); + } + + /** + * @brief 设定指定参数的值,参数仅在生成新的资源时生效 + * @details 在原本存在该参数的情况下,新设定的值类型须和原有参数类型相同,否则将抛出异常 + * @param name 参数名 + * @param value 参数值 + * @exception std::logic_error + */ + template + void setParam(const std::string &name, const ValueType &value) { + std::lock_guard lock(m_mutex); + // 如果参数未实际发送变化,则直接返回 + HKU_IF_RETURN(m_param.have(name) && value == m_param.get(name), void()); + m_param.set(name, value); + m_version++; + _releaseIdleResourceNoLock(); // 释放当前空闲资源,以便新参数值生效 + } + + /** + * @brief 设置资源参数,参数仅在生成新的资源时生效 + * @param param 参数对象 + */ + void setParameter(const Parameter ¶m) { + std::lock_guard lock(m_mutex); + m_param = param; + m_version++; + _releaseIdleResourceNoLock(); // 释放当前空闲资源,以便新参数值生效 + } + + /** + * @brief 设置资源参数,参数仅在生成新的资源时生效 + * @param param 参数对象 + */ + void setParameter(Parameter &¶m) { + std::lock_guard lock(m_mutex); + m_param = std::move(param); + m_version++; + _releaseIdleResourceNoLock(); // 释放当前空闲资源,以便新参数值生效 + } + + /** 获取当前资源池版本 */ + int getVersion() { + std::lock_guard lock(m_mutex); + return m_version; + } + + /** 递增当前资源池版本,相当于通知资源池资源版本发生变化 */ + void incVersion(int version) { + std::lock_guard lock(m_mutex); + m_version++; + } + + /** 资源实例指针类型 */ + typedef std::shared_ptr ResourcePtr; + + /** + * 获取可用资源,如超出允许的最大资源数将返回空指针 + * @exception CreateResourceException 新资源创建可能抛出异常 + */ + ResourcePtr get() { + std::lock_guard lock(m_mutex); + ResourcePtr result; + ResourceType *p = nullptr; + if (m_resourceList.empty()) { + if (m_maxPoolSize > 0 && m_count >= m_maxPoolSize) { + return result; + } + try { + p = new ResourceType(m_param); + p->setVersion(m_version); + } catch (const std::exception &e) { + HKU_THROW_EXCEPTION(CreateResourceException, "Failed create a new Resource! {}", + e.what()); + } catch (...) { + HKU_THROW_EXCEPTION(CreateResourceException, + "Failed create a new Resource! Unknown error!"); + } + m_count++; + result = ResourcePtr(p, ResourceCloser(this)); + m_closer_set.insert(std::get_deleter(result)); + return result; + } + p = m_resourceList.front(); + m_resourceList.pop(); + result = ResourcePtr(p, ResourceCloser(this)); + m_closer_set.insert(std::get_deleter(result)); + return result; + } + + /** + * 在指定的超时时间内获取可用资源 + * @param ms_timeout 超时时间,单位毫秒 + * @exception GetResourceTimeoutException, CreateResourceException + */ + ResourcePtr getWaitFor(uint64_t ms_timeout) { // NOSONAR + std::unique_lock lock(m_mutex); + ResourcePtr result; + ResourceType *p = nullptr; + if (m_resourceList.empty()) { + if (m_maxPoolSize > 0 && m_count >= m_maxPoolSize) { + // HKU_TRACE("超出最大资源数,等待空闲资源"); + if (ms_timeout > 0) { + if (m_cond.wait_for(lock, + std::chrono::duration(ms_timeout), + [&] { return !m_resourceList.empty(); })) { + HKU_CHECK_THROW(!m_resourceList.empty(), GetResourceTimeoutException, + "Failed get resource!"); + } else { + HKU_THROW_EXCEPTION(GetResourceTimeoutException, "Failed get resource!"); + } + } else { + m_cond.wait(lock, [this] { return !m_resourceList.empty(); }); + } + } else { + try { + p = new ResourceType(m_param); + p->setVersion(m_version); + } catch (const std::exception &e) { + HKU_THROW_EXCEPTION(CreateResourceException, "Failed create a new Resource! {}", + e.what()); + } catch (...) { + HKU_THROW_EXCEPTION(CreateResourceException, + "Failed create a new Resource! Unknown error!"); + } + m_count++; + result = ResourcePtr(p, ResourceCloser(this)); + m_closer_set.insert(std::get_deleter(result)); + return result; + } + } + p = m_resourceList.front(); + m_resourceList.pop(); + result = ResourcePtr(p, ResourceCloser(this)); + m_closer_set.insert(std::get_deleter(result)); + return result; + } + + /** + * 获取可用资源,如超出允许的最大资源数,将阻塞等待直到获得空闲资源 + * @exception CreateResourceException 新资源创建可能抛出异常 + */ + ResourcePtr getAndWait() { + return getWaitFor(0); + } + + /** 当前活动的资源数, 即全部资源数(含空闲及被使用的资源) */ + size_t count() const { + return m_count; + } + + /** 当前空闲的资源数 */ + size_t idleCount() const { + return m_resourceList.size(); + } + + /** 释放当前所有的空闲资源 */ + void releaseIdleResource() { + std::lock_guard lock(m_mutex); + _releaseIdleResourceNoLock(); + } + +private: + void _releaseIdleResourceNoLock() { + while (!m_resourceList.empty()) { + ResourceType *p = m_resourceList.front(); + m_resourceList.pop(); + m_count--; + if (p) { + delete p; + } + } + } + +private: + size_t m_maxPoolSize; // 允许的最大共享资源数 + size_t m_maxIdelSize; // 允许的最大空闲资源数 + size_t m_count; // 当前活动的资源数 + Parameter m_param; + std::mutex m_mutex; + std::condition_variable m_cond; + std::queue m_resourceList; + int m_version; + + class ResourceCloser { + public: + explicit ResourceCloser(ResourceVersionPool *pool) : m_pool(pool) { // NOSONAR + } + + void operator()(ResourceType *conn) { // NOSONAR + if (conn) { + // 如果绑定了 pool,则归还资源;否则删除 + if (m_pool) { + // HKU_DEBUG("retuan to pool"); + m_pool->returnResource(conn, this); + } else { + // HKU_DEBUG("delete resource not in pool"); + delete conn; + } + } + } + + // 解绑资源池 + void unbind() { + m_pool = nullptr; + } + + private: + ResourceVersionPool *m_pool; + }; + + /** 归还至资源池 */ + void returnResource(ResourceType *p, ResourceCloser *closer) { + std::unique_lock lock(m_mutex); + if (p) { + // 当前归还资源的版本和资源池版本相等,且空闲资源列表小于最大空闲资源数时,接受归还的资源 + if (p->getVersion() == m_version && m_resourceList.size() < m_maxIdelSize) { + m_resourceList.push(p); + m_cond.notify_all(); + } else { + delete p; + m_count--; + } + } else { + m_count--; + // HKU_WARN("Trying to return an empty pointer!"); + } + if (closer) { + m_closer_set.erase(closer); // 移除该 closer + } + } + + std::unordered_set m_closer_set; // 占用资源的 closer +}; + +} // namespace hku + +#endif /* HKU_UTILS_RESOURCE_POOL_H */ \ No newline at end of file diff --git a/hikyuu_cpp/hikyuu/utilities/base64.cpp b/hikyuu_cpp/hikyuu/utilities/base64.cpp new file mode 100644 index 00000000..09dcb1f6 --- /dev/null +++ b/hikyuu_cpp/hikyuu/utilities/base64.cpp @@ -0,0 +1,108 @@ +/* + * Copyright (c) hikyuu.org + * + * Created on: 2020-6-2 + * Author: fasiondog + */ + +#include "base64.h" +#include "Log.h" + +namespace hku { + +static const std::string base64_chars = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; + +std::string base64_encode(unsigned char const* bytes_to_encode, size_t in_len) { // NOSONAR + HKU_CHECK(bytes_to_encode, "Input null ptr!"); + std::string ret; + HKU_IF_RETURN(in_len == 0, ret); + + int i = 0; + unsigned char char_array_3[3]; + unsigned char char_array_4[4]; + + while (in_len--) { + char_array_3[i++] = *(bytes_to_encode++); + if (i == 3) { + char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; + char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); + char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); + char_array_4[3] = char_array_3[2] & 0x3f; + + for (i = 0; (i < 4); i++) + ret += base64_chars[char_array_4[i]]; + i = 0; + } + } + + if (i) { + for (int j = i; j < 3; j++) + char_array_3[j] = '\0'; + + char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; + char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); + char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); + char_array_4[3] = char_array_3[2] & 0x3f; + + for (int j = 0; (j < i + 1); j++) + ret += base64_chars[char_array_4[j]]; + + while ((i++ < 3)) + ret += '='; + } + + return ret; +} + +static inline bool is_base64(unsigned char c) { + return (isalnum(c) || (c == '+') || (c == '/')); +} + +std::string base64_decode(unsigned char const* encoded_string, size_t in_len) { + HKU_CHECK(encoded_string, "Input null ptr!"); + std::string ret; + HKU_IF_RETURN(in_len == 0, ret); + + int i = 0; + int in_ = 0; + unsigned char char_array_4[4], char_array_3[3]; + + while (in_len-- && (encoded_string[in_] != '=') && is_base64(encoded_string[in_])) { + char_array_4[i++] = encoded_string[in_]; + in_++; + if (i == 4) { + for (i = 0; i < 4; i++) + char_array_4[i] = (unsigned char)base64_chars.find(char_array_4[i]); + + char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); + char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); + char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; + + for (i = 0; (i < 3); i++) + ret += char_array_3[i]; + i = 0; + } + } + + if (i) { + for (int j = i; j < 4; j++) + char_array_4[j] = 0; + + for (int j = 0; j < 4; j++) + char_array_4[j] = (unsigned char)base64_chars.find(char_array_4[j]); + + char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); + char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); + char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; + + for (int j = 0; (j < i - 1); j++) + ret += char_array_3[j]; + } + + return ret; +} + +} // namespace hku \ No newline at end of file diff --git a/hikyuu_cpp/hikyuu/utilities/base64.h b/hikyuu_cpp/hikyuu/utilities/base64.h new file mode 100644 index 00000000..54ea3d99 --- /dev/null +++ b/hikyuu_cpp/hikyuu/utilities/base64.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) hikyuu.org + * + * Created on: 2020-6-2 + * Author: fasiondog + */ + +#pragma once +#ifndef HKU_UTILS_BASE64_H +#define HKU_UTILS_BASE64_H + +#include +#include "string_view.h" + +#ifndef HKU_UTILS_API +#define HKU_UTILS_API +#endif + +namespace hku { + +/** + * 将二进制 bytes 数组编码成 base64 字符串 + * @param bytes_to_encode 内存起始地址 + * @param in_len 待计算的字节长度 + */ +std::string HKU_UTILS_API base64_encode(unsigned char const* bytes_to_encode, size_t in_len); + +/** + * 字符串编码为 base64 + * @param bytes_to_encode 内存起始地址 + * @param in_len 待计算的字节长度 + * @note 通过 func(unsigned char *, unsigned int) 函数实现,而不是直接只提供 string_view + * 版本的原因是:c++17 string_view 处理 nullptr 时,程序会直接挂掉,无异常 + */ +inline std::string base64_encode(string_view src) { + return base64_encode((unsigned char const*)src.data(), src.size()); +} + +/** + * 将 base64 字符串解码 + * @param encoded_string base64 编码的字符串 + * @param in_len 字符串长度 + * @return string 实际解码后的二进制内容保存在返回的字符串对象中 + * @note 如果传入的base64编码字符串中含有非法字符,不会告警,仅处理到能处理的字符 + */ +std::string HKU_UTILS_API base64_decode(unsigned char const* encoded_string, size_t in_len); + +/** + * 将 base64 字符串解码 + * @param encoded_string base64 编码的字符串 + * @return string 实际解码后的二进制内容保存在返回的字符串对象中 + */ +inline std::string base64_decode(string_view encoded_string) { + return base64_decode((unsigned char const*)encoded_string.data(), encoded_string.size()); +} + +} // namespace hku + +#endif // HKU_UTILS_BASE64_H \ No newline at end of file diff --git a/hikyuu_cpp/hikyuu/utilities/exception.h b/hikyuu_cpp/hikyuu/utilities/exception.h index 4a6faae9..da0cafdf 100644 --- a/hikyuu_cpp/hikyuu/utilities/exception.h +++ b/hikyuu_cpp/hikyuu/utilities/exception.h @@ -29,6 +29,7 @@ public: exception(const std::string& msg) // cppcheck-suppress noExplicitConstructor : std::exception(msg.c_str()) {} exception(const char* msg) : std::exception(msg) {} // cppcheck-suppress noExplicitConstructor + virtual ~exception() noexcept {}; }; #else diff --git a/hikyuu_cpp/hikyuu/utilities/http_client/HttpClient.cpp b/hikyuu_cpp/hikyuu/utilities/http_client/HttpClient.cpp new file mode 100644 index 00000000..1f5fb194 --- /dev/null +++ b/hikyuu_cpp/hikyuu/utilities/http_client/HttpClient.cpp @@ -0,0 +1,208 @@ +/* + * Copyright (c) 2024 hikyuu.org + * + * Created on: 2024-07-26 + * Author: fasiondog + */ + +#include "HttpClient.h" + +#if HKU_ENABLE_HTTP_CLIENT_ZIP +#include "gzip/compress.hpp" +#include "gzip/decompress.hpp" +#endif + +#include +#include "hikyuu/utilities/Log.h" +#include "hikyuu/utilities/os.h" +#include "url.h" + +namespace hku { + +HttpResponse::HttpResponse() { + NNG_CHECK(nng_http_res_alloc(&m_res)); +} + +HttpResponse::~HttpResponse() { + if (m_res) { + nng_http_res_free(m_res); + } +} + +void HttpResponse::reset() { + if (m_res) { + nng_http_res_free(m_res); + NNG_CHECK(nng_http_res_alloc(&m_res)); + } + m_body.clear(); +} + +HttpResponse::HttpResponse(HttpResponse&& rhs) : m_res(rhs.m_res), m_body(std::move(rhs.m_body)) { + rhs.m_res = nullptr; +} + +HttpResponse& HttpResponse::operator=(HttpResponse&& rhs) { + if (this != &rhs) { + if (m_res != nullptr) { + nng_http_res_free(m_res); + } + m_res = rhs.m_res; + rhs.m_res = nullptr; + m_body = std::move(rhs.m_body); + } + return *this; +} + +HttpClient::~HttpClient() { + reset(); +#if HKU_ENABLE_HTTP_CLIENT_SSL + m_tls_cfg.release(); +#endif +} + +void HttpClient::reset() { + m_client.release(); + m_conn.close(); + m_aio.release(); +} + +void HttpClient::setCaFile(const std::string& filename) { +#if HKU_ENABLE_HTTP_CLIENT_SSL + HKU_CHECK(!filename.empty(), "Input filename is empty!"); + HKU_IF_RETURN(filename == m_ca_file, void()); + HKU_CHECK(existFile(filename), "Not exist file: {}", filename); + m_tls_cfg.set_ca_file(filename); + m_ca_file = filename; + reset(); +#else + HKU_THROW("Not support https! Please complie with --http_client_ssl!"); +#endif +} + +void HttpClient::_connect() { + HKU_CHECK(m_url.valid(), "Invalid url: {}", m_url.raw_url()); + + m_client.set_url(m_url); + + if (m_url.is_https()) { +#if HKU_ENABLE_HTTP_CLIENT_SSL + auto* old_cfg = m_client.get_tls_cfg(); + if (!old_cfg || old_cfg != m_tls_cfg.get()) { + m_client.set_tls_cfg(m_tls_cfg.get()); + } +#endif + } + + m_aio.alloc(m_timeout_ms); + m_client.connect(m_aio); + + if (!m_conn.valid()) { + NNG_CHECK(m_aio.wait().result()); + m_conn = std::move(nng::http_conn((nng_http_conn*)m_aio.get_output(0))); + } +} + +HttpResponse HttpClient::request(const std::string& method, const std::string& path, + const HttpParams& params, const HttpHeaders& headers, + const char* body, size_t body_len, + const std::string& content_type) { + HKU_CHECK(m_url.valid(), "Invalid url: {}", m_url.raw_url()); + + HttpResponse res; + try { + std::ostringstream buf; + bool first = true; + for (auto iter = params.cbegin(); iter != params.cend(); ++iter) { + if (first) { + buf << "?"; + } else { + buf << "&"; + } + buf << iter->first << "=" << iter->second; + } + + std::string uri = buf.str(); + uri = uri.empty() ? path : fmt::format("{}{}", path, uri); + res = _readResChunk(method, uri, headers, body, body_len, content_type); + + if (res.getHeader("Connection") == "close") { + HKU_WARN("Connect closed"); + reset(); + } + + } catch (const std::exception&) { + reset(); + throw; + } catch (...) { + reset(); + HKU_THROW_UNKNOWN; + } + return res; +} + +HttpResponse HttpClient::_readResChunk(const std::string& method, const std::string& uri, + const HttpHeaders& headers, const char* body, + size_t body_len, const std::string& content_type) { + HttpResponse res; + nng::http_req req(m_url); + req.set_method(method).set_uri(uri).add_headers(m_default_headers).add_headers(headers); + if (body != nullptr) { + HKU_CHECK(body_len > 0, "Body is not null, but len is zero!"); + req.add_header("Content-Type", content_type); + +#if HKU_ENABLE_HTTP_CLIENT_ZIP + if (req.get_header("Content-Encoding") == "gzip") { + gzip::Compressor comp(Z_DEFAULT_COMPRESSION); + std::string output; + comp.compress(output, body, body_len); + req.copy_data(output.data(), output.size()); + } else { + req.set_data(body, body_len); + } +#else + req.del_header("Content-Encoding").set_data(body, body_len); +#endif + } + + int count = 0; + while (count < 2) { + count++; + _connect(); + + m_conn.transact(req.get(), res.get(), m_aio); + int rv = m_aio.wait().result(); + if (0 == rv) { + break; + } else if (NNG_ETIMEDOUT == rv) { + throw HttpTimeoutException(); + } else if (NNG_ECLOSED == rv || NNG_ECONNSHUT == rv || NNG_ECONNREFUSED == rv) { + // HKU_DEBUG("rv: {}", nng_strerror(rv)); + reset(); + res.reset(); + } else { + HKU_THROW("[NNG_ERROR] {} ", nng_strerror(rv)); + } + } + + HKU_IF_RETURN(res.status() != NNG_HTTP_STATUS_OK, res); + + void* data; + size_t len; + nng_http_res_get_data(res.get(), &data, &len); + +#if HKU_ENABLE_HTTP_CLIENT_ZIP + if (res.getHeader("Content-Encoding") == "gzip") { + res.m_body = gzip::decompress((const char*)data, len); + } else { + res._resizeBody(len); + memcpy(res.m_body.data(), data, len); + } +#else + res._resizeBody(len); + memcpy(res.m_body.data(), data, len); +#endif + + return res; +} + +} // namespace hku \ No newline at end of file diff --git a/hikyuu_cpp/hikyuu/utilities/http_client/HttpClient.h b/hikyuu_cpp/hikyuu/utilities/http_client/HttpClient.h new file mode 100644 index 00000000..3ecba6fd --- /dev/null +++ b/hikyuu_cpp/hikyuu/utilities/http_client/HttpClient.h @@ -0,0 +1,191 @@ +/* + * Copyright (c) 2024 hikyuu.org + * + * Created on: 2024-07-26 + * Author: fasiondog + */ + +#pragma once +#ifndef HKU_UTILS_HTTP_CLIENT_H +#define HKU_UTILS_HTTP_CLIENT_H + +#include "hikyuu/utilities/config.h" +#if !HKU_ENABLE_HTTP_CLIENT +#error "Don't enable http client, please config with --http_client=y" +#endif + +#include +#include +#include "nng_wrap.h" + +#ifndef HKU_UTILS_API +#define HKU_UTILS_API +#endif + +namespace hku { + +using json = nlohmann::json; + +class HKU_UTILS_API HttpClient; + +class HKU_UTILS_API HttpResponse final { + friend class HKU_UTILS_API HttpClient; + +public: + HttpResponse(); + ~HttpResponse(); + + HttpResponse(const HttpResponse&) = delete; + HttpResponse& operator=(const HttpResponse&) = delete; + + HttpResponse(HttpResponse&& rhs); + HttpResponse& operator=(HttpResponse&& rhs); + + const std::string& body() const noexcept { + return m_body; + } + + hku::json json() const { + return json::parse(m_body); + } + + int status() const noexcept { + return nng_http_res_get_status(m_res); + } + + std::string reason() noexcept { + return nng_http_res_get_reason(m_res); + } + + std::string getHeader(const std::string& key) noexcept { + const char* hdr = nng_http_res_get_header(m_res, key.c_str()); + return hdr ? std::string(hdr) : std::string(); + } + + size_t getContentLength() noexcept { + std::string slen = getHeader("Content-Length"); + return slen.empty() ? 0 : std::stoi(slen); + } + +private: + void _resizeBody(size_t len) { + m_body.resize(len); + } + + nng_http_res* get() const noexcept { + return m_res; + } + + void reset(); + +private: + nng_http_res* m_res{nullptr}; + std::string m_body; +}; + +class HKU_UTILS_API HttpClient { +public: + HttpClient() = default; + explicit HttpClient(const std::string& url) : m_url(nng::url(url)) {}; + virtual ~HttpClient(); + + bool valid() const noexcept { + return m_url.valid(); + } + + const std::string& url() const noexcept { + return m_url.raw_url(); + } + + void setUrl(const std::string& url) noexcept { + m_url = std::move(nng::url(url)); + reset(); + } + + // #define NNG_DURATION_INFINITE (-1) + // #define NNG_DURATION_DEFAULT (-2) + // #define NNG_DURATION_ZERO (0) + void setTimeout(int32_t ms) { + if (m_timeout_ms != ms) { + m_timeout_ms = ms; + reset(); + } + } + + void setDefaultHeaders(const HttpHeaders& headers) { + m_default_headers = headers; + } + + void setDefaultHeaders(HttpHeaders&& headers) { + m_default_headers = std::move(headers); + } + + void setCaFile(const std::string& filename); + + void reset(); + + HttpResponse request(const std::string& method, const std::string& path, + const HttpParams& params, const HttpHeaders& headers, const char* body, + size_t body_len, const std::string& content_type); + + HttpResponse get(const std::string& path, const HttpHeaders& headers = HttpHeaders()) { + return request("GET", path, HttpParams(), headers, nullptr, 0, ""); + } + + HttpResponse get(const std::string& path, const HttpParams& params, + const HttpHeaders& headers) { + return request("GET", path, params, headers, nullptr, 0, ""); + } + + HttpResponse post(const std::string& path, const HttpParams& params, const HttpHeaders& headers, + const char* body, size_t len, const std::string& content_type) { + return request("POST", path, params, headers, body, len, content_type); + } + + HttpResponse post(const std::string& path, const HttpHeaders& headers, const char* body, + size_t len, const std::string& content_type) { + return request("POST", path, HttpParams(), headers, body, len, content_type); + } + + HttpResponse post(const std::string& path, const HttpParams& params, const HttpHeaders& headers, + const std::string& content, const std::string& content_type = "text/plaint") { + return post(path, params, headers, content.data(), content.size(), content_type); + } + + HttpResponse post(const std::string& path, const HttpHeaders& headers, + const std::string& content, const std::string& content_type = "text/plaint") { + return post(path, HttpParams(), headers, content, content_type); + } + + HttpResponse post(const std::string& path, const HttpParams& params, const HttpHeaders& headers, + const json& body) { + return post(path, params, headers, body.dump(), "application/json"); + } + + HttpResponse post(const std::string& path, const HttpHeaders& headers, const json& body) { + return post(path, HttpParams(), headers, body); + } + +private: + void _connect(); + HttpResponse _readResChunk(const std::string& method, const std::string& uri, + const HttpHeaders& headers, const char* body, size_t body_len, + const std::string& content_type); + +private: + HttpHeaders m_default_headers; + nng::url m_url; + nng::http_client m_client; + nng::aio m_aio; + nng::http_conn m_conn; +#if HKU_ENABLE_HTTP_CLIENT_SSL + nng::tls_config m_tls_cfg; + std::string m_ca_file; +#endif + + int32_t m_timeout_ms{NNG_DURATION_DEFAULT}; +}; + +} // namespace hku + +#endif \ No newline at end of file diff --git a/hikyuu_cpp/hikyuu/utilities/http_client/nng_wrap.h b/hikyuu_cpp/hikyuu/utilities/http_client/nng_wrap.h new file mode 100644 index 00000000..722e8d14 --- /dev/null +++ b/hikyuu_cpp/hikyuu/utilities/http_client/nng_wrap.h @@ -0,0 +1,478 @@ +/* + * Copyright (c) 2024 hikyuu.org + * + * Created on: 2024-07-26 + * Author: fasiondog + */ + +#pragma once +#ifndef HKU_UTILS_NNG_WRAP_H +#define HKU_UTILS_NNG_WRAP_H + +#include +#include +#include +#include "hikyuu/utilities/Log.h" + +#include "hikyuu/utilities/config.h" +#if HKU_ENABLE_HTTP_CLIENT_SSL +#include +#endif + +namespace hku { + +struct HttpTimeoutException : hku::exception { + HttpTimeoutException() : hku::exception("Http timeout!") {} + virtual ~HttpTimeoutException() noexcept = default; +}; + +using HttpHeaders = std::map; +using HttpParams = std::map; + +} // namespace hku + +namespace hku { +namespace nng { + +#ifndef NNG_CHECK +#define NNG_CHECK(rv) \ + { \ + if (rv != 0) { \ + HKU_THROW("[NNG_ERROR] {} ", nng_strerror(rv)); \ + } \ + } +#endif + +#ifndef NNG_CHECK_M +#define NNG_CHECK_M(rv, ...) \ + { \ + if (rv != 0) { \ + HKU_THROW("{} | [NNG_ERROR] {}", fmt::format(__VA_ARGS__), nng_strerror(rv)); \ + } \ + } +#endif + +class url final { +public: + url() = default; + explicit url(const std::string& url_) noexcept : m_rawurl(url_) { + nng_url_parse(&m_url, m_rawurl.c_str()); + } + + url(const url&) = delete; + url(url&& rhs) noexcept : m_rawurl(std::move(rhs.m_rawurl)), m_url(rhs.m_url) { + rhs.m_url = nullptr; + } + + url& operator=(const url&) = delete; + url& operator=(url&& rhs) noexcept { + if (this != &rhs) { + if (m_url != nullptr) { + nng_url_free(m_url); + } + m_url = rhs.m_url; + m_rawurl = std::move(rhs.m_rawurl); + rhs.m_url = nullptr; + } + return *this; + } + + ~url() { + if (m_url) { + nng_url_free(m_url); + } + } + + const std::string& raw_url() const noexcept { + return m_rawurl; + } + + nng_url* get() const noexcept { + return m_url; + } + + nng_url* operator->() const noexcept { + return m_url; + } + + bool valid() const noexcept { + return m_url != nullptr; + } + + bool is_https() const noexcept { + return m_url == nullptr ? false : strcmp("https", m_url->u_scheme) == 0; + } + +private: + std::string m_rawurl; + nng_url* m_url{nullptr}; +}; + +class aio final { +public: + aio() = default; + aio(const aio&) = delete; + ~aio() { + if (m_aio) { + nng_aio_free(m_aio); + } + } + + bool valid() const noexcept { + return m_aio != nullptr; + } + + nng_aio* get() const noexcept { + return m_aio; + } + + nng_aio* operator->() const noexcept { + return m_aio; + } + + void alloc(int32_t timeout_ms) { + if (m_aio == nullptr) { + NNG_CHECK(nng_aio_alloc(&m_aio, NULL, NULL)); + } + set_timeout(timeout_ms); + } + + void release() { + if (m_aio) { + nng_aio_free(m_aio); + m_aio = nullptr; + } + } + + aio& wait() { + nng_aio_wait(m_aio); + return *this; + } + + int result() { + // 直接返回结果,在调用处判断异常,才知道是在具体哪里 + return nng_aio_result(m_aio); + } + + void* get_output(unsigned index) { + return nng_aio_get_output(m_aio, index); + } + + /* + * 0 - 恢复默认值 + * <0 - 不限制 + */ + void set_timeout(int32_t ms) { + if (ms != m_timeout) { + m_timeout = ms; + nng_aio_set_timeout(m_aio, ms); + } + } + + int32_t get_timeout() const noexcept { + return m_timeout; + } + + void set_iov(unsigned n, const nng_iov* iov) { + NNG_CHECK(nng_aio_set_iov(m_aio, n, iov)); + } + +private: + nng_aio* m_aio{nullptr}; + int32_t m_timeout{NNG_DURATION_DEFAULT}; +}; + +#if HKU_ENABLE_HTTP_CLIENT_SSL +class tls_config final { +public: + tls_config() = default; + + tls_config(const tls_config& th) : m_cfg(th.m_cfg) { + if (m_cfg) { + nng_tls_config_hold(th.m_cfg); + } + } + + tls_config(tls_config&& rhs) : m_cfg(rhs.m_cfg) { + rhs.m_cfg = nullptr; + } + + ~tls_config() { + if (m_cfg) { + nng_tls_config_free(m_cfg); + } + } + + tls_config& operator=(const tls_config& th) { + if (this != &th) { + m_cfg = th.m_cfg; + if (m_cfg) { + nng_tls_config_hold(m_cfg); + } + } + return *this; + } + + tls_config& operator=(tls_config&& rhs) { + if (this != &rhs) { + m_cfg = rhs.m_cfg; + rhs.m_cfg = nullptr; + } + return *this; + } + + void release() { + if (m_cfg) { + nng_tls_config_free(m_cfg); + m_cfg = nullptr; + } + } + + nng_tls_config* get() const noexcept { + return m_cfg; + } + + tls_config& set_ca_file(const std::string& filename) { + NNG_CHECK(alloc()); + NNG_CHECK(nng_tls_config_ca_file(m_cfg, filename.c_str())); + return *this; + } + +private: + int alloc() { + return m_cfg ? 0 : nng_tls_config_alloc(&m_cfg, NNG_TLS_MODE_CLIENT); + } + +private: + nng_tls_config* m_cfg{nullptr}; +}; +#endif // #if HKU_ENABLE_HTTP_CLIENT_SSL + +class http_client final { +public: + http_client() = default; + ~http_client() { + if (m_client) { + nng_http_client_free(m_client); + } + } + + void set_url(const nng::url& url) { +#if !HKU_ENABLE_HTTP_CLIENT_SSL + if (url.is_https()) { + HKU_THROW("Not support https: ({})! Please compile with --http_client_ssl", + url.raw_url()); + } +#endif + if (!m_client) { + NNG_CHECK(nng_http_client_alloc(&m_client, url.get())); + m_tls_cfg = nullptr; + m_aio = nullptr; + } + } + + void connect(const aio& aio) { + if (m_aio != aio.get()) { + nng_http_client_connect(m_client, aio.get()); + m_aio = aio.get(); + } + } + + void set_tls_cfg(nng_tls_config* cfg) { + if (cfg != m_tls_cfg) { + NNG_CHECK(nng_http_client_set_tls(m_client, cfg)); + m_tls_cfg = cfg; + } + } + + nng_tls_config* get_tls_cfg() const noexcept { + return m_tls_cfg; + } + + nng_http_client* get() const noexcept { + return m_client; + } + + nng_http_client* operator->() const noexcept { + return m_client; + } + + explicit operator bool() const noexcept { + return m_client != nullptr; + } + + void release() { + if (m_client) { + nng_http_client_free(m_client); + m_client = nullptr; + m_aio = nullptr; + m_tls_cfg = nullptr; + } + } + +private: + nng_http_client* m_client{nullptr}; + nng_aio* m_aio{nullptr}; + nng_tls_config* m_tls_cfg{nullptr}; +}; + +class http_req final { +public: + http_req() = default; + explicit http_req(const url& url) { + NNG_CHECK(nng_http_req_alloc(&m_req, url.get())); + } + + http_req(const http_req&) = delete; + http_req(http_req&& rhs) : m_req(rhs.m_req) { + rhs.m_req = nullptr; + } + + ~http_req() { + if (m_req) { + nng_http_req_free(m_req); + } + } + + http_req& operator=(const http_req&) = delete; + http_req& operator=(http_req&& rhs) { + if (this != &rhs) { + if (m_req) { + nng_http_req_free(m_req); + } + m_req = rhs.m_req; + rhs.m_req = nullptr; + } + return *this; + } + + nng_http_req* get() const noexcept { + return m_req; + } + + http_req& set_method(const std::string& method) { + NNG_CHECK(nng_http_req_set_method(m_req, method.c_str())); + return *this; + } + + http_req& set_uri(const std::string& uri) { + NNG_CHECK(nng_http_req_set_uri(m_req, uri.c_str())); + return *this; + } + + http_req& add_header(const std::string& key, const std::string& val) { + NNG_CHECK_M(nng_http_req_add_header(m_req, key.c_str(), val.c_str()), + "Failed add head {}: {}", key, val); + return *this; + } + + http_req& add_headers(const HttpHeaders& headers) { + for (auto iter = headers.cbegin(); iter != headers.cend(); ++iter) { + NNG_CHECK_M(nng_http_req_add_header(m_req, iter->first.c_str(), iter->second.c_str()), + "Failed add header {}: {}", iter->first, iter->second); + } + return *this; + } + + std::string get_header(const std::string& key) { + const char* head = nng_http_req_get_header(m_req, key.c_str()); + return head ? std::string(head) : std::string(); + } + + http_req& del_header(const std::string& key) { + nng_http_req_del_header(m_req, key.c_str()); + return *this; + } + + /* 注: data 需要自行管理且在 req 释放之前应该一直存在,主要避免拷贝 */ + http_req& set_data(const char* data, size_t len) { + if (data != nullptr && len != 0) { + NNG_CHECK(nng_http_req_set_data(m_req, data, len)); + } + return *this; + } + + http_req& copy_data(const char* data, size_t len) { + if (data != nullptr && len != 0) { + NNG_CHECK(nng_http_req_copy_data(m_req, data, len)); + } + return *this; + } + +private: + nng_http_req* m_req{nullptr}; +}; + +class http_conn final { +public: + http_conn() = default; + explicit http_conn(nng_http_conn* conn_) noexcept : m_conn(conn_) {} + + http_conn(const http_conn&) = delete; + + http_conn(http_conn&& rhs) noexcept : m_conn(rhs.m_conn) { + rhs.m_conn = nullptr; + } + + http_conn& operator=(const http_conn& rhs) = delete; + + http_conn& operator=(http_conn&& rhs) noexcept { + if (this != &rhs) { + if (m_conn != nullptr) { + nng_http_conn_close(m_conn); + } + m_conn = rhs.m_conn; + rhs.m_conn = nullptr; + } + return *this; + } + + ~http_conn() { + if (m_conn) { + nng_http_conn_close(m_conn); + } + } + + void close() { + if (m_conn) { + nng_http_conn_close(m_conn); + m_conn = nullptr; + } + } + + nng_http_conn* get() const noexcept { + return m_conn; + } + + nng_http_conn* operator->() const noexcept { + return m_conn; + } + + bool valid() const noexcept { + return m_conn != nullptr; + } + + void write_req(const http_req& req, const aio& aio) { + nng_http_conn_write_req(m_conn, req.get(), aio.get()); + } + + void read_res(nng_http_res* res, const aio& aio) { + nng_http_conn_read_res(m_conn, res, aio.get()); + } + + void read_all(const aio& aio) { + nng_http_conn_read_all(m_conn, aio.get()); + } + + void transact(nng_http_req* req, nng_http_res* res, const aio& aio) { + nng_http_conn_transact(m_conn, req, res, aio.get()); + } + +private: + nng_http_conn* m_conn{nullptr}; +}; + +} // namespace nng +} // namespace hku + +#endif \ No newline at end of file diff --git a/hikyuu_cpp/hikyuu/utilities/http_client/url.cpp b/hikyuu_cpp/hikyuu/utilities/http_client/url.cpp new file mode 100644 index 00000000..4052ff9d --- /dev/null +++ b/hikyuu_cpp/hikyuu/utilities/http_client/url.cpp @@ -0,0 +1,56 @@ +/* + * Copyright(C) 2021 hikyuu.org + * + * Create on: 2021-03-07 + * Author: fasiondog + */ + +#include "url.h" + +namespace hku { + +#define IS_ALPHA(c) (((c) >= 'a' && (c) <= 'z') || ((c) >= 'A' && (c) <= 'Z')) +#define IS_NUM(c) ((c) >= '0' && (c) <= '9') +#define IS_ALPHANUM(c) (IS_ALPHA(c) || IS_NUM(c)) +#define IS_HEX(c) (IS_NUM(c) || ((c) >= 'a' && (c) <= 'f') || ((c) >= 'A' && (c) <= 'F')) + +static inline bool is_unambiguous(char c) { + return IS_ALPHANUM(c) || c == '-' || c == '_' || c == '.' || c == '~'; +} + +static inline unsigned char hex2i(char hex) { + return hex <= '9' ? hex - '0' : hex <= 'F' ? hex - 'A' + 10 : hex - 'a' + 10; +} + +std::string url_escape(const char* istr) { + std::string ostr; + const char* p = istr; + char szHex[4] = {0}; + while (*p != '\0') { + if (is_unambiguous(*p)) { + ostr += *p; + } else { + sprintf(szHex, "%%%02X", *p); + ostr += szHex; + } + ++p; + } + return ostr; +} + +std::string url_unescape(const char* istr) { + std::string ostr; + const char* p = istr; + while (*p != '\0') { + if (*p == '%' && IS_HEX(p[1]) && IS_HEX(p[2])) { + ostr += ((hex2i(p[1]) << 4) | hex2i(p[2])); + p += 3; + } else { + ostr += *p; + ++p; + } + } + return ostr; +} + +} // namespace hku \ No newline at end of file diff --git a/hikyuu_cpp/hikyuu/utilities/http_client/url.h b/hikyuu_cpp/hikyuu/utilities/http_client/url.h new file mode 100644 index 00000000..aa432066 --- /dev/null +++ b/hikyuu_cpp/hikyuu/utilities/http_client/url.h @@ -0,0 +1,25 @@ +/* + * Copyright(C) 2021 hikyuu.org + * + * Create on: 2021-03-07 + * Author: fasiondog + */ + +#pragma once +#ifndef HKU_UTILS_URL_H +#define HKU_UTILS_URL_H + +#include + +#ifndef HKU_UTILS_API +#define HKU_UTILS_API +#endif + +namespace hku { + +std::string HKU_UTILS_API url_escape(const char* istr); +std::string HKU_UTILS_API url_unescape(const char* istr); + +} // namespace hku + +#endif \ No newline at end of file diff --git a/hikyuu_cpp/hikyuu/utilities/mo/mo.cpp b/hikyuu_cpp/hikyuu/utilities/mo/mo.cpp new file mode 100644 index 00000000..4b314fb1 --- /dev/null +++ b/hikyuu_cpp/hikyuu/utilities/mo/mo.cpp @@ -0,0 +1,23 @@ +/* + * Copyright(C) 2021 hikyuu.org + * + * Create on: 2021-05-02 + * Author: fasiondog + */ + +#include "hikyuu/utilities/os.h" +#include "hikyuu/utilities/Log.h" +#include "mo.h" + +namespace hku { + +std::unordered_map MOHelper::ms_dict; + +void MOHelper::init() { + HKU_WARN_IF_RETURN(!existFile("i8n/zh_CN.mo"), void(), + "There is no internationalized language file: i8n/zh_CN.mo"); + ms_dict["zh_cn"] = moFileLib::moFileReader(); + ms_dict["zh_cn"].ReadFile("i8n/zh_CN.mo"); +} + +} // namespace hku \ No newline at end of file diff --git a/hikyuu_cpp/hikyuu/utilities/mo/mo.h b/hikyuu_cpp/hikyuu/utilities/mo/mo.h new file mode 100644 index 00000000..7fe168fc --- /dev/null +++ b/hikyuu_cpp/hikyuu/utilities/mo/mo.h @@ -0,0 +1,48 @@ +/* + * Copyright(C) 2021 hikyuu.org + * + * Create on: 2021-05-01 + * Author: fasiondog + */ + +#pragma once + +#include "hikyuu/utilities/config.h" +#if !HKU_ENABLE_MO +#error "Don't enable mo, please config with --mo=y" +#endif + +#include +#include "hikyuu/utilities/string_view.h" +#include "moFileReader.h" + +#if defined(_MSC_VER) +// moFileReader.hpp 最后打开了4251告警,这里关闭 +#pragma warning(disable : 4251) +#endif /* _MSC_VER */ + +#ifndef HKU_UTILS_API +#define HKU_UTILS_API +#endif + +namespace hku { + +class HKU_UTILS_API MOHelper { +public: + static void init(); + + static std::string translate(const std::string &lang, const char *id) { + auto iter = ms_dict.find(lang); + return iter != ms_dict.end() ? ms_dict[lang].Lookup(id) : std::string(id); + } + + static std::string translate(const std::string &lang, const char *ctx, const char *id) { + auto iter = ms_dict.find(lang); + return iter != ms_dict.end() ? ms_dict[lang].LookupWithContext(ctx, id) : std::string(id); + } + +private: + static std::unordered_map ms_dict; +}; + +} // namespace hku \ No newline at end of file diff --git a/hikyuu_cpp/hikyuu/utilities/mo/moFileReader.h b/hikyuu_cpp/hikyuu/utilities/mo/moFileReader.h new file mode 100644 index 00000000..862d8d4f --- /dev/null +++ b/hikyuu_cpp/hikyuu/utilities/mo/moFileReader.h @@ -0,0 +1,836 @@ +/* + * moFileReader - A simple .mo-File-Reader + * Copyright (C) 2009 Domenico Gentner (scorcher24@gmail.com) + * Copyright (C) 2018-2021 Edgar (Edgar@AnotherFoxGuy.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. The names of its contributors may not 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 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 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. + */ +#ifndef __MOFILEREADER_SINGLE_INCLUDE_H_INCLUDED__ +#define __MOFILEREADER_SINGLE_INCLUDE_H_INCLUDED__ + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wsign-compare" +#endif + +#if defined(_MSC_VER) +#pragma warning(disable : 4267) +#endif /* _MSC_VER */ + +#include // this is for memset when compiling with gcc. +#include +#include +#include +#include +#include + +//------------------------------------------------------------- +// Path-Seperators are different on other OS. +//------------------------------------------------------------- +#ifndef MO_PATHSEP +#ifdef WIN32 +#define MO_PATHSEP std::string("\\") +#else +#define MO_PATHSEP std::string("/") +#endif +#endif + +//------------------------------------------------------------- +// Defines the beginning of the namespace moFileLib. +//------------------------------------------------------------- +#ifndef MO_BEGIN_NAMESPACE +#define MO_BEGIN_NAMESPACE namespace moFileLib { +#endif +//------------------------------------------------------------- +// Ends the current namespace. +//------------------------------------------------------------- +#ifndef MO_END_NAMESPACE +#define MO_END_NAMESPACE } +#endif + +/** \mainpage moFileReaderSDK + * + * + *

Include in project

+ * + * Usage of this library is quite easy, simply add moFileReader.hpp to your project. Thats all you + * have to do. You can safely exclude mo.cpp, since this file keeps the entry-points of the .exe + * only. + * + *

Usage

+ * + * This is moFileReader, a simple gettext-replacement. The usage of this library is, hopefully, + * fairly simple: \code + * + * // Instanciate the class + * moFileLib::moFileReader reader; + * + * // Load a .mo-File. + * if ( reader.ReadFile("myTranslationFile.mo") != moFileLib::moFileReader::EC_SUCCESS ) + * { + * // Error Handling + * } + * + * // Now, you can lookup the strings you stored in the .mo-File: + * std::cout << reader.Lookup("MyTranslationString") << std::endl; + * + * \endcode + * Thats all! This small code has no dependencies, except the C/C++-runtime of your compiler, + * so it should work on all machines where a C++-runtime is provided. + * + * \note We do not yet support .mo-Files with reversed magic-numbers, since I don't have + * a file to test it and I hate to release stuff I wasn't able to test. + * + *

Changelog

+ * + * - Version 1.2.0 + * - Proper implementation of contexts strings + * Now it uses a separate 2D map for storing strings with context + * - Fixed MagicNumber check not working on Linux + * - Removed duplicate code + * - Added option to disable convenience Class + * + * - Version 1.1.0 + * - Converted library to a header-only library + * + * - Version 1.0.0 + * - Added new function: LookupWithContext + * - Added unit-tests + * - Added support for packaging with Conan + * - Moved project to https://github.com/AnotherFoxGuy/MofileReader + * + * - Version 0.1.2 + * - Generic improvements to the documentation. + * - Generic improvements to the code + * - Fixed a bug in mo.cpp which caused the application not to print the help + * message if only --export or --lookup where missing. + * - Added -h, --help and -? to moReader[.exe]. It will print the help-screen. + * - Added --version and -v to moReader[.exe]. It will print some informations about the + * program. + * - Added --license to moReader[.exe]. This will print its license. + * - --export gives now a feedback about success or failure. + * - The HTML-Dump-Method outputs now the whole table from the empty msgid in a nice html-table, + * not only a few hardcoded. + * - I had an issue-report that the Error-Constants can collide with foreign code under certain + * conditions, so I added a patch which renamed the error-constants to more compatible names. + * + * - Version 0.1.1 + * - Added the ability to export mo's as HTML. + * - Fixed a bug causing a crash when passing an invalid value to moFileReader::Lookup(). + * - Added a new file, moFileConfig.h, holding the macros for the project. + * - Added the ability to be configured by cmake. + * - Added some more inline-functions, which really enhance the singleton. + * + * - Version 0.1.0 + * - Initial Version and release to http://googlecode.com + * + * + *

Credits

+ * + * Gettext is part of the GNU-Tools and (C) by the Free Software + * Foundation.\n Visual C++ Express is a registered Trademark of Microsoft, One Microsoft Way, + * Redmond, USA.\n All other Trademarks are property of their respective owners. \n \n Thanks for + * using this piece of OpenSource-Software.\n Submit patches and/or bugs on + * https://github.com/AnotherFoxGuy/MofileReader. Send your flames, dumb comments etc to /dev/null, + * thank you. + */ + +/** \namespace moFileLib + * \brief This is the only namespace of this small sourcecode. + */ +MO_BEGIN_NAMESPACE + +const std::string g_css = R"( +body { + background-color: black; + color: silver; +} +table { + width: 80%; +} +th { + background-color: orange; + color: black; +} +hr { + color: red; + width: 80%; + size: 5px; +} +a:link{ + color: gold; +} +a:visited{ + color: grey; +} +a:hover{ + color:blue; +} +.copyleft{ + font-size: 12px; + text-align: center; +})"; + +/** + * \brief Keeps the Description of translated and original strings. + * + * + * To load a String from the file, we need its offset and its length. + * This struct helps us grouping this information. + */ +struct moTranslationPairInformation { + /// \brief Constructor + moTranslationPairInformation() : m_orLength(0), m_orOffset(0), m_trLength(0), m_trOffset(0) {} + + /// \brief Length of the Original String + int m_orLength; + + /// \brief Offset of the Original String (absolute) + int m_orOffset; + + /// \brief Length of the Translated String + int m_trLength; + + /// \brief Offset of the Translated String (absolute) + int m_trOffset; +}; + +/** + * \brief Describes the "Header" of a .mo-File. + * + * + * The File info keeps the header of a .mo-file and + * a list of the string-descriptions. + * The typedef is for the type of the string-list. + * The constructor ensures, that all members get a nice + * initial value. + */ +struct moFileInfo { + /// \brief Type for the list of all Translation-Pair-Descriptions. + typedef std::deque moTranslationPairList; + + /// \brief Constructor + moFileInfo() + : m_magicNumber(0), + m_fileVersion(0), + m_numStrings(0), + m_offsetOriginal(0), + m_offsetTranslation(0), + m_sizeHashtable(0), + m_offsetHashtable(0), + m_reversed(false) {} + + /// \brief The Magic Number, compare it to g_MagicNumber. + int m_magicNumber; + + /// \brief The File Version, 0 atm according to the manpage. + int m_fileVersion; + + /// \brief Number of Strings in the .mo-file. + int m_numStrings; + + /// \brief Offset of the Table of the Original Strings + int m_offsetOriginal; + + /// \brief Offset of the Table of the Translated Strings + int m_offsetTranslation; + + /// \brief Size of 1 Entry in the Hashtable. + int m_sizeHashtable; + + /// \brief The Offset of the Hashtable. + int m_offsetHashtable; + + /** \brief Tells you if the bytes are reversed + * \note When this is true, the bytes are reversed and the Magic number is like g_MagicReversed + */ + bool m_reversed; + + /// \brief A list containing offset and length of the strings in the file. + moTranslationPairList m_translationPairInformation; +}; + +/** + * \brief This class is a gettext-replacement. + * + * + * The usage is quite simple:\n + * Tell the class which .mo-file it shall load via + * moFileReader::ReadFile(). The method will attempt to load + * the file, all translations will be stored in memory. + * Afterwards you can lookup the strings with moFileReader::Lookup() just + * like you would do with gettext. + * Additionally, you can call moFileReader::ReadFile() for as much files as you + * like. But please be aware, that if there are duplicated keys (original strings), + * that they will replace each other in the lookup-table. There is no check done, if a + * key already exists. + * + * \note If you add "Lookup" to the keywords of the gettext-parser (like poEdit), + * it will recognize the Strings loaded with an instance of this class. + * \note I strongly recommend poEdit from Vaclav Slavik for editing .po-Files, + * get it at http://poedit.net for various systems :). + */ +class moFileReader { +protected: + /// \brief Type for the map which holds the translation-pairs later. + typedef std::map moLookupList; + + /// \brief Type for the 2D map which holds the translation-pairs later. + typedef std::map moContextLookupList; + +public: + /// \brief The Magic Number describes the endianess of bytes on the system. + static const unsigned int MagicNumber = 0x950412DE; + + /// \brief If the Magic Number is Reversed, we need to swap the bytes. + static const unsigned int MagicReversed = 0xDE120495; + + /// \brief The character that is used to separate context strings + static const char ContextSeparator = '\x04'; + + /// \brief The possible errorcodes for methods of this class + enum eErrorCode { + /// \brief Indicated success + EC_SUCCESS = 0, + + /// \brief Indicates an error + EC_ERROR, + + /// \brief The given File was not found. + EC_FILENOTFOUND, + + /// \brief The file is invalid. + EC_FILEINVALID, + + /// \brief Empty Lookup-Table (returned by ExportAsHTML()) + EC_TABLEEMPTY, + + /// \brief The magic number did not match + EC_MAGICNUMBER_NOMATCH, + + /** + * \brief The magic number is reversed. + * \note This is an error until the class supports it. + */ + EC_MAGICNUMBER_REVERSED, + }; + + /** \brief Reads a .mo-file + * \param[in] _filename The path to the file to load. + * \return SUCCESS on success or one of the other error-codes in eErrorCode on error. + * + * This is the core-feature. This method loads the .mo-file and stores + * all translation-pairs in a map. You can access this map via the method + * moFileReader::Lookup(). + */ + moFileReader::eErrorCode ParseData(const std::string &data) { + // Opening the file. + std::stringstream stream(data); + + return ReadStream(stream); + } + + /** \brief Reads a .mo-file + * \param[in] _filename The path to the file to load. + * \return SUCCESS on success or one of the other error-codes in eErrorCode on error. + * + * This is the core-feature. This method loads the .mo-file and stores + * all translation-pairs in a map. You can access this map via the method + * moFileReader::Lookup(). + */ + eErrorCode ReadFile(const char *filename) { + // Opening the file. + std::ifstream stream(filename, std::ios_base::binary | std::ios_base::in); + if (!stream.is_open()) { + m_error = std::string("Cannot open File ") + std::string(filename); + return moFileReader::EC_FILENOTFOUND; + } + + eErrorCode res = ReadStream(stream); + stream.close(); + + return res; + } + + /** \brief Reads data from a stream + * \param[in] stream + * \return SUCCESS on success or one of the other error-codes in eErrorCode on error. + * + */ + template + eErrorCode ReadStream(T &stream) { + // Creating a file-description. + moFileInfo moInfo; + + // Reference to the List inside moInfo. + moFileInfo::moTranslationPairList &TransPairInfo = moInfo.m_translationPairInformation; + + // Read in all the 4 bytes of fire-magic, offsets and stuff... + stream.read((char *)&moInfo.m_magicNumber, 4); + stream.read((char *)&moInfo.m_fileVersion, 4); + stream.read((char *)&moInfo.m_numStrings, 4); + stream.read((char *)&moInfo.m_offsetOriginal, 4); + stream.read((char *)&moInfo.m_offsetTranslation, 4); + stream.read((char *)&moInfo.m_sizeHashtable, 4); + stream.read((char *)&moInfo.m_offsetHashtable, 4); + + if (stream.bad()) { + m_error = + "Stream bad during reading. The .mo-file seems to be invalid or has bad " + "descriptions!"; + return moFileReader::EC_FILEINVALID; + } + + // Checking the Magic Number + if (MagicNumber != moInfo.m_magicNumber) { + if (MagicReversed != moInfo.m_magicNumber) { + m_error = "The Magic Number does not match in all cases!"; + return moFileReader::EC_MAGICNUMBER_NOMATCH; + } else { + moInfo.m_reversed = true; + m_error = "Magic Number is reversed. We do not support this yet!"; + return moFileReader::EC_MAGICNUMBER_REVERSED; + } + } + + // Now we search all Length & Offsets of the original strings + for (int i = 0; i < moInfo.m_numStrings; i++) { + moTranslationPairInformation _str; + stream.read((char *)&_str.m_orLength, 4); + stream.read((char *)&_str.m_orOffset, 4); + if (stream.bad()) { + m_error = + "Stream bad during reading. The .mo-file seems to be invalid or has bad " + "descriptions!"; + return moFileReader::EC_FILEINVALID; + } + + TransPairInfo.push_back(_str); + } + + // Get all Lengths & Offsets of the translated strings + // Be aware: The Descriptors already exist in our list, so we just mod. refs from the deque. + for (int i = 0; i < moInfo.m_numStrings; i++) { + moTranslationPairInformation &_str = TransPairInfo[i]; + stream.read((char *)&_str.m_trLength, 4); + stream.read((char *)&_str.m_trOffset, 4); + if (stream.bad()) { + m_error = + "Stream bad during reading. The .mo-file seems to be invalid or has bad " + "descriptions!"; + return moFileReader::EC_FILEINVALID; + } + } + + // Normally you would read the hash-table here, but we don't use it. :) + + // Now to the interesting part, we read the strings-pairs now + for (int i = 0; i < moInfo.m_numStrings; i++) { + // We need a length of +1 to catch the trailing \0. + int orLength = TransPairInfo[i].m_orLength + 1; + int trLength = TransPairInfo[i].m_trLength + 1; + + int orOffset = TransPairInfo[i].m_orOffset; + int trOffset = TransPairInfo[i].m_trOffset; + + // Original + char *original = new char[orLength]; + memset(original, 0, sizeof(char) * orLength); + + stream.seekg(orOffset); + stream.read(original, orLength); + + if (stream.bad()) { + m_error = + "Stream bad during reading. The .mo-file seems to be invalid or has bad " + "descriptions!"; + return moFileReader::EC_FILEINVALID; + } + + // Translation + char *translation = new char[trLength]; + memset(translation, 0, sizeof(char) * trLength); + + stream.seekg(trOffset); + stream.read(translation, trLength); + + if (stream.bad()) { + m_error = + "Stream bad during reading. The .mo-file seems to be invalid or has bad " + "descriptions!"; + return moFileReader::EC_FILEINVALID; + } + + std::string original_str = original; + std::string translation_str = translation; + auto ctxSeparator = original_str.find(ContextSeparator); + + // Store it in the map. + if (ctxSeparator == std::string::npos) { + m_lookup[original_str] = translation_str; + numStrings++; + } else { + // try-catch for handling out_of_range exceptions + try { + m_lookup_context[original_str.substr(0, ctxSeparator)][original_str.substr( + ctxSeparator + 1, original_str.length())] = translation_str; + numStrings++; + } catch (...) { + m_error = + "Stream bad during reading. The .mo-file seems to be invalid or has bad " + "descriptions!"; + return moFileReader::EC_ERROR; + } + } + + // Cleanup... + delete[] original; + delete[] translation; + } + + // Done :) + return moFileReader::EC_SUCCESS; + } + + /** \brief Returns the searched translation or returns the input. + * \param[in] id The id of the translation to search for. + * \return The value you passed in via _id or the translated string. + */ + std::string Lookup(const char *id) const { + if (m_lookup.empty()) + return id; + auto iterator = m_lookup.find(id); + + return iterator == m_lookup.end() ? id : iterator->second; + } + + /** \brief Returns the searched translation or returns the input, restricted to the context + * given by context. See https://www.gnu.org/software/gettext/manual/html_node/Contexts.html for + * more info. \param[in] context Restrict to the context given. \param[in] id The id of the + * translation to search for. \return The value you passed in via _id or the translated string. + */ + std::string LookupWithContext(const char *context, const char *id) const { + if (m_lookup_context.empty()) + return id; + auto iterator = m_lookup_context.find(context); + + if (iterator == m_lookup_context.end()) + return id; + auto iterator2 = iterator->second.find(id); + + return iterator2 == iterator->second.end() ? id : iterator2->second; + } + + /// \brief Returns the Error Description. + const std::string &GetErrorDescription() const { + return m_error; + } + + /// \brief Empties the Lookup-Table. + void ClearTable() { + m_lookup.clear(); + m_lookup_context.clear(); + numStrings = 0; + } + + /** \brief Returns the Number of Entries in our Lookup-Table. + * \note The mo-File-table always contains an empty msgid, which contains informations + * about the tranlsation-project. So the real number of strings is always minus 1. + */ + unsigned int GetNumStrings() const { + return numStrings; + } + + /** \brief Exports the whole content of the .mo-File as .html + * \param[in] infile The .mo-File to export. + * \param[in] filename Where to store the .html-file. If empty, the path and filename of the + * _infile with .html appended. \param[in,out] css The css-script for the visual style of the + * file, in case you don't like mine ;). + * \see g_css for the possible and used css-values. + */ + static eErrorCode ExportAsHTML(const std::string &infile, const std::string &filename = "", + const std::string &css = g_css) { + // Read the file + moFileReader reader; + moFileReader::eErrorCode r = reader.ReadFile(infile.c_str()); + if (r != moFileReader::EC_SUCCESS) { + return r; + } + if (reader.m_lookup.empty()) { + return moFileReader::EC_TABLEEMPTY; + } + + // Beautify Output + std::string fname; + unsigned int pos = infile.find_last_of(MO_PATHSEP); + if (pos != std::string::npos) { + fname = infile.substr(pos + 1, infile.length()); + } else { + fname = infile; + } + + // if there is no filename given, we set it to the .mo + html, e.g. test.mo.html + std::string htmlfile(filename); + if (htmlfile.empty()) { + htmlfile = infile + std::string(".html"); + } + + // Ok, now prepare output. + std::ofstream stream(htmlfile.c_str()); + if (stream.is_open()) { + stream + << R"()" + << std::endl; + stream << "" << std::endl; + stream << R"()" + << std::endl; + stream << "Dump of " << fname << "" << std::endl; + stream << "" << std::endl; + stream << "
" << std::endl; + stream << "

" << fname << "

" << std::endl; + stream << R"()" << std::endl; + + std::stringstream parsee; + parsee << reader.Lookup(""); + + while (!parsee.eof()) { + char buffer[1024]; + parsee.getline(buffer, 1024); + std::string name; + std::string value; + + reader.GetPoEditorString(buffer, name, value); + if (!(name.empty() || value.empty())) { + stream << "" + << std::endl; + } + } + stream << "
Project Info
" << name << "" << value << "
" << std::endl; + stream << "
" << std::endl; + + // Now output the content + stream << R"()" << std::endl; + for (const auto &it : reader.m_lookup) { + if (!it.first.empty()) // Skip the empty msgid, its the table we handled above. + { + stream << "" + << std::endl; + } + } + stream << "
Content
" << it.first << "" << it.second << "

" << std::endl; + + // Separate tables for each context + for (const auto &it : reader.m_lookup_context) { + stream << R"(" + << std::endl; + for (const auto &its : it.second) { + stream << "" + << std::endl; + } + stream << "
)" << it.first << "
" << its.first << "" << its.second << "

" << std::endl; + } + + stream << "
" << std::endl; + stream << "
File generated by moFileReaderSDK
" + << std::endl; + stream << "" << std::endl; + stream.close(); + } else { + return moFileReader::EC_FILENOTFOUND; + } + + return moFileReader::EC_SUCCESS; + } + +protected: + /// \brief Keeps the last error as String. + std::string m_error; + + /** \brief Swap the endianness of a 4 byte WORD. + * \param[in] in The value to swap. + * \return The swapped value. + */ + unsigned long SwapBytes(unsigned long in) { + unsigned long b0 = (in >> 0) & 0xff; + unsigned long b1 = (in >> 8) & 0xff; + unsigned long b2 = (in >> 16) & 0xff; + unsigned long b3 = (in >> 24) & 0xff; + + return (b0 << 24) | (b1 << 16) | (b2 << 8) | b3; + } + +private: + // Holds the lookup-table + moLookupList m_lookup; + moContextLookupList m_lookup_context; + + int numStrings = 0; + + // Replaces < with ( to satisfy html-rules. + static void MakeHtmlConform(std::string &_inout) { + std::string temp = _inout; + for (unsigned int i = 0; i < temp.length(); i++) { + if (temp[i] == '>') { + _inout.replace(i, 1, ")"); + } + if (temp[i] == '<') { + _inout.replace(i, 1, "("); + } + } + } + + // Extracts a value-pair from the po-edit-information + bool GetPoEditorString(const char *_buffer, std::string &_name, std::string &_value) { + std::string line(_buffer); + size_t first = line.find_first_of(':'); + + if (first != std::string::npos) { + _name = line.substr(0, first); + _value = line.substr(first + 1, line.length()); + + // Replace <> with () for Html-Conformity. + MakeHtmlConform(_value); + MakeHtmlConform(_name); + + // Remove spaces from front and end. + Trim(_value); + Trim(_name); + + return true; + } + return false; + } + + // Removes spaces from front and end. + static void Trim(std::string &_in) { + if (_in.empty()) { + return; + } + + _in.erase(0, _in.find_first_not_of(" ")); + _in.erase(_in.find_last_not_of(" ") + 1); + + /*while (_in[0] == ' ') + { + _in = _in.substr(1, _in.length()); + } + while (_in[_in.length()] == ' ') + { + _in = _in.substr(0, _in.length() - 1); + }*/ + } +}; + +#ifndef MO_NO_CONVENIENCE_CLASS +/** \brief Convenience Class + * + * + * This class derives from moFileReader and builds a singleton to access its methods + * in a global manner. + * \note This class is a Singleton. Please access it via moFileReaderSingleton::GetInstance() + * or use the provided wrappers:\n + * - moReadMoFile() + * - _() + * - moFileClearTable() + * - moFileGetErrorDescription() + * - moFileGetNumStrings(); + */ +class moFileReaderSingleton : public moFileReader { +private: + // Private Contructor and Copy-Constructor to avoid + // that this class is instanced. + moFileReaderSingleton() {} + + moFileReaderSingleton(const moFileReaderSingleton &); + + moFileReaderSingleton &operator=(const moFileReaderSingleton &) { + return *this; + } + +public: + /** \brief Singleton-Accessor. + * \return A static instance of moFileReaderSingleton. + */ + static moFileReaderSingleton &GetInstance() { + static moFileReaderSingleton theoneandonly; + return theoneandonly; + } +}; + +/** \brief Reads the .mo-File. + * \param[in] _filename The path to the file to use. + * \see moFileReader::ReadFile() for details. + */ +inline moFileReader::eErrorCode moReadMoFile(const char *_filename) { + moFileReader::eErrorCode r = moFileReaderSingleton::GetInstance().ReadFile(_filename); + return r; +} + +/** \brief Looks for the spec. string to translate. + * \param[in] id The string-id to search. + * \return The translation if found, otherwise it returns id. + */ +inline std::string _(const char *id) { + std::string r = moFileReaderSingleton::GetInstance().Lookup(id); + return r; +} + +/// \brief Resets the Lookup-Table. +inline void moFileClearTable() { + moFileReaderSingleton::GetInstance().ClearTable(); +} + +/// \brief Returns the last known error as string or an empty class. +inline std::string moFileGetErrorDescription() { + std::string r = moFileReaderSingleton::GetInstance().GetErrorDescription(); + return r; +} + +/// \brief Returns the number of entries loaded from the .mo-File. +inline int moFileGetNumStrings() { + int r = moFileReaderSingleton::GetInstance().GetNumStrings(); + return r; +} +#endif + +MO_END_NAMESPACE + +#if defined(_MSC_VER) +#pragma warning(default : 4251) +#endif /* _MSC_VER */ + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + +#endif /* __MOFILEREADER_SINGLE_INCLUDE_H_INCLUDED__ */ \ No newline at end of file diff --git a/hikyuu_cpp/hikyuu/utilities/snowflake.h b/hikyuu_cpp/hikyuu/utilities/snowflake.h new file mode 100644 index 00000000..577dbae4 --- /dev/null +++ b/hikyuu_cpp/hikyuu/utilities/snowflake.h @@ -0,0 +1,110 @@ +/* + * Copyright(C) 2021 hikyuu.org + * + * The code comes from: https://github.com/sniper00/snowflake-cpp + * Thanks sniper00 + * + * Create on: 2021-04-13 + * Author: fasiondog + */ + +#pragma once + +#include +#include +#include +#include + +namespace hku { + +class snowflake_nonlock { +public: + void lock() {} + void unlock() {} +}; + +template +class snowflake { + using lock_type = Lock; + static constexpr int64_t TWEPOCH = Twepoch; + static constexpr int64_t WORKER_ID_BITS = 5L; + static constexpr int64_t DATACENTER_ID_BITS = 5L; + static constexpr int64_t MAX_WORKER_ID = (1 << WORKER_ID_BITS) - 1; + static constexpr int64_t MAX_DATACENTER_ID = (1 << DATACENTER_ID_BITS) - 1; + static constexpr int64_t SEQUENCE_BITS = 12L; + static constexpr int64_t WORKER_ID_SHIFT = SEQUENCE_BITS; + static constexpr int64_t DATACENTER_ID_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS; + static constexpr int64_t TIMESTAMP_LEFT_SHIFT = + SEQUENCE_BITS + WORKER_ID_BITS + DATACENTER_ID_BITS; + static constexpr int64_t SEQUENCE_MASK = (1 << SEQUENCE_BITS) - 1; + + using time_point = std::chrono::time_point; + + time_point start_time_point_ = std::chrono::steady_clock::now(); + int64_t start_millsecond_ = std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()) + .count(); + + int64_t last_timestamp_ = -1; + int64_t workerid_ = 0; + int64_t datacenterid_ = 0; + int64_t sequence_ = 0; + lock_type lock_; + +public: + snowflake() = default; + + snowflake(const snowflake&) = delete; + + snowflake& operator=(const snowflake&) = delete; + + void init(int64_t workerid, int64_t datacenterid) { + std::lock_guard lock(lock_); + if (workerid > MAX_WORKER_ID || workerid < 0) { + throw std::runtime_error("worker Id can't be greater than 31 or less than 0"); + } + + if (datacenterid > MAX_DATACENTER_ID || datacenterid < 0) { + throw std::runtime_error("datacenter Id can't be greater than 31 or less than 0"); + } + + workerid_ = workerid; + datacenterid_ = datacenterid; + } + + int64_t nextid() { + std::lock_guard lock(lock_); + // std::chrono::steady_clock cannot decrease as physical time moves forward + auto timestamp = millsecond(); + if (last_timestamp_ == timestamp) { + sequence_ = (sequence_ + 1) & SEQUENCE_MASK; + if (sequence_ == 0) { + timestamp = wait_next_millis(last_timestamp_); + } + } else { + sequence_ = 0; + } + + last_timestamp_ = timestamp; + + return ((timestamp - TWEPOCH) << TIMESTAMP_LEFT_SHIFT) | + (datacenterid_ << DATACENTER_ID_SHIFT) | (workerid_ << WORKER_ID_SHIFT) | sequence_; + } + +private: + int64_t millsecond() const { + auto diff = std::chrono::duration_cast( + std::chrono::steady_clock::now() - start_time_point_); + return start_millsecond_ + diff.count(); + } + + int64_t wait_next_millis(int64_t last) const { + auto timestamp = millsecond(); + while (timestamp <= last) { + timestamp = millsecond(); + } + return timestamp; + } +}; + +} // namespace hku \ No newline at end of file diff --git a/hikyuu_cpp/hikyuu/xmake.lua b/hikyuu_cpp/hikyuu/xmake.lua index 28744057..89aabdc8 100644 --- a/hikyuu_cpp/hikyuu/xmake.lua +++ b/hikyuu_cpp/hikyuu/xmake.lua @@ -77,7 +77,7 @@ target("hikyuu") -- add files -- add_files("./**.cpp|data_driver/**.cpp|utilities/db_connect/mysql/*.cpp") - add_files("./**.cpp|data_driver/**.cpp|utilities/db_connect/mysql/*.cpp") + add_files("./**.cpp|data_driver/**.cpp|utilities/db_connect/mysql/*.cpp|utilities/mo/*.cpp") add_files("./data_driver/*.cpp") if get_config("hdf5") or get_config("sqlite") then add_files("./data_driver/base_info/sqlite/**.cpp") diff --git a/setup.py b/setup.py index a0ace2fe..52b2ef12 100644 --- a/setup.py +++ b/setup.py @@ -123,8 +123,9 @@ def start_build(verbose=False, mode='release', feedback=True, worker_num=2, low_ if py_version != history_compile_info[ 'py_version'] or history_compile_info['mode'] != mode: clear_with_python_changed(mode) - cmd = "xmake f {} -c -y -m {} --feedback={} -k {} --low_precision={}".format( - "-v -D" if verbose else "", mode, feedback, "shared" if mode == 'release' else "static", low_precision) + cmd = "xmake f {} -c -y -m {} --feedback={} -k {} --low_precision={} --log_level={}".format( + "-v -D" if verbose else "", mode, feedback, "shared" if mode == 'release' else "static", low_precision, + 2 if mode == 'release' else 0) print(cmd) os.system(cmd) diff --git a/xmake.lua b/xmake.lua index 6484a30e..34521c6d 100644 --- a/xmake.lua +++ b/xmake.lua @@ -1,11 +1,18 @@ set_xmakever("2.8.2") -option("hdf5") - set_default(true) - set_showmenu(true) - set_category("hikyuu") - set_description("Enable hdf5 kdata engine.") -option_end() +-- project +set_project("hikyuu") + +add_rules("mode.debug", "mode.release") + +-- version +set_version("2.1.0", {build = "%Y%m%d%H%M"}) + +set_warnings("all") + +-- set language: C99, c++ standard +set_languages("cxx17", "c99") + option("mysql") set_default(true) @@ -36,85 +43,19 @@ option("mysql") end option_end() -option("sqlite") - set_default(true) - set_showmenu(true) - set_category("hikyuu") - set_description("Enable sqlite kdata engine.") -option_end() - -option("tdx") - set_default(true) - set_showmenu(true) - set_category("hikyuu") - set_description("Enable tdx kdata engine.") -option_end() - -option("sql_trace") - set_default(false) - set_showmenu(true) - set_category("hikyuu") - set_description("打印执行的 SQL 语句") -option_end() +option("hdf5", {description = "Enable hdf5 kdata engine.", default = true}) +option("sqlite", {description = "Enable sqlite kdata engine.", default = true}) +option("tdx", {description = "Enable tdx kdata engine.", default = true}) +option("sql_trace", {description = "trace print sql", default = false}) -- 注意:stacktrace 在 windows 下会严重影响性能 -option("stacktrace") - set_default(false) - set_showmenu(true) - set_category("hikyuu") - set_description("Enable check/assert with stack trace info.") -option_end() - -option("spend_time") - set_default(true) - set_showmenu(true) - set_category("hikyuu") - set_description("Enable spend time.") -option_end() - -option("feedback") - set_default(true) - set_showmenu(true) - set_category("hikyuu") - set_description("Enable send feedback.") -option_end() - -option("low_precision") - set_default(false) - set_showmenu(true) - set_category("hikyuu") - set_description("Enable low precision.") -option_end() - -option("log_level") - set_default("info") - set_values("trace", "debug", "info", "warn", "error", "fatal", "off") - set_showmenu(true) - set_category("hikyuu") - set_description("set log level") -option_end() - -option("async_log") - set_default(false) - set_showmenu(true) - set_category("hikyuu") - set_description("Use async log.") -option_end() - -option("leak_check") - set_default(false) - set_showmenu(true) - set_category("hikyuu") - set_description("Enable leak check for test") -option_end() - --- project -set_project("hikyuu") - -add_rules("mode.debug", "mode.release") - --- version -set_version("2.1.0", {build = "%Y%m%d%H%M"}) +option("stacktrace", {description = "Enable check/assert with stack trace info.", default = false}) +option("spend_time", {description = "Enable spend time.", default = true}) +option("feedback", {description = "Enable send feedback.", default = true}) +option("low_precision", {description = "Enable low precision.", default = false}) +option("log_level", {description = "set log level.", default = 2, values = {1, 2, 3, 4, 5, 6}}) +option("async_log", {description = "Use async log.", default = false}) +option("leak_check", {description = "Enable leak check for test", default = false}) if get_config("leak_check") then -- 需要 export LD_PRELOAD=libasan.so @@ -124,32 +65,18 @@ if get_config("leak_check") then -- set_policy("build.sanitizer.thread", true) end -local level = get_config("log_level") -if is_mode("debug") then - level = "trace" -end -if level == "trace" then - set_configvar("HKU_LOG_ACTIVE_LEVEL", 0) -elseif level == "debug" then - set_configvar("HKU_LOG_ACTIVE_LEVEL", 1) -elseif level == "info" then - set_configvar("HKU_LOG_ACTIVE_LEVEL", 2) -elseif level == "warn" then - set_configvar("HKU_LOG_ACTIVE_LEVEL", 3) -elseif level == "error" then - set_configvar("HKU_LOG_ACTIVE_LEVEL", 4) -elseif level == "fatal" then - set_configvar("HKU_LOG_ACTIVE_LEVEL", 5) -else - set_configvar("HKU_LOG_ACTIVE_LEVEL", 6) +-- SPDLOG_ACTIVE_LEVEL 需要单独加 +local log_level = get_config("log_level") +if log_level == nil then + log_level = 2 end +add_defines("SPDLOG_ACTIVE_LEVEL=" .. log_level) if is_mode("debug") then set_configvar("HKU_DEBUG_MODE", 1) else set_configvar("HKU_DEBUG_MODE", 0) end -set_configvar("USE_SPDLOG_ASYNC_LOGGER", 0) -- 使用异步的spdlog set_configvar("CHECK_ACCESS_BOUND", 1) if is_plat("macosx") or get_config("leak_check") then set_configvar("SUPPORT_SERIALIZATION", 0) @@ -172,19 +99,18 @@ set_configvar("HKU_ENABLE_TDX_KDATA", get_config("tdx") and 1 or 0) set_configvar("HKU_USE_LOW_PRECISION", get_config("low_precision") and 1 or 0) -set_configvar("HKU_DEFAULT_LOG_NAME", "hikyuu") set_configvar("HKU_SUPPORT_DATETIME", 1) set_configvar("HKU_ENABLE_SQLCIPHER", 0) set_configvar("HKU_SQL_TRACE", get_config("sql_trace")) set_configvar("HKU_ENABLE_INI_PARSER", 1) -set_configvar("HKU_USE_SPDLOG_ASYNC_LOGGER", get_config("async_log") and 1 or 0) set_configvar("HKU_ENABLE_STACK_TRACE", get_config("stacktrace") and 1 or 0) set_configvar("HKU_CLOSE_SPEND_TIME", get_config("spend_time") and 0 or 1) - -set_warnings("all") - --- set language: C99, c++ standard -set_languages("cxx17", "c99") +set_configvar("HKU_USE_SPDLOG_ASYNC_LOGGER", get_config("async_log") and 1 or 0) +set_configvar("HKU_LOG_ACTIVE_LEVEL", get_config("log_level")) +set_configvar("HKU_ENABLE_MO", 0) +set_configvar("HKU_ENABLE_HTTP_CLIENT", 1) +set_configvar("HKU_ENABLE_HTTP_CLIENT_SSL", 0) +set_configvar("HKU_ENABLE_HTTP_CLIENT_ZIP", 0) if is_plat("windows") then if is_mode("release") then @@ -229,10 +155,10 @@ elseif is_plat("linux", "cross") then elseif is_plat("macosx") then if get_config("hdf5") then - add_requires("brew::hdf5") + add_requires("brew::hdf5", {alias = "hdf5"}) end if get_config("mysql") then - add_requires("brew::mysql-client") + add_requires("brew::mysql-client", {alias = "mysql"}) end end