changeset 754:c216d148558d

Irccd: remove service directory
author David Demelier <markand@malikania.fr>
date Sun, 05 Aug 2018 15:47:10 +0200
parents 6d09b5fc82e8
children 580b030c875f
files irccd-test/main.cpp irccd/main.cpp irccdctl/cli.cpp libirccd-js/irccd/js/logger_jsapi.cpp libirccd-js/irccd/js/plugin_jsapi.cpp libirccd-js/irccd/js/server_jsapi.cpp libirccd-js/irccd/js/timer_jsapi.cpp libirccd-test/irccd/test/cli_test.cpp libirccd-test/irccd/test/command_test.hpp libirccd-test/irccd/test/plugin_cli_test.cpp libirccd-test/irccd/test/plugin_cli_test.hpp libirccd-test/irccd/test/plugin_test.cpp libirccd-test/irccd/test/rule_cli_test.cpp libirccd-test/irccd/test/rule_cli_test.hpp libirccd-test/irccd/test/server_cli_test.cpp libirccd-test/irccd/test/server_cli_test.hpp libirccd/CMakeLists.txt libirccd/irccd/daemon/command.cpp libirccd/irccd/daemon/irccd.cpp libirccd/irccd/daemon/plugin_service.cpp libirccd/irccd/daemon/plugin_service.hpp libirccd/irccd/daemon/rule_service.cpp libirccd/irccd/daemon/rule_service.hpp libirccd/irccd/daemon/rule_util.cpp libirccd/irccd/daemon/server_service.cpp libirccd/irccd/daemon/server_service.hpp libirccd/irccd/daemon/server_util.cpp libirccd/irccd/daemon/server_util.hpp 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_server.cpp libirccd/irccd/daemon/transport_server.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 tests/src/plugins/tictactoe/main.cpp
diffstat 71 files changed, 2022 insertions(+), 2031 deletions(-) [+]
line wrap: on
line diff
--- a/irccd-test/main.cpp	Sun Aug 05 12:14:16 2018 +0200
+++ b/irccd-test/main.cpp	Sun Aug 05 15:47:10 2018 +0200
@@ -37,9 +37,8 @@
 
 #include <irccd/daemon/dynlib_plugin.hpp>
 #include <irccd/daemon/irccd.hpp>
-
-#include <irccd/daemon/service/plugin_service.hpp>
-#include <irccd/daemon/service/server_service.hpp>
+#include <irccd/daemon/plugin_service.hpp>
+#include <irccd/daemon/server_service.hpp>
 
 #include <irccd/test/debug_server.hpp>
 
--- a/irccd/main.cpp	Sun Aug 05 12:14:16 2018 +0200
+++ b/irccd/main.cpp	Sun Aug 05 15:47:10 2018 +0200
@@ -30,11 +30,10 @@
 #include <irccd/daemon/dynlib_plugin.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>
+#include <irccd/daemon/plugin_service.hpp>
+#include <irccd/daemon/rule_service.hpp>
+#include <irccd/daemon/server_service.hpp>
+#include <irccd/daemon/transport_service.hpp>
 
 #if defined(IRCCD_HAVE_JS)
 #   include <irccd/js/js_plugin.hpp>
--- a/irccdctl/cli.cpp	Sun Aug 05 12:14:16 2018 +0200
+++ b/irccdctl/cli.cpp	Sun Aug 05 15:47:10 2018 +0200
@@ -24,7 +24,7 @@
 
 #include <irccd/ctl/controller.hpp>
 
-#include <irccd/daemon/service/rule_service.hpp>
+#include <irccd/daemon/rule_service.hpp>
 
 #include "cli.hpp"
 
--- a/libirccd-js/irccd/js/logger_jsapi.cpp	Sun Aug 05 12:14:16 2018 +0200
+++ b/libirccd-js/irccd/js/logger_jsapi.cpp	Sun Aug 05 15:47:10 2018 +0200
@@ -18,7 +18,7 @@
 
 #include <irccd/daemon/irccd.hpp>
 #include <irccd/daemon/logger.hpp>
-#include <irccd/daemon/service/plugin_service.hpp>
+#include <irccd/daemon/plugin_service.hpp>
 
 #include "irccd_jsapi.hpp"
 #include "js_plugin.hpp"
--- a/libirccd-js/irccd/js/plugin_jsapi.cpp	Sun Aug 05 12:14:16 2018 +0200
+++ b/libirccd-js/irccd/js/plugin_jsapi.cpp	Sun Aug 05 15:47:10 2018 +0200
@@ -17,8 +17,7 @@
  */
 
 #include <irccd/daemon/irccd.hpp>
-
-#include <irccd/daemon/service/plugin_service.hpp>
+#include <irccd/daemon/plugin_service.hpp>
 
 #include "irccd_jsapi.hpp"
 #include "js_plugin.hpp"
--- a/libirccd-js/irccd/js/server_jsapi.cpp	Sun Aug 05 12:14:16 2018 +0200
+++ b/libirccd-js/irccd/js/server_jsapi.cpp	Sun Aug 05 15:47:10 2018 +0200
@@ -21,10 +21,9 @@
 #include <unordered_map>
 
 #include <irccd/daemon/irccd.hpp>
+#include <irccd/daemon/server_service.hpp>
 #include <irccd/daemon/server_util.hpp>
 
-#include <irccd/daemon/service/server_service.hpp>
-
 #include "duktape_vector.hpp"
 #include "irccd_jsapi.hpp"
 #include "js_plugin.hpp"
--- a/libirccd-js/irccd/js/timer_jsapi.cpp	Sun Aug 05 12:14:16 2018 +0200
+++ b/libirccd-js/irccd/js/timer_jsapi.cpp	Sun Aug 05 15:47:10 2018 +0200
@@ -20,7 +20,7 @@
 
 #include <irccd/daemon/irccd.hpp>
 #include <irccd/daemon/logger.hpp>
-#include <irccd/daemon/service/plugin_service.hpp>
+#include <irccd/daemon/plugin_service.hpp>
 
 #include "irccd_jsapi.hpp"
 #include "js_plugin.hpp"
--- a/libirccd-test/irccd/test/cli_test.cpp	Sun Aug 05 12:14:16 2018 +0200
+++ b/libirccd-test/irccd/test/cli_test.cpp	Sun Aug 05 15:47:10 2018 +0200
@@ -22,7 +22,8 @@
 #include <irccd/string_util.hpp>
 #include <irccd/socket_acceptor.hpp>
 
-#include <irccd/daemon/service/transport_service.hpp>
+#include <irccd/daemon/transport_service.hpp>
+#include <irccd/daemon/transport_server.hpp>
 
 #include "cli_test.hpp"
 
--- a/libirccd-test/irccd/test/command_test.hpp	Sun Aug 05 12:14:16 2018 +0200
+++ b/libirccd-test/irccd/test/command_test.hpp	Sun Aug 05 15:47:10 2018 +0200
@@ -27,7 +27,8 @@
 #include <irccd/daemon/command.hpp>
 #include <irccd/daemon/irccd.hpp>
 #include <irccd/daemon/logger.hpp>
-#include <irccd/daemon/service/transport_service.hpp>
+#include <irccd/daemon/transport_server.hpp>
+#include <irccd/daemon/transport_service.hpp>
 
 #include <irccd/ctl/controller.hpp>
 
--- a/libirccd-test/irccd/test/plugin_cli_test.cpp	Sun Aug 05 12:14:16 2018 +0200
+++ b/libirccd-test/irccd/test/plugin_cli_test.cpp	Sun Aug 05 15:47:10 2018 +0200
@@ -16,8 +16,8 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
-#include <irccd/daemon/service/transport_service.hpp>
 #include <irccd/daemon/command.hpp>
+#include <irccd/daemon/transport_service.hpp>
 
 #include "plugin_cli_test.hpp"
 
--- a/libirccd-test/irccd/test/plugin_cli_test.hpp	Sun Aug 05 12:14:16 2018 +0200
+++ b/libirccd-test/irccd/test/plugin_cli_test.hpp	Sun Aug 05 15:47:10 2018 +0200
@@ -24,9 +24,8 @@
  * \brief Test fixture for irccdctl frontend (plugins support).
  */
 
-#include <irccd/daemon/service/plugin_service.hpp>
-
 #include <irccd/daemon/plugin.hpp>
+#include <irccd/daemon/plugin_service.hpp>
 
 #include "cli_test.hpp"
 
--- a/libirccd-test/irccd/test/plugin_test.cpp	Sun Aug 05 12:14:16 2018 +0200
+++ b/libirccd-test/irccd/test/plugin_test.cpp	Sun Aug 05 15:47:10 2018 +0200
@@ -19,9 +19,8 @@
 #include <cassert>
 
 #include <irccd/daemon/logger.hpp>
-
-#include <irccd/daemon/service/plugin_service.hpp>
-#include <irccd/daemon/service/server_service.hpp>
+#include <irccd/daemon/plugin_service.hpp>
+#include <irccd/daemon/server_service.hpp>
 
 #include <irccd/js/directory_jsapi.hpp>
 #include <irccd/js/elapsed_timer_jsapi.hpp>
--- a/libirccd-test/irccd/test/rule_cli_test.cpp	Sun Aug 05 12:14:16 2018 +0200
+++ b/libirccd-test/irccd/test/rule_cli_test.cpp	Sun Aug 05 15:47:10 2018 +0200
@@ -16,8 +16,8 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
-#include <irccd/daemon/service/transport_service.hpp>
 #include <irccd/daemon/command.hpp>
+#include <irccd/daemon/transport_service.hpp>
 
 #include "rule_cli_test.hpp"
 
--- a/libirccd-test/irccd/test/rule_cli_test.hpp	Sun Aug 05 12:14:16 2018 +0200
+++ b/libirccd-test/irccd/test/rule_cli_test.hpp	Sun Aug 05 15:47:10 2018 +0200
@@ -24,9 +24,8 @@
  * \brief Test fixture for irccdctl frontend (rule support).
  */
 
-#include <irccd/daemon/service/rule_service.hpp>
-
 #include <irccd/daemon/rule.hpp>
+#include <irccd/daemon/rule_service.hpp>
 
 #include "cli_test.hpp"
 
--- a/libirccd-test/irccd/test/server_cli_test.cpp	Sun Aug 05 12:14:16 2018 +0200
+++ b/libirccd-test/irccd/test/server_cli_test.cpp	Sun Aug 05 15:47:10 2018 +0200
@@ -16,8 +16,8 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
-#include <irccd/daemon/service/transport_service.hpp>
 #include <irccd/daemon/command.hpp>
+#include <irccd/daemon/transport_service.hpp>
 
 #include "server_cli_test.hpp"
 
--- a/libirccd-test/irccd/test/server_cli_test.hpp	Sun Aug 05 12:14:16 2018 +0200
+++ b/libirccd-test/irccd/test/server_cli_test.hpp	Sun Aug 05 15:47:10 2018 +0200
@@ -24,7 +24,7 @@
  * \brief Test fixture for irccdctl frontend (server support).
  */
 
-#include <irccd/daemon/service/server_service.hpp>
+#include <irccd/daemon/server_service.hpp>
 
 #include <irccd/test/mock_server.hpp>
 
--- a/libirccd/CMakeLists.txt	Sun Aug 05 12:14:16 2018 +0200
+++ b/libirccd/CMakeLists.txt	Sun Aug 05 15:47:10 2018 +0200
@@ -22,20 +22,20 @@
     HEADERS
     ${libirccd_SOURCE_DIR}/irccd/daemon/command.hpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/dynlib_plugin.hpp
+    ${libirccd_SOURCE_DIR}/irccd/daemon/irc.hpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/irccd.hpp
-    ${libirccd_SOURCE_DIR}/irccd/daemon/irc.hpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/logger.hpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/plugin.hpp
+    ${libirccd_SOURCE_DIR}/irccd/daemon/plugin_service.hpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/rule.hpp
+    ${libirccd_SOURCE_DIR}/irccd/daemon/rule_service.hpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/rule_util.hpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/server.hpp
+    ${libirccd_SOURCE_DIR}/irccd/daemon/server_service.hpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/server_util.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
     ${libirccd_SOURCE_DIR}/irccd/daemon/transport_util.hpp
 )
 
@@ -43,20 +43,20 @@
     SOURCES
     ${libirccd_SOURCE_DIR}/irccd/daemon/command.cpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/dynlib_plugin.cpp
+    ${libirccd_SOURCE_DIR}/irccd/daemon/irc.cpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/irccd.cpp
-    ${libirccd_SOURCE_DIR}/irccd/daemon/irc.cpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/logger.cpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/plugin.cpp
+    ${libirccd_SOURCE_DIR}/irccd/daemon/plugin_service.cpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/rule.cpp
+    ${libirccd_SOURCE_DIR}/irccd/daemon/rule_service.cpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/rule_util.cpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/server.cpp
+    ${libirccd_SOURCE_DIR}/irccd/daemon/server_service.cpp
     ${libirccd_SOURCE_DIR}/irccd/daemon/server_util.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
     ${libirccd_SOURCE_DIR}/irccd/daemon/transport_util.cpp
 )
 
--- a/libirccd/irccd/daemon/command.cpp	Sun Aug 05 12:14:16 2018 +0200
+++ b/libirccd/irccd/daemon/command.cpp	Sun Aug 05 15:47:10 2018 +0200
@@ -16,21 +16,19 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
-#include <irccd/daemon/service/plugin_service.hpp>
-#include <irccd/daemon/service/rule_service.hpp>
-#include <irccd/daemon/service/server_service.hpp>
-
-#include <irccd/daemon/irccd.hpp>
-#include <irccd/daemon/plugin.hpp>
-#include <irccd/daemon/rule.hpp>
-#include <irccd/daemon/rule_util.hpp>
-#include <irccd/daemon/server.hpp>
-#include <irccd/daemon/server_util.hpp>
-#include <irccd/daemon/transport_client.hpp>
-
 #include <irccd/string_util.hpp>
 
 #include "command.hpp"
+#include "irccd.hpp"
+#include "plugin.hpp"
+#include "plugin_service.hpp"
+#include "rule.hpp"
+#include "rule_service.hpp"
+#include "rule_util.hpp"
+#include "server.hpp"
+#include "server_service.hpp"
+#include "server_util.hpp"
+#include "transport_client.hpp"
 
 using namespace std::string_literals;
 
--- a/libirccd/irccd/daemon/irccd.cpp	Sun Aug 05 12:14:16 2018 +0200
+++ b/libirccd/irccd/daemon/irccd.cpp	Sun Aug 05 15:47:10 2018 +0200
@@ -25,11 +25,10 @@
 
 #include "irccd.hpp"
 #include "logger.hpp"
