Mercurial > irccd
changeset 318:8457f5bd2036
Irccd: add missing bits for tests
author | David Demelier <markand@malikania.fr> |
---|---|
date | Sun, 23 Oct 2016 11:25:26 +0200 |
parents | 249ccc1e4840 |
children | 04e83ea7147b |
files | libirccd/CMakeLists.txt libirccd/irccd/command.cpp libirccd/irccd/command.hpp libirccd/irccd/irccd.cpp libirccd/irccd/irccd.hpp libirccd/irccd/server.hpp libirccd/irccd/service-command.cpp libirccd/irccd/service-command.hpp libirccd/irccd/service-transport.cpp libirccd/irccd/transport.cpp libirccd/irccd/transport.hpp libirccdctl/CMakeLists.txt libirccdctl/irccd/irccdctl.cpp libirccdctl/irccd/irccdctl.hpp |
diffstat | 14 files changed, 116 insertions(+), 1700 deletions(-) [+] |
line wrap: on
line diff
--- a/libirccd/CMakeLists.txt Sun Oct 23 11:20:29 2016 +0200 +++ b/libirccd/CMakeLists.txt Sun Oct 23 11:25:26 2016 +0200 @@ -67,7 +67,6 @@ ${libirccd_SOURCE_DIR}/irccd/cmd-server-part.cpp ${libirccd_SOURCE_DIR}/irccd/cmd-server-reconnect.cpp ${libirccd_SOURCE_DIR}/irccd/cmd-server-topic.cpp - ${libirccd_SOURCE_DIR}/irccd/command.cpp ${libirccd_SOURCE_DIR}/irccd/config.cpp ${libirccd_SOURCE_DIR}/irccd/irccd.cpp ${libirccd_SOURCE_DIR}/irccd/plugin-dynlib.cpp
--- a/libirccd/irccd/command.cpp Sun Oct 23 11:20:29 2016 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,272 +0,0 @@ -/* - * command.cpp -- remote command - * - * Copyright (c) 2013-2016 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 <iomanip> -#include <numeric> -#include <sstream> - -#include <format.h> - -#include "command.hpp" -#include "logger.hpp" -#include "system.hpp" - -using namespace std::string_literals; - -using namespace fmt::literals; - -using json = nlohmann::json; - -namespace irccd { - -namespace { - -/* - * typeName - * ------------------------------------------------------------------ - * - * Convert a JSON value type to string for convenience. - */ -std::string typeName(nlohmann::json::value_t type) noexcept -{ - switch (type) { - case nlohmann::json::value_t::array: - return "array"; - case nlohmann::json::value_t::boolean: - return "bool"; - case nlohmann::json::value_t::number_float: - return "float"; - case nlohmann::json::value_t::number_integer: - return "integer"; - case nlohmann::json::value_t::number_unsigned: - return "unsigned"; - case nlohmann::json::value_t::null: - return "null"; - case nlohmann::json::value_t::object: - return "object"; - case nlohmann::json::value_t::string: - return "string"; - default: - return ""; - } -} - -/* - * typeNameList - * ------------------------------------------------------------------ - * - * Construct a list of names to send a convenient error message if properties - * are invalid, example: string, int or bool expected. - */ - -std::string typeNameList(const std::vector<json::value_t> &types) -{ - std::ostringstream oss; - - if (types.size() == 1) - return typeName(types[0]); - - for (std::size_t i = 0; i < types.size(); ++i) { - oss << typeName(types[i]); - - if (i == types.size() - 2) - oss << " or "; - else if (i < types.size() - 1) - oss << ", "; - } - - return oss.str(); -} - -} // !namespace - -/* - * JSON errors - * ------------------------------------------------------------------ - */ - -MissingPropertyError::MissingPropertyError(std::string name, std::vector<nlohmann::json::value_t> types) - : m_name(std::move(name)) - , m_types(std::move(types)) -{ - m_message = "missing '" + m_name + "' property (" + typeNameList(m_types) + " expected)"; -} - -InvalidPropertyError::InvalidPropertyError(std::string name, nlohmann::json::value_t expected, nlohmann::json::value_t result) - : m_name(std::move(name)) - , m_expected(expected) - , m_result(result) -{ - m_message += "invalid '" + m_name + "' property "; - m_message += "(" + typeName(expected) + " expected, "; - m_message += "got " + typeName(result) + ")"; -} - -PropertyRangeError::PropertyRangeError(std::string name, std::uint64_t min, std::uint64_t max, std::uint64_t value) - : m_name(std::move(name)) - , m_min(min) - , m_max(max) - , m_value(value) -{ - assert(value < min || value > max); - - m_message += "property '" + m_name + "' is out of range "; - m_message += std::to_string(min) + ".." + std::to_string(max) + ", got " + std::to_string(value); -} - -PropertyError::PropertyError(std::string name, std::string message) - : m_name(std::move(name)) -{ - m_message += "property '" + m_name + "': " + message; -} - -/* - * Command implementation - * ------------------------------------------------------------------ - */ - -std::string Command::usage() const -{ - std::ostringstream oss; - - oss << m_name << " "; - - // Options. - auto optlist = options(); - - if (optlist.size() > 0) { - for (const auto &opt : optlist) { - oss << "["; - - /* - * Long options are too big so only show them in the help - * command usage or only if no short option is available. - */ - if (opt.simpleKey().size() > 0) - oss << "-" << opt.simpleKey(); - else if (opt.longKey().size() > 0) - oss << " --" << opt.longKey(); - - oss << (opt.arg().empty() ? "" : " ") << opt.arg() << "] "; - } - } - - // Arguments. - auto argslist = args(); - - if (argslist.size() > 0) { - for (const auto &arg : argslist) - oss << (arg.required() ? "" : "[") - << arg.name() - << (arg.required() ? "" : "]") << " "; - } - - return oss.str(); -} - -std::string Command::help() const -{ - std::ostringstream oss; - - oss << "usage: " << sys::programName() << " " << m_name; - - // Options summary. - if (options().size() > 0) - oss << " [options...]"; - - // Arguments summary. - if (args().size() > 0) { - oss << " "; - - for (const auto &arg : args()) - oss << (arg.required() ? "" : "[") << arg.name() << (arg.required() ? "" : "]") << " "; - } - - // Description. - oss << "\n\n" << m_description << "\n\n"; - - // Options. - if (options().size() > 0) { - oss << "Options:\n"; - - for (const auto &opt : options()) { - std::ostringstream optoss; - - // Construct the line for the option in a single string to pad it correctly. - optoss << " "; - optoss << (!opt.simpleKey().empty() ? ("-"s + opt.simpleKey() + " ") : " "); - optoss << (!opt.longKey().empty() ? ("--"s + opt.longKey() + " "s) : ""); - optoss << opt.arg(); - - // Add it padded with spaces. - oss << std::left << std::setw(28) << optoss.str(); - oss << opt.description() << "\n"; - } - } - - return oss.str(); -} - -unsigned Command::min() const noexcept -{ - auto list = args(); - - return std::accumulate(list.begin(), list.end(), 0U, [] (unsigned i, const auto &arg) noexcept -> unsigned { - return i + (arg.required() ? 1 : 0); - }); -} - -unsigned Command::max() const noexcept -{ - return (unsigned)args().size(); -} - -nlohmann::json Command::request(Irccdctl &, const CommandRequest &) const -{ - return nlohmann::json::object({}); -} - -nlohmann::json Command::exec(Irccd &, const nlohmann::json &request) const -{ - // Verify that requested properties are present in the request. - for (const auto &prop : properties()) { - auto it = request.find(prop.name()); - - if (it == request.end()) - throw std::invalid_argument("missing '{}' property"_format(prop.name())); - - if (std::find(prop.types().begin(), prop.types().end(), it->type()) == prop.types().end()) { - auto expected = typeNameList(prop.types()); - auto got = typeName(it->type()); - - throw std::invalid_argument("invalid '{}' property ({} expected, got {})"_format(prop.name(), expected, got)); - } - } - - return nlohmann::json::object({}); -} - -void Command::result(Irccdctl &, const nlohmann::json &response) const -{ - auto it = response.find("error"); - - if (it != response.end() && it->is_string()) - log::warning() << "irccdctl: " << it->dump() << std::endl; -} - -} // !irccd
--- a/libirccd/irccd/command.hpp Sun Oct 23 11:20:29 2016 +0200 +++ b/libirccd/irccd/command.hpp Sun Oct 23 11:25:26 2016 +0200 @@ -35,336 +35,26 @@ class Irccd; class Irccdctl; - -/** - * \brief A JSON property is missing. - */ -class MissingPropertyError : public std::exception { -private: - std::string m_message; - std::string m_name; - std::vector<nlohmann::json::value_t> m_types; - -public: - /** - * Constructor. - */ - MissingPropertyError(std::string name, std::vector<nlohmann::json::value_t> types); - - /** - * Get human error message. - * - * \return a message - */ - const char *what() const noexcept override - { - return m_message.c_str(); - } -}; +class TransportClient; /** - * \brief A JSON property is invalid + * \brief Server side remote command */ -class InvalidPropertyError : public std::exception { -private: - std::string m_message; - std::string m_name; - - nlohmann::json::value_t m_expected; - nlohmann::json::value_t m_result; - -public: - /** - * Constructor. - * - * \param name the property name - * \param expected the expected type - * \param result the type received - */ - InvalidPropertyError(std::string name, nlohmann::json::value_t expected, nlohmann::json::value_t result); - - /** - * Get human error message. - * - * \return a message - */ - const char *what() const noexcept override - { - return m_message.c_str(); - } -}; - -/** - * \brief Property range error. - */ -class PropertyRangeError : public std::exception { +class Command { private: - std::string m_message; - std::string m_name; - std::uint64_t m_min; - std::uint64_t m_max; - std::uint64_t m_value; - -public: - /** - * Constructor. - * - * \pre value < min || value > max - * \param name the property name - * \param min the minimum value - * \param max the maximum value - * \param value the actual value - */ - PropertyRangeError(std::string name, std::uint64_t min, std::uint64_t max, std::uint64_t value); - - /** - * Get human error message. - * - * \return a message - */ - const char *what() const noexcept override - { - return m_message.c_str(); - } -}; - -/** - * \brief Generic error for JSON properties. - */ -class PropertyError : public std::exception { -private: - std::string m_message; std::string m_name; public: /** - * Constructor. - * - * \param name the property name - * \param message the error message - */ - PropertyError(std::string name, std::string message); - - /** - * Get human error message. - * - * \return a message - */ - const char *what() const noexcept override - { - return m_message.c_str(); - } -}; - - -/** - * \brief Namespace for remote commands. - */ -//namespace command { - -/** - * \brief Command line arguments to irccdctl. - * - * This class contains the resolved arguments from command line that can apply - * to the command. - */ -class CommandRequest { -public: - /** - * The options given by command line. - */ - using Options = std::multimap<std::string, std::string>; - - /** - * Command line arguments in the same order. - */ - using Args = std::vector<std::string>; - -private: - Options m_options; - Args m_args; - -public: - /** - * Construct the request. + * Construct a command. * - * \param options the options - * \param args the arguments - */ - inline CommandRequest(Options options, Args args) noexcept - : m_options(std::move(options)) - , m_args(std::move(args)) - { - } - - /** - * Get the arguments. - * - * \return the arguments - */ - inline const Args &args() const noexcept - { - return m_args; - } - - /** - * Get the options. - * - * \return the options - */ - inline const Options &options() const noexcept - { - return m_options; - } - - /** - * Get the number of arguments. - * - * \return the number of arguments - */ - inline unsigned length() const noexcept - { - return (unsigned)m_args.size(); - } - - /** - * Check if the request has the given option id. - * - * \param option the option id - * \return true if the option is available - */ - inline bool has(const std::string &option) const noexcept - { - return m_options.count(option) != 0; - } - - /** - * Get the argument at the specified index. - * - * \pre index < length() - * \param index the argument index - * \return the argument + * \pre !name.empty() + * \param name the command name */ - inline const std::string &arg(unsigned index) const noexcept - { - assert(index < m_args.size()); - - return m_args[index]; - } - - /** - * Get the argument or default value if not available. - * - * \param index the index - * \param defaultValue the value if index is out of range - * \return the argument - */ - inline std::string argOr(unsigned index, std::string defaultValue) const noexcept - { - return index < m_args.size() ? m_args[index] : defaultValue; - } - - /** - * Get the given option by its id. - * - * \pre has(key) - * \param key the option id - * \return the option - */ - inline const std::string &option(const std::string &key) const noexcept - { - assert(m_options.count(key) != 0); - - return m_options.find(key)->second; - } - - /** - * Get the given option by its id or defaultValue if not found. - * - * \param key the option id - * \param defaultValue the value replacement - * \return the option - */ - inline std::string optionOr(const std::string &key, std::string defaultValue) const noexcept - { - auto it = m_options.find(key); - - if (it == m_options.end()) - return defaultValue; - - return it->second; - } -}; - -/** - * \brief Invokable command. - * - * A remote command is a invokable command in the irccd daemon. You can register - * dynamically any remote command you like using Application::addCommand. - * - * The remote command will be usable directly from irccdctl without any other - * code. - * - * A remote command can have options and arguments. Options always come first, - * before arguments. - * - * The command workflow is defined as follow: - * - * 1. User wants to invoke a command, request() is called and return a JSON - * object containaing the request, it it send to the daemon. - * - * 2. The daemon receive the request and execute it using exec(). It returns a - * JSON object containint the request result or error if any. - * - * 3. Finally, the command receives the result in result() function and user can - * manipulate it. For convenience, the default implementation shows the error - * if any. - */ -class Command { -public: - /** - * \brief Defines available options for this command. - */ - class Option; - - /** - * \brief Defines available arguments for this command. - */ - class Arg; - - /** - * \brief Defines properties that must be available in the JSON request. - */ - class Property; - -private: - std::string m_name; - std::string m_category; - std::string m_description; - bool m_visible; - -public: - /** - * Create the remote command. - * - * \pre name must not be empty - * \pre category must not be empty - * \param name the command name (e.g. server-list) - * \param category the category (e.g. Server) - * \param description a one line description with no dots, no new line - * \param visible true if the command should be visible without verbosity - */ - inline Command(std::string name, - std::string category, - std::string description, - bool visible = true) noexcept + inline Command(std::string name) noexcept : m_name(std::move(name)) - , m_category(std::move(category)) - , m_description(std::move(description)) - , m_visible(visible) { assert(!m_name.empty()); - assert(!m_category.empty()); } /** @@ -383,325 +73,19 @@ } /** - * Get the command category. - * - * Irccdctl will sort commands by categories. - * - * \return the category - */ - inline const std::string &category() const noexcept - { - return m_category; - } - - /** - * Get the command description. - * - * \return the description - */ - inline const std::string &description() const noexcept - { - return m_description; - } - - /** - * Hide the command in non-verbose mode. - * - * \return true if the command should be visible in non-verbose mode - */ - inline bool visible() const noexcept - { - return m_visible; - } - - /** - * Return the command documentation usage. - * - * \return the usage - */ - IRCCD_EXPORT std::string usage() const; - - /** - * Return the help message. - * - * \return the help message - */ - IRCCD_EXPORT std::string help() const; - - /** - * Get the supported irccdctl options. - * - * \return the options - */ - virtual std::vector<Option> options() const - { - return {}; - } - - /** - * Get the supported arguments. - * - * \return the arguments - */ - virtual std::vector<Arg> args() const - { - return {}; - } - - /** - * Get the properties required in the JSON request. - * - * Default implementation returns empty list. + * Execute the command. * - * \return the required properties - * \note Put only **required** properties - */ - virtual std::vector<Property> properties() const - { - return {}; - } - - /** - * Get the minimum number of arguments required. - * - * \return the minimum - */ - IRCCD_EXPORT unsigned min() const noexcept; - - /** - * Get the maximum number of arguments required. - * - * \return the maximum - */ - IRCCD_EXPORT unsigned max() const noexcept; - - /** - * Prepare a JSON request to the daemon. - * - * If the command is local and does not need to send anything to irccd's - * instance, return a null JSON value. - * - * The default implementation just send the command name with no arguments. - * - * \param irccdctl the irccdctl instance - * \param args the command line arguments and options - * \return the JSON object to send to the daemon - * \post the returned JSON value must be an object - */ - IRCCD_EXPORT virtual nlohmann::json request(Irccdctl &irccdctl, const CommandRequest &args) const; - - /** - * Execute the command in the daemon. - * - * The user can return an object with any properties to forward to the - * client. Irccd will automatically add the command name and the appropriate - * status code. - * - * The default return an empty object which indicates success. - * - * If any exception is thrown from this function, it is forwarded to the - * client as error status. - * - * \param irccd the instance - * \param request the JSON request - * \return the response - */ - IRCCD_EXPORT virtual nlohmann::json exec(Irccd &irccd, const nlohmann::json &request) const; - - /** - * What to do when receiving the response from irccd. - * - * This default implementation just check for an error string and shows it - * if any. - * - * \param irccdctl the irccdctl instance - * \param response the JSON response - */ - IRCCD_EXPORT virtual void result(Irccdctl &irccdctl, const nlohmann::json &response) const; -}; - -/** - * \brief Option description for a command. - */ -class Command::Option { -private: - std::string m_id; - std::string m_simple; - std::string m_long; - std::string m_arg; - std::string m_description; - -public: - /** - * Constructor an option description. - * - * Simple and long keys must not start with '-' or '--', they will be added - * automatically. - * - * If arg is not empty, the option takes an argument. + * If the command throw an exception, the error is sent to the client so be + * careful about sensitive information. * - * \pre id must not be empty - * \pre at least simpleKey or longKey must not be empty - * \pre description must not be empty - * \param id the option id - * \param simpleKey the key the option key - * \param longKey the long option name - * \param arg the argument name if needed - * \param description the description - */ - inline Option(std::string id, - std::string simpleKey, - std::string longKey, - std::string arg, - std::string description) noexcept - : m_id(std::move(id)) - , m_simple(std::move(simpleKey)) - , m_long(std::move(longKey)) - , m_arg(std::move(arg)) - , m_description(std::move(description)) - { - assert(!m_id.empty()); - assert(!m_simple.empty() || !m_long.empty()); - assert(!m_description.empty()); - } - - /** - * Get the id. + * The implementation should use client.success() or client.error() to send + * some data. * - * \return the id - */ - inline const std::string &id() const noexcept - { - return m_id; - } - - /** - * Get the option key. - * - * \return the key - */ - inline const std::string &simpleKey() const noexcept - { - return m_simple; - } - - /** - * Get the long option. - * - * \return the long option - */ - inline const std::string &longKey() const noexcept - { - return m_long; - } - - /** - * Get the option description. - * - * \return the description - */ - inline const std::string &description() const noexcept - { - return m_description; - } - - /** - * Get the option argument name. - * - * \return the argument name if any + * \param irccd the irccd instance + * \param client the client + * \param args the client arguments */ - inline const std::string &arg() const noexcept - { - return m_arg; - } -}; - -/** - * \brief Argument description for command. - */ -class Command::Arg { -private: - std::string m_name; - bool m_required; - -public: - /** - * Construct an argument. - * - * \param name the name - * \param required true if the argument is required - */ - inline Arg(std::string name, bool required) noexcept - : m_name(std::move(name)) - , m_required(required) - { - } - - /** - * Get the argument name. - * - * \return the name - */ - inline const std::string &name() const noexcept - { - return m_name; - } - - /** - * Tells if the argument is required. - * - * \return true if required - */ - inline bool required() const noexcept - { - return m_required; - } -}; - -/** - * \brief Property description for JSON request. - */ -class Command::Property { -private: - std::string m_name; - std::vector<nlohmann::json::value_t> m_types; - -public: - /** - * Construct the property description. - * - * \pre !name.empty() - * \pre types.size() >= 1 - * \param name the name - * \param types the json types allowed - */ - inline Property(std::string name, std::vector<nlohmann::json::value_t> types = { nlohmann::json::value_t::string }) noexcept - : m_name(std::move(name)) - , m_types(std::move(types)) - { - assert(!m_name.empty()); - assert(m_types.size() >= 1); - } - - /** - * Get the property name. - * - * \return the name - */ - inline const std::string &name() const noexcept - { - return m_name; - } - - /** - * Get the property types. - * - * \return the types - */ - inline const std::vector<nlohmann::json::value_t> &types() const noexcept - { - return m_types; - } + IRCCD_EXPORT virtual void exec(Irccd &irccd, TransportClient &client, const nlohmann::json &args) = 0; }; } // !irccd
--- a/libirccd/irccd/irccd.cpp Sun Oct 23 11:20:29 2016 +0200 +++ b/libirccd/irccd/irccd.cpp Sun Oct 23 11:25:26 2016 +0200 @@ -53,10 +53,8 @@ void Irccd::run() { - while (m_running) { + while (m_running) util::poller::poll(250, *m_interruptService, *m_servers, *m_transports); - dispatch(); - } } void Irccd::prepare(fd_set &in, fd_set &out, net::Handle &max) @@ -67,10 +65,7 @@ void Irccd::sync(fd_set &in, fd_set &out) { util::poller::sync(in, out, *m_interruptService, *m_servers, *m_transports); -} -void Irccd::dispatch() -{ /* * Make a copy because the events can add other events while we are * iterating it. Also lock because the timers may alter these events too.
--- a/libirccd/irccd/irccd.hpp Sun Oct 23 11:20:29 2016 +0200 +++ b/libirccd/irccd/irccd.hpp Sun Oct 23 11:25:26 2016 +0200 @@ -154,16 +154,11 @@ IRCCD_EXPORT void post(std::function<void (Irccd &)> ev) noexcept; /** - * Loop forever by calling poll() and dispatch() indefinitely. + * Loop forever by calling prepare and sync indefinitely. */ IRCCD_EXPORT void run(); /** - * Dispatch the pending events, usually after calling poll(). - */ - IRCCD_EXPORT void dispatch(); - - /** * Request to stop, usually from a signal. */ IRCCD_EXPORT void stop();
--- a/libirccd/irccd/server.hpp Sun Oct 23 11:20:29 2016 +0200 +++ b/libirccd/irccd/server.hpp Sun Oct 23 11:25:26 2016 +0200 @@ -633,7 +633,7 @@ * * \param nickname the nickname */ - IRCCD_EXPORT void setNickname(std::string nickname); + IRCCD_EXPORT virtual void setNickname(std::string nickname); /** * Get the username. @@ -816,7 +816,7 @@ /** * Asks for a reconnection. */ - IRCCD_EXPORT void reconnect() noexcept; + IRCCD_EXPORT virtual void reconnect() noexcept; /** * Prepare the IRC session.
--- a/libirccd/irccd/service-command.cpp Sun Oct 23 11:20:29 2016 +0200 +++ b/libirccd/irccd/service-command.cpp Sun Oct 23 11:25:26 2016 +0200 @@ -36,4 +36,16 @@ return it == m_commands.end() ? nullptr : *it; } +void CommandService::add(std::shared_ptr<Command> command) +{ + auto it = std::find_if(m_commands.begin(), m_commands.end(), [&] (const auto &cmd) { + return cmd->name() == command->name(); + }); + + if (it != m_commands.end()) + *it = std::move(command); + else + m_commands.push_back(std::move(command)); +} + } // !irccd
--- a/libirccd/irccd/service-command.hpp Sun Oct 23 11:20:29 2016 +0200 +++ b/libirccd/irccd/service-command.hpp Sun Oct 23 11:25:26 2016 +0200 @@ -66,6 +66,14 @@ * \return the command or empty one if not found */ IRCCD_EXPORT std::shared_ptr<Command> find(const std::string &name) const noexcept; + + /** + * Add a command or replace existing one. + * + * \pre command != nullptr + * \param command the command name + */ + IRCCD_EXPORT void add(std::shared_ptr<Command> command); }; } // !irccd
--- a/libirccd/irccd/service-transport.cpp Sun Oct 23 11:20:29 2016 +0200 +++ b/libirccd/irccd/service-transport.cpp Sun Oct 23 11:25:26 2016 +0200 @@ -37,7 +37,6 @@ if (!tc) return; - // 1. Check if the Json object is valid. auto name = object.find("command"); if (name == object.end() || !name->is_string()) { // TODO: send error. @@ -45,36 +44,17 @@ return; } - // 2. Search for a command auto cmd = m_irccd.commands().find(*name); - if (!cmd) { - // TODO: send error again. - log::warning("command does not exists"); - return; + if (!cmd) + tc->error(*name, "command does not exist"); + else { + try { + cmd->exec(m_irccd, *tc, object); + } catch (const std::exception &ex) { + tc->error(cmd->name(), ex.what()); + } } - - // 3. Try to execute it. - auto response = nlohmann::json::object({}); - - try { - response = cmd->exec(m_irccd, object); - - // Adjust if command has returned something else. - if (!response.is_object()) - response = nlohmann::json::object({}); - - response.push_back({"status", true}); - } catch (const std::exception &ex) { - response.push_back({"status", false}); - response.push_back({"error", ex.what()}); - } - - // 4. Store the command name result. - response.push_back({"response", *name}); - - // 5. Send the result. - tc->send(response); }); }
--- a/libirccd/irccd/transport.cpp Sun Oct 23 11:20:29 2016 +0200 +++ b/libirccd/irccd/transport.cpp Sun Oct 23 11:25:26 2016 +0200 @@ -232,6 +232,35 @@ m_output += "\r\n\r\n"; } +void TransportClient::success(const std::string &cmd, nlohmann::json extra) +{ + assert(extra.is_object() || extra.is_null()); + + if (!extra.is_object()) + extra = nlohmann::json::object(); + + extra["command"] = cmd; + extra["status"] = true; + + m_output += extra.dump(); + m_output += "\r\n\r\n"; +} + +void TransportClient::error(const std::string &cmd, const std::string &error, nlohmann::json extra) +{ + assert(extra.is_object() || extra.is_null()); + + if (!extra.is_object()) + extra = nlohmann::json::object(); + + extra["command"] = cmd; + extra["status"] = false; + extra["error"] = error; + + m_output += extra.dump(); + m_output += "\r\n\r\n"; +} + /* * TransportClientTls * ------------------------------------------------------------------
--- a/libirccd/irccd/transport.hpp Sun Oct 23 11:20:29 2016 +0200 +++ b/libirccd/irccd/transport.hpp Sun Oct 23 11:25:26 2016 +0200 @@ -155,6 +155,27 @@ * \copydoc Service::sync */ IRCCD_EXPORT virtual void sync(fd_set &in, fd_set &out); + + /** + * Send a successful command to the client with optional extra data + * + * \pre extra must be null or object + * \param cmd the command name + * \param extra the optional extra data + */ + IRCCD_EXPORT void success(const std::string &cmd, nlohmann::json extra = nullptr); + + /** + * Send an error status to the client. + * + * \pre extra must be null or object + * \param cmd the command name + * \param error the error string + * \param extra the optional extra data + */ + IRCCD_EXPORT void error(const std::string &cmd, + const std::string &error, + nlohmann::json extra = nullptr); }; /*
--- a/libirccdctl/CMakeLists.txt Sun Oct 23 11:20:29 2016 +0200 +++ b/libirccdctl/CMakeLists.txt Sun Oct 23 11:25:26 2016 +0200 @@ -19,4 +19,5 @@ ${HEADERS} ${SOURCES} LIBRARIES libcommon + PUBLIC_INCLUDES ${libirccdctl_SOURCE_DIR}/irccd )
--- a/libirccdctl/irccd/irccdctl.cpp Sun Oct 23 11:20:29 2016 +0200 +++ b/libirccdctl/irccd/irccdctl.cpp Sun Oct 23 11:25:26 2016 +0200 @@ -16,631 +16,4 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -#include <format.h> - -#include "command.hpp" -#include "client.hpp" -#include "elapsed-timer.hpp" -#include "fs.hpp" -#include "ini.hpp" -#include "irccdctl.hpp" -#include "logger.hpp" -#include "options.hpp" -#include "path.hpp" -#include "system.hpp" -#include "util.hpp" - -using namespace std::string_literals; - -using namespace fmt::literals; - -namespace irccd { - -void Irccdctl::usage() const -{ - bool first = true; - - for (const auto &cmd : m_commandService.commands()) { - log::warning() << (first ? "usage: " : " ") << sys::programName() << " " - << cmd->usage() << std::endl; - first = false; - } - - std::exit(1); -} - -void Irccdctl::help() const -{ - log::warning() << "usage: " << sys::programName() << " [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\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 : m_commandService.commands()) - log::warning() << "\t" << std::left << std::setw(32) - << cmd->name() << cmd->description() << std::endl; - - log::warning() << "\nFor more information on a command, type " << sys::programName() << " help <command>" << std::endl; - - std::exit(1); -} - -/* - * Configuration file parsing. - * ------------------------------------------------------------------- - */ - -/* - * readConnectIp - * ------------------------------------------------------------------- - * - * Extract IP connection information from the config file. - * - * [connect] - * type = "ip" - * host = "ip or hostname" - * port = "port number or service" - * domain = "ipv4 or ipv6" (Optional, default: ipv4) - * ssl = true | false - */ -void Irccdctl::readConnectIp(const ini::Section &sc) -{ - ini::Section::const_iterator it; - - std::string host, port; - - if ((it = sc.find("host")) == sc.end()) - throw std::invalid_argument("missing host parameter"); - - host = it->value(); - - 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()) { - if (it->value() == "ipv6") - domain = AF_INET6; - else if (it->value() == "ipv4") - domain = AF_INET; - else - throw std::invalid_argument("invalid domain: " + it->value()); - } - - m_address = net::resolveOne(host, port, domain, SOCK_STREAM); - - if ((it = sc.find("ssl")) != sc.end() && util::isBoolean(it->value())) - m_connection = std::make_unique<TlsClient>(); - else - m_connection = std::make_unique<Client>(); -} - -/* - * readConnectLocal - * ------------------------------------------------------------------- - * - * Extract local connection for Unix. - * - * [connect] - * type = "unix" - * path = "path to socket file" - */ -void Irccdctl::readConnectLocal(const ini::Section &sc) -{ -#if !defined(IRCCD_SYSTEM_WINDOWS) - auto it = sc.find("path"); - - if (it == sc.end()) - throw std::invalid_argument("missing path parameter"); - - m_address = net::local::create(it->value()); - m_connection = std::make_unique<Client>(); -#else - (void)sc; - - throw std::invalid_argument("unix connection not supported on Windows"); -#endif -} - -/* - * readConnect - * ------------------------------------------------------------------- - * - * Generic function for reading the [connect] section. - */ -void Irccdctl::readConnect(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()); - - auto password = sc.find("password"); - - if (password != sc.end()) - m_connection->setPassword(password->value()); -} - -/* - * readGeneral - * ------------------------------------------------------------------- - * - * Read the general section. - * - * [general] - * verbose = true - */ -void Irccdctl::readGeneral(const ini::Section &sc) -{ - auto verbose = sc.find("verbose"); - - if (verbose != sc.end()) - log::setVerbose(util::isBoolean(verbose->value())); -} - -/* - * readAliases - * ------------------------------------------------------------------- - * - * Read aliases for irccdctl. - * - * [alias] - * name = ( "command", "arg1, "...", "argn" ) - */ -void Irccdctl::readAliases(const ini::Section &sc) -{ - for (const auto &option : sc) { - // This is the alias name. - Alias alias(option.key()); - - // Iterate over the list of commands to execute for this alias. - for (const auto &repl : option) { - // This is the alias split string. - auto list = util::split(repl, " \t"); - - if (list.size() < 1) - throw std::invalid_argument("alias require at least one argument"); - - // First argument is the command/alias to execute. - auto command = list[0]; - - // Remove command name and puts arguments. - alias.push_back({std::move(command), std::vector<AliasArg>(list.begin() + 1, list.end())}); - } - - m_aliases.emplace(option.key(), std::move(alias)); - } -} - -void Irccdctl::read(const std::string &path) -{ - try { - ini::Document doc = ini::readFile(path); - ini::Document::const_iterator it; - - if (!m_connection && (it = doc.find("connect")) != doc.end()) - readConnect(*it); - if ((it = doc.find("general")) != doc.end()) - readGeneral(*it); - if ((it = doc.find("alias")) != doc.end()) - readAliases(*it); - } catch (const std::exception &ex) { - log::warning() << path << ": " << ex.what() << std::endl; - } -} - -/* - * Command line parsing. - * ------------------------------------------------------------------- - */ - -/* - * parseConnectIp - * ------------------------------------------------------------------ - * - * Parse internet connection from command line. - * - * -t ip | ipv6 - * -h host or ip - * -p port - */ -void Irccdctl::parseConnectIp(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()) - throw std::invalid_argument("missing host argument (-h or --host)"); - - host = it->second; - - // Port (-p or --port). - std::string port; - - 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; - - 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; - - m_address = net::resolveOne(host, port, domain, SOCK_STREAM); - m_connection = std::make_unique<Client>(); -} - -/* - * parseConnectLocal - * ------------------------------------------------------------------ - * - * Parse local connection. - * - * -P file - */ -void Irccdctl::parseConnectLocal(const option::Result &options) -{ -#if !defined(IRCCD_SYSTEM_WINDOWS) - option::Result::const_iterator it; - - if ((it = options.find("-P")) == options.end() && (it = options.find("--path")) == options.end()) - throw std::invalid_argument("missing path parameter (-P or --path)"); - - m_address = net::local::create(it->second, false); - m_connection = std::make_unique<Client>(); -#else - (void)options; - - throw std::invalid_argument("unix connection not supported on Windows"); -#endif -} - -/* - * parseConnect - * ------------------------------------------------------------------ - * - * Generic parsing of command line option for connection. - */ -void Irccdctl::parseConnect(const option::Result &options) -{ - assert(options.count("-t") > 0 || options.count("--type") > 0); - - auto it = options.find("-t"); - - if (it == options.end()) - it = options.find("--type"); - if (it->second == "ip" || it->second == "ipv6") - return parseConnectIp(options); - if (it->second == "unix") - return parseConnectLocal(options); - - throw std::invalid_argument("invalid type given: " + it->second); -} - -option::Result Irccdctl::parse(int &argc, char **&argv) -{ - // 1. Parse command line options. - option::Options def{ - { "-c", true }, - { "--config", true }, - { "-h", true }, - { "--help", false }, - { "--host", true }, - { "-p", true }, - { "--port", true }, - { "-P", true }, - { "--path", true }, - { "-t", true }, - { "--type", true }, - { "-v", false }, - { "--verbose", false } - }; - - option::Result result; - - try { - result = option::read(argc, argv, def); - - if (result.count("--help") != 0) { - usage(); - // NOTREACHED - } - - if (result.count("-v") != 0 || result.count("--verbose") != 0) - log::setVerbose(true); - } catch (const std::exception &ex) { - log::warning("{}: {}"_format(sys::programName(), ex.what())); - usage(); - } - - return result; -} - -nlohmann::json Irccdctl::waitMessage(const std::string id) -{ - ElapsedTimer timer; - - while (m_messages.empty() && m_connection->isConnected() && timer.elapsed() < m_timeout) - util::poller::poll(250, *m_connection); - - if (m_messages.empty()) - return nlohmann::json(); - - nlohmann::json value; - - if (id == "") { - value = m_messages[0]; - m_messages.erase(m_messages.begin()); - } else { - auto it = std::find_if(m_messages.begin(), m_messages.end(), [&] (const auto &v) { - auto rt = v.find("response"); - - if (v.count("error") > 0 || (rt != v.end() && rt->is_string() && *rt == id)) - return true; - - return false; - }); - - // Remove the previous messages. - if (it != m_messages.end()) { - value = *it; - m_messages.erase(m_messages.begin(), it + 1); - } - } - - auto error = value.find("error"); - - if (error != value.end() && error->is_string()) - throw std::runtime_error(error->template get<std::string>()); - - return value; -} - -nlohmann::json Irccdctl::waitEvent() -{ - ElapsedTimer timer; - - while (m_events.empty() && m_connection->isConnected() && timer.elapsed() < m_timeout) - util::poller::poll(250, *m_connection); - - if (m_events.empty()) - return nullptr; - - auto first = m_events.front(); - m_events.erase(m_events.begin()); - - return first; -} - -nlohmann::json Irccdctl::exec(const Command &cmd, std::vector<std::string> args) -{ - // 1. Build options from command line arguments. - option::Options def; - - for (const auto &opt : cmd.options()) { - // parser::read needs '-' and '--' so add them. - if (!opt.simpleKey().empty()) - def.emplace("-"s + opt.simpleKey(), !opt.arg().empty()); - if (!opt.longKey().empty()) - def.emplace("--"s + opt.longKey(), !opt.arg().empty()); - } - - // 2. Parse them, remove them from args (in parser::read) and build the map with id. - CommandRequest::Options requestOptions; - - for (const auto &pair : option::read(args, def)) { - auto options = cmd.options(); - auto it = std::find_if(options.begin(), options.end(), [&] (const auto &opt) { - return ("-"s + opt.simpleKey()) == pair.first || ("--"s + opt.longKey()) == pair.first; - }); - - requestOptions.emplace(it->id(), pair.second); - } - - // 3. Check number of arguments. - if (args.size() < cmd.min()) - throw std::runtime_error("too few arguments"); - - /* - * 4. Construct the request, if the returned value is not an object, do not - * send anything (e.g. help). - */ - auto request = cmd.request(*this, CommandRequest(std::move(requestOptions), std::move(args))); - - if (!request.is_object()) - throw std::invalid_argument("command has returned invalid request"); - - request.push_back({"command", cmd.name()}); - - // 5. Send the command. - m_connection->request(request); - - // 6. Returns the response. - return waitMessage(cmd.name()); -} - -std::vector<nlohmann::json> Irccdctl::exec(const Alias &alias, std::vector<std::string> argsCopy) -{ - std::vector<nlohmann::json> values; - - for (const AliasCommand &cmd : alias) { - std::vector<std::string> args(argsCopy); - std::vector<std::string> cmdArgs; - std::vector<std::string>::size_type toremove = 0; - - // 1. Append command name before. - cmdArgs.push_back(cmd.command()); - - for (const auto &arg : cmd.args()) { - if (arg.isPlaceholder()) { - if (args.size() < arg.index() + 1) - throw std::invalid_argument("missing argument for placeholder %" + std::to_string(arg.index())); - - cmdArgs.push_back(args[arg.index()]); - - if (arg.index() + 1 > toremove) - toremove = arg.index() + 1; - } else - cmdArgs.push_back(arg.value()); - } - - assert(toremove <= args.size()); - - // 2. Remove the arguments that been placed in placeholders. - args.erase(args.begin(), args.begin() + toremove); - - // 3. Now append the rest of arguments. - std::copy(args.begin(), args.end(), std::back_inserter(cmdArgs)); - - // 4. Finally try to execute. - auto response = exec(cmdArgs); - - values.insert(values.end(), response.begin(), response.end()); - } - - return values; -} - -std::vector<nlohmann::json> Irccdctl::exec(std::vector<std::string> args) -{ - assert(args.size() > 0); - - auto name = args[0]; - auto alias = m_aliases.find(name); - - // Remove name. - args.erase(args.begin()); - - std::vector<nlohmann::json> values; - - if (alias != m_aliases.end()) { - auto response = exec(alias->second, args); - - values.insert(values.end(), response.begin(), response.end()); - } else { - auto cmd = m_commandService.find(name); - - if (cmd) - values.push_back(exec(*cmd, args)); - else - throw std::invalid_argument("no alias or command named " + name); - } - - return values; -} - -void Irccdctl::run(int argc, char **argv) -{ - // 1. Read command line arguments. - auto result = parse(argc, argv); - - /* - * 2. Open optional config by command line or by searching it - * - * The connection to irccd is searched in the following order : - * - * 1. From the command line if specified - * 2. From the configuration file specified by -c - * 3. From the configuration file searched through directories - */ - try { - if (result.count("-t") > 0 || result.count("--type") > 0) - parseConnect(result); - - auto it = result.find("-c"); - - if (it != result.end() || (it = result.find("--config")) != result.end()) - read(it->second); - else { - for (const std::string &dir : path::list(path::PathConfig)) { - std::string path = dir + "irccdctl.conf"; - - if (fs::exists(path)) { - read(path); - break; - } - } - } - } catch (const std::exception &ex) { - log::warning() << sys::programName() << ": " << ex.what() << std::endl; - std::exit(1); - } - - if (argc <= 0) { - usage(); - // NOTREACHED - } - - // Help does not require connection. - if (std::strcmp(argv[0], "help") != 0) { - if (!m_connection) { - log::warning("{}: no connection specified"_format(sys::programName())); - std::exit(1); - } - - m_connection->onDisconnect.connect([this] (auto reason) { - log::warning() << "connection lost to irccd: " << reason << std::endl; - }); - m_connection->onConnect.connect([this] (auto info) { - log::info() << "connected to irccd " - << info.major << "." - << info.minor << "." - << info.patch << std::endl; - }); - m_connection->onEvent.connect([this] (auto msg) { - m_events.push_back(std::move(msg)); - }); - m_connection->onMessage.connect([this] (auto msg) { - m_messages.push_back(std::move(msg)); - }); - - m_connection->connect(m_address); - } else if (argc == 1) - help(); - // NOTREACHED - - // Build a vector of arguments. - std::vector<std::string> args; - - for (int i = 0; i < argc; ++i) - args.push_back(argv[i]); - - auto commands = exec(args); - - for (const auto &r : commands) { - auto name = r.find("response"); - - if (name == r.end() || !name->is_string()) - log::warning() << "unknown irccd response with no response" << std::endl; - - auto it = m_commandService.find(*name); - - it->result(*this, r); - } -} - -} // !irccd +// empty for the moment.
--- a/libirccdctl/irccd/irccdctl.hpp Sun Oct 23 11:20:29 2016 +0200 +++ b/libirccdctl/irccd/irccdctl.hpp Sun Oct 23 11:25:26 2016 +0200 @@ -19,153 +19,44 @@ #ifndef IRCCD_IRCCDCTL_HPP #define IRCCD_IRCCDCTL_HPP -/** - * \file irccdctl.hpp - * \brief Base class for irccdctl front end. - */ - -#include <map> #include <memory> -#include <string> -#include <vector> #include "client.hpp" -#include "alias.hpp" -#include "options.hpp" -#include "service-command.hpp" - -#include <json.hpp> namespace irccd { -class Client; - -namespace ini { - -class Document; -class Section; - -} // !ini - /** - * \brief Main irccdctl class. + * \brief Transient class for connecting to irccd */ class Irccdctl { private: - // Commands. - CommandService m_commandService; - - // Connection handler. - std::unique_ptr<Client> m_connection; - std::uint32_t m_timeout{30000}; - net::Address m_address; - - // Aliases. - std::map<std::string, Alias> m_aliases; - - // Incoming data. - std::vector<nlohmann::json> m_events; - std::vector<nlohmann::json> m_messages; - - void usage() const; - void help() const; - - // Parse configuration file. - void readConnectIp(const ini::Section &sc); - void readConnectLocal(const ini::Section &sc); - void readConnect(const ini::Section &sc); - void readGeneral(const ini::Section &sc); - void readAliases(const ini::Section &sc); - void read(const std::string &path); - - // Parse command line options. - void parseConnectIp(const option::Result &options); - void parseConnectLocal(const option::Result &options); - void parseConnect(const option::Result &options); - option::Result parse(int &argc, char **&argv); + std::unique_ptr<Client> m_client; public: - /** - * Get the command service. - * - * \return the command service - */ - inline CommandService &commandService() noexcept + inline Irccdctl(std::unique_ptr<Client> client) noexcept + : m_client(std::move(client)) { - return m_commandService; } - /** - * Get the client connection to irccd. - * - * \return the connection - */ - inline const Client &client() const noexcept + inline Client &client() noexcept { - return *m_connection; + return *m_client; } - /** - * Get the client connection to irccd. - * - * \return the connection - */ - inline Client &client() noexcept + inline const Client &client() const noexcept { - return *m_connection; + return *m_client; } - /** - * Get the next message response with the given id. - * - * If the response id is not provided, get the next incoming message. - * - * Otherwise, if the id is provided, all other previous messages will be - * discarded. - * - * \param id the response id (e.g. server-message) - * \return the next message - * \warning this may skip previous events - */ - IRCCD_EXPORT nlohmann::json waitMessage(const std::string id = ""); - - /** - * Get the next pending even within the internal timeout. - * - * \return the next event or empty if not available - */ - IRCCD_EXPORT nlohmann::json waitEvent(); + inline void prepare(fd_set &in, fd_set &out, net::Handle &max) + { + m_client->prepare(in, out, max); + } - /** - * Execute the given command and wait for its result. - * - * \param cmd the command - * \param args the arguments - */ - IRCCD_EXPORT nlohmann::json exec(const Command &cmd, std::vector<std::string> args); - - /** - * Execute the given alias. - * - * \param alias the alias - * \param args the arguments - */ - IRCCD_EXPORT std::vector<nlohmann::json> exec(const Alias &alias, std::vector<std::string> args); - - /** - * Resolve the command line arguments. - * - * \param args the main arguments - */ - IRCCD_EXPORT std::vector<nlohmann::json> exec(std::vector<std::string> args); - - /** - * Run the irccdctl front end. - * - * \param argc the number of arguments - * \param argv the arguments - */ - IRCCD_EXPORT void run(int argc, char **argv); + inline void sync(fd_set &in, fd_set &out) + { + m_client->sync(in, out); + } }; } // !irccd