mirror of
https://gitee.com/fasiondog/hikyuu.git
synced 2024-11-29 18:39:10 +08:00
同步 hku_utils
This commit is contained in:
parent
6cfb99249b
commit
bf55a8e151
@ -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_ */
|
@ -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
|
||||
|
@ -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 的输出窗口
|
||||
|
@ -10,7 +10,6 @@
|
||||
#include <boost/uuid/uuid.hpp>
|
||||
#include <boost/uuid/uuid_io.hpp>
|
||||
#include <boost/uuid/uuid_generators.hpp>
|
||||
// #include <httplib.h>
|
||||
#include <nlohmann/json.hpp>
|
||||
#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() {
|
||||
|
267
hikyuu_cpp/hikyuu/utilities/FilterNode.h
Normal file
267
hikyuu_cpp/hikyuu/utilities/FilterNode.h
Normal file
@ -0,0 +1,267 @@
|
||||
/*
|
||||
* Copyright (c) 2023 hikyuu.org
|
||||
*
|
||||
* Created on: 2023-01-13
|
||||
* Author: fasiondog
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
#include <forward_list>
|
||||
#include <unordered_map>
|
||||
#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<FilterNode>;
|
||||
|
||||
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<ptr_t>::const_iterator;
|
||||
using iterator = std::forward_list<ptr_t>::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 <typename ValueT>
|
||||
ValueT value() const {
|
||||
return any_cast<ValueT>(m_value);
|
||||
}
|
||||
|
||||
template <typename ValueT>
|
||||
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<ptr_t> m_children;
|
||||
bool m_exclusive = false;
|
||||
};
|
||||
|
||||
template <>
|
||||
inline const any_t& FilterNode::value() const {
|
||||
return m_value;
|
||||
}
|
||||
|
||||
typedef std::shared_ptr<FilterNode> FilterNodePtr;
|
||||
|
||||
/**
|
||||
* @brief 绑定过滤节点,通过 std::function 绑定自定义的 filter 和 process 处理函数
|
||||
*/
|
||||
class BindFilterNode : public FilterNode {
|
||||
public:
|
||||
BindFilterNode() = default;
|
||||
virtual ~BindFilterNode() = default;
|
||||
|
||||
using filter_func = std::function<bool(FilterNode*, const any_t&)>;
|
||||
using process_func = std::function<void(FilterNode*, const any_t&)>;
|
||||
|
||||
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 EventT>
|
||||
class AsyncSerialEventProcessor {
|
||||
public:
|
||||
/**
|
||||
* @brief 构造函数
|
||||
* @param quit_wait 退出时等待所有任务完成
|
||||
*/
|
||||
explicit AsyncSerialEventProcessor(bool quit_wait = true) : m_quit_wait(quit_wait) {
|
||||
m_tg = std::unique_ptr<ThreadPool>(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<std::mutex> 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<EventT, FilterNodePtr> m_trees;
|
||||
std::unique_ptr<ThreadPool> m_tg;
|
||||
bool m_quit_wait = true;
|
||||
};
|
||||
|
||||
} // namespace hku
|
@ -10,7 +10,6 @@
|
||||
#include "os.h"
|
||||
#include "Log.h"
|
||||
|
||||
#if USE_SPDLOG_LOGGER
|
||||
// 使用 stdout_color 将无法将日志输出重定向至 python
|
||||
#include <spdlog/sinks/stdout_color_sinks.h>
|
||||
#include <iostream>
|
||||
@ -20,11 +19,6 @@
|
||||
#if HKU_USE_SPDLOG_ASYNC_LOGGER
|
||||
#include <spdlog/async.h>
|
||||
#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<spdlog::logger> 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<spdlog::logger> 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<spdlog::logger> 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<spdlog::sinks::ostream_sink_mt>(std::cout, true);
|
||||
} else {
|
||||
stdout_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
|
||||
}
|
||||
stdout_sink->set_level(spdlog::level::trace);
|
||||
|
||||
std::shared_ptr<spdlog::sinks::rotating_file_sink_mt> 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<spdlog::sinks::rotating_file_sink_mt>(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<spdlog::sinks::rotating_file_sink_mt>(logfile, 1024 * 1024 * 10, 3);
|
||||
rotating_sink->set_level(spdlog::level::warn);
|
||||
|
||||
std::vector<spdlog::sink_ptr> 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<spdlog::async_logger>(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<spdlog::logger> logger = spdlog::get(logname);
|
||||
if (logger) {
|
||||
spdlog::drop(logname);
|
||||
}
|
||||
spdlog::sink_ptr stdout_sink;
|
||||
if (inJupyter) {
|
||||
stdout_sink = std::make_shared<spdlog::sinks::ostream_sink_mt>(std::cout, true);
|
||||
} else {
|
||||
stdout_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
|
||||
}
|
||||
stdout_sink->set_level(spdlog::level::trace);
|
||||
|
||||
std::shared_ptr<spdlog::sinks::rotating_file_sink_mt> 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<spdlog::sinks::rotating_file_sink_mt>(log_filename, 1024 * 1024 * 10, 3);
|
||||
rotating_sink->set_level(spdlog::level::warn);
|
||||
}
|
||||
|
||||
std::vector<spdlog::sink_ptr> sinks{stdout_sink};
|
||||
if (rotating_sink) {
|
||||
sinks.emplace_back(rotating_sink);
|
||||
}
|
||||
#else
|
||||
logger = std::make_shared<spdlog::logger>(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<std::chrono::milliseconds>(now.time_since_epoch()).count() -
|
||||
std::chrono::duration_cast<std::chrono::seconds>(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
|
||||
|
@ -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 <spdlog/spdlog.h>
|
||||
#include <spdlog/fmt/ostr.h>
|
||||
#if HKU_USE_SPDLOG_ASYNC_LOGGER
|
||||
#include "spdlog/async.h"
|
||||
#endif
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <spdlog/fmt/ostr.h>
|
||||
#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<spdlog::logger> 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 */
|
||||
|
636
hikyuu_cpp/hikyuu/utilities/ResourcePool.h
Normal file
636
hikyuu_cpp/hikyuu/utilities/ResourcePool.h
Normal file
@ -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 <thread>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include <queue>
|
||||
#include <chrono>
|
||||
#include <unordered_set>
|
||||
#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 <typename ResourceType>
|
||||
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<std::mutex> 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<std::mutex> lock(m_mutex);
|
||||
m_maxPoolSize = num;
|
||||
}
|
||||
|
||||
/** 设置允许的最大空闲资源数 */
|
||||
void maxIdleSize(size_t num) {
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_maxIdelSize = num;
|
||||
}
|
||||
|
||||
/** 资源实例指针类型 */
|
||||
typedef std::shared_ptr<ResourceType> ResourcePtr;
|
||||
|
||||
/**
|
||||
* 获取可用资源,如超出允许的最大资源数将返回空指针
|
||||
* @exception CreateResourceException 新资源创建可能抛出异常
|
||||
*/
|
||||
ResourcePtr get() {
|
||||
std::lock_guard<std::mutex> 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<ResourceCloser>(result));
|
||||
return result;
|
||||
}
|
||||
p = m_resourceList.front();
|
||||
m_resourceList.pop();
|
||||
result = ResourcePtr(p, ResourceCloser(this));
|
||||
m_closer_set.insert(std::get_deleter<ResourceCloser>(result));
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 在指定的超时时间内获取可用资源
|
||||
* @param ms_timeout 超时时间,单位毫秒
|
||||
* @exception GetResourceTimeoutException, CreateResourceException
|
||||
*/
|
||||
ResourcePtr getWaitFor(uint64_t ms_timeout) { // NOSONAR
|
||||
std::unique_lock<std::mutex> 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<uint64_t, std::milli>(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<ResourceCloser>(result));
|
||||
return result;
|
||||
}
|
||||
}
|
||||
p = m_resourceList.front();
|
||||
m_resourceList.pop();
|
||||
result = ResourcePtr(p, ResourceCloser(this));
|
||||
m_closer_set.insert(std::get_deleter<ResourceCloser>(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<std::mutex> 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<ResourceType *> 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<std::mutex> 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<ResourceCloser *> 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 <typename ResourceType>
|
||||
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<std::mutex> 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<std::mutex> lock(m_mutex);
|
||||
m_maxPoolSize = num;
|
||||
}
|
||||
|
||||
/** 设置允许的最大空闲资源数 */
|
||||
void maxIdleSize(size_t num) {
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_maxIdelSize = num;
|
||||
}
|
||||
|
||||
/** 指定参数是否存在 */
|
||||
bool haveParam(const std::string &name) {
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
return m_param.have(name);
|
||||
}
|
||||
|
||||
/** 获取指定参数的值,如参数不存在或类型不匹配抛出异常 */
|
||||
template <typename ValueType>
|
||||
ValueType getParam(const std::string &name) {
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
return m_param.get<ValueType>(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 设定指定参数的值,参数仅在生成新的资源时生效
|
||||
* @details 在原本存在该参数的情况下,新设定的值类型须和原有参数类型相同,否则将抛出异常
|
||||
* @param name 参数名
|
||||
* @param value 参数值
|
||||
* @exception std::logic_error
|
||||
*/
|
||||
template <typename ValueType>
|
||||
void setParam(const std::string &name, const ValueType &value) {
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
// 如果参数未实际发送变化,则直接返回
|
||||
HKU_IF_RETURN(m_param.have(name) && value == m_param.get<ValueType>(name), void());
|
||||
m_param.set<ValueType>(name, value);
|
||||
m_version++;
|
||||
_releaseIdleResourceNoLock(); // 释放当前空闲资源,以便新参数值生效
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 设置资源参数,参数仅在生成新的资源时生效
|
||||
* @param param 参数对象
|
||||
*/
|
||||
void setParameter(const Parameter ¶m) {
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_param = param;
|
||||
m_version++;
|
||||
_releaseIdleResourceNoLock(); // 释放当前空闲资源,以便新参数值生效
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 设置资源参数,参数仅在生成新的资源时生效
|
||||
* @param param 参数对象
|
||||
*/
|
||||
void setParameter(Parameter &¶m) {
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_param = std::move(param);
|
||||
m_version++;
|
||||
_releaseIdleResourceNoLock(); // 释放当前空闲资源,以便新参数值生效
|
||||
}
|
||||
|
||||
/** 获取当前资源池版本 */
|
||||
int getVersion() {
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
return m_version;
|
||||
}
|
||||
|
||||
/** 递增当前资源池版本,相当于通知资源池资源版本发生变化 */
|
||||
void incVersion(int version) {
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_version++;
|
||||
}
|
||||
|
||||
/** 资源实例指针类型 */
|
||||
typedef std::shared_ptr<ResourceType> ResourcePtr;
|
||||
|
||||
/**
|
||||
* 获取可用资源,如超出允许的最大资源数将返回空指针
|
||||
* @exception CreateResourceException 新资源创建可能抛出异常
|
||||
*/
|
||||
ResourcePtr get() {
|
||||
std::lock_guard<std::mutex> 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<ResourceCloser>(result));
|
||||
return result;
|
||||
}
|
||||
p = m_resourceList.front();
|
||||
m_resourceList.pop();
|
||||
result = ResourcePtr(p, ResourceCloser(this));
|
||||
m_closer_set.insert(std::get_deleter<ResourceCloser>(result));
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 在指定的超时时间内获取可用资源
|
||||
* @param ms_timeout 超时时间,单位毫秒
|
||||
* @exception GetResourceTimeoutException, CreateResourceException
|
||||
*/
|
||||
ResourcePtr getWaitFor(uint64_t ms_timeout) { // NOSONAR
|
||||
std::unique_lock<std::mutex> 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<uint64_t, std::milli>(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<ResourceCloser>(result));
|
||||
return result;
|
||||
}
|
||||
}
|
||||
p = m_resourceList.front();
|
||||
m_resourceList.pop();
|
||||
result = ResourcePtr(p, ResourceCloser(this));
|
||||
m_closer_set.insert(std::get_deleter<ResourceCloser>(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<std::mutex> 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<ResourceType *> 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<std::mutex> 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<ResourceCloser *> m_closer_set; // 占用资源的 closer
|
||||
};
|
||||
|
||||
} // namespace hku
|
||||
|
||||
#endif /* HKU_UTILS_RESOURCE_POOL_H */
|
108
hikyuu_cpp/hikyuu/utilities/base64.cpp
Normal file
108
hikyuu_cpp/hikyuu/utilities/base64.cpp
Normal file
@ -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
|
59
hikyuu_cpp/hikyuu/utilities/base64.h
Normal file
59
hikyuu_cpp/hikyuu/utilities/base64.h
Normal file
@ -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 <memory>
|
||||
#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
|
@ -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
|
||||
|
208
hikyuu_cpp/hikyuu/utilities/http_client/HttpClient.cpp
Normal file
208
hikyuu_cpp/hikyuu/utilities/http_client/HttpClient.cpp
Normal file
@ -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 <sstream>
|
||||
#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
|
191
hikyuu_cpp/hikyuu/utilities/http_client/HttpClient.h
Normal file
191
hikyuu_cpp/hikyuu/utilities/http_client/HttpClient.h
Normal file
@ -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 <string>
|
||||
#include <nlohmann/json.hpp>
|
||||
#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
|
478
hikyuu_cpp/hikyuu/utilities/http_client/nng_wrap.h
Normal file
478
hikyuu_cpp/hikyuu/utilities/http_client/nng_wrap.h
Normal file
@ -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 <string>
|
||||
#include <nng/nng.h>
|
||||
#include <nng/supplemental/http/http.h>
|
||||
#include "hikyuu/utilities/Log.h"
|
||||
|
||||
#include "hikyuu/utilities/config.h"
|
||||
#if HKU_ENABLE_HTTP_CLIENT_SSL
|
||||
#include <nng/supplemental/tls/tls.h>
|
||||
#endif
|
||||
|
||||
namespace hku {
|
||||
|
||||
struct HttpTimeoutException : hku::exception {
|
||||
HttpTimeoutException() : hku::exception("Http timeout!") {}
|
||||
virtual ~HttpTimeoutException() noexcept = default;
|
||||
};
|
||||
|
||||
using HttpHeaders = std::map<std::string, std::string>;
|
||||
using HttpParams = std::map<std::string, std::string>;
|
||||
|
||||
} // 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
|
56
hikyuu_cpp/hikyuu/utilities/http_client/url.cpp
Normal file
56
hikyuu_cpp/hikyuu/utilities/http_client/url.cpp
Normal file
@ -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
|
25
hikyuu_cpp/hikyuu/utilities/http_client/url.h
Normal file
25
hikyuu_cpp/hikyuu/utilities/http_client/url.h
Normal file
@ -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 <string>
|
||||
|
||||
#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
|
23
hikyuu_cpp/hikyuu/utilities/mo/mo.cpp
Normal file
23
hikyuu_cpp/hikyuu/utilities/mo/mo.cpp
Normal file
@ -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<std::string, moFileLib::moFileReader> 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
|
48
hikyuu_cpp/hikyuu/utilities/mo/mo.h
Normal file
48
hikyuu_cpp/hikyuu/utilities/mo/mo.h
Normal file
@ -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 <unordered_map>
|
||||
#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<std::string, moFileLib::moFileReader> ms_dict;
|
||||
};
|
||||
|
||||
} // namespace hku
|
836
hikyuu_cpp/hikyuu/utilities/mo/moFileReader.h
Normal file
836
hikyuu_cpp/hikyuu/utilities/mo/moFileReader.h
Normal file
@ -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 <cstring> // this is for memset when compiling with gcc.
|
||||
#include <deque>
|
||||
#include <fstream>
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
//-------------------------------------------------------------
|
||||
// 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
|
||||
*
|
||||
*
|
||||
* <h2>Include in project</h2>
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* <h2>Usage</h2>
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* <h2>Changelog</h2>
|
||||
*
|
||||
* - 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
|
||||
*
|
||||
*
|
||||
* <h2>Credits</h2>
|
||||
*
|
||||
* Gettext is part of the GNU-Tools and (C) by the <a href="http://fsf.org">Free Software
|
||||
* Foundation</a>.\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<moTranslationPairInformation> 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<std::string, std::string> moLookupList;
|
||||
|
||||
/// \brief Type for the 2D map which holds the translation-pairs later.
|
||||
typedef std::map<std::string, moLookupList> 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 <typename T>
|
||||
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"(<!DOCTYPE HTML PUBLIC "- //W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">)"
|
||||
<< std::endl;
|
||||
stream << "<html><head><style type=\"text/css\">\n" << std::endl;
|
||||
stream << css << std::endl;
|
||||
stream << "</style>" << std::endl;
|
||||
stream << R"(<meta http-equiv="content-type" content="text/html; charset=utf-8">)"
|
||||
<< std::endl;
|
||||
stream << "<title>Dump of " << fname << "</title></head>" << std::endl;
|
||||
stream << "<body>" << std::endl;
|
||||
stream << "<center>" << std::endl;
|
||||
stream << "<h1>" << fname << "</h1>" << std::endl;
|
||||
stream << R"(<table border="1"><th colspan="2">Project Info</th>)" << 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 << "<tr><td>" << name << "</td><td>" << value << "</td></tr>"
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
stream << "</table>" << std::endl;
|
||||
stream << "<hr noshade/>" << std::endl;
|
||||
|
||||
// Now output the content
|
||||
stream << R"(<table border="1"><th colspan="2">Content</th>)" << std::endl;
|
||||
for (const auto &it : reader.m_lookup) {
|
||||
if (!it.first.empty()) // Skip the empty msgid, its the table we handled above.
|
||||
{
|
||||
stream << "<tr><td>" << it.first << "</td><td>" << it.second << "</td></tr>"
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
stream << "</table><br/>" << std::endl;
|
||||
|
||||
// Separate tables for each context
|
||||
for (const auto &it : reader.m_lookup_context) {
|
||||
stream << R"(<table border="1"><th colspan="2">)" << it.first << "</th>"
|
||||
<< std::endl;
|
||||
for (const auto &its : it.second) {
|
||||
stream << "<tr><td>" << its.first << "</td><td>" << its.second << "</td></tr>"
|
||||
<< std::endl;
|
||||
}
|
||||
stream << "</table><br/>" << std::endl;
|
||||
}
|
||||
|
||||
stream << "</center>" << std::endl;
|
||||
stream << "<div class=\"copyleft\">File generated by <a "
|
||||
"href=\"https://github.com/AnotherFoxGuy/MofileReader\" "
|
||||
"target=\"_blank\">moFileReaderSDK</a></div>"
|
||||
<< std::endl;
|
||||
stream << "</body></html>" << 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__ */
|
110
hikyuu_cpp/hikyuu/utilities/snowflake.h
Normal file
110
hikyuu_cpp/hikyuu/utilities/snowflake.h
Normal file
@ -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 <cstdint>
|
||||
#include <chrono>
|
||||
#include <stdexcept>
|
||||
#include <mutex>
|
||||
|
||||
namespace hku {
|
||||
|
||||
class snowflake_nonlock {
|
||||
public:
|
||||
void lock() {}
|
||||
void unlock() {}
|
||||
};
|
||||
|
||||
template <int64_t Twepoch, typename Lock = snowflake_nonlock>
|
||||
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<std::chrono::steady_clock>;
|
||||
|
||||
time_point start_time_point_ = std::chrono::steady_clock::now();
|
||||
int64_t start_millsecond_ = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
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_type> 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_type> 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::milliseconds>(
|
||||
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
|
@ -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")
|
||||
|
5
setup.py
5
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)
|
||||
|
||||
|
148
xmake.lua
148
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
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user