Mercurial > irccd
changeset 224:01fc0e526a9d
Irccd: cleanup server stuff, Closes T1
author | David Demelier <markand@malikania.fr> |
---|---|
date | Thu, 14 Jul 2016 10:47:20 +0200 |
parents | 2dc0355c3417 |
children | 277d92d29a3d |
files | extern/json/json.hpp lib/irccd/cmd-server-connect.cpp lib/irccd/cmd-server-info.cpp lib/irccd/config.cpp lib/irccd/mod-server.cpp lib/irccd/server-state-connected.cpp lib/irccd/server-state-connected.hpp lib/irccd/server-state-connecting.cpp lib/irccd/server-state-connecting.hpp lib/irccd/server-state-disconnected.cpp lib/irccd/server-state-disconnected.hpp lib/irccd/server-state.hpp lib/irccd/server.cpp lib/irccd/server.hpp lib/irccd/service-server.cpp lib/irccd/util.hpp |
diffstat | 16 files changed, 595 insertions(+), 458 deletions(-) [+] |
line wrap: on
line diff
--- a/extern/json/json.hpp Tue Jul 12 12:50:44 2016 +0200 +++ b/extern/json/json.hpp Thu Jul 14 10:47:20 2016 +0200 @@ -5788,7 +5788,6 @@ /// @} - private: /////////////////////////// // convenience functions // /////////////////////////// @@ -5815,6 +5814,7 @@ } } +private: /*! @brief calculates the extra space to escape a JSON string
--- a/lib/irccd/cmd-server-connect.cpp Tue Jul 12 12:50:44 2016 +0200 +++ b/lib/irccd/cmd-server-connect.cpp Thu Jul 14 10:47:20 2016 +0200 @@ -34,91 +34,6 @@ namespace command { -namespace { - -std::string readInfoName(const json &object) -{ - std::string name = object["name"]; - - if (!util::isIdentifierValid(name)) - throw PropertyError("name", "invalid identifier"); - - return name; -} - -std::string readInfoHost(const json &object) -{ - std::string host = object["host"]; - - if (host.empty()) - throw PropertyError("host", "empty hostname"); - - return host; -} - -std::uint16_t readInfoPort(const json &object) -{ - json::const_iterator it = object.find("port"); - - if (it == object.end()) - return 6667; - - if (!it->is_number()) - throw InvalidPropertyError("port", json::value_t::number_unsigned, it->type()); - if (!util::isBound(it->get<int>(), 0, UINT16_MAX)) - throw PropertyRangeError("port", 0, UINT16_MAX, it->get<int>()); - - return static_cast<std::uint16_t>(it->get<int>()); -} - -ServerInfo readInfo(const json &object) -{ - ServerInfo info; - - info.host = readInfoHost(object); - info.port = readInfoPort(object); - - json::const_iterator it; - - if ((it = object.find("ssl")) != object.end() && it->is_boolean() && *it) - info.flags |= ServerInfo::Ssl; - if ((it = object.find("sslVerify")) != object.end() && it->is_boolean() && *it) - info.flags |= ServerInfo::SslVerify; - - return info; -} - -void readIdentity(Server &server, const json &object) -{ - json::const_iterator it; - - if ((it = object.find("nickname")) != object.end() && it->is_string()) - server.setNickname(*it); - if ((it = object.find("realname")) != object.end() && it->is_string()) - server.setRealname(*it); - if ((it = object.find("username")) != object.end() && it->is_string()) - server.setUsername(*it); - if ((it = object.find("ctcpVersion")) != object.end() && it->is_string()) - server.setCtcpVersion(*it); -} - -ServerSettings readSettings(const json &object) -{ - ServerSettings settings; - json::const_iterator it; - - if ((it = object.find("commandChar")) != object.end() && it->is_string()) - settings.command = *it; - if ((it = object.find("reconnectTries")) != object.end() && it->is_number_integer()) - settings.reconnectTries = *it; - if ((it = object.find("reconnectTimeout")) != object.end() && it->is_number_integer()) - settings.reconnectDelay = *it; - - return settings; -} - -} // !namespace - ServerConnect::ServerConnect() : Command("server-connect", "Server") { @@ -160,9 +75,7 @@ json ServerConnect::exec(Irccd &irccd, const json &request) const { - auto server = std::make_shared<Server>(readInfoName(request), readInfo(request), readSettings(request)); - - readIdentity(*server, request); + auto server = Server::fromJson(request); if (irccd.serverService().has(server->name())) throw std::invalid_argument("server '{}' already exists"_format(server->name()));
--- a/lib/irccd/cmd-server-info.cpp Tue Jul 12 12:50:44 2016 +0200 +++ b/lib/irccd/cmd-server-info.cpp Thu Jul 14 10:47:20 2016 +0200 @@ -63,24 +63,24 @@ // General stuff. response.push_back({"name", server->name()}); - response.push_back({"host", server->info().host}); - response.push_back({"port", server->info().port}); + response.push_back({"host", server->host()}); + response.push_back({"port", server->port()}); response.push_back({"nickname", server->nickname()}); response.push_back({"username", server->username()}); response.push_back({"realname", server->realname()}); // Optional stuff. - if (server->info().flags & irccd::ServerInfo::Ipv6) + if (server->flags() & Server::Ipv6) response.push_back({"ipv6", true}); - if (server->info().flags & irccd::ServerInfo::Ssl) + if (server->flags() & Server::Ssl) response.push_back({"ssl", true}); - if (server->info().flags & irccd::ServerInfo::SslVerify) + if (server->flags() & Server::SslVerify) response.push_back({"sslVerify", true}); // Channel list. auto channels = nlohmann::json::array(); - for (const auto &c : server->settings().channels) + for (const auto &c : server->channels()) channels.push_back(c.name); response.push_back({"channels", std::move(channels)});
--- a/lib/irccd/config.cpp Tue Jul 12 12:50:44 2016 +0200 +++ b/lib/irccd/config.cpp Thu Jul 14 10:47:20 2016 +0200 @@ -270,10 +270,6 @@ { assert(sc.key() == "server"); - std::string name; - ServerInfo info; - ServerSettings settings; - // Name. ini::Section::const_iterator it; @@ -282,49 +278,40 @@ else if (!util::isIdentifierValid(it->value())) throw std::invalid_argument("server: invalid identifier: {}"_format(it->value())); - name = it->value(); + auto server = std::make_shared<Server>(it->value()); // Host if ((it = sc.find("host")) == sc.end()) - throw std::invalid_argument("server {}: missing host"_format(name)); - - info.host = it->value(); + throw std::invalid_argument("server {}: missing host"_format(server->name())); - // Optional port - if ((it = sc.find("port")) != sc.end()) { - try { - info.port = util::toNumber<std::uint16_t>(it->value()); - } catch (const std::exception &) { - throw std::invalid_argument("server {}: invalid number for {}: {}"_format(name, it->key(), it->value())); - } - } + server->setHost(it->value()); // Optional password if ((it = sc.find("password")) != sc.end()) - info.password = it->value(); + server->setPassword(it->value()); // Optional flags if ((it = sc.find("ipv6")) != sc.end() && util::isBoolean(it->value())) - info.flags |= ServerInfo::Ipv6; - if ((it = sc.find("ssl")) != sc.end()) { - if (util::isBoolean(it->value())) - info.flags |= ServerInfo::Ssl; - } - if ((it = sc.find("ssl-verify")) != sc.end()) { - if (util::isBoolean(it->value())) - info.flags |= ServerInfo::SslVerify; - } + server->setFlags(server->flags() | Server::Ipv6); + if ((it = sc.find("ssl")) != sc.end() && util::isBoolean(it->value())) + server->setFlags(server->flags() | Server::Ssl); + if ((it = sc.find("ssl-verify")) != sc.end() && util::isBoolean(it->value())) + server->setFlags(server->flags() | Server::SslVerify); + + // Optional identity + if ((it = sc.find("identity")) != sc.end()) + config.loadServerIdentity(*server, it->value()); // Options if ((it = sc.find("auto-rejoin")) != sc.end() && util::isBoolean(it->value())) - settings.flags |= ServerSettings::AutoRejoin; + server->setFlags(server->flags() | Server::AutoRejoin); if ((it = sc.find("join-invite")) != sc.end() && util::isBoolean(it->value())) - settings.flags |= ServerSettings::JoinInvite; + server->setFlags(server->flags() | Server::JoinInvite); // Channels if ((it = sc.find("channels")) != sc.end()) { for (const std::string &s : *it) { - ServerChannel channel; + Channel channel; if (auto pos = s.find(":") != std::string::npos) { channel.name = s.substr(0, pos); @@ -332,30 +319,28 @@ } else channel.name = s; - settings.channels.push_back(std::move(channel)); + //server.channels.push_back(std::move(channel)); + //server->join() + server->join(channel.name, channel.password); } } if ((it = sc.find("command-char")) != sc.end()) - settings.command = it->value(); + server->setCommandCharacter(it->value()); // Reconnect and ping timeout try { + if ((it = sc.find("port")) != sc.end()) + server->setPort(util::toNumber<std::uint16_t>(it->value())); if ((it = sc.find("reconnect-tries")) != sc.end()) - settings.reconnectTries = util::toNumber<std::int8_t>(it->value()); + server->setReconnectTries(util::toNumber<std::int8_t>(it->value())); if ((it = sc.find("reconnect-timeout")) != sc.end()) - settings.reconnectDelay = util::toNumber<std::uint16_t>(it->value()); + server->setReconnectDelay(util::toNumber<std::uint16_t>(it->value())); if ((it = sc.find("ping-timeout")) != sc.end()) - settings.pingTimeout = util::toNumber<std::uint16_t>(it->value()); + server->setPingTimeout(util::toNumber<std::uint16_t>(it->value())); } catch (const std::exception &) { - log::warning("server {}: invalid number for {}: {}"_format(name, it->key(), it->value())); + log::warning("server {}: invalid number for {}: {}"_format(server->name(), it->key(), it->value())); } - auto server = std::make_shared<Server>(std::move(name), std::move(info), std::move(settings)); - - // Optional identity - if ((it = sc.find("identity")) != sc.end()) - config.loadServerIdentity(*server, it->value()); - return server; }
--- a/lib/irccd/mod-server.cpp Tue Jul 12 12:50:44 2016 +0200 +++ b/lib/irccd/mod-server.cpp Thu Jul 14 10:47:20 2016 +0200 @@ -49,109 +49,6 @@ return *static_cast<std::shared_ptr<Server> *>(ptr); } -std::string readName(duk_context *ctx) -{ - duk_get_prop_string(ctx, 0, "name"); - auto name = dukx_get_std_string(ctx, -1); - duk_pop(ctx); - - if (!util::isIdentifierValid(name)) - duk_error(ctx, DUK_ERR_ERROR, "invalid 'name' property"); - - return name; -} - -ServerInfo readInfo(duk_context *ctx) -{ - ServerInfo info; - - // 'host' property. - duk_get_prop_string(ctx, 0, "host"); - info.host = duk_is_string(ctx, -1) ? dukx_get_std_string(ctx, -1) : info.host; - duk_pop(ctx); - - // 'port' property. - duk_get_prop_string(ctx, 0, "port"); - info.port = duk_is_number(ctx, -1) ? duk_get_int(ctx, -1) : info.port; - duk_pop(ctx); - - // 'password' property. - duk_get_prop_string(ctx, 0, "password"); - info.password = duk_is_string(ctx, -1) ? dukx_get_std_string(ctx, -1) : info.password; - duk_pop(ctx); - - // 'ipv6' property. - duk_get_prop_string(ctx, 0, "ipv6"); - if (duk_get_boolean(ctx, -1)) - info.flags |= ServerInfo::Ipv6; - duk_pop(ctx); - - return info; -} - -void readIdentity(Server &server, duk_context *ctx) -{ - // 'nickname' property. - duk_get_prop_string(ctx, 0, "nickname"); - if (duk_is_string(ctx, -1)) - server.setNickname(dukx_get_std_string(ctx, -1)); - duk_pop(ctx); - - // 'username' property. - duk_get_prop_string(ctx, 0, "username"); - if (duk_is_string(ctx, -1)) - server.setUsername(dukx_get_std_string(ctx, -1)); - duk_pop(ctx); - - // 'realname' property. - duk_get_prop_string(ctx, 0, "realname"); - if (duk_is_string(ctx, -1)) - server.setRealname(dukx_get_std_string(ctx, -1)); - duk_pop(ctx); - - // 'ctcpversion' property. - duk_get_prop_string(ctx, 0, "version"); - if (duk_is_string(ctx, -1)) - server.setCtcpVersion(dukx_get_std_string(ctx, -1)); - duk_pop(ctx); -} - -ServerSettings readSettings(duk_context *ctx) -{ - ServerSettings settings; - - // 'channels' property. - duk_get_prop_string(ctx, 0, "channels"); - settings.channels = dukx_get_array(ctx, -1, [] (auto ctx, auto) { - return Server::splitChannel(dukx_get_std_string(ctx, -1)); - }); - duk_pop(ctx); - - // 'recoTries' property. - duk_get_prop_string(ctx, 0, "recoTries"); - settings.reconnectTries = duk_is_number(ctx, -1) ? duk_get_int(ctx, -1) : settings.reconnectTries; - duk_pop(ctx); - - // 'recoTimeout' property. - duk_get_prop_string(ctx, 0, "recoTimeout"); - settings.reconnectDelay = duk_is_number(ctx, -1) ? duk_get_int(ctx, -1) : settings.reconnectDelay; - duk_pop(ctx); - - // 'joinInvite' property. - duk_get_prop_string(ctx, 0, "joinInvite"); - if (duk_get_boolean(ctx, -1)) - settings.flags |= ServerSettings::JoinInvite; - duk_pop(ctx); - - // 'autoRejoin' property. - duk_get_prop_string(ctx, 0, "autoRejoin"); - if (duk_get_boolean(ctx, -1)) - settings.flags |= ServerSettings::AutoRejoin; - duk_pop(ctx); - - return settings; -} - /* * Method: Server.cmode(channel, mode) * ------------------------------------------------------------------ @@ -206,15 +103,15 @@ duk_push_object(ctx); dukx_push_std_string(ctx, server->name()); duk_put_prop_string(ctx, -2, "name"); - dukx_push_std_string(ctx, server->info().host); + dukx_push_std_string(ctx, server->host()); duk_put_prop_string(ctx, -2, "host"); - duk_push_int(ctx, server->info().port); + duk_push_int(ctx, server->port()); duk_put_prop_string(ctx, -2, "port"); - duk_push_boolean(ctx, server->info().flags & ServerInfo::Ssl); + duk_push_boolean(ctx, server->flags() & Server::Ssl); duk_put_prop_string(ctx, -2, "ssl"); - duk_push_boolean(ctx, server->info().flags & ServerInfo::SslVerify); + duk_push_boolean(ctx, server->flags() & Server::SslVerify); duk_put_prop_string(ctx, -2, "sslVerify"); - dukx_push_std_string(ctx, server->settings().command); + dukx_push_std_string(ctx, server->commandCharacter()); duk_put_prop_string(ctx, -2, "commandChar"); dukx_push_std_string(ctx, server->realname()); duk_put_prop_string(ctx, -2, "realname"); @@ -222,7 +119,7 @@ duk_put_prop_string(ctx, -2, "nickname"); dukx_push_std_string(ctx, server->username()); duk_put_prop_string(ctx, -2, "username"); - dukx_push_array(ctx, server->settings().channels, [] (auto ctx, auto channel) { + dukx_push_array(ctx, server->channels(), [] (auto ctx, auto channel) { dukx_push_std_string(ctx, channel.name); }); duk_put_prop_string(ctx, -2, "channels"); @@ -490,10 +387,11 @@ if (!duk_is_constructor_call(ctx)) return 0; + duk_check_type(ctx, 0, DUK_TYPE_OBJECT); + try { - auto s = std::make_shared<Server>(readName(ctx), readInfo(ctx), readSettings(ctx)); - - readIdentity(*s, ctx); + auto json = duk_json_encode(ctx, 0); + auto s = Server::fromJson(nlohmann::json::parse(json)); duk_push_this(ctx); duk_push_pointer(ctx, new std::shared_ptr<Server>(std::move(s)));
--- a/lib/irccd/server-state-connected.cpp Tue Jul 12 12:50:44 2016 +0200 +++ b/lib/irccd/server-state-connected.cpp Thu Jul 14 10:47:20 2016 +0200 @@ -27,33 +27,26 @@ namespace irccd { -namespace state { - -void Connected::prepare(Server &server, fd_set &setinput, fd_set &setoutput, net::Handle &maxfd) +void ConnectedState::prepare(Server &server, fd_set &setinput, fd_set &setoutput, net::Handle &maxfd) { - const ServerSettings &settings = server.settings(); - if (!irc_is_connected(server.session())) { log::warning() << "server " << server.name() << ": disconnected" << std::endl; - if (settings.reconnectDelay > 0) - log::warning("server {}: retrying in {} seconds"_format(server.name(), settings.reconnectDelay)); + if (server.reconnectDelay() > 0) + log::warning("server {}: retrying in {} seconds"_format(server.name(), server.reconnectDelay())); - server.next(std::make_unique<state::Disconnected>()); - } else if (server.cache().pingTimer.elapsed() >= settings.pingTimeout * 1000) { + server.next(std::make_unique<DisconnectedState>()); + } else if (server.cache().pingTimer.elapsed() >= server.pingTimeout() * 1000) { log::warning() << "server " << server.name() << ": ping timeout after " << (server.cache().pingTimer.elapsed() / 1000) << " seconds" << std::endl; - server.next(std::make_unique<state::Disconnected>()); - } else { + server.next(std::make_unique<DisconnectedState>()); + } else irc_add_select_descriptors(server.session(), &setinput, &setoutput, reinterpret_cast<int *>(&maxfd)); - } } -std::string Connected::ident() const +std::string ConnectedState::ident() const { return "Connected"; } -} // !state - } // !irccd
--- a/lib/irccd/server-state-connected.hpp Tue Jul 12 12:50:44 2016 +0200 +++ b/lib/irccd/server-state-connected.hpp Thu Jul 14 10:47:20 2016 +0200 @@ -28,13 +28,11 @@ namespace irccd { -namespace state { - /** * \brief Connected state. * \ingroup states */ -class Connected : public ServerState { +class ConnectedState : public State { public: /** * \copydoc ServerState::prepare @@ -47,8 +45,6 @@ IRCCD_EXPORT std::string ident() const override; }; -} // !state - } // !irccd #endif // !IRCCD_SERVER_STATE_CONNECTED_HPP
--- a/lib/irccd/server-state-connecting.cpp Tue Jul 12 12:50:44 2016 +0200 +++ b/lib/irccd/server-state-connecting.cpp Thu Jul 14 10:47:20 2016 +0200 @@ -36,32 +36,29 @@ namespace irccd { -namespace state { - namespace { bool connect(Server &server) { - const ServerInfo &info = server.info(); - const char *password = info.password.empty() ? nullptr : info.password.c_str(); - std::string host = info.host; + const char *password = server.password().empty() ? nullptr : server.password().c_str(); + std::string host = server.host(); int code; // libircclient requires # for SSL connection. #if defined(WITH_SSL) - if (info.flags & ServerInfo::Ssl) + if (server.flags() & Server::Ssl) host.insert(0, 1, '#'); - if (!(info.flags & ServerInfo::SslVerify)) + if (!(server.flags() & Server::SslVerify)) irc_option_set(server.session(), LIBIRC_OPTION_SSL_NO_VERIFY); #endif - if (info.flags & ServerInfo::Ipv6) { - code = irc_connect6(server.session(), host.c_str(), info.port, password, + if (server.flags() & Server::Ipv6) { + code = irc_connect6(server.session(), host.c_str(), server.port(), password, server.nickname().c_str(), server.username().c_str(), server.realname().c_str()); } else { - code = irc_connect(server.session(), host.c_str(), info.port, password, + code = irc_connect(server.session(), host.c_str(), server.port(), password, server.nickname().c_str(), server.username().c_str(), server.realname().c_str()); @@ -72,7 +69,7 @@ } // !namespace -void Connecting::prepare(Server &server, fd_set &setinput, fd_set &setoutput, net::Handle &maxfd) +void ConnectingState::prepare(Server &server, fd_set &setinput, fd_set &setoutput, net::Handle &maxfd) { /* * The connect function will either fail if the hostname wasn't resolved or if any of the internal functions @@ -85,22 +82,18 @@ * * Otherwise, the libircclient event_connect will change the state. */ - const ServerInfo &info = server.info(); - if (m_started) { - const ServerSettings &settings = server.settings(); - - if (m_timer.elapsed() > static_cast<unsigned>(settings.reconnectDelay * 1000)) { + if (m_timer.elapsed() > static_cast<unsigned>(server.reconnectDelay() * 1000)) { log::warning() << "server " << server.name() << ": timeout while connecting" << std::endl; - server.next(std::make_unique<state::Disconnected>()); + server.next(std::make_unique<DisconnectedState>()); } else if (!irc_is_connected(server.session())) { log::warning() << "server " << server.name() << ": error while connecting: "; log::warning() << irc_strerror(irc_errno(server.session())) << std::endl; - if (settings.reconnectTries != 0) - log::warning("server {}: retrying in {} seconds"_format(server.name(), settings.reconnectDelay)); + if (server.reconnectTries() != 0) + log::warning("server {}: retrying in {} seconds"_format(server.name(), server.reconnectDelay())); - server.next(std::make_unique<state::Disconnected>()); + server.next(std::make_unique<DisconnectedState>()); } else irc_add_select_descriptors(server.session(), &setinput, &setoutput, reinterpret_cast<int *>(&maxfd)); } else { @@ -112,12 +105,12 @@ #if !defined(IRCCD_SYSTEM_WINDOWS) (void)res_init(); #endif - log::info("server {}: trying to connect to {}, port {}"_format(server.name(), info.host, info.port)); + log::info("server {}: trying to connect to {}, port {}"_format(server.name(), server.host(), server.port())); if (!connect(server)) { log::warning() << "server " << server.name() << ": disconnected while connecting: "; log::warning() << irc_strerror(irc_errno(server.session())) << std::endl; - server.next(std::make_unique<state::Disconnected>()); + server.next(std::make_unique<DisconnectedState>()); } else { m_started = true; @@ -127,11 +120,9 @@ } } -std::string Connecting::ident() const +std::string ConnectingState::ident() const { return "Connecting"; } -} // !state - } // !irccd
--- a/lib/irccd/server-state-connecting.hpp Tue Jul 12 12:50:44 2016 +0200 +++ b/lib/irccd/server-state-connecting.hpp Thu Jul 14 10:47:20 2016 +0200 @@ -29,13 +29,11 @@ namespace irccd { -namespace state { - /** * \brief Connecting state. * \ingroup states */ -class Connecting : public ServerState { +class ConnectingState : public State { private: bool m_started{false}; ElapsedTimer m_timer; @@ -52,8 +50,6 @@ IRCCD_EXPORT std::string ident() const override; }; -} // !state - } // !irccd #endif // !IRCCD_SERVER_STATE_CONNECTING_HPP
--- a/lib/irccd/server-state-disconnected.cpp Tue Jul 12 12:50:44 2016 +0200 +++ b/lib/irccd/server-state-disconnected.cpp Thu Jul 14 10:47:20 2016 +0200 @@ -23,34 +23,29 @@ namespace irccd { -namespace state { - -void Disconnected::prepare(Server &server, fd_set &, fd_set &, net::Handle &) +void DisconnectedState::prepare(Server &server, fd_set &, fd_set &, net::Handle &) { - ServerSettings &settings = server.settings(); - ServerCache &cache = server.cache(); + auto &cache = server.cache(); - if (settings.reconnectTries == 0) { + if (server.reconnectTries() == 0) { log::warning() << "server " << server.name() << ": reconnection disabled, skipping" << std::endl; server.onDie(); - } else if (settings.reconnectTries > 0 && cache.reconnectCurrent > settings.reconnectTries) { + } else if (server.reconnectTries() > 0 && cache.reconnectCurrent > server.reconnectTries()) { log::warning() << "server " << server.name() << ": giving up" << std::endl; server.onDie(); } else { - if (m_timer.elapsed() > static_cast<unsigned>(settings.reconnectDelay * 1000)) { + if (m_timer.elapsed() > static_cast<unsigned>(server.reconnectDelay() * 1000)) { irc_disconnect(server.session()); server.cache().reconnectCurrent ++; - server.next(std::make_unique<state::Connecting>()); + server.next(std::make_unique<ConnectingState>()); } } } -std::string Disconnected::ident() const +std::string DisconnectedState::ident() const { return "Disconnected"; } -} // !state - } // !irccd
--- a/lib/irccd/server-state-disconnected.hpp Tue Jul 12 12:50:44 2016 +0200 +++ b/lib/irccd/server-state-disconnected.hpp Thu Jul 14 10:47:20 2016 +0200 @@ -29,13 +29,11 @@ namespace irccd { -namespace state { - /** * \brief Disconnected state. * \ingroup states */ -class Disconnected : public ServerState { +class DisconnectedState : public State { private: ElapsedTimer m_timer; @@ -51,8 +49,6 @@ IRCCD_EXPORT std::string ident() const override; }; -} // !state - } // !irccd #endif // !IRCCD_SERVER_STATE_DISCONNECTED_HPP
--- a/lib/irccd/server-state.hpp Tue Jul 12 12:50:44 2016 +0200 +++ b/lib/irccd/server-state.hpp Thu Jul 14 10:47:20 2016 +0200 @@ -38,26 +38,20 @@ class Server; /** - * \brief Namespace for server states. - */ -namespace state { -} - -/** * \class ServerState * \brief Server current state. */ -class ServerState { +class State { public: /** * Default constructor. */ - ServerState() = default; + State() = default; /** * Virtual default destructor. */ - virtual ~ServerState() = default; + virtual ~State() = default; /** * Prepare the state.
--- a/lib/irccd/server.cpp Tue Jul 12 12:50:44 2016 +0200 +++ b/lib/irccd/server.cpp Thu Jul 14 10:47:20 2016 +0200 @@ -55,12 +55,12 @@ * Remove the user prefix only if it is present in the mode table, for example removes @ from @irccd if and only if * @ is a character mode (e.g. operator). */ -std::string cleanPrefix(const ServerInfo &info, std::string nickname) +std::string cleanPrefix(const std::map<ChannelMode, char> &modes, std::string nickname) { if (nickname.length() == 0) return nickname; - for (const auto &pair : info.modes) + for (const auto &pair : modes) if (nickname[0] == pair.second) nickname.erase(0, 1); @@ -73,11 +73,11 @@ * * Read modes from the IRC event numeric. */ -std::map<ServerChanMode, char> extractPrefixes(const std::string &line) +std::map<ChannelMode, char> extractPrefixes(const std::string &line) { std::pair<char, char> table[16]; std::string buf = line.substr(7); - std::map<ServerChanMode, char> modes; + std::map<ChannelMode, char> modes; for (int i = 0; i < 16; ++i) table[i] = std::make_pair(-1, -1); @@ -101,7 +101,7 @@ // Put these as a map of mode to prefix. for (int i = 0; i < 16; ++i) { - auto key = static_cast<ServerChanMode>(table[i].first); + auto key = static_cast<ChannelMode>(table[i].first); auto value = table[i].second; modes.emplace(key, value); @@ -121,11 +121,11 @@ m_cache.pingTimer.reset(); // Don't forget to change state and notify. - next(std::make_unique<state::Connected>()); + next(std::make_unique<ConnectedState>()); onConnect(ConnectEvent{shared_from_this()}); // Auto join listed channels. - for (const ServerChannel &channel : m_settings.channels) { + for (const Channel &channel : m_channels) { log::info() << "server " << m_name << ": auto joining " << channel.name << std::endl; join(channel.name, channel.password); } @@ -154,7 +154,7 @@ void Server::handleInvite(const char *orig, const char **params) noexcept { // If joininvite is set, join the channel. - if ((m_settings.flags & ServerSettings::JoinInvite) && isSelf(strify(params[0]))) + if ((m_flags & JoinInvite) && isSelf(strify(params[0]))) join(strify(params[1])); /* @@ -173,7 +173,7 @@ void Server::handleKick(const char *orig, const char **params) noexcept { // Rejoin the channel if the option has been set and I was kicked. - if ((m_settings.flags & ServerSettings::AutoRejoin) && isSelf(strify(params[1]))) + if ((m_flags & AutoRejoin) && isSelf(strify(params[1]))) join(strify(params[0])); onKick(KickEvent{shared_from_this(), strify(orig), strify(params[0]), strify(params[1]), strify(params[2])}); @@ -219,7 +219,7 @@ // The listing may add some prefixes, remove them if needed. for (std::string u : users) - m_cache.namesMap[params[2]].insert(cleanPrefix(m_info, u)); + m_cache.namesMap[params[2]].insert(cleanPrefix(m_modes, u)); } else if (event == LIBIRC_RFC_RPL_ENDOFNAMES) { /* * Called when end of name listing has finished on a channel. @@ -252,7 +252,7 @@ if (c < 6 || !params[1] || !params[2] || !params[3] || !params[5]) return; - ServerWhois info; + Whois info; info.nick = strify(params[1]); info.user = strify(params[2]); @@ -277,7 +277,7 @@ // Clean their prefixes. for (auto &s : channels) - s = cleanPrefix(m_info, s); + s = cleanPrefix(m_modes, s); it->second.channels = std::move(channels); } @@ -302,7 +302,7 @@ */ for (unsigned int i = 0; i < c; ++i) { if (strncmp(params[i], "PREFIX", 6) == 0) { - m_info.modes = extractPrefixes(params[i]); + m_modes = extractPrefixes(params[i]); break; } } @@ -333,22 +333,48 @@ onTopic(TopicEvent{shared_from_this(), strify(orig), strify(params[0]), strify(params[1])}); } -ServerChannel Server::splitChannel(const std::string &value) +std::shared_ptr<Server> Server::fromJson(const nlohmann::json &object) +{ + auto server = std::make_shared<Server>(util::json::requireIdentifier(object, "name")); + + server->setHost(util::json::requireString(object, "host")); + server->setPassword(util::json::getString(object, "password")); + server->setNickname(util::json::getString(object, "nickname", server->nickname())); + server->setRealname(util::json::getString(object, "realname", server->realname())); + server->setUsername(util::json::getString(object, "username", server->username())); + server->setCtcpVersion(util::json::getString(object, "ctcpVersion", server->ctcpVersion())); + server->setCommandCharacter(util::json::getString(object, "commandChar", server->commandCharacter())); + + if (object.find("port") != object.end()) + server->setPort(util::json::getUintRange<std::uint16_t>(object, "port")); + if (util::json::getBool(object, "ipv6")) + server->setFlags(server->flags() | Server::Ipv6); + if (util::json::getBool(object, "ssl")) + server->setFlags(server->flags() | Server::Ssl); + if (util::json::getBool(object, "sslVerify")) + server->setFlags(server->flags() | Server::SslVerify); + if (util::json::getBool(object, "autoRejoin")) + server->setFlags(server->flags() | Server::AutoRejoin); + if (util::json::getBool(object, "joinInvite")) + server->setFlags(server->flags() | Server::JoinInvite); + + return server; +} + +Channel Server::splitChannel(const std::string &value) { auto pos = value.find(':'); if (pos != std::string::npos) - return ServerChannel{value.substr(0, pos), value.substr(pos + 1)}; + return { value.substr(0, pos), value.substr(pos + 1) }; - return ServerChannel{value, ""}; + return { value, "" }; } -Server::Server(std::string name, ServerInfo info, ServerSettings settings) +Server::Server(std::string name) : m_name(std::move(name)) - , m_info(std::move(info)) - , m_settings(std::move(settings)) , m_session(std::make_unique<Session>()) - , m_state(std::make_unique<state::Connecting>()) + , m_state(std::make_unique<ConnectingState>()) { irc_callbacks_t callbacks; @@ -462,7 +488,7 @@ void Server::reconnect() noexcept { irc_disconnect(*m_session); - next(std::make_unique<state::Connecting>()); + next(std::make_unique<ConnectingState>()); } void Server::sync(fd_set &setinput, fd_set &setoutput) @@ -518,11 +544,14 @@ void Server::join(std::string channel, std::string password) { - m_queue.push([=] () { - const char *ptr = password.empty() ? nullptr : password.c_str(); + if (m_session->isConnected()) + m_queue.push([=] () { + const char *ptr = password.empty() ? nullptr : password.c_str(); - return irc_cmd_join(*m_session, channel.c_str(), ptr) == 0; - }); + return irc_cmd_join(*m_session, channel.c_str(), ptr) == 0; + }); + else + m_channels.push_back({ std::move(channel), std::move(password) }); } void Server::kick(std::string target, std::string channel, std::string reason)
--- a/lib/irccd/server.hpp Tue Jul 12 12:50:44 2016 +0200 +++ b/lib/irccd/server.hpp Thu Jul 14 10:47:20 2016 +0200 @@ -35,6 +35,8 @@ #include <utility> #include <vector> +#include <json.hpp> + #include "elapsed-timer.hpp" #include "server-state.hpp" #include "signals.hpp" @@ -42,19 +44,12 @@ namespace irccd { -/** - * \brief A channel to join with an optional password. - */ -class ServerChannel { -public: - std::string name; //!< the channel to join - std::string password; //!< the optional password -}; +class Server; /** * \brief Prefixes for nicknames. */ -enum class ServerChanMode { +enum class ChannelMode { Creator = 'O', //!< Channel creator HalfOperator = 'h', //!< Half operator Operator = 'o', //!< Channel operator @@ -63,9 +58,18 @@ }; /** + * \brief A channel to join with an optional password. + */ +class Channel { +public: + std::string name; //!< the channel to join + std::string password; //!< the optional password +}; + +/** * \brief Describe a whois information. */ -class ServerWhois { +class Whois { public: std::string nick; //!< user's nickname std::string user; //!< user's user @@ -75,49 +79,9 @@ }; /** - * \brief Server information - * - * This class contains everything needed to connect to a server. - */ -class ServerInfo { -public: - enum { - Ipv6 = (1 << 0), //!< Connect using IPv6 - Ssl = (1 << 1), //!< Use SSL - SslVerify = (1 << 2) //!< Verify SSL - }; - - std::string host; //!< Hostname - std::string password; //!< Optional server password - std::uint16_t port{6667}; //!< Server's port - std::uint8_t flags{0}; //!< Optional flags - std::map<ServerChanMode, char> modes; //!< IRC modes (e.g. @~) -}; - -/** - * \brief Contains settings to tweak the server - * - * This class contains additional settings that tweaks the server operations. - */ -class ServerSettings { -public: - enum { - AutoRejoin = (1 << 0), //!< Auto rejoin a channel after being kicked - JoinInvite = (1 << 1) //!< Join a channel on invitation - }; - - std::vector<ServerChannel> channels; //!< List of channel to join - std::string command{"!"}; //!< The command character to trigger plugin command - std::int8_t reconnectTries{-1}; //!< Number of tries to reconnect before giving up - std::uint16_t reconnectDelay{30}; //!< Number of seconds to wait before trying to connect - std::uint8_t flags{0}; //!< Optional flags - std::uint16_t pingTimeout{300}; //!< Time in seconds before ping timeout is announced -}; - -/** * \brief Some variables that are needed in many places internally. */ -class ServerCache { +class Cache { public: ElapsedTimer pingTimer; //!< Track elapsed time for ping timeout. std::int8_t reconnectCurrent{1}; //!< Number of reconnection already tested. @@ -130,7 +94,7 @@ /** * Map of whois being build by nicknames. */ - std::map<std::string, ServerWhois> whoisMap; + std::map<std::string, Whois> whoisMap; }; /** @@ -297,7 +261,7 @@ class WhoisEvent { public: std::shared_ptr<Server> server; //!< The server. - ServerWhois whois; //!< The whois information. + Whois whois; //!< The whois information. }; /** @@ -321,6 +285,17 @@ class Session; /** + * \brief Various options for server. + */ + enum { + Ipv6 = (1 << 0), //!< Connect using IPv6 + Ssl = (1 << 1), //!< Use SSL + SslVerify = (1 << 2), //!< Verify SSL + AutoRejoin = (1 << 3), //!< Auto rejoin a channel after being kicked + JoinInvite = (1 << 4) //!< Join a channel on invitation + }; + + /** * Signal: onChannelMode * ---------------------------------------------------------- * @@ -459,19 +434,35 @@ Signal<WhoisEvent> onWhois; private: + // Misc + std::map<ChannelMode, char> m_modes; + // Identifier. std::string m_name; + // Connection information + std::string m_host; + std::string m_password; + std::uint16_t m_port{6667}; + std::uint8_t m_flags{0}; + // Identity. std::string m_nickname{"irccd"}; std::string m_username{"irccd"}; std::string m_realname{"IRC Client Daemon"}; std::string m_ctcpversion{"IRC Client Daemon"}; + // Settings. + std::string m_commandCharacter{"!"}; + std::int8_t m_reconnectTries{-1}; + std::uint16_t m_reconnectDelay{30}; + std::uint16_t m_pingTimeout{300}; + // Various settings. - ServerInfo m_info; - ServerSettings m_settings; - ServerCache m_cache; + std::vector<Channel> m_channels; + + // TODO: find another way. + Cache m_cache; // Queue of requests to send. std::queue<std::function<bool ()>> m_queue; @@ -480,8 +471,8 @@ std::unique_ptr<Session> m_session; // States. - std::unique_ptr<ServerState> m_state; - std::unique_ptr<ServerState> m_stateNext; + std::unique_ptr<State> m_state; + std::unique_ptr<State> m_stateNext; // Handle libircclient callbacks. void handleChannel(const char *, const char **) noexcept; @@ -503,21 +494,30 @@ public: /** + * Convert a JSON object as a server. + * + * Used in JavaScript API and transport commands. + * + * \param object the object + * \return the server + * \throw std::exception on failures + */ + IRCCD_EXPORT static std::shared_ptr<Server> fromJson(const nlohmann::json &object); + + /** * Split a channel from the form channel:password into a ServerChannel object. * * \param value the value * \return a channel */ - IRCCD_EXPORT static ServerChannel splitChannel(const std::string &value); + IRCCD_EXPORT static Channel splitChannel(const std::string &value); /** * Construct a server. * * \param name the identifier - * \param info the information - * \param settings the settings */ - IRCCD_EXPORT Server(std::string name, ServerInfo info, ServerSettings settings = {}); + IRCCD_EXPORT Server(std::string name); /** * Destructor. Close the connection if needed. @@ -535,6 +535,91 @@ } /** + * Get the hostname. + * + * \return the hostname + */ + inline const std::string &host() const noexcept + { + return m_host; + } + + /** + * Set the hostname. + * + * \param host the hostname + */ + inline void setHost(std::string host) noexcept + { + m_host = std::move(host); + } + + /** + * Get the password. + * + * \return the password + */ + inline const std::string &password() const noexcept + { + return m_password; + } + + /** + * Set the password. + * + * An empty password means no password. + * + * \param password the password + */ + inline void setPassword(std::string password) noexcept + { + m_password = std::move(password); + } + + /** + * Get the port. + * + * \return the port + */ + inline std::uint16_t port() const noexcept + { + return m_port; + } + + /** + * Set the port. + * + * \param port the port + */ + inline void setPort(std::uint16_t port) noexcept + { + m_port = port; + } + + /** + * Get the flags. + * + * \return the flags + */ + inline std::uint8_t flags() const noexcept + { + return m_flags; + } + + /** + * Set the flags. + * + * \pre flags must be valid + * \param flags the flags + */ + inline void setFlags(std::uint8_t flags) noexcept + { + assert(flags <= 0x1f); + + m_flags = flags; + } + + /** * Get the nickname. * * \return the nickname @@ -554,23 +639,6 @@ IRCCD_EXPORT void setNickname(std::string nickname); /** - * Get the CTCP version. - * - * \return the CTCP version - */ - inline const std::string &ctcpVersion() const noexcept - { - return m_ctcpversion; - } - - /** - * Set the CTCP version. - * - * \param ctcpversion the version - */ - IRCCD_EXPORT void setCtcpVersion(std::string ctcpversion); - - /** * Get the username. * * \return the username @@ -613,34 +681,115 @@ } /** - * Get the server information. + * Get the CTCP version. * - * \return the server information + * \return the CTCP version + */ + inline const std::string &ctcpVersion() const noexcept + { + return m_ctcpversion; + } + + /** + * Set the CTCP version. + * + * \param ctcpversion the version + */ + IRCCD_EXPORT void setCtcpVersion(std::string ctcpversion); + + /** + * Get the command character. + * + * \return the character */ - inline const ServerInfo &info() const noexcept + inline const std::string &commandCharacter() const noexcept + { + return m_commandCharacter; + } + + /** + * Set the command character. + * + * \pre !commandCharacter.empty() + * \param commandCharacter the command character + */ + inline void setCommandCharacter(std::string commandCharacter) noexcept { - return m_info; + assert(!commandCharacter.empty()); + + m_commandCharacter = std::move(commandCharacter); + } + + /** + * Get the number of reconnections before giving up. + * + * \return the number of reconnections + */ + inline std::int8_t reconnectTries() const noexcept + { + return m_reconnectTries; } /** - * Get the server settings. + * Set the number of reconnections to test before giving up. + * + * A value less than 0 means infinite. * - * \note some settings will be used only after the next reconnection - * \return the settings + * \param reconnectTries the number of reconnections */ - inline ServerSettings &settings() noexcept + inline void setReconnectTries(std::int8_t reconnectTries) noexcept { - return m_settings; + m_reconnectTries = reconnectTries; + } + + /** + * Get the reconnection delay before retrying. + * + * \return the number of seconds + */ + inline std::uint16_t reconnectDelay() const noexcept + { + return m_reconnectDelay; } /** - * Get the server settings. + * Set the number of seconds before retrying. * - * \return the settings + * \param reconnectDelay the number of seconds + */ + inline void setReconnectDelay(std::uint16_t reconnectDelay) noexcept + { + m_reconnectDelay = reconnectDelay; + } + + /** + * Get the ping timeout. + * + * \return the ping timeout */ - inline const ServerSettings &settings() const noexcept + inline std::uint16_t pingTimeout() const noexcept + { + return m_pingTimeout; + } + + /** + * Set the ping timeout before considering a server as dead. + * + * \param pingTimeout the delay in seconds + */ + inline void setPingTimeout(std::uint16_t pingTimeout) noexcept { - return m_settings; + m_pingTimeout = pingTimeout; + } + + /** + * Get the list of channels joined. + * + * \return the channels + */ + inline const std::vector<Channel> &channels() const noexcept + { + return m_channels; } /** @@ -649,7 +798,7 @@ * \return the cache * \warning use with care */ - inline ServerCache &cache() noexcept + inline Cache &cache() noexcept { return m_cache; } @@ -669,7 +818,7 @@ * * \param state the new state */ - inline void next(std::unique_ptr<ServerState> state) noexcept + inline void next(std::unique_ptr<State> state) noexcept { m_stateNext = std::move(state); }
--- a/lib/irccd/service-server.cpp Tue Jul 12 12:50:44 2016 +0200 +++ b/lib/irccd/service-server.cpp Thu Jul 14 10:47:20 2016 +0200 @@ -236,11 +236,11 @@ m_irccd.post(EventHandler{ev.server->name(), ev.origin, ev.channel, [=] (Plugin &plugin) -> std::string { - return util::parseMessage(ev.message, ev.server->settings().command, plugin.name()).second == util::MessageType::Command ? "onCommand" : "onMessage"; + return util::parseMessage(ev.message, ev.server->commandCharacter(), plugin.name()).second == util::MessageType::Command ? "onCommand" : "onMessage"; }, [=] (Plugin &plugin) mutable { auto copy = ev; - auto pack = util::parseMessage(copy.message, copy.server->settings().command, plugin.name()); + auto pack = util::parseMessage(copy.message, copy.server->commandCharacter(), plugin.name()); copy.message = pack.first; @@ -414,11 +414,11 @@ m_irccd.post(EventHandler{ev.server->name(), ev.origin, /* channel */ "", [=] (Plugin &plugin) -> std::string { - return util::parseMessage(ev.message, ev.server->settings().command, plugin.name()).second == util::MessageType::Command ? "onQueryCommand" : "onQuery"; + return util::parseMessage(ev.message, ev.server->commandCharacter(), plugin.name()).second == util::MessageType::Command ? "onQueryCommand" : "onQuery"; }, [=] (Plugin &plugin) mutable { auto copy = ev; - auto pack = util::parseMessage(copy.message, copy.server->settings().command, plugin.name()); + auto pack = util::parseMessage(copy.message, copy.server->commandCharacter(), plugin.name()); copy.message = pack.first;
--- a/lib/irccd/util.hpp Tue Jul 12 12:50:44 2016 +0200 +++ b/lib/irccd/util.hpp Thu Jul 14 10:47:20 2016 +0200 @@ -35,6 +35,7 @@ #include <unordered_map> #include <vector> +#include <format.h> #include <json.hpp> #include "sysconfig.hpp" @@ -199,6 +200,20 @@ } /** + * Clamp the value between low and high. + * + * \param value the value + * \param low the minimum value + * \param high the maximum value + * \return the value between minimum and maximum + */ +template <typename T> +constexpr T clamp(T value, T low, T high) noexcept +{ + return (value < high) ? std::max(value, low) : std::min(value, high); +} + +/** * Parse IRC message and determine if it's a command or a simple message. * * \param message the message line @@ -328,26 +343,98 @@ namespace json { /** - * Get a property or return null one if not found or if json is not an object. + * Require a property. * * \param json the json value - * \param property the property key - * \return the value or null one if not found + * \param key the property name + * \param type the requested property type + * \return the value + * \throw std::runtime_error if the property is missing */ -inline nlohmann::json get(const nlohmann::json &json, const std::string &property) noexcept +inline nlohmann::json require(const nlohmann::json &json, const std::string &key, nlohmann::json::value_t type) { - if (!json.is_object()) - return nlohmann::json(); - - auto it = json.find(property); + auto it = json.find(key); + auto dummy = nlohmann::json(type); if (it == json.end()) - return nlohmann::json(); + throw std::runtime_error(fmt::format("missing '{}' property", key)); + if (it->type() != type) + throw std::runtime_error(fmt::format("invalid '{}' property ({} expected, got {})", key, it->type_name(), dummy.type_name())); return *it; } /** + * Convenient access for booleans. + * + * \param json the json object + * \param key the property key + * \return the boolean + * \throw std::runtime_error if the property is missing or not a boolean + */ +inline bool requireBool(const nlohmann::json &json, const std::string &key) +{ + return require(json, key, nlohmann::json::value_t::boolean); +} + +/** + * Convenient access for ints. + * + * \param json the json object + * \param key the property key + * \return the int + * \throw std::runtime_error if the property is missing or not ant int + */ +inline std::int64_t requireInt(const nlohmann::json &json, const std::string &key) +{ + return require(json, key, nlohmann::json::value_t::number_integer); +} + +/** + * Convenient access for unsigned ints. + * + * \param json the json object + * \param key the property key + * \return the unsigned int + * \throw std::runtime_error if the property is missing or not ant int + */ +inline std::uint64_t requireUint(const nlohmann::json &json, const std::string &key) +{ + return require(json, key, nlohmann::json::value_t::number_unsigned); +} + +/** + * Convenient access for strings. + * + * \param json the json object + * \param key the property key + * \return the string + * \throw std::runtime_error if the property is missing or not a string + */ +inline std::string requireString(const nlohmann::json &json, const std::string &key) +{ + return require(json, key, nlohmann::json::value_t::string); +} + +/** + * Convenient access for unique identifiers. + * + * \param json the json object + * \param key the property key + * \return the identifier + * \throw std::runtime_error if the property is invalid + */ +inline std::string requireIdentifier(const nlohmann::json &json, const std::string &key) +{ + auto id = requireString(json, key); + + if (!isIdentifierValid(id)) + throw std::runtime_error("invalid '{}' identifier property"); + + return id; +} + +/** * Convert the json value to boolean. * * \param json the json value @@ -366,9 +453,21 @@ * \param def the default value if not an int * \return an int */ -inline int toInt(const nlohmann::json &json, int def = 0) noexcept +inline std::int64_t toInt(const nlohmann::json &json, std::int64_t def = 0) noexcept { - return json.is_number() ? json.get<int>() : def; + return json.is_number_integer() ? json.get<std::int64_t>() : def; +} + +/** + * Convert the json value to unsigned. + * + * \param json the json value + * \param def the default value if not a unsigned int + * \return an unsigned int + */ +inline std::uint64_t toUint(const nlohmann::json &json, std::uint64_t def = 0) noexcept +{ + return json.is_number_unsigned() ? json.get<std::uint64_t>() : def; } /** @@ -383,6 +482,109 @@ return json.is_string() ? json.get<std::string>() : def; } +/** + * Get a property or return null one if not found or if json is not an object. + * + * \param json the json value + * \param property the property key + * \return the value or null one if not found + */ +inline nlohmann::json get(const nlohmann::json &json, const std::string &property) noexcept +{ + auto it = json.find(property); + + if (it == json.end()) + return nlohmann::json(); + + return *it; +} + +/** + * Convenient access for boolean with default value. + * + * \param json the json value + * \param key the property key + * \param def the default value + * \return the boolean + */ +inline bool getBool(const nlohmann::json &json, const std::string &key, bool def = false) noexcept +{ + return toBool(get(json, key), def); +} + +/** + * Convenient access for ints with default value. + * + * \param json the json value + * \param key the property key + * \param def the default value + * \return the int + */ +inline std::int64_t getInt(const nlohmann::json &json, const std::string &key, std::int64_t def = 0) noexcept +{ + return toInt(get(json, key), def); +} + +/** + * Convenient access for unsigned ints with default value. + * + * \param json the json value + * \param key the property key + * \param def the default value + * \return the unsigned int + */ +inline std::uint64_t getUint(const nlohmann::json &json, const std::string &key, std::uint64_t def = 0) noexcept +{ + return toUint(get(json, key), def); +} + +/** + * Get an integer in the given range. + * + * \param json the json value + * \param key the property key + * \param def the default value + * \return the boolean + */ +template <typename T> +inline T getIntRange(const nlohmann::json &json, + const std::string &key, + std::int64_t min = std::numeric_limits<T>::min(), + std::int64_t max = std::numeric_limits<T>::max()) noexcept +{ + return clamp(getInt(json, key), min, max); +} + +/** + * Get an unsigned integer in the given range. + * + * \param json the json value + * \param key the property key + * \param def the default value + * \return the boolean + */ +template <typename T> +inline T getUintRange(const nlohmann::json &json, + const std::string &key, + std::uint64_t min = std::numeric_limits<T>::min(), + std::uint64_t max = std::numeric_limits<T>::max()) noexcept +{ + return clamp(getUint(json, key), min, max); +} + +/** + * Convenient access for strings with default value. + * + * \param json the json value + * \param key the property key + * \param def the default value + * \return the string + */ +inline std::string getString(const nlohmann::json &json, const std::string &key, std::string def = "") noexcept +{ + return toString(get(json, key), def); +} + } // !json } // !util