-
-#include "service/plugin_service.hpp"
-#include "service/rule_service.hpp"
-#include "service/server_service.hpp"
-#include "service/transport_service.hpp"
+#include "plugin_service.hpp"
+#include "rule_service.hpp"
+#include "server_service.hpp"
+#include "transport_service.hpp"
 
 namespace irccd {
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/daemon/plugin_service.cpp	Sun Aug 05 15:47:10 2018 +0200
@@ -0,0 +1,265 @@
+/*
+ * 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 <boost/format.hpp>
+
+#include <irccd/config.hpp>
+#include <irccd/string_util.hpp>
+#include <irccd/system.hpp>
+
+#include "irccd.hpp"
+#include "logger.hpp"
+#include "plugin_service.hpp"
+
+using boost::format;
+using boost::str;
+
+namespace irccd {
+
+namespace {
+
+auto to_map(const config& conf, const std::string& section) -> plugin::map
+{
+    plugin::map ret;
+
+    for (const auto& opt : conf.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& plg : plugins_) {
+        try {
+            plg->handle_unload(irccd_);
+        } catch (const std::exception& ex) {
+            irccd_.get_log().warning(*plg) << ex.what() << std::endl;
+        }
+    }
+}
+
+auto plugin_service::all() const noexcept -> plugins
+{
+    return plugins_;
+}
+
+auto plugin_service::has(std::string_view id) const noexcept -> bool
+{
+    return get(id) != nullptr;
+}
+
+auto plugin_service::get(std::string_view id) const noexcept -> std::shared_ptr<plugin>
+{
+    const auto find = [id] (const auto& plg) {
+        return plg->get_id() == id;
+    };
+
+    if (const auto it = std::find_if(plugins_.begin(), plugins_.end(), find); it != plugins_.end())
+        return *it;
+
+    return nullptr;
+}
+
+auto plugin_service::require(std::string_view id) const -> std::shared_ptr<plugin>
+{
+    auto plugin = get(id);
+
+    if (!plugin)
+        throw plugin_error(plugin_error::not_found, id);
+
+    return plugin;
+}
+
+void plugin_service::add(std::shared_ptr<plugin> plugin)
+{
+    assert(plugin);
+
+    plugins_.push_back(std::move(plugin));
+}
+
+void plugin_service::add_loader(std::unique_ptr<plugin_loader> loader)
+{
+    assert(loader);
+
+    loaders_.push_back(std::move(loader));
+}
+
+auto plugin_service::get_options(std::string_view id) -> plugin::map
+{
+    return to_map(irccd_.get_config(), str(format("plugin.%1%") % id));
+}
+
+auto plugin_service::get_formats(std::string_view id) -> plugin::map
+{
+    return to_map(irccd_.get_config(), str(format("format.%1%") % id));
+}
+
+auto plugin_service::get_paths(std::string_view id) -> plugin::map
+{
+    auto defaults = to_map(irccd_.get_config(), "paths");
+    auto paths = to_map(irccd_.get_config(), str(format("paths.%1%") % id));
+
+    // Fill defaults paths.
+    if (!defaults.count("cache"))
+        defaults.emplace("cache", sys::cachedir().string());
+    if (!defaults.count("data"))
+        defaults.emplace("data", sys::datadir().string());
+    if (!defaults.count("config"))
+        defaults.emplace("config", sys::sysconfdir().string());
+
+    const auto join = [id] (auto path) {
+        return (boost::filesystem::path(path) / "plugin" / std::string(id)).string();
+    };
+
+    // Now fill missing fields.
+    if (!paths.count("cache"))
+        paths.emplace("cache", join(defaults["cache"]));
+    if (!paths.count("data"))
+        paths.emplace("data", join(defaults["data"]));
+    if (!paths.count("config"))
+        paths.emplace("config", join(defaults["config"]));
+
+    return paths;
+}
+
+auto plugin_service::open(std::string_view id, std::string_view path) -> std::shared_ptr<plugin>
+{
+    for (const auto& loader : loaders_) {
+        auto plugin = loader->open(id, path);
+
+        if (plugin)
+            return plugin;
+    }
+
+    return nullptr;
+}
+
+auto plugin_service::find(std::string_view id) -> std::shared_ptr<plugin>
+{
+    for (const auto& loader : loaders_) {
+        try {
+            auto plugin = loader->find(id);
+
+            if (plugin)
+                return plugin;
+        } catch (const std::exception& ex) {
+            irccd_.get_log().warning("plugin", id) << ex.what() << std::endl;
+        }
+    }
+
+    return nullptr;
+}
+
+void plugin_service::load(std::string_view id, std::string_view path)
+{
+    if (has(id))
+        throw plugin_error(plugin_error::already_exists, id);
+
+    std::shared_ptr<plugin> plugin;
+
+    if (path.empty())
+        plugin = find(id);
+    else
+        plugin = open(id, std::move(path));
+
+    if (!plugin)
+        throw plugin_error(plugin_error::not_found, id);
+
+    plugin->set_options(get_options(id));
+    plugin->set_formats(get_formats(id));
+    plugin->set_paths(get_paths(id));
+
+    exec(plugin, &plugin::handle_load, irccd_);
+    add(std::move(plugin));
+}
+
+void plugin_service::reload(std::string_view id)
+{
+    auto plugin = get(id);
+
+    if (!plugin)
+        throw plugin_error(plugin_error::not_found, id);
+
+    exec(plugin, &plugin::handle_reload, irccd_);
+}
+
+void plugin_service::unload(std::string_view id)
+{
+    const auto find = [id] (const auto& plg) {
+        return plg->get_id() == id;
+    };
+
+    const auto it = std::find_if(plugins_.begin(), plugins_.end(), find);
+
+    if (it == plugins_.end())
+        throw plugin_error(plugin_error::not_found, id);
+
+    // Erase first, in case of throwing.
+    const auto save = *it;
+
+    plugins_.erase(it);
+    exec(save, &plugin::handle_unload, irccd_);
+}
+
+void plugin_service::load(const config& cfg) noexcept
+{
+    for (const auto& option : cfg.get("plugins")) {
+        if (!string_util::is_identifier(option.key()))
+            continue;
+
+        auto id = option.key();
+        auto p = get(id);
+
+        // Reload the plugin if already loaded.
+        if (p) {
+            p->set_options(get_options(id));
+            p->set_formats(get_formats(id));
+            p->set_paths(get_paths(id));
+        } else {
+            try {
+                load(id, option.value());
+            } catch (const std::exception& ex) {
+                irccd_.get_log().warning("plugin", id) << ex.what() << std::endl;
+            }
+        }
+    }
+}
+
+namespace logger {
+
+auto loggable_traits<plugin>::get_category(const plugin&) -> std::string_view
+{
+    return "plugin";
+}
+
+auto loggable_traits<plugin>::get_component(const plugin& plugin) -> std::string_view
+{
+    return plugin.get_id();
+}
+
+} // !logger
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/daemon/plugin_service.hpp	Sun Aug 05 15:47:10 2018 +0200
@@ -0,0 +1,279 @@
+/*
+ * 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 <string_view>
+#include <vector>
+
+#include "plugin.hpp"
+
+namespace irccd {
+
+class irccd;
+class config;
+
+/**
+ * \brief Manage plugins.
+ * \ingroup services
+ */
+class plugin_service {
+public:
+    /**
+     * \brief Map of plugins.
+     */
+    using plugins = std::vector<std::shared_ptr<plugin>>;
+
+    /**
+     * \brief List of loaders.
+     */
+    using plugin_loaders = std::vector<std::unique_ptr<plugin_loader>>;
+
+private:
+    irccd& irccd_;
+    plugins plugins_;
+    plugin_loaders loaders_;
+
+public:
+    /**
+     * Create the plugin service.
+     *
+     * \param irccd the irccd instance
+     */
+    plugin_service(irccd& irccd) noexcept;
+
+    /**
+     * Destroy plugins.
+     */
+    virtual ~plugin_service();
+
+    /**
+     * Get the list of plugins.
+     *
+     * \return the list of plugins
+     */
+    auto all() const noexcept -> plugins;
+
+    /**
+     * Check if a plugin is loaded.
+     *
+     * \param id the plugin id
+     * \return true if has plugin
+     */
+    auto has(std::string_view id) const noexcept -> bool;
+
+    /**
+     * Get a loaded plugin or null if not found.
+     *
+     * \param id the plugin id
+     * \return the plugin or empty one if not found
+     */
+    auto get(std::string_view id) const noexcept -> std::shared_ptr<plugin>;
+
+    /**
+     * Find a loaded plugin.
+     *
+     * \param id the plugin id
+     * \return the plugin
+     * \throw plugin_error on errors
+     */
+    auto require(std::string_view id) const -> std::shared_ptr<plugin>;
+
+    /**
+     * Add the specified plugin to the registry.
+     *
+     * \pre plg != nullptr
+     * \param plg the plugin
+     * \note the plugin is only added to the list, no action is performed on it
+     */
+    void add(std::shared_ptr<plugin> plg);
+
+    /**
+     * Add a loader.
+     *
+     * \pre loader != nullptr
+     * \param loader the loader
+     */
+    void add_loader(std::unique_ptr<plugin_loader> loader);
+
+    /**
+     * Get the configuration for the specified plugin.
+     *
+     * \param id the plugin id
+     * \return the configuration
+     */
+    auto get_options(std::string_view id) -> plugin::map;
+
+    /**
+     * Get the formats for the specified plugin.
+     *
+     * \param id the plugin id
+     * \return the formats
+     */
+    auto get_formats(std::string_view id) -> plugin::map;
+
+    /**
+     * Get the paths for the specified plugin.
+     *
+     * If none is defined, return the default ones.
+     *
+     * \param id the plugin id
+     * \return the paths
+     */
+    auto get_paths(std::string_view id) -> plugin::map;
+
+    /**
+     * 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
+     */
+    auto open(std::string_view id, std::string_view path) -> std::shared_ptr<plugin>;
+
+    /**
+     * Generic function for finding a plugin.
+     *
+     * \param id the plugin id
+     * \return the plugin or nullptr on failures
+     */
+    auto find(std::string_view id) -> std::shared_ptr<plugin>;
+
+    /**
+     * Convenient wrapper that loads a plugin, call handle_load and add it to
+     * the registry.
+     *
+     * Any errors are printed using logger.
+     *
+     * \param id the plugin id
+     * \param path the optional path (searched if empty)
+     */
+    void load(std::string_view name, std::string_view path = "");
+
+    /**
+     * Unload a plugin and remove it.
+     *
+     * \param id the plugin id
+     * \param name the plugin id
+     */
+    void unload(std::string_view id);
+
+    /**
+     * Reload a plugin by calling onReload.
+     *
+     * \param id the plugin id
+     * \throw std::exception on failures
+     */
+    void reload(std::string_view id);
+
+    /**
+     * 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->get_name(), ex.what());
+        } catch (...) {
+            throw plugin_error(plugin_error::exec_error, plugin->get_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->get_name());
+
+        exec(plugin, fn, std::forward<Args>(args)...);
+    }
+
+    /**
+     * Load all plugins.
+     *
+     * \param cfg the config
+     */
+    void load(const config& cfg) noexcept;
+};
+
+namespace logger {
+
+template <typename T>
+struct loggable_traits;
+
+/**
+ * \brief Implement Loggable traits for plugin.
+ */
+template <>
+struct loggable_traits<plugin> {
+    /**
+     * Return "plugin"
+     *
+     * \param plugin the plugin
+     * \return the category
+     */
+    static auto get_category(const plugin& plugin) -> std::string_view;
+
+    /**
+     * Return the plugin id.
+     *
+     * \param plugin the plugin
+     * \return the plugin id
+     */
+    static auto get_component(const plugin& plugin) -> std::string_view;
+};
+
+} // !logger
+
+} // !irccd
+
+#endif // !IRCCD_DAEMON_PLUGIN_SERVICE_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/daemon/rule_service.cpp	Sun Aug 05 15:47:10 2018 +0200
@@ -0,0 +1,135 @@
+/*
+ * 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"
+#include "rule_util.hpp"
+
+namespace irccd {
+
+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(std::string_view server,
+                         std::string_view channel,
+                         std::string_view origin,
+                         std::string_view plugin,
+                         std::string_view event) noexcept
+{
+    bool result = true;
+
+    irccd_.get_log().debug("rule", "")
+        << "solving for server=" << server
+        << ", channel=" << channel
+        << ", origin=" << origin
+        << ", plugin=" << plugin
+        << ", event=" << event << std::endl;
+
+    int i = 0;
+    for (const auto& rule : rules_) {
+        auto action = rule.get_action() == rule::action::accept ? "accept" : "drop";
+
+        irccd_.get_log().debug(rule) << "candidate "  << i++ << ":" << std::endl;
+        irccd_.get_log().debug(rule) << "  servers: "  << string_util::join(rule.get_servers()) << std::endl;
+        irccd_.get_log().debug(rule) << "  channels: " << string_util::join(rule.get_channels()) << std::endl;
+        irccd_.get_log().debug(rule) << "  origins: "  << string_util::join(rule.get_origins()) << std::endl;
+        irccd_.get_log().debug(rule) << "  plugins: "  << string_util::join(rule.get_plugins()) << std::endl;
+        irccd_.get_log().debug(rule) << "  events: "   << string_util::join(rule.get_events()) << std::endl;
+        irccd_.get_log().debug(rule) << "  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) {
+        if (section.key() != "rule")
+            continue;
+
+        try {
+            rules_.push_back(rule_util::from_config(section));
+        } catch (const std::exception& ex) {
+            irccd_.get_log().warning("rule", "") << ex.what() << std::endl;
+        }
+    }
+}
+
+namespace logger {
+
+auto loggable_traits<rule>::get_category(const rule&) -> std::string_view
+{
+    return "rule";
+}
+
+auto loggable_traits<rule>::get_component(const rule&) -> std::string_view
+{
+    return "";
+}
+
+} // !logger
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/daemon/rule_service.hpp	Sun Aug 05 15:47:10 2018 +0200
@@ -0,0 +1,152 @@
+/*
+ * 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:
+    /**
+     * 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(std::string_view server,
+               std::string_view channel,
+               std::string_view origin,
+               std::string_view plugin,
+               std::string_view event) noexcept;
+
+    /**
+     * Load rules from the configuration.
+     *
+     * \param cfg the config
+     */
+    void load(const config& cfg) noexcept;
+};
+
+namespace logger {
+
+template <typename T>
+struct loggable_traits;
+
+template <>
+struct loggable_traits<rule> {
+    static auto get_category(const rule& rule) -> std::string_view;
+
+    static auto get_component(const rule& rule) -> std::string_view;
+};
+
+} // !logger
+
+} // !irccd
+
+#endif // !IRCCD_DAEMON_RULE_SERVICE_HPP
--- a/libirccd/irccd/daemon/rule_util.cpp	Sun Aug 05 12:14:16 2018 +0200
+++ b/libirccd/irccd/daemon/rule_util.cpp	Sun Aug 05 15:47:10 2018 +0200
@@ -18,8 +18,7 @@
 
 #include <irccd/ini.hpp>
 
