同步 hku_utils

This commit is contained in:
fasiondog 2024-08-02 18:15:43 +08:00
parent 6cfb99249b
commit bf55a8e151
23 changed files with 3155 additions and 307 deletions

View File

@ -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_ */

View File

@ -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

View File

@ -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 的输出窗口

View File

@ -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() {

View 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

View File

@ -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

View File

@ -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 */

View 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 &param, 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 &param, 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 &param) {
std::lock_guard<std::mutex> lock(m_mutex);
m_param = param;
m_version++;
_releaseIdleResourceNoLock(); // 释放当前空闲资源,以便新参数值生效
}
/**
* @brief
* @param param
*/
void setParameter(Parameter &&param) {
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 */

View 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

View 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

View File

@ -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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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__ */

View 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

View File

@ -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")

View File

@ -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
View File

@ -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