aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/core/card.cc52
-rw-r--r--src/core/card.h27
-rw-r--r--src/core/session.cc341
-rw-r--r--src/core/session.h58
-rw-r--r--src/core/user.h10
-rw-r--r--src/core/user_state.h19
-rw-r--r--src/server/main.cc160
-rw-r--r--src/util/common.cc123
-rw-r--r--src/util/common.h33
9 files changed, 823 insertions, 0 deletions
diff --git a/src/core/card.cc b/src/core/card.cc
new file mode 100644
index 0000000..1a4c63d
--- /dev/null
+++ b/src/core/card.cc
@@ -0,0 +1,52 @@
+#include "core/card.h"
+
+#include <sstream>
+#include <fstream>
+
+#include "util/common.h"
+
+static std::map<std::string, Card> card_db;
+
+void init_card_db(){
+ auto fs = std::ifstream{"./resource/mtgzhs"};
+ std::string line;
+ while(std::getline(fs, line, '\n')) {
+ Card card;
+ card.name = line;
+ std::getline(fs, card.zhsname);
+ std::getline(fs, card.zhstext);
+ card_db[card.name] = card;
+ }
+}
+
+Arc<Card> get_card(std::string name) {
+ auto card = make_shared<Card>();
+ card->name = name;
+ card->image_url = "https://api.scryfall.com/cards/named?format=image&version=normal&exact=" + url_encode(name);
+ if (card_db.find(name) != card_db.end()) {
+ card->zhsname = card_db[name].zhsname;
+ card->zhstext = card_db[name].zhstext;
+ card->image_url = "https://api.scryfall.com/cards/named?format=image&version=normal&fuzzy=" + url_encode(card->zhsname);
+ str_replace(card->zhstext, "\001", "\n");
+ }
+ return card;
+}
+
+std::vector<Arc<Card>> parse_cardlist(const std::string &in_str) {
+ std::string str;
+ for (auto c : in_str) {
+ if (c != '\r') {
+ str += c;
+ }
+ }
+
+ auto result = std::vector<Arc<Card>>{};
+ auto ss = std::stringstream{str};
+
+ for (std::string line; std::getline(ss, line, '\n');) {
+ if (!line.empty()) {
+ result.push_back(get_card(line));
+ }
+ }
+ return result;
+} \ No newline at end of file
diff --git a/src/core/card.h b/src/core/card.h
new file mode 100644
index 0000000..afd8198
--- /dev/null
+++ b/src/core/card.h
@@ -0,0 +1,27 @@
+#ifndef HIVE_MIND_CORE_CARD_H_
+#define HIVE_MIND_CORE_CARD_H_
+
+#include <vector>
+#include <map>
+
+#include "util/common.h"
+
+constexpr int PACK_NUM = 3;
+constexpr int CARD_NUM_PER_PACK = 15;
+
+struct Card {
+ std::string name;
+ std::string text;
+ std::string zhsname;
+ std::string zhstext;
+ std::string image_url;
+};
+
+using CardPack = std::vector<Arc<Card>>;
+
+void init_card_db();
+Arc<Card> get_card(std::string name);
+
+std::vector<Arc<Card>> parse_cardlist(const std::string &s);
+
+#endif
diff --git a/src/core/session.cc b/src/core/session.cc
new file mode 100644
index 0000000..2b10e9f
--- /dev/null
+++ b/src/core/session.cc
@@ -0,0 +1,341 @@
+#include "core/session.h"
+
+#include <exception>
+#include <random>
+
+static std::map<std::string, Arc<Session>> session_map;
+static std::mutex session_map_lock;
+
+std::string to_string(SessionState state) {
+ switch(state) {
+ case SessionState::pending: return "未开始";
+ case SessionState::ongoing: return "进行中";
+ default: return "已经结束";
+ }
+ return "";
+};
+
+Arc<Session> Session::create_session(Arc<User> creator, std::vector<Arc<Card>> &card_list, int player_num) {
+ ulock _l{session_map_lock};
+
+ if(player_num < 1) {
+ throw std::runtime_error{"play num must > 1"};
+ }
+ if (card_list.size() < static_cast<size_t>(PACK_NUM * CARD_NUM_PER_PACK * player_num)) {
+ throw std::runtime_error{"insufficient cards"};
+ }
+
+ auto session = make_shared<Session>();
+ session->player_num = player_num;
+ session->session_id = gen_random();
+ session->time_created = std::chrono::system_clock::now();
+
+ auto user_state = make_shared<UserState>();
+ user_state->is_creator = true;
+ user_state->user = creator;
+ user_state->user->nick = "房主";
+ user_state->time_last_updated = std::chrono::system_clock::now();
+ session->users.push_back(user_state);
+ std::random_device rd;
+ std::mt19937 g(rd());
+ std::shuffle(card_list.begin(), card_list.end(), g);
+ for (auto &card : card_list){
+ session->remained_cards.push(card);
+ }
+
+ session_map[session->session_id] = session;
+ return session;
+}
+
+Arc<Session> Session::get_session(const std::string &session_id) {
+ ulock _l{session_map_lock};
+
+ if (session_map.find(session_id) == session_map.end()) {
+ throw std::runtime_error{"cannot find session"};
+ }
+ return session_map[session_id];
+}
+
+void Session::validate_user(Arc<User> user) {
+ for (auto &user_state : this->users) {
+ if (user_state->user->user_id == user->user_id) {
+ return;
+ }
+ }
+ throw std::runtime_error{"user do not exist"};
+}
+
+void Session::validate_user_is_new(Arc<User> user) {
+ for (auto &user_state : this->users) {
+ if (user_state->user->user_id == user->user_id) {
+ throw std::runtime_error{"user already exist"};
+ }
+ }
+ return;
+}
+
+void Session::validate_creator(Arc<User> user) {
+ for (auto &user_state : this->users) {
+ if (user_state->user->user_id == user->user_id && user_state->is_creator) {
+ return;
+ }
+ }
+ throw std::runtime_error{"not creator"};
+}
+
+void Session::validate_session_state(SessionState state) {
+ if (this->session_state != state) {
+ throw std::runtime_error{"session is not valid"};
+ }
+ return;
+}
+
+void Session::add_user(Arc<User> user) {
+ ulock _l{this->lock};
+ validate_session_state(SessionState::pending);
+ validate_user_is_new(user);
+ if (user->nick.empty()) {
+ throw std::runtime_error{"invalid nick"};
+ }
+ if (users.size() >= static_cast<size_t>(player_num)) {
+ throw std::runtime_error{"room is full"};
+ }
+
+ auto user_state = make_shared<UserState>();
+ user_state->user = user;
+ user_state->time_last_updated = std::chrono::system_clock::now();
+ users.push_back(user_state);
+
+ get_creator()->time_last_updated = std::chrono::system_clock::now();
+}
+
+void Session::kick_user(Arc<User> kicking_user, Arc<User> kicked_user) {
+ ulock _l{this->lock};
+ validate_session_state(SessionState::pending);
+ validate_creator(kicking_user);
+ validate_user(kicked_user);
+
+ for (auto it = users.begin(); it < users.end(); it++) {
+ if ((*it)->user->user_id == kicked_user->user_id && !(*it)->is_creator) {
+ users.erase(it);
+ }
+ }
+}
+
+void Session::start(Arc<User> starting_user) {
+ ulock _l{this->lock};
+ validate_session_state(SessionState::pending);
+ validate_creator(starting_user);
+
+ if (users.size() != static_cast<size_t>(player_num)) {
+ throw std::runtime_error{"player num wrong"};
+ }
+
+ this->session_state = SessionState::ongoing;
+ for (auto &user_state : users) {
+ user_state->time_last_updated = std::chrono::system_clock::now();
+ }
+ sanitize_state();
+}
+
+void Session::select_card(Arc<User> user, Arc<Card> card) {
+ ulock _l{this->lock};
+ validate_session_state(SessionState::ongoing);
+ validate_user(user);
+
+ // find the user
+ Arc<UserState> user_state;
+ size_t user_idx;
+ for (size_t i = 0; i < users.size(); i++) {
+ if (users[i]->user->user_id == user->user_id) {
+ user_state = users[i];
+ user_idx = i;
+ }
+ }
+ if (user_state->card_pack_queue.empty()) {
+ throw std::runtime_error{"no card to select"};
+ }
+ // find the card
+ auto pack = user_state->card_pack_queue.front();
+ for (auto it = pack->begin(); it < pack->end(); it++) {
+ if ((*it)->name == card->name) {
+ // get the card and transfer pack to next player
+ user_state->selected_card.push_back(card);
+ pack->erase(it);
+ if (!pack->empty()) {
+ users[(user_idx + 1) % player_num]->card_pack_queue.push(pack);
+ if (users[(user_idx + 1) % player_num]->card_pack_queue.size() == 1) {
+ users[(user_idx + 1) % player_num]->time_last_updated = std::chrono::system_clock::now();
+ }
+ }
+ user_state->card_pack_queue.pop();
+ sanitize_state();
+ return;
+ }
+ }
+ throw std::runtime_error{"invalid card"};
+}
+
+int64_t Session::get_user_timestamp(Arc<User> user) {
+ ulock _l{this->lock};
+ validate_user(user);
+
+ auto chrono_stamp = get_user_state(user)->time_last_updated;
+
+ return std::chrono::duration_cast<std::chrono::milliseconds>(chrono_stamp.time_since_epoch()).count();
+}
+
+Arc<UserState> Session::get_user_state(Arc<User> user) {
+ Arc<UserState> cur_user;
+ for (auto &user_state : this->users) {
+ if (user_state->user->user_id == user->user_id) {
+ cur_user = user_state;
+ }
+ }
+ return cur_user;
+}
+
+Arc<UserState> Session::get_creator() {
+ Arc<UserState> cur_user;
+ for (auto &user_state : this->users) {
+ if (user_state->is_creator) {
+ cur_user = user_state;
+ }
+ }
+ return cur_user;
+}
+
+
+std::string Session::get_card_str(Arc<User> user) {
+ ulock _l{this->lock};
+ validate_user(user);
+
+ auto cur_user = get_user_state(user);
+ std::string card_str;
+ for (auto &card : cur_user->selected_card) {
+ card_str += card->name;
+ card_str += "\n";
+ }
+ return card_str;
+}
+
+bool Session::need_new_pack() {
+ for (auto &user_state : users) {
+ if (!user_state->card_pack_queue.empty()) {
+ return false;
+ }
+ }
+ round_num++;
+ if (round_num > PACK_NUM) {
+ return false;
+ }
+ return true;
+}
+
+bool Session::should_finish() {
+ for (auto &user_state : users) {
+ if (!user_state->card_pack_queue.empty()) {
+ return false;
+ }
+ }
+ if (round_num >= PACK_NUM) {
+ return true;
+ }
+ return false;
+}
+
+Arc<CardPack> create_pack(std::queue<Arc<Card>> &cards) {
+ auto pack = make_shared<CardPack>();
+ for (int i = 0; i < CARD_NUM_PER_PACK; i++) {
+ pack->push_back(cards.front());
+ cards.pop();
+ }
+ return pack;
+}
+
+void Session::sanitize_state() {
+ if (need_new_pack()) {
+ for (auto &user_state : users) {
+ user_state->card_pack_queue.push(create_pack(remained_cards));
+ user_state->time_last_updated = std::chrono::system_clock::now();
+ }
+ }
+ if (should_finish()) {
+ for (auto &user_state : users) {
+ user_state->time_last_updated = std::chrono::system_clock::now();
+ }
+ session_state = SessionState::finished;
+ }
+}
+
+mstch::map card_view(Card &card, std::string &session_id) {
+ auto card_info = mstch::map{
+ {"sessionid", session_id},
+ {"name", html_encode(card.name)},
+ {"imgurl", card.image_url},
+ {"nameurl", url_encode(card.name)}};
+ std::string zhstext;
+ if (!card.zhsname.empty()) {
+ zhstext = "【" + card.zhsname + "】";
+ }
+ if (!card.zhstext.empty()) {
+ zhstext += "\r";
+ zhstext += card.zhstext;
+ }
+ zhstext = html_encode(zhstext);
+ str_replace(zhstext, "\n", "<br>");
+ str_replace(zhstext, "\r", "</p><p>");
+ card_info["zhstext"] = zhstext;
+ if (zhstext.empty()) {
+ card_info["stab"] = std::string("<div></div>");
+ }
+ return card_info;
+}
+
+mstch::map Session::get_user_session_view(Arc<User> user) {
+ ulock _l{this->lock};
+ mstch::map view{
+ {"download", mstch::array{
+ mstch::map{{"sessionid", session_id}},
+ }}
+ };
+ mstch::array players{};
+ Arc<UserState> cur_user;
+ for (auto &user_state : this->users) {
+ if (user_state->user->user_id == user->user_id) {
+ cur_user = user_state;
+ if (user_state->is_creator && session_state == SessionState::pending) {
+ view["start"] = mstch::array{mstch::map{{"sessionid", session_id}}};
+ }
+ }
+ players.push_back(mstch::map{
+ {"nick", user_state->user->nick},
+ {"packnum", std::to_string(user_state->card_pack_queue.size())}});
+ }
+ view["players"] = players;
+ view["state"] = to_string(session_state);
+ if (cur_user == nullptr && session_state == SessionState::pending) {
+ view["join"] = mstch::array{mstch::map{{"sessionid", session_id}}};
+ }
+ if (cur_user == nullptr) {
+ return view;
+ }
+
+ mstch::array cardstopick{};
+ mstch::array cardspicked{};
+ if (!cur_user->card_pack_queue.empty()) {
+ auto pack = cur_user->card_pack_queue.front();
+ for (auto &card : *pack) {
+ cardstopick.push_back(card_view(*card, session_id));
+ }
+ }
+ if (!cur_user->selected_card.empty()) {
+ for (auto &card : cur_user->selected_card) {
+ cardspicked.push_back(card_view(*card, session_id));
+ }
+ }
+ view["cardstopick"] = cardstopick;
+ view["cardspicked"] = cardspicked;
+
+ return view;
+} \ No newline at end of file
diff --git a/src/core/session.h b/src/core/session.h
new file mode 100644
index 0000000..2bb92a2
--- /dev/null
+++ b/src/core/session.h
@@ -0,0 +1,58 @@
+#ifndef HIVE_MIND_CORE_SESSION_H_
+#define HIVE_MIND_CORE_SESSION_H_
+
+#include <chrono>
+#include <map>
+#include <mutex>
+#include <set>
+#include <vector>
+
+#include <mstch/mstch.hpp>
+
+#include "core/card.h"
+#include "core/user.h"
+#include "core/user_state.h"
+#include "util/common.h"
+
+enum class SessionState {
+ pending, ongoing, finished
+};
+
+class Session {
+ public:
+ static Arc<Session> create_session(Arc<User> creator, std::vector<Arc<Card>> &card_list, int player_num);
+ static Arc<Session> get_session(const std::string &session_id);
+
+ void add_user(Arc<User> user);
+ void kick_user(Arc<User> kicking_user, Arc<User> kicked_user);
+ void select_card(Arc<User> user, Arc<Card> card);
+ void start(Arc<User> starting_user);
+ mstch::map get_user_session_view(Arc<User> user);
+ std::string get_card_str(Arc<User> user);
+ int64_t get_user_timestamp(Arc<User> user);
+ const std::string &get_session_id() {return session_id;}
+
+ private:
+ Arc<UserState> get_user_state(Arc<User> user);
+ Arc<UserState> get_creator();
+
+ void sanitize_state();
+ bool need_new_pack();
+ bool should_finish();
+
+ void validate_user(Arc<User> user);
+ void validate_user_is_new(Arc<User> user);
+ void validate_creator(Arc<User> user);
+ void validate_session_state(SessionState state);
+
+ std::string session_id;
+ int player_num;
+ SessionState session_state = SessionState::pending;
+ std::vector<Arc<UserState>> users;
+ std::queue<Arc<Card>> remained_cards;
+ std::chrono::time_point<std::chrono::system_clock> time_created;
+ int round_num = 0;
+ std::mutex lock;
+};
+
+#endif
diff --git a/src/core/user.h b/src/core/user.h
new file mode 100644
index 0000000..1feaf2f
--- /dev/null
+++ b/src/core/user.h
@@ -0,0 +1,10 @@
+#ifndef HIVE_MIND_CORE_USER_H_
+#define HIVE_MIND_CORE_USER_H_
+
+
+struct User {
+ std::string user_id;
+ std::string nick;
+};
+
+#endif
diff --git a/src/core/user_state.h b/src/core/user_state.h
new file mode 100644
index 0000000..c8020d8
--- /dev/null
+++ b/src/core/user_state.h
@@ -0,0 +1,19 @@
+#ifndef HIVE_MIND_CORE_USER_STATE_H_
+#define HIVE_MIND_CORE_USER_STATE_H_
+
+#include <chrono>
+#include <queue>
+#include <set>
+
+#include "util/common.h"
+
+struct UserState {
+ bool is_creator = false;
+ Arc<User> user;
+ std::string error_msg;
+ std::queue<Arc<CardPack>> card_pack_queue;
+ std::vector<Arc<Card>> selected_card;
+ std::chrono::time_point<std::chrono::system_clock> time_last_updated;
+};
+
+#endif
diff --git a/src/server/main.cc b/src/server/main.cc
new file mode 100644
index 0000000..84f7f0f
--- /dev/null
+++ b/src/server/main.cc
@@ -0,0 +1,160 @@
+#include <iostream>
+
+#include <crow.h>
+
+#include "core/session.h"
+#include "util/common.h"
+
+Arc<User> handler_cookie(crow::App<crow::CookieParser> &app, const crow::request &req) {
+ auto& ctx = app.get_context<crow::CookieParser>(req);
+ auto uid = ctx.get_cookie("uid");
+ if (uid.empty()) {
+ ctx.set_cookie("uid", gen_random() + ";Path=/");
+ }
+ return Arc<User>(new User{uid, ""});
+}
+
+crow::query_string get_parsed_param(const std::string &body) {
+ return crow::query_string(std::string{"http://example.org?"} + body);
+}
+
+int main(int argc, char** argv)
+{
+ crow::App<crow::CookieParser> app;
+ load_text_resource("./resource/index.html", "index");
+ load_text_resource("./resource/session.html", "session");
+ init_card_db();
+
+ CROW_ROUTE(app, "/")([&](const crow::request& req){
+ handler_cookie(app, req);
+ return get_text_resource("index");
+ });
+
+ CROW_ROUTE(app, "/create-session/")
+ .methods("POST"_method)
+ ([&](const crow::request& req){
+ auto user = handler_cookie(app, req);
+ auto params = get_parsed_param(req.body);
+ try {
+ int player_num = std::stoi(params.get("playernum"));
+ auto cardlist = parse_cardlist(params.get("cardlist"));
+ auto session = Session::create_session(user, cardlist, player_num);
+ CROW_LOG_INFO << "create session, "
+ << "playnum: "<< player_num
+ << ", cards: " << cardlist.size()
+ << ", sessionid: " << session->get_session_id();
+ crow::response resp{302};
+ resp.set_header("Location", std::string{"/session/"} + session->get_session_id());
+ return resp;
+ } catch (std::exception &e) {
+ return crow::response{400, e.what()};
+ }
+ return crow::response{500};
+ });
+
+ CROW_ROUTE(app, "/session/<string>")([&](const crow::request& req, std::string session_id){
+ auto user = handler_cookie(app, req);
+ try {
+ auto session = Session::get_session(session_id);
+ auto view = session->get_user_session_view(user);
+ auto tmplt = get_text_resource("session");
+ return crow::response(mstch::render(tmplt, view));
+ } catch (std::exception &e) {
+ return crow::response{400, e.what()};
+ }
+ return crow::response{500};
+ });
+
+ CROW_ROUTE(app, "/start-session/<string>")
+ .methods("POST"_method)
+ ([&](const crow::request& req, std::string session_id){
+ auto user = handler_cookie(app, req);
+ try {
+ auto session = Session::get_session(session_id);
+ session->start(user);
+ CROW_LOG_INFO << "start session, "
+ << "sessionid: " << session_id;
+ crow::response resp{302};
+ resp.set_header("Location", std::string{"/session/"} + session->get_session_id());
+ return resp;
+ } catch (std::exception &e) {
+ return crow::response{400, e.what()};
+ }
+ return crow::response{500};
+ });
+
+ CROW_ROUTE(app, "/join/<string>")
+ .methods("POST"_method)
+ ([&](const crow::request& req, std::string session_id){
+ auto user = handler_cookie(app, req);
+ auto params = get_parsed_param(req.body);
+ try {
+ std::string nick = params.get("nickname");
+ user->nick = nick;
+ auto session = Session::get_session(session_id);
+ session->add_user(user);
+ CROW_LOG_INFO << "user join session"
+ << "|sessionid:" << session_id
+ << "|uid:" << user->user_id
+ << "|nick:" << user->nick;
+ crow::response resp{302};
+ resp.set_header("Location", std::string{"/session/"} + session->get_session_id());
+ return resp;
+ } catch (std::exception &e) {
+ return crow::response{400, e.what()};
+ }
+ return crow::response{500};
+ });
+
+ CROW_ROUTE(app, "/select/<string>/<string>")
+ .methods("POST"_method)
+ ([&](const crow::request& req, std::string session_id, std::string card_name){
+ auto user = handler_cookie(app, req);
+ try {
+ auto session = Session::get_session(session_id);
+ card_name = url_decode(card_name);
+ CROW_LOG_INFO << "select card"
+ << "|sessionid:" << session_id
+ << "|uid:" << user->user_id
+ << "|card:" << card_name;
+ auto card = get_card(card_name);
+ session->select_card(user, card);
+ crow::response resp{302};
+ resp.set_header("Location", std::string{"/session/"} + session->get_session_id());
+ return resp;
+ } catch (std::exception &e) {
+ return crow::response{400, e.what()};
+ }
+ return crow::response{500};
+ });
+
+ CROW_ROUTE(app, "/download/<string>")
+ ([&](const crow::request& req, std::string session_id){
+ auto user = handler_cookie(app, req);
+ try {
+ auto session = Session::get_session(session_id);
+ return crow::response{200, session->get_card_str(user)};
+ } catch (std::exception &e) {
+ return crow::response{400, e.what()};
+ }
+ return crow::response{500};
+ });
+
+ CROW_ROUTE(app, "/timestamp/<string>")
+ ([&](const crow::request& req, std::string session_id){
+ auto user = handler_cookie(app, req);
+ try {
+ auto session = Session::get_session(session_id);
+ return crow::response{200, std::to_string(session->get_user_timestamp(user))};
+ } catch (std::exception &e) {
+ return crow::response{400, e.what()};
+ }
+ return crow::response{500};
+ });
+
+ int port = 18080;
+ if (argc > 1) {
+ port = std::stoi(std::string{argv[1]});
+ }
+ app.port(port).multithreaded().run();
+} \ No newline at end of file
diff --git a/src/util/common.cc b/src/util/common.cc
new file mode 100644
index 0000000..7a6275d
--- /dev/null
+++ b/src/util/common.cc
@@ -0,0 +1,123 @@
+#include "util/common.h"
+
+#include <algorithm>
+#include <iostream>
+#include <fstream>
+#include <sstream>
+#include <map>
+#include <cstdio>
+
+std::string gen_random()
+{
+ std::string::size_type length = 20;
+ static auto& chrs = "0123456789"
+ "abcdefghijklmnopqrstuvwxyz"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+
+ thread_local static std::mt19937 rg{std::random_device{}()};
+ thread_local static std::uniform_int_distribution<std::string::size_type> pick(0, sizeof(chrs) - 2);
+
+ std::string s;
+
+ s.reserve(length);
+
+ while(length--)
+ s += chrs[pick(rg)];
+
+ return s;
+}
+
+void hexchar(unsigned char c, unsigned char &hex1, unsigned char &hex2)
+{
+ hex1 = c / 16;
+ hex2 = c % 16;
+ hex1 += hex1 <= 9 ? '0' : 'a' - 10;
+ hex2 += hex2 <= 9 ? '0' : 'a' - 10;
+}
+
+std::string url_encode(const std::string &s) {
+ const char *str = s.c_str();
+ std::vector<char> v(s.size());
+ v.clear();
+ for (size_t i = 0, l = s.size(); i < l; i++)
+ {
+ char c = str[i];
+ if ((c >= '0' && c <= '9') ||
+ (c >= 'a' && c <= 'z') ||
+ (c >= 'A' && c <= 'Z') ||
+ c == '-' || c == '_' || c == '.' || c == '!' || c == '~' ||
+ c == '*' || c == '\'' || c == '(' || c == ')')
+ {
+ v.push_back(c);
+ }
+ else
+ {
+ v.push_back('%');
+ unsigned char d1, d2;
+ hexchar(c, d1, d2);
+ v.push_back(d1);
+ v.push_back(d2);
+ }
+ }
+ return std::string(v.cbegin(), v.cend());
+}
+
+std::string url_decode(const std::string &SRC) {
+ std::string ret;
+ char ch;
+ int ii;
+ for (size_t i = 0; i < SRC.length(); i++) {
+ if (int(SRC[i])==37) {
+ std::sscanf(SRC.substr(i+1,2).c_str(), "%x", &ii);
+ ch=static_cast<char>(ii);
+ ret+=ch;
+ i=i+2;
+ } else {
+ ret+=SRC[i];
+ }
+ }
+ return (ret);
+}
+
+std::string html_encode(const std::string& data) {
+ std::string buffer;
+ buffer.reserve(data.size());
+ for(size_t pos = 0; pos != data.size(); ++pos) {
+ switch(data[pos]) {
+ case '&': buffer.append("&amp;"); break;
+ case '\"': buffer.append("&quot;"); break;
+ case '\'': buffer.append("&apos;"); break;
+ case '<': buffer.append("&lt;"); break;
+ case '>': buffer.append("&gt;"); break;
+ default: buffer.append(&data[pos], 1); break;
+ }
+ }
+ return buffer;
+}
+
+static std::map<std::string, std::string> text_resource;
+static std::string empty{};
+
+void load_text_resource(std::string filename, std::string key) {
+ std::ifstream t(filename);
+ std::stringstream buffer;
+ buffer << t.rdbuf();
+ text_resource[key] = buffer.str();
+}
+
+const std::string &get_text_resource(std::string key) {
+ if (text_resource.find(key) == text_resource.end()) {
+ return empty;
+ }
+ return text_resource[key];
+}
+
+void str_replace(std::string& str, const std::string& from, const std::string& to) {
+ while (true) {
+ size_t start_pos = str.find(from);
+ if(start_pos == std::string::npos) {
+ return;
+ }
+ str.replace(start_pos, from.length(), to);
+ }
+}
diff --git a/src/util/common.h b/src/util/common.h
new file mode 100644
index 0000000..4ec4c4a
--- /dev/null
+++ b/src/util/common.h
@@ -0,0 +1,33 @@
+#ifndef HIVE_MIND_UTIL_COMMON_H_
+#define HIVE_MIND_UTIL_COMMON_H_
+
+#include <exception>
+#include <memory>
+#include <variant>
+#include <vector>
+#include <random>
+#include <string>
+#include <mutex>
+
+template<typename T>
+using Arc = std::shared_ptr<T>;
+
+template<typename T>
+using Box = std::unique_ptr<T>;
+
+using ulock = std::unique_lock<std::mutex>;
+using std::make_shared;
+using std::make_unique;
+
+struct Void{};
+
+std::string gen_random();
+std::string url_encode(const std::string &s);
+std::string url_decode(const std::string &s);
+std::string html_encode(const std::string& data);
+
+void load_text_resource(std::string filename, std::string key);
+const std::string &get_text_resource(std::string key);
+void str_replace(std::string& str, const std::string& from, const std::string& to);
+
+#endif