-#include <irccd/daemon/rule.hpp>
-
+#include "rule.hpp"
 #include "rule_util.hpp"
 
 namespace irccd {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/daemon/server_service.cpp	Sun Aug 05 15:47:10 2018 +0200
@@ -0,0 +1,690 @@
+/*
+ * server_service.cpp -- 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.hpp"
+#include "server_service.hpp"
+#include "server_util.hpp"
+#include "transport_service.hpp"
+
+namespace irccd {
+
+namespace {
+
+class dispatcher {
+private:
+    irccd& irccd_;
+
+    template <typename EventNameFunc, typename ExecFunc>
+    void dispatch(std::string_view, std::string_view, std::string_view, EventNameFunc&&, ExecFunc);
+
+public:
+    dispatcher(irccd& irccd);
+    void operator()(const std::monostate&);
+    void operator()(const connect_event&);
+    void operator()(const disconnect_event&);
+    void operator()(const invite_event&);
+    void operator()(const join_event&);
+    void operator()(const kick_event&);
+    void operator()(const message_event&);
+    void operator()(const me_event&);
+    void operator()(const mode_event&);
+    void operator()(const names_event&);
+    void operator()(const nick_event&);
+    void operator()(const notice_event&);
+    void operator()(const part_event&);
+    void operator()(const topic_event&);
+    void operator()(const whois_event&);
+};
+
+template <typename EventNameFunc, typename ExecFunc>
+void dispatcher::dispatch(std::string_view server,
+                          std::string_view origin,
+                          std::string_view target,
+                          EventNameFunc&& name_func,
+                          ExecFunc exec_func)
+{
+    for (const auto& plugin : irccd_.plugins().all()) {
+        const auto eventname = name_func(*plugin);
+        const auto allowed = irccd_.rules().solve(server, target, origin, plugin->get_name(), eventname);
+
+        if (!allowed) {
+            irccd_.get_log().debug("rule", "") << "event skipped on match" << std::endl;
+            continue;
+        }
+
+        irccd_.get_log().debug("rule", "") << "event allowed" << std::endl;
+
+        try {
+            exec_func(*plugin);
+        } catch (const std::exception& ex) {
+            irccd_.get_log().warning(*plugin) << ex.what() << std::endl;
+        }
+    }
+}
+
+dispatcher::dispatcher(irccd& irccd)
+    : irccd_(irccd)
+{
+}
+
+void dispatcher::operator()(const std::monostate&)
+{
+}
+
+void dispatcher::operator()(const connect_event& ev)
+{
+    irccd_.get_log().debug(*ev.server) << "event onConnect" << std::endl;
+    irccd_.transports().broadcast(nlohmann::json::object({
+        { "event",      "onConnect"         },
+        { "server",     ev.server->get_id() }
+    }));
+
+    dispatch(ev.server->get_id(), /* origin */ "", /* channel */ "",
+        [=] (plugin&) -> std::string {
+            return "onConnect";
+        },
+        [=] (plugin& plugin) {
+            plugin.handle_connect(irccd_, ev);
+        }
+    );
+}
+
+void dispatcher::operator()(const disconnect_event& ev)
+{
+    irccd_.get_log().debug(*ev.server) << "event onDisconnect" << std::endl;
+    irccd_.transports().broadcast(nlohmann::json::object({
+        { "event",      "onDisconnect"      },
+        { "server",     ev.server->get_id() }
+    }));
+
+    dispatch(ev.server->get_id(), /* origin */ "", /* channel */ "",
+        [=] (plugin&) -> std::string {
+            return "onDisconnect";
+        },
+        [=] (plugin& plugin) {
+            plugin.handle_disconnect(irccd_, ev);
+        }
+    );
+}
+
+void dispatcher::operator()(const invite_event& ev)
+{
+    irccd_.get_log().debug(*ev.server) << "event onInvite:" << std::endl;
+    irccd_.get_log().debug(*ev.server) << "  origin: " << ev.origin << std::endl;
+    irccd_.get_log().debug(*ev.server) << "  channel: " << ev.channel << std::endl;
+    irccd_.get_log().debug(*ev.server) << "  target: " << ev.nickname << std::endl;
+
+    irccd_.transports().broadcast(nlohmann::json::object({
+        { "event",      "onInvite"          },
+        { "server",     ev.server->get_id() },
+        { "origin",     ev.origin           },
+        { "channel",    ev.channel          }
+    }));
+
+    dispatch(ev.server->get_id(), ev.origin, ev.channel,
+        [=] (plugin&) -> std::string {
+            return "onInvite";
+        },
+        [=] (plugin& plugin) {
+            plugin.handle_invite(irccd_, ev);
+        }
+    );
+}
+
+void dispatcher::operator()(const join_event& ev)
+{
+    irccd_.get_log().debug(*ev.server) << "event onJoin:" << std::endl;
+    irccd_.get_log().debug(*ev.server) << "  origin: " << ev.origin << std::endl;
+    irccd_.get_log().debug(*ev.server) << "  channel: " << ev.channel << std::endl;
+
+    irccd_.transports().broadcast(nlohmann::json::object({
+        { "event",      "onJoin"            },
+        { "server",     ev.server->get_id() },
+        { "origin",     ev.origin           },
+        { "channel",    ev.channel          }
+    }));
+
+    dispatch(ev.server->get_id(), ev.origin, ev.channel,
+        [=] (plugin&) -> std::string {
+            return "onJoin";
+        },
+        [=] (plugin& plugin) {
+            plugin.handle_join(irccd_, ev);
+        }
+    );
+}
+
+void dispatcher::operator()(const kick_event& ev)
+{
+    irccd_.get_log().debug(*ev.server) << "event onKick:" << std::endl;
+    irccd_.get_log().debug(*ev.server) << "  origin: " << ev.origin << std::endl;
+    irccd_.get_log().debug(*ev.server) << "  channel: " << ev.channel << std::endl;
+    irccd_.get_log().debug(*ev.server) << "  target: " << ev.target << std::endl;
+    irccd_.get_log().debug(*ev.server) << "  reason: " << ev.reason << std::endl;
+
+    irccd_.transports().broadcast(nlohmann::json::object({
+        { "event",      "onKick"            },
+        { "server",     ev.server->get_id() },
+        { "origin",     ev.origin           },
+        { "channel",    ev.channel          },
+        { "target",     ev.target           },
+        { "reason",     ev.reason           }
+    }));
+
+    dispatch(ev.server->get_id(), ev.origin, ev.channel,
+        [=] (plugin&) -> std::string {
+            return "onKick";
+        },
+        [=] (plugin& plugin) {
+            plugin.handle_kick(irccd_, ev);
+        }
+    );
+}
+
+void dispatcher::operator()(const message_event& ev)
+{
+    irccd_.get_log().debug(*ev.server) << "event onMessage:" << std::endl;
+    irccd_.get_log().debug(*ev.server) << "  origin: " << ev.origin << std::endl;
+    irccd_.get_log().debug(*ev.server) << "  channel: " << ev.channel << std::endl;
+    irccd_.get_log().debug(*ev.server) << "  message: " << ev.message << std::endl;
+
+    irccd_.transports().broadcast(nlohmann::json::object({
+        { "event",      "onMessage"         },
+        { "server",     ev.server->get_id() },
+        { "origin",     ev.origin           },
+        { "channel",    ev.channel          },
+        { "message",    ev.message          }
+    }));
+
+    dispatch(ev.server->get_id(), ev.origin, ev.channel,
+        [=] (plugin& plugin) -> std::string {
+            return server_util::parse_message(
+                ev.message,
+                ev.server->get_command_char(),
+                plugin.get_id()
+            ).type == server_util::message_pack::type::command ? "onCommand" : "onMessage";
+        },
+        [=] (plugin& plugin) mutable {
+            auto copy = ev;
+            auto pack = server_util::parse_message(
+                copy.message,
+                copy.server->get_command_char(),
+                plugin.get_id()
+            );
+
+            copy.message = pack.message;
+
+            if (pack.type == server_util::message_pack::type::command)
+                plugin.handle_command(irccd_, copy);
+            else
+                plugin.handle_message(irccd_, copy);
+        }
+    );
+}
+
+void dispatcher::operator()(const me_event& ev)
+{
+    irccd_.get_log().debug(*ev.server) << "event onMe:" << std::endl;
+    irccd_.get_log().debug(*ev.server) << "  origin: " << ev.origin << std::endl;
+    irccd_.get_log().debug(*ev.server) << "  target: " << ev.channel << std::endl;
+    irccd_.get_log().debug(*ev.server) << "  message: " << ev.message << std::endl;
+
+    irccd_.transports().broadcast(nlohmann::json::object({
+        { "event",      "onMe"              },
+        { "server",     ev.server->get_id() },
+        { "origin",     ev.origin           },
+        { "target",     ev.channel          },
+        { "message",    ev.message          }
+    }));
+
+    dispatch(ev.server->get_id(), ev.origin, ev.channel,
+        [=] (plugin&) -> std::string {
+            return "onMe";
+        },
+        [=] (plugin& plugin) {
+            plugin.handle_me(irccd_, ev);
+        }
+    );
+}
+
+void dispatcher::operator()(const mode_event& ev)
+{
+    irccd_.get_log().debug(*ev.server) << "event onMode" << std::endl;
+    irccd_.get_log().debug(*ev.server) << "  origin: " << ev.origin << std::endl;
+    irccd_.get_log().debug(*ev.server) << "  channel: " << ev.channel << std::endl;
+    irccd_.get_log().debug(*ev.server) << "  mode: " << ev.mode << std::endl;
+    irccd_.get_log().debug(*ev.server) << "  limit: " << ev.limit << std::endl;
+    irccd_.get_log().debug(*ev.server) << "  user: " << ev.user << std::endl;
+    irccd_.get_log().debug(*ev.server) << "  mask: " << ev.mask << std::endl;
+
+    irccd_.transports().broadcast(nlohmann::json::object({
+        { "event",      "onMode"            },
+        { "server",     ev.server->get_id() },
+        { "origin",     ev.origin           },
+        { "channel",    ev.channel          },
+        { "mode",       ev.mode             },
+        { "limit",      ev.limit            },
+        { "user",       ev.user             },
+        { "mask",       ev.mask             }
+    }));
+
+    dispatch(ev.server->get_id(), ev.origin, /* channel */ "",
+        [=] (plugin &) -> std::string {
+            return "onMode";
+        },
+        [=] (plugin &plugin) {
+            plugin.handle_mode(irccd_, ev);
+        }
+    );
+}
+
+void dispatcher::operator()(const names_event& ev)
+{
+    irccd_.get_log().debug(*ev.server) << "event onNames:" << std::endl;
+    irccd_.get_log().debug(*ev.server) << "  channel: " << ev.channel << std::endl;
+    irccd_.get_log().debug(*ev.server) << "  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->get_id() },
+        { "channel",    ev.channel          },
+        { "names",      std::move(names)    }
+    }));
+
+    dispatch(ev.server->get_id(), /* origin */ "", ev.channel,
+        [=] (plugin&) -> std::string {
+            return "onNames";
+        },
+        [=] (plugin& plugin) {
+            plugin.handle_names(irccd_, ev);
+        }
+    );
+}
+
+void dispatcher::operator()(const nick_event& ev)
+{
+    irccd_.get_log().debug(*ev.server) << "event onNick:" << std::endl;
+    irccd_.get_log().debug(*ev.server) << "  origin: " << ev.origin << std::endl;
+    irccd_.get_log().debug(*ev.server) << "  nickname: " << ev.nickname << std::endl;
+
+    irccd_.transports().broadcast(nlohmann::json::object({
+        { "event",      "onNick"            },
+        { "server",     ev.server->get_id() },
+        { "origin",     ev.origin           },
+        { "nickname",   ev.nickname         }
+    }));
+
+    dispatch(ev.server->get_id(), ev.origin, /* channel */ "",
+        [=] (plugin&) -> std::string {
+            return "onNick";
+        },
+        [=] (plugin& plugin) {
+            plugin.handle_nick(irccd_, ev);
+        }
+    );
+}
+
+void dispatcher::operator()(const notice_event& ev)
+{
+    irccd_.get_log().debug(*ev.server) << "event onNotice:" << std::endl;
+    irccd_.get_log().debug(*ev.server) << "  origin: " << ev.origin << std::endl;
+    irccd_.get_log().debug(*ev.server) << "  channel: " << ev.channel << std::endl;
+    irccd_.get_log().debug(*ev.server) << "  message: " << ev.message << std::endl;
+
+    irccd_.transports().broadcast(nlohmann::json::object({
+        { "event",      "onNotice"          },
+        { "server",     ev.server->get_id() },
+        { "origin",     ev.origin           },
+        { "channel",    ev.channel          },
+        { "message",    ev.message          }
+    }));
+
+    dispatch(ev.server->get_id(), ev.origin, /* channel */ "",
+        [=] (plugin&) -> std::string {
+            return "onNotice";
+        },
+        [=] (plugin& plugin) {
+            plugin.handle_notice(irccd_, ev);
+        }
+    );
+}
+
+void dispatcher::operator()(const part_event& ev)
+{
+    irccd_.get_log().debug(*ev.server) << "event onPart:" << std::endl;
+    irccd_.get_log().debug(*ev.server) << "  origin: " << ev.origin << std::endl;
+    irccd_.get_log().debug(*ev.server) << "  channel: " << ev.channel << std::endl;
+    irccd_.get_log().debug(*ev.server) << "  reason: " << ev.reason << std::endl;
+
+    irccd_.transports().broadcast(nlohmann::json::object({
+        { "event",      "onPart"            },
+        { "server",     ev.server->get_id() },
+        { "origin",     ev.origin           },
+        { "channel",    ev.channel          },
+        { "reason",     ev.reason           }
+    }));
+
+    dispatch(ev.server->get_id(), ev.origin, ev.channel,
+        [=] (plugin&) -> std::string {
+            return "onPart";
+        },
+        [=] (plugin& plugin) {
+            plugin.handle_part(irccd_, ev);
+        }
+    );
+}
+
+void dispatcher::operator()(const topic_event& ev)
+{
+    irccd_.get_log().debug(*ev.server) << "event onTopic:" << std::endl;
+    irccd_.get_log().debug(*ev.server) << "  origin: " << ev.origin << std::endl;
+    irccd_.get_log().debug(*ev.server) << "  channel: " << ev.channel << std::endl;
+    irccd_.get_log().debug(*ev.server) << "  topic: " << ev.topic << std::endl;
+
+    irccd_.transports().broadcast(nlohmann::json::object({
+        { "event",      "onTopic"           },
+        { "server",     ev.server->get_id() },
+        { "origin",     ev.origin           },
+        { "channel",    ev.channel          },
+        { "topic",      ev.topic            }
+    }));
+
+    dispatch(ev.server->get_id(), ev.origin, ev.channel,
+        [=] (plugin&) -> std::string {
+            return "onTopic";
+        },
+        [=] (plugin& plugin) {
+            plugin.handle_topic(irccd_, ev);
+        }
+    );
+}
+
+void dispatcher::operator()(const whois_event& ev)
+{
+    irccd_.get_log().debug(*ev.server) << "event onWhois" << std::endl;
+    irccd_.get_log().debug(*ev.server) << "  nickname: " << ev.whois.nick << std::endl;
+    irccd_.get_log().debug(*ev.server) << "  username: " << ev.whois.user << std::endl;
+    irccd_.get_log().debug(*ev.server) << "  host: " << ev.whois.host << std::endl;
+    irccd_.get_log().debug(*ev.server) << "  realname: " << ev.whois.realname << std::endl;
+    irccd_.get_log().debug(*ev.server) << "  channels: " << string_util::join(ev.whois.channels, ", ") << std::endl;
+
+    irccd_.transports().broadcast(nlohmann::json::object({
+        { "event",      "onWhois"           },
+        { "server",     ev.server->get_id() },
+        { "nickname",   ev.whois.nick       },
+        { "username",   ev.whois.user       },
+        { "host",       ev.whois.host       },
+        { "realname",   ev.whois.realname   }
+    }));
+
+    dispatch(ev.server->get_id(), /* origin */ "", /* channel */ "",
+        [=] (plugin&) -> std::string {
+            return "onWhois";
+        },
+        [=] (plugin& plugin) {
+            plugin.handle_whois(irccd_, ev);
+        }
+    );
+}
+
+} // !namespace
+
+void server_service::handle_error(const std::shared_ptr<server>& server,
+                                  const std::error_code& code)
+{
+    assert(server);
+
+    irccd_.get_log().warning(*server) << code.message() << std::endl;
+
+    irccd_.get_log().warning(*server) << int(server->get_options()) << std::endl;
+
+    if ((server->get_options() & server::options::auto_reconnect) != server::options::auto_reconnect)
+        remove(server->get_id());
+    else {
+        irccd_.get_log().info(*server) << "reconnecting in "
+            << server->get_reconnect_delay() << " second(s)" << std::endl;
+        wait(server);
+    }
+}
+
+void server_service::handle_wait(const std::shared_ptr<server>& server, const std::error_code& code)
+{
+    /*
+     * The timer runs on his own control, it will complete either if the delay
+     * was reached, there was an error or if the io_context was called to cancel
+     * all pending operations.
+     *
+     * This means while the timer is running someone may already have ask a
+     * server for explicit reconnection (e.g. remote command, plugin). Thus we
+     * check for server state and if it is still present in service.
+     */
+    if (code && code != std::errc::operation_canceled) {
+        irccd_.get_log().warning(*server) << code.message() << std::endl;
+        return;
+    }
+
+    if (server->get_state() == server::state::connected || !has(server->get_id()))
+        return;
+
+    connect(server);
+}
+
+void server_service::handle_recv(const std::shared_ptr<server>& server,
+                                 const std::error_code& code,
+                                 const event& event)
+{
+    assert(server);
+
+    if (code)
+        handle_error(server, code);
+    else {
+        recv(server);
+        std::visit(dispatcher(irccd_), event);
+    }
+}
+
+void server_service::handle_connect(const std::shared_ptr<server>& server, const std::error_code& code)
+{
+    if (code)
+        handle_error(server, code);
+    else
+        recv(server);
+}
+
+void server_service::wait(const std::shared_ptr<server>& server)
+{
+    assert(server);
+
+    auto timer = std::make_shared<boost::asio::deadline_timer>(irccd_.get_service());
+
+    timer->expires_from_now(boost::posix_time::seconds(server->get_reconnect_delay()));
+    timer->async_wait([this, server, timer] (auto code) {
+        handle_wait(server, code);
+    });
+}
+
+void server_service::recv(const std::shared_ptr<server>& server)
+{
+    assert(server);
+
+    server->recv([this, server] (auto code, auto event) {
+        handle_recv(server, code, event);
+    });
+}
+
+void server_service::connect(const std::shared_ptr<server>& server)
+{
+    assert(server);
+
+    server->connect([this, server] (auto code) {
+        handle_connect(server, code);
+    });
+}
+
+server_service::server_service(irccd &irccd)
+    : irccd_(irccd)
+{
+}
+
+auto server_service::all() const noexcept -> const std::vector<std::shared_ptr<server>>&
+{
+    return servers_;
+}
+
+auto server_service::has(const std::string& name) const noexcept -> bool
+{
+    return std::count_if(servers_.begin(), servers_.end(), [&] (const auto& server) {
+        return server->get_id() == name;
+    }) > 0;
+}
+
+void server_service::add(std::shared_ptr<server> server)
+{
+    assert(server);
+    assert(!has(server->get_id()));
+
+    servers_.push_back(server);
+    connect(server);
+}
+
+auto server_service::get(std::string_view name) const noexcept -> std::shared_ptr<server>
+{
+    const auto it = std::find_if(servers_.begin(), servers_.end(), [&] (const auto& server) {
+        return server->get_id() == name;
+    });
+
+    if (it == servers_.end())
+        return nullptr;
+
+    return *it;
+}
+
+auto server_service::require(std::string_view name) const -> std::shared_ptr<server>
+{
+    if (!string_util::is_identifier(name))
+        throw server_error(server_error::invalid_identifier);
+
+    const auto s = get(name);
+
+    if (!s)
+        throw server_error(server_error::not_found);
+
+    return s;
+}
+
+void server_service::disconnect(std::string_view id)
+{
+    const auto s = require(id);
+
+    s->disconnect();
+    dispatcher{irccd_}(disconnect_event{s});
+}
+
+void server_service::reconnect(std::string_view id)
+{
+    disconnect(id);
+    connect(require(id));
+}
+
+void server_service::reconnect()
+{
+    for (const auto& s : servers_) {
+        try {
+            s->disconnect();
+            dispatcher{irccd_}(disconnect_event{s});
+            connect(s);
+        } catch (const server_error& ex) {
+            irccd_.get_log().warning(*s) << ex.what() << std::endl;
+        }
+    }
+}
+
+void server_service::remove(std::string_view name)
+{
+    const auto it = std::find_if(servers_.begin(), servers_.end(), [&] (const auto& server) {
+        return server->get_id() == name;
+    });
+
+    if (it != servers_.end()) {
+        (*it)->disconnect();
+        servers_.erase(it);
+    }
+}
+
+void server_service::clear() noexcept
+{
+    /*
+     * Copy the array, because disconnect() may trigger on_die signal which
+     * erase the server from itself.
+     */
+    const auto save = servers_;
+
+    for (const auto& server : save)
+        server->disconnect();
+
+    servers_.clear();
+}
+
+void server_service::load(const config& cfg) noexcept
+{
+    for (const auto& section : cfg) {
+        if (section.key() != "server")
+            continue;
+
+        const auto id = section.get("name").value();
+
+        try {
+            auto server = server_util::from_config(irccd_.get_service(), cfg, section);
+
+            if (has(server->get_id()))
+                throw server_error(server_error::already_exists);
+
+            add(std::move(server));
+        } catch (const std::exception& ex) {
+            irccd_.get_log().warning("server", id) << ex.what() << std::endl;
+        }
+    }
+}
+
+namespace logger {
+
+auto loggable_traits<server>::get_category(const server&) -> std::string_view
+{
+    return "server";
+}
+
+auto loggable_traits<server>::get_component(const server& sv) -> std::string_view
+{
+    return sv.get_id();
+}
+
+} // !logger
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/daemon/server_service.hpp	Sun Aug 05 15:47:10 2018 +0200
@@ -0,0 +1,164 @@
+/*
+ * 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 <system_error>
+#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_error(const std::shared_ptr<server>&, const std::error_code&);
+    void handle_wait(const std::shared_ptr<server>&, const std::error_code&);
+    void handle_recv(const std::shared_ptr<server>&, const std::error_code&, const event&);
+    void handle_connect(const std::shared_ptr<server>&, const std::error_code&);
+
+    void wait(const std::shared_ptr<server>&);
+    void recv(const std::shared_ptr<server>&);
+    void connect(const std::shared_ptr<server>&);
+
+public:
+    /**
+     * Create the server service.
+     */
+    server_service(irccd& instance);
+
+    /**
+     * Get the list of servers
+     *
+     * \return the servers
+     */
+    auto all() const noexcept -> const std::vector<std::shared_ptr<server>>&;
+
+    /**
+     * Check if a server exists.
+     *
+     * \param name the name
+     * \return true if exists
+     */
+    auto has(const std::string& name) const noexcept -> bool;
+
+    /**
+     * 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
+     */
+    auto get(std::string_view name) const noexcept -> std::shared_ptr<server>;
+
+    /**
+     * Find a server from a JSON object.
+     *
+     * \param name the server name
+     * \return the server
+     * \throw server_error on errors
+     */
+    auto require(std::string_view name) const -> std::shared_ptr<server>;
+
+    /**
+     * Force disconnection, this also call plugin::handle_disconnect handler.
+     *
+     * \param id the server id
+     * \throw server_error on errors
+     */
+    void disconnect(std::string_view id);
+
+    /**
+     * Force reconnection, this also call plugin::handle_disconnect handler.
+     *
+     * \param id the server id
+     * \return the server
+     * \throw server_error on errors
+     */
+    void reconnect(std::string_view id);
+
+    /**
+     * Force reconnection of all servers.
+     */
+    void reconnect();
+
+    /**
+     * Remove a server from the irccd instance.
+     *
+     * The server if any, will be disconnected.
+     *
+     * \param name the server name
+     */
+    void remove(std::string_view 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;
+};
+
+namespace logger {
+
+template <typename T>
+struct loggable_traits;
+
+template <>
+struct loggable_traits<server> {
+    static auto get_category(const server& server) -> std::string_view;
+
+    static auto get_component(const server& server) -> std::string_view;
+};
+
+} // !logger
+
+} // !irccd
+
+#endif // !IRCCD_DAEMON_SERVER_SERVICE_HPP
--- a/libirccd/irccd/daemon/server_util.cpp	Sun Aug 05 12:14:16 2018 +0200
+++ b/libirccd/irccd/daemon/server_util.cpp	Sun Aug 05 15:47:10 2018 +0200
@@ -23,6 +23,7 @@
 #include <irccd/json_util.hpp>
 #include <irccd/string_util.hpp>
 
