changeset 630:711e0bd01eaf

Irccd: move services to service folder
author David Demelier <markand@malikania.fr>
date Wed, 07 Mar 2018 17:49:56 +0100
parents e44fc24e0787
children 1fa9e5222e87
files irccd-test/main.cpp irccd/main.cpp libirccd-js/irccd/js/plugin_jsapi.cpp libirccd-js/irccd/js/server_jsapi.cpp libirccd-test/irccd/test/command_test.hpp libirccd-test/irccd/test/plugin_test.cpp libirccd/CMakeLists.txt libirccd/irccd/daemon/command_service.cpp libirccd/irccd/daemon/command_service.hpp libirccd/irccd/daemon/irccd.cpp libirccd/irccd/daemon/irccd.hpp libirccd/irccd/daemon/plugin_config_command.cpp libirccd/irccd/daemon/plugin_info_command.cpp libirccd/irccd/daemon/plugin_list_command.cpp libirccd/irccd/daemon/plugin_load_command.cpp libirccd/irccd/daemon/plugin_reload_command.cpp libirccd/irccd/daemon/plugin_service.cpp libirccd/irccd/daemon/plugin_service.hpp libirccd/irccd/daemon/plugin_unload_command.cpp libirccd/irccd/daemon/rule_add_command.cpp libirccd/irccd/daemon/rule_edit_command.cpp libirccd/irccd/daemon/rule_info_command.cpp libirccd/irccd/daemon/rule_list_command.cpp libirccd/irccd/daemon/rule_move_command.cpp libirccd/irccd/daemon/rule_remove_command.cpp libirccd/irccd/daemon/rule_service.cpp libirccd/irccd/daemon/rule_service.hpp libirccd/irccd/daemon/server_connect_command.cpp libirccd/irccd/daemon/server_disconnect_command.cpp libirccd/irccd/daemon/server_info_command.cpp libirccd/irccd/daemon/server_invite_command.cpp libirccd/irccd/daemon/server_join_command.cpp libirccd/irccd/daemon/server_kick_command.cpp libirccd/irccd/daemon/server_list_command.cpp libirccd/irccd/daemon/server_me_command.cpp libirccd/irccd/daemon/server_message_command.cpp libirccd/irccd/daemon/server_mode_command.cpp libirccd/irccd/daemon/server_nick_command.cpp libirccd/irccd/daemon/server_notice_command.cpp libirccd/irccd/daemon/server_part_command.cpp libirccd/irccd/daemon/server_reconnect_command.cpp libirccd/irccd/daemon/server_service.cpp libirccd/irccd/daemon/server_service.hpp libirccd/irccd/daemon/server_topic_command.cpp libirccd/irccd/daemon/service/plugin_service.cpp libirccd/irccd/daemon/service/plugin_service.hpp libirccd/irccd/daemon/service/rule_service.cpp libirccd/irccd/daemon/service/rule_service.hpp libirccd/irccd/daemon/service/server_service.cpp libirccd/irccd/daemon/service/server_service.hpp libirccd/irccd/daemon/service/transport_service.cpp libirccd/irccd/daemon/service/transport_service.hpp libirccd/irccd/daemon/transport_service.cpp libirccd/irccd/daemon/transport_service.hpp tests/src/libirccd-js/js-plugin/main.cpp tests/src/libirccd/command-plugin-config/main.cpp tests/src/libirccd/command-plugin-info/main.cpp tests/src/libirccd/command-plugin-list/main.cpp tests/src/libirccd/command-plugin-load/main.cpp tests/src/libirccd/command-plugin-reload/main.cpp tests/src/libirccd/command-plugin-unload/main.cpp tests/src/libirccd/command-rule-add/main.cpp tests/src/libirccd/command-rule-edit/main.cpp tests/src/libirccd/command-rule-info/main.cpp tests/src/libirccd/command-rule-list/main.cpp tests/src/libirccd/command-rule-move/main.cpp tests/src/libirccd/command-rule-remove/main.cpp tests/src/libirccd/command-server-connect/main.cpp tests/src/libirccd/command-server-disconnect/main.cpp tests/src/libirccd/command-server-info/main.cpp tests/src/libirccd/command-server-invite/main.cpp tests/src/libirccd/command-server-join/main.cpp tests/src/libirccd/command-server-kick/main.cpp tests/src/libirccd/command-server-list/main.cpp tests/src/libirccd/command-server-me/main.cpp tests/src/libirccd/command-server-message/main.cpp tests/src/libirccd/command-server-mode/main.cpp tests/src/libirccd/command-server-nick/main.cpp tests/src/libirccd/command-server-notice/main.cpp tests/src/libirccd/command-server-part/main.cpp tests/src/libirccd/command-server-reconnect/main.cpp tests/src/libirccd/command-server-topic/main.cpp tests/src/libirccd/rules/main.cpp tests/src/plugins/plugin/main.cpp
diffstat 84 files changed, 2292 insertions(+), 2377 deletions(-) [+]
line wrap: on
line diff
--- a/irccd-test/main.cpp	Mon Jan 15 13:37:32 2018 +0100
+++ b/irccd-test/main.cpp	Wed Mar 07 17:49:56 2018 +0100
@@ -36,8 +36,9 @@
 
 #include <irccd/daemon/dynlib_plugin.hpp>
 #include <irccd/daemon/irccd.hpp>
-#include <irccd/daemon/plugin_service.hpp>
-#include <irccd/daemon/server_service.hpp>
+
+#include <irccd/daemon/service/plugin_service.hpp>
+#include <irccd/daemon/service/server_service.hpp>
 
 #include <irccd/test/debug_server.hpp>
 
--- a/irccd/main.cpp	Mon Jan 15 13:37:32 2018 +0100
+++ b/irccd/main.cpp	Wed Mar 07 17:49:56 2018 +0100
@@ -38,7 +38,6 @@
 #include <irccd/string_util.hpp>
 #include <irccd/system.hpp>
 
-#include <irccd/daemon/command_service.hpp>
 #include <irccd/daemon/irccd.hpp>
 #include <irccd/daemon/logger.hpp>
 #include <irccd/daemon/plugin_config_command.hpp>
@@ -47,7 +46,6 @@
 #include <irccd/daemon/plugin_list_command.hpp>
 #include <irccd/daemon/plugin_load_command.hpp>
 #include <irccd/daemon/plugin_reload_command.hpp>
-#include <irccd/daemon/plugin_service.hpp>
 #include <irccd/daemon/plugin_unload_command.hpp>
 #include <irccd/daemon/rule_add_command.hpp>
 #include <irccd/daemon/rule_edit_command.hpp>
@@ -55,7 +53,6 @@
 #include <irccd/daemon/rule_list_command.hpp>
 #include <irccd/daemon/rule_move_command.hpp>
 #include <irccd/daemon/rule_remove_command.hpp>
-#include <irccd/daemon/rule_service.hpp>
 #include <irccd/daemon/server_connect_command.hpp>
 #include <irccd/daemon/server_disconnect_command.hpp>
 #include <irccd/daemon/server_info_command.hpp>
@@ -70,9 +67,12 @@
 #include <irccd/daemon/server_notice_command.hpp>
 #include <irccd/daemon/server_part_command.hpp>
 #include <irccd/daemon/server_reconnect_command.hpp>
-#include <irccd/daemon/server_service.hpp>
 #include <irccd/daemon/server_topic_command.hpp>
-#include <irccd/daemon/transport_service.hpp>
+
+#include <irccd/daemon/service/plugin_service.hpp>
+#include <irccd/daemon/service/rule_service.hpp>
+#include <irccd/daemon/service/server_service.hpp>
+#include <irccd/daemon/service/transport_service.hpp>
 
 #if defined(HAVE_JS)
 #   include <irccd/js/js_plugin.hpp>
@@ -202,33 +202,33 @@
 
     auto options = parse(argc, argv);
 
-    instance->commands().add(std::make_unique<plugin_config_command>());
-    instance->commands().add(std::make_unique<plugin_info_command>());
-    instance->commands().add(std::make_unique<plugin_list_command>());
-    instance->commands().add(std::make_unique<plugin_load_command>());
-    instance->commands().add(std::make_unique<plugin_reload_command>());
-    instance->commands().add(std::make_unique<plugin_unload_command>());
-    instance->commands().add(std::make_unique<server_connect_command>());
-    instance->commands().add(std::make_unique<server_disconnect_command>());
-    instance->commands().add(std::make_unique<server_info_command>());
-    instance->commands().add(std::make_unique<server_invite_command>());
-    instance->commands().add(std::make_unique<server_join_command>());
-    instance->commands().add(std::make_unique<server_kick_command>());
-    instance->commands().add(std::make_unique<server_list_command>());
-    instance->commands().add(std::make_unique<server_me_command>());
-    instance->commands().add(std::make_unique<server_message_command>());
-    instance->commands().add(std::make_unique<server_mode_command>());
-    instance->commands().add(std::make_unique<server_nick_command>());
-    instance->commands().add(std::make_unique<server_notice_command>());
-    instance->commands().add(std::make_unique<server_part_command>());
-    instance->commands().add(std::make_unique<server_reconnect_command>());
-    instance->commands().add(std::make_unique<server_topic_command>());
-    instance->commands().add(std::make_unique<rule_add_command>());
-    instance->commands().add(std::make_unique<rule_edit_command>());
-    instance->commands().add(std::make_unique<rule_info_command>());
-    instance->commands().add(std::make_unique<rule_list_command>());
-    instance->commands().add(std::make_unique<rule_move_command>());
-    instance->commands().add(std::make_unique<rule_remove_command>());
+    instance->transports().get_commands().push_back(std::make_unique<plugin_config_command>());
+    instance->transports().get_commands().push_back(std::make_unique<plugin_info_command>());
+    instance->transports().get_commands().push_back(std::make_unique<plugin_list_command>());
+    instance->transports().get_commands().push_back(std::make_unique<plugin_load_command>());
+    instance->transports().get_commands().push_back(std::make_unique<plugin_reload_command>());
+    instance->transports().get_commands().push_back(std::make_unique<plugin_unload_command>());
+    instance->transports().get_commands().push_back(std::make_unique<server_connect_command>());
+    instance->transports().get_commands().push_back(std::make_unique<server_disconnect_command>());
+    instance->transports().get_commands().push_back(std::make_unique<server_info_command>());
+    instance->transports().get_commands().push_back(std::make_unique<server_invite_command>());
+    instance->transports().get_commands().push_back(std::make_unique<server_join_command>());
+    instance->transports().get_commands().push_back(std::make_unique<server_kick_command>());
+    instance->transports().get_commands().push_back(std::make_unique<server_list_command>());
+    instance->transports().get_commands().push_back(std::make_unique<server_me_command>());
+    instance->transports().get_commands().push_back(std::make_unique<server_message_command>());
+    instance->transports().get_commands().push_back(std::make_unique<server_mode_command>());
+    instance->transports().get_commands().push_back(std::make_unique<server_nick_command>());
+    instance->transports().get_commands().push_back(std::make_unique<server_notice_command>());
+    instance->transports().get_commands().push_back(std::make_unique<server_part_command>());
+    instance->transports().get_commands().push_back(std::make_unique<server_reconnect_command>());
+    instance->transports().get_commands().push_back(std::make_unique<server_topic_command>());
+    instance->transports().get_commands().push_back(std::make_unique<rule_add_command>());
+    instance->transports().get_commands().push_back(std::make_unique<rule_edit_command>());
+    instance->transports().get_commands().push_back(std::make_unique<rule_info_command>());
+    instance->transports().get_commands().push_back(std::make_unique<rule_list_command>());
+    instance->transports().get_commands().push_back(std::make_unique<rule_move_command>());
+    instance->transports().get_commands().push_back(std::make_unique<rule_remove_command>());
 
 #if defined(HAVE_JS)
     instance->plugins().add_loader(js_plugin_loader::defaults(*instance));
--- a/libirccd-js/irccd/js/plugin_jsapi.cpp	Mon Jan 15 13:37:32 2018 +0100
+++ b/libirccd-js/irccd/js/plugin_jsapi.cpp	Wed Mar 07 17:49:56 2018 +0100
@@ -17,7 +17,8 @@
  */
 
 #include <irccd/daemon/irccd.hpp>
-#include <irccd/daemon/plugin_service.hpp>
+
+#include <irccd/daemon/service/plugin_service.hpp>
 
 #include "irccd_jsapi.hpp"
 #include "js_plugin.hpp"
--- a/libirccd-js/irccd/js/server_jsapi.cpp	Mon Jan 15 13:37:32 2018 +0100
+++ b/libirccd-js/irccd/js/server_jsapi.cpp	Wed Mar 07 17:49:56 2018 +0100
@@ -21,7 +21,8 @@
 #include <unordered_map>
 
 #include <irccd/daemon/irccd.hpp>
-#include <irccd/daemon/server_service.hpp>
+
+#include <irccd/daemon/service/server_service.hpp>
 
 #include "duktape_vector.hpp"
 #include "irccd_jsapi.hpp"
--- a/libirccd-test/irccd/test/command_test.hpp	Mon Jan 15 13:37:32 2018 +0100
+++ b/libirccd-test/irccd/test/command_test.hpp	Wed Mar 07 17:49:56 2018 +0100
@@ -21,11 +21,10 @@
 
 #include <memory>
 
-#include <irccd/daemon/command_service.hpp>
 #include <irccd/daemon/ip_transport_server.hpp>
 #include <irccd/daemon/irccd.hpp>
 #include <irccd/daemon/logger.hpp>
-#include <irccd/daemon/transport_service.hpp>
+#include <irccd/daemon/service/transport_service.hpp>
 
 #include <irccd/ctl/ip_connection.hpp>
 #include <irccd/ctl/controller.hpp>
@@ -38,7 +37,7 @@
     template <typename Command>
     inline void add()
     {
-        daemon_->commands().add(std::make_unique<Command>());
+        daemon_->transports().get_commands().push_back(std::make_unique<Command>());
     }
 
     template <typename C1, typename C2, typename... Tail>
--- a/libirccd-test/irccd/test/plugin_test.cpp	Mon Jan 15 13:37:32 2018 +0100
+++ b/libirccd-test/irccd/test/plugin_test.cpp	Wed Mar 07 17:49:56 2018 +0100
@@ -19,8 +19,9 @@
 #include <cassert>
 
 #include <irccd/daemon/logger.hpp>
-#include <irccd/daemon/plugin_service.hpp>
-#include <irccd/daemon/server_service.hpp>
+
+#include <irccd/daemon/service/plugin_service.hpp>
+#include <irccd/daemon/service/server_service.hpp>
 
 #include <irccd/js/directory_jsapi.hpp>
 #include <irccd/js/elapsed_timer_jsapi.hpp>
--- a/libirccd/CMakeLists.txt	Mon Jan 15 13:37:32 2018 +0100
+++ b/libirccd/CMakeLists.txt	Wed Mar 07 17:49:56 2018 +0100
@@ -25,7 +25,6 @@
     ${libirccd_SOURCE_DIR}/irccd/daemon/basic_transport_client.hpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/basic_transport_server.hpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/command.hpp
-    ${libirccd_SOURCE_DIR}/irccd/daemon/command_service.hpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/dynlib_plugin.hpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/ip_transport_server.hpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/irccd.hpp
@@ -39,7 +38,6 @@
     ${libirccd_SOURCE_DIR}/irccd/daemon/plugin_list_command.hpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/plugin_load_command.hpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/plugin_reload_command.hpp
-    ${libirccd_SOURCE_DIR}/irccd/daemon/plugin_service.hpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/plugin_unload_command.hpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/rule_add_command.hpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/rule_edit_command.hpp
@@ -48,7 +46,6 @@
     ${libirccd_SOURCE_DIR}/irccd/daemon/rule_list_command.hpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/rule_move_command.hpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/rule_remove_command.hpp
-    ${libirccd_SOURCE_DIR}/irccd/daemon/rule_service.hpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/server_connect_command.hpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/server_disconnect_command.hpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/server.hpp
@@ -64,17 +61,18 @@
     ${libirccd_SOURCE_DIR}/irccd/daemon/server_notice_command.hpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/server_part_command.hpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/server_reconnect_command.hpp
-    ${libirccd_SOURCE_DIR}/irccd/daemon/server_service.hpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/server_topic_command.hpp
+    ${libirccd_SOURCE_DIR}/irccd/daemon/service/plugin_service.hpp
+    ${libirccd_SOURCE_DIR}/irccd/daemon/service/rule_service.hpp
+    ${libirccd_SOURCE_DIR}/irccd/daemon/service/server_service.hpp
+    ${libirccd_SOURCE_DIR}/irccd/daemon/service/transport_service.hpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/transport_client.hpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/transport_server.hpp
-    ${libirccd_SOURCE_DIR}/irccd/daemon/transport_service.hpp
     $<$<BOOL:${HAVE_SSL}>:${libirccd_SOURCE_DIR}/irccd/daemon/tls_transport_server.hpp>
 )
 
 set(
     SOURCES
-    ${libirccd_SOURCE_DIR}/irccd/daemon/command_service.cpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/dynlib_plugin.cpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/irccd.cpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/irc.cpp
@@ -85,7 +83,6 @@
     ${libirccd_SOURCE_DIR}/irccd/daemon/plugin_list_command.cpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/plugin_load_command.cpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/plugin_reload_command.cpp
-    ${libirccd_SOURCE_DIR}/irccd/daemon/plugin_service.cpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/plugin_unload_command.cpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/rule_add_command.cpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/rule.cpp
@@ -94,7 +91,6 @@
     ${libirccd_SOURCE_DIR}/irccd/daemon/rule_list_command.cpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/rule_move_command.cpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/rule_remove_command.cpp
-    ${libirccd_SOURCE_DIR}/irccd/daemon/rule_service.cpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/server_connect_command.cpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/server.cpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/server_disconnect_command.cpp
@@ -110,11 +106,13 @@
     ${libirccd_SOURCE_DIR}/irccd/daemon/server_notice_command.cpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/server_part_command.cpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/server_reconnect_command.cpp
-    ${libirccd_SOURCE_DIR}/irccd/daemon/server_service.cpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/server_topic_command.cpp
+    ${libirccd_SOURCE_DIR}/irccd/daemon/service/plugin_service.cpp
+    ${libirccd_SOURCE_DIR}/irccd/daemon/service/rule_service.cpp
+    ${libirccd_SOURCE_DIR}/irccd/daemon/service/server_service.cpp
+    ${libirccd_SOURCE_DIR}/irccd/daemon/service/transport_service.cpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/transport_client.cpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/transport_server.cpp
-    ${libirccd_SOURCE_DIR}/irccd/daemon/transport_service.cpp
     $<$<BOOL:${HAVE_SSL}>:${libirccd_SOURCE_DIR}/irccd/daemon/tls_transport_server.cpp>
 )
 
--- a/libirccd/irccd/daemon/command_service.cpp	Mon Jan 15 13:37:32 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,49 +0,0 @@
-/*
- * command_service.cpp -- command service
- *
- * Copyright (c) 2013-2018 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_service.hpp"
-
-namespace irccd {
-
-bool command_service::contains(const std::string& name) const noexcept
-{
-    return find(name) != nullptr;
-}
-
-std::shared_ptr<command> command_service::find(const std::string& name) const noexcept
-{
-    auto it = std::find_if(commands_.begin(), commands_.end(), [&] (const auto& cmd) {
-        return cmd->get_name() == name;
-    });
-
-    return it == commands_.end() ? nullptr : *it;
-}
-
-void command_service::add(std::shared_ptr<command> command)
-{
-    auto it = std::find_if(commands_.begin(), commands_.end(), [&] (const auto& cmd) {
-        return cmd->get_name() == command->get_name();
-    });
-
-    if (it != commands_.end())
-        *it = std::move(command);
-    else
-        commands_.push_back(std::move(command));
-}
-
-} // !irccd
--- a/libirccd/irccd/daemon/command_service.hpp	Mon Jan 15 13:37:32 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,80 +0,0 @@
-/*
- * command_service.hpp -- command service
- *
- * Copyright (c) 2013-2018 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_DAEMON_COMMAND_SERVICE_HPP
-#define IRCCD_DAEMON_COMMAND_SERVICE_HPP
-
-/**
- * \file command_service.hpp
- * \brief Command service.
- */
-
-#include <memory>
-#include <vector>
-
-#include "command.hpp"
-
-namespace irccd {
-
-/**
- * \brief Store remote commands.
- * \ingroup services
- */
-class command_service {
-private:
-    std::vector<std::shared_ptr<command>> commands_;
-
-public:
-    /**
-     * Get all commands.
-     *
-     * \return the list of commands.
-     */
-    inline const std::vector<std::shared_ptr<command>>& commands() const noexcept
-    {
-        return commands_;
-    }
-
-    /**
-     * Tells if a command exists.
-     *
-     * \param name the command name
-     * \return true if the command exists
-     */
-    bool contains(const std::string& name) const noexcept;
-
-    /**
-     * Find a command by name.
-     *
-     * \param name the command name
-     * \return the command or empty one if not found
-     */
-    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
-     */
-    void add(std::shared_ptr<command> command);
-};
-
-} // !irccd
-
-#endif // !IRCCD_DAEMON_COMMAND_SERVICE_HPP
--- a/libirccd/irccd/daemon/irccd.cpp	Mon Jan 15 13:37:32 2018 +0100
+++ b/libirccd/irccd/daemon/irccd.cpp	Wed Mar 07 17:49:56 2018 +0100
@@ -19,13 +19,13 @@
 #include <irccd/string_util.hpp>
 #include <irccd/system.hpp>
 
-#include "command_service.hpp"
 #include "irccd.hpp"
 #include "logger.hpp"
