Mercurial > irccd
changeset 527:a88796ed040a
Irccdctl: switch to Boost.Asio, closes #697
author | David Demelier <markand@malikania.fr> |
---|---|
date | Thu, 16 Nov 2017 22:45:12 +0100 |
parents | 5df38b4fbc55 |
children | 9daccaeedcce |
files | irccdctl/alias.cpp irccdctl/alias.hpp irccdctl/cli.cpp irccdctl/cli.hpp irccdctl/main.cpp libcommon/CMakeLists.txt libcommon/irccd/errors.cpp libcommon/irccd/errors.hpp libirccd-test/CMakeLists.txt libirccd-test/irccd/command-tester.cpp libirccd-test/irccd/command-tester.hpp libirccdctl/CMakeLists.txt libirccdctl/irccd/client.cpp libirccdctl/irccd/client.hpp libirccdctl/irccd/ctl/connection.hpp libirccdctl/irccd/ctl/controller.cpp libirccdctl/irccd/ctl/controller.hpp libirccdctl/irccd/ctl/ip_connection.cpp libirccdctl/irccd/ctl/ip_connection.hpp libirccdctl/irccd/ctl/local_connection.cpp libirccdctl/irccd/ctl/local_connection.hpp libirccdctl/irccd/ctl/network_connection.hpp libirccdctl/irccd/irccdctl.hpp tests/CMakeLists.txt |
diffstat | 24 files changed, 1786 insertions(+), 2298 deletions(-) [+] |
line wrap: on
line diff
--- a/irccdctl/alias.cpp Tue Nov 14 20:25:30 2017 +0100 +++ b/irccdctl/alias.cpp Thu Nov 16 22:45:12 2017 +0100 @@ -23,38 +23,42 @@ namespace irccd { -AliasArg::AliasArg(std::string value) +namespace ctl { + +alias_arg::alias_arg(std::string value) { assert(!value.empty()); - if ((m_isPlaceholder = std::regex_match(value, std::regex("^%\\d+$")))) - m_value = value.substr(1); + if ((is_placeholder_ = std::regex_match(value, std::regex("^%\\d+$")))) + value_ = value.substr(1); else - m_value = std::move(value); + value_ = std::move(value); } -unsigned AliasArg::index() const noexcept +unsigned alias_arg::index() const noexcept { - assert(isPlaceholder()); + assert(is_placeholder_); - return std::stoi(m_value); + return std::stoi(value_); } -const std::string &AliasArg::value() const noexcept +const std::string& alias_arg::value() const noexcept { - assert(!isPlaceholder()); + assert(!is_placeholder_); - return m_value; + return value_; } -std::ostream &operator<<(std::ostream &out, const AliasArg &arg) +std::ostream& operator<<(std::ostream& out, const alias_arg& arg) { - if (arg.m_isPlaceholder) - out << "%" << arg.m_value; + if (arg.is_placeholder_) + out << "%" << arg.value_; else - out << arg.m_value; + out << arg.value_; return out; } +} // !ctl + } // !irccd
--- a/irccdctl/alias.hpp Tue Nov 14 20:25:30 2017 +0100 +++ b/irccdctl/alias.hpp Thu Nov 16 22:45:12 2017 +0100 @@ -32,6 +32,8 @@ namespace irccd { +namespace ctl { + /** * \brief Describe an alias argument. * @@ -40,10 +42,10 @@ * * Placeholders are placed using %n where n is an integer starting from 0. */ -class AliasArg { +class alias_arg { private: - std::string m_value; - bool m_isPlaceholder; + std::string value_; + bool is_placeholder_; public: /** @@ -52,33 +54,33 @@ * \pre value must not be empty * \param value the value */ - IRCCD_EXPORT AliasArg(std::string value); + alias_arg(std::string value); /** * Check if the argument is a placeholder. * * \return true if the argument is a placeholder */ - inline bool isPlaceholder() const noexcept + inline bool is_placeholder() const noexcept { - return m_isPlaceholder; + return is_placeholder_; } /** * Get the placeholder index (e.g %0 returns 0) * - * \pre isPlaceholder() must return true + * \pre is_placeholder() must return true * \return the position */ - IRCCD_EXPORT unsigned index() const noexcept; + unsigned index() const noexcept; /** * Get the real value. * - * \pre isPlaceholder() must return false + * \pre is_placeholder() must return false * \return the value */ - IRCCD_EXPORT const std::string &value() const noexcept; + const std::string& value() const noexcept; /** * Output the alias to the stream. @@ -86,7 +88,7 @@ * \param out the output stream * \return out */ - IRCCD_EXPORT friend std::ostream &operator<<(std::ostream &out, const AliasArg &); + friend std::ostream& operator<<(std::ostream& out, const alias_arg&); }; /** @@ -95,10 +97,10 @@ * An alias command is just a command with a set of applied or placeholders * arguments. */ -class AliasCommand { +class alias_command { private: - std::string m_command; - std::vector<AliasArg> m_args; + std::string command_; + std::vector<alias_arg> args_; public: /** @@ -107,9 +109,9 @@ * \param command the command * \param args the arguments */ - inline AliasCommand(std::string command, std::vector<AliasArg> args = {}) noexcept - : m_command(std::move(command)) - , m_args(std::move(args)) + inline alias_command(std::string command, std::vector<alias_arg> args = {}) noexcept + : command_(std::move(command)) + , args_(std::move(args)) { } @@ -118,9 +120,9 @@ * * \return the command name */ - inline const std::string &command() const noexcept + inline const std::string& command() const noexcept { - return m_command; + return command_; } /** @@ -128,21 +130,22 @@ * * \return the arguments */ - inline const std::vector<AliasArg> &args() const noexcept + inline const std::vector<alias_arg>& args() const noexcept { - return m_args; + return args_; } }; /** * \brief A set of commands to execute with their arguments. * - * An alias is a composition of AliasCommand, typically, the user is able to set - * an alias that execute a list of specified commands in order they are defined. + * An alias is a composition of alias_command, typically, the user is able to + * set an alias that execute a list of specified commands in order they are + * defined. */ -class Alias : public std::vector<AliasCommand> { +class alias : public std::vector<alias_command> { private: - std::string m_name; + std::string name_; public: /** @@ -150,8 +153,8 @@ * * \param name the alias name */ - inline Alias(std::string name) noexcept - : m_name(std::move(name)) + inline alias(std::string name) noexcept + : name_(std::move(name)) { } @@ -160,12 +163,14 @@ * * \return the name */ - inline const std::string &name() const noexcept + inline const std::string& name() const noexcept { - return m_name; + return name_; } }; +} // !ctl + } // !irccd #endif // !IRCCD_ALIAS_HPP
--- a/irccdctl/cli.cpp Tue Nov 14 20:25:30 2017 +0100 +++ b/irccdctl/cli.cpp Thu Nov 16 22:45:12 2017 +0100 @@ -16,150 +16,107 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -#include <cassert> -#include <iostream> -#include <sstream> +#include <boost/system/system_error.hpp> -#include <boost/timer/timer.hpp> +#include <irccd/errors.hpp> +#include <irccd/json_util.hpp> +#include <irccd/options.hpp> +#include <irccd/string_util.hpp> -#include <json.hpp> +#include <irccd/ctl/controller.hpp> #include "cli.hpp" -#include "irccdctl.hpp" -#include "json_util.hpp" -#include "logger.hpp" -#include "options.hpp" -#include "net_util.hpp" -#include "string_util.hpp" - -using namespace std::string_literals; namespace irccd { +namespace ctl { + +void cli::recv_response(ctl::controller& ctl, nlohmann::json req, handler_t handler) +{ + ctl.recv([&ctl, req, handler, this] (auto code, auto message) { + if (code) + throw boost::system::system_error(code); + + auto c = json_util::to_string(message["command"]); + + if (c != req["command"].get<std::string>()) { + recv_response(ctl, std::move(req), std::move(handler)); + return; + } + + if (message["error"].is_number_integer()) + throw boost::system::system_error(static_cast<network_error>(message["error"].template get<int>())); + if (message["error"].is_string()) + throw std::runtime_error(message["error"].template get<std::string>()); + + if (handler) + handler(std::move(message)); + }); +} + +void cli::request(ctl::controller& ctl, nlohmann::json req, handler_t handler) +{ + ctl.send(req, [&ctl, req, handler, this] (auto code, auto) { + if (code) + throw boost::system::system_error(code); + + recv_response(ctl, std::move(req), std::move(handler)); + }); +} + /* - * Cli. + * plugin_info_cli. * ------------------------------------------------------------------ */ -void Cli::check(const nlohmann::json &response) -{ - if (!json_util::get_bool(response, "status", false)) { - auto error = json_util::get_string(response, "error"); - - if (error.empty()) - throw std::runtime_error("command failed with an unknown error"); - - throw std::runtime_error(error); - } -} - -nlohmann::json Cli::request(Irccdctl &irccdctl, nlohmann::json args) +void plugin_config_cli::set(ctl::controller& ctl, const std::vector<std::string>&args) { - auto msg = nlohmann::json(); - - if (!args.is_object()) - args = nlohmann::json::object(); - - args.push_back({"command", m_name}); - irccdctl.client().request(args); - - auto id = irccdctl.client().onMessage.connect([&] (auto input) { - msg = std::move(input); - }); - - try { - boost::timer::cpu_timer timer; - - while (irccdctl.client().isConnected() && !msg.is_object() && timer.elapsed().wall / 1000000LL < 3000) - net_util::poll(3000 - timer.elapsed().wall / 1000000LL, irccdctl); - } catch (const std::exception &) { - irccdctl.client().onMessage.disconnect(id); - throw; - } - - irccdctl.client().onMessage.disconnect(id); - - if (!msg.is_object()) - throw std::runtime_error("no response received"); - if (json_util::get_string(msg, "command") != m_name) - throw std::runtime_error("unexpected command result received"); - - check(msg); - - return msg; -} - -void Cli::call(Irccdctl &irccdctl, nlohmann::json args) -{ - check(request(irccdctl, args)); -} - -namespace cli { - -/* - * PluginConfigCli. - * ------------------------------------------------------------------ - */ - -void PluginConfigCli::set(Irccdctl &irccdctl, const std::vector<std::string> &args) -{ - check(request(irccdctl, nlohmann::json::object({ + request(ctl, { { "plugin", args[0] }, { "variable", args[1] }, { "value", args[2] } - }))); + }); } -void PluginConfigCli::get(Irccdctl &irccdctl, const std::vector<std::string> &args) +void plugin_config_cli::get(ctl::controller& ctl, const std::vector<std::string>& args) { - auto result = request(irccdctl, nlohmann::json::object({ + auto json = nlohmann::json::object({ { "plugin", args[0] }, { "variable", args[1] } - })); + }); - check(result); - - if (result["variables"].is_object()) - std::cout << json_util::pretty(result["variables"][args[1]]) << std::endl; + request(ctl, std::move(json), [args] (auto result) { + if (result["variables"].is_object()) + std::cout << json_util::pretty(result["variables"][args[1]]) << std::endl; + }); } -void PluginConfigCli::getall(Irccdctl &irccdctl, const std::vector<std::string> &args) +void plugin_config_cli::getall(ctl::controller& ctl, const std::vector<std::string> &args) { - auto result = request(irccdctl, nlohmann::json::object({{ "plugin", args[0] }})); + request(ctl, {{ "plugin", args[0] }}, [] (auto result) { + auto variables = result["variables"]; - check(result); - - auto variables = result["variables"]; - - for (auto v = variables.begin(); v != variables.end(); ++v) - std::cout << std::setw(16) << std::left << v.key() << " : " << json_util::pretty(v.value()) << std::endl; + for (auto v = variables.begin(); v != variables.end(); ++v) + std::cout << std::setw(16) << std::left << v.key() << " : " << json_util::pretty(v.value()) << std::endl; + }); } -PluginConfigCli::PluginConfigCli() - : Cli("plugin-config", - "configure a plugin", - "plugin-config plugin [variable] [value]", - "Get or set a plugin configuration variable.\n\n" - "If both variable and value are provided, sets the plugin configuration " - "to the\nrespective variable name and value.\n\n" - "If only variable is specified, shows its current value. Otherwise, list " - "all\nvariables and their values.\n\n" - "Examples:\n" - "\tirccdctl plugin-config ask") +std::string plugin_config_cli::name() const { + return "plugin-config"; } -void PluginConfigCli::exec(Irccdctl &irccdctl, const std::vector<std::string> &args) +void plugin_config_cli::exec(ctl::controller& ctl, const std::vector<std::string> &args) { switch (args.size()) { case 3: - set(irccdctl, args); + set(ctl, args); break; case 2: - get(irccdctl, args); + get(ctl, args); break; case 1: - getall(irccdctl, args); + getall(ctl, args); break; default: throw std::invalid_argument("plugin-config requires at least 1 argument"); @@ -167,188 +124,150 @@ } /* - * PluginInfoCli. + * plugin_info_cli. * ------------------------------------------------------------------ */ -PluginInfoCli::PluginInfoCli() - : Cli("plugin-info", - "get plugin information", - "plugin-info plugin", - "Get plugin information.\n\n" - "Example:\n" - "\tirccdctl plugin-info ask" - ) +std::string plugin_info_cli::name() const { + return "plugin-info"; } -void PluginInfoCli::exec(Irccdctl &irccdctl, const std::vector<std::string> &args) +void plugin_info_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args) { if (args.size() < 1) throw std::invalid_argument("plugin-info requires 1 argument"); - auto result = request(irccdctl, {{ "plugin", args[0] }}); - - std::cout << std::boolalpha; - std::cout << "Author : " << json_util::get_string(result, "author") << std::endl; - std::cout << "License : " << json_util::get_string(result, "license") << std::endl; - std::cout << "Summary : " << json_util::get_string(result, "summary") << std::endl; - std::cout << "Version : " << json_util::get_string(result, "version") << std::endl; + request(ctl, {{ "plugin", args[0] }}, [] (auto result) { + std::cout << std::boolalpha; + std::cout << "Author : " << json_util::get_string(result, "author") << std::endl; + std::cout << "License : " << json_util::get_string(result, "license") << std::endl; + std::cout << "Summary : " << json_util::get_string(result, "summary") << std::endl; + std::cout << "Version : " << json_util::get_string(result, "version") << std::endl; + }); } /* - * PluginListCli. + * plugin_list_cli. * ------------------------------------------------------------------ */ -PluginListCli::PluginListCli() - : Cli("plugin-list", - "list loaded plugins", - "plugin-list", - "Get the list of all loaded plugins.\n\n" - "Example:\n" - "\tirccdctl plugin-list") +std::string plugin_list_cli::name() const { + return "plugin-list"; } -void PluginListCli::exec(Irccdctl &irccdctl, const std::vector<std::string> &) +void plugin_list_cli::exec(ctl::controller& ctl, const std::vector<std::string>&) { - auto result = request(irccdctl); - - for (const auto &value : result["list"]) - if (value.is_string()) - std::cout << value.get<std::string>() << std::endl; + request(ctl, {{ "command", "plugin-list" }}, [] (auto result) { + for (const auto &value : result["list"]) + if (value.is_string()) + std::cout << value.template get<std::string>() << std::endl; + }); } /* - * PluginLoadCli. + * plugin_load_cli. * ------------------------------------------------------------------ */ -PluginLoadCli::PluginLoadCli() - : Cli("plugin-load", - "load a plugin", - "plugin-load logger", - "Load a plugin into the irccd instance.\n\n" - "Example:\n" - "\tirccdctl plugin-load logger") +std::string plugin_load_cli::name() const { + return "plugin-load"; } -void PluginLoadCli::exec(Irccdctl &irccdctl, const std::vector<std::string> &args) +void plugin_load_cli::exec(ctl::controller& ctl, const std::vector<std::string> &args) { if (args.size() < 1) throw std::invalid_argument("plugin-load requires 1 argument"); - check(request(irccdctl, {{"plugin", args[0]}})); + request(ctl, {{ "plugin", args[0] }}); } /* - * PluginReloadCli. + * plugin_reload_cli. * ------------------------------------------------------------------ */ -PluginReloadCli::PluginReloadCli() - : Cli("plugin-reload", - "reload a plugin", - "plugin-reload plugin", - "Reload a plugin by calling the appropriate onReload event, the plugin is not\n" - "unloaded and must be already loaded.\n\n" - "Example:\n" - "\tirccdctl plugin-reload logger") +std::string plugin_reload_cli::name() const { + return "plugin-reload"; } -void PluginReloadCli::exec(Irccdctl &irccdctl, const std::vector<std::string> &args) +void plugin_reload_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args) { if (args.size() < 1) throw std::invalid_argument("plugin-reload requires 1 argument"); - check(request(irccdctl, {{ "plugin", args[0] }})); + request(ctl, {{ "plugin", args[0] }}); } /* - * PluginUnloadCli. + * plugin_unload_cli. * ------------------------------------------------------------------ */ -PluginUnloadCli::PluginUnloadCli() - : Cli("plugin-unload", - "unload a plugin", - "plugin-unload plugin", - "Unload a loaded plugin from the irccd instance.\n\n" - "Example:\n" - "\tirccdctl plugin-unload logger") +std::string plugin_unload_cli::name() const { + return "plugin-unload"; } -void PluginUnloadCli::exec(Irccdctl &irccdctl, const std::vector<std::string> &args) +void plugin_unload_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args) { if (args.size() < 1) throw std::invalid_argument("plugin-unload requires 1 argument"); - check(request(irccdctl, {{ "plugin", args[0] }})); + request(ctl, {{ "plugin", args[0] }}); } /* - * ServerChannelCli. + * server_channel_cli. * ------------------------------------------------------------------ */ -ServerChannelMode::ServerChannelMode() - : Cli("server-cmode", - "change channel mode", - "server-cmode server channel mode", - "Change the mode of the specified channel.\n\n" - "Example:\n" - "\tirccdctl server-cmode freenode #staff +t") +std::string server_channel_mode_cli::name() const { + return "server-cmode"; } -void ServerChannelMode::exec(Irccdctl &irccdctl, const std::vector<std::string> &args) +void server_channel_mode_cli::exec(ctl::controller& ctl, const std::vector<std::string> &args) { if (args.size() < 3) throw std::invalid_argument("server-cmode requires 3 arguments"); - check(request(irccdctl, { + request(ctl, { { "command", "server-cmode" }, { "server", args[0] }, { "channel", args[1] }, { "mode", args[2] } - })); + }); } /* - * ServerChannelNoticeCli. + * server_channel_notice_cli. * ------------------------------------------------------------------ */ -ServerChannelNoticeCli::ServerChannelNoticeCli() - : Cli("server-cnotice", - "send a channel notice", - "server-cnotice server channel message", - "Send a notice to a public channel. This is a notice that everyone on the channel\n" - "will receive.\n\n" - "Example:\n" - "\tirccdctl server-cnotice freenode #staff \"Don't flood!\"") +std::string server_channel_notice_cli::name() const { + return "server-cnotice"; } -void ServerChannelNoticeCli::exec(Irccdctl &irccdctl, const std::vector<std::string> &args) +void server_channel_notice_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args) { if (args.size() < 3) throw std::invalid_argument("server-cnotice requires 3 arguments"); - check(request(irccdctl, { + request(ctl, { { "command", "server-cnotice" }, { "server", args[0] }, { "channel", args[1] }, { "message", args[2] } - })); + }); } /* - * ServerConnectCli. + * server_connect_cli. * ------------------------------------------------------------------ */ @@ -376,25 +295,12 @@ } // !namespace -ServerConnectCli::ServerConnectCli() - : Cli("server-connect", - "add a server", - "server-connect [options] id host [port]", - "Connect to a new IRC server.\n\n" - "Available options:\n" - " -c, --command\t\tspecify the command char\n" - " -n, --nickname\tspecify a nickname\n" - " -r, --realname\tspecify a real name\n" - " -S, --ssl-verify\tverify SSL\n" - " -s, --ssl\t\tconnect using SSL\n" - " -u, --username\tspecify a user name\n\n" - "Example:\n" - "\tirccdctl server-connect -n jean example irc.example.org\n" - "\tirccdctl server-connect --ssl example irc.example.org 6697") +std::string server_connect_cli::name() const { + return "server-connect"; } -void ServerConnectCli::exec(Irccdctl &irccdctl, const std::vector<std::string> &args) +void server_connect_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args) { std::vector<std::string> copy(args); @@ -427,26 +333,20 @@ if ((it = result.find("-u")) != result.end() || (it = result.find("--username")) != result.end()) object["username"] = it->second; - check(request(irccdctl, object)); + request(ctl, object); } /* - * ServerDisconnectCli. + * server_disconnect_cli. * ------------------------------------------------------------------ */ -ServerDisconnectCli::ServerDisconnectCli() - : Cli("server-disconnect", - "disconnect server", - "server-disconnect [server]", - "Disconnect from a server.\n\n" - "If server is not specified, irccd disconnects all servers.\n\n" - "Example:\n" - "\tirccdctl server-disconnect localhost") +std::string server_disconnect_cli::name() const { + return "server-disconnect"; } -void ServerDisconnectCli::exec(Irccdctl &irccdctl, const std::vector<std::string> &args) +void server_disconnect_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args) { auto object = nlohmann::json::object({ { "command", "server-disconnect" } @@ -455,101 +355,85 @@ if (args.size() > 0) object["server"] = args[0]; - check(request(irccdctl, object)); + request(ctl, object); } /* - * ServerInfoCli. + * server_info_cli. * ------------------------------------------------------------------ */ -ServerInfoCli::ServerInfoCli() - : Cli("server-info", - "get server information", - "server-info server", - "Get information about a server.\n\n" - "Example:\n" - "\tirccdctl server-info freenode") +std::string server_info_cli::name() const { + return "server-info"; } -void ServerInfoCli::exec(Irccdctl &irccdctl, const std::vector<std::string> &args) +void server_info_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args) { if (args.size() < 1) throw std::invalid_argument("server-info requires 1 argument"); - auto result = request(irccdctl, { + auto json = nlohmann::json::object({ { "command", "server-info" }, { "server", args[0] } }); - check(result); + request(ctl, std::move(json), [] (auto result) { + std::cout << std::boolalpha; + std::cout << "Name : " << json_util::pretty(result["name"]) << std::endl; + std::cout << "Host : " << json_util::pretty(result["host"]) << std::endl; + std::cout << "Port : " << json_util::pretty(result["port"]) << std::endl; + std::cout << "Ipv6 : " << json_util::pretty(result["ipv6"]) << std::endl; + std::cout << "SSL : " << json_util::pretty(result["ssl"]) << std::endl; + std::cout << "SSL verified : " << json_util::pretty(result["sslVerify"]) << std::endl; + std::cout << "Channels : "; - std::cout << std::boolalpha; - std::cout << "Name : " << json_util::pretty(result["name"]) << std::endl; - std::cout << "Host : " << json_util::pretty(result["host"]) << std::endl; - std::cout << "Port : " << json_util::pretty(result["port"]) << std::endl; - std::cout << "Ipv6 : " << json_util::pretty(result["ipv6"]) << std::endl; - std::cout << "SSL : " << json_util::pretty(result["ssl"]) << std::endl; - std::cout << "SSL verified : " << json_util::pretty(result["sslVerify"]) << std::endl; - std::cout << "Channels : "; + for (const auto &v : result["channels"]) + if (v.is_string()) + std::cout << v.template get<std::string>() << " "; - for (const auto &v : result["channels"]) - if (v.is_string()) - std::cout << v.get<std::string>() << " "; + std::cout << std::endl; - std::cout << std::endl; - - std::cout << "Nickname : " << json_util::pretty(result["nickname"]) << std::endl; - std::cout << "User name : " << json_util::pretty(result["username"]) << std::endl; - std::cout << "Real name : " << json_util::pretty(result["realname"]) << std::endl; + std::cout << "Nickname : " << json_util::pretty(result["nickname"]) << std::endl; + std::cout << "User name : " << json_util::pretty(result["username"]) << std::endl; + std::cout << "Real name : " << json_util::pretty(result["realname"]) << std::endl; + }); } /* - * ServerInviteCli. + * server_invite_cli. * ------------------------------------------------------------------ */ -ServerInviteCli::ServerInviteCli() - : Cli("server-invite", - "invite someone", - "server-invite server nickname channel", - "Invite the specified target on the channel.\n\n" - "Example:\n" - "\tirccdctl server-invite freenode xorg62 #staff") +std::string server_invite_cli::name() const { + return "server-invite"; } -void ServerInviteCli::exec(Irccdctl &irccdctl, const std::vector<std::string> &args) +void server_invite_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args) { if (args.size() < 3) throw std::invalid_argument("server-invite requires 3 arguments"); - check(request(irccdctl, { + request(ctl, { { "command", "server-invite" }, { "server", args[0] }, { "target", args[1] }, { "channel", args[2] } - })); + }); } /* - * ServerJoinCli. + * server_join_cli. * ------------------------------------------------------------------ */ -ServerJoinCli::ServerJoinCli() - : Cli("server-join", - "join a channel", - "server-join server channel [password]", - "Join the specified channel, the password is optional.\n\n" - "Example:\n" - "\tirccdctl server-join freenode #test\n" - "\tirccdctl server-join freenode #private-club secret") +std::string server_join_cli::name() const { + return "server-join"; } -void ServerJoinCli::exec(Irccdctl &irccdctl, const std::vector<std::string> &args) +void server_join_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args) { if (args.size() < 2) throw std::invalid_argument("server-join requires at least 2 arguments"); @@ -562,25 +446,20 @@ if (args.size() == 3) object["password"] = args[2]; - check(request(irccdctl, object)); + request(ctl, object); } /* - * ServerKickCli. + * server_kick_cli. * ------------------------------------------------------------------ */ -ServerKickCli::ServerKickCli() - : Cli("server-kick", - "kick someone from a channel", - "server-kick server target channel [reason]", - "Kick the specified target from the channel, the reason is optional.\n\n" - "Example:\n" - "\tirccdctl server-kick freenode jean #staff \"Stop flooding\"") +std::string server_kick_cli::name() const { + return "server-kick"; } -void ServerKickCli::exec(Irccdctl &irccdctl, const std::vector<std::string> &args) +void server_kick_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args) { if (args.size() < 3) throw std::invalid_argument("server-kick requires at least 3 arguments "); @@ -594,187 +473,147 @@ if (args.size() == 4) object["reason"] = args[3]; - check(request(irccdctl, object)); + request(ctl, object); } /* - * ServerListCli. + * server_list_cli. * ------------------------------------------------------------------ */ -ServerListCli::ServerListCli() - : Cli("server-list", - "get list of servers", - "server-list", - "Get the list of all connected servers.\n\n" - "Example:\n" - "\tirccdctl server-list") +std::string server_list_cli::name() const { + return "server-list"; } -void ServerListCli::exec(Irccdctl &irccdctl, const std::vector<std::string> &) +void server_list_cli::exec(ctl::controller& ctl, const std::vector<std::string>&) { - auto response = request(irccdctl); - - check(response); - - for (const auto &n : response["list"]) - if (n.is_string()) - std::cout << n.get<std::string>() << std::endl; + request(ctl, {{ "command", "server-list" }}, [] (auto result) { + for (const auto &n : result["list"]) + if (n.is_string()) + std::cout << n.template get<std::string>() << std::endl; + }); } /* - * ServerMeCli. + * server_me_cli. * ------------------------------------------------------------------ */ -ServerMeCli::ServerMeCli() - : Cli("server-me", - "send an action emote", - "server-me server target message", - "Send an action emote.\n\n" - "Example:\n" - "\tirccdctl server-me freenode #staff \"going back soon\"") +std::string server_me_cli::name() const { + return "server-me"; } -void ServerMeCli::exec(Irccdctl &irccdctl, const std::vector<std::string> &args) +void server_me_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args) { if (args.size() < 3) throw std::runtime_error("server-me requires 3 arguments"); - check(request(irccdctl, { + request(ctl, { { "server", args[0] }, { "target", args[1] }, { "message", args[2] } - })); + }); } /* - * ServerMessageCli. + * server_message_cli. * ------------------------------------------------------------------ */ -ServerMessageCli::ServerMessageCli() - : Cli("server-message", - "send a message", - "server-message server target message", - "Send a message to the specified target or channel.\n\n" - "Example:\n" - "\tirccdctl server-message freenode #staff \"Hello from irccd\"") +std::string server_message_cli::name() const { + return "server-message"; } -void ServerMessageCli::exec(Irccdctl &irccdctl, const std::vector<std::string> &args) +void server_message_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args) { if (args.size() < 3) throw std::invalid_argument("server-message requires 3 arguments"); - check(request(irccdctl, { + request(ctl, { { "server", args[0] }, { "target", args[1] }, { "message", args[2] } - })); + }); } /* - * ServerModeCli. + * server_mode_cli. * ------------------------------------------------------------------ */ -ServerModeCli::ServerModeCli() - : Cli("server-mode", - "the the user mode", - "server-mode server mode", - "Set the irccd's user mode.\n\n" - "Example:\n" - "\tirccdctl server-mode +i") +std::string server_mode_cli::name() const { + return "server-mode"; } -void ServerModeCli::exec(Irccdctl &irccdctl, const std::vector<std::string> &args) +void server_mode_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args) { if (args.size() < 2) throw std::invalid_argument("server-mode requires 2 arguments"); - check(request(irccdctl, { + request(ctl, { { "server", args[0] }, { "mode", args[1] } - })); + }); } /* - * ServerNickCli. + * server_nick_cli. * ------------------------------------------------------------------ */ -ServerNickCli::ServerNickCli() - : Cli("server-nick", - "change your nickname", - "server-nick server nickname", - "Change irccd's nickname.\n\n" - "Example:\n" - "\tirccdctl server-nick freenode david") +std::string server_nick_cli::name() const { + return "server-nick"; } -void ServerNickCli::exec(Irccdctl &irccdctl, const std::vector<std::string> &args) +void server_nick_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args) { if (args.size() < 2) throw std::invalid_argument("server-nick requires 2 arguments"); - check(request(irccdctl, { + request(ctl, { { "server", args[0] }, { "nickname", args[1] } - })); + }); } /* - * ServerNoticeCli. + * server_notice_cli. * ------------------------------------------------------------------ */ -ServerNoticeCli::ServerNoticeCli() - : Cli("server-notice", - "send a private notice", - "server-notice server target message", - "Send a private notice to the specified target.\n\n" - "Example:\n" - "\tirccdctl server-notice freenode jean \"I know you are here.\"") +std::string server_notice_cli::name() const { + return "server-notice"; } -void ServerNoticeCli::exec(Irccdctl &irccdctl, const std::vector<std::string> &args) +void server_notice_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args) { if (args.size() < 3) throw std::invalid_argument("server-notice requires 3 arguments"); - check(request(irccdctl, { + request(ctl, { { "server", args[0] }, { "target", args[1] }, { "message", args[2] } - })); + }); } /* - * ServerPartCli. + * server_part_cli. * ------------------------------------------------------------------ */ -ServerPartCli::ServerPartCli() - : Cli("server-part", - "leave a channel", - "server-part server channel [reason]", - "Leave the specified channel, the reason is optional.\n\n" - "Not all IRC servers support giving a reason to leave a channel, do not specify\n" - "it if this is a concern.\n\n" - "Example:\n" - "\tirccdctl server-part freenode #staff\n" - "\tirccdctl server-part freenode #botwar \"too noisy\"") +std::string server_part_cli::name() const { + return "server-part"; } -void ServerPartCli::exec(Irccdctl &irccdctl, const std::vector<std::string> &args) +void server_part_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args) { if (args.size() < 2) throw std::invalid_argument("server-part requires at least 2 arguments"); @@ -787,27 +626,20 @@ if (args.size() >= 3) object["reason"] = args[2]; - check(request(irccdctl, object)); + request(ctl, object); } /* - * ServerReconnectCli. + * server_reconnect_cli. * ------------------------------------------------------------------ */ -ServerReconnectCli::ServerReconnectCli() - : Cli("server-reconnect", - "force reconnection of a server", - "server-reconnect [server]", - "Force reconnection of one or all servers.\n\n" - "If server is not specified, all servers will try to reconnect.\n\n" - "Example:\n" - "\tirccdctl server-reconnect\n" - "\tirccdctl server-reconnect wanadoo") +std::string server_reconnect_cli::name() const { + return "server-reconnect"; } -void ServerReconnectCli::exec(Irccdctl &irccdctl, const std::vector<std::string> &args) +void server_reconnect_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args) { auto object = nlohmann::json::object({ { "command", "server-reconnect" } @@ -816,60 +648,42 @@ if (args.size() >= 1) object["server"] = args[0]; - check(request(irccdctl, object)); + request(ctl, object); } /* - * ServerTopicCli. + * server_topic_cli. * ------------------------------------------------------------------ */ -ServerTopicCli::ServerTopicCli() - : Cli("server-topic", - "change channel topic", - "server-topic server channel topic", - "Change the topic of the specified channel.\n\n" - "Example:\n" - "\tirccdctl server-topic freenode #wmfs \"This is the best channel\"") +std::string server_topic_cli::name() const { + return "server-topic"; } -void ServerTopicCli::exec(Irccdctl &irccdctl, const std::vector<std::string> &args) +void server_topic_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args) { if (args.size() < 3) throw std::invalid_argument("server-topic requires 3 arguments"); - check(request(irccdctl, { + request(ctl, { { "server", args[0] }, { "channel", args[1] }, { "topic", args[2] } - })); + }); } /* - * RuleAddCli. + * rule_add_cli. * ------------------------------------------------------------------ */ -RuleAddCli::RuleAddCli() - : Cli("rule-add", - "add a new rule", - "rule-add [options] accept|drop", - "Add a new rule to irccd.\n\n" - "If no index is specified, the rule is added to the end.\n\n" - "Available options:\n" - " -c, --add-channel\t\tmatch a channel\n" - " -e, --add-event\t\tmatch an event\n" - " -i, --index\t\t\trule position\n" - " -p, --add-plugin\t\tmatch a plugin\n" - " -s, --add-server\t\tmatch a server\n\n" - "Example:\n" - "\tirccdctl rule-add -p hangman drop\n" - "\tirccdctl rule-add -s localhost -c #games -p hangman accept") +std::string rule_add_cli::name() const { + return "rule-add"; } -void RuleAddCli::exec(Irccdctl &irccdctl, const std::vector<std::string> &args) +void rule_add_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args) { static const option::options options{ { "-c", true }, @@ -918,41 +732,24 @@ // And action. if (copy[0] != "accept" && copy[0] != "drop") - throw std::runtime_error("invalid action '"s + copy[0] + "'"); + throw std::runtime_error(string_util::sprintf("invalid action '%s'", copy[0])); json["action"] = copy[0]; - check(request(irccdctl, json)); + request(ctl, json); } /* - * RuleEditCli. + * rule_edit_cli. * ------------------------------------------------------------------ */ -RuleEditCli::RuleEditCli() - : Cli("rule-edit", - "edit an existing rule", - "rule-edit [options] index", - "Edit an existing rule in irccd.\n\n" - "All options can be specified multiple times.\n\n" - "Available options:\n" - " -a, --action\t\t\tset action\n" - " -c, --add-channel\t\tmatch a channel\n" - " -C, --remove-channel\t\tremove a channel\n" - " -e, --add-event\t\tmatch an event\n" - " -E, --remove-event\t\tremove an event\n" - " -p, --add-plugin\t\tmatch a plugin\n" - " -P, --add-plugin\t\tremove a plugin\n" - " -s, --add-server\t\tmatch a server\n" - " -S, --remove-server\t\tremove a server\n\n" - "Example:\n" - "\tirccdctl rule-edit -p hangman 0\n" - "\tirccdctl rule-edit -S localhost -c #games -p hangman 1") +std::string rule_edit_cli::name() const { + return "rule-edit"; } -void RuleEditCli::exec(Irccdctl &irccdctl, const std::vector<std::string> &args) +void rule_edit_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args) { static const option::options options{ { "-a", true }, @@ -1018,17 +815,17 @@ // Index. json["index"] = string_util::to_number<unsigned>(copy[0]); - check(request(irccdctl, json)); + request(ctl, json); } /* - * RuleListCli. + * rule_list_cli. * ------------------------------------------------------------------ */ namespace { -void showRule(const nlohmann::json& json, int index) +void show_rule(const nlohmann::json& json, int index) { assert(json.is_object()); @@ -1062,48 +859,34 @@ } // !namespace -RuleListCli::RuleListCli() - : Cli("rule-list", - "list all rules", - "rule-list", - "List all rules.\n\n" - "Example:\n" - "\tirccdctl rule-list") +std::string rule_list_cli::name() const { + return "rule-list"; } -void RuleListCli::exec(Irccdctl &irccdctl, const std::vector<std::string> &) +void rule_list_cli::exec(ctl::controller& ctl, const std::vector<std::string>&) { - auto response = request(irccdctl); - auto pos = 0; - - check(response); + request(ctl, {{ "command", "rule-list" }}, [] (auto result) { + auto pos = 0; - for (const auto &obj : response["list"]) { - if (!obj.is_object()) - continue; - - showRule(obj, pos++); - } + for (const auto& obj : result["list"]) { + if (obj.is_object()) + show_rule(obj, pos++); + } + }); } /* - * RuleInfoCli. + * rule_info_cli. * ------------------------------------------------------------------ */ -RuleInfoCli::RuleInfoCli() - : Cli("rule-info", - "show a rule", - "rule-info index", - "Show a rule.\n\n" - "Example:\n" - "\tirccdctl rule-info 0\n" - "\tirccdctl rule-info 1") +std::string rule_info_cli::name() const { + return "rule-info"; } -void RuleInfoCli::exec(Irccdctl &irccdctl, const std::vector<std::string> &args) +void rule_info_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args) { if (args.size() < 1) throw std::invalid_argument("rule-info requires 1 argument"); @@ -1116,32 +899,27 @@ throw std::invalid_argument("invalid number '" + args[0] + "'"); } - auto result = request(irccdctl, { + auto json = nlohmann::json::object({ { "command", "rule-info" }, { "index", index } }); - check(result); - showRule(result, 0); + request(ctl, std::move(json), [] (auto result) { + show_rule(result, 0); + }); } /* - * RuleRemoveCli. + * rule_remove_cli. * ------------------------------------------------------------------ */ -RuleRemoveCli::RuleRemoveCli() - : Cli("rule-remove", - "remove a rule", - "rule-remove index", - "Remove an existing rule.\n\n" - "Example:\n" - "\tirccdctl rule-remove 0\n" - "\tirccdctl rule-remove 1") +std::string rule_remove_cli::name() const { + return "rule-remove"; } -void RuleRemoveCli::exec(Irccdctl &irccdctl, const std::vector<std::string> &args) +void rule_remove_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args) { if (args.size() < 1) throw std::invalid_argument("rule-remove requires 1 argument"); @@ -1154,34 +932,23 @@ throw std::invalid_argument("invalid number '" + args[0] + "'"); } - auto result = request(irccdctl, { + request(ctl, { { "command", "rule-remove" }, { "index", index } }); - - check(result); } /* - * RuleMoveCli. + * rule_move_cli. * ------------------------------------------------------------------ */ -RuleMoveCli::RuleMoveCli() - : Cli("rule-move", - "move a rule to a new position", - "rule-move source destination", - "Move a rule from the given source at the specified destination index.\n\n" - "The rule will replace the existing one at the given destination moving\ndown every " - "other rules. If destination is greater or equal the number of rules,\nthe rule " - "is moved to the end.\n\n" - "Example:\n" - "\tirccdctl rule-move 0 5\n" - "\tirccdctl rule-move 4 3") +std::string rule_move_cli::name() const { + return "rule-move"; } -void RuleMoveCli::exec(Irccdctl &irccdctl, const std::vector<std::string> &args) +void rule_move_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args) { if (args.size() < 2) throw std::invalid_argument("rule-move requires 2 arguments"); @@ -1196,11 +963,11 @@ throw std::invalid_argument("invalid number"); } - check(request(irccdctl, { + request(ctl, { { "command", "rule-move" }, { "from", from }, { "to", to } - })); + }); } /* @@ -1361,7 +1128,7 @@ std::cout << "realname: " << json_util::pretty(v, "realname") << "\n"; } -const std::unordered_map<std::string, std::function<void (const nlohmann::json &)>> events{ +const std::unordered_map<std::string, std::function<void (const nlohmann::json&)>> events{ { "onChannelMode", onChannelMode }, { "onChannelNotice", onChannelNotice }, { "onConnect", onConnect }, @@ -1380,60 +1147,42 @@ { "onWhois", onWhois } }; +void get_event(ctl::controller& ctl, std::string fmt) +{ + ctl.recv([&ctl, fmt] (auto code, auto message) { + if (code) + throw boost::system::system_error(code); + + auto it = events.find(json_util::to_string(message["event"])); + + if (it != events.end()) { + if (fmt == "json") + std::cout << message.dump(4) << std::endl; + else { + it->second(message); + std::cout << std::endl; + } + } + + get_event(ctl, std::move(fmt)); + }); +} + } // !namespace -WatchCli::WatchCli() - : Cli("watch", - "watch irccd events", - "watch [-f|--format json|native]", - "Start watching irccd events.\n\n" - "You can use different output formats, native is human readable format, json is\n" - "pretty formatted json.\n\n" - "Example:\n" - "\tirccdctl watch\n" - "\tirccdctl watch -f json") +std::string watch_cli::name() const { + return "watch"; } -void WatchCli::exec(Irccdctl& client, const std::vector<std::string>& args) +void watch_cli::exec(ctl::controller& ctl, const std::vector<std::string>& args) { - std::string fmt = format(args); + auto fmt = format(args); if (fmt != "native" && fmt != "json") throw std::invalid_argument("invalid format given: " + fmt); - auto id = client.client().onEvent.connect([&] (auto ev) { - try { - auto name = ev.find("event"); - - if (name == ev.end() || !name->is_string()) - return; - - auto it = events.find(*name); - - // Silently ignore to avoid breaking user output. - if (it == events.end()) - return; - - if (fmt == "json") - std::cout << ev.dump(4) << std::endl; - else { - it->second(ev); - std::cout << std::endl; - } - } catch (...) { - } - }); - - try { - while (client.client().isConnected()) { - net_util::poll(500, client); - } - } catch (const std::exception &ex) { - log::warning() << ex.what() << std::endl; - } - - client.client().onEvent.disconnect(id); + get_event(ctl, fmt); } } // !cli
--- a/irccdctl/cli.hpp Tue Nov 14 20:25:30 2017 +0100 +++ b/irccdctl/cli.hpp Thu Nov 16 22:45:12 2017 +0100 @@ -26,713 +26,541 @@ namespace irccd { -class Irccdctl; +namespace ctl { + +class controller; /* - * Cli. + * cli. * ------------------------------------------------------------------ */ /** * \brief Abstract CLI class. */ -class Cli { -protected: - std::string m_name; - std::string m_summary; - std::string m_usage; - std::string m_help; +class cli { +public: + using handler_t = std::function<void (nlohmann::json)>; - /** - * Check the message result and print an error if any. - * - * \param result the result - * \throw an error if any - */ - void check(const nlohmann::json &result); +private: + void recv_response(ctl::controller&, nlohmann::json, handler_t); +protected: /** - * Send a request and wait for the response. + * Convenient request helper. + * + * This function send and receive the response for the given request. It + * checks for an error code or string in the command result and throws it if + * any. * - * \param irccdctl the client - * \param args the optional arguments + * If handler is not null, it will be called once the command result has + * been received. + * + * This function may executes successive read calls until we get the + * response. + * + * \param ctl the controller + * \param json the json object + * \param handler the optional handler */ - nlohmann::json request(Irccdctl &irccdctl, nlohmann::json args = nullptr); - - /** - * Call a command and wait for success/error response. - * - * \param irccdctl the client - * \param args the extra arguments (may be null) - */ - void call(Irccdctl &irccdctl, nlohmann::json args); + void request(ctl::controller& ctl, nlohmann::json json, handler_t handler = nullptr); public: - inline Cli(std::string name, std::string summary, std::string usage, std::string help) noexcept - : m_name(std::move(name)) - , m_summary(std::move(summary)) - , m_usage(std::move(usage)) - , m_help(std::move(help)) - { - } - - virtual ~Cli() = default; + cli() noexcept = default; - inline const std::string &name() const noexcept - { - return m_name; - } - - inline const std::string &summary() const noexcept - { - return m_summary; - } + virtual ~cli() noexcept = default; - inline const std::string &usage() const noexcept - { - return m_usage; - } - - inline const std::string &help() const noexcept - { - return m_help; - } + virtual std::string name() const = 0; - virtual void exec(Irccdctl &client, const std::vector<std::string> &args) = 0; + virtual void exec(ctl::controller& ctl, const std::vector<std::string>& args) = 0; }; -namespace cli { - -/* - * PluginConfigCli. - * ------------------------------------------------------------------ - */ - /** * \brief Implementation of irccdctl plugin-config. */ -class PluginConfigCli : public Cli { +class plugin_config_cli : public cli { private: - void set(Irccdctl &, const std::vector<std::string> &); - void get(Irccdctl &, const std::vector<std::string> &); - void getall(Irccdctl &, const std::vector<std::string> &); + void set(ctl::controller&, const std::vector<std::string>&); + void get(ctl::controller&, const std::vector<std::string>&); + void getall(ctl::controller&, const std::vector<std::string>&); public: /** - * Default constructor. + * \copydoc cli::name */ - PluginConfigCli(); + std::string name() const override; /** - * \copydoc Cli::exec + * \copydoc cli::exec */ - void exec(Irccdctl &irccdctl, const std::vector<std::string> &args) override; + void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override; }; -/* - * PluginInfoCli. - * ------------------------------------------------------------------ - */ - /** * \brief Implementation of irccdctl plugin-info. */ -class PluginInfoCli : public Cli { +class plugin_info_cli : public cli { public: /** - * Default constructor. + * \copydoc cli::name */ - PluginInfoCli(); + std::string name() const override; /** - * \copydoc Cli::exec + * \copydoc cli::exec */ - void exec(Irccdctl &irccdctl, const std::vector<std::string> &args) override; + void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override; }; -/* - * PluginListCli. - * ------------------------------------------------------------------ - */ - /** * \brief Implementation of irccdctl plugin-list. */ -class PluginListCli : public Cli { +class plugin_list_cli : public cli { public: /** - * Default constructor. + * \copydoc cli::name */ - PluginListCli(); + std::string name() const override; /** - * \copydoc Cli::exec + * \copydoc cli::exec */ - void exec(Irccdctl &irccdctl, const std::vector<std::string> &args) override; + void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override; }; -/* - * PluginLoadCli. - * ------------------------------------------------------------------ - */ - /** * \brief Implementation of irccdctl plugin-load. */ -class PluginLoadCli : public Cli { +class plugin_load_cli : public cli { public: /** - * Default constructor. + * \copydoc cli::name */ - PluginLoadCli(); + std::string name() const override; /** - * \copydoc Cli::exec + * \copydoc cli::exec */ - void exec(Irccdctl &irccdctl, const std::vector<std::string> &args) override; + void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override; }; -/* - * PluginReloadCli. - * ------------------------------------------------------------------ - */ - /** * \brief Implementation of irccdctl plugin-reload. */ -class PluginReloadCli : public Cli { +class plugin_reload_cli : public cli { public: - PluginReloadCli(); + /** + * \copydoc cli::name + */ + std::string name() const override; /** - * \copydoc Command::exec + * \copydoc cli::exec */ - void exec(Irccdctl &irccdctl, const std::vector<std::string> &args) override; + void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override; }; -/* - * PluginUnloadCli. - * ------------------------------------------------------------------ - */ - /** * \brief Implementation of irccdctl plugin-unload. */ -class PluginUnloadCli : public Cli { +class plugin_unload_cli : public cli { public: - PluginUnloadCli(); + /** + * \copydoc cli::name + */ + std::string name() const override; /** - * \copydoc Cli::exec + * \copydoc cli::exec */ - void exec(Irccdctl &irccdctl, const std::vector<std::string> &args) override; + void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override; }; -/* - * ServerChannelCli. - * ------------------------------------------------------------------ - */ - /** * \brief Implementation of irccdctl server-cmode. */ -class ServerChannelMode : public Cli { +class server_channel_mode_cli : public cli { public: /** - * Default constructor. + * \copydoc cli::name */ - ServerChannelMode(); + std::string name() const override; /** - * \copydoc Cli::exec + * \copydoc cli::exec */ - void exec(Irccdctl &irccdctl, const std::vector<std::string> &args) override; + void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override; }; -/* - * ServerChannelNoticeCli. - * ------------------------------------------------------------------ - */ - /** * \brief Implementation of irccdctl server-cnotice. */ -class ServerChannelNoticeCli : public Cli { +class server_channel_notice_cli : public cli { public: /** - * Default constructor. + * \copydoc cli::name */ - ServerChannelNoticeCli(); + std::string name() const override; /** - * \copydoc Cli::exec + * \copydoc cli::exec */ - void exec(Irccdctl &irccdctl, const std::vector<std::string> &args) override; + void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override; }; -/* - * ServerConnectCli. - * ------------------------------------------------------------------ - */ - /** * \brief Implementation of irccdctl server-connect. */ -class ServerConnectCli : public Cli { +class server_connect_cli : public cli { public: /** - * Default constructor. + * \copydoc cli::name */ - ServerConnectCli(); + std::string name() const override; /** - * \copydoc Cli::exec + * \copydoc cli::exec */ - void exec(Irccdctl &irccdctl, const std::vector<std::string> &args) override; + void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override; }; -/* - * ServerDisconnectCli. - * ------------------------------------------------------------------ - */ - /** * \brief Implementation of irccdctl server-disconnect. */ -class ServerDisconnectCli : public Cli { +class server_disconnect_cli : public cli { public: /** - * Default constructor. + * \copydoc cli::name */ - ServerDisconnectCli(); + std::string name() const override; /** - * \copydoc Cli::exec + * \copydoc cli::exec */ - void exec(Irccdctl &irccdctl, const std::vector<std::string> &args) override; + void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override; }; -/* - * ServerInfoCli. - * ------------------------------------------------------------------ - */ - /** * \brief Implementation of irccdctl server-info. */ -class ServerInfoCli : public Cli { +class server_info_cli : public cli { public: /** - * Default constructor. + * \copydoc cli::name */ - ServerInfoCli(); + std::string name() const override; /** - * \copydoc Cli::exec + * \copydoc cli::exec */ - void exec(Irccdctl &irccdctl, const std::vector<std::string> &args) override; + void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override; }; -/* - * ServerInviteCli. - * ------------------------------------------------------------------ - */ - /** * \brief Implementation of irccdctl server-invite. */ -class ServerInviteCli : public Cli { +class server_invite_cli : public cli { public: /** - * Default constructor. + * \copydoc cli::name */ - ServerInviteCli(); + std::string name() const override; /** - * \copydoc Cli::exec + * \copydoc cli::exec */ - void exec(Irccdctl &irccdctl, const std::vector<std::string> &args) override; + void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override; }; -/* - * ServerJoinCli. - * ------------------------------------------------------------------ - */ - /** * \brief Implementation of irccdctl server-join. */ -class ServerJoinCli : public Cli { +class server_join_cli : public cli { public: /** - * Default constructor. + * \copydoc cli::name */ - ServerJoinCli(); + std::string name() const override; /** - * \copydoc Cli::exec + * \copydoc cli::exec */ - void exec(Irccdctl &irccdctl, const std::vector<std::string> &args) override; + void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override; }; -/* - * ServerKickCli. - * ------------------------------------------------------------------ - */ - /** * \brief Implementation of irccdctl server-kick. */ -class ServerKickCli : public Cli { +class server_kick_cli : public cli { public: /** - * Default constructor. + * \copydoc cli::name */ - ServerKickCli(); + std::string name() const override; /** - * \copydoc Cli::exec + * \copydoc cli::exec */ - void exec(Irccdctl &irccdctl, const std::vector<std::string> &args) override; + void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override; }; -/* - * ServerListCli. - * ------------------------------------------------------------------ - */ - /** * \brief Implementation of irccdctl server-list. */ -class ServerListCli : public Cli { +class server_list_cli : public cli { public: /** - * Default constructor. + * \copydoc cli::name */ - ServerListCli(); + std::string name() const override; /** - * \copydoc Cli::exec + * \copydoc cli::exec */ - void exec(Irccdctl &irccdctl, const std::vector<std::string> &args) override; + void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override; }; -/* - * ServerMeCli. - * ------------------------------------------------------------------ - */ - /** * \brief Implementation of irccdctl server-me. */ -class ServerMeCli : public Cli { +class server_me_cli : public cli { public: /** - * Default constructor. + * \copydoc cli::name */ - ServerMeCli(); + std::string name() const override; /** - * \copydoc Command::exec + * \copydoc cli::exec */ - void exec(Irccdctl &irccdctl, const std::vector<std::string> &args) override; + void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override; }; -/* - * ServerMessageCli. - * ------------------------------------------------------------------ - */ - /** * \brief Implementation of irccdctl server-message. */ -class ServerMessageCli : public Cli { +class server_message_cli : public cli { public: /** - * Default constructor. + * \copydoc cli::name */ - ServerMessageCli(); + std::string name() const override; /** - * \copydoc Cli::exec + * \copydoc cli::exec */ - void exec(Irccdctl &irccdctl, const std::vector<std::string> &args) override; + void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override; }; -/* - * ServerModeCli. - * ------------------------------------------------------------------ - */ - /** * \brief Implementation of irccdctl server-mode. */ -class ServerModeCli : public Cli { +class server_mode_cli : public cli { public: /** - * Default constructor. + * \copydoc cli::name */ - ServerModeCli(); + std::string name() const override; /** - * \copydoc Cli::exec + * \copydoc cli::exec */ - void exec(Irccdctl &irccdctl, const std::vector<std::string> &args) override; + void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override; }; -/* - * ServerNickCli. - * ------------------------------------------------------------------ - */ - /** * \brief Implementation of irccdctl server-nick. */ -class ServerNickCli : public Cli { +class server_nick_cli : public cli { public: /** - * Default constructor. + * \copydoc cli::name */ - ServerNickCli(); + std::string name() const override; /** - * \copydoc Cli::exec + * \copydoc cli::exec */ - void exec(Irccdctl &irccdctl, const std::vector<std::string> &args) override; + void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override; }; -/* - * ServerNoticeCli. - * ------------------------------------------------------------------ - */ - /** * \brief Implementation of irccdctl server-notice. */ -class ServerNoticeCli : public Cli { +class server_notice_cli : public cli { public: /** - * Default constructor. + * \copydoc cli::name */ - ServerNoticeCli(); + std::string name() const override; /** - * \copydoc Cli::exec + * \copydoc cli::exec */ - void exec(Irccdctl &irccdctl, const std::vector<std::string> &args) override; + void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override; }; -/* - * ServerPartCli. - * ------------------------------------------------------------------ - */ - /** * \brief Implementation of irccdctl server-part. */ -class ServerPartCli : public Cli { +class server_part_cli : public cli { public: /** - * Default constructor. + * \copydoc cli::name */ - ServerPartCli(); + std::string name() const override; /** - * \copydoc Cli::exec + * \copydoc cli::exec */ - void exec(Irccdctl &irccdctl, const std::vector<std::string> &args) override; + void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override; }; -/* - * ServerReconnectCli. - * ------------------------------------------------------------------ - */ - /** * \brief Implementation of irccdctl server-reconnect. */ -class ServerReconnectCli : public Cli { +class server_reconnect_cli : public cli { public: /** - * Default constructor. + * \copydoc cli::name */ - ServerReconnectCli(); + std::string name() const override; /** - * \copydoc Cli::exec + * \copydoc cli::exec */ - void exec(Irccdctl &irccdctl, const std::vector<std::string> &args) override; + void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override; }; -/* - * ServerTopicCli. - * ------------------------------------------------------------------ - */ - /** * \brief Implementation of irccdctl server-topic. */ -class ServerTopicCli : public Cli { +class server_topic_cli : public cli { public: /** - * Default constructor. + * \copydoc cli::name */ - ServerTopicCli(); + std::string name() const override; /** - * \copydoc Cli::exec + * \copydoc cli::exec */ - void exec(Irccdctl &client, const std::vector<std::string> &args) override; + void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override; }; -/* - * RuleAddCli. - * ------------------------------------------------------------------ - */ - /** * \brief Implementation of irccdctl rule-add. */ -class RuleAddCli : public Cli { +class rule_add_cli : public cli { public: /** - * Default constructor. + * \copydoc cli::name */ - RuleAddCli(); + std::string name() const override; /** - * \copydoc Cli::exec + * \copydoc cli::exec */ - void exec(Irccdctl &client, const std::vector<std::string> &args) override; + void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override; }; -/* - * RuleEditCli. - * ------------------------------------------------------------------ - */ - /** * \brief Implementation of irccdctl rule-edit. */ -class RuleEditCli : public Cli { +class rule_edit_cli : public cli { public: /** - * Default constructor. + * \copydoc cli::name */ - RuleEditCli(); + std::string name() const override; /** - * \copydoc Cli::exec + * \copydoc cli::exec */ - void exec(Irccdctl &client, const std::vector<std::string> &args) override; + void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override; }; -/* - * RuleListCli. - * ------------------------------------------------------------------ - */ - /** * \brief Implementation of irccdctl rule-list. */ -class RuleListCli : public Cli { +class rule_list_cli : public cli { public: /** - * Default constructor. + * \copydoc cli::name */ - RuleListCli(); + std::string name() const override; /** - * \copydoc Cli::exec + * \copydoc cli::exec */ - void exec(Irccdctl &client, const std::vector<std::string> &args) override; + void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override; }; -/* - * RuleInfoCli - * ------------------------------------------------------------------ - */ - /** * \brief Implementation of irccdctl rule-info. */ -class RuleInfoCli : public Cli { +class rule_info_cli : public cli { public: /** - * Default constructor. + * \copydoc cli::name */ - RuleInfoCli(); + std::string name() const override; /** - * \copydoc Cli::exec + * \copydoc cli::exec */ - void exec(Irccdctl &client, const std::vector<std::string> &args) override; + void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override; }; -/* - * RuleRemoveCli - * ------------------------------------------------------------------ - */ - /** * \brief Implementation of irccdctl rule-remove. */ -class RuleRemoveCli : public Cli { +class rule_remove_cli : public cli { public: /** - * Default constructor. + * \copydoc cli::name */ - RuleRemoveCli(); + std::string name() const override; /** - * \copydoc Cli::exec + * \copydoc cli::exec */ - void exec(Irccdctl &client, const std::vector<std::string> &args) override; + void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override; }; -/* - * RuleMoveCli - * ------------------------------------------------------------------ - */ - /** * \brief Implementation of irccdctl rule-move. */ -class RuleMoveCli : public Cli { +class rule_move_cli : public cli { public: /** - * Default constructor. + * \copydoc cli::name */ - RuleMoveCli(); + std::string name() const override; /** - * \copydoc Cli::exec + * \copydoc cli::exec */ - void exec(Irccdctl &client, const std::vector<std::string> &args) override; + void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override; }; -/* - * WatchCli. - * ------------------------------------------------------------------ - */ - /** * \brief Implementation of irccdctl watch. */ -class WatchCli : public Cli { +class watch_cli : public cli { public: /** - * Default constructor. + * \copydoc cli::name */ - WatchCli(); + std::string name() const override; /** - * \copydoc Cli::exec + * \copydoc cli::exec */ - void exec(Irccdctl &client, const std::vector<std::string> &args) override; + void exec(ctl::controller& irccdctl, const std::vector<std::string>& args) override; }; -} // !cli +} // !ctl } // !irccd
--- a/irccdctl/main.cpp Tue Nov 14 20:25:30 2017 +0100 +++ b/irccdctl/main.cpp Thu Nov 16 22:45:12 2017 +0100 @@ -16,92 +16,67 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#include <irccd/sysconfig.hpp> + #include <unordered_map> #include <boost/filesystem.hpp> #include <boost/timer/timer.hpp> +#include <irccd/ini.hpp> +#include <irccd/json_util.hpp> +#include <irccd/logger.hpp> +#include <irccd/options.hpp> +#include <irccd/string_util.hpp> +#include <irccd/system.hpp> +#include <irccd/util.hpp> + +#include <irccd/ctl/controller.hpp> +#include <irccd/ctl/ip_connection.hpp> + +#if !defined(IRCCD_SYSTEM_WINDOWS) +# include <irccd/ctl/local_connection.hpp> +#endif + #include "alias.hpp" #include "cli.hpp" -#include "client.hpp" -#include "ini.hpp" -#include "irccdctl.hpp" -#include "logger.hpp" -#include "options.hpp" -#include "system.hpp" -#include "string_util.hpp" -#include "util.hpp" -using namespace std::string_literals; -using namespace irccd; +namespace irccd { + +namespace ctl { namespace { -std::vector<std::unique_ptr<Cli>> commands; -std::unordered_map<std::string, Alias> aliases; -std::unique_ptr<Client> client; -std::unique_ptr<Irccdctl> irccdctl; -net::Address address; +// Main service; +boost::asio::io_service service; -void usage() -{ - bool first = true; +#if defined(HAVE_SSL) - for (const auto &cmd : commands) { - log::warning() << (first ? "usage: " : " ") << sys::program_name() << " " - << cmd->usage() << std::endl; - first = false; - } - - std::exit(1); -} +// For tls_connection. +boost::asio::ssl::context ctx(boost::asio::ssl::context::sslv23); -void help() -{ - log::warning() << "usage: " << sys::program_name() << " [options...] <command> [command-options...] [command-args...]\n\n"; - log::warning() << "General options:\n"; - log::warning() << "\t-c, --config file\tspecify the configuration file\n"; - log::warning() << "\t --help\t\tshow this help\n"; - log::warning() << "\t-t, --type type\t\tspecify connection type\n"; - log::warning() << "\t-v, --verbose\t\tbe verbose\n\n"; - log::warning() << "Available options for type ip and ipv6 (-t, --type):\n"; - log::warning() << "\t-h, --host address\tconnect to the specified address\n"; - log::warning() << "\t-p, --port port\t\tuse the specified port number\n\n"; - log::warning() << "Available options for type unix (-t, --type):\n"; - log::warning() << "\t-P, --path file\t\tconnect to the specified socket file\n\n"; - log::warning() << "Available commands:\n"; - - for (const auto &cmd : commands) - log::warning() << "\t" << std::left << std::setw(32) - << cmd->name() << cmd->summary() << std::endl; +#endif - log::warning() << "\nFor more information on a command, type " << sys::program_name() << " help <command>" << std::endl; - std::exit(1); -} - -void help(const std::string &command) -{ - auto it = std::find_if(commands.begin(), commands.end(), [&] (const auto &c) { - return c->name() == command; - }); +// Connection to instance. +std::unique_ptr<connection> conn; +std::unique_ptr<controller> ctl; - if (it == commands.end()) { - log::warning() << "no command named " << command << std::endl; - } else { - log::warning() << "usage: " << sys::program_name() << " " << (*it)->usage() << "\n" << std::endl; - log::warning() << (*it)->help() << std::endl; - } - - std::exit(1); -} +// List of all commands and alias. +std::unordered_map<std::string, alias> aliases; +std::unordered_map<std::string, std::unique_ptr<cli>> commands; /* * Configuration file parsing. * ------------------------------------------------------------------- */ +void usage() +{ + std::exit(1); +} + /* - * readConnectIp + * read_connect_ip * ------------------------------------------------------------------- * * Extract IP connection information from the config file. @@ -113,10 +88,12 @@ * domain = "ipv4 or ipv6" (Optional, default: ipv4) * ssl = true | false */ -void readConnectIp(const ini::section &sc) +std::unique_ptr<connection> read_connect_ip(const ini::section& sc) { + std::unique_ptr<connection> conn; + std::string host; + std::uint16_t port; ini::section::const_iterator it; - std::string host, port; if ((it = sc.find("host")) == sc.end()) throw std::invalid_argument("missing host parameter"); @@ -126,34 +103,22 @@ if ((it = sc.find("port")) == sc.end()) throw std::invalid_argument("missing port parameter"); - port = it->value(); - - int domain = AF_INET; - - if ((it = sc.find("domain")) != sc.end() || (it = sc.find("family")) != sc.end()) { - if (it->value() == "ipv6") { - domain = AF_INET6; - } else if (it->value() == "ipv4") { - domain = AF_INET; - } else { - throw std::invalid_argument("invalid family: " + it->value()); - } - } - - address = net::resolveOne(host, port, domain, SOCK_STREAM); + port = string_util::to_number<std::uint16_t>(it->value()); if ((it = sc.find("ssl")) != sc.end() && string_util::is_boolean(it->value())) #if defined(HAVE_SSL) - client = std::make_unique<TlsClient>(); + conn = std::make_unique<tls_connection>(service, ctx, host, port); #else throw std::runtime_error("SSL disabled"); #endif else - client = std::make_unique<Client>(); + conn = std::make_unique<ip_connection>(service, host, port); + + return conn; } /* - * readConnectLocal + * read_connect_local * ------------------------------------------------------------------- * * Extract local connection for Unix. @@ -162,7 +127,7 @@ * type = "unix" * path = "path to socket file" */ -void readConnectLocal(const ini::section &sc) +std::unique_ptr<connection> read_connect_local(const ini::section& sc) { #if !defined(IRCCD_SYSTEM_WINDOWS) auto it = sc.find("path"); @@ -170,8 +135,7 @@ if (it == sc.end()) throw std::invalid_argument("missing path parameter"); - address = net::local::create(it->value()); - client = std::make_unique<Client>(); + return std::make_unique<local_connection>(service, it->value()); #else (void)sc; @@ -180,34 +144,37 @@ } /* - * readConnect + * read_connect * ------------------------------------------------------------------- * * Generic function for reading the [connect] section. */ -void readConnect(const ini::section &sc) +void read_connect(const ini::section& sc) { auto it = sc.find("type"); if (it == sc.end()) throw std::invalid_argument("missing type parameter"); - if (it->value() == "ip") { - readConnectIp(sc); - } else if (it->value() == "unix") { - readConnectLocal(sc); - } else { - throw std::invalid_argument("invalid type given: " + it->value()); + if (it->value() == "ip") + conn = read_connect_ip(sc); + else if (it->value() == "unix") + conn = read_connect_local(sc); + else + throw std::invalid_argument(string_util::sprintf("invalid type given: %s", it->value())); + + if (conn) { + ctl = std::make_unique<controller>(*conn); + + auto password = sc.find("password"); + + if (password != sc.end()) + ctl->set_password(password->value()); } - - auto password = sc.find("password"); - - if (password != sc.end()) - client->setPassword(password->value()); } /* - * readGeneral + * read_general * ------------------------------------------------------------------- * * Read the general section. @@ -215,7 +182,7 @@ * [general] * verbose = true */ -void readGeneral(const ini::section &sc) +void read_general(const ini::section& sc) { auto verbose = sc.find("verbose"); @@ -224,7 +191,7 @@ } /* - * readAlias + * read_alias * ------------------------------------------------------------------- * * Read aliases for irccdctl. @@ -233,15 +200,15 @@ * cmd1 = ( "command", "arg1, "...", "argn" ) * cmd2 = ( "command", "arg1, "...", "argn" ) */ -Alias readAlias(const ini::section &sc, const std::string &name) +alias read_alias(const ini::section& sc, const std::string& name) { - Alias alias(name); + alias alias(name); /* * Each defined option is a command that the user can call. The name is * unused and serves as documentation purpose. */ - for (const auto &option : sc) { + for (const auto& option : sc) { /* * Iterate over the arguments which are usually a list and the first * argument is a command name. @@ -250,7 +217,7 @@ throw std::runtime_error(string_util::sprintf("alias %s: missing command name in '%s'", name, option.key())); std::string command = option[0]; - std::vector<AliasArg> args(option.begin() + 1, option.end()); + std::vector<alias_arg> args(option.begin() + 1, option.end()); alias.emplace_back(std::move(command), std::move(args)); } @@ -258,22 +225,22 @@ return alias; } -void read(const std::string &path) +void read(const std::string& path) { try { ini::document doc = ini::read_file(path); ini::document::const_iterator it; - if (!client && (it = doc.find("connect")) != doc.end()) - readConnect(*it); + if (!ctl && (it = doc.find("connect")) != doc.end()) + read_connect(*it); if ((it = doc.find("general")) != doc.end()) - readGeneral(*it); + read_general(*it); // [alias.*] sections. for (const auto& sc : doc) { if (sc.key().compare(0, 6, "alias.") == 0) { auto name = sc.key().substr(6); - auto alias = readAlias(sc, name); + auto alias = read_alias(sc, name); aliases.emplace(std::move(name), std::move(alias)); } @@ -289,7 +256,7 @@ */ /* - * parseConnectIp + * parse_connect_ip * ------------------------------------------------------------------ * * Parse internet connection from command line. @@ -298,50 +265,35 @@ * -h host or ip * -p port */ -void parseConnectIp(const option::result &options) +std::unique_ptr<connection> parse_connect_ip(const option::result& options) { option::result::const_iterator it; // Host (-h or --host). - std::string host; - - if ((it = options.find("-h")) == options.end() && (it = options.find("--host")) == options.end()) { + if ((it = options.find("-h")) == options.end() && (it = options.find("--host")) == options.end()) throw std::invalid_argument("missing host argument (-h or --host)"); - } - host = it->second; + auto host = it->second; // Port (-p or --port). - std::string port; - - if ((it = options.find("-p")) == options.end() && (it = options.find("--port")) == options.end()) { + if ((it = options.find("-p")) == options.end() && (it = options.find("--port")) == options.end()) throw std::invalid_argument("missing port argument (-p or --port)"); - } - - port = it->second; - // Domain - int domain = AF_INET; + auto port = string_util::to_number<std::uint16_t>(it->second); - if ((it = options.find("-t")) != options.end()) { - domain = it->second == "ipv6" ? AF_INET6 : AF_INET; - } else if ((it = options.find("--type")) != options.end()) { - domain = it->second == "ipv6" ? AF_INET6: AF_INET; - } - address = net::resolveOne(host, port, domain, SOCK_STREAM); - client = std::make_unique<Client>(); + return std::make_unique<ip_connection>(service, host, port); } /* - * parseConnectLocal + * parse_connect_local * ------------------------------------------------------------------ * * Parse local connection. * * -P file */ -void parseConnectLocal(const option::result &options) +std::unique_ptr<connection> parse_connect_local(const option::result& options) { #if !defined(IRCCD_SYSTEM_WINDOWS) option::result::const_iterator it; @@ -349,8 +301,7 @@ if ((it = options.find("-P")) == options.end() && (it = options.find("--path")) == options.end()) throw std::invalid_argument("missing path parameter (-P or --path)"); - address = net::local::create(it->second, false); - client = std::make_unique<Client>(); + return std::make_unique<local_connection>(service, it->second); #else (void)options; @@ -359,12 +310,12 @@ } /* - * parseConnect + * parse_connect * ------------------------------------------------------------------ * * Generic parsing of command line option for connection. */ -void parseConnect(const option::result &options) +void parse_connect(const option::result& options) { assert(options.count("-t") > 0 || options.count("--type") > 0); @@ -372,15 +323,19 @@ if (it == options.end()) it = options.find("--type"); + if (it->second == "ip" || it->second == "ipv6") - return parseConnectIp(options); + conn = parse_connect_ip(options); if (it->second == "unix") - return parseConnectLocal(options); + conn = parse_connect_local(options); + else + throw std::invalid_argument(string_util::sprintf("invalid type given: %s", it->second)); - throw std::invalid_argument("invalid type given: " + it->second); + if (conn) + ctl = std::make_unique<controller>(*conn); } -option::result parse(int &argc, char **&argv) +option::result parse(int& argc, char**& argv) { // 1. Parse command line options. option::options def{ @@ -404,15 +359,13 @@ try { result = option::read(argc, argv, def); - if (result.count("--help") != 0) { + if (result.count("--help") > 0 || result.count("-h") > 0) usage(); // NOTREACHED - } - if (result.count("-v") != 0 || result.count("--verbose") != 0) { + if (result.count("-v") != 0 || result.count("--verbose") != 0) log::set_verbose(true); - } - } catch (const std::exception &ex) { + } catch (const std::exception& ex) { log::warning() << "irccdctl: " << ex.what() << std::endl; usage(); } @@ -422,31 +375,27 @@ void exec(std::vector<std::string>); -void exec(const Alias &alias, std::vector<std::string> argsCopy) +void exec(const alias& alias, std::vector<std::string> args_copy) { - std::vector<nlohmann::json> values; - - for (const AliasCommand &cmd : alias) { - std::vector<std::string> args(argsCopy); - std::vector<std::string> cmdArgs; + for (const auto& cmd : alias) { + std::vector<std::string> args(args_copy); + std::vector<std::string> cmd_args; std::vector<std::string>::size_type toremove = 0; // 1. Append command name before. - cmdArgs.push_back(cmd.command()); + cmd_args.push_back(cmd.command()); - for (const auto &arg : cmd.args()) { - if (arg.isPlaceholder()) { + for (const auto& arg : cmd.args()) { + if (arg.is_placeholder()) { if (args.size() < arg.index() + 1) throw std::invalid_argument(string_util::sprintf("missing argument for placeholder %d", arg.index())); - cmdArgs.push_back(args[arg.index()]); + cmd_args.push_back(args[arg.index()]); - if (arg.index() + 1 > toremove) { + if (arg.index() + 1 > toremove) toremove = arg.index() + 1; - } - } else { - cmdArgs.push_back(arg.value()); - } + } else + cmd_args.push_back(arg.value()); } assert(toremove <= args.size()); @@ -455,10 +404,10 @@ args.erase(args.begin(), args.begin() + toremove); // 3. Now append the rest of arguments. - std::copy(args.begin(), args.end(), std::back_inserter(cmdArgs)); + std::copy(args.begin(), args.end(), std::back_inserter(cmd_args)); // 4. Finally try to execute. - exec(cmdArgs); + exec(cmd_args); } } @@ -472,69 +421,108 @@ // Remove name. args.erase(args.begin()); - if (alias != aliases.end()) { + if (alias != aliases.end()) exec(alias->second, args); - } else { - auto cmd = std::find_if(commands.begin(), commands.end(), [&] (auto &it) { - return it->name() == name; - }); + else { + auto cmd = commands.find(name); - if (cmd != commands.end()) { - (*cmd)->exec(*irccdctl, args); - } else { + if (cmd != commands.end()) + cmd->second->exec(*ctl, args); + else throw std::invalid_argument("no alias or command named " + name); - } } } void init(int &argc, char **&argv) { sys::set_program_name("irccdctl"); - net::init(); + + -- argc; + ++ argv; - --argc; - ++argv; + auto add = [] (auto c) { + commands.emplace(c->name(), std::move(c)); + }; - commands.push_back(std::make_unique<cli::PluginConfigCli>()); - commands.push_back(std::make_unique<cli::PluginInfoCli>()); - commands.push_back(std::make_unique<cli::PluginListCli>()); - commands.push_back(std::make_unique<cli::PluginLoadCli>()); - commands.push_back(std::make_unique<cli::PluginReloadCli>()); - commands.push_back(std::make_unique<cli::PluginUnloadCli>()); - commands.push_back(std::make_unique<cli::ServerChannelMode>()); - commands.push_back(std::make_unique<cli::ServerChannelNoticeCli>()); - commands.push_back(std::make_unique<cli::ServerConnectCli>()); - commands.push_back(std::make_unique<cli::ServerDisconnectCli>()); - commands.push_back(std::make_unique<cli::ServerInfoCli>()); - commands.push_back(std::make_unique<cli::ServerInviteCli>()); - commands.push_back(std::make_unique<cli::ServerJoinCli>()); - commands.push_back(std::make_unique<cli::ServerKickCli>()); - commands.push_back(std::make_unique<cli::ServerListCli>()); - commands.push_back(std::make_unique<cli::ServerMeCli>()); - commands.push_back(std::make_unique<cli::ServerMessageCli>()); - commands.push_back(std::make_unique<cli::ServerModeCli>()); - commands.push_back(std::make_unique<cli::ServerNickCli>()); - commands.push_back(std::make_unique<cli::ServerNoticeCli>()); - commands.push_back(std::make_unique<cli::ServerPartCli>()); - commands.push_back(std::make_unique<cli::ServerReconnectCli>()); - commands.push_back(std::make_unique<cli::ServerTopicCli>()); - commands.push_back(std::make_unique<cli::RuleAddCli>()); - commands.push_back(std::make_unique<cli::RuleEditCli>()); - commands.push_back(std::make_unique<cli::RuleListCli>()); - commands.push_back(std::make_unique<cli::RuleInfoCli>()); - commands.push_back(std::make_unique<cli::RuleMoveCli>()); - commands.push_back(std::make_unique<cli::RuleRemoveCli>()); - commands.push_back(std::make_unique<cli::WatchCli>()); + add(std::make_unique<plugin_config_cli>()); + add(std::make_unique<plugin_info_cli>()); + add(std::make_unique<plugin_list_cli>()); + add(std::make_unique<plugin_load_cli>()); + add(std::make_unique<plugin_reload_cli>()); + add(std::make_unique<plugin_unload_cli>()); + add(std::make_unique<server_channel_mode_cli>()); + add(std::make_unique<server_channel_notice_cli>()); + add(std::make_unique<server_connect_cli>()); + add(std::make_unique<server_disconnect_cli>()); + add(std::make_unique<server_info_cli>()); + add(std::make_unique<server_invite_cli>()); + add(std::make_unique<server_join_cli>()); + add(std::make_unique<server_kick_cli>()); + add(std::make_unique<server_list_cli>()); + add(std::make_unique<server_me_cli>()); + add(std::make_unique<server_message_cli>()); + add(std::make_unique<server_mode_cli>()); + add(std::make_unique<server_nick_cli>()); + add(std::make_unique<server_notice_cli>()); + add(std::make_unique<server_part_cli>()); + add(std::make_unique<server_reconnect_cli>()); + add(std::make_unique<server_topic_cli>()); + add(std::make_unique<rule_add_cli>()); + add(std::make_unique<rule_edit_cli>()); + add(std::make_unique<rule_list_cli>()); + add(std::make_unique<rule_info_cli>()); + add(std::make_unique<rule_move_cli>()); + add(std::make_unique<rule_remove_cli>()); + add(std::make_unique<watch_cli>()); +} + +void do_connect() +{ + bool connected = false; + + ctl->connect([&] (auto code, auto info) { + if (code) + throw boost::system::system_error(code); + + log::info(string_util::sprintf("connected to irccd %d.%d.%d", + json_util::to_int(info["major"]), + json_util::to_int(info["minor"]), + json_util::to_int(info["patch"]))); + + connected = true; + }); + + while (!connected) + service.run(); + + service.reset(); +} + +void do_exec(int argc, char** argv) +{ + std::vector<std::string> args; + + for (int i = 0; i < argc; ++i) + args.push_back(argv[i]); + + exec(args); + + while (ctl->has_pending()) + service.run(); } } // !namespace -int main(int argc, char **argv) +} // !ctl + +} // !irccd + +int main(int argc, char** argv) { - init(argc, argv); + irccd::ctl::init(argc, argv); // 1. Read command line arguments. - auto result = parse(argc, argv); + auto result = irccd::ctl::parse(argc, argv); /* * 2. Open optional config by command line or by searching it @@ -547,65 +535,42 @@ */ try { if (result.count("-t") > 0 || result.count("--type") > 0) - parseConnect(result); + irccd::ctl::parse_connect(result); auto it = result.find("-c"); if (it != result.end() || (it = result.find("--config")) != result.end()) - read(it->second); + irccd::ctl::read(it->second); else { - for (const auto& path : sys::config_filenames("irccdctl.conf")) { + for (const auto& path : irccd::sys::config_filenames("irccdctl.conf")) { boost::system::error_code ec; if (boost::filesystem::exists(path, ec) && !ec) { - read(path); + irccd::ctl::read(path); break; } } } - } catch (const std::exception &ex) { - log::warning() << sys::program_name() << ": " << ex.what() << std::endl; - std::exit(1); - } - - if (argc <= 0) { - usage(); - // NOTREACHED - } - if (std::strcmp(argv[0], "help") == 0) { - if (argc >= 2) - help(argv[1]); - else - help(); - // NOTREACHED - } - - if (!client) { - log::warning("irccdctl: no connection specified"); - std::exit(1); + } catch (const std::exception& ex) { + irccd::log::warning() << "abort: " << ex.what() << std::endl; + return 1; } - irccdctl = std::make_unique<Irccdctl>(std::move(client)); - irccdctl->client().onDisconnect.connect([&] (auto reason) { - log::warning() << "connection lost to irccd: " << reason << std::endl; - }); - irccdctl->client().onConnect.connect([&] (auto info) { - log::info() << "connected to irccd " - << info.major << "." - << info.minor << "." - << info.patch << std::endl; - }); - irccdctl->client().connect(address); + if (argc <= 0) + irccd::ctl::usage(); + // NOTREACHED - // Build a vector of arguments. - std::vector<std::string> args; - - for (int i = 0; i < argc; ++i) - args.push_back(argv[i]); + if (!irccd::ctl::ctl) { + irccd::log::warning("abort: no connection specified"); + return 1; + } try { - exec(args); - } catch (const std::exception &ex) { - std::cerr << sys::program_name() << ": unrecoverable error: " << ex.what() << std::endl; + irccd::ctl::do_connect(); + irccd::ctl::do_exec(argc, argv); + } catch (const boost::system::system_error& ex) { + std::cerr << "abort: " << ex.code().message() << std::endl; + } catch (const std::exception& ex) { + std::cerr << "abort: " << ex.what() << std::endl; } }
--- a/libcommon/CMakeLists.txt Tue Nov 14 20:25:30 2017 +0100 +++ b/libcommon/CMakeLists.txt Thu Nov 16 22:45:12 2017 +0100 @@ -22,6 +22,7 @@ set( HEADERS + ${libcommon_SOURCE_DIR}/irccd/errors.hpp ${libcommon_SOURCE_DIR}/irccd/fs_util.hpp ${libcommon_SOURCE_DIR}/irccd/ini.hpp ${libcommon_SOURCE_DIR}/irccd/json_util.hpp @@ -38,6 +39,7 @@ set( SOURCES + ${libcommon_SOURCE_DIR}/irccd/errors.cpp ${libcommon_SOURCE_DIR}/irccd/ini.cpp ${libcommon_SOURCE_DIR}/irccd/json_util.cpp ${libcommon_SOURCE_DIR}/irccd/logger.cpp
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libcommon/irccd/errors.cpp Thu Nov 16 22:45:12 2017 +0100 @@ -0,0 +1,57 @@ +/* + * errors.cpp -- describe some error codes + * + * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "errors.hpp" + +namespace irccd { + +const boost::system::error_category& network_category() noexcept +{ + static const class network_category : public boost::system::error_category { + public: + const char* name() const noexcept override + { + return "network_category"; + } + + std::string message(int code) const override + { + switch (static_cast<network_error>(code)) { + case network_error::invalid_program: + return "invalid program"; + case network_error::invalid_version: + return "invalid version"; + case network_error::invalid_auth: + return "invalid authentication"; + case network_error::invalid_message: + return "invalid message"; + default: + return "unknown error"; + } + } + } category; + + return category; +} + +boost::system::error_code make_error_code(network_error errc) noexcept +{ + return {static_cast<int>(errc), network_category()}; +} + +} // !irccd
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libcommon/irccd/errors.hpp Thu Nov 16 22:45:12 2017 +0100 @@ -0,0 +1,71 @@ +/* + * errors.hpp -- describe some error codes + * + * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef IRCCD_COMMON_ERRORS_HPP +#define IRCCD_COMMON_ERRORS_HPP + +/** + * \file errors.hpp + * \brief Describe some error codes. + */ + +#include <boost/system/error_code.hpp> + +#include <type_traits> + +namespace irccd { + +/** + * \brief Error code for transport/irccdctl + */ +enum class network_error { + invalid_program = 1, //!< connected daemon is not irccd + invalid_version, //!< irccd daemon is incompatible + invalid_auth, //!< invalid credentials in auth command + invalid_message //!< the message was not JSON +}; + +/** + * Get the network category singleton. + * + * \return the category for network_error enum + */ +const boost::system::error_category& network_category() noexcept; + +/** + * Construct an error_code from network_error enum. + * + * \return the error code + */ +boost::system::error_code make_error_code(network_error errc) noexcept; + +} // !irccd + +namespace boost { + +namespace system { + +template <> +struct is_error_code_enum<irccd::network_error> : public std::true_type { +}; + +} // !system + +} // !boost + +#endif // !IRCCD_COMMON_ERRORS_HPP
--- a/libirccd-test/CMakeLists.txt Tue Nov 14 20:25:30 2017 +0100 +++ b/libirccd-test/CMakeLists.txt Thu Nov 16 22:45:12 2017 +0100 @@ -21,8 +21,6 @@ irccd_define_library( TARGET libirccd-test SOURCES - ${libirccd-test_SOURCE_DIR}/irccd/command-tester.cpp - ${libirccd-test_SOURCE_DIR}/irccd/command-tester.hpp ${libirccd-test_SOURCE_DIR}/irccd/journal_server.cpp ${libirccd-test_SOURCE_DIR}/irccd/journal_server.hpp $<$<BOOL:${HAVE_JS}>:${libirccd-test_SOURCE_DIR}/irccd/plugin_test.cpp>
--- a/libirccd-test/irccd/command-tester.cpp Tue Nov 14 20:25:30 2017 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,47 +0,0 @@ -/* - * command-tester.cpp -- test fixture helper for remote commands - * - * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "command-tester.hpp" -#include "client.hpp" -#include "logger.hpp" -#include "service.hpp" -#include "transport.hpp" - -namespace irccd { - -CommandTester::CommandTester(std::unique_ptr<command> cmd, - std::unique_ptr<server> server) - : m_irccdctl(std::make_unique<Client>()) -{ - // Be silent. - log::set_logger(std::make_unique<log::silent_logger>()); - log::set_verbose(false); - - auto tpt = std::make_unique<transport_server_ip>("*", 0); - auto port = tpt->port(); - - m_irccd.transports().add(std::move(tpt)); - m_irccdctl.client().connect(net::ipv4::pton("127.0.0.1", port)); - - if (cmd) - m_irccd.commands().add(std::move(cmd)); - if (server) - m_irccd.servers().add(std::move(server)); -} - -} // !irccd
--- a/libirccd-test/irccd/command-tester.hpp Tue Nov 14 20:25:30 2017 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,56 +0,0 @@ -/* - * command-tester.hpp -- test fixture helper for remote commands - * - * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef IRCCD_COMMAND_TESTER_HPP -#define IRCCD_COMMAND_TESTER_HPP - -#include <boost/timer/timer.hpp> - -#include <gtest/gtest.h> - -#include "irccd.hpp" -#include "irccdctl.hpp" -#include "net_util.hpp" - -namespace irccd { - -class command; -class server; - -class CommandTester : public testing::Test { -protected: - irccd m_irccd; - Irccdctl m_irccdctl; - -public: - CommandTester(std::unique_ptr<command> cmd = nullptr, - std::unique_ptr<server> server = nullptr); - - template <typename Predicate> - void poll(Predicate &&predicate) - { - boost::timer::cpu_timer timer; - - while (!predicate() && timer.elapsed().wall / 1000000LL < 30000) - net_util::poll(250, m_irccd, m_irccdctl); - } -}; - -} // !irccd - -#endif // !IRCCD_COMMAND_TESTER_HPP
--- a/libirccdctl/CMakeLists.txt Tue Nov 14 20:25:30 2017 +0100 +++ b/libirccdctl/CMakeLists.txt Thu Nov 16 22:45:12 2017 +0100 @@ -20,21 +20,33 @@ set( HEADERS - ${libirccdctl_SOURCE_DIR}/irccd/client.hpp - ${libirccdctl_SOURCE_DIR}/irccd/irccdctl.hpp + ${libirccdctl_SOURCE_DIR}/irccd/ctl/controller.hpp + ${libirccdctl_SOURCE_DIR}/irccd/ctl/connection.hpp + ${libirccdctl_SOURCE_DIR}/irccd/ctl/network_connection.hpp + ${libirccdctl_SOURCE_DIR}/irccd/ctl/ip_connection.hpp ) set( SOURCES - ${libirccdctl_SOURCE_DIR}/irccd/client.cpp + ${libirccdctl_SOURCE_DIR}/irccd/ctl/controller.cpp + ${libirccdctl_SOURCE_DIR}/irccd/ctl/ip_connection.cpp ) +if (NOT IRCCD_SYSTEM_WINDOWS) + list(APPEND LIBRARIES -pthread) + list(APPEND HEADERS ${libirccdctl_SOURCE_DIR}/irccd/ctl/local_connection.hpp) + list(APPEND SOURCES ${libirccdctl_SOURCE_DIR}/irccd/ctl/local_connection.cpp) +endif () + irccd_define_library( TARGET libirccdctl SOURCES ${libirccdctl_SOURCE_DIR}/CMakeLists.txt ${HEADERS} ${SOURCES} - LIBRARIES libcommon - PUBLIC_INCLUDES ${libirccdctl_SOURCE_DIR}/irccd + LIBRARIES + ${LIBRARIES} + libcommon + PUBLIC_INCLUDES + $<BUILD_INTERFACE:${libirccdctl_SOURCE_DIR}> )
--- a/libirccdctl/irccd/client.cpp Tue Nov 14 20:25:30 2017 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,553 +0,0 @@ -/* - * client.cpp -- value wrapper for connecting to irccd - * - * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN Client WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include <stdexcept> - -#include "client.hpp" -#include "net_util.hpp" -#include "string_util.hpp" - -namespace irccd { - -/* - * Client::State. - * ------------------------------------------------------------------ - */ - -class Client::State { -public: - State() = default; - virtual ~State() = default; - virtual Status status() const noexcept = 0; - virtual void prepare(Client &cnt, fd_set &in, fd_set &out) = 0; - virtual void sync(Client &cnt, fd_set &in, fd_set &out) = 0; -}; - -/* - * Client::DisconnectedState. - * ------------------------------------------------------------------ - */ - -class Client::DisconnectedState : public Client::State { -public: - Client::Status status() const noexcept override - { - return Disconnected; - } - - void prepare(Client &, fd_set &, fd_set &) override {} - void sync(Client &, fd_set &, fd_set &) override {} -}; - -/* - * Client::DisconnectedState. - * ------------------------------------------------------------------ - */ - -class Client::ReadyState : public Client::State { -private: - void parse(Client &client, const std::string &message) - { - try { - auto json = nlohmann::json::parse(message); - - if (!json.is_object()) - return; - - if (json.count("event") > 0) - client.onEvent(json); - else - client.onMessage(json); - } catch (const std::exception &) { - } - } -public: - Client::Status status() const noexcept override - { - return Ready; - } - - void prepare(Client &cnx, fd_set &in, fd_set &out) override - { - FD_SET(cnx.m_socket.handle(), &in); - - if (!cnx.m_output.empty()) - FD_SET(cnx.m_socket.handle(), &out); - } - - void sync(Client &cnx, fd_set &in, fd_set &out) override - { - if (FD_ISSET(cnx.m_socket.handle(), &out)) - cnx.send(); - - if (FD_ISSET(cnx.m_socket.handle(), &in)) - cnx.recv(); - - std::string msg; - - do { - msg = net_util::next_network(cnx.m_input); - - if (!msg.empty()) - parse(cnx, msg); - } while (!msg.empty()); - } -}; - -/* - * Client::AuthState. - * ------------------------------------------------------------------ - */ - -class Client::AuthState : public Client::State { -private: - enum { - Created, - Sending, - Checking - } m_auth{Created}; - - std::string m_output; - - void send(Client &cnt) noexcept - { - try { - auto n = cnt.send(m_output.data(), m_output.size()); - - if (n == 0) { - m_output.clear(); - throw std::runtime_error("Client lost"); - } - - m_output.erase(0, n); - - if (m_output.empty()) - m_auth = Checking; - } catch (const std::exception &ex) { - cnt.m_state = std::make_unique<DisconnectedState>(); - cnt.onDisconnect(ex.what()); - } - } - - void check(Client &cnt) noexcept - { - cnt.recv(); - - auto msg = net_util::next_network(cnt.m_input); - - if (msg.empty()) - return; - - try { - auto doc = nlohmann::json::parse(msg); - - if (!doc.is_object()) - throw std::invalid_argument("invalid argument"); - - auto cmd = doc.find("response"); - - if (cmd == doc.end() || !cmd->is_string() || *cmd != "auth") - throw std::invalid_argument("authentication result expected"); - - auto result = doc.find("result"); - - if (result == doc.end() || !result->is_boolean()) - throw std::invalid_argument("bad protocol"); - - if (!*result) - throw std::runtime_error("authentication failed"); - - cnt.m_state = std::make_unique<ReadyState>(); - } catch (const std::exception &ex) { - cnt.m_state = std::make_unique<DisconnectedState>(); - cnt.onDisconnect(ex.what()); - } - } - -public: - Client::Status status() const noexcept override - { - return Authenticating; - } - - void prepare(Client &cnt, fd_set &in, fd_set &out) override - { - switch (m_auth) { - case Created: - m_auth = Sending; - m_output += nlohmann::json({ - { "command", "auth" }, - { "password", cnt.m_password } - }).dump(); - m_output += "\r\n\r\n"; - - // FALLTHROUGH - case Sending: - FD_SET(cnt.m_socket.handle(), &out); - break; - case Checking: - FD_SET(cnt.m_socket.handle(), &in); - break; - default: - break; - } - } - - void sync(Client &cnt, fd_set &in, fd_set &out) override - { - switch (m_auth) { - case Sending: - if (FD_ISSET(cnt.m_socket.handle(), &out)) - send(cnt); - break; - case Checking: - if (FD_ISSET(cnt.m_socket.handle(), &in)) - check(cnt); - break; - default: - break; - } - } -}; - -/* - * Client::CheckingState. - * ------------------------------------------------------------------ - */ - -class Client::CheckingState : public Client::State { -private: - void verifyProgram(const nlohmann::json &json) const - { - auto prog = json.find("program"); - - if (prog == json.end() || !prog->is_string() || prog->get<std::string>() != "irccd") - throw std::runtime_error("not an irccd instance"); - } - - void verifyVersion(Client &cnx, const nlohmann::json &json) const - { - auto getVersionVar = [&] (auto key) { - auto it = json.find(key); - - if (it == json.end() || !it->is_number_unsigned()) - throw std::runtime_error("invalid irccd instance"); - - return *it; - }; - - Info info{ - getVersionVar("major"), - getVersionVar("minor"), - getVersionVar("patch") - }; - - // Ensure compatibility. - if (info.major != IRCCD_VERSION_MAJOR || info.minor > IRCCD_VERSION_MINOR) - throw std::runtime_error(string_util::sprintf("server version too recent %d.%d.%d vs %d.%d.%d", - info.major, info.minor, info.patch, - IRCCD_VERSION_MAJOR, IRCCD_VERSION_MINOR, IRCCD_VERSION_PATCH - )); - - // Successfully connected. - if (cnx.m_password.empty()) - cnx.m_stateNext = std::make_unique<ReadyState>(); - else - cnx.m_stateNext = std::make_unique<AuthState>(); - - cnx.onConnect(info); - } - - void verify(Client &cnx) const - { - auto msg = net_util::next_network(cnx.m_input); - - if (msg.empty()) - return; - - try { - auto json = nlohmann::json::parse(msg); - - verifyProgram(json); - verifyVersion(cnx, json); - } catch (const std::exception &ex) { - cnx.m_stateNext = std::make_unique<DisconnectedState>(); - cnx.onDisconnect(ex.what()); - } - } - -public: - Client::Status status() const noexcept override - { - return Checking; - } - - void prepare(Client &cnx, fd_set &in, fd_set &) override - { - FD_SET(cnx.m_socket.handle(), &in); - } - - void sync(Client &cnx, fd_set &in, fd_set &) override - { - if (FD_ISSET(cnx.m_socket.handle(), &in)) { - cnx.recv(); - verify(cnx); - } - } -}; - -/* - * Client::ConnectingState. - * ------------------------------------------------------------------ - */ - -class Client::ConnectingState : public Client::State { -public: - Client::Status status() const noexcept override - { - return Connecting; - } - - void prepare(Client &cnx, fd_set &, fd_set &out) override - { - FD_SET(cnx.m_socket.handle(), &out); - } - - void sync(Client &cnx, fd_set &, fd_set &out) override - { - if (!FD_ISSET(cnx.m_socket.handle(), &out)) - return; - - try { - auto errc = cnx.m_socket.get<int>(SOL_SOCKET, SO_ERROR); - - if (errc != 0) { - cnx.m_stateNext = std::make_unique<DisconnectedState>(); - cnx.onDisconnect(net::error(errc)); - } else - cnx.m_stateNext = std::make_unique<CheckingState>(); - } catch (const std::exception &ex) { - cnx.m_stateNext = std::make_unique<DisconnectedState>(); - cnx.onDisconnect(ex.what()); - } - } -}; - -/* - * Client. - * ------------------------------------------------------------------ - */ - -unsigned Client::recv(char *buffer, unsigned length) -{ - return m_socket.recv(buffer, length); -} - -unsigned Client::send(const char *buffer, unsigned length) -{ - return m_socket.send(buffer, length); -} - -void Client::recv() -{ - try { - std::string buffer; - - buffer.resize(512); - buffer.resize(recv(&buffer[0], buffer.size())); - - if (buffer.empty()) - throw std::runtime_error("Client lost"); - - m_input += std::move(buffer); - } catch (const std::exception &ex) { - m_stateNext = std::make_unique<DisconnectedState>(); - onDisconnect(ex.what()); - } -} - -void Client::send() -{ - try { - auto ns = send(m_output.data(), m_output.length()); - - if (ns > 0) - m_output.erase(0, ns); - } catch (const std::exception &ex) { - m_stateNext = std::make_unique<DisconnectedState>(); - onDisconnect(ex.what()); - } -} - -Client::Client() - : m_state(std::make_unique<DisconnectedState>()) -{ -} - -Client::~Client() = default; - -Client::Status Client::status() const noexcept -{ - return m_state->status(); -} - -void Client::connect(const net::Address &address) -{ - assert(status() == Disconnected); - - try { - m_socket = net::TcpSocket(address.domain(), 0); - m_socket.set(net::option::SockBlockMode(false)); - m_socket.connect(address); - m_state = std::make_unique<CheckingState>(); - } catch (const net::WouldBlockError &) { - m_state = std::make_unique<ConnectingState>(); - } catch (const std::exception &ex) { - m_state = std::make_unique<DisconnectedState>(); - onDisconnect(ex.what()); - } -} - -void Client::prepare(fd_set &in, fd_set &out, net::Handle &max) -{ - try { - m_state->prepare(*this, in, out); - - if (m_socket.handle() > max) - max = m_socket.handle(); - } catch (const std::exception &ex) { - m_state = std::make_unique<DisconnectedState>(); - onDisconnect(ex.what()); - } -} - -void Client::sync(fd_set &in, fd_set &out) -{ - try { - m_state->sync(*this, in, out); - - if (m_stateNext) { - m_state = std::move(m_stateNext); - m_stateNext = nullptr; - } - } catch (const std::exception &ex) { - m_state = std::make_unique<DisconnectedState>(); - onDisconnect(ex.what()); - } -} - -/* - * TlsClient. - * ------------------------------------------------------------------ - */ - -#if defined(HAVE_SSL) - -void TlsClient::handshake() -{ - try { - m_ssl->handshake(); - m_handshake = HandshakeReady; - } catch (const net::WantReadError &) { - m_handshake = HandshakeRead; - } catch (const net::WantWriteError &) { - m_handshake = HandshakeWrite; - } catch (const std::exception &ex) { - m_state = std::make_unique<DisconnectedState>(); - onDisconnect(ex.what()); - } -} - -unsigned TlsClient::recv(char *buffer, unsigned length) -{ - unsigned nread = 0; - - try { - nread = m_ssl->recv(buffer, length); - } catch (const net::WantReadError &) { - m_handshake = HandshakeRead; - } catch (const net::WantWriteError &) { - m_handshake = HandshakeWrite; - } - - return nread; -} - -unsigned TlsClient::send(const char *buffer, unsigned length) -{ - unsigned nsent = 0; - - try { - nsent = m_ssl->send(buffer, length); - } catch (const net::WantReadError &) { - m_handshake = HandshakeRead; - } catch (const net::WantWriteError &) { - m_handshake = HandshakeWrite; - } - - return nsent; -} - -void TlsClient::connect(const net::Address &address) -{ - Client::connect(address); - - m_ssl = std::make_unique<net::TlsSocket>(m_socket, net::TlsSocket::Client); -} - -void TlsClient::prepare(fd_set &in, fd_set &out, net::Handle &max) -{ - if (m_state->status() == Connecting) - Client::prepare(in, out, max); - else { - if (m_socket.handle() > max) - max = m_socket.handle(); - - /* - * Attempt an immediate handshake immediately if Client succeeded - * in last iteration. - */ - if (m_handshake == HandshakeUndone) - handshake(); - - switch (m_handshake) { - case HandshakeRead: - FD_SET(m_socket.handle(), &in); - break; - case HandshakeWrite: - FD_SET(m_socket.handle(), &out); - break; - default: - Client::prepare(in, out, max); - } - } -} - -void TlsClient::sync(fd_set &in, fd_set &out) -{ - if (m_state->status() == Connecting) - Client::sync(in, out); - else if (m_handshake != HandshakeReady) - handshake(); - else - Client::sync(in, out); -} - -#endif // !HAVE_SSL - -} // !irccd
--- a/libirccdctl/irccd/client.hpp Tue Nov 14 20:25:30 2017 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,325 +0,0 @@ -/* - * client.hpp -- value wrapper for connecting to irccd - * - * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef IRCCD_CLIENT_HPP -#define IRCCD_CLIENT_HPP - -/** - * \file client.hpp - * \brief Connection to irccd instance. - */ - -#include <cassert> -#include <memory> -#include <string> - -#include <json.hpp> - -#include "net.hpp" -#include "signals.hpp" -#include "sysconfig.hpp" - -namespace irccd { - -/** - * \brief Low level connection to irccd instance. - * - * This class is an event-based connection to an irccd instance. You can use - * it directly if you want to issue commands to irccd in an asynchronous way. - * - * Being asynchronous makes mixing the event loop with this connection easier. - * - * It is implemented as a finite state machine as it may requires several - * roundtrips between the controller and irccd. - * - * Be aware that there are no namespaces for commands, if you plan to use - * Irccdctl class and you also connect the onMessage signal, irccdctl will also - * use it. Do not use Irccdctl directly if this is a concern. - * - * The state may change and is currently implementing as following: - * - * [o] - * | +----------------------------+ - * v v | - * +--------------+ +----------+ +----------------+ - * | Disconnected |-->| Checking |---->| Authenticating | - * +--------------+ +----------+ +----------------+ - * ^ | ^ | - * | | | v - * | | +------------+ +-------+ - * | +----->| Connecting |<--| Ready | - * | +------------+ +-------+ - * | | - * ------------------------------------+ - */ -class Client { -public: - /** - * \brief The current connection state. - */ - enum Status { - Disconnected, //!< Socket is closed - Connecting, //!< Connection is in progress - Checking, //!< Connection is checking irccd daemon - Authenticating, //!< Connection is authenticating - Ready //!< Socket is ready for I/O - }; - - /** - * \brief Irccd information. - */ - class Info { - public: - unsigned short major; //!< Major version number - unsigned short minor; //!< Minor version number - unsigned short patch; //!< Patch version - }; - - /** - * onConnect - * -------------------------------------------------------------- - * - * Connection was successful. - */ - Signal<const Info &> onConnect; - - /** - * onEvent - * -------------------------------------------------------------- - * - * An event has been received. - */ - Signal<const nlohmann::json &> onEvent; - - /** - * onMessage - * --------------------------------------------------------------- - * - * A message from irccd was received. - */ - Signal<const nlohmann::json &> onMessage; - - /** - * onDisconnect - * -------------------------------------------------------------- - * - * A fatal error occured resulting in disconnection. - */ - Signal<const std::string &> onDisconnect; - -private: - std::string m_input; - std::string m_output; - std::string m_password; - -public: - class State; - class AuthState; - class DisconnectedState; - class ConnectingState; - class CheckingState; - class ReadyState; - -protected: - std::unique_ptr<State> m_state; - std::unique_ptr<State> m_stateNext; - net::TcpSocket m_socket{net::Invalid}; - - /** - * Try to receive some data into the given buffer. - * - * \param buffer the destination buffer - * \param length the buffer length - * \return the number of bytes received - */ - virtual unsigned recv(char *buffer, unsigned length); - - /** - * Try to send some data into the given buffer. - * - * \param buffer the source buffer - * \param length the buffer length - * \return the number of bytes sent - */ - virtual unsigned send(const char *buffer, unsigned length); - - /** - * Convenient wrapper around recv(). - * - * Must be used in sync() function. - */ - void recv(); - - /** - * Convenient wrapper around send(). - * - * Must be used in sync() function. - */ - void send(); - -public: - /** - * Default constructor. - */ - Client(); - - /** - * Default destructor. - */ - virtual ~Client(); - - /** - * Get the optional password. - * - * \return the password - */ - inline const std::string &password() const noexcept - { - return m_password; - } - - /** - * Set the optional password - * - * \param password the password - */ - inline void setPassword(std::string password) noexcept - { - m_password = std::move(password); - } - - /** - * Send an asynchronous request to irccd. - * - * \pre json.is_object - * \param json the JSON object - */ - inline void request(const nlohmann::json &json) - { - assert(json.is_object()); - - m_output += json.dump(); - m_output += "\r\n\r\n"; - } - - /** - * Get the underlying socket handle. - * - * \return the handle - */ - inline net::Handle handle() const noexcept - { - return m_socket.handle(); - } - - /** - * Shorthand for state() != Disconnected. - * - * \return true if state() != Disconnected - */ - inline bool isConnected() const noexcept - { - return status() != Disconnected; - } - - /** - * Get the current state. - * - * \return the state - */ - Status status() const noexcept; - - /** - * Initiate connection to irccd. - * - * \pre state() == Disconnected - * \param address the address - */ - virtual void connect(const net::Address &address); - - /** - * Prepare the input and output set according to the current connection - * state. - * - * \param in the input set - * \param out the output set - * \param max the maximum file descriptor - */ - virtual void prepare(fd_set &in, fd_set &out, net::Handle &max); - - /** - * Do some I/O using the protected recv and send functions. - * - * \param in the input set - * \param out the output set - */ - virtual void sync(fd_set &in, fd_set &out); -}; - -#if defined(HAVE_SSL) - -/** - * \brief TLS over IP connection. - */ -class TlsClient : public Client { -private: - enum { - HandshakeUndone, - HandshakeRead, - HandshakeWrite, - HandshakeReady - } m_handshake{HandshakeUndone}; - -private: - std::unique_ptr<net::TlsSocket> m_ssl; - - void handshake(); - -protected: - /** - * \copydoc Client::recv - */ - unsigned recv(char *buffer, unsigned length) override; - - /** - * \copydoc Client::send - */ - unsigned send(const char *buffer, unsigned length) override; - -public: - /** - * \copydoc Client::connect - */ - void connect(const net::Address &address) override; - - /** - * \copydoc Service::prepare - */ - void prepare(fd_set &in, fd_set &out, net::Handle &max) override; - - /** - * \copydoc Service::sync - */ - void sync(fd_set &in, fd_set &out) override; -}; - -#endif // !HAVE_SSL - -} // !irccd - -#endif // !IRCCD_CLIENT_HPP
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccdctl/irccd/ctl/connection.hpp Thu Nov 16 22:45:12 2017 +0100 @@ -0,0 +1,90 @@ +/* + * connection.hpp -- abstract connection for irccdctl + * + * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef IRCCD_CTL_CONNECTION_HPP +#define IRCCD_CTL_CONNECTION_HPP + +/** + * \file connection.hpp + * \brief Abstract connection for irccdctl. + */ + +#include <boost/system/error_code.hpp> + +#include <json.hpp> + +namespace irccd { + +namespace ctl { + +/** + * \brief Abstract connection for irccdctl. + */ +class connection { +public: + /** + * Connect handler. + * + * Call the handler when the underlying protocol connection is complete. + */ + using connect_t = std::function<void (boost::system::error_code)>; + + /** + * Receive handler. + * + * Call the handler when you have read a JSON message from the underlying + * protocol. + */ + using recv_t = std::function<void (boost::system::error_code, nlohmann::json)>; + + /** + * Send handler. + * + * Call the handler when you have sent a JSON message to the underlying + * protocol. + */ + using send_t = std::function<void (boost::system::error_code)>; + + /** + * Connect to the daemon. + * + * \param handler the non-null handler + */ + virtual void connect(connect_t handler) = 0; + + /** + * Receive a message. + * + * \param handler the non-null handler + */ + virtual void recv(recv_t) = 0; + + /** + * Send a message. + * + * \param message the JSON message object + * \param handler the non-null handler + */ + virtual void send(const nlohmann::json& json, send_t) = 0; +}; + +} // !ctl + +} // !irccd + +#endif // !IRCCD_CTL_CONNECTION_HPP
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccdctl/irccd/ctl/controller.cpp Thu Nov 16 22:45:12 2017 +0100 @@ -0,0 +1,143 @@ +/* + * controller.cpp -- main irccdctl interface + * + * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <cassert> + +#include <irccd/errors.hpp> +#include <irccd/sysconfig.hpp> +#include <irccd/json_util.hpp> + +#include "controller.hpp" +#include "connection.hpp" + +namespace irccd { + +namespace ctl { + +void controller::flush_recv() +{ + if (rqueue_.empty()) + return; + + conn_.recv([this] (auto code, auto json) { + rqueue_.front()(code, std::move(json)); + rqueue_.pop_front(); + + if (!code) + flush_recv(); + }); +} + +void controller::flush_send() +{ + if (squeue_.empty()) + return; + + conn_.send(squeue_.front().first, [this] (auto code) { + if (squeue_.front().second) + squeue_.front().second(code, squeue_.front().first); + + squeue_.pop_front(); + + if (!code) + flush_send(); + }); +} + +void controller::authenticate(connect_t handler, nlohmann::json info) +{ + auto cmd = nlohmann::json::object({ + { "command", "authenticate" }, + { "password", password_ } + }); + + send(std::move(cmd), [handler, info, this] (auto code, auto) { + if (code) { + handler(std::move(code), nullptr); + return; + } + + recv([handler, info, this] (auto code, auto message) { + if (message["error"].is_string()) + code = network_error::invalid_auth; + + handler(std::move(code), std::move(info)); + }); + }); +} + +void controller::verify(connect_t handler) +{ + recv([handler, this] (auto code, auto message) { + if (code) { + handler(std::move(code), std::move(message)); + return; + } + + if (json_util::to_string(message["program"]) != "irccd") + handler(network_error::invalid_program, std::move(message)); + else if (json_util::to_int(message["major"]) != IRCCD_VERSION_MAJOR) + handler(network_error::invalid_version, std::move(message)); + else { + if (!password_.empty()) + authenticate(std::move(handler), message); + else + handler(code, std::move(message)); + } + }); +} + +void controller::connect(connect_t handler) +{ + assert(handler); + + conn_.connect([handler, this] (auto code) { + if (code) + handler(std::move(code), nullptr); + else + verify(std::move(handler)); + }); +} + +void controller::recv(recv_t handler) +{ + assert(handler); + + auto in_progress = !rqueue_.empty(); + + rqueue_.push_back(std::move(handler)); + + if (!in_progress) + flush_recv(); +} + +void controller::send(nlohmann::json message, send_t handler) +{ + assert(message.is_object()); + + auto in_progress = !squeue_.empty(); + + squeue_.emplace_back(std::move(message), std::move(handler)); + + if (!in_progress) + flush_send(); +} + +} // !ctl + +} // !irccd
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccdctl/irccd/ctl/controller.hpp Thu Nov 16 22:45:12 2017 +0100 @@ -0,0 +1,197 @@ +/* + * controller.hpp -- main irccdctl interface + * + * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef IRCCD_CTL_CONTROLLER_HPP +#define IRCCD_CTL_CONTROLLER_HPP + +/** + * \file controller.hpp + * \brief Main irccdctl interface. + */ + +#include <boost/system/error_code.hpp> + +#include <deque> +#include <functional> +#include <string> + +#include <json.hpp> + +namespace irccd { + +namespace ctl { + +class connection; + +/** + * \brief Main irccdctl interface. + * + * This class is an easy frontend to issue commands to irccd, it uses an + * independant connection to perform the requests. + * + * This class is responsible of doing initial connection, performing checks and + * optional authentication. + * + * It is implemented in mind that connection are asynchronous even though this + * is not necessary. + * + * \see connection + * \see network_connection + * \see local_connection + * \see ip_connection + * \see tls_connection + */ +class controller { +public: + /** + * Connection handler. + * + * This callback is called when connection has been completed or failed. In + * both case, the error code is set and the JSON object may contain the + * irccd program information. + */ + using connect_t = std::function<void (boost::system::error_code, nlohmann::json)>; + + /** + * Receive handler. + * + * This callback is called when a message has been received. If an error + * occured the error_code is set and the JSON object is null, otherwise it + * contains the received message. + */ + using recv_t = std::function<void (boost::system::error_code, nlohmann::json)>; + + /** + * Send handler. + * + * This callback is optional and is called when a message has been sent, it + * is also called if an error occured. + */ + using send_t = std::function<void (boost::system::error_code, nlohmann::json)>; + +private: + using recv_queue_t = std::deque<recv_t>; + using send_queue_t = std::deque<std::pair<nlohmann::json, send_t>>; + + connection& conn_; + recv_queue_t rqueue_; + send_queue_t squeue_; + std::string password_; + + void flush_recv(); + void flush_send(); + void authenticate(connect_t, nlohmann::json); + void verify(connect_t); + +public: + /** + * Construct the controller with its connection. + * + * \note no connect attempt is done + */ + inline controller(connection& conn) noexcept + : conn_(conn) + { + } + + /** + * Tells if receive requests are pending. + * + * \return true if receive queue is not empty + */ + inline bool has_recv_pending() const noexcept + { + return !rqueue_.empty(); + } + + /** + * Tells if send requests are pending. + * + * \return true if send queue is not empty + */ + inline bool has_send_pending() const noexcept + { + return !squeue_.empty(); + } + + /** + * Tells if receive or send requests are pending. + * + * \return true if one of receive/send queue is not empty + */ + inline bool has_pending() const noexcept + { + return has_recv_pending() || has_send_pending(); + } + + /** + * Get the optional password set. + * + * \return the password + */ + inline const std::string& password() const noexcept + { + return password_; + } + + /** + * Set an optional password. + * + * An empty password means no authentication (default). + * + * \param password the password + * \note this must be called before connect + */ + inline void set_password(std::string password) noexcept + { + password_ = std::move(password); + } + + /** + * Attempt to connect to the irccd daemon. + * + * \pre handler != nullptr + * \param handler the handler + */ + void connect(connect_t handler); + + /** + * Queue a receive operation, if receive operations are already running, it + * is queued and ran once ready. + * + * \pre handler != nullptr + * \param handler the recv handler + */ + void recv(recv_t handler); + + /** + * Queue a send operation, if receive operations are already running, it is + * queued and ran once ready. + * + * \pre message.is_object() + * \param message the JSON message + * \param handler the optional completion handler + */ + void send(nlohmann::json message, send_t handler); +}; + +} // !ctl + +} // !irccd + +#endif // !IRCCD_CTL_CONTROLLER_HPP
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccdctl/irccd/ctl/ip_connection.cpp Thu Nov 16 22:45:12 2017 +0100 @@ -0,0 +1,88 @@ +/* + * ip_connection.cpp -- TCP/IP and SSL connections + * + * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "ip_connection.hpp" + +using resolver = boost::asio::ip::tcp::resolver; + +namespace irccd { + +namespace ctl { + +namespace { + +template <typename Socket> +void do_connect(Socket& socket, resolver::iterator it, connection::connect_t handler) +{ + socket.close(); + socket.async_connect(*it, [&socket, it, handler] (auto code) mutable { + if (code && it != resolver::iterator()) + do_connect(socket, ++it, std::move(handler)); + else + handler(code); + }); +} + +template <typename Socket> +void do_resolve(const std::string& host, + const std::string& port, + Socket& socket, + resolver& resolver, + connection::connect_t handler) +{ + resolver::query query(host, port); + + resolver.async_resolve(query, [&socket, handler] (auto code, auto it) { + if (code) + handler(code); + else + do_connect(socket, it, std::move(handler)); + }); +} + +} // !namespace + +void ip_connection::connect(connect_t handler) +{ + do_resolve(host_, std::to_string(port_), socket_, resolver_, std::move(handler)); +} + +#if defined(HAVE_SSL) + +void tls_connection::handshake(connect_t handler) +{ + socket_.async_handshake(boost::asio::ssl::stream_base::client, [handler] (auto code) { + handler(code); + }); +} + +void tls_connection::connect(connect_t handler) +{ + do_resolve(host_, std::to_string(port_), socket_.lowest_layer(), resolver_, [handler, this] (auto code) { + if (code) + handler(code); + else + handshake(std::move(handler)); + }); +} + +#endif // !HAVE_SSL + +} // !ctl + +} // !irccd
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccdctl/irccd/ctl/ip_connection.hpp Thu Nov 16 22:45:12 2017 +0100 @@ -0,0 +1,129 @@ +/* + * ip_connection.hpp -- TCP/IP and SSL connections + * + * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef IRCCD_CTL_IP_CONNECTION_HPP +#define IRCCD_CTL_IP_CONNECTION_HPP + +/** + * \file ip_connection.hpp + * \brief TCP/IP and SSL connections. + */ + +#include <irccd/sysconfig.hpp> + +#include <string> +#include <cstdint> + +#if defined(HAVE_SSL) +# include <boost/asio/ssl.hpp> +#endif + +#include "network_connection.hpp" + +namespace irccd { + +namespace ctl { + +/** + * \brief Common class for both ip and tls connections. + */ +template <typename Socket> +class basic_ip_connection : public network_connection<Socket> { +protected: + boost::asio::ip::tcp::resolver resolver_; + std::string host_; + std::uint16_t port_; + +public: + /** + * Construct the ip connection. + * + * The socket is created as invoked like this: + * + * socket_(service_, std::forward<Args>(args)...) + * + * \param service the io service + * \param host the host + * \param port the port number + * \param extra args (except service) to pass to the socket constructor + */ + template <typename... Args> + inline basic_ip_connection(boost::asio::io_service& service, std::string host, std::uint16_t port, Args&&... args) + : network_connection<Socket>(service, std::forward<Args>(args)...) + , resolver_(service) + , host_(std::move(host)) + , port_(std::move(port)) + { + } +}; + +/** + * \brief Raw TCP/IP connection. + */ +class ip_connection : public basic_ip_connection<boost::asio::ip::tcp::socket> { +public: + /** + * Inherited constructor. + */ + using basic_ip_connection::basic_ip_connection; + + /** + * \copydoc connection::connect + */ + void connect(connect_t handler); +}; + +#if defined(HAVE_SSL) + +/** + * \brief Secure layer connection. + */ +class tls_connection : public basic_ip_connection<boost::asio::ssl::stream<boost::asio::ip::tcp::socket>> { +private: + void handshake(connect_t); + +public: + /** + * Construct the TLS connection. + * + * \param service the io service + * \param context the context + * \param host the host + * \param port the port number + */ + inline tls_connection(boost::asio::io_service& service, + boost::asio::ssl::context& context, + std::string host, + std::uint16_t port) + : basic_ip_connection(service, std::move(host), port, context) + { + } + + /** + * \copydoc connection::connect + */ + void connect(connect_t handler); +}; + +#endif // !HAVE_SSL + +} // !ctl + +} // !irccd + +#endif // !IRCCD_CTL_IP_CONNECTION_HPP
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccdctl/irccd/ctl/local_connection.cpp Thu Nov 16 22:45:12 2017 +0100 @@ -0,0 +1,38 @@ +/* + * local_connection.cpp -- unix domain connection for irccdctl + * + * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "local_connection.hpp" + +#if !defined(IRCCD_SYSTEM_WINDOWS) + +namespace irccd { + +namespace ctl { + +void local_connection::connect(connect_t handler) +{ + using endpoint = boost::asio::local::stream_protocol::endpoint; + + socket_.async_connect(endpoint(path_), std::move(handler)); +} + +} // !ctl + +} // !irccd + +#endif // !IRCCD_SYSTEM_WINDOWS
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccdctl/irccd/ctl/local_connection.hpp Thu Nov 16 22:45:12 2017 +0100 @@ -0,0 +1,71 @@ +/* + * local_connection.hpp -- unix domain connection for irccdctl + * + * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef IRCCD_CTL_LOCAL_CONNECTION_HPP +#define IRCCD_CTL_LOCAL_CONNECTION_HPP + +/** + * \file local_connection.hpp + * \brief Unix domain connection for irccdctl. + */ + +#include <irccd/sysconfig.hpp> + +#include "network_connection.hpp" + +#if !defined(IRCCD_SYSTEM_WINDOWS) + +namespace irccd { + +namespace ctl { + +/** + * \brief Unix domain connection for irccdctl. + */ +class local_connection : public network_connection<boost::asio::local::stream_protocol::socket> { +private: + std::string path_; + +public: + /** + * Construct the local connection. + * + * \param service the io_service + * \param path the path to the socket file + */ + inline local_connection(boost::asio::io_service& service, std::string path) noexcept + : network_connection(service) + , path_(std::move(path)) + { + } + + /** + * Connect to the socket file. + * + * \param handler the handler + */ + void connect(connect_t handler) override; +}; + +} // !ctl + +} // !irccd + +#endif // !IRCCD_SYSTEM_WINDOWS + +#endif // !IRCCD_CTL_LOCAL_CONNECTION_HPP
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccdctl/irccd/ctl/network_connection.hpp Thu Nov 16 22:45:12 2017 +0100 @@ -0,0 +1,119 @@ +/* + * network_connection.hpp -- network based connection for controller + * + * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef IRCCD_CTL_NETWORK_CONNECTION_HPP +#define IRCCD_CTL_NETWORK_CONNECTION_HPP + +/** + * \file network_connection.hpp + * \brief Network based connection for controller. + */ + +#include <boost/asio.hpp> + +#include <irccd/errors.hpp> + +#include "connection.hpp" + +namespace irccd { + +namespace ctl { + +/** + * \brief Network based connection for controller. + * + * This class implements recv and send functions for Boost.Asio based sockets, + * the subclasses only need to implement a connect function. + */ +template <typename Socket> +class network_connection : public connection { +private: + boost::asio::streambuf input_; + +protected: + /** + * The underlying socket. + */ + Socket socket_; + +public: + /** + * Construct the network connection. + * + * \param args the arguments to pass to the socket + */ + template <typename... Args> + inline network_connection(Args&&... args) + : socket_(std::forward<Args>(args)...) + { + } + + /** + * Implements connection::recv using boost::asio::async_read_until. + * + * \param handler the handler + */ + void recv(recv_t handler) override; + + /** + * Implements connection::send using boost::asio::async_write. + * + * \param handler the handler + */ + void send(const nlohmann::json& json, send_t) override; +}; + +template <typename Socket> +void network_connection<Socket>::recv(recv_t handler) +{ + boost::asio::async_read_until(socket_, input_, "\r\n\r\n", [handler, this] (auto code, auto xfer) { + if (code || xfer == 0) { + handler(code, nullptr); + return; + } + + std::string command{ + boost::asio::buffers_begin(input_.data()), + boost::asio::buffers_begin(input_.data()) + xfer - 4 + }; + + input_.consume(xfer); + + try { + handler(code, nlohmann::json::parse(command)); + } catch (...) { + handler(network_error::invalid_message, nullptr); + } + }); +} + +template <typename Socket> +void network_connection<Socket>::send(const nlohmann::json& message, send_t handler) +{ + auto data = std::make_shared<std::string>(message.dump(0) + "\r\n\r\n"); + + boost::asio::async_write(socket_, boost::asio::buffer(*data), [handler, data, this] (auto code, auto) { + handler(code); + }); +} + +} // !ctl + +} // !irccd + +#endif // !IRCCD_CTL_NETWORK_CONNECTION_HPP
--- a/libirccdctl/irccd/irccdctl.hpp Tue Nov 14 20:25:30 2017 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,97 +0,0 @@ -/* - * irccdctl.hpp -- main irccdctl class - * - * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef IRCCD_IRCCDCTL_HPP -#define IRCCD_IRCCDCTL_HPP - -/** - * \file irccdctl.hpp - * \brief Main irccdctl class - */ - -#include <memory> - -#include "client.hpp" - -namespace irccd { - -/** - * \brief Transient class for connecting to irccd - */ -class Irccdctl { -private: - std::unique_ptr<Client> m_client; - -public: - /** - * Create the irccdctl instance with the specified client. - * - * \param client the client - */ - inline Irccdctl(std::unique_ptr<Client> client) noexcept - : m_client(std::move(client)) - { - } - - /** - * Get the client. - * - * \return the client reference - */ - inline Client &client() noexcept - { - return *m_client; - } - - /** - * Overloaded function. - * - * \return the client - */ - inline const Client &client() const noexcept - { - return *m_client; - } - - /** - * Pollable prepare function. - * - * \param in the input set - * \param out the output set - * \param max the maximum handle - */ - inline void prepare(fd_set &in, fd_set &out, net::Handle &max) - { - m_client->prepare(in, out, max); - } - - /** - * Pollable sync function. - * - * \param in the input set - * \param out the output set - */ - inline void sync(fd_set &in, fd_set &out) - { - m_client->sync(in, out); - } -}; - -} // !irccd - -#endif // !IRCCD_IRCCDCTL_HPP
--- a/tests/CMakeLists.txt Tue Nov 14 20:25:30 2017 +0100 +++ b/tests/CMakeLists.txt Thu Nov 16 22:45:12 2017 +0100 @@ -27,35 +27,35 @@ file(WRITE ${tests_BINARY_DIR}/root/file-1.txt "\n") file(WRITE ${tests_BINARY_DIR}/root/level-a/level-1/file-2.txt) - add_subdirectory(cmd-plugin-config) +# add_subdirectory(cmd-plugin-config) # add_subdirectory(cmd-plugin-info) # add_subdirectory(cmd-plugin-list) # add_subdirectory(cmd-plugin-load) # add_subdirectory(cmd-plugin-reload) # add_subdirectory(cmd-plugin-unload) - add_subdirectory(cmd-rule-add) - add_subdirectory(cmd-rule-edit) - add_subdirectory(cmd-rule-info) - add_subdirectory(cmd-rule-list) - add_subdirectory(cmd-rule-move) - add_subdirectory(cmd-rule-remove) - add_subdirectory(cmd-server-cmode) - add_subdirectory(cmd-server-cnotice) - add_subdirectory(cmd-server-connect) - add_subdirectory(cmd-server-disconnect) - add_subdirectory(cmd-server-info) - add_subdirectory(cmd-server-invite) - add_subdirectory(cmd-server-join) - add_subdirectory(cmd-server-kick) - add_subdirectory(cmd-server-list) - add_subdirectory(cmd-server-me) - add_subdirectory(cmd-server-message) - add_subdirectory(cmd-server-mode) - add_subdirectory(cmd-server-nick) - add_subdirectory(cmd-server-notice) - add_subdirectory(cmd-server-part) - add_subdirectory(cmd-server-reconnect) - add_subdirectory(cmd-server-topic) +# add_subdirectory(cmd-rule-add) +# add_subdirectory(cmd-rule-edit) +# add_subdirectory(cmd-rule-info) +# add_subdirectory(cmd-rule-list) +# add_subdirectory(cmd-rule-move) +# add_subdirectory(cmd-rule-remove) +# add_subdirectory(cmd-server-cmode) +# add_subdirectory(cmd-server-cnotice) +# add_subdirectory(cmd-server-connect) +# add_subdirectory(cmd-server-disconnect) +# add_subdirectory(cmd-server-info) +# add_subdirectory(cmd-server-invite) +# add_subdirectory(cmd-server-join) +# add_subdirectory(cmd-server-kick) +# add_subdirectory(cmd-server-list) +# add_subdirectory(cmd-server-me) +# add_subdirectory(cmd-server-message) +# add_subdirectory(cmd-server-mode) +# add_subdirectory(cmd-server-nick) +# add_subdirectory(cmd-server-notice) +# add_subdirectory(cmd-server-part) +# add_subdirectory(cmd-server-reconnect) +# add_subdirectory(cmd-server-topic) # add_subdirectory(dynlib_plugin) # Misc