forked from OpenGamers/abaddon
initial sqlite, store user
This commit is contained in:
@@ -69,6 +69,9 @@ if(NOT USE_PKGCONFIG)
|
|||||||
|
|
||||||
find_package(nlohmann_json 3.2.0 REQUIRED)
|
find_package(nlohmann_json 3.2.0 REQUIRED)
|
||||||
|
|
||||||
|
find_package(unofficial-sqlite3 CONFIG REQUIRED)
|
||||||
|
link_libraries(unofficial::sqlite3::sqlite3)
|
||||||
|
|
||||||
find_path(IXWEBSOCKET_INCLUDE_DIRS ixwebsocket/ixwebsocket.h)
|
find_path(IXWEBSOCKET_INCLUDE_DIRS ixwebsocket/ixwebsocket.h)
|
||||||
find_library(IXWEBSOCKET_LIBRARY ixwebsocket)
|
find_library(IXWEBSOCKET_LIBRARY ixwebsocket)
|
||||||
link_libraries(${IXWEBSOCKET_LIBRARY})
|
link_libraries(${IXWEBSOCKET_LIBRARY})
|
||||||
|
|||||||
16
abaddon.cpp
16
abaddon.cpp
@@ -54,7 +54,6 @@ int Abaddon::StartGTK() {
|
|||||||
|
|
||||||
m_main_window = std::make_unique<MainWindow>();
|
m_main_window = std::make_unique<MainWindow>();
|
||||||
m_main_window->set_title(APP_TITLE);
|
m_main_window->set_title(APP_TITLE);
|
||||||
m_main_window->show();
|
|
||||||
m_main_window->UpdateComponents();
|
m_main_window->UpdateComponents();
|
||||||
|
|
||||||
// crashes for some stupid reason if i put it somewhere else
|
// crashes for some stupid reason if i put it somewhere else
|
||||||
@@ -111,6 +110,13 @@ int Abaddon::StartGTK() {
|
|||||||
dlg.run();
|
dlg.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!m_discord.IsStoreValid()) {
|
||||||
|
Gtk::MessageDialog dlg(*m_main_window, "The Discord cache could not be created!", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true);
|
||||||
|
dlg.run();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_main_window->show();
|
||||||
return m_gtk_app->run(*m_main_window);
|
return m_gtk_app->run(*m_main_window);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -380,8 +386,8 @@ void Abaddon::ActionLeaveGuild(Snowflake id) {
|
|||||||
|
|
||||||
void Abaddon::ActionKickMember(Snowflake user_id, Snowflake guild_id) {
|
void Abaddon::ActionKickMember(Snowflake user_id, Snowflake guild_id) {
|
||||||
ConfirmDialog dlg(*m_main_window);
|
ConfirmDialog dlg(*m_main_window);
|
||||||
const auto *user = m_discord.GetUser(user_id);
|
const auto user = m_discord.GetUser(user_id);
|
||||||
if (user != nullptr)
|
if (user.has_value())
|
||||||
dlg.SetConfirmText("Are you sure you want to kick " + user->Username + "#" + user->Discriminator + "?");
|
dlg.SetConfirmText("Are you sure you want to kick " + user->Username + "#" + user->Discriminator + "?");
|
||||||
auto response = dlg.run();
|
auto response = dlg.run();
|
||||||
if (response == Gtk::RESPONSE_OK)
|
if (response == Gtk::RESPONSE_OK)
|
||||||
@@ -390,8 +396,8 @@ void Abaddon::ActionKickMember(Snowflake user_id, Snowflake guild_id) {
|
|||||||
|
|
||||||
void Abaddon::ActionBanMember(Snowflake user_id, Snowflake guild_id) {
|
void Abaddon::ActionBanMember(Snowflake user_id, Snowflake guild_id) {
|
||||||
ConfirmDialog dlg(*m_main_window);
|
ConfirmDialog dlg(*m_main_window);
|
||||||
const auto *user = m_discord.GetUser(user_id);
|
const auto user = m_discord.GetUser(user_id);
|
||||||
if (user != nullptr)
|
if (user.has_value())
|
||||||
dlg.SetConfirmText("Are you sure you want to ban " + user->Username + "#" + user->Discriminator + "?");
|
dlg.SetConfirmText("Are you sure you want to ban " + user->Username + "#" + user->Discriminator + "?");
|
||||||
auto response = dlg.run();
|
auto response = dlg.run();
|
||||||
if (response == Gtk::RESPONSE_OK)
|
if (response == Gtk::RESPONSE_OK)
|
||||||
|
|||||||
@@ -416,9 +416,9 @@ void ChatMessageItemContainer::HandleUserMentions(Gtk::TextView *tv) {
|
|||||||
int mstart, mend;
|
int mstart, mend;
|
||||||
if (!match.fetch_pos(0, mstart, mend)) break;
|
if (!match.fetch_pos(0, mstart, mend)) break;
|
||||||
const Glib::ustring user_id = match.fetch(1);
|
const Glib::ustring user_id = match.fetch(1);
|
||||||
const auto *user = discord.GetUser(user_id);
|
const auto user = discord.GetUser(user_id);
|
||||||
const auto *channel = discord.GetChannel(ChannelID);
|
const auto *channel = discord.GetChannel(ChannelID);
|
||||||
if (user == nullptr || channel == nullptr) {
|
if (!user.has_value() || channel == nullptr) {
|
||||||
startpos = mend;
|
startpos = mend;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -814,8 +814,8 @@ void ChatMessageHeader::UpdateNameColor() {
|
|||||||
const auto &discord = Abaddon::Get().GetDiscordClient();
|
const auto &discord = Abaddon::Get().GetDiscordClient();
|
||||||
const auto guild_id = discord.GetChannel(ChannelID)->GuildID;
|
const auto guild_id = discord.GetChannel(ChannelID)->GuildID;
|
||||||
const auto role_id = discord.GetMemberHoistedRole(guild_id, UserID, true);
|
const auto role_id = discord.GetMemberHoistedRole(guild_id, UserID, true);
|
||||||
const auto *user = discord.GetUser(UserID);
|
const auto user = discord.GetUser(UserID);
|
||||||
if (user == nullptr) return;
|
if (!user.has_value()) return;
|
||||||
const auto *role = discord.GetRole(role_id);
|
const auto *role = discord.GetRole(role_id);
|
||||||
|
|
||||||
std::string md;
|
std::string md;
|
||||||
|
|||||||
@@ -179,8 +179,8 @@ void ChatWindow::ProcessNewMessage(Snowflake id, bool prepend) {
|
|||||||
} else {
|
} else {
|
||||||
const auto guild_id = client.GetChannel(m_active_channel)->GuildID;
|
const auto guild_id = client.GetChannel(m_active_channel)->GuildID;
|
||||||
const auto user_id = data->Author.ID;
|
const auto user_id = data->Author.ID;
|
||||||
const auto *user = client.GetUser(user_id);
|
const auto user = client.GetUser(user_id);
|
||||||
if (user == nullptr) return;
|
if (!user.has_value()) return;
|
||||||
|
|
||||||
header = Gtk::manage(new ChatMessageHeader(data));
|
header = Gtk::manage(new ChatMessageHeader(data));
|
||||||
header->signal_action_insert_mention().connect([this, user_id]() {
|
header->signal_action_insert_mention().connect([this, user_id]() {
|
||||||
|
|||||||
@@ -112,13 +112,13 @@ void MemberList::UpdateMemberListInternal() {
|
|||||||
|
|
||||||
// process all the shit first so its in proper order
|
// process all the shit first so its in proper order
|
||||||
std::map<int, const Role *> pos_to_role;
|
std::map<int, const Role *> pos_to_role;
|
||||||
std::map<int, std::vector<const User *>> pos_to_users;
|
std::map<int, std::vector<User>> pos_to_users;
|
||||||
std::unordered_map<Snowflake, int> user_to_color;
|
std::unordered_map<Snowflake, int> user_to_color;
|
||||||
std::vector<Snowflake> roleless_users;
|
std::vector<Snowflake> roleless_users;
|
||||||
|
|
||||||
for (const auto &id : ids) {
|
for (const auto &id : ids) {
|
||||||
auto *user = discord.GetUser(id);
|
auto user = discord.GetUser(id);
|
||||||
if (user == nullptr) {
|
if (!user.has_value()) {
|
||||||
roleless_users.push_back(id);
|
roleless_users.push_back(id);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -134,7 +134,7 @@ void MemberList::UpdateMemberListInternal() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
pos_to_role[pos_role->Position] = pos_role;
|
pos_to_role[pos_role->Position] = pos_role;
|
||||||
pos_to_users[pos_role->Position].push_back(user);
|
pos_to_users[pos_role->Position].push_back(std::move(*user));
|
||||||
if (col_role != nullptr) {
|
if (col_role != nullptr) {
|
||||||
if (ColorDistance(col_role->Color, 0xFFFFFF) < 15)
|
if (ColorDistance(col_role->Color, 0xFFFFFF) < 15)
|
||||||
user_to_color[id] = 0x000000;
|
user_to_color[id] = 0x000000;
|
||||||
@@ -198,10 +198,10 @@ void MemberList::UpdateMemberListInternal() {
|
|||||||
if (pos_to_users.find(pos) == pos_to_users.end()) continue;
|
if (pos_to_users.find(pos) == pos_to_users.end()) continue;
|
||||||
|
|
||||||
auto &users = pos_to_users.at(pos);
|
auto &users = pos_to_users.at(pos);
|
||||||
AlphabeticalSort(users.begin(), users.end(), [](auto e) { return e->Username; });
|
AlphabeticalSort(users.begin(), users.end(), [](const auto &e) { return e.Username; });
|
||||||
|
|
||||||
for (const auto data : users)
|
for (const auto data : users)
|
||||||
add_user(data);
|
add_user(&data);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (chan->Type == ChannelType::DM || chan->Type == ChannelType::GROUP_DM)
|
if (chan->Type == ChannelType::DM || chan->Type == ChannelType::GROUP_DM)
|
||||||
@@ -209,9 +209,9 @@ void MemberList::UpdateMemberListInternal() {
|
|||||||
else
|
else
|
||||||
add_role("@everyone");
|
add_role("@everyone");
|
||||||
for (const auto &id : roleless_users) {
|
for (const auto &id : roleless_users) {
|
||||||
const auto *user = discord.GetUser(id);
|
const auto user = discord.GetUser(id);
|
||||||
if (user != nullptr)
|
if (user.has_value())
|
||||||
add_user(user);
|
add_user(&*user);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -47,6 +47,10 @@ bool DiscordClient::IsStarted() const {
|
|||||||
return m_client_connected;
|
return m_client_connected;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool DiscordClient::IsStoreValid() const {
|
||||||
|
return m_store.IsValid();
|
||||||
|
}
|
||||||
|
|
||||||
std::unordered_set<Snowflake> DiscordClient::GetGuildsID() const {
|
std::unordered_set<Snowflake> DiscordClient::GetGuildsID() const {
|
||||||
const auto &guilds = m_store.GetGuilds();
|
const auto &guilds = m_store.GetGuilds();
|
||||||
std::unordered_set<Snowflake> ret;
|
std::unordered_set<Snowflake> ret;
|
||||||
@@ -121,6 +125,8 @@ void DiscordClient::FetchMessagesInChannel(Snowflake id, std::function<void(cons
|
|||||||
std::vector<Snowflake> ids;
|
std::vector<Snowflake> ids;
|
||||||
|
|
||||||
nlohmann::json::parse(r.text).get_to(msgs);
|
nlohmann::json::parse(r.text).get_to(msgs);
|
||||||
|
|
||||||
|
m_store.BeginTransaction();
|
||||||
for (const auto &msg : msgs) {
|
for (const auto &msg : msgs) {
|
||||||
m_store.SetMessage(msg.ID, msg);
|
m_store.SetMessage(msg.ID, msg);
|
||||||
AddMessageToChannel(msg.ID, id);
|
AddMessageToChannel(msg.ID, id);
|
||||||
@@ -128,6 +134,7 @@ void DiscordClient::FetchMessagesInChannel(Snowflake id, std::function<void(cons
|
|||||||
AddUserToGuild(msg.Author.ID, *msg.GuildID);
|
AddUserToGuild(msg.Author.ID, *msg.GuildID);
|
||||||
ids.push_back(msg.ID);
|
ids.push_back(msg.ID);
|
||||||
}
|
}
|
||||||
|
m_store.EndTransaction();
|
||||||
|
|
||||||
cb(ids);
|
cb(ids);
|
||||||
});
|
});
|
||||||
@@ -142,6 +149,8 @@ void DiscordClient::FetchMessagesInChannelBefore(Snowflake channel_id, Snowflake
|
|||||||
std::vector<Snowflake> ids;
|
std::vector<Snowflake> ids;
|
||||||
|
|
||||||
nlohmann::json::parse(r.text).get_to(msgs);
|
nlohmann::json::parse(r.text).get_to(msgs);
|
||||||
|
|
||||||
|
m_store.BeginTransaction();
|
||||||
for (const auto &msg : msgs) {
|
for (const auto &msg : msgs) {
|
||||||
m_store.SetMessage(msg.ID, msg);
|
m_store.SetMessage(msg.ID, msg);
|
||||||
AddMessageToChannel(msg.ID, channel_id);
|
AddMessageToChannel(msg.ID, channel_id);
|
||||||
@@ -149,6 +158,7 @@ void DiscordClient::FetchMessagesInChannelBefore(Snowflake channel_id, Snowflake
|
|||||||
AddUserToGuild(msg.Author.ID, *msg.GuildID);
|
AddUserToGuild(msg.Author.ID, *msg.GuildID);
|
||||||
ids.push_back(msg.ID);
|
ids.push_back(msg.ID);
|
||||||
}
|
}
|
||||||
|
m_store.EndTransaction();
|
||||||
|
|
||||||
cb(ids);
|
cb(ids);
|
||||||
});
|
});
|
||||||
@@ -162,7 +172,7 @@ const Channel *DiscordClient::GetChannel(Snowflake id) const {
|
|||||||
return m_store.GetChannel(id);
|
return m_store.GetChannel(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
const User *DiscordClient::GetUser(Snowflake id) const {
|
std::optional<User> DiscordClient::GetUser(Snowflake id) const {
|
||||||
return m_store.GetUser(id);
|
return m_store.GetUser(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -594,11 +604,13 @@ void DiscordClient::HandleGatewayReady(const GatewayMessage &msg) {
|
|||||||
for (auto &g : data.Guilds)
|
for (auto &g : data.Guilds)
|
||||||
ProcessNewGuild(g);
|
ProcessNewGuild(g);
|
||||||
|
|
||||||
|
m_store.BeginTransaction();
|
||||||
for (const auto &dm : data.PrivateChannels) {
|
for (const auto &dm : data.PrivateChannels) {
|
||||||
m_store.SetChannel(dm.ID, dm);
|
m_store.SetChannel(dm.ID, dm);
|
||||||
for (const auto &recipient : dm.Recipients)
|
for (const auto &recipient : dm.Recipients)
|
||||||
m_store.SetUser(recipient.ID, recipient);
|
m_store.SetUser(recipient.ID, recipient);
|
||||||
}
|
}
|
||||||
|
m_store.EndTransaction();
|
||||||
|
|
||||||
m_session_id = data.SessionID;
|
m_session_id = data.SessionID;
|
||||||
m_user_data = data.User;
|
m_user_data = data.User;
|
||||||
@@ -642,8 +654,10 @@ void DiscordClient::HandleGatewayGuildMemberUpdate(const GatewayMessage &msg) {
|
|||||||
void DiscordClient::HandleGatewayPresenceUpdate(const GatewayMessage &msg) {
|
void DiscordClient::HandleGatewayPresenceUpdate(const GatewayMessage &msg) {
|
||||||
PresenceUpdateMessage data = msg.Data;
|
PresenceUpdateMessage data = msg.Data;
|
||||||
auto cur = m_store.GetUser(data.User.at("id").get<Snowflake>());
|
auto cur = m_store.GetUser(data.User.at("id").get<Snowflake>());
|
||||||
if (cur != nullptr)
|
if (cur.has_value()) {
|
||||||
User::update_from_json(data.User, *cur);
|
User::update_from_json(data.User, *cur);
|
||||||
|
m_store.SetUser(cur->ID, *cur);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DiscordClient::HandleGatewayChannelDelete(const GatewayMessage &msg) {
|
void DiscordClient::HandleGatewayChannelDelete(const GatewayMessage &msg) {
|
||||||
@@ -715,6 +729,8 @@ void DiscordClient::HandleGatewayMessageUpdate(const GatewayMessage &msg) {
|
|||||||
void DiscordClient::HandleGatewayGuildMemberListUpdate(const GatewayMessage &msg) {
|
void DiscordClient::HandleGatewayGuildMemberListUpdate(const GatewayMessage &msg) {
|
||||||
GuildMemberListUpdateMessage data = msg.Data;
|
GuildMemberListUpdateMessage data = msg.Data;
|
||||||
|
|
||||||
|
m_store.BeginTransaction();
|
||||||
|
|
||||||
bool has_sync = false;
|
bool has_sync = false;
|
||||||
for (const auto &op : data.Ops) {
|
for (const auto &op : data.Ops) {
|
||||||
if (op.Op == "SYNC") {
|
if (op.Op == "SYNC") {
|
||||||
@@ -730,6 +746,8 @@ void DiscordClient::HandleGatewayGuildMemberListUpdate(const GatewayMessage &msg
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m_store.EndTransaction();
|
||||||
|
|
||||||
// todo: manage this event a little better
|
// todo: manage this event a little better
|
||||||
if (has_sync)
|
if (has_sync)
|
||||||
m_signal_guild_member_list_update.emit(data.GuildID);
|
m_signal_guild_member_list_update.emit(data.GuildID);
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ public:
|
|||||||
void Start();
|
void Start();
|
||||||
void Stop();
|
void Stop();
|
||||||
bool IsStarted() const;
|
bool IsStarted() const;
|
||||||
|
bool IsStoreValid() const;
|
||||||
|
|
||||||
using guilds_type = Store::guilds_type;
|
using guilds_type = Store::guilds_type;
|
||||||
using channels_type = Store::channels_type;
|
using channels_type = Store::channels_type;
|
||||||
@@ -81,7 +82,7 @@ public:
|
|||||||
void FetchMessagesInChannelBefore(Snowflake channel_id, Snowflake before_id, std::function<void(const std::vector<Snowflake> &)> cb);
|
void FetchMessagesInChannelBefore(Snowflake channel_id, Snowflake before_id, std::function<void(const std::vector<Snowflake> &)> cb);
|
||||||
const Message *GetMessage(Snowflake id) const;
|
const Message *GetMessage(Snowflake id) const;
|
||||||
const Channel *GetChannel(Snowflake id) const;
|
const Channel *GetChannel(Snowflake id) const;
|
||||||
const User *GetUser(Snowflake id) const;
|
std::optional<User> GetUser(Snowflake id) const;
|
||||||
const Role *GetRole(Snowflake id) const;
|
const Role *GetRole(Snowflake id) const;
|
||||||
const Guild *GetGuild(Snowflake id) const;
|
const Guild *GetGuild(Snowflake id) const;
|
||||||
const GuildMember *GetMember(Snowflake user_id, Snowflake guild_id) const;
|
const GuildMember *GetMember(Snowflake user_id, Snowflake guild_id) const;
|
||||||
|
|||||||
@@ -1,17 +1,12 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
#include "../util.hpp"
|
||||||
|
|
||||||
namespace detail { // more or less because idk what to name this stuff
|
namespace detail { // more or less because idk what to name this stuff
|
||||||
template<typename T>
|
|
||||||
struct is_optional : ::std::false_type {};
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
struct is_optional<::std::optional<T>> : ::std::true_type {};
|
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
inline void json_direct(const ::nlohmann::json &j, const char *key, T &val) {
|
inline void json_direct(const ::nlohmann::json &j, const char *key, T &val) {
|
||||||
if constexpr (is_optional<T>::value)
|
if constexpr (::util::is_optional<T>::value)
|
||||||
val = j.at(key).get<typename T::value_type>();
|
val = j.at(key).get<typename T::value_type>();
|
||||||
else
|
else
|
||||||
j.at(key).get_to(val);
|
j.at(key).get_to(val);
|
||||||
@@ -19,7 +14,7 @@ inline void json_direct(const ::nlohmann::json &j, const char *key, T &val) {
|
|||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
inline void json_optional(const ::nlohmann::json &j, const char *key, T &val) {
|
inline void json_optional(const ::nlohmann::json &j, const char *key, T &val) {
|
||||||
if constexpr (is_optional<T>::value) {
|
if constexpr (::util::is_optional<T>::value) {
|
||||||
if (j.contains(key))
|
if (j.contains(key))
|
||||||
val = j.at(key).get<typename T::value_type>();
|
val = j.at(key).get<typename T::value_type>();
|
||||||
else
|
else
|
||||||
@@ -32,7 +27,7 @@ inline void json_optional(const ::nlohmann::json &j, const char *key, T &val) {
|
|||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
inline void json_nullable(const ::nlohmann::json &j, const char *key, T &val) {
|
inline void json_nullable(const ::nlohmann::json &j, const char *key, T &val) {
|
||||||
if constexpr (is_optional<T>::value) {
|
if constexpr (::util::is_optional<T>::value) {
|
||||||
const auto &at = j.at(key);
|
const auto &at = j.at(key);
|
||||||
if (!at.is_null())
|
if (!at.is_null())
|
||||||
val = at.get<typename T::value_type>();
|
val = at.get<typename T::value_type>();
|
||||||
@@ -47,7 +42,7 @@ inline void json_nullable(const ::nlohmann::json &j, const char *key, T &val) {
|
|||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
inline void json_optional_nullable(const ::nlohmann::json &j, const char *key, T &val) {
|
inline void json_optional_nullable(const ::nlohmann::json &j, const char *key, T &val) {
|
||||||
if constexpr (is_optional<T>::value) {
|
if constexpr (::util::is_optional<T>::value) {
|
||||||
if (j.contains(key)) {
|
if (j.contains(key)) {
|
||||||
const auto &at = j.at(key);
|
const auto &at = j.at(key);
|
||||||
if (!at.is_null())
|
if (!at.is_null())
|
||||||
@@ -68,7 +63,7 @@ inline void json_optional_nullable(const ::nlohmann::json &j, const char *key, T
|
|||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
inline void json_update_optional_nullable(const ::nlohmann::json &j, const char *key, T &val) {
|
inline void json_update_optional_nullable(const ::nlohmann::json &j, const char *key, T &val) {
|
||||||
if constexpr (is_optional<T>::value) {
|
if constexpr (::util::is_optional<T>::value) {
|
||||||
if (j.contains(key)) {
|
if (j.contains(key)) {
|
||||||
const auto &at = j.at(key);
|
const auto &at = j.at(key);
|
||||||
if (!at.is_null())
|
if (!at.is_null())
|
||||||
@@ -89,7 +84,7 @@ inline void json_update_optional_nullable(const ::nlohmann::json &j, const char
|
|||||||
|
|
||||||
template<typename T, typename U>
|
template<typename T, typename U>
|
||||||
inline void json_update_optional_nullable_default(const ::nlohmann::json &j, const char *key, T &val, const U &fallback) {
|
inline void json_update_optional_nullable_default(const ::nlohmann::json &j, const char *key, T &val, const U &fallback) {
|
||||||
if constexpr (is_optional<T>::value) {
|
if constexpr (::util::is_optional<T>::value) {
|
||||||
if (j.contains(key)) {
|
if (j.contains(key)) {
|
||||||
const auto &at = j.at(key);
|
const auto &at = j.at(key);
|
||||||
if (at.is_null())
|
if (at.is_null())
|
||||||
|
|||||||
@@ -1,7 +1,71 @@
|
|||||||
#include "store.hpp"
|
#include "store.hpp"
|
||||||
|
|
||||||
|
// hopefully the casting between signed and unsigned int64 doesnt cause issues
|
||||||
|
|
||||||
|
Store::Store() {
|
||||||
|
m_db_path = std::filesystem::temp_directory_path() / "abaddon-store.db";
|
||||||
|
m_db_err = sqlite3_open(m_db_path.string().c_str(), &m_db);
|
||||||
|
if (m_db_err != SQLITE_OK) {
|
||||||
|
fprintf(stderr, "error opening database: %s\n", sqlite3_errstr(m_db_err));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_db_err = sqlite3_exec(m_db, "PRAGMA journal_mode = WAL", nullptr, nullptr, nullptr);
|
||||||
|
if (m_db_err != SQLITE_OK) {
|
||||||
|
fprintf(stderr, "enabling write-ahead-log failed: %s\n", sqlite3_errstr(m_db_err));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_db_err = sqlite3_exec(m_db, "PRAGMA synchronous = NORMAL", nullptr, nullptr, nullptr);
|
||||||
|
if (m_db_err != SQLITE_OK) {
|
||||||
|
fprintf(stderr, "setting synchronous failed: %s\n", sqlite3_errstr(m_db_err));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CreateTables();
|
||||||
|
CreateStatements();
|
||||||
|
}
|
||||||
|
|
||||||
|
Store::~Store() {
|
||||||
|
Cleanup();
|
||||||
|
|
||||||
|
m_db_err = sqlite3_close(m_db);
|
||||||
|
if (m_db_err != SQLITE_OK) {
|
||||||
|
fprintf(stderr, "error closing database: %s\n", sqlite3_errstr(m_db_err));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::filesystem::remove(m_db_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Store::IsValid() const {
|
||||||
|
return m_db_err == SQLITE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
void Store::SetUser(Snowflake id, const User &user) {
|
void Store::SetUser(Snowflake id, const User &user) {
|
||||||
m_users[id] = user;
|
if ((uint64_t)id == 0) {
|
||||||
|
printf("???: %s\n", user.Username.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
Bind(m_set_user_stmt, 1, id);
|
||||||
|
Bind(m_set_user_stmt, 2, user.Username);
|
||||||
|
Bind(m_set_user_stmt, 3, user.Discriminator);
|
||||||
|
Bind(m_set_user_stmt, 4, user.Avatar);
|
||||||
|
Bind(m_set_user_stmt, 5, user.IsBot);
|
||||||
|
Bind(m_set_user_stmt, 6, user.IsSystem);
|
||||||
|
Bind(m_set_user_stmt, 7, user.IsMFAEnabled);
|
||||||
|
Bind(m_set_user_stmt, 8, user.Locale);
|
||||||
|
Bind(m_set_user_stmt, 9, user.IsVerified);
|
||||||
|
Bind(m_set_user_stmt, 10, user.Email);
|
||||||
|
Bind(m_set_user_stmt, 11, user.Flags);
|
||||||
|
Bind(m_set_user_stmt, 12, user.PremiumType);
|
||||||
|
Bind(m_set_user_stmt, 13, user.PublicFlags);
|
||||||
|
|
||||||
|
if (!RunInsert(m_set_user_stmt)) {
|
||||||
|
fprintf(stderr, "user insert failed: %s\n", sqlite3_errstr(m_db_err));
|
||||||
|
}
|
||||||
|
|
||||||
|
// m_users[id] = user;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Store::SetChannel(Snowflake id, const Channel &channel) {
|
void Store::SetChannel(Snowflake id, const Channel &channel) {
|
||||||
@@ -32,18 +96,33 @@ void Store::SetEmoji(Snowflake id, const Emoji &emoji) {
|
|||||||
m_emojis[id] = emoji;
|
m_emojis[id] = emoji;
|
||||||
}
|
}
|
||||||
|
|
||||||
User *Store::GetUser(Snowflake id) {
|
std::optional<User> Store::GetUser(Snowflake id) const {
|
||||||
auto it = m_users.find(id);
|
Bind(m_get_user_stmt, 1, id);
|
||||||
if (it == m_users.end())
|
if (!FetchOne(m_get_user_stmt)) {
|
||||||
return nullptr;
|
if (m_db_err != SQLITE_DONE) // not an error, just means user isnt found
|
||||||
return &it->second;
|
fprintf(stderr, "error while fetching user info: %s\n", sqlite3_errstr(m_db_err));
|
||||||
}
|
Reset(m_get_user_stmt);
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
const User *Store::GetUser(Snowflake id) const {
|
User ret;
|
||||||
auto it = m_users.find(id);
|
Get(m_get_user_stmt, 0, ret.ID);
|
||||||
if (it == m_users.end())
|
Get(m_get_user_stmt, 1, ret.Username);
|
||||||
return nullptr;
|
Get(m_get_user_stmt, 2, ret.Discriminator);
|
||||||
return &it->second;
|
Get(m_get_user_stmt, 3, ret.Avatar);
|
||||||
|
Get(m_get_user_stmt, 4, ret.IsBot);
|
||||||
|
Get(m_get_user_stmt, 5, ret.IsSystem);
|
||||||
|
Get(m_get_user_stmt, 6, ret.IsMFAEnabled);
|
||||||
|
Get(m_get_user_stmt, 7, ret.Locale);
|
||||||
|
Get(m_get_user_stmt, 8, ret.IsVerified);
|
||||||
|
Get(m_get_user_stmt, 9, ret.Email);
|
||||||
|
Get(m_get_user_stmt, 10, ret.Flags);
|
||||||
|
Get(m_get_user_stmt, 11, ret.PremiumType);
|
||||||
|
Get(m_get_user_stmt, 12, ret.PublicFlags);
|
||||||
|
|
||||||
|
Reset(m_get_user_stmt);
|
||||||
|
|
||||||
|
return std::optional<User>(std::move(ret));
|
||||||
}
|
}
|
||||||
|
|
||||||
Channel *Store::GetChannel(Snowflake id) {
|
Channel *Store::GetChannel(Snowflake id) {
|
||||||
@@ -186,3 +265,136 @@ void Store::ClearAll() {
|
|||||||
m_roles.clear();
|
m_roles.clear();
|
||||||
m_users.clear();
|
m_users.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Store::BeginTransaction() {
|
||||||
|
m_db_err = sqlite3_exec(m_db, "BEGIN TRANSACTION", nullptr, nullptr, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Store::EndTransaction() {
|
||||||
|
m_db_err = sqlite3_exec(m_db, "COMMIT", nullptr, nullptr, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Store::CreateTables() {
|
||||||
|
constexpr const char *create_users = R"(
|
||||||
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
username TEXT NOT NULL,
|
||||||
|
discriminator TEXT NOT NULL,
|
||||||
|
avatar TEXT,
|
||||||
|
bot BOOL,
|
||||||
|
system BOOL,
|
||||||
|
mfa BOOL,
|
||||||
|
locale TEXT,
|
||||||
|
verified BOOl,
|
||||||
|
email TEXT,
|
||||||
|
flags INTEGER,
|
||||||
|
premium INTEGER,
|
||||||
|
pubflags INTEGER
|
||||||
|
)
|
||||||
|
)";
|
||||||
|
|
||||||
|
m_db_err = sqlite3_exec(m_db, create_users, nullptr, nullptr, nullptr);
|
||||||
|
if (m_db_err != SQLITE_OK) {
|
||||||
|
fprintf(stderr, "failed to create user table\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Store::CreateStatements() {
|
||||||
|
constexpr const char *set_user = R"(
|
||||||
|
REPLACE INTO users VALUES (
|
||||||
|
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
||||||
|
)
|
||||||
|
)";
|
||||||
|
|
||||||
|
constexpr const char *get_user = R"(
|
||||||
|
SELECT * FROM users WHERE id = ?
|
||||||
|
)";
|
||||||
|
|
||||||
|
m_db_err = sqlite3_prepare_v2(m_db, set_user, -1, &m_set_user_stmt, nullptr);
|
||||||
|
if (m_db_err != SQLITE_OK) {
|
||||||
|
fprintf(stderr, "failed to prepare set user statement\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_db_err = sqlite3_prepare_v2(m_db, get_user, -1, &m_get_user_stmt, nullptr);
|
||||||
|
if (m_db_err != SQLITE_OK) {
|
||||||
|
fprintf(stderr, "failed to prepare get user statement\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Store::Cleanup() {
|
||||||
|
sqlite3_finalize(m_set_user_stmt);
|
||||||
|
sqlite3_finalize(m_get_user_stmt);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Store::Bind(sqlite3_stmt *stmt, int index, int num) const {
|
||||||
|
m_db_err = sqlite3_bind_int(stmt, index, num);
|
||||||
|
if (m_db_err != SQLITE_OK) {
|
||||||
|
fprintf(stderr, "error binding index %d: %s\n", index, sqlite3_errstr(m_db_err));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Store::Bind(sqlite3_stmt *stmt, int index, uint64_t num) const {
|
||||||
|
m_db_err = sqlite3_bind_int64(stmt, index, num);
|
||||||
|
if (m_db_err != SQLITE_OK) {
|
||||||
|
fprintf(stderr, "error binding index %d: %s\n", index, sqlite3_errstr(m_db_err));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Store::Bind(sqlite3_stmt *stmt, int index, const std::string &str) const {
|
||||||
|
m_db_err = sqlite3_bind_text(stmt, index, str.c_str(), -1, nullptr);
|
||||||
|
if (m_db_err != SQLITE_OK) {
|
||||||
|
fprintf(stderr, "error binding index %d: %s\n", index, sqlite3_errstr(m_db_err));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Store::Bind(sqlite3_stmt *stmt, int index, bool val) const {
|
||||||
|
m_db_err = sqlite3_bind_int(stmt, index, val ? 1 : 0);
|
||||||
|
if (m_db_err != SQLITE_OK) {
|
||||||
|
fprintf(stderr, "error binding index %d: %s\n", index, sqlite3_errstr(m_db_err));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Store::RunInsert(sqlite3_stmt *stmt) {
|
||||||
|
m_db_err = sqlite3_step(stmt);
|
||||||
|
sqlite3_reset(stmt);
|
||||||
|
sqlite3_clear_bindings(stmt);
|
||||||
|
return m_db_err == SQLITE_DONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Store::FetchOne(sqlite3_stmt *stmt) const {
|
||||||
|
m_db_err = sqlite3_step(stmt);
|
||||||
|
return m_db_err == SQLITE_ROW;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Store::Get(sqlite3_stmt *stmt, int index, int &out) const {
|
||||||
|
out = sqlite3_column_int(stmt, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Store::Get(sqlite3_stmt *stmt, int index, std::string &out) const {
|
||||||
|
const unsigned char *ptr = sqlite3_column_text(stmt, index);
|
||||||
|
if (ptr == nullptr)
|
||||||
|
out = "";
|
||||||
|
else
|
||||||
|
out = reinterpret_cast<const char *>(ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Store::Get(sqlite3_stmt *stmt, int index, bool &out) const {
|
||||||
|
out = sqlite3_column_int(stmt, index) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Store::Get(sqlite3_stmt *stmt, int index, Snowflake &out) const {
|
||||||
|
const int64_t num = sqlite3_column_int64(stmt, index);
|
||||||
|
out = static_cast<uint64_t>(num);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Store::Reset(sqlite3_stmt *stmt) const {
|
||||||
|
sqlite3_reset(stmt);
|
||||||
|
sqlite3_clear_bindings(stmt);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
#include "../util.hpp"
|
||||||
#include "objects.hpp"
|
#include "objects.hpp"
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <sqlite3.h>
|
||||||
|
|
||||||
#ifdef GetMessage // fuck you windows.h
|
#ifdef GetMessage // fuck you windows.h
|
||||||
#undef GetMessage
|
#undef GetMessage
|
||||||
@@ -9,6 +12,11 @@
|
|||||||
|
|
||||||
class Store {
|
class Store {
|
||||||
public:
|
public:
|
||||||
|
Store();
|
||||||
|
~Store();
|
||||||
|
|
||||||
|
bool IsValid() const;
|
||||||
|
|
||||||
void SetUser(Snowflake id, const User &user);
|
void SetUser(Snowflake id, const User &user);
|
||||||
void SetChannel(Snowflake id, const Channel &channel);
|
void SetChannel(Snowflake id, const Channel &channel);
|
||||||
void SetGuild(Snowflake id, const Guild &guild);
|
void SetGuild(Snowflake id, const Guild &guild);
|
||||||
@@ -18,7 +26,8 @@ public:
|
|||||||
void SetPermissionOverwrite(Snowflake channel_id, Snowflake id, const PermissionOverwrite &perm);
|
void SetPermissionOverwrite(Snowflake channel_id, Snowflake id, const PermissionOverwrite &perm);
|
||||||
void SetEmoji(Snowflake id, const Emoji &emoji);
|
void SetEmoji(Snowflake id, const Emoji &emoji);
|
||||||
|
|
||||||
User *GetUser(Snowflake id);
|
// slap const on everything even tho its not *really* const
|
||||||
|
|
||||||
Channel *GetChannel(Snowflake id);
|
Channel *GetChannel(Snowflake id);
|
||||||
Guild *GetGuild(Snowflake id);
|
Guild *GetGuild(Snowflake id);
|
||||||
Role *GetRole(Snowflake id);
|
Role *GetRole(Snowflake id);
|
||||||
@@ -26,7 +35,7 @@ public:
|
|||||||
GuildMember *GetGuildMemberData(Snowflake guild_id, Snowflake user_id);
|
GuildMember *GetGuildMemberData(Snowflake guild_id, Snowflake user_id);
|
||||||
PermissionOverwrite *GetPermissionOverwrite(Snowflake channel_id, Snowflake id);
|
PermissionOverwrite *GetPermissionOverwrite(Snowflake channel_id, Snowflake id);
|
||||||
Emoji *GetEmoji(Snowflake id);
|
Emoji *GetEmoji(Snowflake id);
|
||||||
const User *GetUser(Snowflake id) const;
|
std::optional<User> GetUser(Snowflake id) const;
|
||||||
const Channel *GetChannel(Snowflake id) const;
|
const Channel *GetChannel(Snowflake id) const;
|
||||||
const Guild *GetGuild(Snowflake id) const;
|
const Guild *GetGuild(Snowflake id) const;
|
||||||
const Role *GetRole(Snowflake id) const;
|
const Role *GetRole(Snowflake id) const;
|
||||||
@@ -53,6 +62,9 @@ public:
|
|||||||
|
|
||||||
void ClearAll();
|
void ClearAll();
|
||||||
|
|
||||||
|
void BeginTransaction();
|
||||||
|
void EndTransaction();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
users_type m_users;
|
users_type m_users;
|
||||||
channels_type m_channels;
|
channels_type m_channels;
|
||||||
@@ -62,4 +74,50 @@ private:
|
|||||||
members_type m_members;
|
members_type m_members;
|
||||||
permission_overwrites_type m_permissions;
|
permission_overwrites_type m_permissions;
|
||||||
emojis_type m_emojis;
|
emojis_type m_emojis;
|
||||||
|
|
||||||
|
bool CreateTables();
|
||||||
|
bool CreateStatements();
|
||||||
|
void Cleanup();
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
void Bind(sqlite3_stmt *stmt, int index, const std::optional<T> &opt) const;
|
||||||
|
void Bind(sqlite3_stmt *stmt, int index, int num) const;
|
||||||
|
void Bind(sqlite3_stmt *stmt, int index, uint64_t num) const;
|
||||||
|
void Bind(sqlite3_stmt *stmt, int index, const std::string &str) const;
|
||||||
|
void Bind(sqlite3_stmt *stmt, int index, bool val) const;
|
||||||
|
bool RunInsert(sqlite3_stmt *stmt);
|
||||||
|
bool FetchOne(sqlite3_stmt *stmt) const;
|
||||||
|
template<typename T>
|
||||||
|
void Get(sqlite3_stmt *stmt, int index, std::optional<T> &out) const;
|
||||||
|
void Get(sqlite3_stmt *stmt, int index, int &out) const;
|
||||||
|
void Get(sqlite3_stmt *stmt, int index, uint64_t &out) const;
|
||||||
|
void Get(sqlite3_stmt *stmt, int index, std::string &out) const;
|
||||||
|
void Get(sqlite3_stmt *stmt, int index, bool &out) const;
|
||||||
|
void Get(sqlite3_stmt *stmt, int index, Snowflake &out) const;
|
||||||
|
void Reset(sqlite3_stmt *stmt) const;
|
||||||
|
|
||||||
|
std::filesystem::path m_db_path;
|
||||||
|
mutable sqlite3 *m_db;
|
||||||
|
mutable int m_db_err;
|
||||||
|
mutable sqlite3_stmt *m_set_user_stmt;
|
||||||
|
mutable sqlite3_stmt *m_get_user_stmt;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
inline void Store::Bind(sqlite3_stmt *stmt, int index, const std::optional<T> &opt) const {
|
||||||
|
if (opt.has_value())
|
||||||
|
Bind(stmt, index, *opt);
|
||||||
|
else
|
||||||
|
sqlite3_bind_null(stmt, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
inline void Store::Get(sqlite3_stmt *stmt, int index, std::optional<T> &out) const {
|
||||||
|
if (sqlite3_column_type(stmt, index) == SQLITE_NULL)
|
||||||
|
out = std::nullopt;
|
||||||
|
else {
|
||||||
|
T v;
|
||||||
|
Get(stmt, index, v);
|
||||||
|
out = std::optional<T>(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -34,20 +34,20 @@ void from_json(const nlohmann::json &j, User &m) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void User::update_from_json(const nlohmann::json &j, User &m) {
|
void User::update_from_json(const nlohmann::json &j, User &m) {
|
||||||
JS_ON("username", m.Username);
|
JS_RD("username", m.Username);
|
||||||
JS_ON("discriminator", m.Discriminator);
|
JS_RD("discriminator", m.Discriminator);
|
||||||
JS_ON("avatar", m.Avatar);
|
JS_RD("avatar", m.Avatar);
|
||||||
JS_ON("bot", m.IsBot);
|
JS_RD("bot", m.IsBot);
|
||||||
JS_ON("system", m.IsSystem);
|
JS_RD("system", m.IsSystem);
|
||||||
JS_ON("mfa_enabled", m.IsMFAEnabled);
|
JS_RD("mfa_enabled", m.IsMFAEnabled);
|
||||||
JS_ON("locale", m.Locale);
|
JS_RD("locale", m.Locale);
|
||||||
JS_ON("verified", m.IsVerified);
|
JS_RD("verified", m.IsVerified);
|
||||||
JS_ON("email", m.Email);
|
JS_RD("email", m.Email);
|
||||||
JS_ON("flags", m.Flags);
|
JS_RD("flags", m.Flags);
|
||||||
JS_ON("premium_type", m.PremiumType);
|
JS_RD("premium_type", m.PremiumType);
|
||||||
JS_ON("public_flags", m.PublicFlags);
|
JS_RD("public_flags", m.PublicFlags);
|
||||||
JS_ON("desktop", m.IsDesktop);
|
JS_RD("desktop", m.IsDesktop);
|
||||||
JS_ON("mobile", m.IsMobile);
|
JS_RD("mobile", m.IsMobile);
|
||||||
JS_ON("nsfw_allowed", m.IsNSFWAllowed);
|
JS_RD("nsfw_allowed", m.IsNSFWAllowed);
|
||||||
JS_ON("phone", m.Phone);
|
JS_RD("phone", m.Phone);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,19 +4,19 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
struct User {
|
struct User {
|
||||||
Snowflake ID; //
|
Snowflake ID;
|
||||||
std::string Username; //
|
std::string Username;
|
||||||
std::string Discriminator; //
|
std::string Discriminator;
|
||||||
std::string Avatar; // null
|
std::string Avatar; // null
|
||||||
bool IsBot = false; // opt
|
std::optional<bool> IsBot;
|
||||||
bool IsSystem = false; // opt
|
std::optional<bool> IsSystem;
|
||||||
bool IsMFAEnabled = false; // opt
|
std::optional<bool> IsMFAEnabled;
|
||||||
std::string Locale; // opt
|
std::optional<std::string> Locale;
|
||||||
bool IsVerified = false; // opt
|
std::optional<bool> IsVerified;
|
||||||
std::string Email; // opt, null
|
std::optional<std::string> Email; // null
|
||||||
int Flags = 0; // opt
|
std::optional<int> Flags;
|
||||||
int PremiumType = 0; // opt, null (docs wrong)
|
std::optional<int> PremiumType; // null
|
||||||
int PublicFlags = 0; // opt
|
std::optional<int> PublicFlags;
|
||||||
|
|
||||||
// undocumented (opt)
|
// undocumented (opt)
|
||||||
bool IsDesktop = false; //
|
bool IsDesktop = false; //
|
||||||
|
|||||||
10
util.hpp
10
util.hpp
@@ -11,6 +11,16 @@
|
|||||||
#include <regex>
|
#include <regex>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <condition_variable>
|
#include <condition_variable>
|
||||||
|
#include <optional>
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
|
namespace util {
|
||||||
|
template<typename T>
|
||||||
|
struct is_optional : ::std::false_type {};
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
struct is_optional<::std::optional<T>> : ::std::true_type {};
|
||||||
|
}
|
||||||
|
|
||||||
class Semaphore {
|
class Semaphore {
|
||||||
public:
|
public:
|
||||||
|
|||||||
Reference in New Issue
Block a user