-#include "plugin_service.hpp"
-#include "rule_service.hpp"
-#include "server_service.hpp"
-#include "transport_service.hpp"
+
+#include "service/plugin_service.hpp"
+#include "service/rule_service.hpp"
+#include "service/server_service.hpp"
+#include "service/transport_service.hpp"
 
 namespace irccd {
 
@@ -211,7 +211,6 @@
     : config_(std::move(config))
     , service_(service)
     , logger_(std::make_unique<console_logger>())
-    , command_service_(std::make_unique<command_service>())
     , server_service_(std::make_unique<server_service>(*this))
     , tpt_service_(std::make_unique<transport_service>(*this))
     , rule_service_(std::make_unique<rule_service>(*this))
--- a/libirccd/irccd/daemon/irccd.hpp	Mon Jan 15 13:37:32 2018 +0100
+++ b/libirccd/irccd/daemon/irccd.hpp	Wed Mar 07 17:49:56 2018 +0100
@@ -37,7 +37,6 @@
  */
 namespace irccd {
 
-class command_service;
 class logger;
 class plugin_service;
 class rule_service;
@@ -62,7 +61,6 @@
     std::unique_ptr<logger> logger_;
 
     // Services.
-    std::shared_ptr<command_service> command_service_;
     std::shared_ptr<server_service> server_service_;
     std::shared_ptr<transport_service> tpt_service_;
     std::shared_ptr<rule_service> rule_service_;
@@ -167,16 +165,6 @@
     void set_log(std::unique_ptr<logger> logger) noexcept;
 
     /**
-     * Access the command service.
-     *
-     * \return the service
-     */
-    inline command_service& commands() noexcept
-    {
-        return *command_service_;
-    }
-
-    /**
      * Access the server service.
      *
      * \return the service
--- a/libirccd/irccd/daemon/plugin_config_command.cpp	Mon Jan 15 13:37:32 2018 +0100
+++ b/libirccd/irccd/daemon/plugin_config_command.cpp	Wed Mar 07 17:49:56 2018 +0100
@@ -20,9 +20,10 @@
 
 #include "irccd.hpp"
 #include "plugin_config_command.hpp"
-#include "plugin_service.hpp"
 #include "transport_client.hpp"
 
+#include <irccd/daemon/service/plugin_service.hpp>
+
 namespace irccd {
 
 namespace {
--- a/libirccd/irccd/daemon/plugin_info_command.cpp	Mon Jan 15 13:37:32 2018 +0100
+++ b/libirccd/irccd/daemon/plugin_info_command.cpp	Wed Mar 07 17:49:56 2018 +0100
@@ -20,9 +20,10 @@
 
 #include "irccd.hpp"
 #include "plugin_info_command.hpp"
-#include "plugin_service.hpp"
 #include "transport_client.hpp"
 
+#include <irccd/daemon/service/plugin_service.hpp>
+
 namespace irccd {
 
 std::string plugin_info_command::get_name() const noexcept
--- a/libirccd/irccd/daemon/plugin_list_command.cpp	Mon Jan 15 13:37:32 2018 +0100
+++ b/libirccd/irccd/daemon/plugin_list_command.cpp	Wed Mar 07 17:49:56 2018 +0100
@@ -18,9 +18,10 @@
 
 #include "irccd.hpp"
 #include "plugin_list_command.hpp"
-#include "plugin_service.hpp"
 #include "transport_client.hpp"
 
+#include <irccd/daemon/service/plugin_service.hpp>
+
 namespace irccd {
 
 std::string plugin_list_command::get_name() const noexcept
--- a/libirccd/irccd/daemon/plugin_load_command.cpp	Mon Jan 15 13:37:32 2018 +0100
+++ b/libirccd/irccd/daemon/plugin_load_command.cpp	Wed Mar 07 17:49:56 2018 +0100
@@ -20,9 +20,10 @@
 
 #include "irccd.hpp"
 #include "plugin_load_command.hpp"
-#include "plugin_service.hpp"
 #include "transport_client.hpp"
 
+#include <irccd/daemon/service/plugin_service.hpp>
+
 namespace irccd {
 
 std::string plugin_load_command::get_name() const noexcept
--- a/libirccd/irccd/daemon/plugin_reload_command.cpp	Mon Jan 15 13:37:32 2018 +0100
+++ b/libirccd/irccd/daemon/plugin_reload_command.cpp	Wed Mar 07 17:49:56 2018 +0100
@@ -20,9 +20,10 @@
 
 #include "irccd.hpp"
 #include "plugin_reload_command.hpp"
-#include "plugin_service.hpp"
 #include "transport_client.hpp"
 
+#include <irccd/daemon/service/plugin_service.hpp>
+
 namespace irccd {
 
 std::string plugin_reload_command::get_name() const noexcept
--- a/libirccd/irccd/daemon/plugin_service.cpp	Mon Jan 15 13:37:32 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,231 +0,0 @@
-/*
- * plugin_service.cpp -- plugin service
- *
- * Copyright (c) 2013-2018 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 <irccd/config.hpp>
-#include <irccd/string_util.hpp>
-#include <irccd/system.hpp>
-
-#include "irccd.hpp"
-#include "logger.hpp"
-#include "plugin_service.hpp"
-
-namespace irccd {
-
-namespace {
-
-template <typename Map>
-Map to_map(const config& conf, const std::string& section)
-{
-    Map ret;
-
-    for (const auto& opt : conf.doc().get(section))
-        ret.emplace(opt.key(), opt.value());
-
-    return ret;
-}
-
-} // !namespace
-
-plugin_service::plugin_service(irccd& irccd) noexcept
-    : irccd_(irccd)
-{
-}
-
-plugin_service::~plugin_service()
-{
-    for (const auto& plugin : plugins_) {
-        try {
-            plugin->on_unload(irccd_);
-        } catch (const std::exception& ex) {
-            irccd_.log().warning() << "plugin: " << plugin->name() << ": " << ex.what() << std::endl;
-        }
-    }
-}
-
-bool plugin_service::has(const std::string& name) const noexcept
-{
-    return std::count_if(plugins_.cbegin(), plugins_.cend(), [&] (const auto& plugin) {
-        return plugin->name() == name;
-    }) > 0;
-}
-
-std::shared_ptr<plugin> plugin_service::get(const std::string& name) const noexcept
-{
-    auto it = std::find_if(plugins_.begin(), plugins_.end(), [&] (const auto& plugin) {
-        return plugin->name() == name;
-    });
-
-    if (it == plugins_.end())
-        return nullptr;
-
-    return *it;
-}
-
-std::shared_ptr<plugin> plugin_service::require(const std::string& name) const
-{
-    auto plugin = get(name);
-
-    if (!plugin)
-        throw plugin_error(plugin_error::not_found, name);
-
-    return plugin;
-}
-
-void plugin_service::add(std::shared_ptr<plugin> plugin)
-{
-    plugins_.push_back(std::move(plugin));
-}
-
-void plugin_service::add_loader(std::unique_ptr<plugin_loader> loader)
-{
-    loaders_.push_back(std::move(loader));
-}
-
-plugin_config plugin_service::config(const std::string& id)
-{
-    return to_map<plugin_config>(irccd_.config(), string_util::sprintf("plugin.%s", id));
-}
-
-plugin_formats plugin_service::formats(const std::string& id)
-{
-    return to_map<plugin_formats>(irccd_.config(), string_util::sprintf("format.%s", id));
-}
-
-plugin_paths plugin_service::paths(const std::string& id)
-{
-    auto defaults = to_map<plugin_paths>(irccd_.config(), "paths");
-    auto paths = to_map<plugin_paths>(irccd_.config(), string_util::sprintf("paths.%s", id));
-
-    // Fill defaults paths.
-    if (!defaults.count("cache"))
-        defaults.emplace("cache", sys::cachedir() + "/plugin/" + id);
-    if (!defaults.count("data"))
-        paths.emplace("data", sys::datadir() + "/plugin/" + id);
-    if (!defaults.count("config"))
-        paths.emplace("config", sys::sysconfigdir() + "/plugin/" + id);
-
-    // Now fill missing fields.
-    if (!paths.count("cache"))
-        paths.emplace("cache", defaults["cache"]);
-    if (!paths.count("data"))
-        paths.emplace("data", defaults["data"]);
-    if (!paths.count("config"))
-        paths.emplace("config", defaults["config"]);
-
-    return paths;
-}
-
-std::shared_ptr<plugin> plugin_service::open(const std::string& id,
-                                             const std::string& path)
-{
-    for (const auto& loader : loaders_) {
-        auto plugin = loader->open(id, path);
-
-        if (plugin)
-            return plugin;
-    }
-
-    return nullptr;
-}
-
-std::shared_ptr<plugin> plugin_service::find(const std::string& id)
-{
-    for (const auto& loader : loaders_) {
-        auto plugin = loader->find(id);
-
-        if (plugin)
-            return plugin;
-    }
-
-    return nullptr;
-}
-
-void plugin_service::load(std::string name, std::string path)
-{
-    if (has(name))
-        throw plugin_error(plugin_error::already_exists, name);
-
-    std::shared_ptr<plugin> plugin;
-
-    if (path.empty())
-        plugin = find(name);
-    else
-        plugin = open(name, std::move(path));
-
-    if (!plugin)
-        throw plugin_error(plugin_error::not_found, name);
-
-    plugin->set_config(config(name));
-    plugin->set_formats(formats(name));
-    plugin->set_paths(paths(name));
-
-    exec(plugin, &plugin::on_load, irccd_);
-    add(std::move(plugin));
-}
-
-void plugin_service::reload(const std::string& name)
-{
-    auto plugin = get(name);
-
-    if (!plugin)
-        throw plugin_error(plugin_error::not_found, name);
-
-    exec(plugin, &plugin::on_reload, irccd_);
-}
-
-void plugin_service::unload(const std::string& name)
-{
-    auto it = std::find_if(plugins_.begin(), plugins_.end(), [&] (const auto& plugin) {
-        return plugin->name() == name;
-    });
-
-    if (it == plugins_.end())
-        throw plugin_error(plugin_error::not_found, name);
-
-    // Erase first, in case of throwing.
-    auto save = *it;
-
-    plugins_.erase(it);
-    exec(save, &plugin::on_unload, irccd_);
-}
-
-void plugin_service::load(const class config& cfg) noexcept
-{
-    for (const auto& option : cfg.section("plugins")) {
-        if (!string_util::is_identifier(option.key()))
-            continue;
-
-        auto name = option.key();
-        auto p = get(name);
-
-        // Reload the plugin if already loaded.
-        if (p) {
-            p->set_config(config(name));
-            p->set_formats(formats(name));
-            p->set_paths(paths(name));
-        } else {
-            try {
-                load(name, option.value());
-            } catch (const std::exception& ex) {
-                irccd_.log().warning(ex.what());
-            }
-        }
-    }
-}
-
-} // !irccd
--- a/libirccd/irccd/daemon/plugin_service.hpp	Mon Jan 15 13:37:32 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,237 +0,0 @@
-/*
- * plugin_service.hpp -- plugin service
- *
- * Copyright (c) 2013-2018 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_DAEMON_PLUGIN_SERVICE_HPP
-#define IRCCD_DAEMON_PLUGIN_SERVICE_HPP
-
-/**
- * \file plugin_service.hpp
- * \brief Plugin service.
- */
-
-#include <cassert>
-#include <memory>
-#include <string>
-#include <vector>
-
-#include "plugin.hpp"
-
-namespace irccd {
-
-class irccd;
-class config;
-
-/**
- * \brief Manage plugins.
- * \ingroup services
- */
-class plugin_service {
-private:
-    irccd& irccd_;
-    std::vector<std::shared_ptr<plugin>> plugins_;
-    std::vector<std::unique_ptr<plugin_loader>> loaders_;
-
-public:
-    /**
-     * Create the plugin service.
-     *
-     * \param irccd the irccd instance
-     */
-    plugin_service(irccd& irccd) noexcept;
-
-    /**
-     * Destroy plugins.
-     */
-    ~plugin_service();
-
-    /**
-     * Get the list of plugins.
-     *
-     * \return the list of plugins
-     */
-    inline const std::vector<std::shared_ptr<plugin>>& list() const noexcept
-    {
-        return plugins_;
-    }
-
-    /**
-     * Check if a plugin is loaded.
-     *
-     * \param name the plugin id
-     * \return true if has plugin
-     */
-    bool has(const std::string& name) const noexcept;
-
-    /**
-     * Get a loaded plugin or null if not found.
-     *
-     * \param name the plugin id
-     * \return the plugin or empty one if not found
-     */
-    std::shared_ptr<plugin> get(const std::string& name) const noexcept;
-
-    /**
-     * Find a loaded plugin.
-     *
-     * \param name the plugin id
-     * \return the plugin
-     * \throws std::out_of_range if not found
-     */
-    std::shared_ptr<plugin> require(const std::string& name) const;
-
-    /**
-     * Add the specified plugin to the registry.
-     *
-     * \pre plugin != nullptr
-     * \param plugin the plugin
-     * \note the plugin is only added to the list, no action is performed on it
-     */
-    void add(std::shared_ptr<plugin> plugin);
-
-    /**
-     * Add a loader.
-     *
-     * \param loader the loader
-     */
-    void add_loader(std::unique_ptr<plugin_loader> loader);
-
-    /**
-     * Get the configuration for the specified plugin.
-     *
-     * \return the configuration
-     */
-    plugin_config config(const std::string& id);
-
-    /**
-     * Get the formats for the specified plugin.
-     *
-     * \return the formats
-     */
-    plugin_formats formats(const std::string& id);
-
-    /**
-     * Get the paths for the specified plugin.
-     *
-     * If none is defined, return the default ones.
-     *
-     * \return the paths
-     */
-    plugin_paths paths(const std::string& id);
-
-    /**
-     * Generic function for opening the plugin at the given path.
-     *
-     * This function will search for every pluginLoader and call open() on it,
-     * the first one that success will be returned.
-     *
-     * \param id the plugin id
-     * \param path the path to the file
-     * \return the plugin or nullptr on failures
-     */
-    std::shared_ptr<plugin> open(const std::string& id,
-                                 const std::string& path);
-
-    /**
-     * Generic function for finding a plugin.
-     *
-     * \param id the plugin id
-     * \return the plugin or nullptr on failures
-     */
-    std::shared_ptr<plugin> find(const std::string& id);
-
-    /**
-     * Convenient wrapper that loads a plugin, call onLoad and add it to the
-     * registry.
-     *
-     * Any errors are printed using logger.
-     *
-     * \param name the name
-     * \param path the optional path (searched if empty)
-     */
-    void load(std::string name, std::string path = "");
-
-    /**
-     * Unload a plugin and remove it.
-     *
-     * \param name the plugin id
-     */
-    void unload(const std::string& name);
-
-    /**
-     * Reload a plugin by calling onReload.
-     *
-     * \param name the plugin name
-     * \throw std::exception on failures
-     */
-    void reload(const std::string& name);
-
-    /**
-     * Call a plugin function and throw an exception with the following errors:
-     *
-     *   - plugin_error::not_found if not loaded
-     *   - plugin_error::exec_error if function failed
-     *
-     * \pre plugin != nullptr
-     * \param plugin the plugin
-     * \param fn the plugin member function (pointer to member)
-     * \param args the arguments to pass
-     */
-    template <typename Func, typename... Args>
-    void exec(std::shared_ptr<plugin> plugin, Func fn, Args&&... args)
-    {
-        assert(plugin);
-
-        // TODO: replace with C++17 std::invoke.
-        try {
-            ((*plugin).*(fn))(std::forward<Args>(args)...);
-        } catch (const std::exception& ex) {
-            throw plugin_error(plugin_error::exec_error, plugin->name(), ex.what());
-        } catch (...) {
-            throw plugin_error(plugin_error::exec_error, plugin->name());
-        }
-    }
-
-    /**
-     * Overloaded function.
-     *
-     * \param name the plugin name
-     * \param fn the plugin member function (pointer to member)
-     * \param args the arguments to pass
-     */
-    template <typename Func, typename... Args>
-    void exec(const std::string& name, Func fn, Args&&... args)
-    {
-        auto plugin = find(name);
-
-        if (!plugin)
-            throw plugin_error(plugin_error::not_found, plugin->name());
-
-        exec(plugin, fn, std::forward<Args>(args)...);
-    }
-
-    /**
-     * Load all plugins.
-     *
-     * \param cfg the config
-     */
-    void load(const class config& cfg) noexcept;
-};
-
-} // !irccd
-
-#endif // !IRCCD_DAEMON_PLUGIN_SERVICE_HPP
--- a/libirccd/irccd/daemon/plugin_unload_command.cpp	Mon Jan 15 13:37:32 2018 +0100
+++ b/libirccd/irccd/daemon/plugin_unload_command.cpp	Wed Mar 07 17:49:56 2018 +0100
@@ -19,10 +19,11 @@
 #include <irccd/json_util.hpp>
 
 #include "irccd.hpp"
-#include "plugin_service.hpp"
 #include "plugin_unload_command.hpp"
 #include "transport_client.hpp"
 
+#include <irccd/daemon/service/plugin_service.hpp>
+
 namespace irccd {
 
 std::string plugin_unload_command::get_name() const noexcept
--- a/libirccd/irccd/daemon/rule_add_command.cpp	Mon Jan 15 13:37:32 2018 +0100
+++ b/libirccd/irccd/daemon/rule_add_command.cpp	Wed Mar 07 17:49:56 2018 +0100
@@ -20,9 +20,10 @@
 
 #include "irccd.hpp"
 #include "rule_add_command.hpp"
-#include "rule_service.hpp"
 #include "transport_client.hpp"
 
+#include <irccd/daemon/service/rule_service.hpp>
+
 namespace irccd {
 
 std::string rule_add_command::get_name() const noexcept
--- a/libirccd/irccd/daemon/rule_edit_command.cpp	Mon Jan 15 13:37:32 2018 +0100
+++ b/libirccd/irccd/daemon/rule_edit_command.cpp	Wed Mar 07 17:49:56 2018 +0100
@@ -18,9 +18,10 @@
 
 #include "irccd.hpp"
 #include "rule_edit_command.hpp"
-#include "rule_service.hpp"
 #include "transport_client.hpp"
 
+#include <irccd/daemon/service/rule_service.hpp>
+
 using namespace std::string_literals;
 
 namespace irccd {
--- a/libirccd/irccd/daemon/rule_info_command.cpp	Mon Jan 15 13:37:32 2018 +0100
+++ b/libirccd/irccd/daemon/rule_info_command.cpp	Wed Mar 07 17:49:56 2018 +0100
@@ -18,9 +18,10 @@
 
 #include "irccd.hpp"
 #include "rule_info_command.hpp"
-#include "rule_service.hpp"
 #include "transport_client.hpp"
 
+#include <irccd/daemon/service/rule_service.hpp>
+
 namespace irccd {
 
 std::string rule_info_command::get_name() const noexcept
--- a/libirccd/irccd/daemon/rule_list_command.cpp	Mon Jan 15 13:37:32 2018 +0100
+++ b/libirccd/irccd/daemon/rule_list_command.cpp	Wed Mar 07 17:49:56 2018 +0100
@@ -18,9 +18,10 @@
 
 #include "irccd.hpp"
 #include "rule_list_command.hpp"
-#include "rule_service.hpp"
 #include "transport_client.hpp"
 
+#include <irccd/daemon/service/rule_service.hpp>
+
 namespace irccd {
 
 std::string rule_list_command::get_name() const noexcept
--- a/libirccd/irccd/daemon/rule_move_command.cpp	Mon Jan 15 13:37:32 2018 +0100
+++ b/libirccd/irccd/daemon/rule_move_command.cpp	Wed Mar 07 17:49:56 2018 +0100
@@ -18,9 +18,10 @@
 
 #include "irccd.hpp"
 #include "rule_move_command.hpp"
-#include "rule_service.hpp"
 #include "transport_client.hpp"
 
+#include <irccd/daemon/service/rule_service.hpp>
+
 namespace irccd {
 
 std::string rule_move_command::get_name() const noexcept
--- a/libirccd/irccd/daemon/rule_remove_command.cpp	Mon Jan 15 13:37:32 2018 +0100
+++ b/libirccd/irccd/daemon/rule_remove_command.cpp	Wed Mar 07 17:49:56 2018 +0100
@@ -18,9 +18,10 @@
 
 #include "irccd.hpp"
 #include "rule_remove_command.hpp"
-#include "rule_service.hpp"
 #include "transport_client.hpp"
 
+#include <irccd/daemon/service/rule_service.hpp>
+
 namespace irccd {
 
 std::string rule_remove_command::get_name() const noexcept
--- a/libirccd/irccd/daemon/rule_service.cpp	Mon Jan 15 13:37:32 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,240 +0,0 @@
-/*
- * rule_service.cpp -- rule service
- *
- * Copyright (c) 2013-2018 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 <stdexcept>
-
-#include <irccd/config.hpp>
-#include <irccd/string_util.hpp>
-
-#include "irccd.hpp"
-#include "logger.hpp"
-#include "rule_service.hpp"
-
-namespace irccd {
-
-namespace {
-
-rule load_rule(const ini::section& sc)
-{
-    assert(sc.key() == "rule");
-
-    // Simple converter from std::vector to std::unordered_set.
-    auto toset = [] (const auto& v) {
-        return std::unordered_set<std::string>(v.begin(), v.end());
-    };
-
-    rule::set servers, channels, origins, plugins, events;
-    rule::action action = rule::action::accept;
-
-    // Get the sets.
-    ini::section::const_iterator it;
-
-    if ((it = sc.find("servers")) != sc.end())
-        servers = toset(*it);
-    if ((it = sc.find("channels")) != sc.end())
-        channels = toset(*it);
-    if ((it = sc.find("origins")) != sc.end())
-        origins = toset(*it);
-    if ((it = sc.find("plugins")) != sc.end())
-        plugins = toset(*it);
-    if ((it = sc.find("channels")) != sc.end())
-        channels = toset(*it);
-
-    // Get the action.
-    auto actionstr = sc.get("action").value();
-
-    if (actionstr == "drop")
-        action = rule::action::drop;
-    else if (actionstr == "accept")
-        action = rule::action::accept;
-    else
-        throw rule_error(rule_error::invalid_action);
-
-    return {
-        std::move(servers),
-        std::move(channels),
-        std::move(origins),
-        std::move(plugins),
-        std::move(events),
-        action
-    };
-}
-
-} // !namespace
-
-rule rule_service::from_json(const nlohmann::json& json)
-{
-    auto toset = [] (auto object, auto name) {
-        rule::set result;
-
-        for (const auto& s : object[name])
-            if (s.is_string())
-                result.insert(s.template get<std::string>());
-
-        return result;
-    };
-    auto toaction = [] (auto object, auto name) {
-        auto v = object[name];
-
-        if (!v.is_string())
-            throw rule_error(rule_error::invalid_action);
-
-        auto s = v.template get<std::string>();
-        if (s == "accept")
-            return rule::action::accept;
-        if (s == "drop")
-            return rule::action::drop;
-
-        throw rule_error(rule_error::invalid_action);
-    };
-
-    return {
-        toset(json, "servers"),
-        toset(json, "channels"),
-        toset(json, "origins"),
-        toset(json, "plugins"),
-        toset(json, "events"),
-        toaction(json, "action")
-    };
-}
-
-unsigned rule_service::get_index(const nlohmann::json& json, const std::string& key)
-{
-    auto index = json.find(key);
-
-    if (index == json.end() || !index->is_number_integer() || index->get<int>() < 0)
-        throw rule_error(rule_error::invalid_index);
-
-    return index->get<int>();
-}
-
-nlohmann::json rule_service::to_json(const rule& rule)
-{
-    auto join = [] (const auto& set) {
-        auto array = nlohmann::json::array();
-
-        for (const auto& entry : set)
-            array.push_back(entry);
-
-        return array;
-    };
-    auto str = [] (auto action) {
-        switch (action) {
-        case rule::action::accept:
-            return "accept";
-        default:
-            return "drop";
-        }
-    };
-
-    return {
-        { "servers",    join(rule.get_servers())    },
-        { "channels",   join(rule.get_channels())   },
-        { "plugins",    join(rule.get_plugins())    },
-        { "events",     join(rule.get_events())     },
-        { "action",     str(rule.get_action())      }
-    };
-}
-
-rule_service::rule_service(irccd &irccd)
-    : irccd_(irccd)
-{
-}
-
-void rule_service::add(rule rule)
-{
-    rules_.push_back(std::move(rule));
-}
-
-void rule_service::insert(rule rule, unsigned position)
-{
-    assert(position <= rules_.size());
-
-    rules_.insert(rules_.begin() + position, std::move(rule));
-}
-
-void rule_service::remove(unsigned position)
-{
-    assert(position < rules_.size());
-
-    rules_.erase(rules_.begin() + position);
-}
-
-const rule &rule_service::require(unsigned position) const
-{
-    if (position >= rules_.size())
-        throw rule_error(rule_error::invalid_index);
-
-    return rules_[position];
-}
-
-rule &rule_service::require(unsigned position)
-{
-    if (position >= rules_.size())
-        throw rule_error(rule_error::invalid_index);
-
-    return rules_[position];
-}
-
-bool rule_service::solve(const std::string& server,
-                         const std::string& channel,
-                         const std::string& origin,
-                         const std::string& plugin,
-                         const std::string& event) noexcept
-{
-    bool result = true;
-
-    irccd_.log().debug(string_util::sprintf("rule: solving for server=%s, channel=%s, origin=%s, plugin=%s, event=%s",
-        server, channel, origin, plugin, event));
-
-    int i = 0;
-    for (const auto& rule : rules_) {
-        auto action = rule.get_action() == rule::action::accept ? "accept" : "drop";
-
-        irccd_.log().debug() << "  candidate "   << i++ << ":\n"
-            << "    servers: "  << string_util::join(rule.get_servers()) << "\n"
-            << "    channels: " << string_util::join(rule.get_channels()) << "\n"
-            << "    origins: "  << string_util::join(rule.get_origins()) << "\n"
-            << "    plugins: "  << string_util::join(rule.get_plugins()) << "\n"
-            << "    events: "   << string_util::join(rule.get_events()) << "\n"
-            << "    action: "   << action << std::endl;
-
-        if (rule.match(server, channel, origin, plugin, event))
-            result = rule.get_action() == rule::action::accept;
-    }
-
-    return result;
-}
-
-void rule_service::load(const config& cfg) noexcept
-{
-    rules_.clear();
-
-    for (const auto& section : cfg.doc()) {
-        if (section.key() != "rule")
-            continue;
-
-        try {
-            rules_.push_back(load_rule(section));
-        } catch (const std::exception& ex) {
-            irccd_.log().warning() << "rule: " << ex.what() << std::endl;
-        }
-    }
-}
-
-} // !irccd
--- a/libirccd/irccd/daemon/rule_service.hpp	Mon Jan 15 13:37:32 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,169 +0,0 @@
-/*
- * rule_service.hpp -- rule service
- *
- * Copyright (c) 2013-2018 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_DAEMON_RULE_SERVICE_HPP
-#define IRCCD_DAEMON_RULE_SERVICE_HPP
-
-/**
- * \file rule_service.hpp
- * \brief Rule service.
- */
-
-#include <vector>
-
-#include <json.hpp>
-
-#include "rule.hpp"
-
-namespace irccd {
-
-class config;
-class irccd;
-
-/**
- * \brief Store and solve rules.
- * \ingroup services
- */
-class rule_service {
-private:
-    irccd& irccd_;
-    std::vector<rule> rules_;
-
-public:
-    /**
-     * Load a rule from a JSON object.
-     *
-     * For possible use in transport commands or Javascript API.
-     *
-     * \pre json.is_object()
-     * \param json the JSON object
-     * \return the new rule
-     * \throw rule_error on errors
-     */
-    static rule from_json(const nlohmann::json& json);
-
-    /**
-     * Helper to get rule index in a JSON object.
-     *
-     * \pre json.is_object()
-     * \param json the JSON object
-     * \param key the index property
-     * \return the index
-     * \throw rule_error on errors
-     */
-    static unsigned get_index(const nlohmann::json& json, const std::string& key = "index");
-
-    /**
-     * Convert a rule into a JSON object.
-     *
-     * \param rule the rule
-     * \throw the JSON representation
-     */
-    static nlohmann::json to_json(const rule& rule);
-
-    /**
-     * Create the rule service.
-     */
-    rule_service(irccd& instance);
-
-    /**
-     * Get the list of rules.
-     *
-     * \return the list of rules
-     */
-    inline const std::vector<rule>& list() const noexcept
-    {
-        return rules_;
-    }
-
-    /**
-     * Get the number of rules.
-     *
-     * \return the number of rules
-     */
-    inline std::size_t length() const noexcept
-    {
-        return rules_.size();
-    }
-
-    /**
-     * Append a rule.
-     *
-     * \param rule the rule to append
-     */
-    void add(rule rule);
-
-    /**
-     * Insert a new rule at the specified position.
-     *
-     * \param rule the rule
-     * \param position the position
-     */
-    void insert(rule rule, unsigned position);
-
-    /**
-     * Remove a new rule from the specified position.
-     *
-     * \pre position must be valid
-     * \param position the position
-     */
-    void remove(unsigned position);
-
-    /**
-     * Get a rule at the specified index or throw an exception if not found.
-     *
-     * \param position the position
-     * \return the rule
-     * \throw std::out_of_range if position is invalid
-     */
-    const rule& require(unsigned position) const;
-
-    /**
-     * Overloaded function.
-     *
-     * \copydoc require
-     */
-    rule& require(unsigned position);
-
-    /**
-     * Resolve the action to execute with the specified list of rules.
-     *
-     * \param server the server name
-     * \param channel the channel name
-     * \param origin the origin
-     * \param plugin the plugin name
-     * \param event the event name (e.g onKick)
-     * \return true if the plugin must be called
-     */
-    bool solve(const std::string& server,
-               const std::string& channel,
-               const std::string& origin,
-               const std::string& plugin,
-               const std::string& event) noexcept;
-
-    /**
-     * Load rules from the configuration.
-     *
-     * \param cfg the config
-     */
-    void load(const config& cfg) noexcept;
-};
-
-} // !irccd
-
-#endif // !IRCCD_DAEMON_RULE_SERVICE_HPP
--- a/libirccd/irccd/daemon/server_connect_command.cpp	Mon Jan 15 13:37:32 2018 +0100
+++ b/libirccd/irccd/daemon/server_connect_command.cpp	Wed Mar 07 17:49:56 2018 +0100
@@ -18,9 +18,10 @@
 
 #include "irccd.hpp"
 #include "server_connect_command.hpp"
-#include "server_service.hpp"
 #include "transport_client.hpp"
 
+#include <irccd/daemon/service/server_service.hpp>
+
 namespace irccd {
 
 std::string server_connect_command::get_name() const noexcept
--- a/libirccd/irccd/daemon/server_disconnect_command.cpp	Mon Jan 15 13:37:32 2018 +0100
+++ b/libirccd/irccd/daemon/server_disconnect_command.cpp	Wed Mar 07 17:49:56 2018 +0100
@@ -18,9 +18,10 @@
 
 #include "irccd.hpp"
 #include "server_disconnect_command.hpp"
-#include "server_service.hpp"
 #include "transport_client.hpp"
 
+#include <irccd/daemon/service/server_service.hpp>
+
 namespace irccd {
 
 std::string server_disconnect_command::get_name() const noexcept
--- a/libirccd/irccd/daemon/server_info_command.cpp	Mon Jan 15 13:37:32 2018 +0100
+++ b/libirccd/irccd/daemon/server_info_command.cpp	Wed Mar 07 17:49:56 2018 +0100
@@ -18,9 +18,10 @@
 
 #include "irccd.hpp"
 #include "server_info_command.hpp"
-#include "server_service.hpp"
 #include "transport_client.hpp"
 
+#include <irccd/daemon/service/server_service.hpp>
+
 namespace irccd {
 
 std::string server_info_command::get_name() const noexcept
--- a/libirccd/irccd/daemon/server_invite_command.cpp	Mon Jan 15 13:37:32 2018 +0100
+++ b/libirccd/irccd/daemon/server_invite_command.cpp	Wed Mar 07 17:49:56 2018 +0100
@@ -20,9 +20,10 @@
 
 #include "irccd.hpp"
 #include "server_invite_command.hpp"
-#include "server_service.hpp"
 #include "transport_client.hpp"
 
+#include <irccd/daemon/service/server_service.hpp>
+
 namespace irccd {
 
 std::string server_invite_command::get_name() const noexcept
--- a/libirccd/irccd/daemon/server_join_command.cpp	Mon Jan 15 13:37:32 2018 +0100
+++ b/libirccd/irccd/daemon/server_join_command.cpp	Wed Mar 07 17:49:56 2018 +0100
@@ -20,9 +20,10 @@
 
 #include "irccd.hpp"
 #include "server_join_command.hpp"
-#include "server_service.hpp"
 #include "transport_client.hpp"
 
+#include <irccd/daemon/service/server_service.hpp>
+
 namespace irccd {
 
 std::string server_join_command::get_name() const noexcept
--- a/libirccd/irccd/daemon/server_kick_command.cpp	Mon Jan 15 13:37:32 2018 +0100
+++ b/libirccd/irccd/daemon/server_kick_command.cpp	Wed Mar 07 17:49:56 2018 +0100
@@ -20,9 +20,10 @@
 
 #include "irccd.hpp"
 #include "server_kick_command.hpp"
-#include "server_service.hpp"
 #include "transport_client.hpp"
 
+#include <irccd/daemon/service/server_service.hpp>
+
 namespace irccd {
 
 std::string server_kick_command::get_name() const noexcept
--- a/libirccd/irccd/daemon/server_list_command.cpp	Mon Jan 15 13:37:32 2018 +0100
+++ b/libirccd/irccd/daemon/server_list_command.cpp	Wed Mar 07 17:49:56 2018 +0100
@@ -18,9 +18,10 @@
 
 #include "irccd.hpp"
 #include "server_list_command.hpp"
-#include "server_service.hpp"
 #include "transport_client.hpp"
 
+#include <irccd/daemon/service/server_service.hpp>
+
 namespace irccd {
 
 std::string server_list_command::get_name() const noexcept
--- a/libirccd/irccd/daemon/server_me_command.cpp	Mon Jan 15 13:37:32 2018 +0100
+++ b/libirccd/irccd/daemon/server_me_command.cpp	Wed Mar 07 17:49:56 2018 +0100
@@ -20,9 +20,10 @@
 
 #include "irccd.hpp"
 #include "server_me_command.hpp"
-#include "server_service.hpp"
 #include "transport_client.hpp"
 
+#include <irccd/daemon/service/server_service.hpp>
+
 namespace irccd {
 
 std::string server_me_command::get_name() const noexcept
--- a/libirccd/irccd/daemon/server_message_command.cpp	Mon Jan 15 13:37:32 2018 +0100
+++ b/libirccd/irccd/daemon/server_message_command.cpp	Wed Mar 07 17:49:56 2018 +0100
@@ -20,9 +20,10 @@
 
 #include "irccd.hpp"
 #include "server_message_command.hpp"
-#include "server_service.hpp"
 #include "transport_client.hpp"
 
+#include <irccd/daemon/service/server_service.hpp>
+
 namespace irccd {
 
 std::string server_message_command::get_name() const noexcept
--- a/libirccd/irccd/daemon/server_mode_command.cpp	Mon Jan 15 13:37:32 2018 +0100
+++ b/libirccd/irccd/daemon/server_mode_command.cpp	Wed Mar 07 17:49:56 2018 +0100
@@ -20,9 +20,10 @@
 
 #include "irccd.hpp"
 #include "server_mode_command.hpp"
-#include "server_service.hpp"
 #include "transport_client.hpp"
 
+#include <irccd/daemon/service/server_service.hpp>
+
 namespace irccd {
 
 std::string server_mode_command::get_name() const noexcept
--- a/libirccd/irccd/daemon/server_nick_command.cpp	Mon Jan 15 13:37:32 2018 +0100
+++ b/libirccd/irccd/daemon/server_nick_command.cpp	Wed Mar 07 17:49:56 2018 +0100
@@ -20,9 +20,10 @@
 
 #include "irccd.hpp"
 #include "server_nick_command.hpp"
-#include "server_service.hpp"
 #include "transport_client.hpp"
 
+#include <irccd/daemon/service/server_service.hpp>
+
 namespace irccd {
 
 std::string server_nick_command::get_name() const noexcept
--- a/libirccd/irccd/daemon/server_notice_command.cpp	Mon Jan 15 13:37:32 2018 +0100
+++ b/libirccd/irccd/daemon/server_notice_command.cpp	Wed Mar 07 17:49:56 2018 +0100
@@ -20,9 +20,10 @@
 
 #include "irccd.hpp"
 #include "server_notice_command.hpp"
-#include "server_service.hpp"
 #include "transport_client.hpp"
 
+#include <irccd/daemon/service/server_service.hpp>
+
 namespace irccd {
 
 std::string server_notice_command::get_name() const noexcept
--- a/libirccd/irccd/daemon/server_part_command.cpp	Mon Jan 15 13:37:32 2018 +0100
+++ b/libirccd/irccd/daemon/server_part_command.cpp	Wed Mar 07 17:49:56 2018 +0100
@@ -20,9 +20,10 @@
 
 #include "irccd.hpp"
 #include "server_part_command.hpp"
-#include "server_service.hpp"
 #include "transport_client.hpp"
 
+#include <irccd/daemon/service/server_service.hpp>
+
 namespace irccd {
 
 std::string server_part_command::get_name() const noexcept
--- a/libirccd/irccd/daemon/server_reconnect_command.cpp	Mon Jan 15 13:37:32 2018 +0100
+++ b/libirccd/irccd/daemon/server_reconnect_command.cpp	Wed Mar 07 17:49:56 2018 +0100
@@ -20,9 +20,10 @@
 
 #include "irccd.hpp"
 #include "server_reconnect_command.hpp"
-#include "server_service.hpp"
 #include "transport_client.hpp"
 
+#include <irccd/daemon/service/server_service.hpp>
+
 namespace irccd {
 
 std::string server_reconnect_command::get_name() const noexcept
--- a/libirccd/irccd/daemon/server_service.cpp	Mon Jan 15 13:37:32 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,727 +0,0 @@
-/*
- * server_service.hpp -- server service
- *
- * Copyright (c) 2013-2018 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 <irccd/json_util.hpp>
-#include <irccd/string_util.hpp>
-
-#include "irccd.hpp"
-#include "logger.hpp"
-#include "plugin_service.hpp"
-#include "rule_service.hpp"
-#include "server_service.hpp"
-#include "transport_service.hpp"
-
-namespace irccd {
-
-namespace {
-
-template <typename EventNameFunc, typename ExecFunc>
-void dispatch(irccd& daemon,
-              const std::string& server,
-              const std::string& origin,
-              const std::string& target,
-              EventNameFunc&& name_func,
-              ExecFunc exec_func)
-{
-    for (auto& plugin : daemon.plugins().list()) {
-        auto eventname = name_func(*plugin);
-        auto allowed = daemon.rules().solve(server, target, origin, plugin->name(), eventname);
-
-        if (!allowed) {
-            daemon.log().debug("rule: event skipped on match");
-            continue;
-        }
-
-        daemon.log().debug("rule: event allowed");
-
-        try {
-            exec_func(*plugin);
-        } catch (const std::exception& ex) {
-            daemon.log().warning() << "plugin " << plugin->name() << ": error: "
-                << ex.what() << std::endl;
-        }
-    }
-}
-
-template <typename T>
-T to_int(const std::string& value, const std::string& name, server_error::error errc)
-{
-    try {
-        return string_util::to_int<T>(value);
-    } catch (...) {
-        throw server_error(errc, name);
-    }
-}
-
-template <typename T>
-T to_uint(const std::string& value, const std::string& name, server_error::error errc)
-{
-    try {
-        return string_util::to_uint<T>(value);
-    } catch (...) {
-        throw server_error(errc, name);
-    }
-}
-
-template <typename T>
-T to_uint(const nlohmann::json& value, const std::string& name, server_error::error errc)
-{
-    if (!value.is_number())
-        throw server_error(errc, name);
-
-    auto n = value.get<unsigned>();
-
-    if (n > std::numeric_limits<T>::max())
-        throw server_error(errc, name);
-
-    return static_cast<T>(n);
-}
-
-std::string to_id(const ini::section& sc)
-{
-    auto id = sc.get("name");
-
-    if (!string_util::is_identifier(id.value()))
-        throw server_error(server_error::invalid_identifier, "");
-
-    return id.value();
-}
-
-std::string to_id(const nlohmann::json& object)
-{
-    auto id = json_util::get_string(object, "name");
-
-    if (!string_util::is_identifier(id))
-        throw server_error(server_error::invalid_identifier, "");
-
-    return id;
-}
-
-std::string to_host(const ini::section& sc, const std::string& name)
-{
-    auto value = sc.get("host");
-
-    if (value.empty())
-        throw server_error(server_error::invalid_hostname, name);
-
-    return value.value();
-}
-
-std::string to_host(const nlohmann::json& object, const std::string& name)
-{
-    auto value = json_util::get_string(object, "host");
-
-    if (value.empty())
-        throw server_error(server_error::invalid_hostname, name);
-
-    return value;
-}
-
-void load_server_identity(std::shared_ptr<server>& server,
-                          const config& cfg,
-                          const std::string& identity)
-{
-    auto sc = std::find_if(cfg.doc().begin(), cfg.doc().end(), [&] (const auto& sc) {
-        if (sc.key() != "identity")
-            return false;
-
-        auto name = sc.find("name");
-
-        return name != sc.end() && name->value() == identity;
-    });
-
-    if (sc == cfg.doc().end())
-        return;
-
-    ini::section::const_iterator it;
-
-    if ((it = sc->find("username")) != sc->end())
-        server->set_username(it->value());
-    if ((it = sc->find("realname")) != sc->end())
-        server->set_realname(it->value());
-    if ((it = sc->find("nickname")) != sc->end())
-        server->set_nickname(it->value());
-    if ((it = sc->find("ctcp-version")) != sc->end())
-        server->set_ctcp_version(it->value());
-}
-
-std::shared_ptr<server> load_server(boost::asio::io_service& service,
-                                    const config& cfg,
-                                    const ini::section& sc)
-{
-    assert(sc.key() == "server");
-
-    auto sv = std::make_shared<server>(service, to_id(sc));
-
-    // Mandatory fields.
-    sv->set_host(to_host(sc, sv->name()));
-
-    // Optional fields.
-    ini::section::const_iterator it;
-
-    if ((it = sc.find("password")) != sc.end())
-        sv->set_password(it->value());
-
-    // Optional flags
-    if ((it = sc.find("ipv6")) != sc.end() && string_util::is_boolean(it->value()))
-        sv->set_flags(sv->flags() | server::ipv6);
-
-    if ((it = sc.find("ssl")) != sc.end() && string_util::is_boolean(it->value())) {
-#if defined(HAVE_SSL)
-        sv->set_flags(sv->flags() | server::ssl);
-#else
-        throw server_error(server_error::ssl_disabled, sv->name());
-#endif
-    }
-
-    if ((it = sc.find("ssl-verify")) != sc.end() && string_util::is_boolean(it->value()))
-        sv->set_flags(sv->flags() | server::ssl_verify);
-
-    // Optional identity
-    if ((it = sc.find("identity")) != sc.end())
-        load_server_identity(sv, cfg, it->value());
-
-    // Options
-    if ((it = sc.find("auto-rejoin")) != sc.end() && string_util::is_boolean(it->value()))
-        sv->set_flags(sv->flags() | server::auto_rejoin);
-    if ((it = sc.find("join-invite")) != sc.end() && string_util::is_boolean(it->value()))
-        sv->set_flags(sv->flags() | server::join_invite);
-
-    // Channels
-    if ((it = sc.find("channels")) != sc.end()) {
-        for (const auto& s : *it) {
-            channel channel;
-
-            if (auto pos = s.find(":") != std::string::npos) {
-                channel.name = s.substr(0, pos);
-                channel.password = s.substr(pos + 1);
-            } else
-                channel.name = s;
-
-            sv->join(channel.name, channel.password);
-        }
-    }
-    if ((it = sc.find("command-char")) != sc.end())
-        sv->set_command_char(it->value());
-
-    // Reconnect and ping timeout
-    if ((it = sc.find("port")) != sc.end())
-        sv->set_port(to_uint<std::uint16_t>(it->value(),
-            sv->name(), server_error::invalid_port));
-
-    if ((it = sc.find("reconnect-tries")) != sc.end())
-        sv->set_reconnect_tries(to_int<std::int8_t>(it->value(),
-            sv->name(), server_error::invalid_reconnect_tries));
-
-    if ((it = sc.find("reconnect-timeout")) != sc.end())
-        sv->set_reconnect_delay(to_uint<std::uint16_t>(it->value(),
-            sv->name(), server_error::invalid_reconnect_timeout));
-
-    if ((it = sc.find("ping-timeout")) != sc.end())
-        sv->set_ping_timeout(to_uint<std::uint16_t>(it->value(),
-            sv->name(), server_error::invalid_ping_timeout));
-
-    return sv;
-}
-
-} // !namespace
-
-void server_service::handle_connect(const connect_event& ev)
-{
-    irccd_.log().debug() << "server " << ev.server->name() << ": event onConnect" << std::endl;
-
-    irccd_.transports().broadcast(nlohmann::json::object({
-        { "event",      "onConnect"         },
-        { "server",     ev.server->name()   }
-    }));
-
-    dispatch(irccd_, ev.server->name(), /* origin */ "", /* channel */ "",
-        [=] (plugin&) -> std::string {
-            return "onConnect";
-        },
-        [=] (plugin& plugin) {
-            plugin.on_connect(irccd_, ev);
-        }
-    );
-}
-
-void server_service::handle_invite(const invite_event& ev)
-{
-    irccd_.log().debug() << "server " << ev.server->name() << ": event onInvite:\n";
-    irccd_.log().debug() << "  origin: " << ev.origin << "\n";
-    irccd_.log().debug() << "  channel: " << ev.channel << "\n";
-    irccd_.log().debug() << "  target: " << ev.nickname << std::endl;
-
-    irccd_.transports().broadcast(nlohmann::json::object({
-        { "event",      "onInvite"          },
-        { "server",     ev.server->name()   },
-        { "origin",     ev.origin           },
-        { "channel",    ev.channel          }
-    }));
-
-    dispatch(irccd_, ev.server->name(), ev.origin, ev.channel,
-        [=] (plugin&) -> std::string {
-            return "onInvite";
-        },
-        [=] (plugin& plugin) {
-            plugin.on_invite(irccd_, ev);
-        }
-    );
-}
-
-void server_service::handle_join(const join_event& ev)
-{
-    irccd_.log().debug() << "server " << ev.server->name() << ": event onJoin:\n";
-    irccd_.log().debug() << "  origin: " << ev.origin << "\n";
-    irccd_.log().debug() << "  channel: " << ev.channel << std::endl;
-
-    irccd_.transports().broadcast(nlohmann::json::object({
-        { "event",      "onJoin"            },
-        { "server",     ev.server->name()   },
-        { "origin",     ev.origin           },
-        { "channel",    ev.channel          }
-    }));
-
-    dispatch(irccd_, ev.server->name(), ev.origin, ev.channel,
-        [=] (plugin&) -> std::string {
-            return "onJoin";
-        },
-        [=] (plugin& plugin) {
-            plugin.on_join(irccd_, ev);
-        }
-    );
-}
-
-void server_service::handle_kick(const kick_event& ev)
-{
-    irccd_.log().debug() << "server " << ev.server->name() << ": event onKick:\n";
-    irccd_.log().debug() << "  origin: " << ev.origin << "\n";
-    irccd_.log().debug() << "  channel: " << ev.channel << "\n";
-    irccd_.log().debug() << "  target: " << ev.target << "\n";
-    irccd_.log().debug() << "  reason: " << ev.reason << std::endl;
-
-    irccd_.transports().broadcast(nlohmann::json::object({
-        { "event",      "onKick"            },
-        { "server",     ev.server->name()   },
-        { "origin",     ev.origin           },
-        { "channel",    ev.channel          },
-        { "target",     ev.target           },
-        { "reason",     ev.reason           }
-    }));
-
-    dispatch(irccd_, ev.server->name(), ev.origin, ev.channel,
-        [=] (plugin&) -> std::string {
-            return "onKick";
-        },
-        [=] (plugin& plugin) {
-            plugin.on_kick(irccd_, ev);
-        }
-    );
-}
-
-void server_service::handle_message(const message_event& ev)
-{
-    irccd_.log().debug() << "server " << ev.server->name() << ": event onMessage:\n";
-    irccd_.log().debug() << "  origin: " << ev.origin << "\n";
-    irccd_.log().debug() << "  channel: " << ev.channel << "\n";
-    irccd_.log().debug() << "  message: " << ev.message << std::endl;
-
-    irccd_.transports().broadcast(nlohmann::json::object({
-        { "event",      "onMessage"         },
-        { "server",     ev.server->name()   },
-        { "origin",     ev.origin           },
-        { "channel",    ev.channel          },
-        { "message",    ev.message          }
-    }));
-
-    dispatch(irccd_, ev.server->name(), ev.origin, ev.channel,
-        [=] (plugin& plugin) -> std::string {
-            return string_util::parse_message(
-                ev.message,
-                ev.server->command_char(),
-                plugin.name()
-            ).type == string_util::message_pack::type::command ? "onCommand" : "onMessage";
-        },
-        [=] (plugin& plugin) mutable {
-            auto copy = ev;
-            auto pack = string_util::parse_message(copy.message, copy.server->command_char(), plugin.name());
-
-            copy.message = pack.message;
-
-            if (pack.type == string_util::message_pack::type::command)
-                plugin.on_command(irccd_, copy);
-            else
-                plugin.on_message(irccd_, copy);
-        }
-    );
-}
-
-void server_service::handle_me(const me_event& ev)
-{
-    irccd_.log().debug() << "server " << ev.server->name() << ": event onMe:\n";
-    irccd_.log().debug() << "  origin: " << ev.origin << "\n";
-    irccd_.log().debug() << "  target: " << ev.channel << "\n";
-    irccd_.log().debug() << "  message: " << ev.message << std::endl;
-
-    irccd_.transports().broadcast(nlohmann::json::object({
-        { "event",      "onMe"              },
-        { "server",     ev.server->name()   },
-        { "origin",     ev.origin           },
-        { "target",     ev.channel          },
-        { "message",    ev.message          }
-    }));
-
-    dispatch(irccd_, ev.server->name(), ev.origin, ev.channel,
-        [=] (plugin&) -> std::string {
-            return "onMe";
-        },
-        [=] (plugin& plugin) {
-            plugin.on_me(irccd_, ev);
-        }
-    );
-}
-
-void server_service::handle_mode(const mode_event& ev)
-{
-    irccd_.log().debug() << "server " << ev.server->name() << ": event onMode\n";
-    irccd_.log().debug() << "  origin: " << ev.origin << "\n";
-    irccd_.log().debug() << "  channel: " << ev.channel << "\n";
-    irccd_.log().debug() << "  mode: " << ev.mode << "\n";
-    irccd_.log().debug() << "  limit: " << ev.limit << "\n";
-    irccd_.log().debug() << "  user: " << ev.user << "\n";
-    irccd_.log().debug() << "  mask: " << ev.mask << std::endl;
-
-    irccd_.transports().broadcast(nlohmann::json::object({
-        { "event",      "onMode"            },
-        { "server",     ev.server->name()   },
-        { "origin",     ev.origin           },
-        { "channel",    ev.channel          },
-        { "mode",       ev.mode             },
-        { "limit",      ev.limit            },
-        { "user",       ev.user             },
-        { "mask",       ev.mask             }
-    }));
-
-    dispatch(irccd_, ev.server->name(), ev.origin, /* channel */ "",
-        [=] (plugin &) -> std::string {
-            return "onMode";
-        },
-        [=] (plugin &plugin) {
-            plugin.on_mode(irccd_, ev);
-        }
-    );
-}
-
-void server_service::handle_names(const names_event& ev)
-{
-    irccd_.log().debug() << "server " << ev.server->name() << ": event onNames:\n";
-    irccd_.log().debug() << "  channel: " << ev.channel << "\n";
-    irccd_.log().debug() << "  names: " << string_util::join(ev.names.begin(), ev.names.end(), ", ") << std::endl;
-
-    auto names = nlohmann::json::array();
-
-    for (const auto& v : ev.names)
-        names.push_back(v);
-
-    irccd_.transports().broadcast(nlohmann::json::object({
-        { "event",      "onNames"           },
-        { "server",     ev.server->name()   },
-        { "channel",    ev.channel          },
-        { "names",      std::move(names)    }
-    }));
-
-    dispatch(irccd_, ev.server->name(), /* origin */ "", ev.channel,
-        [=] (plugin&) -> std::string {
-            return "onNames";
-        },
-        [=] (plugin& plugin) {
-            plugin.on_names(irccd_, ev);
-        }
-    );
-}
-
-void server_service::handle_nick(const nick_event& ev)
-{
-    irccd_.log().debug() << "server " << ev.server->name() << ": event onNick:\n";
-    irccd_.log().debug() << "  origin: " << ev.origin << "\n";
-    irccd_.log().debug() << "  nickname: " << ev.nickname << std::endl;
-
-    irccd_.transports().broadcast(nlohmann::json::object({
-        { "event",      "onNick"            },
-        { "server",     ev.server->name()   },
-        { "origin",     ev.origin           },
-        { "nickname",   ev.nickname         }
-    }));
-
-    dispatch(irccd_, ev.server->name(), ev.origin, /* channel */ "",
-        [=] (plugin&) -> std::string {
-            return "onNick";
-        },
-        [=] (plugin& plugin) {
-            plugin.on_nick(irccd_, ev);
-        }
-    );
-}
-
-void server_service::handle_notice(const notice_event& ev)
-{
-    irccd_.log().debug() << "server " << ev.server->name() << ": event onNotice:\n";
-    irccd_.log().debug() << "  origin: " << ev.origin << "\n";
-    irccd_.log().debug() << "  channel: " << ev.channel << "\n";
-    irccd_.log().debug() << "  message: " << ev.message << std::endl;
-
-    irccd_.transports().broadcast(nlohmann::json::object({
-        { "event",      "onNotice"          },
-        { "server",     ev.server->name()   },
-        { "origin",     ev.origin           },
-        { "channel",    ev.channel          },
-        { "message",    ev.message          }
-    }));
-
-    dispatch(irccd_, ev.server->name(), ev.origin, /* channel */ "",
-        [=] (plugin&) -> std::string {
-            return "onNotice";
-        },
-        [=] (plugin& plugin) {
-            plugin.on_notice(irccd_, ev);
-        }
-    );
-}
-
-void server_service::handle_part(const part_event& ev)
-{
-    irccd_.log().debug() << "server " << ev.server->name() << ": event onPart:\n";
-    irccd_.log().debug() << "  origin: " << ev.origin << "\n";
-    irccd_.log().debug() << "  channel: " << ev.channel << "\n";
-    irccd_.log().debug() << "  reason: " << ev.reason << std::endl;
-
-    irccd_.transports().broadcast(nlohmann::json::object({
-        { "event",      "onPart"            },
-        { "server",     ev.server->name()   },
-        { "origin",     ev.origin           },
-        { "channel",    ev.channel          },
-        { "reason",     ev.reason           }
-    }));
-
-    dispatch(irccd_, ev.server->name(), ev.origin, ev.channel,
-        [=] (plugin&) -> std::string {
-            return "onPart";
-        },
-        [=] (plugin& plugin) {
-            plugin.on_part(irccd_, ev);
-        }
-    );
-}
-
-void server_service::handle_topic(const topic_event& ev)
-{
-    irccd_.log().debug() << "server " << ev.server->name() << ": event onTopic:\n";
-    irccd_.log().debug() << "  origin: " << ev.origin << "\n";
-    irccd_.log().debug() << "  channel: " << ev.channel << "\n";
-    irccd_.log().debug() << "  topic: " << ev.topic << std::endl;
-
-    irccd_.transports().broadcast(nlohmann::json::object({
-        { "event",      "onTopic"           },
-        { "server",     ev.server->name()   },
-        { "origin",     ev.origin           },
-        { "channel",    ev.channel          },
-        { "topic",      ev.topic            }
-    }));
-
-    dispatch(irccd_, ev.server->name(), ev.origin, ev.channel,
-        [=] (plugin&) -> std::string {
-            return "onTopic";
-        },
-        [=] (plugin& plugin) {
-            plugin.on_topic(irccd_, ev);
-        }
-    );
-}
-
-void server_service::handle_whois(const whois_event& ev)
-{
-    irccd_.log().debug() << "server " << ev.server->name() << ": event onWhois\n";
-    irccd_.log().debug() << "  nickname: " << ev.whois.nick << "\n";
-    irccd_.log().debug() << "  username: " << ev.whois.user << "\n";
-    irccd_.log().debug() << "  host: " << ev.whois.host << "\n";
-    irccd_.log().debug() << "  realname: " << ev.whois.realname << "\n";
-    irccd_.log().debug() << "  channels: " << string_util::join(ev.whois.channels, ", ") << std::endl;
-
-    irccd_.transports().broadcast(nlohmann::json::object({
-        { "event",      "onWhois"           },
-        { "server",     ev.server->name()   },
-        { "nickname",   ev.whois.nick       },
-        { "username",   ev.whois.user       },
-        { "host",       ev.whois.host       },
-        { "realname",   ev.whois.realname   }
-    }));
-
-    dispatch(irccd_, ev.server->name(), /* origin */ "", /* channel */ "",
-        [=] (plugin&) -> std::string {
-            return "onWhois";
-        },
-        [=] (plugin& plugin) {
-            plugin.on_whois(irccd_, ev);
-        }
-    );
-}
-
-std::shared_ptr<server> server_service::from_json(boost::asio::io_service& service, const nlohmann::json& object)
-{
-    // TODO: move this function in server_service.
-    auto sv = std::make_shared<server>(service, to_id(object));
-
-    // Mandatory fields.
-    sv->set_host(to_host(object, sv->name()));
-
-    // Optional fields.
-    if (object.count("port"))
-        sv->set_port(to_uint<std::uint16_t>(object["port"], sv->name(), server_error::invalid_port));
-    sv->set_password(json_util::get_string(object, "password"));
-    sv->set_nickname(json_util::get_string(object, "nickname", sv->nickname()));
-    sv->set_realname(json_util::get_string(object, "realname", sv->realname()));
-    sv->set_username(json_util::get_string(object, "username", sv->username()));
-    sv->set_ctcp_version(json_util::get_string(object, "ctcpVersion", sv->ctcp_version()));
-    sv->set_command_char(json_util::get_string(object, "commandChar", sv->command_char()));
-
-    if (json_util::get_bool(object, "ipv6"))
-        sv->set_flags(sv->flags() | server::ipv6);
-    if (json_util::get_bool(object, "sslVerify"))
-        sv->set_flags(sv->flags() | server::ssl_verify);
-    if (json_util::get_bool(object, "autoRejoin"))
-        sv->set_flags(sv->flags() | server::auto_rejoin);
-    if (json_util::get_bool(object, "joinInvite"))
-        sv->set_flags(sv->flags() | server::join_invite);
-
-    if (json_util::get_bool(object, "ssl"))
-#if defined(HAVE_SSL)
-        sv->set_flags(sv->flags() | server::ssl);
-#else
-        throw server_error(server_error::ssl_disabled, sv->name());
-#endif
-
-    return sv;
-}
-
-server_service::server_service(irccd &irccd)
-    : irccd_(irccd)
-{
-}
-
-bool server_service::has(const std::string& name) const noexcept
-{
-    return std::count_if(servers_.begin(), servers_.end(), [&] (const auto& server) {
-        return server->name() == name;
-    }) > 0;
-}
-
-void server_service::add(std::shared_ptr<server> server)
-{
-    assert(!has(server->name()));
-
-    std::weak_ptr<class server> ptr(server);
-
-    server->on_connect.connect(boost::bind(&server_service::handle_connect, this, _1));
-    server->on_invite.connect(boost::bind(&server_service::handle_invite, this, _1));
-    server->on_join.connect(boost::bind(&server_service::handle_join, this, _1));
-    server->on_kick.connect(boost::bind(&server_service::handle_kick, this, _1));
-    server->on_message.connect(boost::bind(&server_service::handle_message, this, _1));
-    server->on_me.connect(boost::bind(&server_service::handle_me, this, _1));
-    server->on_mode.connect(boost::bind(&server_service::handle_mode, this, _1));
-    server->on_names.connect(boost::bind(&server_service::handle_names, this, _1));
-    server->on_nick.connect(boost::bind(&server_service::handle_nick, this, _1));
-    server->on_notice.connect(boost::bind(&server_service::handle_notice, this, _1));
-    server->on_part.connect(boost::bind(&server_service::handle_part, this, _1));
-    server->on_topic.connect(boost::bind(&server_service::handle_topic, this, _1));
-    server->on_whois.connect(boost::bind(&server_service::handle_whois, this, _1));
-    server->on_die.connect([this, ptr] () {
-        auto server = ptr.lock();
-
-        if (server) {
-            irccd_.log().info(string_util::sprintf("server %s: removed", server->name()));
-            servers_.erase(std::find(servers_.begin(), servers_.end(), server));
-        }
-    });
-
-    server->connect();
-    servers_.push_back(std::move(server));
-}
-
-std::shared_ptr<server> server_service::get(const std::string& name) const noexcept
-{
-    auto it = std::find_if(servers_.begin(), servers_.end(), [&] (const auto& server) {
-        return server->name() == name;
-    });
-
-    if (it == servers_.end())
-        return nullptr;
-
-    return *it;
-}
-
-std::shared_ptr<server> server_service::require(const nlohmann::json& args, const std::string& key)
-{
-    auto id = json_util::get_string(args, key);
-
-    if (!string_util::is_identifier(id))
-        throw server_error(server_error::invalid_identifier, "");
-
-    auto server = get(id);
-
-    if (!server)
-        throw server_error(server_error::not_found, id);
-
-    return server;
-}
-
-void server_service::remove(const std::string& name)
-{
-    auto it = std::find_if(servers_.begin(), servers_.end(), [&] (const auto& server) {
-        return server->name() == name;
-    });
-
-    if (it != servers_.end()) {
-        (*it)->disconnect();
-        servers_.erase(it);
-    }
-}
-
-void server_service::clear() noexcept
-{
-    for (auto &server : servers_)
-        server->disconnect();
-
-    servers_.clear();
-}
-
-void server_service::load(const config& cfg) noexcept
-{
-    for (const auto& section : cfg.doc()) {
-        if (section.key() != "server")
-            continue;
-
-        try {
-            add(load_server(irccd_.service(), cfg, section));
-        } catch (const std::exception& ex) {
-            irccd_.log().warning() << "server " << section.get("name").value() << ": "
-                << ex.what() << std::endl;
-        }
-    }
-}
-
-} // !irccd
--- a/libirccd/irccd/daemon/server_service.hpp	Mon Jan 15 13:37:32 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,149 +0,0 @@
-/*
- * server_service.hpp -- server service
- *
- * Copyright (c) 2013-2018 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_DAEMON_SERVER_SERVICE_HPP
-#define IRCCD_DAEMON_SERVER_SERVICE_HPP
-
-/**
- * \file server_service.hpp
- * \brief Server service.
- */
-
-#include <memory>
-#include <vector>
-
-#include "server.hpp"
-
-namespace irccd {
-
-class config;
-class irccd;
-
-/**
- * \brief Manage IRC servers.
- * \ingroup services
- */
-class server_service {
-private:
-    irccd& irccd_;
-    std::vector<std::shared_ptr<server>> servers_;
-
-    void handle_connect(const connect_event&);
-    void handle_invite(const invite_event&);
-    void handle_join(const join_event&);
-    void handle_kick(const kick_event&);
-    void handle_message(const message_event&);
-    void handle_me(const me_event&);
-    void handle_mode(const mode_event&);
-    void handle_names(const names_event&);
-    void handle_nick(const nick_event&);
-    void handle_notice(const notice_event&);
-    void handle_part(const part_event&);
-    void handle_query(const query_event&);
-    void handle_topic(const topic_event&);
-    void handle_whois(const whois_event&);
-
-public:
-    /**
-     * Convert a JSON object as a server.
-     *
-     * Used in JavaScript API and transport commands.
-     *
-     * \param service the io service
-     * \param object the object
-     * \return the server
-     * \throw std::exception on failures
-     */
-    static std::shared_ptr<server> from_json(boost::asio::io_service& service, const nlohmann::json& object);
-
-    /**
-     * Create the server service.
-     */
-    server_service(irccd& instance);
-
-    /**
-     * Get the list of servers
-     *
-     * \return the servers
-     */
-    inline const std::vector<std::shared_ptr<server>>& servers() const noexcept
-    {
-        return servers_;
-    }
-
-    /**
-     * Check if a server exists.
-     *
-     * \param name the name
-     * \return true if exists
-     */
-    bool has(const std::string& name) const noexcept;
-
-    /**
-     * Add a new server to the application.
-     *
-     * \pre hasServer must return false
-     * \param sv the server
-     */
-    void add(std::shared_ptr<server> sv);
-
-    /**
-     * Get a server or empty one if not found
-     *
-     * \param name the server name
-     * \return the server or empty one if not found
-     */
-    std::shared_ptr<server> get(const std::string& name) const noexcept;
-
-    /**
-     * Find a server from a JSON object.
-     *
-     * \pre json.is_object()
-     * \param json the JSON object
-     * \param key the server identifier property
-     * \throw server_error on errors
-     */
-    std::shared_ptr<server> require(const nlohmann::json& json, const std::string& key = "server");
-
-    /**
-     * Remove a server from the irccd instance.
-     *
-     * The server if any, will be disconnected.
-     *
-     * \param name the server name
-     */
-    void remove(const std::string& name);
-
-    /**
-     * Remove all servers.
-     *
-     * All servers will be disconnected.
-     */
-    void clear() noexcept;
-
-    /**
-     * Load servers from the configuration.
-     *
-     * \param cfg the config
-     */
-    void load(const config& cfg) noexcept;
-};
-
-} // !irccd
-
-#endif // !IRCCD_DAEMON_SERVER_SERVICE_HPP
--- a/libirccd/irccd/daemon/server_topic_command.cpp	Mon Jan 15 13:37:32 2018 +0100
+++ b/libirccd/irccd/daemon/server_topic_command.cpp	Wed Mar 07 17:49:56 2018 +0100
@@ -19,10 +19,11 @@
 #include <irccd/json_util.hpp>
 
 #include "irccd.hpp"
-#include "server_service.hpp"
 #include "server_topic_command.hpp"
 #include "transport_client.hpp"
 
+#include <irccd/daemon/service/server_service.hpp>
+
 namespace irccd {
 
 std::string server_topic_command::get_name() const noexcept
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/daemon/service/plugin_service.cpp	Wed Mar 07 17:49:56 2018 +0100
@@ -0,0 +1,232 @@
+/*
+ * plugin_service.cpp -- plugin service
+ *
+ * Copyright (c) 2013-2018 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 <irccd/config.hpp>
+#include <irccd/string_util.hpp>
+#include <irccd/system.hpp>
+
+#include <irccd/daemon/irccd.hpp>
+#include <irccd/daemon/logger.hpp>
+
+#include <irccd/daemon/service/plugin_service.hpp>
+
+namespace irccd {
+
+namespace {
+
+template <typename Map>
+Map to_map(const config& conf, const std::string& section)
+{
+    Map ret;
+
+    for (const auto& opt : conf.doc().get(section))
+        ret.emplace(opt.key(), opt.value());
+
+    return ret;
+}
+
+} // !namespace
+
+plugin_service::plugin_service(irccd& irccd) noexcept
+    : irccd_(irccd)
+{
+}
+
+plugin_service::~plugin_service()
+{
+    for (const auto& plugin : plugins_) {
+        try {
+            plugin->on_unload(irccd_);
+        } catch (const std::exception& ex) {
+            irccd_.log().warning() << "plugin: " << plugin->name() << ": " << ex.what() << std::endl;
+        }
+    }
+}
+
+bool plugin_service::has(const std::string& name) const noexcept
+{
+    return std::count_if(plugins_.cbegin(), plugins_.cend(), [&] (const auto& plugin) {
+        return plugin->name() == name;
+    }) > 0;
+}
+
+std::shared_ptr<plugin> plugin_service::get(const std::string& name) const noexcept
+{
+    auto it = std::find_if(plugins_.begin(), plugins_.end(), [&] (const auto& plugin) {
+        return plugin->name() == name;
+    });
+
+    if (it == plugins_.end())
+        return nullptr;
+
+    return *it;
+}
+
+std::shared_ptr<plugin> plugin_service::require(const std::string& name) const
+{
+    auto plugin = get(name);
+
+    if (!plugin)
+        throw plugin_error(plugin_error::not_found, name);
+
+    return plugin;
+}
+
+void plugin_service::add(std::shared_ptr<plugin> plugin)
+{
+    plugins_.push_back(std::move(plugin));
+}
+
+void plugin_service::add_loader(std::unique_ptr<plugin_loader> loader)
+{
+    loaders_.push_back(std::move(loader));
+}
+
+plugin_config plugin_service::config(const std::string& id)
+{
+    return to_map<plugin_config>(irccd_.config(), string_util::sprintf("plugin.%s", id));
+}
+
+plugin_formats plugin_service::formats(const std::string& id)
+{
+    return to_map<plugin_formats>(irccd_.config(), string_util::sprintf("format.%s", id));
+}
+
+plugin_paths plugin_service::paths(const std::string& id)
+{
+    auto defaults = to_map<plugin_paths>(irccd_.config(), "paths");
+    auto paths = to_map<plugin_paths>(irccd_.config(), string_util::sprintf("paths.%s", id));
+
+    // Fill defaults paths.
+    if (!defaults.count("cache"))
+        defaults.emplace("cache", sys::cachedir() + "/plugin/" + id);
+    if (!defaults.count("data"))
+        paths.emplace("data", sys::datadir() + "/plugin/" + id);
+    if (!defaults.count("config"))
+        paths.emplace("config", sys::sysconfigdir() + "/plugin/" + id);
+
+    // Now fill missing fields.
+    if (!paths.count("cache"))
+        paths.emplace("cache", defaults["cache"]);
+    if (!paths.count("data"))
+        paths.emplace("data", defaults["data"]);
+    if (!paths.count("config"))
+        paths.emplace("config", defaults["config"]);
+
+    return paths;
+}
+
+std::shared_ptr<plugin> plugin_service::open(const std::string& id,
+                                             const std::string& path)
+{
+    for (const auto& loader : loaders_) {
+        auto plugin = loader->open(id, path);
+
+        if (plugin)
+            return plugin;
+    }
+
+    return nullptr;
+}
+
+std::shared_ptr<plugin> plugin_service::find(const std::string& id)
+{
+    for (const auto& loader : loaders_) {
+        auto plugin = loader->find(id);
+
+        if (plugin)
+            return plugin;
+    }
+
+    return nullptr;
+}
+
+void plugin_service::load(std::string name, std::string path)
+{
+    if (has(name))
+        throw plugin_error(plugin_error::already_exists, name);
+
+    std::shared_ptr<plugin> plugin;
+
+    if (path.empty())
+        plugin = find(name);
+    else
+        plugin = open(name, std::move(path));
+
+    if (!plugin)
+        throw plugin_error(plugin_error::not_found, name);
+
+    plugin->set_config(config(name));
+    plugin->set_formats(formats(name));
+    plugin->set_paths(paths(name));
+
+    exec(plugin, &plugin::on_load, irccd_);
+    add(std::move(plugin));
+}
+
+void plugin_service::reload(const std::string& name)
+{
+    auto plugin = get(name);
+
+    if (!plugin)
+        throw plugin_error(plugin_error::not_found, name);
+
+    exec(plugin, &plugin::on_reload, irccd_);
+}
+
+void plugin_service::unload(const std::string& name)
+{
+    auto it = std::find_if(plugins_.begin(), plugins_.end(), [&] (const auto& plugin) {
+        return plugin->name() == name;
+    });
+
+    if (it == plugins_.end())
+        throw plugin_error(plugin_error::not_found, name);
+
+    // Erase first, in case of throwing.
+    auto save = *it;
+
+    plugins_.erase(it);
+    exec(save, &plugin::on_unload, irccd_);
+}
+
+void plugin_service::load(const class config& cfg) noexcept
+{
+    for (const auto& option : cfg.section("plugins")) {
+        if (!string_util::is_identifier(option.key()))
+            continue;
+
+        auto name = option.key();
+        auto p = get(name);
+
+        // Reload the plugin if already loaded.
+        if (p) {
+            p->set_config(config(name));
+            p->set_formats(formats(name));
+            p->set_paths(paths(name));
+        } else {
+            try {
+                load(name, option.value());
+            } catch (const std::exception& ex) {
+                irccd_.log().warning(ex.what());
+            }
+        }
+    }
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/daemon/service/plugin_service.hpp	Wed Mar 07 17:49:56 2018 +0100
@@ -0,0 +1,237 @@
+/*
+ * plugin_service.hpp -- plugin service
+ *
+ * Copyright (c) 2013-2018 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_DAEMON_PLUGIN_SERVICE_HPP
+#define IRCCD_DAEMON_PLUGIN_SERVICE_HPP
+
+/**
+ * \file plugin_service.hpp
+ * \brief Plugin service.
+ */
+
+#include <cassert>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <irccd/daemon/plugin.hpp>
+
+namespace irccd {
+
+class irccd;
+class config;
+
+/**
+ * \brief Manage plugins.
+ * \ingroup services
+ */
+class plugin_service {
+private:
+    irccd& irccd_;
+    std::vector<std::shared_ptr<plugin>> plugins_;
+    std::vector<std::unique_ptr<plugin_loader>> loaders_;
+
+public:
+    /**
+     * Create the plugin service.
+     *
+     * \param irccd the irccd instance
+     */
+    plugin_service(irccd& irccd) noexcept;
+
+    /**
+     * Destroy plugins.
+     */
+    ~plugin_service();
+
+    /**
+     * Get the list of plugins.
+     *
+     * \return the list of plugins
+     */
+    inline const std::vector<std::shared_ptr<plugin>>& list() const noexcept
+    {
+        return plugins_;
+    }
+
+    /**
+     * Check if a plugin is loaded.
+     *
+     * \param name the plugin id
+     * \return true if has plugin
+     */
+    bool has(const std::string& name) const noexcept;
+
+    /**
+     * Get a loaded plugin or null if not found.
+     *
+     * \param name the plugin id
+     * \return the plugin or empty one if not found
+     */
+    std::shared_ptr<plugin> get(const std::string& name) const noexcept;
+
+    /**
+     * Find a loaded plugin.
+     *
+     * \param name the plugin id
+     * \return the plugin
+     * \throws std::out_of_range if not found
+     */
+    std::shared_ptr<plugin> require(const std::string& name) const;
+
+    /**
+     * Add the specified plugin to the registry.
+     *
+     * \pre plugin != nullptr
+     * \param plugin the plugin
+     * \note the plugin is only added to the list, no action is performed on it
+     */
+    void add(std::shared_ptr<plugin> plugin);
+
+    /**
+     * Add a loader.
+     *
+     * \param loader the loader
+     */
+    void add_loader(std::unique_ptr<plugin_loader> loader);
+
+    /**
+     * Get the configuration for the specified plugin.
+     *
+     * \return the configuration
+     */
+    plugin_config config(const std::string& id);
+
+    /**
+     * Get the formats for the specified plugin.
+     *
+     * \return the formats
+     */
+    plugin_formats formats(const std::string& id);
+
+    /**
+     * Get the paths for the specified plugin.
+     *
+     * If none is defined, return the default ones.
+     *
+     * \return the paths
+     */
+    plugin_paths paths(const std::string& id);
+
+    /**
+     * Generic function for opening the plugin at the given path.
+     *
+     * This function will search for every pluginLoader and call open() on it,
+     * the first one that success will be returned.
+     *
+     * \param id the plugin id
+     * \param path the path to the file
+     * \return the plugin or nullptr on failures
+     */
+    std::shared_ptr<plugin> open(const std::string& id,
+                                 const std::string& path);
+
+    /**
+     * Generic function for finding a plugin.
+     *
+     * \param id the plugin id
+     * \return the plugin or nullptr on failures
+     */
+    std::shared_ptr<plugin> find(const std::string& id);
+
+    /**
+     * Convenient wrapper that loads a plugin, call onLoad and add it to the
+     * registry.
+     *
+     * Any errors are printed using logger.
+     *
+     * \param name the name
+     * \param path the optional path (searched if empty)
+     */
+    void load(std::string name, std::string path = "");
+
+    /**
+     * Unload a plugin and remove it.
+     *
+     * \param name the plugin id
+     */
+    void unload(const std::string& name);
+
+    /**
+     * Reload a plugin by calling onReload.
+     *
+     * \param name the plugin name
+     * \throw std::exception on failures
+     */
+    void reload(const std::string& name);
+
+    /**
+     * Call a plugin function and throw an exception with the following errors:
+     *
+     *   - plugin_error::not_found if not loaded
+     *   - plugin_error::exec_error if function failed
+     *
+     * \pre plugin != nullptr
+     * \param plugin the plugin
+     * \param fn the plugin member function (pointer to member)
+     * \param args the arguments to pass
+     */
+    template <typename Func, typename... Args>
+    void exec(std::shared_ptr<plugin> plugin, Func fn, Args&&... args)
+    {
+        assert(plugin);
+
+        // TODO: replace with C++17 std::invoke.
+        try {
+            ((*plugin).*(fn))(std::forward<Args>(args)...);
+        } catch (const std::exception& ex) {
+            throw plugin_error(plugin_error::exec_error, plugin->name(), ex.what());
+        } catch (...) {
+            throw plugin_error(plugin_error::exec_error, plugin->name());
+        }
+    }
+
+    /**
+     * Overloaded function.
+     *
+     * \param name the plugin name
+     * \param fn the plugin member function (pointer to member)
+     * \param args the arguments to pass
+     */
+    template <typename Func, typename... Args>
+    void exec(const std::string& name, Func fn, Args&&... args)
+    {
+        auto plugin = find(name);
+
+        if (!plugin)
+            throw plugin_error(plugin_error::not_found, plugin->name());
+
+        exec(plugin, fn, std::forward<Args>(args)...);
+    }
+
+    /**
+     * Load all plugins.
+     *
+     * \param cfg the config
+     */
+    void load(const class config& cfg) noexcept;
+};
+
+} // !irccd
+
+#endif // !IRCCD_DAEMON_PLUGIN_SERVICE_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/daemon/service/rule_service.cpp	Wed Mar 07 17:49:56 2018 +0100
@@ -0,0 +1,241 @@
+/*
+ * rule_service.cpp -- rule service
+ *
+ * Copyright (c) 2013-2018 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 <stdexcept>
+
+#include <irccd/config.hpp>
+#include <irccd/string_util.hpp>
+
+#include <irccd/daemon/irccd.hpp>
+#include <irccd/daemon/logger.hpp>
+
+#include <irccd/daemon/service/rule_service.hpp>
+
+namespace irccd {
+
+namespace {
+
+rule load_rule(const ini::section& sc)
+{
+    assert(sc.key() == "rule");
+
+    // Simple converter from std::vector to std::unordered_set.
+    auto toset = [] (const auto& v) {
+        return std::unordered_set<std::string>(v.begin(), v.end());
+    };
+
+    rule::set servers, channels, origins, plugins, events;
+    rule::action action = rule::action::accept;
+
+    // Get the sets.
+    ini::section::const_iterator it;
+
+    if ((it = sc.find("servers")) != sc.end())
+        servers = toset(*it);
+    if ((it = sc.find("channels")) != sc.end())
+        channels = toset(*it);
+    if ((it = sc.find("origins")) != sc.end())
+        origins = toset(*it);
+    if ((it = sc.find("plugins")) != sc.end())
+        plugins = toset(*it);
+    if ((it = sc.find("channels")) != sc.end())
+        channels = toset(*it);
+
+    // Get the action.
+    auto actionstr = sc.get("action").value();
+
+    if (actionstr == "drop")
+        action = rule::action::drop;
+    else if (actionstr == "accept")
+        action = rule::action::accept;
+    else
+        throw rule_error(rule_error::invalid_action);
+
+    return {
+        std::move(servers),
+        std::move(channels),
+        std::move(origins),
+        std::move(plugins),
+        std::move(events),
+        action
+    };
+}
+
+} // !namespace
+
+rule rule_service::from_json(const nlohmann::json& json)
+{
+    auto toset = [] (auto object, auto name) {
+        rule::set result;
+
+        for (const auto& s : object[name])
+            if (s.is_string())
+                result.insert(s.template get<std::string>());
+
+        return result;
+    };
+    auto toaction = [] (auto object, auto name) {
+        auto v = object[name];
+
+        if (!v.is_string())
+            throw rule_error(rule_error::invalid_action);
+
+        auto s = v.template get<std::string>();
+        if (s == "accept")
+            return rule::action::accept;
+        if (s == "drop")
+            return rule::action::drop;
+
+        throw rule_error(rule_error::invalid_action);
+    };
+
+    return {
+        toset(json, "servers"),
+        toset(json, "channels"),
+        toset(json, "origins"),
+        toset(json, "plugins"),
+        toset(json, "events"),
+        toaction(json, "action")
+    };
+}
+
+unsigned rule_service::get_index(const nlohmann::json& json, const std::string& key)
+{
+    auto index = json.find(key);
+
+    if (index == json.end() || !index->is_number_integer() || index->get<int>() < 0)
+        throw rule_error(rule_error::invalid_index);
+
+    return index->get<int>();
+}
+
+nlohmann::json rule_service::to_json(const rule& rule)
+{
+    auto join = [] (const auto& set) {
+        auto array = nlohmann::json::array();
+
+        for (const auto& entry : set)
+            array.push_back(entry);
+
+        return array;
+    };
+    auto str = [] (auto action) {
+        switch (action) {
+        case rule::action::accept:
+            return "accept";
+        default:
+            return "drop";
+        }
+    };
+
+    return {
+        { "servers",    join(rule.get_servers())    },
+        { "channels",   join(rule.get_channels())   },
+        { "plugins",    join(rule.get_plugins())    },
+        { "events",     join(rule.get_events())     },
+        { "action",     str(rule.get_action())      }
+    };
+}
+
+rule_service::rule_service(irccd &irccd)
+    : irccd_(irccd)
+{
+}
+
+void rule_service::add(rule rule)
+{
+    rules_.push_back(std::move(rule));
+}
+
+void rule_service::insert(rule rule, unsigned position)
+{
+    assert(position <= rules_.size());
+
+    rules_.insert(rules_.begin() + position, std::move(rule));
+}
+
+void rule_service::remove(unsigned position)
+{
+    assert(position < rules_.size());
+
+    rules_.erase(rules_.begin() + position);
+}
+
+const rule &rule_service::require(unsigned position) const
+{
+    if (position >= rules_.size())
+        throw rule_error(rule_error::invalid_index);
+
+    return rules_[position];
+}
+
+rule &rule_service::require(unsigned position)
+{
+    if (position >= rules_.size())
+        throw rule_error(rule_error::invalid_index);
+
+    return rules_[position];
+}
+
+bool rule_service::solve(const std::string& server,
+                         const std::string& channel,
+                         const std::string& origin,
+                         const std::string& plugin,
+                         const std::string& event) noexcept
+{
+    bool result = true;
+
+    irccd_.log().debug(string_util::sprintf("rule: solving for server=%s, channel=%s, origin=%s, plugin=%s, event=%s",
+        server, channel, origin, plugin, event));
+
+    int i = 0;
+    for (const auto& rule : rules_) {
+        auto action = rule.get_action() == rule::action::accept ? "accept" : "drop";
+
+        irccd_.log().debug() << "  candidate "   << i++ << ":\n"
+            << "    servers: "  << string_util::join(rule.get_servers()) << "\n"
+            << "    channels: " << string_util::join(rule.get_channels()) << "\n"
+            << "    origins: "  << string_util::join(rule.get_origins()) << "\n"
+            << "    plugins: "  << string_util::join(rule.get_plugins()) << "\n"
+            << "    events: "   << string_util::join(rule.get_events()) << "\n"
+            << "    action: "   << action << std::endl;
+
+        if (rule.match(server, channel, origin, plugin, event))
+            result = rule.get_action() == rule::action::accept;
+    }
+
+    return result;
+}
+
+void rule_service::load(const config& cfg) noexcept
+{
+    rules_.clear();
+
+    for (const auto& section : cfg.doc()) {
+        if (section.key() != "rule")
+            continue;
+
+        try {
+            rules_.push_back(load_rule(section));
+        } catch (const std::exception& ex) {
+            irccd_.log().warning() << "rule: " << ex.what() << std::endl;
+        }
+    }
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/daemon/service/rule_service.hpp	Wed Mar 07 17:49:56 2018 +0100
@@ -0,0 +1,169 @@
+/*
+ * rule_service.hpp -- rule service
+ *
+ * Copyright (c) 2013-2018 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_DAEMON_RULE_SERVICE_HPP
+#define IRCCD_DAEMON_RULE_SERVICE_HPP
+
+/**
+ * \file rule_service.hpp
+ * \brief Rule service.
+ */
+
+#include <vector>
+
+#include <json.hpp>
+
+#include <irccd/daemon/rule.hpp>
+
+namespace irccd {
+
+class config;
+class irccd;
+
+/**
+ * \brief Store and solve rules.
+ * \ingroup services
+ */
+class rule_service {
+private:
+    irccd& irccd_;
+    std::vector<rule> rules_;
+
+public:
+    /**
+     * Load a rule from a JSON object.
+     *
+     * For possible use in transport commands or Javascript API.
+     *
+     * \pre json.is_object()
+     * \param json the JSON object
+     * \return the new rule
+     * \throw rule_error on errors
+     */
+    static rule from_json(const nlohmann::json& json);
+
+    /**
+     * Helper to get rule index in a JSON object.
+     *
+     * \pre json.is_object()
+     * \param json the JSON object
+     * \param key the index property
+     * \return the index
+     * \throw rule_error on errors
+     */
+    static unsigned get_index(const nlohmann::json& json, const std::string& key = "index");
+
+    /**
+     * Convert a rule into a JSON object.
+     *
+     * \param rule the rule
+     * \throw the JSON representation
+     */
+    static nlohmann::json to_json(const rule& rule);
+
+    /**
+     * Create the rule service.
+     */
+    rule_service(irccd& instance);
+
+    /**
+     * Get the list of rules.
+     *
+     * \return the list of rules
+     */
+    inline const std::vector<rule>& list() const noexcept
+    {
+        return rules_;
+    }
+
+    /**
+     * Get the number of rules.
+     *
+     * \return the number of rules
+     */
+    inline std::size_t length() const noexcept
+    {
+        return rules_.size();
+    }
+
+    /**
+     * Append a rule.
+     *
+     * \param rule the rule to append
+     */
+    void add(rule rule);
+
+    /**
+     * Insert a new rule at the specified position.
+     *
+     * \param rule the rule
+     * \param position the position
+     */
+    void insert(rule rule, unsigned position);
+
+    /**
+     * Remove a new rule from the specified position.
+     *
+     * \pre position must be valid
+     * \param position the position
+     */
+    void remove(unsigned position);
+
+    /**
+     * Get a rule at the specified index or throw an exception if not found.
+     *
+     * \param position the position
+     * \return the rule
+     * \throw std::out_of_range if position is invalid
+     */
+    const rule& require(unsigned position) const;
+
+    /**
+     * Overloaded function.
+     *
+     * \copydoc require
+     */
+    rule& require(unsigned position);
+
+    /**
+     * Resolve the action to execute with the specified list of rules.
+     *
+     * \param server the server name
+     * \param channel the channel name
+     * \param origin the origin
+     * \param plugin the plugin name
+     * \param event the event name (e.g onKick)
+     * \return true if the plugin must be called
+     */
+    bool solve(const std::string& server,
+               const std::string& channel,
+               const std::string& origin,
+               const std::string& plugin,
+               const std::string& event) noexcept;
+
+    /**
+     * Load rules from the configuration.
+     *
+     * \param cfg the config
+     */
+    void load(const config& cfg) noexcept;
+};
+
+} // !irccd
+
+#endif // !IRCCD_DAEMON_RULE_SERVICE_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/daemon/service/server_service.cpp	Wed Mar 07 17:49:56 2018 +0100
@@ -0,0 +1,728 @@
+/*
+ * server_service.hpp -- server service
+ *
+ * Copyright (c) 2013-2018 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 <irccd/json_util.hpp>
+#include <irccd/string_util.hpp>
+
+#include <irccd/daemon/irccd.hpp>
+#include <irccd/daemon/logger.hpp>
+
+#include <irccd/daemon/service/plugin_service.hpp>
+#include <irccd/daemon/service/rule_service.hpp>
+#include <irccd/daemon/service/server_service.hpp>
+#include <irccd/daemon/service/transport_service.hpp>
+
+namespace irccd {
+
+namespace {
+
+template <typename EventNameFunc, typename ExecFunc>
+void dispatch(irccd& daemon,
+              const std::string& server,
+              const std::string& origin,
+              const std::string& target,
+              EventNameFunc&& name_func,
+              ExecFunc exec_func)
+{
+    for (auto& plugin : daemon.plugins().list()) {
+        auto eventname = name_func(*plugin);
+        auto allowed = daemon.rules().solve(server, target, origin, plugin->name(), eventname);
+
+        if (!allowed) {
+            daemon.log().debug("rule: event skipped on match");
+            continue;
+        }
+
+        daemon.log().debug("rule: event allowed");
+
+        try {
+            exec_func(*plugin);
+        } catch (const std::exception& ex) {
+            daemon.log().warning() << "plugin " << plugin->name() << ": error: "
+                << ex.what() << std::endl;
+        }
+    }
+}
+
+template <typename T>
+T to_int(const std::string& value, const std::string& name, server_error::error errc)
+{
+    try {
+        return string_util::to_int<T>(value);
+    } catch (...) {
+        throw server_error(errc, name);
+    }
+}
+
+template <typename T>
+T to_uint(const std::string& value, const std::string& name, server_error::error errc)
+{
+    try {
+        return string_util::to_uint<T>(value);
+    } catch (...) {
+        throw server_error(errc, name);
+    }
+}
+
+template <typename T>
+T to_uint(const nlohmann::json& value, const std::string& name, server_error::error errc)
+{
+    if (!value.is_number())
+        throw server_error(errc, name);
+
+    auto n = value.get<unsigned>();
+
+    if (n > std::numeric_limits<T>::max())
+        throw server_error(errc, name);
+
+    return static_cast<T>(n);
+}
+
+std::string to_id(const ini::section& sc)
+{
+    auto id = sc.get("name");
+
+    if (!string_util::is_identifier(id.value()))
+        throw server_error(server_error::invalid_identifier, "");
+
+    return id.value();
+}
+
+std::string to_id(const nlohmann::json& object)
+{
+    auto id = json_util::get_string(object, "name");
+
+    if (!string_util::is_identifier(id))
+        throw server_error(server_error::invalid_identifier, "");
+
+    return id;
+}
+
+std::string to_host(const ini::section& sc, const std::string& name)
+{
+    auto value = sc.get("host");
+
+    if (value.empty())
+        throw server_error(server_error::invalid_hostname, name);
+
+    return value.value();
+}
+
+std::string to_host(const nlohmann::json& object, const std::string& name)
+{
+    auto value = json_util::get_string(object, "host");
+
+    if (value.empty())
+        throw server_error(server_error::invalid_hostname, name);
+
+    return value;
+}
+
+void load_server_identity(std::shared_ptr<server>& server,
+                          const config& cfg,
+                          const std::string& identity)
+{
+    auto sc = std::find_if(cfg.doc().begin(), cfg.doc().end(), [&] (const auto& sc) {
+        if (sc.key() != "identity")
+            return false;
+
+        auto name = sc.find("name");
+
+        return name != sc.end() && name->value() == identity;
+    });
+
+    if (sc == cfg.doc().end())
+        return;
+
+    ini::section::const_iterator it;
+
+    if ((it = sc->find("username")) != sc->end())
+        server->set_username(it->value());
+    if ((it = sc->find("realname")) != sc->end())
+        server->set_realname(it->value());
+    if ((it = sc->find("nickname")) != sc->end())
+        server->set_nickname(it->value());
+    if ((it = sc->find("ctcp-version")) != sc->end())
+        server->set_ctcp_version(it->value());
+}
+
+std::shared_ptr<server> load_server(boost::asio::io_service& service,
+                                    const config& cfg,
+                                    const ini::section& sc)
+{
+    assert(sc.key() == "server");
+
+    auto sv = std::make_shared<server>(service, to_id(sc));
+
+    // Mandatory fields.
+    sv->set_host(to_host(sc, sv->name()));
+
+    // Optional fields.
+    ini::section::const_iterator it;
+
+    if ((it = sc.find("password")) != sc.end())
+        sv->set_password(it->value());
+
+    // Optional flags
+    if ((it = sc.find("ipv6")) != sc.end() && string_util::is_boolean(it->value()))
+        sv->set_flags(sv->flags() | server::ipv6);
+
+    if ((it = sc.find("ssl")) != sc.end() && string_util::is_boolean(it->value())) {
+#if defined(HAVE_SSL)
+        sv->set_flags(sv->flags() | server::ssl);
+#else
+        throw server_error(server_error::ssl_disabled, sv->name());
+#endif
+    }
+
+    if ((it = sc.find("ssl-verify")) != sc.end() && string_util::is_boolean(it->value()))
+        sv->set_flags(sv->flags() | server::ssl_verify);
+
+    // Optional identity
+    if ((it = sc.find("identity")) != sc.end())
+        load_server_identity(sv, cfg, it->value());
+
+    // Options
+    if ((it = sc.find("auto-rejoin")) != sc.end() && string_util::is_boolean(it->value()))
+        sv->set_flags(sv->flags() | server::auto_rejoin);
+    if ((it = sc.find("join-invite")) != sc.end() && string_util::is_boolean(it->value()))
+        sv->set_flags(sv->flags() | server::join_invite);
+
+    // Channels
+    if ((it = sc.find("channels")) != sc.end()) {
+        for (const auto& s : *it) {
+            channel channel;
+
+            if (auto pos = s.find(":") != std::string::npos) {
+                channel.name = s.substr(0, pos);
+                channel.password = s.substr(pos + 1);
+            } else
+                channel.name = s;
+
+            sv->join(channel.name, channel.password);
+        }
+    }
+    if ((it = sc.find("command-char")) != sc.end())
+        sv->set_command_char(it->value());
+
+    // Reconnect and ping timeout
+    if ((it = sc.find("port")) != sc.end())
+        sv->set_port(to_uint<std::uint16_t>(it->value(),
+            sv->name(), server_error::invalid_port));
+
+    if ((it = sc.find("reconnect-tries")) != sc.end())
+        sv->set_reconnect_tries(to_int<std::int8_t>(it->value(),
+            sv->name(), server_error::invalid_reconnect_tries));
+
+    if ((it = sc.find("reconnect-timeout")) != sc.end())
+        sv->set_reconnect_delay(to_uint<std::uint16_t>(it->value(),
+            sv->name(), server_error::invalid_reconnect_timeout));
+
+    if ((it = sc.find("ping-timeout")) != sc.end())
+        sv->set_ping_timeout(to_uint<std::uint16_t>(it->value(),
+            sv->name(), server_error::invalid_ping_timeout));
+
+    return sv;
+}
+
+} // !namespace
+
+void server_service::handle_connect(const connect_event& ev)
+{
+    irccd_.log().debug() << "server " << ev.server->name() << ": event onConnect" << std::endl;
+
+    irccd_.transports().broadcast(nlohmann::json::object({
+        { "event",      "onConnect"         },
+        { "server",     ev.server->name()   }
+    }));
+
+    dispatch(irccd_, ev.server->name(), /* origin */ "", /* channel */ "",
+        [=] (plugin&) -> std::string {
+            return "onConnect";
+        },
+        [=] (plugin& plugin) {
+            plugin.on_connect(irccd_, ev);
+        }
+    );
+}
+
+void server_service::handle_invite(const invite_event& ev)
+{
+    irccd_.log().debug() << "server " << ev.server->name() << ": event onInvite:\n";
+    irccd_.log().debug() << "  origin: " << ev.origin << "\n";
+    irccd_.log().debug() << "  channel: " << ev.channel << "\n";
+    irccd_.log().debug() << "  target: " << ev.nickname << std::endl;
+
+    irccd_.transports().broadcast(nlohmann::json::object({
+        { "event",      "onInvite"          },
+        { "server",     ev.server->name()   },
+        { "origin",     ev.origin           },
+        { "channel",    ev.channel          }
+    }));
+
+    dispatch(irccd_, ev.server->name(), ev.origin, ev.channel,
+        [=] (plugin&) -> std::string {
+            return "onInvite";
+        },
+        [=] (plugin& plugin) {
+            plugin.on_invite(irccd_, ev);
+        }
+    );
+}
+
+void server_service::handle_join(const join_event& ev)
+{
+    irccd_.log().debug() << "server " << ev.server->name() << ": event onJoin:\n";
+    irccd_.log().debug() << "  origin: " << ev.origin << "\n";
+    irccd_.log().debug() << "  channel: " << ev.channel << std::endl;
+
+    irccd_.transports().broadcast(nlohmann::json::object({
+        { "event",      "onJoin"            },
+        { "server",     ev.server->name()   },
+        { "origin",     ev.origin           },
+        { "channel",    ev.channel          }
+    }));
+
+    dispatch(irccd_, ev.server->name(), ev.origin, ev.channel,
+        [=] (plugin&) -> std::string {
+            return "onJoin";
+        },
+        [=] (plugin& plugin) {
+            plugin.on_join(irccd_, ev);
+        }
+    );
+}
+
+void server_service::handle_kick(const kick_event& ev)
+{
+    irccd_.log().debug() << "server " << ev.server->name() << ": event onKick:\n";
+    irccd_.log().debug() << "  origin: " << ev.origin << "\n";
+    irccd_.log().debug() << "  channel: " << ev.channel << "\n";
+    irccd_.log().debug() << "  target: " << ev.target << "\n";
+    irccd_.log().debug() << "  reason: " << ev.reason << std::endl;
+
+    irccd_.transports().broadcast(nlohmann::json::object({
+        { "event",      "onKick"            },
+        { "server",     ev.server->name()   },
+        { "origin",     ev.origin           },
+        { "channel",    ev.channel          },
+        { "target",     ev.target           },
+        { "reason",     ev.reason           }
+    }));
+
+    dispatch(irccd_, ev.server->name(), ev.origin, ev.channel,
+        [=] (plugin&) -> std::string {
+            return "onKick";
+        },
+        [=] (plugin& plugin) {
+            plugin.on_kick(irccd_, ev);
+        }
+    );
+}
+
+void server_service::handle_message(const message_event& ev)
+{
+    irccd_.log().debug() << "server " << ev.server->name() << ": event onMessage:\n";
+    irccd_.log().debug() << "  origin: " << ev.origin << "\n";
+    irccd_.log().debug() << "  channel: " << ev.channel << "\n";
+    irccd_.log().debug() << "  message: " << ev.message << std::endl;
+
+    irccd_.transports().broadcast(nlohmann::json::object({
+        { "event",      "onMessage"         },
+        { "server",     ev.server->name()   },
+        { "origin",     ev.origin           },
+        { "channel",    ev.channel          },
+        { "message",    ev.message          }
+    }));
+
+    dispatch(irccd_, ev.server->name(), ev.origin, ev.channel,
+        [=] (plugin& plugin) -> std::string {
+            return string_util::parse_message(
+                ev.message,
+                ev.server->command_char(),
+                plugin.name()
+            ).type == string_util::message_pack::type::command ? "onCommand" : "onMessage";
+        },
+        [=] (plugin& plugin) mutable {
+            auto copy = ev;
+            auto pack = string_util::parse_message(copy.message, copy.server->command_char(), plugin.name());
+
+            copy.message = pack.message;
+
+            if (pack.type == string_util::message_pack::type::command)
+                plugin.on_command(irccd_, copy);
+            else
+                plugin.on_message(irccd_, copy);
+        }
+    );
+}
+
+void server_service::handle_me(const me_event& ev)
+{
+    irccd_.log().debug() << "server " << ev.server->name() << ": event onMe:\n";
+    irccd_.log().debug() << "  origin: " << ev.origin << "\n";
+    irccd_.log().debug() << "  target: " << ev.channel << "\n";
+    irccd_.log().debug() << "  message: " << ev.message << std::endl;
+
+    irccd_.transports().broadcast(nlohmann::json::object({
+        { "event",      "onMe"              },
+        { "server",     ev.server->name()   },
+        { "origin",     ev.origin           },
+        { "target",     ev.channel          },
+        { "message",    ev.message          }
+    }));
+
+    dispatch(irccd_, ev.server->name(), ev.origin, ev.channel,
+        [=] (plugin&) -> std::string {
+            return "onMe";
+        },
+        [=] (plugin& plugin) {
+            plugin.on_me(irccd_, ev);
+        }
+    );
+}
+
+void server_service::handle_mode(const mode_event& ev)
+{
+    irccd_.log().debug() << "server " << ev.server->name() << ": event onMode\n";
+    irccd_.log().debug() << "  origin: " << ev.origin << "\n";
+    irccd_.log().debug() << "  channel: " << ev.channel << "\n";
+    irccd_.log().debug() << "  mode: " << ev.mode << "\n";
+    irccd_.log().debug() << "  limit: " << ev.limit << "\n";
+    irccd_.log().debug() << "  user: " << ev.user << "\n";
+    irccd_.log().debug() << "  mask: " << ev.mask << std::endl;
+
+    irccd_.transports().broadcast(nlohmann::json::object({
+        { "event",      "onMode"            },
+        { "server",     ev.server->name()   },
+        { "origin",     ev.origin           },
+        { "channel",    ev.channel          },
+        { "mode",       ev.mode             },
+        { "limit",      ev.limit            },
+        { "user",       ev.user             },
+        { "mask",       ev.mask             }
+    }));
+
+    dispatch(irccd_, ev.server->name(), ev.origin, /* channel */ "",
+        [=] (plugin &) -> std::string {
+            return "onMode";
+        },
+        [=] (plugin &plugin) {
+            plugin.on_mode(irccd_, ev);
+        }
+    );
+}
+
+void server_service::handle_names(const names_event& ev)
+{
+    irccd_.log().debug() << "server " << ev.server->name() << ": event onNames:\n";
+    irccd_.log().debug() << "  channel: " << ev.channel << "\n";
+    irccd_.log().debug() << "  names: " << string_util::join(ev.names.begin(), ev.names.end(), ", ") << std::endl;
+
+    auto names = nlohmann::json::array();
+
+    for (const auto& v : ev.names)
+        names.push_back(v);
+
+    irccd_.transports().broadcast(nlohmann::json::object({
+        { "event",      "onNames"           },
+        { "server",     ev.server->name()   },
+        { "channel",    ev.channel          },
+        { "names",      std::move(names)    }
+    }));
+
+    dispatch(irccd_, ev.server->name(), /* origin */ "", ev.channel,
+        [=] (plugin&) -> std::string {
+            return "onNames";
+        },
+        [=] (plugin& plugin) {
+            plugin.on_names(irccd_, ev);
+        }
+    );
+}
+
+void server_service::handle_nick(const nick_event& ev)
+{
+    irccd_.log().debug() << "server " << ev.server->name() << ": event onNick:\n";
+    irccd_.log().debug() << "  origin: " << ev.origin << "\n";
+    irccd_.log().debug() << "  nickname: " << ev.nickname << std::endl;
+
+    irccd_.transports().broadcast(nlohmann::json::object({
+        { "event",      "onNick"            },
+        { "server",     ev.server->name()   },
+        { "origin",     ev.origin           },
+        { "nickname",   ev.nickname         }
+    }));
+
+    dispatch(irccd_, ev.server->name(), ev.origin, /* channel */ "",
+        [=] (plugin&) -> std::string {
+            return "onNick";
+        },
+        [=] (plugin& plugin) {
+            plugin.on_nick(irccd_, ev);
+        }
+    );
+}
+
+void server_service::handle_notice(const notice_event& ev)
+{
+    irccd_.log().debug() << "server " << ev.server->name() << ": event onNotice:\n";
+    irccd_.log().debug() << "  origin: " << ev.origin << "\n";
+    irccd_.log().debug() << "  channel: " << ev.channel << "\n";
+    irccd_.log().debug() << "  message: " << ev.message << std::endl;
+
+    irccd_.transports().broadcast(nlohmann::json::object({
+        { "event",      "onNotice"          },
+        { "server",     ev.server->name()   },
+        { "origin",     ev.origin           },
+        { "channel",    ev.channel          },
+        { "message",    ev.message          }
+    }));
+
+    dispatch(irccd_, ev.server->name(), ev.origin, /* channel */ "",
+        [=] (plugin&) -> std::string {
+            return "onNotice";
+        },
+        [=] (plugin& plugin) {
+            plugin.on_notice(irccd_, ev);
+        }
+    );
+}
+
+void server_service::handle_part(const part_event& ev)
+{
+    irccd_.log().debug() << "server " << ev.server->name() << ": event onPart:\n";
+    irccd_.log().debug() << "  origin: " << ev.origin << "\n";
+    irccd_.log().debug() << "  channel: " << ev.channel << "\n";
+    irccd_.log().debug() << "  reason: " << ev.reason << std::endl;
+
+    irccd_.transports().broadcast(nlohmann::json::object({
+        { "event",      "onPart"            },
+        { "server",     ev.server->name()   },
+        { "origin",     ev.origin           },
+        { "channel",    ev.channel          },
+        { "reason",     ev.reason           }
+    }));
+
+    dispatch(irccd_, ev.server->name(), ev.origin, ev.channel,
+        [=] (plugin&) -> std::string {
+            return "onPart";
+        },
+        [=] (plugin& plugin) {
+            plugin.on_part(irccd_, ev);
+        }
+    );
+}
+
+void server_service::handle_topic(const topic_event& ev)
+{
+    irccd_.log().debug() << "server " << ev.server->name() << ": event onTopic:\n";
+    irccd_.log().debug() << "  origin: " << ev.origin << "\n";
+    irccd_.log().debug() << "  channel: " << ev.channel << "\n";
+    irccd_.log().debug() << "  topic: " << ev.topic << std::endl;
+
+    irccd_.transports().broadcast(nlohmann::json::object({
+        { "event",      "onTopic"           },
+        { "server",     ev.server->name()   },
+        { "origin",     ev.origin           },
+        { "channel",    ev.channel          },
+        { "topic",      ev.topic            }
+    }));
+
+    dispatch(irccd_, ev.server->name(), ev.origin, ev.channel,
+        [=] (plugin&) -> std::string {
+            return "onTopic";
+        },
+        [=] (plugin& plugin) {
+            plugin.on_topic(irccd_, ev);
+        }
+    );
+}
+
+void server_service::handle_whois(const whois_event& ev)
+{
+    irccd_.log().debug() << "server " << ev.server->name() << ": event onWhois\n";
+    irccd_.log().debug() << "  nickname: " << ev.whois.nick << "\n";
+    irccd_.log().debug() << "  username: " << ev.whois.user << "\n";
+    irccd_.log().debug() << "  host: " << ev.whois.host << "\n";
+    irccd_.log().debug() << "  realname: " << ev.whois.realname << "\n";
+    irccd_.log().debug() << "  channels: " << string_util::join(ev.whois.channels, ", ") << std::endl;
+
+    irccd_.transports().broadcast(nlohmann::json::object({
+        { "event",      "onWhois"           },
+        { "server",     ev.server->name()   },
+        { "nickname",   ev.whois.nick       },
+        { "username",   ev.whois.user       },
+        { "host",       ev.whois.host       },
+        { "realname",   ev.whois.realname   }
+    }));
+
+    dispatch(irccd_, ev.server->name(), /* origin */ "", /* channel */ "",
+        [=] (plugin&) -> std::string {
+            return "onWhois";
+        },
+        [=] (plugin& plugin) {
+            plugin.on_whois(irccd_, ev);
+        }
+    );
+}
+
+std::shared_ptr<server> server_service::from_json(boost::asio::io_service& service, const nlohmann::json& object)
+{
+    // TODO: move this function in server_service.
+    auto sv = std::make_shared<server>(service, to_id(object));
+
+    // Mandatory fields.
+    sv->set_host(to_host(object, sv->name()));
+
+    // Optional fields.
+    if (object.count("port"))
+        sv->set_port(to_uint<std::uint16_t>(object["port"], sv->name(), server_error::invalid_port));
+    sv->set_password(json_util::get_string(object, "password"));
+    sv->set_nickname(json_util::get_string(object, "nickname", sv->nickname()));
+    sv->set_realname(json_util::get_string(object, "realname", sv->realname()));
+    sv->set_username(json_util::get_string(object, "username", sv->username()));
+    sv->set_ctcp_version(json_util::get_string(object, "ctcpVersion", sv->ctcp_version()));
+    sv->set_command_char(json_util::get_string(object, "commandChar", sv->command_char()));
+
+    if (json_util::get_bool(object, "ipv6"))
+        sv->set_flags(sv->flags() | server::ipv6);
+    if (json_util::get_bool(object, "sslVerify"))
+        sv->set_flags(sv->flags() | server::ssl_verify);
+    if (json_util::get_bool(object, "autoRejoin"))
+        sv->set_flags(sv->flags() | server::auto_rejoin);
+    if (json_util::get_bool(object, "joinInvite"))
+        sv->set_flags(sv->flags() | server::join_invite);
+
+    if (json_util::get_bool(object, "ssl"))
+#if defined(HAVE_SSL)
+        sv->set_flags(sv->flags() | server::ssl);
+#else
+        throw server_error(server_error::ssl_disabled, sv->name());
+#endif
+
+    return sv;
+}
+
+server_service::server_service(irccd &irccd)
+    : irccd_(irccd)
+{
+}
+
+bool server_service::has(const std::string& name) const noexcept
+{
+    return std::count_if(servers_.begin(), servers_.end(), [&] (const auto& server) {
+        return server->name() == name;
+    }) > 0;
+}
+
+void server_service::add(std::shared_ptr<server> server)
+{
+    assert(!has(server->name()));
+
+    std::weak_ptr<class server> ptr(server);
+
+    server->on_connect.connect(boost::bind(&server_service::handle_connect, this, _1));
+    server->on_invite.connect(boost::bind(&server_service::handle_invite, this, _1));
+    server->on_join.connect(boost::bind(&server_service::handle_join, this, _1));
+    server->on_kick.connect(boost::bind(&server_service::handle_kick, this, _1));
+    server->on_message.connect(boost::bind(&server_service::handle_message, this, _1));
+    server->on_me.connect(boost::bind(&server_service::handle_me, this, _1));
+    server->on_mode.connect(boost::bind(&server_service::handle_mode, this, _1));
+    server->on_names.connect(boost::bind(&server_service::handle_names, this, _1));
+    server->on_nick.connect(boost::bind(&server_service::handle_nick, this, _1));
+    server->on_notice.connect(boost::bind(&server_service::handle_notice, this, _1));
+    server->on_part.connect(boost::bind(&server_service::handle_part, this, _1));
+    server->on_topic.connect(boost::bind(&server_service::handle_topic, this, _1));
+    server->on_whois.connect(boost::bind(&server_service::handle_whois, this, _1));
+    server->on_die.connect([this, ptr] () {
+        auto server = ptr.lock();
+
+        if (server) {
+            irccd_.log().info(string_util::sprintf("server %s: removed", server->name()));
+            servers_.erase(std::find(servers_.begin(), servers_.end(), server));
+        }
+    });
+
+    server->connect();
+    servers_.push_back(std::move(server));
+}
+
+std::shared_ptr<server> server_service::get(const std::string& name) const noexcept
+{
+    auto it = std::find_if(servers_.begin(), servers_.end(), [&] (const auto& server) {
+        return server->name() == name;
+    });
+
+    if (it == servers_.end())
+        return nullptr;
+
+    return *it;
+}
+
+std::shared_ptr<server> server_service::require(const nlohmann::json& args, const std::string& key)
+{
+    auto id = json_util::get_string(args, key);
+
+    if (!string_util::is_identifier(id))
+        throw server_error(server_error::invalid_identifier, "");
+
+    auto server = get(id);
+
+    if (!server)
+        throw server_error(server_error::not_found, id);
+
+    return server;
+}
+
+void server_service::remove(const std::string& name)
+{
+    auto it = std::find_if(servers_.begin(), servers_.end(), [&] (const auto& server) {
+        return server->name() == name;
+    });
+
+    if (it != servers_.end()) {
+        (*it)->disconnect();
+        servers_.erase(it);
+    }
+}
+
+void server_service::clear() noexcept
+{
+    for (auto &server : servers_)
+        server->disconnect();
+
+    servers_.clear();
+}
+
+void server_service::load(const config& cfg) noexcept
+{
+    for (const auto& section : cfg.doc()) {
+        if (section.key() != "server")
+            continue;
+
+        try {
+            add(load_server(irccd_.service(), cfg, section));
+        } catch (const std::exception& ex) {
+            irccd_.log().warning() << "server " << section.get("name").value() << ": "
+                << ex.what() << std::endl;
+        }
+    }
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/daemon/service/server_service.hpp	Wed Mar 07 17:49:56 2018 +0100
@@ -0,0 +1,149 @@
+/*
+ * server_service.hpp -- server service
+ *
+ * Copyright (c) 2013-2018 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_DAEMON_SERVER_SERVICE_HPP
+#define IRCCD_DAEMON_SERVER_SERVICE_HPP
+
+/**
+ * \file server_service.hpp
+ * \brief Server service.
+ */
+
+#include <memory>
+#include <vector>
+
+#include <irccd/daemon/server.hpp>
+
+namespace irccd {
+
+class config;
+class irccd;
+
+/**
+ * \brief Manage IRC servers.
+ * \ingroup services
+ */
+class server_service {
+private:
+    irccd& irccd_;
+    std::vector<std::shared_ptr<server>> servers_;
+
+    void handle_connect(const connect_event&);
+    void handle_invite(const invite_event&);
+    void handle_join(const join_event&);
+    void handle_kick(const kick_event&);
+    void handle_message(const message_event&);
+    void handle_me(const me_event&);
+    void handle_mode(const mode_event&);
+    void handle_names(const names_event&);
+    void handle_nick(const nick_event&);
+    void handle_notice(const notice_event&);
+    void handle_part(const part_event&);
+    void handle_query(const query_event&);
+    void handle_topic(const topic_event&);
+    void handle_whois(const whois_event&);
+
+public:
+    /**
+     * Convert a JSON object as a server.
+     *
+     * Used in JavaScript API and transport commands.
+     *
+     * \param service the io service
+     * \param object the object
+     * \return the server
+     * \throw std::exception on failures
+     */
+    static std::shared_ptr<server> from_json(boost::asio::io_service& service, const nlohmann::json& object);
+
+    /**
+     * Create the server service.
+     */
+    server_service(irccd& instance);
+
+    /**
+     * Get the list of servers
+     *
+     * \return the servers
+     */
+    inline const std::vector<std::shared_ptr<server>>& servers() const noexcept
+    {
+        return servers_;
+    }
+
+    /**
+     * Check if a server exists.
+     *
+     * \param name the name
+     * \return true if exists
+     */
+    bool has(const std::string& name) const noexcept;
+
+    /**
+     * Add a new server to the application.
+     *
+     * \pre hasServer must return false
+     * \param sv the server
+     */
+    void add(std::shared_ptr<server> sv);
+
+    /**
+     * Get a server or empty one if not found
+     *
+     * \param name the server name
+     * \return the server or empty one if not found
+     */
+    std::shared_ptr<server> get(const std::string& name) const noexcept;
+
+    /**
+     * Find a server from a JSON object.
+     *
+     * \pre json.is_object()
+     * \param json the JSON object
+     * \param key the server identifier property
+     * \throw server_error on errors
+     */
+    std::shared_ptr<server> require(const nlohmann::json& json, const std::string& key = "server");
+
+    /**
+     * Remove a server from the irccd instance.
+     *
+     * The server if any, will be disconnected.
+     *
+     * \param name the server name
+     */
+    void remove(const std::string& name);
+
+    /**
+     * Remove all servers.
+     *
+     * All servers will be disconnected.
+     */
+    void clear() noexcept;
+
+    /**
+     * Load servers from the configuration.
+     *
+     * \param cfg the config
+     */
+    void load(const config& cfg) noexcept;
+};
+
+} // !irccd
+
+#endif // !IRCCD_DAEMON_SERVER_SERVICE_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/daemon/service/transport_service.cpp	Wed Mar 07 17:49:56 2018 +0100
@@ -0,0 +1,284 @@
+/*
+ * transport_service.cpp -- transport service
+ *
+ * Copyright (c) 2013-2018 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 <irccd/sysconfig.hpp>
+
+#include <cassert>
+
+#include <irccd/string_util.hpp>
+
+#include <irccd/daemon/command.hpp>
+#include <irccd/daemon/ip_transport_server.hpp>
+#include <irccd/daemon/irccd.hpp>
+#include <irccd/daemon/logger.hpp>
+#include <irccd/daemon/transport_client.hpp>
+
+#include <irccd/daemon/service/transport_service.hpp>
+
+#if !defined(IRCCD_SYSTEM_WINDOWS)
+#   include <irccd/daemon/local_transport_server.hpp>
+#endif
+
+#if defined(HAVE_SSL)
+#   include <irccd/daemon/tls_transport_server.hpp>
+#endif
+
+namespace irccd {
+
+namespace {
+
+std::unique_ptr<transport_server> load_transport_ip(boost::asio::io_service& service, const ini::section& sc)
+{
+    assert(sc.key() == "transport");
+
+    std::unique_ptr<transport_server> transport;
+    ini::section::const_iterator it;
+
+    // Port.
+    if ((it = sc.find("port")) == sc.cend())
+        throw std::invalid_argument("missing 'port' parameter");
+
+    auto port = string_util::to_uint<std::uint16_t>(it->value());
+
+    // Address.
+    std::string address = "*";
+
+    if ((it = sc.find("address")) != sc.end())
+        address = it->value();
+
+    // 0011
+    //    ^ define IPv4
+    //   ^  define IPv6
+    auto mode = 1U;
+
+    /*
+     * Documentation stated family but code checked for 'domain' option.
+     *
+     * As irccdctl uses domain, accept both and unify the option name to 'family'.
+     *
+     * See #637
+     */
+    if ((it = sc.find("domain")) != sc.end() || (it = sc.find("family")) != sc.end()) {
+        mode = 0U;
+
+        for (const auto& v : *it) {
+            if (v == "ipv4")
+                mode |= (1U << 0);
+            if (v == "ipv6")
+                mode |= (1U << 1);
+        }
+    }
+
+    if (mode == 0U)
+        throw std::invalid_argument("family must at least have ipv4 or ipv6");
+
+    auto protocol = (mode & 0x2U)
+        ? boost::asio::ip::tcp::v4()
+        : boost::asio::ip::tcp::v6();
+
+    // Optional SSL.
+    std::string pkey;
+    std::string cert;
+
+    if ((it = sc.find("ssl")) != sc.end() && string_util::is_boolean(it->value())) {
+        if ((it = sc.find("certificate")) == sc.end())
+            throw std::invalid_argument("missing 'certificate' parameter");
+
+        cert = it->value();
+
+        if ((it = sc.find("key")) == sc.end())
+            throw std::invalid_argument("missing 'key' parameter");
+
+        pkey = it->value();
+    }
+
+    auto endpoint = (address == "*")
+        ? boost::asio::ip::tcp::endpoint(protocol, port)
+        : boost::asio::ip::tcp::endpoint(boost::asio::ip::address::from_string(address), port);
+
+    boost::asio::ip::tcp::acceptor acceptor(service, endpoint, true);
+
+    if (pkey.empty())
+        return std::make_unique<ip_transport_server>(std::move(acceptor));
+
+#if defined(HAVE_SSL)
+    boost::asio::ssl::context ctx(boost::asio::ssl::context::sslv23);
+
+    ctx.use_private_key_file(pkey, boost::asio::ssl::context::pem);
+    ctx.use_certificate_file(cert, boost::asio::ssl::context::pem);
+
+    return std::make_unique<tls_transport_server>(std::move(acceptor), std::move(ctx));
+#else
+    throw std::invalid_argument("SSL disabled");
+#endif
+}
+
+std::unique_ptr<transport_server> load_transport_unix(boost::asio::io_service& service, const ini::section& sc)
+{
+    using boost::asio::local::stream_protocol;
+
+    assert(sc.key() == "transport");
+
+#if !defined(IRCCD_SYSTEM_WINDOWS)
+    ini::section::const_iterator it = sc.find("path");
+
+    if (it == sc.end())
+        throw std::invalid_argument("missing 'path' parameter");
+
+    // Remove the file first.
+    std::remove(it->value().c_str());
+
+    stream_protocol::endpoint endpoint(it->value());
+    stream_protocol::acceptor acceptor(service, std::move(endpoint));
+
+    return std::make_unique<local_transport_server>(std::move(acceptor));
+#else
+    (void)sc;
+
+    throw std::invalid_argument("unix transports not supported on on this platform");
+#endif
+}
+
+std::unique_ptr<transport_server> load_transport(boost::asio::io_service& service, const ini::section& sc)
+{
+    assert(sc.key() == "transport");
+
+    std::unique_ptr<transport_server> transport;
+    ini::section::const_iterator it = sc.find("type");
+
+    if (it == sc.end())
+        throw std::invalid_argument("missing 'type' parameter");
+
+    if (it->value() == "ip")
+        transport = load_transport_ip(service, sc);
+    else if (it->value() == "unix")
+        transport = load_transport_unix(service, sc);
+    else
+        throw std::invalid_argument(string_util::sprintf("invalid type given: %s", it->value()));
+
+    if ((it = sc.find("password")) != sc.end())
+        transport->set_password(it->value());
+
+    return transport;
+}
+
+} // !namespace
+
+void transport_service::handle_command(std::shared_ptr<transport_client> tc, const nlohmann::json& object)
+{
+    assert(object.is_object());
+
+    auto name = object.find("command");
+
+    if (name == object.end() || !name->is_string()) {
+        tc->error(irccd_error::invalid_message);
+        return;
+    }
+
+    auto cmd = std::find_if(commands_.begin(), commands_.end(), [&] (const auto& cptr) {
+        return cptr->get_name() == name->template get<std::string>();
+    });
+
+    if (cmd == commands_.end())
+        tc->error(irccd_error::invalid_command, name->get<std::string>());
+    else {
+        try {
+            (*cmd)->exec(irccd_, *tc, object);
+        } catch (const boost::system::system_error& ex) {
+            tc->error(ex.code(), (*cmd)->get_name());
+        } catch (const std::exception& ex) {
+            irccd_.log().warning() << "transport: unknown error not reported" << std::endl;
+            irccd_.log().warning() << "transport: " << ex.what() << std::endl;
+        }
+    }
+}
+
+void transport_service::do_recv(std::shared_ptr<transport_client> tc)
+{
+    tc->recv([this, tc] (auto code, auto json) {
+        switch (code.value()) {
+        case boost::system::errc::network_down:
+            irccd_.log().warning("transport: client disconnected");
+            break;
+        case boost::system::errc::invalid_argument:
+            tc->error(irccd_error::invalid_message);
+            break;
+        default:
+            handle_command(tc, json);
+
+            if (tc->state() == transport_client::state_t::ready)
+                do_recv(std::move(tc));
+
+            break;
+        }
+    });
+}
+
+void transport_service::do_accept(transport_server& ts)
+{
+    ts.accept([this, &ts] (auto code, auto client) {
+        if (code)
+            irccd_.log().warning() << "transport: new client error: " << code.message() << std::endl;
+        else {
+            do_accept(ts);
+            do_recv(std::move(client));
+
+            irccd_.log().info() << "transport: new client connected" << std::endl;
+        }
+    });
+}
+
+transport_service::transport_service(irccd& irccd) noexcept
+    : irccd_(irccd)
+{
+}
+
+transport_service::~transport_service() noexcept = default;
+
+void transport_service::add(std::unique_ptr<transport_server> ts)
+{
+    assert(ts);
+
+    do_accept(*ts);
+    servers_.push_back(std::move(ts));
+}
+
+void transport_service::broadcast(const nlohmann::json& json)
+{
+    assert(json.is_object());
+
+    for (const auto& servers : servers_)
+        for (const auto& client : servers->clients())
+            client->send(json);
+}
+
+void transport_service::load(const config& cfg) noexcept
+{
+    for (const auto& section : cfg.doc()) {
+        if (section.key() != "transport")
+            continue;
+
+        try {
+            add(load_transport(irccd_.service(), section));
+        } catch (const std::exception& ex) {
+            irccd_.log().warning() << "transport: " << ex.what() << std::endl;
+        }
+    }
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/daemon/service/transport_service.hpp	Wed Mar 07 17:49:56 2018 +0100
@@ -0,0 +1,111 @@
+/*
+ * transport_service.hpp -- transport service
+ *
+ * Copyright (c) 2013-2018 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_DAEMON_TRANSPORT_SERVICE_HPP
+#define IRCCD_DAEMON_TRANSPORT_SERVICE_HPP
+
+#include <memory>
+#include <vector>
+
+#include <json.hpp>
+
+#include <irccd/daemon/transport_client.hpp>
+#include <irccd/daemon/transport_server.hpp>
+
+namespace irccd {
+
+class command;
+class config;
+
+/**
+ * \brief manage transport servers and clients.
+ * \ingroup services
+ */
+class transport_service {
+public:
+    using commands_t = std::vector<std::unique_ptr<command>>;
+    using servers_t = std::vector<std::unique_ptr<transport_server>>;
+
+private:
+    irccd& irccd_;
+    commands_t commands_;
+    servers_t servers_;
+
+    void handle_command(std::shared_ptr<transport_client>, const nlohmann::json&);
+    void do_recv(std::shared_ptr<transport_client>);
+    void do_accept(transport_server&);
+
+public:
+    /**
+     * Create the transport service.
+     *
+     * \param irccd the irccd instance
+     */
+    transport_service(irccd& irccd) noexcept;
+
+    /**
+     * Default destructor.
+     */
+    ~transport_service() noexcept;
+
+    /**
+     * Get underlying commands.
+     *
+     * \return the commands
+     */
+    inline const commands_t& get_commands() const noexcept
+    {
+        return commands_;
+    }
+
+    /**
+     * Get underlying commands.
+     *
+     * \return the commands
+     */
+    inline commands_t& get_commands() noexcept
+    {
+        return commands_;
+    }
+
+    /**
+     * Add a transport server.
+     *
+     * \param ts the transport server
+     */
+    void add(std::unique_ptr<transport_server> ts);
+
+    /**
+     * Send data to all clients.
+     *
+     * \pre object.is_object()
+     * \param object the json object
+     */
+    void broadcast(const nlohmann::json& object);
+
+    /**
+     * Load transports from the configuration.
+     *
+     * \param cfg the config
+     */
+    void load(const config& cfg) noexcept;
+};
+
+} // !irccd
+
+#endif // !IRCCD_DAEMON_TRANSPORT_SERVICE_HPP
--- a/libirccd/irccd/daemon/transport_service.cpp	Mon Jan 15 13:37:32 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,281 +0,0 @@
-/*
- * transport_service.cpp -- transport service
- *
- * Copyright (c) 2013-2018 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 <irccd/sysconfig.hpp>
-
-#include <cassert>
-
-#include <irccd/string_util.hpp>
-
-#include "command_service.hpp"
-#include "ip_transport_server.hpp"
-#include "irccd.hpp"
-#include "logger.hpp"
-#include "transport_client.hpp"
-#include "transport_service.hpp"
-
-#if !defined(IRCCD_SYSTEM_WINDOWS)
-#   include "local_transport_server.hpp"
-#endif
-
-#if defined(HAVE_SSL)
-#   include "tls_transport_server.hpp"
-#endif
-
-namespace irccd {
-
-namespace {
-
-std::unique_ptr<transport_server> load_transport_ip(boost::asio::io_service& service, const ini::section& sc)
-{
-    assert(sc.key() == "transport");
-
-    std::unique_ptr<transport_server> transport;
-    ini::section::const_iterator it;
-
-    // Port.
-    if ((it = sc.find("port")) == sc.cend())
-        throw std::invalid_argument("missing 'port' parameter");
-
-    auto port = string_util::to_uint<std::uint16_t>(it->value());
-
-    // Address.
-    std::string address = "*";
-
-    if ((it = sc.find("address")) != sc.end())
-        address = it->value();
-
-    // 0011
-    //    ^ define IPv4
-    //   ^  define IPv6
-    auto mode = 1U;
-
-    /*
-     * Documentation stated family but code checked for 'domain' option.
-     *
-     * As irccdctl uses domain, accept both and unify the option name to 'family'.
-     *
-     * See #637
-     */
-    if ((it = sc.find("domain")) != sc.end() || (it = sc.find("family")) != sc.end()) {
-        mode = 0U;
-
-        for (const auto& v : *it) {
-            if (v == "ipv4")
-                mode |= (1U << 0);
-            if (v == "ipv6")
-                mode |= (1U << 1);
-        }
-    }
-
-    if (mode == 0U)
-        throw std::invalid_argument("family must at least have ipv4 or ipv6");
-
-    auto protocol = (mode & 0x2U)
-        ? boost::asio::ip::tcp::v4()
-        : boost::asio::ip::tcp::v6();
-
-    // Optional SSL.
-    std::string pkey;
-    std::string cert;
-
-    if ((it = sc.find("ssl")) != sc.end() && string_util::is_boolean(it->value())) {
-        if ((it = sc.find("certificate")) == sc.end())
-            throw std::invalid_argument("missing 'certificate' parameter");
-
-        cert = it->value();
-
-        if ((it = sc.find("key")) == sc.end())
-            throw std::invalid_argument("missing 'key' parameter");
-
-        pkey = it->value();
-    }
-
-    auto endpoint = (address == "*")
-        ? boost::asio::ip::tcp::endpoint(protocol, port)
-        : boost::asio::ip::tcp::endpoint(boost::asio::ip::address::from_string(address), port);
-
-    boost::asio::ip::tcp::acceptor acceptor(service, endpoint, true);
-
-    if (pkey.empty())
-        return std::make_unique<ip_transport_server>(std::move(acceptor));
-
-#if defined(HAVE_SSL)
-    boost::asio::ssl::context ctx(boost::asio::ssl::context::sslv23);
-
-    ctx.use_private_key_file(pkey, boost::asio::ssl::context::pem);
-    ctx.use_certificate_file(cert, boost::asio::ssl::context::pem);
-
-    return std::make_unique<tls_transport_server>(std::move(acceptor), std::move(ctx));
-#else
-    throw std::invalid_argument("SSL disabled");
-#endif
-}
-
-std::unique_ptr<transport_server> load_transport_unix(boost::asio::io_service& service, const ini::section& sc)
-{
-    using boost::asio::local::stream_protocol;
-
-    assert(sc.key() == "transport");
-
-#if !defined(IRCCD_SYSTEM_WINDOWS)
-    ini::section::const_iterator it = sc.find("path");
-
-    if (it == sc.end())
-        throw std::invalid_argument("missing 'path' parameter");
-
-    // Remove the file first.
-    std::remove(it->value().c_str());
-
-    stream_protocol::endpoint endpoint(it->value());
-    stream_protocol::acceptor acceptor(service, std::move(endpoint));
-
-    return std::make_unique<local_transport_server>(std::move(acceptor));
-#else
-    (void)sc;
-
-    throw std::invalid_argument("unix transports not supported on on this platform");
-#endif
-}
-
-std::unique_ptr<transport_server> load_transport(boost::asio::io_service& service, const ini::section& sc)
-{
-    assert(sc.key() == "transport");
-
-    std::unique_ptr<transport_server> transport;
-    ini::section::const_iterator it = sc.find("type");
-
-    if (it == sc.end())
-        throw std::invalid_argument("missing 'type' parameter");
-
-    if (it->value() == "ip")
-        transport = load_transport_ip(service, sc);
-    else if (it->value() == "unix")
-        transport = load_transport_unix(service, sc);
-    else
-        throw std::invalid_argument(string_util::sprintf("invalid type given: %s", it->value()));
-
-    if ((it = sc.find("password")) != sc.end())
-        transport->set_password(it->value());
-
-    return transport;
-}
-
-} // !namespace
-
-void transport_service::handle_command(std::shared_ptr<transport_client> tc, const nlohmann::json& object)
-{
-    assert(object.is_object());
-
-    auto name = object.find("command");
-
-    if (name == object.end() || !name->is_string()) {
-        tc->error(irccd_error::invalid_message);
-        return;
-    }
-
-    auto cmd = irccd_.commands().find(*name);
-
-    if (!cmd)
-        tc->error(irccd_error::invalid_command, name->get<std::string>());
-    else {
-        try {
-            cmd->exec(irccd_, *tc, object);
-        } catch (const boost::system::system_error& ex) {
-            tc->error(ex.code(), cmd->get_name());
-        } catch (const std::exception& ex) {
-            irccd_.log().warning() << "transport: unknown error not reported" << std::endl;
-            irccd_.log().warning() << "transport: " << ex.what() << std::endl;
-        }
-    }
-}
-
-void transport_service::do_recv(std::shared_ptr<transport_client> tc)
-{
-    tc->recv([this, tc] (auto code, auto json) {
-        switch (code.value()) {
-        case boost::system::errc::network_down:
-            irccd_.log().warning("transport: client disconnected");
-            break;
-            case boost::system::errc::invalid_argument:
-            tc->error(irccd_error::invalid_message);
-            break;
-        default:
-            handle_command(tc, json);
-
-            if (tc->state() == transport_client::state_t::ready)
-                do_recv(std::move(tc));
-
-            break;
-        }
-    });
-}
-
-void transport_service::do_accept(transport_server& ts)
-{
-    ts.accept([this, &ts] (auto code, auto client) {
-        if (code)
-            irccd_.log().warning() << "transport: new client error: " << code.message() << std::endl;
-        else {
-            do_accept(ts);
-            do_recv(std::move(client));
-
-            irccd_.log().info() << "transport: new client connected" << std::endl;
-        }
-    });
-}
-
-transport_service::transport_service(irccd& irccd) noexcept
-    : irccd_(irccd)
-{
-}
-
-transport_service::~transport_service() noexcept = default;
-
-void transport_service::add(std::unique_ptr<transport_server> ts)
-{
-    assert(ts);
-
-    do_accept(*ts);
-    servers_.push_back(std::move(ts));
-}
-
-void transport_service::broadcast(const nlohmann::json& json)
-{
-    assert(json.is_object());
-
-    for (const auto& servers : servers_)
-        for (const auto& client : servers->clients())
-            client->send(json);
-}
-
-void transport_service::load(const config& cfg) noexcept
-{
-    for (const auto& section : cfg.doc()) {
-        if (section.key() != "transport")
-            continue;
-
-        try {
-            add(load_transport(irccd_.service(), section));
-        } catch (const std::exception& ex) {
-            irccd_.log().warning() << "transport: " << ex.what() << std::endl;
-        }
-    }
-}
-
-} // !irccd
--- a/libirccd/irccd/daemon/transport_service.hpp	Mon Jan 15 13:37:32 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,88 +0,0 @@
-/*
- * transport_service.hpp -- transport service
- *
- * Copyright (c) 2013-2018 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_DAEMON_TRANSPORT_SERVICE_HPP
-#define IRCCD_DAEMON_TRANSPORT_SERVICE_HPP
-
-#include <memory>
-#include <vector>
-
-#include <json.hpp>
-
-#include "transport_client.hpp"
-#include "transport_server.hpp"
-
-namespace irccd {
-
-class config;
-
-/**
- * \brief manage transport servers and clients.
- * \ingroup services
- */
-class transport_service {
-public:
-    using servers_t = std::vector<std::unique_ptr<transport_server>>;
-
-private:
-    irccd& irccd_;
-    servers_t servers_;
-
-    void handle_command(std::shared_ptr<transport_client>, const nlohmann::json&);
-    void do_recv(std::shared_ptr<transport_client>);
-    void do_accept(transport_server&);
-
-public:
-    /**
-     * Create the transport service.
-     *
-     * \param irccd the irccd instance
-     */
-    transport_service(irccd& irccd) noexcept;
-
-    /**
-     * Default destructor.
-     */
-    ~transport_service() noexcept;
-
-    /**
-     * Add a transport server.
-     *
-     * \param ts the transport server
-     */
-    void add(std::unique_ptr<transport_server> ts);
-
-    /**
-     * Send data to all clients.
-     *
-     * \pre object.is_object()
-     * \param object the json object
-     */
-    void broadcast(const nlohmann::json& object);
-
-    /**
-     * Load transports from the configuration.
-     *
-     * \param cfg the config
-     */
-    void load(const config& cfg) noexcept;
-};
-
-} // !irccd
-
-#endif // !IRCCD_DAEMON_TRANSPORT_SERVICE_HPP
--- a/tests/src/libirccd-js/js-plugin/main.cpp	Mon Jan 15 13:37:32 2018 +0100
+++ b/tests/src/libirccd-js/js-plugin/main.cpp	Wed Mar 07 17:49:56 2018 +0100
@@ -21,7 +21,7 @@
 #include <boost/test/unit_test.hpp>
 
 #include <irccd/daemon/irccd.hpp>
-#include <irccd/daemon/plugin_service.hpp>
+#include <irccd/daemon/service/plugin_service.hpp>
 
 #include <irccd/js/irccd_jsapi.hpp>
 #include <irccd/js/js_plugin.hpp>
--- a/tests/src/libirccd/command-plugin-config/main.cpp	Mon Jan 15 13:37:32 2018 +0100
+++ b/tests/src/libirccd/command-plugin-config/main.cpp	Wed Mar 07 17:49:56 2018 +0100
@@ -20,7 +20,7 @@
 #include <boost/test/unit_test.hpp>
 
 #include <irccd/daemon/plugin_config_command.hpp>
-#include <irccd/daemon/plugin_service.hpp>
+#include <irccd/daemon/service/plugin_service.hpp>
 
 #include <irccd/test/command_test.hpp>
 
--- a/tests/src/libirccd/command-plugin-info/main.cpp	Mon Jan 15 13:37:32 2018 +0100
+++ b/tests/src/libirccd/command-plugin-info/main.cpp	Wed Mar 07 17:49:56 2018 +0100
@@ -20,7 +20,7 @@
 #include <boost/test/unit_test.hpp>
 
 #include <irccd/daemon/plugin_info_command.hpp>
-#include <irccd/daemon/plugin_service.hpp>
+#include <irccd/daemon/service/plugin_service.hpp>
 
 #include <irccd/test/command_test.hpp>
 
--- a/tests/src/libirccd/command-plugin-list/main.cpp	Mon Jan 15 13:37:32 2018 +0100
+++ b/tests/src/libirccd/command-plugin-list/main.cpp	Wed Mar 07 17:49:56 2018 +0100
@@ -20,7 +20,7 @@
 #include <boost/test/unit_test.hpp>
 
 #include <irccd/daemon/plugin_list_command.hpp>
-#include <irccd/daemon/plugin_service.hpp>
+#include <irccd/daemon/service/plugin_service.hpp>
 
 #include <irccd/test/command_test.hpp>
 
--- a/tests/src/libirccd/command-plugin-load/main.cpp	Mon Jan 15 13:37:32 2018 +0100
+++ b/tests/src/libirccd/command-plugin-load/main.cpp	Wed Mar 07 17:49:56 2018 +0100
@@ -20,7 +20,7 @@
 #include <boost/test/unit_test.hpp>
 
 #include <irccd/daemon/plugin_load_command.hpp>
-#include <irccd/daemon/plugin_service.hpp>
+#include <irccd/daemon/service/plugin_service.hpp>
 
 #include <irccd/test/command_test.hpp>
 
--- a/tests/src/libirccd/command-plugin-reload/main.cpp	Mon Jan 15 13:37:32 2018 +0100
+++ b/tests/src/libirccd/command-plugin-reload/main.cpp	Wed Mar 07 17:49:56 2018 +0100
@@ -20,7 +20,7 @@
 #include <boost/test/unit_test.hpp>
 
 #include <irccd/daemon/plugin_reload_command.hpp>
-#include <irccd/daemon/plugin_service.hpp>
+#include <irccd/daemon/service/plugin_service.hpp>
 
 #include <irccd/test/command_test.hpp>
 
--- a/tests/src/libirccd/command-plugin-unload/main.cpp	Mon Jan 15 13:37:32 2018 +0100
+++ b/tests/src/libirccd/command-plugin-unload/main.cpp	Wed Mar 07 17:49:56 2018 +0100
@@ -19,8 +19,8 @@
 #define BOOST_TEST_MODULE "plugin-unload"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/daemon/plugin_service.hpp>
 #include <irccd/daemon/plugin_unload_command.hpp>
+#include <irccd/daemon/service/plugin_service.hpp>
 
 #include <irccd/test/command_test.hpp>
 
--- a/tests/src/libirccd/command-rule-add/main.cpp	Mon Jan 15 13:37:32 2018 +0100
+++ b/tests/src/libirccd/command-rule-add/main.cpp	Wed Mar 07 17:49:56 2018 +0100
@@ -23,7 +23,7 @@
 
 #include <irccd/daemon/rule_add_command.hpp>
 #include <irccd/daemon/rule_list_command.hpp>
-#include <irccd/daemon/rule_service.hpp>
+#include <irccd/daemon/service/rule_service.hpp>
 
 #include <irccd/test/command_test.hpp>
 
--- a/tests/src/libirccd/command-rule-edit/main.cpp	Mon Jan 15 13:37:32 2018 +0100
+++ b/tests/src/libirccd/command-rule-edit/main.cpp	Wed Mar 07 17:49:56 2018 +0100
@@ -23,7 +23,7 @@
 
 #include <irccd/daemon/rule_edit_command.hpp>
 #include <irccd/daemon/rule_info_command.hpp>
-#include <irccd/daemon/rule_service.hpp>
+#include <irccd/daemon/service/rule_service.hpp>
 
 #include <irccd/test/command_test.hpp>
 
--- a/tests/src/libirccd/command-rule-info/main.cpp	Mon Jan 15 13:37:32 2018 +0100
+++ b/tests/src/libirccd/command-rule-info/main.cpp	Wed Mar 07 17:49:56 2018 +0100
@@ -22,7 +22,7 @@
 #include <irccd/json_util.hpp>
 
 #include <irccd/daemon/rule_info_command.hpp>
-#include <irccd/daemon/rule_service.hpp>
+#include <irccd/daemon/service/rule_service.hpp>
 
 #include <irccd/test/command_test.hpp>
 
--- a/tests/src/libirccd/command-rule-list/main.cpp	Mon Jan 15 13:37:32 2018 +0100
+++ b/tests/src/libirccd/command-rule-list/main.cpp	Wed Mar 07 17:49:56 2018 +0100
@@ -22,7 +22,7 @@
 #include <irccd/json_util.hpp>
 
 #include <irccd/daemon/rule_list_command.hpp>
-#include <irccd/daemon/rule_service.hpp>
+#include <irccd/daemon/service/rule_service.hpp>
 
 #include <irccd/test/command_test.hpp>
 
--- a/tests/src/libirccd/command-rule-move/main.cpp	Mon Jan 15 13:37:32 2018 +0100
+++ b/tests/src/libirccd/command-rule-move/main.cpp	Wed Mar 07 17:49:56 2018 +0100
@@ -23,7 +23,7 @@
 
 #include <irccd/daemon/rule_list_command.hpp>
 #include <irccd/daemon/rule_move_command.hpp>
-#include <irccd/daemon/rule_service.hpp>
+#include <irccd/daemon/service/rule_service.hpp>
 
 #include <irccd/test/command_test.hpp>
 
--- a/tests/src/libirccd/command-rule-remove/main.cpp	Mon Jan 15 13:37:32 2018 +0100
+++ b/tests/src/libirccd/command-rule-remove/main.cpp	Wed Mar 07 17:49:56 2018 +0100
@@ -23,7 +23,7 @@
 
 #include <irccd/daemon/rule_remove_command.hpp>
 #include <irccd/daemon/rule_list_command.hpp>
-#include <irccd/daemon/rule_service.hpp>
+#include <irccd/daemon/service/rule_service.hpp>
 
 #include <irccd/test/command_test.hpp>
 
--- a/tests/src/libirccd/command-server-connect/main.cpp	Mon Jan 15 13:37:32 2018 +0100
+++ b/tests/src/libirccd/command-server-connect/main.cpp	Wed Mar 07 17:49:56 2018 +0100
@@ -19,8 +19,8 @@
 #define BOOST_TEST_MODULE "server-connect"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/daemon/server_service.hpp>
 #include <irccd/daemon/server_connect_command.hpp>
+#include <irccd/daemon/service/server_service.hpp>
 
 #include <irccd/test/command_test.hpp>
 #include <irccd/test/journal_server.hpp>
--- a/tests/src/libirccd/command-server-disconnect/main.cpp	Mon Jan 15 13:37:32 2018 +0100
+++ b/tests/src/libirccd/command-server-disconnect/main.cpp	Wed Mar 07 17:49:56 2018 +0100
@@ -20,7 +20,7 @@
 #include <boost/test/unit_test.hpp>
 
 #include <irccd/daemon/server_disconnect_command.hpp>
-#include <irccd/daemon/server_service.hpp>
+#include <irccd/daemon/service/server_service.hpp>
 
 #include <irccd/test/journal_server.hpp>
 #include <irccd/test/command_test.hpp>
--- a/tests/src/libirccd/command-server-info/main.cpp	Mon Jan 15 13:37:32 2018 +0100
+++ b/tests/src/libirccd/command-server-info/main.cpp	Wed Mar 07 17:49:56 2018 +0100
@@ -20,7 +20,7 @@
 #include <boost/test/unit_test.hpp>
 
 #include <irccd/daemon/server_info_command.hpp>
-#include <irccd/daemon/server_service.hpp>
+#include <irccd/daemon/service/server_service.hpp>
 
 #include <irccd/test/command_test.hpp>
 #include <irccd/test/journal_server.hpp>
--- a/tests/src/libirccd/command-server-invite/main.cpp	Mon Jan 15 13:37:32 2018 +0100
+++ b/tests/src/libirccd/command-server-invite/main.cpp	Wed Mar 07 17:49:56 2018 +0100
@@ -20,7 +20,7 @@
 #include <boost/test/unit_test.hpp>
 
 #include <irccd/daemon/server_invite_command.hpp>
-#include <irccd/daemon/server_service.hpp>
+#include <irccd/daemon/service/server_service.hpp>
 
 #include <irccd/test/command_test.hpp>
 #include <irccd/test/journal_server.hpp>
--- a/tests/src/libirccd/command-server-join/main.cpp	Mon Jan 15 13:37:32 2018 +0100
+++ b/tests/src/libirccd/command-server-join/main.cpp	Wed Mar 07 17:49:56 2018 +0100
@@ -20,7 +20,7 @@
 #include <boost/test/unit_test.hpp>
 
 #include <irccd/daemon/server_join_command.hpp>
-#include <irccd/daemon/server_service.hpp>
+#include <irccd/daemon/service/server_service.hpp>
 
 #include <irccd/test/command_test.hpp>
 #include <irccd/test/journal_server.hpp>
--- a/tests/src/libirccd/command-server-kick/main.cpp	Mon Jan 15 13:37:32 2018 +0100
+++ b/tests/src/libirccd/command-server-kick/main.cpp	Wed Mar 07 17:49:56 2018 +0100
@@ -19,8 +19,8 @@
 #define BOOST_TEST_MODULE "server-kick"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/daemon/server_service.hpp>
 #include <irccd/daemon/server_kick_command.hpp>
+#include <irccd/daemon/service/server_service.hpp>
 
 #include <irccd/test/command_test.hpp>
 #include <irccd/test/journal_server.hpp>
--- a/tests/src/libirccd/command-server-list/main.cpp	Mon Jan 15 13:37:32 2018 +0100
+++ b/tests/src/libirccd/command-server-list/main.cpp	Wed Mar 07 17:49:56 2018 +0100
@@ -19,8 +19,8 @@
 #define BOOST_TEST_MODULE "server-list"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/daemon/server_service.hpp>
 #include <irccd/daemon/server_list_command.hpp>
+#include <irccd/daemon/service/server_service.hpp>
 
 #include <irccd/test/command_test.hpp>
 #include <irccd/test/journal_server.hpp>
--- a/tests/src/libirccd/command-server-me/main.cpp	Mon Jan 15 13:37:32 2018 +0100
+++ b/tests/src/libirccd/command-server-me/main.cpp	Wed Mar 07 17:49:56 2018 +0100
@@ -19,8 +19,8 @@
 #define BOOST_TEST_MODULE "server-me"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/daemon/server_service.hpp>
 #include <irccd/daemon/server_me_command.hpp>
+#include <irccd/daemon/service/server_service.hpp>
 
 #include <irccd/test/command_test.hpp>
 #include <irccd/test/journal_server.hpp>
--- a/tests/src/libirccd/command-server-message/main.cpp	Mon Jan 15 13:37:32 2018 +0100
+++ b/tests/src/libirccd/command-server-message/main.cpp	Wed Mar 07 17:49:56 2018 +0100
@@ -19,8 +19,8 @@
 #define BOOST_TEST_MODULE "server-message"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/daemon/server_service.hpp>
 #include <irccd/daemon/server_message_command.hpp>
+#include <irccd/daemon/service/server_service.hpp>
 
 #include <irccd/test/command_test.hpp>
 #include <irccd/test/journal_server.hpp>
--- a/tests/src/libirccd/command-server-mode/main.cpp	Mon Jan 15 13:37:32 2018 +0100
+++ b/tests/src/libirccd/command-server-mode/main.cpp	Wed Mar 07 17:49:56 2018 +0100
@@ -19,8 +19,8 @@
 #define BOOST_TEST_MODULE "server-mode"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/daemon/server_service.hpp>
 #include <irccd/daemon/server_mode_command.hpp>
+#include <irccd/daemon/service/server_service.hpp>
 
 #include <irccd/test/command_test.hpp>
 #include <irccd/test/journal_server.hpp>
--- a/tests/src/libirccd/command-server-nick/main.cpp	Mon Jan 15 13:37:32 2018 +0100
+++ b/tests/src/libirccd/command-server-nick/main.cpp	Wed Mar 07 17:49:56 2018 +0100
@@ -19,8 +19,8 @@
 #define BOOST_TEST_MODULE "server-nick"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/daemon/server_service.hpp>
 #include <irccd/daemon/server_nick_command.hpp>
+#include <irccd/daemon/service/server_service.hpp>
 
 #include <irccd/test/command_test.hpp>
 #include <irccd/test/journal_server.hpp>
--- a/tests/src/libirccd/command-server-notice/main.cpp	Mon Jan 15 13:37:32 2018 +0100
+++ b/tests/src/libirccd/command-server-notice/main.cpp	Wed Mar 07 17:49:56 2018 +0100
@@ -19,8 +19,8 @@
 #define BOOST_TEST_MODULE "server-notice"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/daemon/server_service.hpp>
 #include <irccd/daemon/server_notice_command.hpp>
+#include <irccd/daemon/service/server_service.hpp>
 
 #include <irccd/test/command_test.hpp>
 #include <irccd/test/journal_server.hpp>
--- a/tests/src/libirccd/command-server-part/main.cpp	Mon Jan 15 13:37:32 2018 +0100
+++ b/tests/src/libirccd/command-server-part/main.cpp	Wed Mar 07 17:49:56 2018 +0100
@@ -19,8 +19,8 @@
 #define BOOST_TEST_MODULE "server-part"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/daemon/server_service.hpp>
 #include <irccd/daemon/server_part_command.hpp>
+#include <irccd/daemon/service/server_service.hpp>
 
 #include <irccd/test/command_test.hpp>
 #include <irccd/test/journal_server.hpp>
--- a/tests/src/libirccd/command-server-reconnect/main.cpp	Mon Jan 15 13:37:32 2018 +0100
+++ b/tests/src/libirccd/command-server-reconnect/main.cpp	Wed Mar 07 17:49:56 2018 +0100
@@ -19,8 +19,8 @@
 #define BOOST_TEST_MODULE "server-reconnect"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/daemon/server_service.hpp>
 #include <irccd/daemon/server_reconnect_command.hpp>
+#include <irccd/daemon/service/server_service.hpp>
 
 #include <irccd/test/command_test.hpp>
 #include <irccd/test/journal_server.hpp>
--- a/tests/src/libirccd/command-server-topic/main.cpp	Mon Jan 15 13:37:32 2018 +0100
+++ b/tests/src/libirccd/command-server-topic/main.cpp	Wed Mar 07 17:49:56 2018 +0100
@@ -19,8 +19,8 @@
 #define BOOST_TEST_MODULE "server-topic"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/daemon/server_service.hpp>
 #include <irccd/daemon/server_topic_command.hpp>
+#include <irccd/daemon/service/server_service.hpp>
 
 #include <irccd/test/command_test.hpp>
 #include <irccd/test/journal_server.hpp>
--- a/tests/src/libirccd/rules/main.cpp	Mon Jan 15 13:37:32 2018 +0100
+++ b/tests/src/libirccd/rules/main.cpp	Wed Mar 07 17:49:56 2018 +0100
@@ -21,7 +21,7 @@
 
 #include <irccd/daemon/irccd.hpp>
 #include <irccd/daemon/logger.hpp>
-#include <irccd/daemon/rule_service.hpp>
+#include <irccd/daemon/service/rule_service.hpp>
 
 namespace irccd {
 
--- a/tests/src/plugins/plugin/main.cpp	Mon Jan 15 13:37:32 2018 +0100
+++ b/tests/src/plugins/plugin/main.cpp	Wed Mar 07 17:49:56 2018 +0100
@@ -22,8 +22,8 @@
 #include <irccd/string_util.hpp>
 
 #include <irccd/daemon/irccd.hpp>
-#include <irccd/daemon/plugin_service.hpp>
 #include <irccd/daemon/server.hpp>
+#include <irccd/daemon/service/plugin_service.hpp>
 
 #include <irccd/test/plugin_test.hpp>