+#include "server.hpp"
 #include "server_util.hpp"
 
 namespace irccd {
--- a/libirccd/irccd/daemon/server_util.hpp	Sun Aug 05 12:14:16 2018 +0200
+++ b/libirccd/irccd/daemon/server_util.hpp	Sun Aug 05 15:47:10 2018 +0200
@@ -30,8 +30,6 @@
 
 #include <json.hpp>
 
-#include <irccd/daemon/server.hpp>
-
 namespace irccd {
 
 namespace ini {
@@ -41,6 +39,7 @@
 } // !ini
 
 class config;
+class server;
 
 /**
  * \brief Server utilities.
--- a/libirccd/irccd/daemon/service/plugin_service.cpp	Sun Aug 05 12:14:16 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,266 +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 <boost/format.hpp>
-
-#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>
-
-using boost::format;
-using boost::str;
-
-namespace irccd {
-
-namespace {
-
-auto to_map(const config& conf, const std::string& section) -> plugin::map
-{
-    plugin::map ret;
-
-    for (const auto& opt : conf.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& plg : plugins_) {
-        try {
-            plg->handle_unload(irccd_);
-        } catch (const std::exception& ex) {
-            irccd_.get_log().warning(*plg) << ex.what() << std::endl;
-        }
-    }
-}
-
-auto plugin_service::all() const noexcept -> plugins
-{
-    return plugins_;
-}
-
-auto plugin_service::has(std::string_view id) const noexcept -> bool
-{
-    return get(id) != nullptr;
-}
-
-auto plugin_service::get(std::string_view id) const noexcept -> std::shared_ptr<plugin>
-{
-    const auto find = [id] (const auto& plg) {
-        return plg->get_id() == id;
-    };
-
-    if (const auto it = std::find_if(plugins_.begin(), plugins_.end(), find); it != plugins_.end())
-        return *it;
-
-    return nullptr;
-}
-
-auto plugin_service::require(std::string_view id) const -> std::shared_ptr<plugin>
-{
-    auto plugin = get(id);
-
-    if (!plugin)
-        throw plugin_error(plugin_error::not_found, id);
-
-    return plugin;
-}
-
-void plugin_service::add(std::shared_ptr<plugin> plugin)
-{
-    assert(plugin);
-
-    plugins_.push_back(std::move(plugin));
-}
-
-void plugin_service::add_loader(std::unique_ptr<plugin_loader> loader)
-{
-    assert(loader);
-
-    loaders_.push_back(std::move(loader));
-}
-
-auto plugin_service::get_options(std::string_view id) -> plugin::map
-{
-    return to_map(irccd_.get_config(), str(format("plugin.%1%") % id));
-}
-
-auto plugin_service::get_formats(std::string_view id) -> plugin::map
-{
-    return to_map(irccd_.get_config(), str(format("format.%1%") % id));
-}
-
-auto plugin_service::get_paths(std::string_view id) -> plugin::map
-{
-    auto defaults = to_map(irccd_.get_config(), "paths");
-    auto paths = to_map(irccd_.get_config(), str(format("paths.%1%") % id));
-
-    // Fill defaults paths.
-    if (!defaults.count("cache"))
-        defaults.emplace("cache", sys::cachedir().string());
-    if (!defaults.count("data"))
-        defaults.emplace("data", sys::datadir().string());
-    if (!defaults.count("config"))
-        defaults.emplace("config", sys::sysconfdir().string());
-
-    const auto join = [id] (auto path) {
-        return (boost::filesystem::path(path) / "plugin" / std::string(id)).string();
-    };
-
-    // Now fill missing fields.
-    if (!paths.count("cache"))
-        paths.emplace("cache", join(defaults["cache"]));
-    if (!paths.count("data"))
-        paths.emplace("data", join(defaults["data"]));
-    if (!paths.count("config"))
-        paths.emplace("config", join(defaults["config"]));
-
-    return paths;
-}
-
-auto plugin_service::open(std::string_view id, std::string_view path) -> std::shared_ptr<plugin>
-{
-    for (const auto& loader : loaders_) {
-        auto plugin = loader->open(id, path);
-
-        if (plugin)
-            return plugin;
-    }
-
-    return nullptr;
-}
-
-auto plugin_service::find(std::string_view id) -> std::shared_ptr<plugin>
-{
-    for (const auto& loader : loaders_) {
-        try {
-            auto plugin = loader->find(id);
-
-            if (plugin)
-                return plugin;
-        } catch (const std::exception& ex) {
-            irccd_.get_log().warning("plugin", id) << ex.what() << std::endl;
-        }
-    }
-
-    return nullptr;
-}
-
-void plugin_service::load(std::string_view id, std::string_view path)
-{
-    if (has(id))
-        throw plugin_error(plugin_error::already_exists, id);
-
-    std::shared_ptr<plugin> plugin;
-
-    if (path.empty())
-        plugin = find(id);
-    else
-        plugin = open(id, std::move(path));
-
-    if (!plugin)
-        throw plugin_error(plugin_error::not_found, id);
-
-    plugin->set_options(get_options(id));
-    plugin->set_formats(get_formats(id));
-    plugin->set_paths(get_paths(id));
-
-    exec(plugin, &plugin::handle_load, irccd_);
-    add(std::move(plugin));
-}
-
-void plugin_service::reload(std::string_view id)
-{
-    auto plugin = get(id);
-
-    if (!plugin)
-        throw plugin_error(plugin_error::not_found, id);
-
-    exec(plugin, &plugin::handle_reload, irccd_);
-}
-
-void plugin_service::unload(std::string_view id)
-{
-    const auto find = [id] (const auto& plg) {
-        return plg->get_id() == id;
-    };
-
-    const auto it = std::find_if(plugins_.begin(), plugins_.end(), find);
-
-    if (it == plugins_.end())
-        throw plugin_error(plugin_error::not_found, id);
-
-    // Erase first, in case of throwing.
-    const auto save = *it;
-
-    plugins_.erase(it);
-    exec(save, &plugin::handle_unload, irccd_);
-}
-
-void plugin_service::load(const config& cfg) noexcept
-{
-    for (const auto& option : cfg.get("plugins")) {
-        if (!string_util::is_identifier(option.key()))
-            continue;
-
-        auto id = option.key();
-        auto p = get(id);
-
-        // Reload the plugin if already loaded.
-        if (p) {
-            p->set_options(get_options(id));
-            p->set_formats(get_formats(id));
-            p->set_paths(get_paths(id));
-        } else {
-            try {
-                load(id, option.value());
-            } catch (const std::exception& ex) {
-                irccd_.get_log().warning("plugin", id) << ex.what() << std::endl;
-            }
-        }
-    }
-}
-
-namespace logger {
-
-auto loggable_traits<plugin>::get_category(const plugin&) -> std::string_view
-{
-    return "plugin";
-}
-
-auto loggable_traits<plugin>::get_component(const plugin& plugin) -> std::string_view
-{
-    return plugin.get_id();
-}
-
-} // !logger
-
-} // !irccd
--- a/libirccd/irccd/daemon/service/plugin_service.hpp	Sun Aug 05 12:14:16 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,279 +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 <string_view>
-#include <vector>
-
-#include <irccd/daemon/plugin.hpp>
-
-namespace irccd {
-
-class irccd;
-class config;
-
-/**
- * \brief Manage plugins.
- * \ingroup services
- */
-class plugin_service {
-public:
-    /**
-     * \brief Map of plugins.
-     */
-    using plugins = std::vector<std::shared_ptr<plugin>>;
-
-    /**
-     * \brief List of loaders.
-     */
-    using plugin_loaders = std::vector<std::unique_ptr<plugin_loader>>;
-
-private:
-    irccd& irccd_;
-    plugins plugins_;
-    plugin_loaders loaders_;
-
-public:
-    /**
-     * Create the plugin service.
-     *
-     * \param irccd the irccd instance
-     */
-    plugin_service(irccd& irccd) noexcept;
-
-    /**
-     * Destroy plugins.
-     */
-    virtual ~plugin_service();
-
-    /**
-     * Get the list of plugins.
-     *
-     * \return the list of plugins
-     */
-    auto all() const noexcept -> plugins;
-
-    /**
-     * Check if a plugin is loaded.
-     *
-     * \param id the plugin id
-     * \return true if has plugin
-     */
-    auto has(std::string_view id) const noexcept -> bool;
-
-    /**
-     * Get a loaded plugin or null if not found.
-     *
-     * \param id the plugin id
-     * \return the plugin or empty one if not found
-     */
-    auto get(std::string_view id) const noexcept -> std::shared_ptr<plugin>;
-
-    /**
-     * Find a loaded plugin.
-     *
-     * \param id the plugin id
-     * \return the plugin
-     * \throw plugin_error on errors
-     */
-    auto require(std::string_view id) const -> std::shared_ptr<plugin>;
-
-    /**
-     * Add the specified plugin to the registry.
-     *
-     * \pre plg != nullptr
-     * \param plg the plugin
-     * \note the plugin is only added to the list, no action is performed on it
-     */
-    void add(std::shared_ptr<plugin> plg);
-
-    /**
-     * Add a loader.
-     *
-     * \pre loader != nullptr
-     * \param loader the loader
-     */
-    void add_loader(std::unique_ptr<plugin_loader> loader);
-
-    /**
-     * Get the configuration for the specified plugin.
-     *
-     * \param id the plugin id
-     * \return the configuration
-     */
-    auto get_options(std::string_view id) -> plugin::map;
-
-    /**
-     * Get the formats for the specified plugin.
-     *
-     * \param id the plugin id
-     * \return the formats
-     */
-    auto get_formats(std::string_view id) -> plugin::map;
-
-    /**
-     * Get the paths for the specified plugin.
-     *
-     * If none is defined, return the default ones.
-     *
-     * \param id the plugin id
-     * \return the paths
-     */
-    auto get_paths(std::string_view id) -> plugin::map;
-
-    /**
-     * 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
-     */
-    auto open(std::string_view id, std::string_view path) -> std::shared_ptr<plugin>;
-
-    /**
-     * Generic function for finding a plugin.
-     *
-     * \param id the plugin id
-     * \return the plugin or nullptr on failures
-     */
-    auto find(std::string_view id) -> std::shared_ptr<plugin>;
-
-    /**
-     * Convenient wrapper that loads a plugin, call handle_load and add it to
-     * the registry.
-     *
-     * Any errors are printed using logger.
-     *
-     * \param id the plugin id
-     * \param path the optional path (searched if empty)
-     */
-    void load(std::string_view name, std::string_view path = "");
-
-    /**
-     * Unload a plugin and remove it.
-     *
-     * \param id the plugin id
-     * \param name the plugin id
-     */
-    void unload(std::string_view id);
-
-    /**
-     * Reload a plugin by calling onReload.
-     *
-     * \param id the plugin id
-     * \throw std::exception on failures
-     */
-    void reload(std::string_view id);
-
-    /**
-     * 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->get_name(), ex.what());
-        } catch (...) {
-            throw plugin_error(plugin_error::exec_error, plugin->get_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->get_name());
-
-        exec(plugin, fn, std::forward<Args>(args)...);
-    }
-
-    /**
-     * Load all plugins.
-     *
-     * \param cfg the config
-     */
-    void load(const config& cfg) noexcept;
-};
-
-namespace logger {
-
-template <typename T>
-struct loggable_traits;
-
-/**
- * \brief Implement Loggable traits for plugin.
- */
-template <>
-struct loggable_traits<plugin> {
-    /**
-     * Return "plugin"
-     *
-     * \param plugin the plugin
-     * \return the category
-     */
-    static auto get_category(const plugin& plugin) -> std::string_view;
-
-    /**
-     * Return the plugin id.
-     *
-     * \param plugin the plugin
-     * \return the plugin id
-     */
-    static auto get_component(const plugin& plugin) -> std::string_view;
-};
-
-} // !logger
-
-} // !irccd
-
-#endif // !IRCCD_DAEMON_PLUGIN_SERVICE_HPP
--- a/libirccd/irccd/daemon/service/rule_service.cpp	Sun Aug 05 12:14:16 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,136 +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/daemon/irccd.hpp>
-#include <irccd/daemon/logger.hpp>
-#include <irccd/daemon/rule_util.hpp>
-
-#include <irccd/daemon/service/rule_service.hpp>
-
-namespace irccd {
-
-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(std::string_view server,
-                         std::string_view channel,
-                         std::string_view origin,
-                         std::string_view plugin,
-                         std::string_view event) noexcept
-{
-    bool result = true;
-
-    irccd_.get_log().debug("rule", "")
-        << "solving for server=" << server
-        << ", channel=" << channel
-        << ", origin=" << origin
-        << ", plugin=" << plugin
-        << ", event=" << event << std::endl;
-
-    int i = 0;
-    for (const auto& rule : rules_) {
-        auto action = rule.get_action() == rule::action::accept ? "accept" : "drop";
-
-        irccd_.get_log().debug(rule) << "candidate "  << i++ << ":" << std::endl;
-        irccd_.get_log().debug(rule) << "  servers: "  << string_util::join(rule.get_servers()) << std::endl;
-        irccd_.get_log().debug(rule) << "  channels: " << string_util::join(rule.get_channels()) << std::endl;
-        irccd_.get_log().debug(rule) << "  origins: "  << string_util::join(rule.get_origins()) << std::endl;
-        irccd_.get_log().debug(rule) << "  plugins: "  << string_util::join(rule.get_plugins()) << std::endl;
-        irccd_.get_log().debug(rule) << "  events: "   << string_util::join(rule.get_events()) << std::endl;
-        irccd_.get_log().debug(rule) << "  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) {
-        if (section.key() != "rule")
-            continue;
-
-        try {
-            rules_.push_back(rule_util::from_config(section));
-        } catch (const std::exception& ex) {
-            irccd_.get_log().warning("rule", "") << ex.what() << std::endl;
-        }
-    }
-}
-
-namespace logger {
-
-auto loggable_traits<rule>::get_category(const rule&) -> std::string_view
-{
-    return "rule";
-}
-
-auto loggable_traits<rule>::get_component(const rule&) -> std::string_view
-{
-    return "";
-}
-
-} // !logger
-
-} // !irccd
--- a/libirccd/irccd/daemon/service/rule_service.hpp	Sun Aug 05 12:14:16 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,152 +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 <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:
-    /**
-     * 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(std::string_view server,
-               std::string_view channel,
-               std::string_view origin,
-               std::string_view plugin,
-               std::string_view event) noexcept;
-
-    /**
-     * Load rules from the configuration.
-     *
-     * \param cfg the config
-     */
-    void load(const config& cfg) noexcept;
-};
-
-namespace logger {
-
-template <typename T>
-struct loggable_traits;
-
-template <>
-struct loggable_traits<rule> {
-    static auto get_category(const rule& rule) -> std::string_view;
-
-    static auto get_component(const rule& rule) -> std::string_view;
-};
-
-} // !logger
-
-} // !irccd
-
-#endif // !IRCCD_DAEMON_RULE_SERVICE_HPP
--- a/libirccd/irccd/daemon/service/server_service.cpp	Sun Aug 05 12:14:16 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,690 +0,0 @@
-/*
- * server_service.cpp -- 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/server_util.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 {
-
-class dispatcher {
-private:
-    irccd& irccd_;
-
-    template <typename EventNameFunc, typename ExecFunc>
-    void dispatch(std::string_view, std::string_view, std::string_view, EventNameFunc&&, ExecFunc);
-
-public:
-    dispatcher(irccd& irccd);
-    void operator()(const std::monostate&);
-    void operator()(const connect_event&);
-    void operator()(const disconnect_event&);
-    void operator()(const invite_event&);
-    void operator()(const join_event&);
-    void operator()(const kick_event&);
-    void operator()(const message_event&);
-    void operator()(const me_event&);
-    void operator()(const mode_event&);
-    void operator()(const names_event&);
-    void operator()(const nick_event&);
-    void operator()(const notice_event&);
-    void operator()(const part_event&);
-    void operator()(const topic_event&);
-    void operator()(const whois_event&);
-};
-
-template <typename EventNameFunc, typename ExecFunc>
-void dispatcher::dispatch(std::string_view server,
-                          std::string_view origin,
-                          std::string_view target,
-                          EventNameFunc&& name_func,
-                          ExecFunc exec_func)
-{
-    for (const auto& plugin : irccd_.plugins().all()) {
-        const auto eventname = name_func(*plugin);
-        const auto allowed = irccd_.rules().solve(server, target, origin, plugin->get_name(), eventname);
-
-        if (!allowed) {
-            irccd_.get_log().debug("rule", "") << "event skipped on match" << std::endl;
-            continue;
-        }
-
-        irccd_.get_log().debug("rule", "") << "event allowed" << std::endl;
-
-        try {
-            exec_func(*plugin);
-        } catch (const std::exception& ex) {
-            irccd_.get_log().warning(*plugin) << ex.what() << std::endl;
-        }
-    }
-}
-
-dispatcher::dispatcher(irccd& irccd)
-    : irccd_(irccd)
-{
-}
-
-void dispatcher::operator()(const std::monostate&)
-{
-}
-
-void dispatcher::operator()(const connect_event& ev)
-{
-    irccd_.get_log().debug(*ev.server) << "event onConnect" << std::endl;
-    irccd_.transports().broadcast(nlohmann::json::object({
-        { "event",      "onConnect"         },
-        { "server",     ev.server->get_id() }
-    }));
-
-    dispatch(ev.server->get_id(), /* origin */ "", /* channel */ "",
-        [=] (plugin&) -> std::string {
-            return "onConnect";
-        },
-        [=] (plugin& plugin) {
-            plugin.handle_connect(irccd_, ev);
-        }
-    );
-}
-
-void dispatcher::operator()(const disconnect_event& ev)
-{
-    irccd_.get_log().debug(*ev.server) << "event onDisconnect" << std::endl;
-    irccd_.transports().broadcast(nlohmann::json::object({
-        { "event",      "onDisconnect"      },
-        { "server",     ev.server->get_id() }
-    }));
-
-    dispatch(ev.server->get_id(), /* origin */ "", /* channel */ "",
-        [=] (plugin&) -> std::string {
-            return "onDisconnect";
-        },
-        [=] (plugin& plugin) {
-            plugin.handle_disconnect(irccd_, ev);
-        }
-    );
-}
-
-void dispatcher::operator()(const invite_event& ev)
-{
-    irccd_.get_log().debug(*ev.server) << "event onInvite:" << std::endl;
-    irccd_.get_log().debug(*ev.server) << "  origin: " << ev.origin << std::endl;
-    irccd_.get_log().debug(*ev.server) << "  channel: " << ev.channel << std::endl;
-    irccd_.get_log().debug(*ev.server) << "  target: " << ev.nickname << std::endl;
-
-    irccd_.transports().broadcast(nlohmann::json::object({
-        { "event",      "onInvite"          },
-        { "server",     ev.server->get_id() },
-        { "origin",     ev.origin           },
-        { "channel",    ev.channel          }
-    }));
-
-    dispatch(ev.server->get_id(), ev.origin, ev.channel,
-        [=] (plugin&) -> std::string {
-            return "onInvite";
-        },
-        [=] (plugin& plugin) {
-            plugin.handle_invite(irccd_, ev);
-        }
-    );
-}
-
-void dispatcher::operator()(const join_event& ev)
-{
-    irccd_.get_log().debug(*ev.server) << "event onJoin:" << std::endl;
-    irccd_.get_log().debug(*ev.server) << "  origin: " << ev.origin << std::endl;
-    irccd_.get_log().debug(*ev.server) << "  channel: " << ev.channel << std::endl;
-
-    irccd_.transports().broadcast(nlohmann::json::object({
-        { "event",      "onJoin"            },
-        { "server",     ev.server->get_id() },
-        { "origin",     ev.origin           },
-        { "channel",    ev.channel          }
-    }));
-
-    dispatch(ev.server->get_id(), ev.origin, ev.channel,
-        [=] (plugin&) -> std::string {
-            return "onJoin";
-        },
-        [=] (plugin& plugin) {
-            plugin.handle_join(irccd_, ev);
-        }
-    );
-}
-
-void dispatcher::operator()(const kick_event& ev)
-{
-    irccd_.get_log().debug(*ev.server) << "event onKick:" << std::endl;
-    irccd_.get_log().debug(*ev.server) << "  origin: " << ev.origin << std::endl;
-    irccd_.get_log().debug(*ev.server) << "  channel: " << ev.channel << std::endl;
-    irccd_.get_log().debug(*ev.server) << "  target: " << ev.target << std::endl;
-    irccd_.get_log().debug(*ev.server) << "  reason: " << ev.reason << std::endl;
-
-    irccd_.transports().broadcast(nlohmann::json::object({
-        { "event",      "onKick"            },
-        { "server",     ev.server->get_id() },
-        { "origin",     ev.origin           },
-        { "channel",    ev.channel          },
-        { "target",     ev.target           },
-        { "reason",     ev.reason           }
-    }));
-
-    dispatch(ev.server->get_id(), ev.origin, ev.channel,
-        [=] (plugin&) -> std::string {
-            return "onKick";
-        },
-        [=] (plugin& plugin) {
-            plugin.handle_kick(irccd_, ev);
-        }
-    );
-}
-
-void dispatcher::operator()(const message_event& ev)
-{
-    irccd_.get_log().debug(*ev.server) << "event onMessage:" << std::endl;
-    irccd_.get_log().debug(*ev.server) << "  origin: " << ev.origin << std::endl;
-    irccd_.get_log().debug(*ev.server) << "  channel: " << ev.channel << std::endl;
-    irccd_.get_log().debug(*ev.server) << "  message: " << ev.message << std::endl;
-
-    irccd_.transports().broadcast(nlohmann::json::object({
-        { "event",      "onMessage"         },
-        { "server",     ev.server->get_id() },
-        { "origin",     ev.origin           },
-        { "channel",    ev.channel          },
-        { "message",    ev.message          }
-    }));
-
-    dispatch(ev.server->get_id(), ev.origin, ev.channel,
-        [=] (plugin& plugin) -> std::string {
-            return server_util::parse_message(
-                ev.message,
-                ev.server->get_command_char(),
-                plugin.get_id()
-            ).type == server_util::message_pack::type::command ? "onCommand" : "onMessage";
-        },
-        [=] (plugin& plugin) mutable {
-            auto copy = ev;
-            auto pack = server_util::parse_message(
-                copy.message,
-                copy.server->get_command_char(),
-                plugin.get_id()
-            );
-
-            copy.message = pack.message;
-
-            if (pack.type == server_util::message_pack::type::command)
-                plugin.handle_command(irccd_, copy);
-            else
-                plugin.handle_message(irccd_, copy);
-        }
-    );
-}
-
-void dispatcher::operator()(const me_event& ev)
-{
-    irccd_.get_log().debug(*ev.server) << "event onMe:" << std::endl;
-    irccd_.get_log().debug(*ev.server) << "  origin: " << ev.origin << std::endl;
-    irccd_.get_log().debug(*ev.server) << "  target: " << ev.channel << std::endl;
-    irccd_.get_log().debug(*ev.server) << "  message: " << ev.message << std::endl;
-
-    irccd_.transports().broadcast(nlohmann::json::object({
-        { "event",      "onMe"              },
-        { "server",     ev.server->get_id() },
-        { "origin",     ev.origin           },
-        { "target",     ev.channel          },
-        { "message",    ev.message          }
-    }));
-
-    dispatch(ev.server->get_id(), ev.origin, ev.channel,
-        [=] (plugin&) -> std::string {
-            return "onMe";
-        },
-        [=] (plugin& plugin) {
-            plugin.handle_me(irccd_, ev);
-        }
-    );
-}
-
-void dispatcher::operator()(const mode_event& ev)
-{
-    irccd_.get_log().debug(*ev.server) << "event onMode" << std::endl;
-    irccd_.get_log().debug(*ev.server) << "  origin: " << ev.origin << std::endl;
-    irccd_.get_log().debug(*ev.server) << "  channel: " << ev.channel << std::endl;
-    irccd_.get_log().debug(*ev.server) << "  mode: " << ev.mode << std::endl;
-    irccd_.get_log().debug(*ev.server) << "  limit: " << ev.limit << std::endl;
-    irccd_.get_log().debug(*ev.server) << "  user: " << ev.user << std::endl;
-    irccd_.get_log().debug(*ev.server) << "  mask: " << ev.mask << std::endl;
-
-    irccd_.transports().broadcast(nlohmann::json::object({
-        { "event",      "onMode"            },
-        { "server",     ev.server->get_id() },
-        { "origin",     ev.origin           },
-        { "channel",    ev.channel          },
-        { "mode",       ev.mode             },
-        { "limit",      ev.limit            },
-        { "user",       ev.user             },
-        { "mask",       ev.mask             }
-    }));
-
-    dispatch(ev.server->get_id(), ev.origin, /* channel */ "",
-        [=] (plugin &) -> std::string {
-            return "onMode";
-        },
-        [=] (plugin &plugin) {
-            plugin.handle_mode(irccd_, ev);
-        }
-    );
-}
-
-void dispatcher::operator()(const names_event& ev)
-{
-    irccd_.get_log().debug(*ev.server) << "event onNames:" << std::endl;
-    irccd_.get_log().debug(*ev.server) << "  channel: " << ev.channel << std::endl;
-    irccd_.get_log().debug(*ev.server) << "  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->get_id() },
-        { "channel",    ev.channel          },
-        { "names",      std::move(names)    }
-    }));
-
-    dispatch(ev.server->get_id(), /* origin */ "", ev.channel,
-        [=] (plugin&) -> std::string {
-            return "onNames";
-        },
-        [=] (plugin& plugin) {
-            plugin.handle_names(irccd_, ev);
-        }
-    );
-}
-
-void dispatcher::operator()(const nick_event& ev)
-{
-    irccd_.get_log().debug(*ev.server) << "event onNick:" << std::endl;
-    irccd_.get_log().debug(*ev.server) << "  origin: " << ev.origin << std::endl;
-    irccd_.get_log().debug(*ev.server) << "  nickname: " << ev.nickname << std::endl;
-
-    irccd_.transports().broadcast(nlohmann::json::object({
-        { "event",      "onNick"            },
-        { "server",     ev.server->get_id() },
-        { "origin",     ev.origin           },
-        { "nickname",   ev.nickname         }
-    }));
-
-    dispatch(ev.server->get_id(), ev.origin, /* channel */ "",
-        [=] (plugin&) -> std::string {
-            return "onNick";
-        },
-        [=] (plugin& plugin) {
-            plugin.handle_nick(irccd_, ev);
-        }
-    );
-}
-
-void dispatcher::operator()(const notice_event& ev)
-{
-    irccd_.get_log().debug(*ev.server) << "event onNotice:" << std::endl;
-    irccd_.get_log().debug(*ev.server) << "  origin: " << ev.origin << std::endl;
-    irccd_.get_log().debug(*ev.server) << "  channel: " << ev.channel << std::endl;
-    irccd_.get_log().debug(*ev.server) << "  message: " << ev.message << std::endl;
-
-    irccd_.transports().broadcast(nlohmann::json::object({
-        { "event",      "onNotice"          },
-        { "server",     ev.server->get_id() },
-        { "origin",     ev.origin           },
-        { "channel",    ev.channel          },
-        { "message",    ev.message          }
-    }));
-
-    dispatch(ev.server->get_id(), ev.origin, /* channel */ "",
-        [=] (plugin&) -> std::string {
-            return "onNotice";
-        },
-        [=] (plugin& plugin) {
-            plugin.handle_notice(irccd_, ev);
-        }
-    );
-}
-
-void dispatcher::operator()(const part_event& ev)
-{
-    irccd_.get_log().debug(*ev.server) << "event onPart:" << std::endl;
-    irccd_.get_log().debug(*ev.server) << "  origin: " << ev.origin << std::endl;
-    irccd_.get_log().debug(*ev.server) << "  channel: " << ev.channel << std::endl;
-    irccd_.get_log().debug(*ev.server) << "  reason: " << ev.reason << std::endl;
-
-    irccd_.transports().broadcast(nlohmann::json::object({
-        { "event",      "onPart"            },
-        { "server",     ev.server->get_id() },
-        { "origin",     ev.origin           },
-        { "channel",    ev.channel          },
-        { "reason",     ev.reason           }
-    }));
-
-    dispatch(ev.server->get_id(), ev.origin, ev.channel,
-        [=] (plugin&) -> std::string {
-            return "onPart";
-        },
-        [=] (plugin& plugin) {
-            plugin.handle_part(irccd_, ev);
-        }
-    );
-}
-
-void dispatcher::operator()(const topic_event& ev)
-{
-    irccd_.get_log().debug(*ev.server) << "event onTopic:" << std::endl;
-    irccd_.get_log().debug(*ev.server) << "  origin: " << ev.origin << std::endl;
-    irccd_.get_log().debug(*ev.server) << "  channel: " << ev.channel << std::endl;
-    irccd_.get_log().debug(*ev.server) << "  topic: " << ev.topic << std::endl;
-
-    irccd_.transports().broadcast(nlohmann::json::object({
-        { "event",      "onTopic"           },
-        { "server",     ev.server->get_id() },
-        { "origin",     ev.origin           },
-        { "channel",    ev.channel          },
-        { "topic",      ev.topic            }
-    }));
-
-    dispatch(ev.server->get_id(), ev.origin, ev.channel,
-        [=] (plugin&) -> std::string {
-            return "onTopic";
-        },
-        [=] (plugin& plugin) {
-            plugin.handle_topic(irccd_, ev);
-        }
-    );
-}
-
-void dispatcher::operator()(const whois_event& ev)
-{
-    irccd_.get_log().debug(*ev.server) << "event onWhois" << std::endl;
-    irccd_.get_log().debug(*ev.server) << "  nickname: " << ev.whois.nick << std::endl;
-    irccd_.get_log().debug(*ev.server) << "  username: " << ev.whois.user << std::endl;
-    irccd_.get_log().debug(*ev.server) << "  host: " << ev.whois.host << std::endl;
-    irccd_.get_log().debug(*ev.server) << "  realname: " << ev.whois.realname << std::endl;
-    irccd_.get_log().debug(*ev.server) << "  channels: " << string_util::join(ev.whois.channels, ", ") << std::endl;
-
-    irccd_.transports().broadcast(nlohmann::json::object({
-        { "event",      "onWhois"           },
-        { "server",     ev.server->get_id() },
-        { "nickname",   ev.whois.nick       },
-        { "username",   ev.whois.user       },
-        { "host",       ev.whois.host       },
-        { "realname",   ev.whois.realname   }
-    }));
-
-    dispatch(ev.server->get_id(), /* origin */ "", /* channel */ "",
-        [=] (plugin&) -> std::string {
-            return "onWhois";
-        },
-        [=] (plugin& plugin) {
-            plugin.handle_whois(irccd_, ev);
-        }
-    );
-}
-
-} // !namespace
-
-void server_service::handle_error(const std::shared_ptr<server>& server,
-                                  const std::error_code& code)
-{
-    assert(server);
-
-    irccd_.get_log().warning(*server) << code.message() << std::endl;
-
-    irccd_.get_log().warning(*server) << int(server->get_options()) << std::endl;
-
-    if ((server->get_options() & server::options::auto_reconnect) != server::options::auto_reconnect)
-        remove(server->get_id());
-    else {
-        irccd_.get_log().info(*server) << "reconnecting in "
-            << server->get_reconnect_delay() << " second(s)" << std::endl;
-        wait(server);
-    }
-}
-
-void server_service::handle_wait(const std::shared_ptr<server>& server, const std::error_code& code)
-{
-    /*
-     * The timer runs on his own control, it will complete either if the delay
-     * was reached, there was an error or if the io_context was called to cancel
-     * all pending operations.
-     *
-     * This means while the timer is running someone may already have ask a
-     * server for explicit reconnection (e.g. remote command, plugin). Thus we
-     * check for server state and if it is still present in service.
-     */
-    if (code && code != std::errc::operation_canceled) {
-        irccd_.get_log().warning(*server) << code.message() << std::endl;
-        return;
-    }
-
-    if (server->get_state() == server::state::connected || !has(server->get_id()))
-        return;
-
-    connect(server);
-}
-
-void server_service::handle_recv(const std::shared_ptr<server>& server,
-                                 const std::error_code& code,
-                                 const event& event)
-{
-    assert(server);
-
-    if (code)
-        handle_error(server, code);
-    else {
-        recv(server);
-        std::visit(dispatcher(irccd_), event);
-    }
-}
-
-void server_service::handle_connect(const std::shared_ptr<server>& server, const std::error_code& code)
-{
-    if (code)
-        handle_error(server, code);
-    else
-        recv(server);
-}
-
-void server_service::wait(const std::shared_ptr<server>& server)
-{
-    assert(server);
-
-    auto timer = std::make_shared<boost::asio::deadline_timer>(irccd_.get_service());
-
-    timer->expires_from_now(boost::posix_time::seconds(server->get_reconnect_delay()));
-    timer->async_wait([this, server, timer] (auto code) {
-        handle_wait(server, code);
-    });
-}
-
-void server_service::recv(const std::shared_ptr<server>& server)
-{
-    assert(server);
-
-    server->recv([this, server] (auto code, auto event) {
-        handle_recv(server, code, event);
-    });
-}
-
-void server_service::connect(const std::shared_ptr<server>& server)
-{
-    assert(server);
-
-    server->connect([this, server] (auto code) {
-        handle_connect(server, code);
-    });
-}
-
-server_service::server_service(irccd &irccd)
-    : irccd_(irccd)
-{
-}
-
-auto server_service::all() const noexcept -> const std::vector<std::shared_ptr<server>>&
-{
-    return servers_;
-}
-
-auto server_service::has(const std::string& name) const noexcept -> bool
-{
-    return std::count_if(servers_.begin(), servers_.end(), [&] (const auto& server) {
-        return server->get_id() == name;
-    }) > 0;
-}
-
-void server_service::add(std::shared_ptr<server> server)
-{
-    assert(server);
-    assert(!has(server->get_id()));
-
-    servers_.push_back(server);
-    connect(server);
-}
-
-auto server_service::get(std::string_view name) const noexcept -> std::shared_ptr<server>
-{
-    const auto it = std::find_if(servers_.begin(), servers_.end(), [&] (const auto& server) {
-        return server->get_id() == name;
-    });
-
-    if (it == servers_.end())
-        return nullptr;
-
-    return *it;
-}
-
-auto server_service::require(std::string_view name) const -> std::shared_ptr<server>
-{
-    if (!string_util::is_identifier(name))
-        throw server_error(server_error::invalid_identifier);
-
-    const auto s = get(name);
-
-    if (!s)
-        throw server_error(server_error::not_found);
-
-    return s;
-}
-
-void server_service::disconnect(std::string_view id)
-{
-    const auto s = require(id);
-
-    s->disconnect();
-    dispatcher{irccd_}(disconnect_event{s});
-}
-
-void server_service::reconnect(std::string_view id)
-{
-    disconnect(id);
-    connect(require(id));
-}
-
-void server_service::reconnect()
-{
-    for (const auto& s : servers_) {
-        try {
-            s->disconnect();
-            dispatcher{irccd_}(disconnect_event{s});
-            connect(s);
-        } catch (const server_error& ex) {
-            irccd_.get_log().warning(*s) << ex.what() << std::endl;
-        }
-    }
-}
-
-void server_service::remove(std::string_view name)
-{
-    const auto it = std::find_if(servers_.begin(), servers_.end(), [&] (const auto& server) {
-        return server->get_id() == name;
-    });
-
-    if (it != servers_.end()) {
-        (*it)->disconnect();
-        servers_.erase(it);
-    }
-}
-
-void server_service::clear() noexcept
-{
-    /*
-     * Copy the array, because disconnect() may trigger on_die signal which
-     * erase the server from itself.
-     */
-    const auto save = servers_;
-
-    for (const auto& server : save)
-        server->disconnect();
-
-    servers_.clear();
-}
-
-void server_service::load(const config& cfg) noexcept
-{
-    for (const auto& section : cfg) {
-        if (section.key() != "server")
-            continue;
-
-        const auto id = section.get("name").value();
-
-        try {
-            auto server = server_util::from_config(irccd_.get_service(), cfg, section);
-
-            if (has(server->get_id()))
-                throw server_error(server_error::already_exists);
-
-            add(std::move(server));
-        } catch (const std::exception& ex) {
-            irccd_.get_log().warning("server", id) << ex.what() << std::endl;
-        }
-    }
-}
-
-namespace logger {
-
-auto loggable_traits<server>::get_category(const server&) -> std::string_view
-{
-    return "server";
-}
-
-auto loggable_traits<server>::get_component(const server& sv) -> std::string_view
-{
-    return sv.get_id();
-}
-
-} // !logger
-
-} // !irccd
--- a/libirccd/irccd/daemon/service/server_service.hpp	Sun Aug 05 12:14:16 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,164 +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 <system_error>
-#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_error(const std::shared_ptr<server>&, const std::error_code&);
-    void handle_wait(const std::shared_ptr<server>&, const std::error_code&);
-    void handle_recv(const std::shared_ptr<server>&, const std::error_code&, const event&);
-    void handle_connect(const std::shared_ptr<server>&, const std::error_code&);
-
-    void wait(const std::shared_ptr<server>&);
-    void recv(const std::shared_ptr<server>&);
-    void connect(const std::shared_ptr<server>&);
-
-public:
-    /**
-     * Create the server service.
-     */
-    server_service(irccd& instance);
-
-    /**
-     * Get the list of servers
-     *
-     * \return the servers
-     */
-    auto all() const noexcept -> const std::vector<std::shared_ptr<server>>&;
-
-    /**
-     * Check if a server exists.
-     *
-     * \param name the name
-     * \return true if exists
-     */
-    auto has(const std::string& name) const noexcept -> bool;
-
-    /**
-     * 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
-     */
-    auto get(std::string_view name) const noexcept -> std::shared_ptr<server>;
-
-    /**
-     * Find a server from a JSON object.
-     *
-     * \param name the server name
-     * \return the server
-     * \throw server_error on errors
-     */
-    auto require(std::string_view name) const -> std::shared_ptr<server>;
-
-    /**
-     * Force disconnection, this also call plugin::handle_disconnect handler.
-     *
-     * \param id the server id
-     * \throw server_error on errors
-     */
-    void disconnect(std::string_view id);
-
-    /**
-     * Force reconnection, this also call plugin::handle_disconnect handler.
-     *
-     * \param id the server id
-     * \return the server
-     * \throw server_error on errors
-     */
-    void reconnect(std::string_view id);
-
-    /**
-     * Force reconnection of all servers.
-     */
-    void reconnect();
-
-    /**
-     * Remove a server from the irccd instance.
-     *
-     * The server if any, will be disconnected.
-     *
-     * \param name the server name
-     */
-    void remove(std::string_view 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;
-};
-
-namespace logger {
-
-template <typename T>
-struct loggable_traits;
-
-template <>
-struct loggable_traits<server> {
-    static auto get_category(const server& server) -> std::string_view;
-
-    static auto get_component(const server& server) -> std::string_view;
-};
-
-} // !logger
-
-} // !irccd
-
-#endif // !IRCCD_DAEMON_SERVER_SERVICE_HPP
--- a/libirccd/irccd/daemon/service/transport_service.cpp	Sun Aug 05 12:14:16 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,140 +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/json_util.hpp>
-
-#include <irccd/daemon/command.hpp>
-#include <irccd/daemon/irccd.hpp>
-#include <irccd/daemon/logger.hpp>
-#include <irccd/daemon/transport_util.hpp>
-#include <irccd/daemon/transport_client.hpp>
-
-#include "transport_service.hpp"
-
-namespace irccd {
-
-void transport_service::handle_command(std::shared_ptr<transport_client> tc, const nlohmann::json& object)
-{
-    assert(object.is_object());
-
-    const json_util::document doc(object);
-    const auto name = doc.get<std::string>("command");
-
-    if (!name) {
-        tc->error(irccd_error::invalid_message);
-        return;
-    }
-
-    const auto cmd = std::find_if(commands_.begin(), commands_.end(), [&] (const auto& cptr) {
-        return cptr->get_name() == *name;
-    });
-
-    if (cmd == commands_.end())
-        tc->error(irccd_error::invalid_command, *name);
-    else {
-        try {
-            (*cmd)->exec(irccd_, *tc, doc);
-        } catch (const std::system_error& ex) {
-            tc->error(ex.code(), (*cmd)->get_name());
-        } catch (const std::exception& ex) {
-            irccd_.get_log().warning("transport", "")
-                << "unknown error not reported: "
-                << ex.what() << std::endl;
-        }
-    }
-}
-
-void transport_service::do_recv(std::shared_ptr<transport_client> tc)
-{
-    tc->read([this, tc] (auto code, auto json) {
-        switch (static_cast<std::errc>(code.value())) {
-        case std::errc::not_connected:
-            irccd_.get_log().info("transport", "") << "client disconnected" << std::endl;
-            break;
-        case std::errc::invalid_argument:
-            tc->error(irccd_error::invalid_message);
-            break;
-        default:
-            // Other error may still happen.
-            if (!code) {
-                handle_command(tc, json);
-
-                if (tc->get_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) {
-            do_accept(ts);
-            do_recv(std::move(client));
-
-            irccd_.get_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::shared_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->get_clients())
-            client->write(json);
-}
-
-void transport_service::load(const config& cfg) noexcept
-{
-    for (const auto& section : cfg) {
-        if (section.key() != "transport")
-            continue;
-
-        try {
-            add(transport_util::from_config(irccd_.get_service(), section));
-        } catch (const std::exception& ex) {
-            irccd_.get_log().warning("transport", "") << ex.what() << std::endl;
-        }
-    }
-}
-
-} // !irccd
--- a/libirccd/irccd/daemon/service/transport_service.hpp	Sun Aug 05 12:14:16 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,112 +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 <irccd/daemon/transport_client.hpp>
-#include <irccd/daemon/transport_server.hpp>
-
-namespace irccd {
-
-class command;
-class config;
-class irccd;
-
-/**
- * \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::shared_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::shared_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_server.cpp	Sun Aug 05 12:14:16 2018 +0200
+++ b/libirccd/irccd/daemon/transport_server.cpp	Sun Aug 05 15:47:10 2018 +0200
@@ -24,6 +24,7 @@
 #include <irccd/json_util.hpp>
 
 #include "irccd.hpp"
+#include "transport_client.hpp"
 #include "transport_server.hpp"
 
 namespace irccd {
--- a/libirccd/irccd/daemon/transport_server.hpp	Sun Aug 05 12:14:16 2018 +0200
+++ b/libirccd/irccd/daemon/transport_server.hpp	Sun Aug 05 15:47:10 2018 +0200
@@ -34,9 +34,9 @@
 
 #include <irccd/acceptor.hpp>
 
-#include "transport_client.hpp"
+namespace irccd {
 
-namespace irccd {
+class transport_client;
 
 /**
  * \brief Abstract transport server class.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/daemon/transport_service.cpp	Sun Aug 05 15:47:10 2018 +0200
@@ -0,0 +1,142 @@
+/*
+ * 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/json_util.hpp>
+
+#include <irccd/daemon/command.hpp>
+#include <irccd/daemon/irccd.hpp>
+#include <irccd/daemon/logger.hpp>
+#include <irccd/daemon/transport_util.hpp>
+#include <irccd/daemon/transport_client.hpp>
+#include <irccd/daemon/transport_server.hpp>
+
+
+#include "transport_service.hpp"
+
+namespace irccd {
+
+void transport_service::handle_command(std::shared_ptr<transport_client> tc, const nlohmann::json& object)
+{
+    assert(object.is_object());
+
+    const json_util::document doc(object);
+    const auto name = doc.get<std::string>("command");
+
+    if (!name) {
+        tc->error(irccd_error::invalid_message);
+        return;
+    }
+
+    const auto cmd = std::find_if(commands_.begin(), commands_.end(), [&] (const auto& cptr) {
+        return cptr->get_name() == *name;
+    });
+
+    if (cmd == commands_.end())
+        tc->error(irccd_error::invalid_command, *name);
+    else {
+        try {
+            (*cmd)->exec(irccd_, *tc, doc);
+        } catch (const std::system_error& ex) {
+            tc->error(ex.code(), (*cmd)->get_name());
+        } catch (const std::exception& ex) {
+            irccd_.get_log().warning("transport", "")
+                << "unknown error not reported: "
+                << ex.what() << std::endl;
+        }
+    }
+}
+
+void transport_service::do_recv(std::shared_ptr<transport_client> tc)
+{
+    tc->read([this, tc] (auto code, auto json) {
+        switch (static_cast<std::errc>(code.value())) {
+        case std::errc::not_connected:
+            irccd_.get_log().info("transport", "") << "client disconnected" << std::endl;
+            break;
+        case std::errc::invalid_argument:
+            tc->error(irccd_error::invalid_message);
+            break;
+        default:
+            // Other error may still happen.
+            if (!code) {
+                handle_command(tc, json);
+
+                if (tc->get_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) {
+            do_accept(ts);
+            do_recv(std::move(client));
+
+            irccd_.get_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::shared_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->get_clients())
+            client->write(json);
+}
+
+void transport_service::load(const config& cfg) noexcept
+{
+    for (const auto& section : cfg) {
+        if (section.key() != "transport")
+            continue;
+
+        try {
+            add(transport_util::from_config(irccd_.get_service(), section));
+        } catch (const std::exception& ex) {
+            irccd_.get_log().warning("transport", "") << ex.what() << std::endl;
+        }
+    }
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/daemon/transport_service.hpp	Sun Aug 05 15:47:10 2018 +0200
@@ -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>
+
+namespace irccd {
+
+class command;
+class config;
+class irccd;
+class transport_client;
+class transport_server;
+
+/**
+ * \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::shared_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::shared_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	Sun Aug 05 12:14:16 2018 +0200
+++ b/tests/src/libirccd-js/js-plugin/main.cpp	Sun Aug 05 15:47:10 2018 +0200
@@ -21,7 +21,7 @@
 #include <boost/test/unit_test.hpp>
 
 #include <irccd/daemon/irccd.hpp>
-#include <irccd/daemon/service/plugin_service.hpp>
+#include <irccd/daemon/plugin_service.hpp>
 
 #include <irccd/js/irccd_jsapi.hpp>
 #include <irccd/js/js_plugin.hpp>
--- a/tests/src/libirccd/command-plugin-config/main.cpp	Sun Aug 05 12:14:16 2018 +0200
+++ b/tests/src/libirccd/command-plugin-config/main.cpp	Sun Aug 05 15:47:10 2018 +0200
@@ -19,7 +19,7 @@
 #define BOOST_TEST_MODULE "plugin-config"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/daemon/service/plugin_service.hpp>
+#include <irccd/daemon/plugin_service.hpp>
 
 #include <irccd/test/command_test.hpp>
 #include <irccd/test/mock_plugin.hpp>
--- a/tests/src/libirccd/command-plugin-info/main.cpp	Sun Aug 05 12:14:16 2018 +0200
+++ b/tests/src/libirccd/command-plugin-info/main.cpp	Sun Aug 05 15:47:10 2018 +0200
@@ -19,7 +19,7 @@
 #define BOOST_TEST_MODULE "plugin-info"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/daemon/service/plugin_service.hpp>
+#include <irccd/daemon/plugin_service.hpp>
 
 #include <irccd/test/command_test.hpp>
 #include <irccd/test/mock_plugin.hpp>
--- a/tests/src/libirccd/command-plugin-list/main.cpp	Sun Aug 05 12:14:16 2018 +0200
+++ b/tests/src/libirccd/command-plugin-list/main.cpp	Sun Aug 05 15:47:10 2018 +0200
@@ -19,7 +19,7 @@
 #define BOOST_TEST_MODULE "plugin-list"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/daemon/service/plugin_service.hpp>
+#include <irccd/daemon/plugin_service.hpp>
 
 #include <irccd/test/command_test.hpp>
 #include <irccd/test/mock_plugin.hpp>
--- a/tests/src/libirccd/command-plugin-load/main.cpp	Sun Aug 05 12:14:16 2018 +0200
+++ b/tests/src/libirccd/command-plugin-load/main.cpp	Sun Aug 05 15:47:10 2018 +0200
@@ -19,7 +19,7 @@
 #define BOOST_TEST_MODULE "plugin-load"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/daemon/service/plugin_service.hpp>
+#include <irccd/daemon/plugin_service.hpp>
 
 #include <irccd/test/command_test.hpp>
 #include <irccd/test/mock_plugin.hpp>
--- a/tests/src/libirccd/command-plugin-reload/main.cpp	Sun Aug 05 12:14:16 2018 +0200
+++ b/tests/src/libirccd/command-plugin-reload/main.cpp	Sun Aug 05 15:47:10 2018 +0200
@@ -19,7 +19,7 @@
 #define BOOST_TEST_MODULE "plugin-reload"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/daemon/service/plugin_service.hpp>
+#include <irccd/daemon/plugin_service.hpp>
 
 #include <irccd/test/command_test.hpp>
 #include <irccd/test/mock_plugin.hpp>
--- a/tests/src/libirccd/command-plugin-unload/main.cpp	Sun Aug 05 12:14:16 2018 +0200
+++ b/tests/src/libirccd/command-plugin-unload/main.cpp	Sun Aug 05 15:47:10 2018 +0200
@@ -19,7 +19,7 @@
 #define BOOST_TEST_MODULE "plugin-unload"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/daemon/service/plugin_service.hpp>
+#include <irccd/daemon/plugin_service.hpp>
 
 #include <irccd/test/command_test.hpp>
 #include <irccd/test/mock_plugin.hpp>
--- a/tests/src/libirccd/command-rule-add/main.cpp	Sun Aug 05 12:14:16 2018 +0200
+++ b/tests/src/libirccd/command-rule-add/main.cpp	Sun Aug 05 15:47:10 2018 +0200
@@ -21,7 +21,7 @@
 
 #include <irccd/json_util.hpp>
 
-#include <irccd/daemon/service/rule_service.hpp>
+#include <irccd/daemon/rule_service.hpp>
 
 #include <irccd/test/command_test.hpp>
 
--- a/tests/src/libirccd/command-rule-edit/main.cpp	Sun Aug 05 12:14:16 2018 +0200
+++ b/tests/src/libirccd/command-rule-edit/main.cpp	Sun Aug 05 15:47:10 2018 +0200
@@ -21,7 +21,7 @@
 
 #include <irccd/json_util.hpp>
 
-#include <irccd/daemon/service/rule_service.hpp>
+#include <irccd/daemon/rule_service.hpp>
 
 #include <irccd/test/command_test.hpp>
 
--- a/tests/src/libirccd/command-rule-info/main.cpp	Sun Aug 05 12:14:16 2018 +0200
+++ b/tests/src/libirccd/command-rule-info/main.cpp	Sun Aug 05 15:47:10 2018 +0200
@@ -21,7 +21,7 @@
 
 #include <irccd/json_util.hpp>
 
-#include <irccd/daemon/service/rule_service.hpp>
+#include <irccd/daemon/rule_service.hpp>
 
 #include <irccd/test/command_test.hpp>
 
--- a/tests/src/libirccd/command-rule-list/main.cpp	Sun Aug 05 12:14:16 2018 +0200
+++ b/tests/src/libirccd/command-rule-list/main.cpp	Sun Aug 05 15:47:10 2018 +0200
@@ -21,7 +21,7 @@
 
 #include <irccd/json_util.hpp>
 
-#include <irccd/daemon/service/rule_service.hpp>
+#include <irccd/daemon/rule_service.hpp>
 
 #include <irccd/test/command_test.hpp>
 
--- a/tests/src/libirccd/command-rule-move/main.cpp	Sun Aug 05 12:14:16 2018 +0200
+++ b/tests/src/libirccd/command-rule-move/main.cpp	Sun Aug 05 15:47:10 2018 +0200
@@ -21,7 +21,7 @@
 
 #include <irccd/json_util.hpp>
 
-#include <irccd/daemon/service/rule_service.hpp>
+#include <irccd/daemon/rule_service.hpp>
 
 #include <irccd/test/command_test.hpp>
 
--- a/tests/src/libirccd/command-rule-remove/main.cpp	Sun Aug 05 12:14:16 2018 +0200
+++ b/tests/src/libirccd/command-rule-remove/main.cpp	Sun Aug 05 15:47:10 2018 +0200
@@ -21,7 +21,7 @@
 
 #include <irccd/json_util.hpp>
 
-#include <irccd/daemon/service/rule_service.hpp>
+#include <irccd/daemon/rule_service.hpp>
 
 #include <irccd/test/command_test.hpp>
 
--- a/tests/src/libirccd/command-server-connect/main.cpp	Sun Aug 05 12:14:16 2018 +0200
+++ b/tests/src/libirccd/command-server-connect/main.cpp	Sun Aug 05 15:47:10 2018 +0200
@@ -19,7 +19,7 @@
 #define BOOST_TEST_MODULE "server-connect"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/daemon/service/server_service.hpp>
+#include <irccd/daemon/server_service.hpp>
 
 #include <irccd/test/command_test.hpp>
 #include <irccd/test/mock_server.hpp>
--- a/tests/src/libirccd/command-server-disconnect/main.cpp	Sun Aug 05 12:14:16 2018 +0200
+++ b/tests/src/libirccd/command-server-disconnect/main.cpp	Sun Aug 05 15:47:10 2018 +0200
@@ -19,7 +19,7 @@
 #define BOOST_TEST_MODULE "server-disconnect"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/daemon/service/server_service.hpp>
+#include <irccd/daemon/server_service.hpp>
 
 #include <irccd/test/mock_server.hpp>
 #include <irccd/test/command_test.hpp>
--- a/tests/src/libirccd/command-server-info/main.cpp	Sun Aug 05 12:14:16 2018 +0200
+++ b/tests/src/libirccd/command-server-info/main.cpp	Sun Aug 05 15:47:10 2018 +0200
@@ -19,7 +19,7 @@
 #define BOOST_TEST_MODULE "server-info"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/daemon/service/server_service.hpp>
+#include <irccd/daemon/server_service.hpp>
 
 #include <irccd/test/command_test.hpp>
 #include <irccd/test/mock_server.hpp>
--- a/tests/src/libirccd/command-server-invite/main.cpp	Sun Aug 05 12:14:16 2018 +0200
+++ b/tests/src/libirccd/command-server-invite/main.cpp	Sun Aug 05 15:47:10 2018 +0200
@@ -19,7 +19,7 @@
 #define BOOST_TEST_MODULE "server-invite"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/daemon/service/server_service.hpp>
+#include <irccd/daemon/server_service.hpp>
 
 #include <irccd/test/command_test.hpp>
 #include <irccd/test/mock_server.hpp>
--- a/tests/src/libirccd/command-server-join/main.cpp	Sun Aug 05 12:14:16 2018 +0200
+++ b/tests/src/libirccd/command-server-join/main.cpp	Sun Aug 05 15:47:10 2018 +0200
@@ -19,7 +19,7 @@
 #define BOOST_TEST_MODULE "server-join"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/daemon/service/server_service.hpp>
+#include <irccd/daemon/server_service.hpp>
 
 #include <irccd/test/command_test.hpp>
 #include <irccd/test/mock_server.hpp>
--- a/tests/src/libirccd/command-server-kick/main.cpp	Sun Aug 05 12:14:16 2018 +0200
+++ b/tests/src/libirccd/command-server-kick/main.cpp	Sun Aug 05 15:47:10 2018 +0200
@@ -19,7 +19,7 @@
 #define BOOST_TEST_MODULE "server-kick"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/daemon/service/server_service.hpp>
+#include <irccd/daemon/server_service.hpp>
 
 #include <irccd/test/command_test.hpp>
 #include <irccd/test/mock_server.hpp>
--- a/tests/src/libirccd/command-server-list/main.cpp	Sun Aug 05 12:14:16 2018 +0200
+++ b/tests/src/libirccd/command-server-list/main.cpp	Sun Aug 05 15:47:10 2018 +0200
@@ -19,7 +19,7 @@
 #define BOOST_TEST_MODULE "server-list"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/daemon/service/server_service.hpp>
+#include <irccd/daemon/server_service.hpp>
 
 #include <irccd/test/command_test.hpp>
 #include <irccd/test/mock_server.hpp>
--- a/tests/src/libirccd/command-server-me/main.cpp	Sun Aug 05 12:14:16 2018 +0200
+++ b/tests/src/libirccd/command-server-me/main.cpp	Sun Aug 05 15:47:10 2018 +0200
@@ -19,7 +19,7 @@
 #define BOOST_TEST_MODULE "server-me"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/daemon/service/server_service.hpp>
+#include <irccd/daemon/server_service.hpp>
 
 #include <irccd/test/command_test.hpp>
 #include <irccd/test/mock_server.hpp>
--- a/tests/src/libirccd/command-server-message/main.cpp	Sun Aug 05 12:14:16 2018 +0200
+++ b/tests/src/libirccd/command-server-message/main.cpp	Sun Aug 05 15:47:10 2018 +0200
@@ -19,7 +19,7 @@
 #define BOOST_TEST_MODULE "server-message"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/daemon/service/server_service.hpp>
+#include <irccd/daemon/server_service.hpp>
 
 #include <irccd/test/command_test.hpp>
 #include <irccd/test/mock_server.hpp>
--- a/tests/src/libirccd/command-server-mode/main.cpp	Sun Aug 05 12:14:16 2018 +0200
+++ b/tests/src/libirccd/command-server-mode/main.cpp	Sun Aug 05 15:47:10 2018 +0200
@@ -19,7 +19,7 @@
 #define BOOST_TEST_MODULE "server-mode"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/daemon/service/server_service.hpp>
+#include <irccd/daemon/server_service.hpp>
 
 #include <irccd/test/command_test.hpp>
 #include <irccd/test/mock_server.hpp>
--- a/tests/src/libirccd/command-server-nick/main.cpp	Sun Aug 05 12:14:16 2018 +0200
+++ b/tests/src/libirccd/command-server-nick/main.cpp	Sun Aug 05 15:47:10 2018 +0200
@@ -19,7 +19,7 @@
 #define BOOST_TEST_MODULE "server-nick"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/daemon/service/server_service.hpp>
+#include <irccd/daemon/server_service.hpp>
 
 #include <irccd/test/command_test.hpp>
 #include <irccd/test/mock_server.hpp>
--- a/tests/src/libirccd/command-server-notice/main.cpp	Sun Aug 05 12:14:16 2018 +0200
+++ b/tests/src/libirccd/command-server-notice/main.cpp	Sun Aug 05 15:47:10 2018 +0200
@@ -19,7 +19,7 @@
 #define BOOST_TEST_MODULE "server-notice"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/daemon/service/server_service.hpp>
+#include <irccd/daemon/server_service.hpp>
 
 #include <irccd/test/command_test.hpp>
 #include <irccd/test/mock_server.hpp>
--- a/tests/src/libirccd/command-server-part/main.cpp	Sun Aug 05 12:14:16 2018 +0200
+++ b/tests/src/libirccd/command-server-part/main.cpp	Sun Aug 05 15:47:10 2018 +0200
@@ -19,7 +19,7 @@
 #define BOOST_TEST_MODULE "server-part"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/daemon/service/server_service.hpp>
+#include <irccd/daemon/server_service.hpp>
 
 #include <irccd/test/command_test.hpp>
 #include <irccd/test/mock_server.hpp>
--- a/tests/src/libirccd/command-server-reconnect/main.cpp	Sun Aug 05 12:14:16 2018 +0200
+++ b/tests/src/libirccd/command-server-reconnect/main.cpp	Sun Aug 05 15:47:10 2018 +0200
@@ -19,7 +19,7 @@
 #define BOOST_TEST_MODULE "server-reconnect"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/daemon/service/server_service.hpp>
+#include <irccd/daemon/server_service.hpp>
 
 #include <irccd/test/command_test.hpp>
 #include <irccd/test/mock_server.hpp>
--- a/tests/src/libirccd/command-server-topic/main.cpp	Sun Aug 05 12:14:16 2018 +0200
+++ b/tests/src/libirccd/command-server-topic/main.cpp	Sun Aug 05 15:47:10 2018 +0200
@@ -19,7 +19,7 @@
 #define BOOST_TEST_MODULE "server-topic"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/daemon/service/server_service.hpp>
+#include <irccd/daemon/server_service.hpp>
 
 #include <irccd/test/command_test.hpp>
 #include <irccd/test/mock_server.hpp>
--- a/tests/src/libirccd/rules/main.cpp	Sun Aug 05 12:14:16 2018 +0200
+++ b/tests/src/libirccd/rules/main.cpp	Sun Aug 05 15:47:10 2018 +0200
@@ -21,7 +21,7 @@
 
 #include <irccd/daemon/irccd.hpp>
 #include <irccd/daemon/logger.hpp>
-#include <irccd/daemon/service/rule_service.hpp>
+#include <irccd/daemon/rule_service.hpp>
 
 namespace irccd {
 
--- a/tests/src/plugins/plugin/main.cpp	Sun Aug 05 12:14:16 2018 +0200
+++ b/tests/src/plugins/plugin/main.cpp	Sun Aug 05 15:47:10 2018 +0200
@@ -23,8 +23,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>
 
--- a/tests/src/plugins/tictactoe/main.cpp	Sun Aug 05 12:14:16 2018 +0200
+++ b/tests/src/plugins/tictactoe/main.cpp	Sun Aug 05 15:47:10 2018 +0200
@@ -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>