changeset 347:ec43b9ac3df7

Irccd: unify services
author David Demelier <markand@malikania.fr>
date Sun, 13 Nov 2016 10:41:28 +0100
parents a6c3d73d9641
children 24b1709093e7
files irccd/main.cpp libirccd-js/irccd/mod-plugin.cpp libirccd-js/irccd/mod-server.cpp libirccd-js/irccd/plugin-js.cpp libirccd-test/irccd/command-tester.cpp libirccd/CMakeLists.txt libirccd/irccd/command.cpp libirccd/irccd/config.cpp libirccd/irccd/irccd.cpp libirccd/irccd/service-command.cpp libirccd/irccd/service-command.hpp libirccd/irccd/service-interrupt.cpp libirccd/irccd/service-interrupt.hpp libirccd/irccd/service-plugin.cpp libirccd/irccd/service-plugin.hpp libirccd/irccd/service-rule.cpp libirccd/irccd/service-rule.hpp libirccd/irccd/service-server.cpp libirccd/irccd/service-server.hpp libirccd/irccd/service-transport.cpp libirccd/irccd/service-transport.hpp libirccd/irccd/service.cpp libirccd/irccd/service.hpp tests/cmd-plugin-config/main.cpp tests/cmd-plugin-info/main.cpp tests/cmd-plugin-list/main.cpp tests/cmd-plugin-load/main.cpp tests/cmd-plugin-reload/main.cpp tests/cmd-plugin-unload/main.cpp tests/cmd-server-connect/main.cpp tests/cmd-server-disconnect/main.cpp tests/cmd-server-info/main.cpp tests/cmd-server-list/main.cpp tests/cmd-server-reconnect/main.cpp
diffstat 34 files changed, 1551 insertions(+), 1834 deletions(-) [+]
line wrap: on
line diff
--- a/irccd/main.cpp	Sun Nov 13 10:00:20 2016 +0100
+++ b/irccd/main.cpp	Sun Nov 13 10:41:28 2016 +0100
@@ -39,16 +39,11 @@
 #include "logger.hpp"
 #include "options.hpp"
 #include "path.hpp"
+#include "service.hpp"
 #include "system.hpp"
 #include "config.hpp"
 #include "irccd.hpp"
 
-#include "service-command.hpp"
-#include "service-plugin.hpp"
-#include "service-rule.hpp"
-#include "service-server.hpp"
-#include "service-transport.hpp"
-
 #if defined(WITH_JS)
 #   include "mod-directory.hpp"
 #   include "mod-elapsed-timer.hpp"
--- a/libirccd-js/irccd/mod-plugin.cpp	Sun Nov 13 10:00:20 2016 +0100
+++ b/libirccd-js/irccd/mod-plugin.cpp	Sun Nov 13 10:41:28 2016 +0100
@@ -18,7 +18,7 @@
 
 #include "irccd.hpp"
 #include "plugin-js.hpp"
-#include "service-plugin.hpp"
+#include "service.hpp"
 #include "mod-irccd.hpp"
 #include "mod-plugin.hpp"
 
--- a/libirccd-js/irccd/mod-server.cpp	Sun Nov 13 10:00:20 2016 +0100
+++ b/libirccd-js/irccd/mod-server.cpp	Sun Nov 13 10:41:28 2016 +0100
@@ -25,7 +25,7 @@
 #include "mod-server.hpp"
 #include "plugin-js.hpp"
 #include "server.hpp"
-#include "service-server.hpp"
+#include "service.hpp"
 
 namespace irccd {
 
--- a/libirccd-js/irccd/plugin-js.cpp	Sun Nov 13 10:00:20 2016 +0100
+++ b/libirccd-js/irccd/plugin-js.cpp	Sun Nov 13 10:41:28 2016 +0100
@@ -30,7 +30,7 @@
 #include "mod-plugin.hpp"
 #include "mod-server.hpp"
 #include "plugin-js.hpp"
-#include "service-plugin.hpp"
+#include "service.hpp"
 #include "timer.hpp"
 
 namespace irccd {
--- a/libirccd-test/irccd/command-tester.cpp	Sun Nov 13 10:00:20 2016 +0100
+++ b/libirccd-test/irccd/command-tester.cpp	Sun Nov 13 10:41:28 2016 +0100
@@ -19,9 +19,7 @@
 #include "command-tester.hpp"
 #include "client.hpp"
 #include "logger.hpp"
-#include "service-command.hpp"
-#include "service-server.hpp"
-#include "service-transport.hpp"
+#include "service.hpp"
 #include "transport.hpp"
 
 namespace irccd {
--- a/libirccd/CMakeLists.txt	Sun Nov 13 10:00:20 2016 +0100
+++ b/libirccd/CMakeLists.txt	Sun Nov 13 10:41:28 2016 +0100
@@ -10,12 +10,7 @@
     ${libirccd_SOURCE_DIR}/irccd/plugin.hpp
     ${libirccd_SOURCE_DIR}/irccd/rule.hpp
     ${libirccd_SOURCE_DIR}/irccd/server.hpp
-    ${libirccd_SOURCE_DIR}/irccd/service-command.hpp
-    ${libirccd_SOURCE_DIR}/irccd/service-interrupt.hpp
-    ${libirccd_SOURCE_DIR}/irccd/service-plugin.hpp
-    ${libirccd_SOURCE_DIR}/irccd/service-rule.hpp
-    ${libirccd_SOURCE_DIR}/irccd/service-server.hpp
-    ${libirccd_SOURCE_DIR}/irccd/service-transport.hpp
+    ${libirccd_SOURCE_DIR}/irccd/service.hpp
     ${libirccd_SOURCE_DIR}/irccd/transport.hpp
 )
 
@@ -27,12 +22,7 @@
     ${libirccd_SOURCE_DIR}/irccd/plugin-dynlib.cpp
     ${libirccd_SOURCE_DIR}/irccd/rule.cpp
     ${libirccd_SOURCE_DIR}/irccd/server.cpp
-    ${libirccd_SOURCE_DIR}/irccd/service-command.cpp
-    ${libirccd_SOURCE_DIR}/irccd/service-interrupt.cpp
-    ${libirccd_SOURCE_DIR}/irccd/service-plugin.cpp
-    ${libirccd_SOURCE_DIR}/irccd/service-rule.cpp
-    ${libirccd_SOURCE_DIR}/irccd/service-server.cpp
-    ${libirccd_SOURCE_DIR}/irccd/service-transport.cpp
+    ${libirccd_SOURCE_DIR}/irccd/service.cpp
     ${libirccd_SOURCE_DIR}/irccd/transport.cpp
 )
 
--- a/libirccd/irccd/command.cpp	Sun Nov 13 10:00:20 2016 +0100
+++ b/libirccd/irccd/command.cpp	Sun Nov 13 10:41:28 2016 +0100
@@ -18,8 +18,7 @@
 
 #include "command.hpp"
 #include "irccd.hpp"
-#include "service-plugin.hpp"
-#include "service-server.hpp"
+#include "service.hpp"
 #include "transport.hpp"
 #include "util.hpp"
 
--- a/libirccd/irccd/config.cpp	Sun Nov 13 10:00:20 2016 +0100
+++ b/libirccd/irccd/config.cpp	Sun Nov 13 10:41:28 2016 +0100
@@ -28,7 +28,7 @@
 #include "plugin.hpp"
 #include "rule.hpp"
 #include "server.hpp"
-#include "service-plugin.hpp"
+#include "service.hpp"
 #include "sysconfig.hpp"
 #include "transport.hpp"
 #include "util.hpp"
--- a/libirccd/irccd/irccd.cpp	Sun Nov 13 10:00:20 2016 +0100
+++ b/libirccd/irccd/irccd.cpp	Sun Nov 13 10:41:28 2016 +0100
@@ -19,12 +19,7 @@
 #include "irccd.hpp"
 #include "logger.hpp"
 #include "net.hpp"
-#include "service-command.hpp"
-#include "service-interrupt.hpp"
-#include "service-plugin.hpp"
-#include "service-rule.hpp"
-#include "service-server.hpp"
-#include "service-transport.hpp"
+#include "service.hpp"
 #include "util.hpp"
 
 using namespace std;
--- a/libirccd/irccd/service-command.cpp	Sun Nov 13 10:00:20 2016 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,51 +0,0 @@
-/*
- * service-command.cpp -- store remote commands
- *
- * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#include <algorithm>
-
-#include "service-command.hpp"
-
-namespace irccd {
-
-bool CommandService::contains(const std::string &name) const noexcept
-{
-    return find(name) != nullptr;
-}
-
-std::shared_ptr<Command> CommandService::find(const std::string &name) const noexcept
-{
-    auto it = std::find_if(m_commands.begin(), m_commands.end(), [&] (const auto &cmd) {
-        return cmd->name() == name;
-    });
-
-    return it == m_commands.end() ? nullptr : *it;
-}
-
-void CommandService::add(std::shared_ptr<Command> command)
-{
-    auto it = std::find_if(m_commands.begin(), m_commands.end(), [&] (const auto &cmd) {
-        return cmd->name() == command->name();
-    });
-
-    if (it != m_commands.end())
-        *it = std::move(command);
-    else
-        m_commands.push_back(std::move(command));
-}
-
-} // !irccd
--- a/libirccd/irccd/service-command.hpp	Sun Nov 13 10:00:20 2016 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,81 +0,0 @@
-/*
- * service-command.hpp -- store remote commands
- *
- * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef IRCCD_SERVICE_COMMAND_HPP
-#define IRCCD_SERVICE_COMMAND_HPP
-
-/**
- * \file service-command.hpp
- * \brief Store remote commands.
- */
-
-#include <memory>
-#include <vector>
-
-#include "command.hpp"
-#include "sysconfig.hpp"
-
-namespace irccd {
-
-/**
- * \brief Store remote commands.
- * \ingroup services
- */
-class CommandService {
-private:
-    std::vector<std::shared_ptr<Command>> m_commands;
-
-public:
-    /**
-     * Get all commands.
-     *
-     * \return the list of commands.
-     */
-    inline const std::vector<std::shared_ptr<Command>> &commands() const noexcept
-    {
-        return m_commands;
-    }
-
-    /**
-     * Tells if a command exists.
-     *
-     * \param name the command name
-     * \return true if the command exists
-     */
-    IRCCD_EXPORT bool contains(const std::string &name) const noexcept;
-
-    /**
-     * Find a command by name.
-     *
-     * \param name the command name
-     * \return the command or empty one if not found
-     */
-    IRCCD_EXPORT std::shared_ptr<Command> find(const std::string &name) const noexcept;
-
-    /**
-     * Add a command or replace existing one.
-     *
-     * \pre command != nullptr
-     * \param command the command name
-     */
-    IRCCD_EXPORT void add(std::shared_ptr<Command> command);
-};
-
-} // !irccd
-
-#endif // !IRCCD_SERVICE_COMMAND_HPP
--- a/libirccd/irccd/service-interrupt.cpp	Sun Nov 13 10:00:20 2016 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,75 +0,0 @@
-/*
- * service-interrupt.cpp -- interrupt irccd event loop
- *
- * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#include <array>
-
-#include "logger.hpp"
-#include "service-interrupt.hpp"
-
-namespace irccd {
-
-InterruptService::InterruptService()
-    : m_in(AF_INET, 0)
-    , m_out(AF_INET, 0)
-{
-    // Bind a socket to any port.
-    m_in.set(net::option::SockReuseAddress(true));
-    m_in.bind(net::ipv4::any(0));
-    m_in.listen(1);
-
-    // Do the socket pair.
-    m_out.connect(net::ipv4::pton("127.0.0.1", net::ipv4::port(m_in.getsockname())));
-    m_in = m_in.accept();
-    m_out.set(net::option::SockBlockMode(false));
-}
-
-void InterruptService::prepare(fd_set &in, fd_set &, net::Handle &max)
-{
-    FD_SET(m_in.handle(), &in);
-
-    if (m_in.handle() > max)
-        max = m_in.handle();
-}
-
-void InterruptService::sync(fd_set &in, fd_set &)
-{
-    if (FD_ISSET(m_in.handle(), &in)) {
-        static std::array<char, 32> tmp;
-
-        try {
-            log::debug("irccd: interrupt service recv");
-            m_in.recv(tmp.data(), 32);
-        } catch (const std::exception &ex) {
-            log::warning() << "irccd: interrupt service error: " << ex.what() << std::endl;
-        }
-    }
-}
-
-void InterruptService::interrupt() noexcept
-{
-    try {
-        static char byte;
-
-        log::debug("irccd: interrupt service send");
-        m_out.send(&byte, 1);
-    } catch (const std::exception &ex) {
-        log::warning() << "irccd: interrupt service error: " << ex.what() << std::endl;
-    }
-}
-
-} // !irccd
--- a/libirccd/irccd/service-interrupt.hpp	Sun Nov 13 10:00:20 2016 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,66 +0,0 @@
-/*
- * service-interrupt.hpp -- interrupt irccd event loop
- *
- * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef IRCCD_SERVICE_INTERRUPT_HPP
-#define IRCCD_SERVICE_INTERRUPT_HPP
-
-/**
- * \file service-interrupt.hpp
- * \brief Interrupt irccd event loop.
- */
-
-#include "net.hpp"
-
-namespace irccd {
-
-/**
- * \brief Interrupt irccd event loop.
- * \ingroup services
- */
-class InterruptService {
-private:
-    net::TcpSocket m_in;
-    net::TcpSocket m_out;
-
-public:
-    /**
-     * Prepare the socket pair.
-     *
-     * \throw std::runtime_error on errors
-     */
-    IRCCD_EXPORT InterruptService();
-
-    /**
-     * \copydoc Service::prepare
-     */
-    IRCCD_EXPORT void prepare(fd_set &in, fd_set &out, net::Handle &max);
-
-    /**
-     * \copydoc Service::sync
-     */
-    IRCCD_EXPORT void sync(fd_set &in, fd_set &out);
-
-    /**
-     * Request interruption.
-     */
-    IRCCD_EXPORT void interrupt() noexcept;
-};
-
-} // !irccd
-
-#endif // !IRCCD_SERVICE_INTERRUPT_HPP
--- a/libirccd/irccd/service-plugin.cpp	Sun Nov 13 10:00:20 2016 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,183 +0,0 @@
-/*
- * service-plugin.cpp -- manage plugins
- *
- * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#include <algorithm>
-#include <functional>
-#include <regex>
-#include <stdexcept>
-
-#include <format.h>
-
-#include "logger.hpp"
-#include "service-plugin.hpp"
-
-using namespace fmt::literals;
-
-namespace irccd {
-
-PluginService::PluginService(Irccd &irccd) noexcept
-    : m_irccd(irccd)
-{
-}
-
-PluginService::~PluginService()
-{
-    for (const auto &plugin : m_plugins)
-        plugin->onUnload(m_irccd);
-}
-
-bool PluginService::has(const std::string &name) const noexcept
-{
-    return std::count_if(m_plugins.cbegin(), m_plugins.cend(), [&] (const auto &plugin) {
-        return plugin->name() == name;
-    }) > 0;
-}
-
-std::shared_ptr<Plugin> PluginService::get(const std::string &name) const noexcept
-{
-    auto it = std::find_if(m_plugins.begin(), m_plugins.end(), [&] (const auto &plugin) {
-        return plugin->name() == name;
-    });
-
-    if (it == m_plugins.end())
-        return nullptr;
-
-    return *it;
-}
-
-std::shared_ptr<Plugin> PluginService::require(const std::string &name) const
-{
-    auto plugin = get(name);
-
-    if (!plugin)
-        throw std::invalid_argument("plugin {} not found"_format(name));
-
-    return plugin;
-}
-
-void PluginService::add(std::shared_ptr<Plugin> plugin)
-{
-    m_plugins.push_back(std::move(plugin));
-}
-
-void PluginService::addLoader(std::unique_ptr<PluginLoader> loader)
-{
-    m_loaders.push_back(std::move(loader));
-}
-
-void PluginService::setConfig(const std::string &name, PluginConfig config)
-{
-    m_config.emplace(name, std::move(config));
-}
-
-PluginConfig PluginService::config(const std::string &name) const
-{
-    auto it = m_config.find(name);
-
-    if (it != m_config.end())
-        return it->second;
-
-    return PluginConfig();
-}
-
-void PluginService::setFormats(const std::string &name, PluginFormats formats)
-{
-    m_formats.emplace(name, std::move(formats));
-}
-
-PluginFormats PluginService::formats(const std::string &name) const
-{
-    auto it = m_formats.find(name);
-
-    if (it != m_formats.end())
-        return it->second;
-
-    return PluginFormats();
-}
-
-std::shared_ptr<Plugin> PluginService::open(const std::string &id,
-                                            const std::string &path)
-{
-    for (const auto &loader : m_loaders) {
-        auto plugin = loader->open(id, path);
-
-        if (plugin)
-            return plugin;
-    }
-
-    return nullptr;
-}
-
-std::shared_ptr<Plugin> PluginService::find(const std::string &id)
-{
-    for (const auto &loader : m_loaders) {
-        auto plugin = loader->find(id);
-
-        if (plugin)
-            return plugin;
-    }
-
-    return nullptr;
-}
-
-void PluginService::load(std::string name, std::string path)
-{
-    if (has(name))
-        return;
-
-    try {
-        std::shared_ptr<Plugin> plugin;
-
-        if (path.empty())
-            plugin = find(name);
-        else
-            plugin = open(name, std::move(path));
-
-        if (plugin) {
-            plugin->setConfig(m_config[name]);
-            plugin->setFormats(m_formats[name]);
-            plugin->onLoad(m_irccd);
-
-            add(std::move(plugin));
-        }
-    } catch (const std::exception &ex) {
-        log::warning("plugin {}: {}"_format(name, ex.what()));
-    }
-}
-
-void PluginService::reload(const std::string &name)
-{
-    auto plugin = get(name);
-
-    if (plugin)
-        plugin->onReload(m_irccd);
-}
-
-void PluginService::unload(const std::string &name)
-{
-    auto it = std::find_if(m_plugins.begin(), m_plugins.end(), [&] (const auto &plugin) {
-        return plugin->name() == name;
-    });
-
-    if (it != m_plugins.end()) {
-        (*it)->onUnload(m_irccd);
-        m_plugins.erase(it);
-    }
-}
-
-} // !irccd
--- a/libirccd/irccd/service-plugin.hpp	Sun Nov 13 10:00:20 2016 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,197 +0,0 @@
-/*
- * service-plugin.hpp -- manage plugins
- *
- * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef IRCCD_SERVICE_PLUGIN_HPP
-#define IRCCD_SERVICE_PLUGIN_HPP
-
-/**
- * \file service-plugin.hpp
- * \brief Manage plugins.
- */
-
-#include <unordered_map>
-#include <memory>
-#include <vector>
-
-#include "plugin.hpp"
-
-namespace irccd {
-
-class Irccd;
-
-/**
- * \brief Manage plugins.
- * \ingroup services
- */
-class PluginService {
-private:
-    Irccd &m_irccd;
-    std::vector<std::shared_ptr<Plugin>> m_plugins;
-    std::vector<std::unique_ptr<PluginLoader>> m_loaders;
-    std::unordered_map<std::string, PluginConfig> m_config;
-    std::unordered_map<std::string, PluginFormats> m_formats;
-
-public:
-    /**
-     * Create the plugin service.
-     *
-     * \param irccd the irccd instance
-     */
-    IRCCD_EXPORT PluginService(Irccd &irccd) noexcept;
-
-    /**
-     * Destroy plugins.
-     */
-    IRCCD_EXPORT ~PluginService();
-
-    /**
-     * Get the list of plugins.
-     *
-     * \return the list of plugins
-     */
-    inline const std::vector<std::shared_ptr<Plugin>> &list() const noexcept
-    {
-        return m_plugins;
-    }
-
-    /**
-     * Check if a plugin is loaded.
-     *
-     * \param name the plugin id
-     * \return true if has plugin
-     */
-    IRCCD_EXPORT bool has(const std::string &name) const noexcept;
-
-    /**
-     * Get a loaded plugin or null if not found.
-     *
-     * \param name the plugin id
-     * \return the plugin or empty one if not found
-     */
-    IRCCD_EXPORT std::shared_ptr<Plugin> get(const std::string &name) const noexcept;
-
-    /**
-     * Find a loaded plugin.
-     *
-     * \param name the plugin id
-     * \return the plugin
-     * \throws std::out_of_range if not found
-     */
-    IRCCD_EXPORT std::shared_ptr<Plugin> require(const std::string &name) const;
-
-    /**
-     * Add the specified plugin to the registry.
-     *
-     * \pre plugin != nullptr
-     * \param plugin the plugin
-     * \note the plugin is only added to the list, no action is performed on it
-     */
-    IRCCD_EXPORT void add(std::shared_ptr<Plugin> plugin);
-
-    /**
-     * Add a loader.
-     *
-     * \param loader the loader
-     */
-    IRCCD_EXPORT void addLoader(std::unique_ptr<PluginLoader> loader);
-
-    /**
-     * Configure a plugin.
-     *
-     * If the plugin is already loaded, its configuration is updated.
-     *
-     * \param name the plugin name
-     * \param config the new configuration
-     */
-    IRCCD_EXPORT void setConfig(const std::string &name, PluginConfig config);
-
-    /**
-     * Get a configuration for a plugin.
-     *
-     * \param name the plugin name
-     * \return the configuration or default one if not found
-     */
-    IRCCD_EXPORT PluginConfig config(const std::string &name) const;
-
-    /**
-     * Add formatting for a plugin.
-     *
-     * \param name the plugin name
-     * \param formats the formats
-     */
-    IRCCD_EXPORT void setFormats(const std::string &name, PluginFormats formats);
-
-    /**
-     * Get formats for a plugin.
-     *
-     * \param name the plugin name
-     * \return the formats
-     */
-    IRCCD_EXPORT PluginFormats formats(const std::string &name) const;
-
-    /**
-     * 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
-     */
-    IRCCD_EXPORT std::shared_ptr<Plugin> open(const std::string &id,
-                                              const std::string &path);
-
-    /**
-     * Generic function for finding a plugin.
-     *
-     * \param id the plugin id
-     * \return the plugin or nullptr on failures
-     */
-    IRCCD_EXPORT std::shared_ptr<Plugin> find(const std::string &id);
-
-    /**
-     * Convenient wrapper that loads a plugin, call onLoad and add it to the
-     * registry.
-     *
-     * Any errors are printed using logger.
-     *
-     * \param name the name
-     * \param path the optional path (searched if empty)
-     */
-    IRCCD_EXPORT void load(std::string name, std::string path = "");
-
-    /**
-     * Unload a plugin and remove it.
-     *
-     * \param name the plugin id
-     */
-    IRCCD_EXPORT void unload(const std::string &name);
-
-    /**
-     * Reload a plugin by calling onReload.
-     *
-     * \param name the plugin name
-     * \throw std::exception on failures
-     */
-    IRCCD_EXPORT void reload(const std::string &name);
-};
-
-} // !irccd
-
-#endif // !IRCCD_SERVICE_PLUGIN_HPP
--- a/libirccd/irccd/service-rule.cpp	Sun Nov 13 10:00:20 2016 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,78 +0,0 @@
-/*
- * service-rule.cpp -- store and solve rules
- *
- * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#include <cassert>
-
-#include <format.h>
-
-#include "logger.hpp"
-#include "service-rule.hpp"
-#include "util.hpp"
-
-using namespace fmt::literals;
-
-namespace irccd {
-
-void RuleService::add(Rule rule)
-{
-    m_rules.push_back(std::move(rule));
-}
-
-void RuleService::insert(Rule rule, unsigned position)
-{
-    assert(position <= m_rules.size());
-
-    m_rules.insert(m_rules.begin() + position, std::move(rule));
-}
-
-void RuleService::remove(unsigned position)
-{
-    assert(position < m_rules.size());
-
-    m_rules.erase(m_rules.begin() + position);
-}
-
-bool RuleService::solve(const std::string &server,
-                        const std::string &channel,
-                        const std::string &origin,
-                        const std::string &plugin,
-                        const std::string &event) noexcept
-{
-    bool result = true;
-
-    log::debug("rule: solving for server={}, channel={}, origin={}, plugin={}, event={}"_format(server, channel,
-           origin, plugin, event));
-
-    int i = 0;
-    for (const Rule &rule : m_rules) {
-        log::debug() << "  candidate " << i++ << ":\n"
-                 << "    servers: " << util::join(rule.servers().begin(), rule.servers().end()) << "\n"
-                 << "    channels: " << util::join(rule.channels().begin(), rule.channels().end()) << "\n"
-                 << "    origins: " << util::join(rule.origins().begin(), rule.origins().end()) << "\n"
-                 << "    plugins: " << util::join(rule.plugins().begin(), rule.plugins().end()) << "\n"
-                 << "    events: " << util::join(rule.events().begin(), rule.events().end()) << "\n"
-                 << "    action: " << ((rule.action() == RuleAction::Accept) ? "accept" : "drop") << std::endl;
-
-        if (rule.match(server, channel, origin, plugin, event))
-            result = rule.action() == RuleAction::Accept;
-    }
-
-    return result;
-}
-
-} // !irccd
--- a/libirccd/irccd/service-rule.hpp	Sun Nov 13 10:00:20 2016 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,104 +0,0 @@
-/*
- * service-rule.hpp -- store and solve rules
- *
- * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef IRCCD_SERVICE_RULE_HPP
-#define IRCCD_SERVICE_RULE_HPP
-
-/**
- * \file service-rule.hpp
- * \brief Store and solve rules.
- */
-
-#include <vector>
-
-#include "rule.hpp"
-
-namespace irccd {
-
-/**
- * \brief Store and solve rules.
- * \ingroup services
- */
-class RuleService {
-private:
-    std::vector<Rule> m_rules;
-
-public:
-    /**
-     * Get the list of rules.
-     *
-     * \return the list of rules
-     */
-    inline const std::vector<Rule> &list() const noexcept
-    {
-        return m_rules;
-    }
-
-    /**
-     * Get the number of rules.
-     *
-     * \return the number of rules
-     */
-    inline std::size_t length() const noexcept
-    {
-        return m_rules.size();
-    }
-
-    /**
-     * Append a rule.
-     *
-     * \param rule the rule to append
-     */
-    IRCCD_EXPORT void add(Rule rule);
-
-    /**
-     * Insert a new rule at the specified position.
-     *
-     * \param rule the rule
-     * \param position the position
-     */
-    IRCCD_EXPORT void insert(Rule rule, unsigned position);
-
-    /**
-     * Remove a new rule from the specified position.
-     *
-     * \pre position must be valid
-     * \param position the position
-     */
-    IRCCD_EXPORT void remove(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
-     */
-    IRCCD_EXPORT bool solve(const std::string &server,
-                            const std::string &channel,
-                            const std::string &origin,
-                            const std::string &plugin,
-                            const std::string &event) noexcept;
-};
-
-} // !irccd
-
-#endif // !IRCCD_SERVICE_RULE_HPP
--- a/libirccd/irccd/service-server.cpp	Sun Nov 13 10:00:20 2016 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,586 +0,0 @@
-/*
- * service-server.cpp -- manage IRC servers
- *
- * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#include <algorithm>
-
-#include <json.hpp>
-#include <format.h>
-
-#include "irccd.hpp"
-#include "logger.hpp"
-#include "plugin.hpp"
-#include "server.hpp"
-#include "service-plugin.hpp"
-#include "service-rule.hpp"
-#include "service-server.hpp"
-#include "service-transport.hpp"
-#include "util.hpp"
-
-using namespace fmt::literals;
-using namespace nlohmann;
-
-namespace irccd {
-
-class EventHandler {
-public:
-    std::string server;
-    std::string origin;
-    std::string target;
-    std::function<std::string (Plugin &)> functionName;
-    std::function<void (Plugin &)> functionExec;
-
-    void operator()(Irccd &irccd) const
-    {
-        for (auto &plugin : irccd.plugins().list()) {
-            auto eventname = functionName(*plugin);
-            auto allowed = irccd.rules().solve(server, target, origin, plugin->name(), eventname);
-
-            if (!allowed) {
-                log::debug() << "rule: event skipped on match" << std::endl;
-                continue;
-            } else
-                log::debug() << "rule: event allowed" << std::endl;
-
-            // TODO: server-event must not know which type of plugin.
-            // TODO: get generic error.
-            // TODO: this is the responsability of service-plugin.
-            try {
-                functionExec(*plugin);
-            } catch (const std::exception &ex) {
-                log::warning() << "plugin " << plugin->name() << ": error: " << ex.what() << std::endl;
-            }
-        }
-    }
-};
-
-void ServerService::handleChannelMode(const ChannelModeEvent &ev)
-{
-    log::debug() << "server " << ev.server->name() << ": event onChannelMode:\n";
-    log::debug() << "  origin: " << ev.origin << "\n";
-    log::debug() << "  channel: " << ev.channel << "\n";
-    log::debug() << "  mode: " << ev.mode << "\n";
-    log::debug() << "  argument: " << ev.argument << std::endl;
-
-    m_irccd.transports().broadcast(nlohmann::json::object({
-        { "event",      "onChannelMode"     },
-        { "server",     ev.server->name()   },
-        { "origin",     ev.origin           },
-        { "channel",    ev.channel          },
-        { "mode",       ev.mode             },
-        { "argument",   ev.argument         }
-    }));
-
-    m_irccd.post(EventHandler{ev.server->name(), ev.origin, ev.channel,
-        [=] (Plugin &) -> std::string {
-            return "onChannelMode";
-        },
-        [=] (Plugin &plugin) {
-            plugin.onChannelMode(m_irccd, ev);
-        }
-    });
-}
-
-void ServerService::handleChannelNotice(const ChannelNoticeEvent &ev)
-{
-    log::debug() << "server " << ev.server->name() << ": event onChannelNotice:\n";
-    log::debug() << "  origin: " << ev.origin << "\n";
-    log::debug() << "  channel: " << ev.channel << "\n";
-    log::debug() << "  message: " << ev.message << std::endl;
-
-    m_irccd.transports().broadcast(nlohmann::json::object({
-        { "event",      "onChannelNotice"   },
-        { "server",     ev.server->name()   },
-        { "origin",     ev.origin           },
-        { "channel",    ev.channel          },
-        { "message",    ev.message          }
-    }));
-
-    m_irccd.post(EventHandler{ev.server->name(), ev.origin, ev.channel,
-        [=] (Plugin &) -> std::string {
-            return "onChannelNotice";
-        },
-        [=] (Plugin &plugin) {
-            plugin.onChannelNotice(m_irccd, ev);
-        }
-    });
-}
-
-void ServerService::handleConnect(const ConnectEvent &ev)
-{
-    log::debug() << "server " << ev.server->name() << ": event onConnect" << std::endl;
-
-    m_irccd.transports().broadcast(nlohmann::json::object({
-        { "event",      "onConnect"         },
-        { "server",     ev.server->name()   }
-    }));
-
-    m_irccd.post(EventHandler{ev.server->name(), /* origin */ "", /* channel */ "",
-        [=] (Plugin &) -> std::string {
-            return "onConnect";
-        },
-        [=] (Plugin &plugin) {
-            plugin.onConnect(m_irccd, ev);
-        }
-    });
-}
-
-void ServerService::handleInvite(const InviteEvent &ev)
-{
-    log::debug() << "server " << ev.server->name() << ": event onInvite:\n";
-    log::debug() << "  origin: " << ev.origin << "\n";
-    log::debug() << "  channel: " << ev.channel << "\n";
-    log::debug() << "  target: " << ev.nickname << std::endl;
-
-    m_irccd.transports().broadcast(nlohmann::json::object({
-        { "event",      "onInvite"          },
-        { "server",     ev.server->name()   },
-        { "origin",     ev.origin           },
-        { "channel",    ev.channel          }
-    }));
-
-    m_irccd.post(EventHandler{ev.server->name(), ev.origin, ev.channel,
-        [=] (Plugin &) -> std::string {
-            return "onInvite";
-        },
-        [=] (Plugin &plugin) {
-            plugin.onInvite(m_irccd, ev);
-        }
-    });
-}
-
-void ServerService::handleJoin(const JoinEvent &ev)
-{
-    log::debug() << "server " << ev.server->name() << ": event onJoin:\n";
-    log::debug() << "  origin: " << ev.origin << "\n";
-    log::debug() << "  channel: " << ev.channel << std::endl;
-
-    m_irccd.transports().broadcast(nlohmann::json::object({
-        { "event",      "onJoin"            },
-        { "server",     ev.server->name()   },
-        { "origin",     ev.origin           },
-        { "channel",    ev.channel          }
-    }));
-
-    m_irccd.post(EventHandler{ev.server->name(), ev.origin, ev.channel,
-        [=] (Plugin &) -> std::string {
-            return "onJoin";
-        },
-        [=] (Plugin &plugin) {
-            plugin.onJoin(m_irccd, ev);
-        }
-    });
-}
-
-void ServerService::handleKick(const KickEvent &ev)
-{
-    log::debug() << "server " << ev.server->name() << ": event onKick:\n";
-    log::debug() << "  origin: " << ev.origin << "\n";
-    log::debug() << "  channel: " << ev.channel << "\n";
-    log::debug() << "  target: " << ev.target << "\n";
-    log::debug() << "  reason: " << ev.reason << std::endl;
-
-    m_irccd.transports().broadcast(nlohmann::json::object({
-        { "event",      "onKick"            },
-        { "server",     ev.server->name()   },
-        { "origin",     ev.origin           },
-        { "channel",    ev.channel          },
-        { "target",     ev.target           },
-        { "reason",     ev.reason           }
-    }));
-
-    m_irccd.post(EventHandler{ev.server->name(), ev.origin, ev.channel,
-        [=] (Plugin &) -> std::string {
-            return "onKick";
-        },
-        [=] (Plugin &plugin) {
-            plugin.onKick(m_irccd, ev);
-        }
-    });
-}
-
-void ServerService::handleMessage(const MessageEvent &ev)
-{
-    log::debug() << "server " << ev.server->name() << ": event onMessage:\n";
-    log::debug() << "  origin: " << ev.origin << "\n";
-    log::debug() << "  channel: " << ev.channel << "\n";
-    log::debug() << "  message: " << ev.message << std::endl;
-
-    m_irccd.transports().broadcast(nlohmann::json::object({
-        { "event",      "onMessage"         },
-        { "server",     ev.server->name()   },
-        { "origin",     ev.origin           },
-        { "channel",    ev.channel          },
-        { "message",    ev.message          }
-    }));
-
-    m_irccd.post(EventHandler{ev.server->name(), ev.origin, ev.channel,
-        [=] (Plugin &plugin) -> std::string {
-            return util::parseMessage(ev.message, ev.server->commandCharacter(), plugin.name()).second == util::MessageType::Command ? "onCommand" : "onMessage";
-        },
-        [=] (Plugin &plugin) mutable {
-            auto copy = ev;
-            auto pack = util::parseMessage(copy.message, copy.server->commandCharacter(), plugin.name());
-
-            copy.message = pack.first;
-
-            if (pack.second == util::MessageType::Command)
-                plugin.onCommand(m_irccd, copy);
-            else
-                plugin.onMessage(m_irccd, copy);
-        }
-    });
-}
-
-void ServerService::handleMe(const MeEvent &ev)
-{
-    log::debug() << "server " << ev.server->name() << ": event onMe:\n";
-    log::debug() << "  origin: " << ev.origin << "\n";
-    log::debug() << "  target: " << ev.channel << "\n";
-    log::debug() << "  message: " << ev.message << std::endl;
-
-    m_irccd.transports().broadcast(nlohmann::json::object({
-        { "event",      "onMe"              },
-        { "server",     ev.server->name()   },
-        { "origin",     ev.origin           },
-        { "target",     ev.channel          },
-        { "message",    ev.message          }
-    }));
-
-    m_irccd.post(EventHandler{ev.server->name(), ev.origin, ev.channel,
-        [=] (Plugin &) -> std::string {
-            return "onMe";
-        },
-        [=] (Plugin &plugin) {
-            plugin.onMe(m_irccd, ev);
-        }
-    });
-}
-
-void ServerService::handleMode(const ModeEvent &ev)
-{
-    log::debug() << "server " << ev.server->name() << ": event onMode\n";
-    log::debug() << "  origin: " << ev.origin << "\n";
-    log::debug() << "  mode: " << ev.mode << std::endl;
-
-    m_irccd.transports().broadcast(nlohmann::json::object({
-        { "event",      "onMode"            },
-        { "server",     ev.server->name()   },
-        { "origin",     ev.origin           },
-        { "mode",       ev.mode             }
-    }));
-
-    m_irccd.post(EventHandler{ev.server->name(), ev.origin, /* channel */ "",
-        [=] (Plugin &) -> std::string {
-            return "onMode";
-        },
-        [=] (Plugin &plugin) {
-            plugin.onMode(m_irccd, ev);
-        }
-    });
-}
-
-void ServerService::handleNames(const NamesEvent &ev)
-{
-    log::debug() << "server " << ev.server->name() << ": event onNames:\n";
-    log::debug() << "  channel: " << ev.channel << "\n";
-    log::debug() << "  names: " << util::join(ev.names.begin(), ev.names.end(), ", ") << std::endl;
-
-    auto names = json::array();
-
-    for (const auto &v : ev.names)
-        names.push_back(v);
-
-    m_irccd.transports().broadcast(nlohmann::json::object({
-        { "event",      "onNames"           },
-        { "server",     ev.server->name()   },
-        { "channel",    ev.channel          },
-        { "names",      std::move(names)    }
-    }));
-
-    m_irccd.post(EventHandler{ev.server->name(), /* origin */ "", ev.channel,
-        [=] (Plugin &) -> std::string {
-            return "onNames";
-        },
-        [=] (Plugin &plugin) {
-            plugin.onNames(m_irccd, ev);
-        }
-    });
-}
-
-void ServerService::handleNick(const NickEvent &ev)
-{
-    log::debug() << "server " << ev.server->name() << ": event onNick:\n";
-    log::debug() << "  origin: " << ev.origin << "\n";
-    log::debug() << "  nickname: " << ev.nickname << std::endl;
-
-    m_irccd.transports().broadcast(nlohmann::json::object({
-        { "event",      "onNick"            },
-        { "server",     ev.server->name()   },
-        { "origin",     ev.origin           },
-        { "nickname",   ev.nickname         }
-    }));
-
-    m_irccd.post(EventHandler{ev.server->name(), ev.origin, /* channel */ "",
-        [=] (Plugin &) -> std::string {
-            return "onNick";
-        },
-        [=] (Plugin &plugin) {
-            plugin.onNick(m_irccd, ev);
-        }
-    });
-}
-
-void ServerService::handleNotice(const NoticeEvent &ev)
-{
-    log::debug() << "server " << ev.server->name() << ": event onNotice:\n";
-    log::debug() << "  origin: " << ev.origin << "\n";
-    log::debug() << "  message: " << ev.message << std::endl;
-
-    m_irccd.transports().broadcast(nlohmann::json::object({
-        { "event",      "onNotice"          },
-        { "server",     ev.server->name()   },
-        { "origin",     ev.origin           },
-        { "message",    ev.message          }
-    }));
-
-    m_irccd.post(EventHandler{ev.server->name(), ev.origin, /* channel */ "",
-        [=] (Plugin &) -> std::string {
-            return "onNotice";
-        },
-        [=] (Plugin &plugin) {
-            plugin.onNotice(m_irccd, ev);
-        }
-    });
-}
-
-void ServerService::handlePart(const PartEvent &ev)
-{
-    log::debug() << "server " << ev.server->name() << ": event onPart:\n";
-    log::debug() << "  origin: " << ev.origin << "\n";
-    log::debug() << "  channel: " << ev.channel << "\n";
-    log::debug() << "  reason: " << ev.reason << std::endl;
-
-    m_irccd.transports().broadcast(nlohmann::json::object({
-        { "event",      "onPart"            },
-        { "server",     ev.server->name()   },
-        { "origin",     ev.origin           },
-        { "channel",    ev.channel          },
-        { "reason",     ev.reason           }
-    }));
-
-    m_irccd.post(EventHandler{ev.server->name(), ev.origin, ev.channel,
-        [=] (Plugin &) -> std::string {
-            return "onPart";
-        },
-        [=] (Plugin &plugin) {
-            plugin.onPart(m_irccd, ev);
-        }
-    });
-}
-
-void ServerService::handleQuery(const QueryEvent &ev)
-{
-    log::debug() << "server " << ev.server->name() << ": event onQuery:\n";
-    log::debug() << "  origin: " << ev.origin << "\n";
-    log::debug() << "  message: " << ev.message << std::endl;
-
-    m_irccd.transports().broadcast(nlohmann::json::object({
-        { "event",      "onQuery"           },
-        { "server",     ev.server->name()   },
-        { "origin",     ev.origin           },
-        { "message",    ev.message          }
-    }));
-
-    m_irccd.post(EventHandler{ev.server->name(), ev.origin, /* channel */ "",
-        [=] (Plugin &plugin) -> std::string {
-            return util::parseMessage(ev.message, ev.server->commandCharacter(), plugin.name()).second == util::MessageType::Command ? "onQueryCommand" : "onQuery";
-        },
-        [=] (Plugin &plugin) mutable {
-            auto copy = ev;
-            auto pack = util::parseMessage(copy.message, copy.server->commandCharacter(), plugin.name());
-
-            copy.message = pack.first;
-
-            if (pack.second == util::MessageType::Command)
-                plugin.onQueryCommand(m_irccd, copy);
-            else
-                plugin.onQuery(m_irccd, copy);
-        }
-    });
-}
-
-void ServerService::handleTopic(const TopicEvent &ev)
-{
-    log::debug() << "server " << ev.server->name() << ": event onTopic:\n";
-    log::debug() << "  origin: " << ev.origin << "\n";
-    log::debug() << "  channel: " << ev.channel << "\n";
-    log::debug() << "  topic: " << ev.topic << std::endl;
-
-    m_irccd.transports().broadcast(nlohmann::json::object({
-        { "event",      "onTopic"           },
-        { "server",     ev.server->name()   },
-        { "origin",     ev.origin           },
-        { "channel",    ev.channel          },
-        { "topic",      ev.topic            }
-    }));
-
-    m_irccd.post(EventHandler{ev.server->name(), ev.origin, ev.channel,
-        [=] (Plugin &) -> std::string {
-            return "onTopic";
-        },
-        [=] (Plugin &plugin) {
-            plugin.onTopic(m_irccd, ev);
-        }
-    });
-}
-
-void ServerService::handleWhois(const WhoisEvent &ev)
-{
-    log::debug() << "server " << ev.server->name() << ": event onWhois\n";
-    log::debug() << "  nickname: " << ev.whois.nick << "\n";
-    log::debug() << "  username: " << ev.whois.user << "\n";
-    log::debug() << "  host: " << ev.whois.host << "\n";
-    log::debug() << "  realname: " << ev.whois.realname << "\n";
-    log::debug() << "  channels: " << util::join(ev.whois.channels.begin(), ev.whois.channels.end()) << std::endl;
-
-    m_irccd.transports().broadcast(nlohmann::json::object({
-        { "event",      "onWhois"           },
-        { "server",     ev.server->name()   },
-        { "nickname",   ev.whois.nick       },
-        { "username",   ev.whois.user       },
-        { "host",       ev.whois.host       },
-        { "realname",   ev.whois.realname   }
-    }));
-
-    m_irccd.post(EventHandler{ev.server->name(), /* origin */ "", /* channel */ "",
-        [=] (Plugin &) -> std::string {
-            return "onWhois";
-        },
-        [=] (Plugin &plugin) {
-            plugin.onWhois(m_irccd, ev);
-        }
-    });
-}
-
-ServerService::ServerService(Irccd &irccd)
-    : m_irccd(irccd)
-{
-}
-
-void ServerService::prepare(fd_set &in, fd_set &out, net::Handle &max)
-{
-    for (auto &server : m_servers) {
-        server->update();
-        server->prepare(in, out, max);
-    }
-}
-
-void ServerService::sync(fd_set &in, fd_set &out)
-{
-    for (auto &server : m_servers)
-        server->sync(in, out);
-}
-
-bool ServerService::has(const std::string &name) const noexcept
-{
-    return std::count_if(m_servers.cbegin(), m_servers.end(), [&] (const auto &server) {
-        return server->name() == name;
-    }) > 0;
-}
-
-void ServerService::add(std::shared_ptr<Server> server)
-{
-    assert(!has(server->name()));
-
-    using namespace std::placeholders;
-
-    std::weak_ptr<Server> ptr(server);
-
-    server->onChannelMode.connect(std::bind(&ServerService::handleChannelMode, this, _1));
-    server->onChannelNotice.connect(std::bind(&ServerService::handleChannelNotice, this, _1));
-    server->onConnect.connect(std::bind(&ServerService::handleConnect, this, _1));
-    server->onInvite.connect(std::bind(&ServerService::handleInvite, this, _1));
-    server->onJoin.connect(std::bind(&ServerService::handleJoin, this, _1));
-    server->onKick.connect(std::bind(&ServerService::handleKick, this, _1));
-    server->onMessage.connect(std::bind(&ServerService::handleMessage, this, _1));
-    server->onMe.connect(std::bind(&ServerService::handleMe, this, _1));
-    server->onMode.connect(std::bind(&ServerService::handleMode, this, _1));
-    server->onNames.connect(std::bind(&ServerService::handleNames, this, _1));
-    server->onNick.connect(std::bind(&ServerService::handleNick, this, _1));
-    server->onNotice.connect(std::bind(&ServerService::handleNotice, this, _1));
-    server->onPart.connect(std::bind(&ServerService::handlePart, this, _1));
-    server->onQuery.connect(std::bind(&ServerService::handleQuery, this, _1));
-    server->onTopic.connect(std::bind(&ServerService::handleTopic, this, _1));
-    server->onWhois.connect(std::bind(&ServerService::handleWhois, this, _1));
-    server->onDie.connect([this, ptr] () {
-        m_irccd.post([=] (Irccd &) {
-            auto server = ptr.lock();
-
-            if (server) {
-                log::info("server {}: removed"_format(server->name()));
-                m_servers.erase(std::find(m_servers.begin(), m_servers.end(), server));
-            }
-        });
-    });
-
-    m_servers.push_back(std::move(server));
-}
-
-std::shared_ptr<Server> ServerService::get(const std::string &name) const noexcept
-{
-    auto it = std::find_if(m_servers.begin(), m_servers.end(), [&] (const auto &server) {
-        return server->name() == name;
-    });
-
-    if (it == m_servers.end())
-        return nullptr;
-
-    return *it;
-}
-
-std::shared_ptr<Server> ServerService::require(const std::string &name) const
-{
-    auto server = get(name);
-
-    if (!server)
-        throw std::invalid_argument("server {} not found"_format(name));
-
-    return server;
-}
-
-void ServerService::remove(const std::string &name)
-{
-    auto it = std::find_if(m_servers.begin(), m_servers.end(), [&] (const auto &server) {
-        return server->name() == name;
-    });
-
-    if (it != m_servers.end()) {
-        (*it)->disconnect();
-        m_servers.erase(it);
-    }
-}
-
-void ServerService::clear() noexcept
-{
-    for (auto &server : m_servers)
-        server->disconnect();
-
-    m_servers.clear();
-}
-
-} // !irccd
--- a/libirccd/irccd/service-server.hpp	Sun Nov 13 10:00:20 2016 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,141 +0,0 @@
-/*
- * service-server.hpp -- manage IRC servers
- *
- * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef IRCCD_SERVICE_SERVER_HPP
-#define IRCCD_SERVICE_SERVER_HPP
-
-/**
- * \file service-server.hpp
- * \brief Manage IRC servers.
- */
-
-#include <memory>
-#include <set>
-#include <string>
-
-#include "server.hpp"
-
-namespace irccd {
-
-class Irccd;
-
-/**
- * \brief Manage IRC servers.
- * \ingroup services
- */
-class ServerService {
-private:
-    Irccd &m_irccd;
-    std::vector<std::shared_ptr<Server>> m_servers;
-
-    void handleChannelMode(const ChannelModeEvent &);
-    void handleChannelNotice(const ChannelNoticeEvent &);
-    void handleConnect(const ConnectEvent &);
-    void handleInvite(const InviteEvent &);
-    void handleJoin(const JoinEvent &);
-    void handleKick(const KickEvent &);
-    void handleMessage(const MessageEvent &);
-    void handleMe(const MeEvent &);
-    void handleMode(const ModeEvent &);
-    void handleNames(const NamesEvent &);
-    void handleNick(const NickEvent &);
-    void handleNotice(const NoticeEvent &);
-    void handlePart(const PartEvent &);
-    void handleQuery(const QueryEvent &);
-    void handleTopic(const TopicEvent &);
-    void handleWhois(const WhoisEvent &);
-
-public:
-    /**
-     * Create the server service.
-     */
-    IRCCD_EXPORT ServerService(Irccd &instance);
-
-    /**
-     * \copydoc Service::prepare
-     */
-    IRCCD_EXPORT void prepare(fd_set &in, fd_set &out, net::Handle &max);
-
-    /**
-     * \copydoc Service::sync
-     */
-    IRCCD_EXPORT void sync(fd_set &in, fd_set &out);
-
-    /**
-     * Get the list of servers
-     *
-     * \return the servers
-     */
-    inline const std::vector<std::shared_ptr<Server>> &servers() const noexcept
-    {
-        return m_servers;
-    }
-
-    /**
-     * Check if a server exists.
-     *
-     * \param name the name
-     * \return true if exists
-     */
-    IRCCD_EXPORT bool has(const std::string &name) const noexcept;
-
-    /**
-     * Add a new server to the application.
-     *
-     * \pre hasServer must return false
-     * \param sv the server
-     */
-    IRCCD_EXPORT 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
-     */
-    IRCCD_EXPORT std::shared_ptr<Server> get(const std::string &name) const noexcept;
-
-    /**
-     * Find a server by name.
-     *
-     * \param name the server name
-     * \return the server
-     * \throw std::out_of_range if the server does not exist
-     */
-    IRCCD_EXPORT std::shared_ptr<Server> require(const std::string &name) const;
-
-    /**
-     * Remove a server from the irccd instance.
-     *
-     * The server if any, will be disconnected.
-     *
-     * \param name the server name
-     */
-    IRCCD_EXPORT void remove(const std::string &name);
-
-    /**
-     * Remove all servers.
-     *
-     * All servers will be disconnected.
-     */
-    IRCCD_EXPORT void clear() noexcept;
-};
-
-} // !irccd
-
-#endif // !IRCCD_SERVICE_SERVER_HPP
--- a/libirccd/irccd/service-transport.cpp	Sun Nov 13 10:00:20 2016 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,144 +0,0 @@
-/*
- * service-transport.cpp -- manage transport servers and clients
- *
- * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#include "command.hpp"
-#include "irccd.hpp"
-#include "json.hpp"
-#include "logger.hpp"
-#include "service-command.hpp"
-#include "service-transport.hpp"
-#include "transport.hpp"
-
-namespace irccd {
-
-void TransportService::handleCommand(std::weak_ptr<TransportClient> ptr, const nlohmann::json &object)
-{
-    assert(object.is_object());
-
-    m_irccd.post([=] (Irccd &) {
-        // 0. Be sure the object still exists.
-        auto tc = ptr.lock();
-
-        if (!tc)
-            return;
-
-        auto name = object.find("command");
-        if (name == object.end() || !name->is_string()) {
-            // TODO: send error.
-            log::warning("invalid command object");
-            return;
-        }
-
-        auto cmd = m_irccd.commands().find(*name);
-
-        if (!cmd)
-            tc->error(*name, "command does not exist");
-        else {
-            try {
-                cmd->exec(m_irccd, *tc, object);
-            } catch (const std::exception &ex) {
-                tc->error(cmd->name(), ex.what());
-            }
-        }
-    });
-}
-
-void TransportService::handleDie(std::weak_ptr<TransportClient> ptr)
-{
-    m_irccd.post([=] (Irccd &) {
-        log::info("transport: client disconnected");
-
-        auto tc = ptr.lock();
-
-        if (tc)
-            m_clients.erase(std::find(m_clients.begin(), m_clients.end(), tc));
-    });
-}
-
-TransportService::TransportService(Irccd &irccd) noexcept
-    : m_irccd(irccd)
-{
-}
-
-void TransportService::prepare(fd_set &in, fd_set &out, net::Handle &max)
-{
-    // Add transport servers.
-    for (const auto &transport : m_servers) {
-        FD_SET(transport->handle(), &in);
-
-        if (transport->handle() > max)
-            max = transport->handle();
-    }
-
-    // Transport clients.
-    for (const auto &client : m_clients)
-        client->prepare(in, out, max);
-}
-
-void TransportService::sync(fd_set &in, fd_set &out)
-{
-    using namespace std::placeholders;
-
-    // Transport clients.
-    for (const auto &client : m_clients) {
-        try {
-            client->sync(in, out);
-        } catch (const std::exception &ex) {
-            log::info() << "transport: client disconnected: " << ex.what() << std::endl;
-            handleDie(client);
-        }
-    }
-
-    // Transport servers.
-    for (const auto &transport : m_servers) {
-        if (!FD_ISSET(transport->handle(), &in))
-            continue;
-
-        log::debug("transport: new client connected");
-
-        std::shared_ptr<TransportClient> client = transport->accept();
-        std::weak_ptr<TransportClient> ptr(client);
-
-        try {
-            // Connect signals.
-            client->onCommand.connect(std::bind(&TransportService::handleCommand, this, ptr, _1));
-            client->onDie.connect(std::bind(&TransportService::handleDie, this, ptr));
-
-            // Register it.
-            m_clients.push_back(std::move(client));
-        } catch (const std::exception &ex) {
-            log::info() << "transport: client disconnected: " << ex.what() << std::endl;
-        }
-    }
-}
-
-void TransportService::add(std::shared_ptr<TransportServer> ts)
-{
-    m_servers.push_back(std::move(ts));
-}
-
-void TransportService::broadcast(const nlohmann::json &json)
-{
-    assert(json.is_object());
-
-    for (const auto &client : m_clients)
-        if (client->state() == TransportClient::Ready)
-            client->send(json);
-}
-
-} // !irccd
--- a/libirccd/irccd/service-transport.hpp	Sun Nov 13 10:00:20 2016 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,84 +0,0 @@
-/*
- * service-transport.hpp -- manage transport servers and clients
- *
- * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef IRCCD_SERVICE_TRANSPORT_HPP
-#define IRCCD_SERVICE_TRANSPORT_HPP
-
-/**
- * \file service-transport.hpp
- * \brief manage transport servers and clients.
- */
-
-#include <json.hpp>
-
-namespace irccd {
-
-class TransportServer;
-class TransportClient;
-
-/**
- * \brief manage transport servers and clients.
- * \ingroup services
- */
-class TransportService {
-private:
-    Irccd &m_irccd;
-
-    std::vector<std::shared_ptr<TransportServer>> m_servers;
-    std::vector<std::shared_ptr<TransportClient>> m_clients;
-
-    void handleCommand(std::weak_ptr<TransportClient>, const nlohmann::json &);
-    void handleDie(std::weak_ptr<TransportClient>);
-
-public:
-    /**
-     * Create the transport service.
-     *
-     * \param irccd the irccd instance
-     */
-    IRCCD_EXPORT TransportService(Irccd &irccd) noexcept;
-
-    /**
-     * \copydoc Service::prepare
-     */
-    IRCCD_EXPORT void prepare(fd_set &in, fd_set &out, net::Handle &max);
-
-    /**
-     * \copydoc Service::sync
-     */
-    IRCCD_EXPORT void sync(fd_set &in, fd_set &out);
-
-    /**
-     * Add a transport server.
-     *
-     * \param ts the transport server
-     */
-    IRCCD_EXPORT void add(std::shared_ptr<TransportServer> ts);
-
-    /**
-     * Send data to all clients.
-     *
-     * \pre object.is_object()
-     * \param object the json object
-     */
-    IRCCD_EXPORT void broadcast(const nlohmann::json &object);
-};
-
-} // !irccd
-
-#endif // !IRCCD_SERVICE_TRANSPORT_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/service.cpp	Sun Nov 13 10:41:28 2016 +0100
@@ -0,0 +1,999 @@
+/*
+ * service.cpp -- irccd services
+ *
+ * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <algorithm>
+#include <array>
+#include <functional>
+#include <stdexcept>
+
+#include <format.h>
+
+#include "irccd.hpp"
+#include "logger.hpp"
+#include "service.hpp"
+#include "transport.hpp"
+
+using namespace fmt::literals;
+
+namespace irccd {
+
+/*
+ * CommandService.
+ * ------------------------------------------------------------------
+ */
+
+bool CommandService::contains(const std::string &name) const noexcept
+{
+    return find(name) != nullptr;
+}
+
+std::shared_ptr<Command> CommandService::find(const std::string &name) const noexcept
+{
+    auto it = std::find_if(m_commands.begin(), m_commands.end(), [&] (const auto &cmd) {
+        return cmd->name() == name;
+    });
+
+    return it == m_commands.end() ? nullptr : *it;
+}
+
+void CommandService::add(std::shared_ptr<Command> command)
+{
+    auto it = std::find_if(m_commands.begin(), m_commands.end(), [&] (const auto &cmd) {
+        return cmd->name() == command->name();
+    });
+
+    if (it != m_commands.end())
+        *it = std::move(command);
+    else
+        m_commands.push_back(std::move(command));
+}
+
+/*
+ * InterruptService.
+ * ------------------------------------------------------------------
+ */
+
+InterruptService::InterruptService()
+    : m_in(AF_INET, 0)
+    , m_out(AF_INET, 0)
+{
+    // Bind a socket to any port.
+    m_in.set(net::option::SockReuseAddress(true));
+    m_in.bind(net::ipv4::any(0));
+    m_in.listen(1);
+
+    // Do the socket pair.
+    m_out.connect(net::ipv4::pton("127.0.0.1", net::ipv4::port(m_in.getsockname())));
+    m_in = m_in.accept();
+    m_out.set(net::option::SockBlockMode(false));
+}
+
+void InterruptService::prepare(fd_set &in, fd_set &, net::Handle &max)
+{
+    FD_SET(m_in.handle(), &in);
+
+    if (m_in.handle() > max)
+        max = m_in.handle();
+}
+
+void InterruptService::sync(fd_set &in, fd_set &)
+{
+    if (FD_ISSET(m_in.handle(), &in)) {
+        static std::array<char, 32> tmp;
+
+        try {
+            log::debug("irccd: interrupt service recv");
+            m_in.recv(tmp.data(), 32);
+        } catch (const std::exception &ex) {
+            log::warning() << "irccd: interrupt service error: " << ex.what() << std::endl;
+        }
+    }
+}
+
+void InterruptService::interrupt() noexcept
+{
+    try {
+        static char byte;
+
+        log::debug("irccd: interrupt service send");
+        m_out.send(&byte, 1);
+    } catch (const std::exception &ex) {
+        log::warning() << "irccd: interrupt service error: " << ex.what() << std::endl;
+    }
+}
+
+/*
+ * PluginService.
+ * ------------------------------------------------------------------
+ */
+
+PluginService::PluginService(Irccd &irccd) noexcept
+    : m_irccd(irccd)
+{
+}
+
+PluginService::~PluginService()
+{
+    for (const auto &plugin : m_plugins)
+        plugin->onUnload(m_irccd);
+}
+
+bool PluginService::has(const std::string &name) const noexcept
+{
+    return std::count_if(m_plugins.cbegin(), m_plugins.cend(), [&] (const auto &plugin) {
+        return plugin->name() == name;
+    }) > 0;
+}
+
+std::shared_ptr<Plugin> PluginService::get(const std::string &name) const noexcept
+{
+    auto it = std::find_if(m_plugins.begin(), m_plugins.end(), [&] (const auto &plugin) {
+        return plugin->name() == name;
+    });
+
+    if (it == m_plugins.end())
+        return nullptr;
+
+    return *it;
+}
+
+std::shared_ptr<Plugin> PluginService::require(const std::string &name) const
+{
+    auto plugin = get(name);
+
+    if (!plugin)
+        throw std::invalid_argument("plugin {} not found"_format(name));
+
+    return plugin;
+}
+
+void PluginService::add(std::shared_ptr<Plugin> plugin)
+{
+    m_plugins.push_back(std::move(plugin));
+}
+
+void PluginService::addLoader(std::unique_ptr<PluginLoader> loader)
+{
+    m_loaders.push_back(std::move(loader));
+}
+
+void PluginService::setConfig(const std::string &name, PluginConfig config)
+{
+    m_config.emplace(name, std::move(config));
+}
+
+PluginConfig PluginService::config(const std::string &name) const
+{
+    auto it = m_config.find(name);
+
+    if (it != m_config.end())
+        return it->second;
+
+    return PluginConfig();
+}
+
+void PluginService::setFormats(const std::string &name, PluginFormats formats)
+{
+    m_formats.emplace(name, std::move(formats));
+}
+
+PluginFormats PluginService::formats(const std::string &name) const
+{
+    auto it = m_formats.find(name);
+
+    if (it != m_formats.end())
+        return it->second;
+
+    return PluginFormats();
+}
+
+std::shared_ptr<Plugin> PluginService::open(const std::string &id,
+                                            const std::string &path)
+{
+    for (const auto &loader : m_loaders) {
+        auto plugin = loader->open(id, path);
+
+        if (plugin)
+            return plugin;
+    }
+
+    return nullptr;
+}
+
+std::shared_ptr<Plugin> PluginService::find(const std::string &id)
+{
+    for (const auto &loader : m_loaders) {
+        auto plugin = loader->find(id);
+
+        if (plugin)
+            return plugin;
+    }
+
+    return nullptr;
+}
+
+void PluginService::load(std::string name, std::string path)
+{
+    if (has(name))
+        return;
+
+    try {
+        std::shared_ptr<Plugin> plugin;
+
+        if (path.empty())
+            plugin = find(name);
+        else
+            plugin = open(name, std::move(path));
+
+        if (plugin) {
+            plugin->setConfig(m_config[name]);
+            plugin->setFormats(m_formats[name]);
+            plugin->onLoad(m_irccd);
+
+            add(std::move(plugin));
+        }
+    } catch (const std::exception &ex) {
+        log::warning("plugin {}: {}"_format(name, ex.what()));
+    }
+}
+
+void PluginService::reload(const std::string &name)
+{
+    auto plugin = get(name);
+
+    if (plugin)
+        plugin->onReload(m_irccd);
+}
+
+void PluginService::unload(const std::string &name)
+{
+    auto it = std::find_if(m_plugins.begin(), m_plugins.end(), [&] (const auto &plugin) {
+        return plugin->name() == name;
+    });
+
+    if (it != m_plugins.end()) {
+        (*it)->onUnload(m_irccd);
+        m_plugins.erase(it);
+    }
+}
+
+/*
+ * RuleService.
+ * ------------------------------------------------------------------
+ */
+
+void RuleService::add(Rule rule)
+{
+    m_rules.push_back(std::move(rule));
+}
+
+void RuleService::insert(Rule rule, unsigned position)
+{
+    assert(position <= m_rules.size());
+
+    m_rules.insert(m_rules.begin() + position, std::move(rule));
+}
+
+void RuleService::remove(unsigned position)
+{
+    assert(position < m_rules.size());
+
+    m_rules.erase(m_rules.begin() + position);
+}
+
+bool RuleService::solve(const std::string &server,
+                        const std::string &channel,
+                        const std::string &origin,
+                        const std::string &plugin,
+                        const std::string &event) noexcept
+{
+    bool result = true;
+
+    log::debug("rule: solving for server={}, channel={}, origin={}, plugin={}, event={}"_format(server, channel,
+           origin, plugin, event));
+
+    int i = 0;
+    for (const Rule &rule : m_rules) {
+        log::debug() << "  candidate " << i++ << ":\n"
+                 << "    servers: " << util::join(rule.servers().begin(), rule.servers().end()) << "\n"
+                 << "    channels: " << util::join(rule.channels().begin(), rule.channels().end()) << "\n"
+                 << "    origins: " << util::join(rule.origins().begin(), rule.origins().end()) << "\n"
+                 << "    plugins: " << util::join(rule.plugins().begin(), rule.plugins().end()) << "\n"
+                 << "    events: " << util::join(rule.events().begin(), rule.events().end()) << "\n"
+                 << "    action: " << ((rule.action() == RuleAction::Accept) ? "accept" : "drop") << std::endl;
+
+        if (rule.match(server, channel, origin, plugin, event))
+            result = rule.action() == RuleAction::Accept;
+    }
+
+    return result;
+}
+
+/*
+ * ServerService.
+ * ------------------------------------------------------------------
+ */
+
+class EventHandler {
+public:
+    std::string server;
+    std::string origin;
+    std::string target;
+    std::function<std::string (Plugin &)> functionName;
+    std::function<void (Plugin &)> functionExec;
+
+    void operator()(Irccd &irccd) const
+    {
+        for (auto &plugin : irccd.plugins().list()) {
+            auto eventname = functionName(*plugin);
+            auto allowed = irccd.rules().solve(server, target, origin, plugin->name(), eventname);
+
+            if (!allowed) {
+                log::debug() << "rule: event skipped on match" << std::endl;
+                continue;
+            } else
+                log::debug() << "rule: event allowed" << std::endl;
+
+            // TODO: server-event must not know which type of plugin.
+            // TODO: get generic error.
+            // TODO: this is the responsability of service-plugin.
+            try {
+                functionExec(*plugin);
+            } catch (const std::exception &ex) {
+                log::warning() << "plugin " << plugin->name() << ": error: " << ex.what() << std::endl;
+            }
+        }
+    }
+};
+
+void ServerService::handleChannelMode(const ChannelModeEvent &ev)
+{
+    log::debug() << "server " << ev.server->name() << ": event onChannelMode:\n";
+    log::debug() << "  origin: " << ev.origin << "\n";
+    log::debug() << "  channel: " << ev.channel << "\n";
+    log::debug() << "  mode: " << ev.mode << "\n";
+    log::debug() << "  argument: " << ev.argument << std::endl;
+
+    m_irccd.transports().broadcast(nlohmann::json::object({
+        { "event",      "onChannelMode"     },
+        { "server",     ev.server->name()   },
+        { "origin",     ev.origin           },
+        { "channel",    ev.channel          },
+        { "mode",       ev.mode             },
+        { "argument",   ev.argument         }
+    }));
+
+    m_irccd.post(EventHandler{ev.server->name(), ev.origin, ev.channel,
+        [=] (Plugin &) -> std::string {
+            return "onChannelMode";
+        },
+        [=] (Plugin &plugin) {
+            plugin.onChannelMode(m_irccd, ev);
+        }
+    });
+}
+
+void ServerService::handleChannelNotice(const ChannelNoticeEvent &ev)
+{
+    log::debug() << "server " << ev.server->name() << ": event onChannelNotice:\n";
+    log::debug() << "  origin: " << ev.origin << "\n";
+    log::debug() << "  channel: " << ev.channel << "\n";
+    log::debug() << "  message: " << ev.message << std::endl;
+
+    m_irccd.transports().broadcast(nlohmann::json::object({
+        { "event",      "onChannelNotice"   },
+        { "server",     ev.server->name()   },
+        { "origin",     ev.origin           },
+        { "channel",    ev.channel          },
+        { "message",    ev.message          }
+    }));
+
+    m_irccd.post(EventHandler{ev.server->name(), ev.origin, ev.channel,
+        [=] (Plugin &) -> std::string {
+            return "onChannelNotice";
+        },
+        [=] (Plugin &plugin) {
+            plugin.onChannelNotice(m_irccd, ev);
+        }
+    });
+}
+
+void ServerService::handleConnect(const ConnectEvent &ev)
+{
+    log::debug() << "server " << ev.server->name() << ": event onConnect" << std::endl;
+
+    m_irccd.transports().broadcast(nlohmann::json::object({
+        { "event",      "onConnect"         },
+        { "server",     ev.server->name()   }
+    }));
+
+    m_irccd.post(EventHandler{ev.server->name(), /* origin */ "", /* channel */ "",
+        [=] (Plugin &) -> std::string {
+            return "onConnect";
+        },
+        [=] (Plugin &plugin) {
+            plugin.onConnect(m_irccd, ev);
+        }
+    });
+}
+
+void ServerService::handleInvite(const InviteEvent &ev)
+{
+    log::debug() << "server " << ev.server->name() << ": event onInvite:\n";
+    log::debug() << "  origin: " << ev.origin << "\n";
+    log::debug() << "  channel: " << ev.channel << "\n";
+    log::debug() << "  target: " << ev.nickname << std::endl;
+
+    m_irccd.transports().broadcast(nlohmann::json::object({
+        { "event",      "onInvite"          },
+        { "server",     ev.server->name()   },
+        { "origin",     ev.origin           },
+        { "channel",    ev.channel          }
+    }));
+
+    m_irccd.post(EventHandler{ev.server->name(), ev.origin, ev.channel,
+        [=] (Plugin &) -> std::string {
+            return "onInvite";
+        },
+        [=] (Plugin &plugin) {
+            plugin.onInvite(m_irccd, ev);
+        }
+    });
+}
+
+void ServerService::handleJoin(const JoinEvent &ev)
+{
+    log::debug() << "server " << ev.server->name() << ": event onJoin:\n";
+    log::debug() << "  origin: " << ev.origin << "\n";
+    log::debug() << "  channel: " << ev.channel << std::endl;
+
+    m_irccd.transports().broadcast(nlohmann::json::object({
+        { "event",      "onJoin"            },
+        { "server",     ev.server->name()   },
+        { "origin",     ev.origin           },
+        { "channel",    ev.channel          }
+    }));
+
+    m_irccd.post(EventHandler{ev.server->name(), ev.origin, ev.channel,
+        [=] (Plugin &) -> std::string {
+            return "onJoin";
+        },
+        [=] (Plugin &plugin) {
+            plugin.onJoin(m_irccd, ev);
+        }
+    });
+}
+
+void ServerService::handleKick(const KickEvent &ev)
+{
+    log::debug() << "server " << ev.server->name() << ": event onKick:\n";
+    log::debug() << "  origin: " << ev.origin << "\n";
+    log::debug() << "  channel: " << ev.channel << "\n";
+    log::debug() << "  target: " << ev.target << "\n";
+    log::debug() << "  reason: " << ev.reason << std::endl;
+
+    m_irccd.transports().broadcast(nlohmann::json::object({
+        { "event",      "onKick"            },
+        { "server",     ev.server->name()   },
+        { "origin",     ev.origin           },
+        { "channel",    ev.channel          },
+        { "target",     ev.target           },
+        { "reason",     ev.reason           }
+    }));
+
+    m_irccd.post(EventHandler{ev.server->name(), ev.origin, ev.channel,
+        [=] (Plugin &) -> std::string {
+            return "onKick";
+        },
+        [=] (Plugin &plugin) {
+            plugin.onKick(m_irccd, ev);
+        }
+    });
+}
+
+void ServerService::handleMessage(const MessageEvent &ev)
+{
+    log::debug() << "server " << ev.server->name() << ": event onMessage:\n";
+    log::debug() << "  origin: " << ev.origin << "\n";
+    log::debug() << "  channel: " << ev.channel << "\n";
+    log::debug() << "  message: " << ev.message << std::endl;
+
+    m_irccd.transports().broadcast(nlohmann::json::object({
+        { "event",      "onMessage"         },
+        { "server",     ev.server->name()   },
+        { "origin",     ev.origin           },
+        { "channel",    ev.channel          },
+        { "message",    ev.message          }
+    }));
+
+    m_irccd.post(EventHandler{ev.server->name(), ev.origin, ev.channel,
+        [=] (Plugin &plugin) -> std::string {
+            return util::parseMessage(ev.message, ev.server->commandCharacter(), plugin.name()).second == util::MessageType::Command ? "onCommand" : "onMessage";
+        },
+        [=] (Plugin &plugin) mutable {
+            auto copy = ev;
+            auto pack = util::parseMessage(copy.message, copy.server->commandCharacter(), plugin.name());
+
+            copy.message = pack.first;
+
+            if (pack.second == util::MessageType::Command)
+                plugin.onCommand(m_irccd, copy);
+            else
+                plugin.onMessage(m_irccd, copy);
+        }
+    });
+}
+
+void ServerService::handleMe(const MeEvent &ev)
+{
+    log::debug() << "server " << ev.server->name() << ": event onMe:\n";
+    log::debug() << "  origin: " << ev.origin << "\n";
+    log::debug() << "  target: " << ev.channel << "\n";
+    log::debug() << "  message: " << ev.message << std::endl;
+
+    m_irccd.transports().broadcast(nlohmann::json::object({
+        { "event",      "onMe"              },
+        { "server",     ev.server->name()   },
+        { "origin",     ev.origin           },
+        { "target",     ev.channel          },
+        { "message",    ev.message          }
+    }));
+
+    m_irccd.post(EventHandler{ev.server->name(), ev.origin, ev.channel,
+        [=] (Plugin &) -> std::string {
+            return "onMe";
+        },
+        [=] (Plugin &plugin) {
+            plugin.onMe(m_irccd, ev);
+        }
+    });
+}
+
+void ServerService::handleMode(const ModeEvent &ev)
+{
+    log::debug() << "server " << ev.server->name() << ": event onMode\n";
+    log::debug() << "  origin: " << ev.origin << "\n";
+    log::debug() << "  mode: " << ev.mode << std::endl;
+
+    m_irccd.transports().broadcast(nlohmann::json::object({
+        { "event",      "onMode"            },
+        { "server",     ev.server->name()   },
+        { "origin",     ev.origin           },
+        { "mode",       ev.mode             }
+    }));
+
+    m_irccd.post(EventHandler{ev.server->name(), ev.origin, /* channel */ "",
+        [=] (Plugin &) -> std::string {
+            return "onMode";
+        },
+        [=] (Plugin &plugin) {
+            plugin.onMode(m_irccd, ev);
+        }
+    });
+}
+
+void ServerService::handleNames(const NamesEvent &ev)
+{
+    log::debug() << "server " << ev.server->name() << ": event onNames:\n";
+    log::debug() << "  channel: " << ev.channel << "\n";
+    log::debug() << "  names: " << 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);
+
+    m_irccd.transports().broadcast(nlohmann::json::object({
+        { "event",      "onNames"           },
+        { "server",     ev.server->name()   },
+        { "channel",    ev.channel          },
+        { "names",      std::move(names)    }
+    }));
+
+    m_irccd.post(EventHandler{ev.server->name(), /* origin */ "", ev.channel,
+        [=] (Plugin &) -> std::string {
+            return "onNames";
+        },
+        [=] (Plugin &plugin) {
+            plugin.onNames(m_irccd, ev);
+        }
+    });
+}
+
+void ServerService::handleNick(const NickEvent &ev)
+{
+    log::debug() << "server " << ev.server->name() << ": event onNick:\n";
+    log::debug() << "  origin: " << ev.origin << "\n";
+    log::debug() << "  nickname: " << ev.nickname << std::endl;
+
+    m_irccd.transports().broadcast(nlohmann::json::object({
+        { "event",      "onNick"            },
+        { "server",     ev.server->name()   },
+        { "origin",     ev.origin           },
+        { "nickname",   ev.nickname         }
+    }));
+
+    m_irccd.post(EventHandler{ev.server->name(), ev.origin, /* channel */ "",
+        [=] (Plugin &) -> std::string {
+            return "onNick";
+        },
+        [=] (Plugin &plugin) {
+            plugin.onNick(m_irccd, ev);
+        }
+    });
+}
+
+void ServerService::handleNotice(const NoticeEvent &ev)
+{
+    log::debug() << "server " << ev.server->name() << ": event onNotice:\n";
+    log::debug() << "  origin: " << ev.origin << "\n";
+    log::debug() << "  message: " << ev.message << std::endl;
+
+    m_irccd.transports().broadcast(nlohmann::json::object({
+        { "event",      "onNotice"          },
+        { "server",     ev.server->name()   },
+        { "origin",     ev.origin           },
+        { "message",    ev.message          }
+    }));
+
+    m_irccd.post(EventHandler{ev.server->name(), ev.origin, /* channel */ "",
+        [=] (Plugin &) -> std::string {
+            return "onNotice";
+        },
+        [=] (Plugin &plugin) {
+            plugin.onNotice(m_irccd, ev);
+        }
+    });
+}
+
+void ServerService::handlePart(const PartEvent &ev)
+{
+    log::debug() << "server " << ev.server->name() << ": event onPart:\n";
+    log::debug() << "  origin: " << ev.origin << "\n";
+    log::debug() << "  channel: " << ev.channel << "\n";
+    log::debug() << "  reason: " << ev.reason << std::endl;
+
+    m_irccd.transports().broadcast(nlohmann::json::object({
+        { "event",      "onPart"            },
+        { "server",     ev.server->name()   },
+        { "origin",     ev.origin           },
+        { "channel",    ev.channel          },
+        { "reason",     ev.reason           }
+    }));
+
+    m_irccd.post(EventHandler{ev.server->name(), ev.origin, ev.channel,
+        [=] (Plugin &) -> std::string {
+            return "onPart";
+        },
+        [=] (Plugin &plugin) {
+            plugin.onPart(m_irccd, ev);
+        }
+    });
+}
+
+void ServerService::handleQuery(const QueryEvent &ev)
+{
+    log::debug() << "server " << ev.server->name() << ": event onQuery:\n";
+    log::debug() << "  origin: " << ev.origin << "\n";
+    log::debug() << "  message: " << ev.message << std::endl;
+
+    m_irccd.transports().broadcast(nlohmann::json::object({
+        { "event",      "onQuery"           },
+        { "server",     ev.server->name()   },
+        { "origin",     ev.origin           },
+        { "message",    ev.message          }
+    }));
+
+    m_irccd.post(EventHandler{ev.server->name(), ev.origin, /* channel */ "",
+        [=] (Plugin &plugin) -> std::string {
+            return util::parseMessage(ev.message, ev.server->commandCharacter(), plugin.name()).second == util::MessageType::Command ? "onQueryCommand" : "onQuery";
+        },
+        [=] (Plugin &plugin) mutable {
+            auto copy = ev;
+            auto pack = util::parseMessage(copy.message, copy.server->commandCharacter(), plugin.name());
+
+            copy.message = pack.first;
+
+            if (pack.second == util::MessageType::Command)
+                plugin.onQueryCommand(m_irccd, copy);
+            else
+                plugin.onQuery(m_irccd, copy);
+        }
+    });
+}
+
+void ServerService::handleTopic(const TopicEvent &ev)
+{
+    log::debug() << "server " << ev.server->name() << ": event onTopic:\n";
+    log::debug() << "  origin: " << ev.origin << "\n";
+    log::debug() << "  channel: " << ev.channel << "\n";
+    log::debug() << "  topic: " << ev.topic << std::endl;
+
+    m_irccd.transports().broadcast(nlohmann::json::object({
+        { "event",      "onTopic"           },
+        { "server",     ev.server->name()   },
+        { "origin",     ev.origin           },
+        { "channel",    ev.channel          },
+        { "topic",      ev.topic            }
+    }));
+
+    m_irccd.post(EventHandler{ev.server->name(), ev.origin, ev.channel,
+        [=] (Plugin &) -> std::string {
+            return "onTopic";
+        },
+        [=] (Plugin &plugin) {
+            plugin.onTopic(m_irccd, ev);
+        }
+    });
+}
+
+void ServerService::handleWhois(const WhoisEvent &ev)
+{
+    log::debug() << "server " << ev.server->name() << ": event onWhois\n";
+    log::debug() << "  nickname: " << ev.whois.nick << "\n";
+    log::debug() << "  username: " << ev.whois.user << "\n";
+    log::debug() << "  host: " << ev.whois.host << "\n";
+    log::debug() << "  realname: " << ev.whois.realname << "\n";
+    log::debug() << "  channels: " << util::join(ev.whois.channels.begin(), ev.whois.channels.end()) << std::endl;
+
+    m_irccd.transports().broadcast(nlohmann::json::object({
+        { "event",      "onWhois"           },
+        { "server",     ev.server->name()   },
+        { "nickname",   ev.whois.nick       },
+        { "username",   ev.whois.user       },
+        { "host",       ev.whois.host       },
+        { "realname",   ev.whois.realname   }
+    }));
+
+    m_irccd.post(EventHandler{ev.server->name(), /* origin */ "", /* channel */ "",
+        [=] (Plugin &) -> std::string {
+            return "onWhois";
+        },
+        [=] (Plugin &plugin) {
+            plugin.onWhois(m_irccd, ev);
+        }
+    });
+}
+
+ServerService::ServerService(Irccd &irccd)
+    : m_irccd(irccd)
+{
+}
+
+void ServerService::prepare(fd_set &in, fd_set &out, net::Handle &max)
+{
+    for (auto &server : m_servers) {
+        server->update();
+        server->prepare(in, out, max);
+    }
+}
+
+void ServerService::sync(fd_set &in, fd_set &out)
+{
+    for (auto &server : m_servers)
+        server->sync(in, out);
+}
+
+bool ServerService::has(const std::string &name) const noexcept
+{
+    return std::count_if(m_servers.cbegin(), m_servers.end(), [&] (const auto &server) {
+        return server->name() == name;
+    }) > 0;
+}
+
+void ServerService::add(std::shared_ptr<Server> server)
+{
+    assert(!has(server->name()));
+
+    using namespace std::placeholders;
+
+    std::weak_ptr<Server> ptr(server);
+
+    server->onChannelMode.connect(std::bind(&ServerService::handleChannelMode, this, _1));
+    server->onChannelNotice.connect(std::bind(&ServerService::handleChannelNotice, this, _1));
+    server->onConnect.connect(std::bind(&ServerService::handleConnect, this, _1));
+    server->onInvite.connect(std::bind(&ServerService::handleInvite, this, _1));
+    server->onJoin.connect(std::bind(&ServerService::handleJoin, this, _1));
+    server->onKick.connect(std::bind(&ServerService::handleKick, this, _1));
+    server->onMessage.connect(std::bind(&ServerService::handleMessage, this, _1));
+    server->onMe.connect(std::bind(&ServerService::handleMe, this, _1));
+    server->onMode.connect(std::bind(&ServerService::handleMode, this, _1));
+    server->onNames.connect(std::bind(&ServerService::handleNames, this, _1));
+    server->onNick.connect(std::bind(&ServerService::handleNick, this, _1));
+    server->onNotice.connect(std::bind(&ServerService::handleNotice, this, _1));
+    server->onPart.connect(std::bind(&ServerService::handlePart, this, _1));
+    server->onQuery.connect(std::bind(&ServerService::handleQuery, this, _1));
+    server->onTopic.connect(std::bind(&ServerService::handleTopic, this, _1));
+    server->onWhois.connect(std::bind(&ServerService::handleWhois, this, _1));
+    server->onDie.connect([this, ptr] () {
+        m_irccd.post([=] (Irccd &) {
+            auto server = ptr.lock();
+
+            if (server) {
+                log::info("server {}: removed"_format(server->name()));
+                m_servers.erase(std::find(m_servers.begin(), m_servers.end(), server));
+            }
+        });
+    });
+
+    m_servers.push_back(std::move(server));
+}
+
+std::shared_ptr<Server> ServerService::get(const std::string &name) const noexcept
+{
+    auto it = std::find_if(m_servers.begin(), m_servers.end(), [&] (const auto &server) {
+        return server->name() == name;
+    });
+
+    if (it == m_servers.end())
+        return nullptr;
+
+    return *it;
+}
+
+std::shared_ptr<Server> ServerService::require(const std::string &name) const
+{
+    auto server = get(name);
+
+    if (!server)
+        throw std::invalid_argument("server {} not found"_format(name));
+
+    return server;
+}
+
+void ServerService::remove(const std::string &name)
+{
+    auto it = std::find_if(m_servers.begin(), m_servers.end(), [&] (const auto &server) {
+        return server->name() == name;
+    });
+
+    if (it != m_servers.end()) {
+        (*it)->disconnect();
+        m_servers.erase(it);
+    }
+}
+
+void ServerService::clear() noexcept
+{
+    for (auto &server : m_servers)
+        server->disconnect();
+
+    m_servers.clear();
+}
+
+/*
+ * TransportService.
+ * ------------------------------------------------------------------
+ */
+
+void TransportService::handleCommand(std::weak_ptr<TransportClient> ptr, const nlohmann::json &object)
+{
+    assert(object.is_object());
+
+    m_irccd.post([=] (Irccd &) {
+        // 0. Be sure the object still exists.
+        auto tc = ptr.lock();
+
+        if (!tc)
+            return;
+
+        auto name = object.find("command");
+        if (name == object.end() || !name->is_string()) {
+            // TODO: send error.
+            log::warning("invalid command object");
+            return;
+        }
+
+        auto cmd = m_irccd.commands().find(*name);
+
+        if (!cmd)
+            tc->error(*name, "command does not exist");
+        else {
+            try {
+                cmd->exec(m_irccd, *tc, object);
+            } catch (const std::exception &ex) {
+                tc->error(cmd->name(), ex.what());
+            }
+        }
+    });
+}
+
+void TransportService::handleDie(std::weak_ptr<TransportClient> ptr)
+{
+    m_irccd.post([=] (Irccd &) {
+        log::info("transport: client disconnected");
+
+        auto tc = ptr.lock();
+
+        if (tc)
+            m_clients.erase(std::find(m_clients.begin(), m_clients.end(), tc));
+    });
+}
+
+TransportService::TransportService(Irccd &irccd) noexcept
+    : m_irccd(irccd)
+{
+}
+
+void TransportService::prepare(fd_set &in, fd_set &out, net::Handle &max)
+{
+    // Add transport servers.
+    for (const auto &transport : m_servers) {
+        FD_SET(transport->handle(), &in);
+
+        if (transport->handle() > max)
+            max = transport->handle();
+    }
+
+    // Transport clients.
+    for (const auto &client : m_clients)
+        client->prepare(in, out, max);
+}
+
+void TransportService::sync(fd_set &in, fd_set &out)
+{
+    using namespace std::placeholders;
+
+    // Transport clients.
+    for (const auto &client : m_clients) {
+        try {
+            client->sync(in, out);
+        } catch (const std::exception &ex) {
+            log::info() << "transport: client disconnected: " << ex.what() << std::endl;
+            handleDie(client);
+        }
+    }
+
+    // Transport servers.
+    for (const auto &transport : m_servers) {
+        if (!FD_ISSET(transport->handle(), &in))
+            continue;
+
+        log::debug("transport: new client connected");
+
+        std::shared_ptr<TransportClient> client = transport->accept();
+        std::weak_ptr<TransportClient> ptr(client);
+
+        try {
+            // Connect signals.
+            client->onCommand.connect(std::bind(&TransportService::handleCommand, this, ptr, _1));
+            client->onDie.connect(std::bind(&TransportService::handleDie, this, ptr));
+
+            // Register it.
+            m_clients.push_back(std::move(client));
+        } catch (const std::exception &ex) {
+            log::info() << "transport: client disconnected: " << ex.what() << std::endl;
+        }
+    }
+}
+
+void TransportService::add(std::shared_ptr<TransportServer> ts)
+{
+    m_servers.push_back(std::move(ts));
+}
+
+void TransportService::broadcast(const nlohmann::json &json)
+{
+    assert(json.is_object());
+
+    for (const auto &client : m_clients)
+        if (client->state() == TransportClient::Ready)
+            client->send(json);
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/service.hpp	Sun Nov 13 10:41:28 2016 +0100
@@ -0,0 +1,531 @@
+/*
+ * service.hpp -- irccd services
+ *
+ * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_SERVICE_HPP
+#define IRCCD_SERVICE_HPP
+
+/**
+ * \file service.hpp
+ * \brief Irccd services.
+ */
+
+#include <memory>
+#include <unordered_map>
+#include <vector>
+
+#include <json.hpp>
+
+#include "command.hpp"
+#include "net.hpp"
+#include "plugin.hpp"
+#include "rule.hpp"
+#include "server.hpp"
+#include "sysconfig.hpp"
+
+namespace irccd {
+
+/*
+ * CommandService.
+ * ------------------------------------------------------------------
+ */
+
+/**
+ * \brief Store remote commands.
+ * \ingroup services
+ */
+class CommandService {
+private:
+    std::vector<std::shared_ptr<Command>> m_commands;
+
+public:
+    /**
+     * Get all commands.
+     *
+     * \return the list of commands.
+     */
+    inline const std::vector<std::shared_ptr<Command>> &commands() const noexcept
+    {
+        return m_commands;
+    }
+
+    /**
+     * Tells if a command exists.
+     *
+     * \param name the command name
+     * \return true if the command exists
+     */
+    IRCCD_EXPORT bool contains(const std::string &name) const noexcept;
+
+    /**
+     * Find a command by name.
+     *
+     * \param name the command name
+     * \return the command or empty one if not found
+     */
+    IRCCD_EXPORT std::shared_ptr<Command> find(const std::string &name) const noexcept;
+
+    /**
+     * Add a command or replace existing one.
+     *
+     * \pre command != nullptr
+     * \param command the command name
+     */
+    IRCCD_EXPORT void add(std::shared_ptr<Command> command);
+};
+
+/*
+ * InterruptService.
+ * ------------------------------------------------------------------
+ */
+
+/**
+ * \brief Interrupt irccd event loop.
+ * \ingroup services
+ */
+class InterruptService {
+private:
+    net::TcpSocket m_in;
+    net::TcpSocket m_out;
+
+public:
+    /**
+     * Prepare the socket pair.
+     *
+     * \throw std::runtime_error on errors
+     */
+    IRCCD_EXPORT InterruptService();
+
+    /**
+     * \copydoc Service::prepare
+     */
+    IRCCD_EXPORT void prepare(fd_set &in, fd_set &out, net::Handle &max);
+
+    /**
+     * \copydoc Service::sync
+     */
+    IRCCD_EXPORT void sync(fd_set &in, fd_set &out);
+
+    /**
+     * Request interruption.
+     */
+    IRCCD_EXPORT void interrupt() noexcept;
+};
+
+/*
+ * PluginService.
+ * ------------------------------------------------------------------
+ */
+
+/**
+ * \brief Manage plugins.
+ * \ingroup services
+ */
+class PluginService {
+private:
+    Irccd &m_irccd;
+    std::vector<std::shared_ptr<Plugin>> m_plugins;
+    std::vector<std::unique_ptr<PluginLoader>> m_loaders;
+    std::unordered_map<std::string, PluginConfig> m_config;
+    std::unordered_map<std::string, PluginFormats> m_formats;
+
+public:
+    /**
+     * Create the plugin service.
+     *
+     * \param irccd the irccd instance
+     */
+    IRCCD_EXPORT PluginService(Irccd &irccd) noexcept;
+
+    /**
+     * Destroy plugins.
+     */
+    IRCCD_EXPORT ~PluginService();
+
+    /**
+     * Get the list of plugins.
+     *
+     * \return the list of plugins
+     */
+    inline const std::vector<std::shared_ptr<Plugin>> &list() const noexcept
+    {
+        return m_plugins;
+    }
+
+    /**
+     * Check if a plugin is loaded.
+     *
+     * \param name the plugin id
+     * \return true if has plugin
+     */
+    IRCCD_EXPORT bool has(const std::string &name) const noexcept;
+
+    /**
+     * Get a loaded plugin or null if not found.
+     *
+     * \param name the plugin id
+     * \return the plugin or empty one if not found
+     */
+    IRCCD_EXPORT std::shared_ptr<Plugin> get(const std::string &name) const noexcept;
+
+    /**
+     * Find a loaded plugin.
+     *
+     * \param name the plugin id
+     * \return the plugin
+     * \throws std::out_of_range if not found
+     */
+    IRCCD_EXPORT std::shared_ptr<Plugin> require(const std::string &name) const;
+
+    /**
+     * Add the specified plugin to the registry.
+     *
+     * \pre plugin != nullptr
+     * \param plugin the plugin
+     * \note the plugin is only added to the list, no action is performed on it
+     */
+    IRCCD_EXPORT void add(std::shared_ptr<Plugin> plugin);
+
+    /**
+     * Add a loader.
+     *
+     * \param loader the loader
+     */
+    IRCCD_EXPORT void addLoader(std::unique_ptr<PluginLoader> loader);
+
+    /**
+     * Configure a plugin.
+     *
+     * If the plugin is already loaded, its configuration is updated.
+     *
+     * \param name the plugin name
+     * \param config the new configuration
+     */
+    IRCCD_EXPORT void setConfig(const std::string &name, PluginConfig config);
+
+    /**
+     * Get a configuration for a plugin.
+     *
+     * \param name the plugin name
+     * \return the configuration or default one if not found
+     */
+    IRCCD_EXPORT PluginConfig config(const std::string &name) const;
+
+    /**
+     * Add formatting for a plugin.
+     *
+     * \param name the plugin name
+     * \param formats the formats
+     */
+    IRCCD_EXPORT void setFormats(const std::string &name, PluginFormats formats);
+
+    /**
+     * Get formats for a plugin.
+     *
+     * \param name the plugin name
+     * \return the formats
+     */
+    IRCCD_EXPORT PluginFormats formats(const std::string &name) const;
+
+    /**
+     * 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
+     */
+    IRCCD_EXPORT std::shared_ptr<Plugin> open(const std::string &id,
+                                              const std::string &path);
+
+    /**
+     * Generic function for finding a plugin.
+     *
+     * \param id the plugin id
+     * \return the plugin or nullptr on failures
+     */
+    IRCCD_EXPORT std::shared_ptr<Plugin> find(const std::string &id);
+
+    /**
+     * Convenient wrapper that loads a plugin, call onLoad and add it to the
+     * registry.
+     *
+     * Any errors are printed using logger.
+     *
+     * \param name the name
+     * \param path the optional path (searched if empty)
+     */
+    IRCCD_EXPORT void load(std::string name, std::string path = "");
+
+    /**
+     * Unload a plugin and remove it.
+     *
+     * \param name the plugin id
+     */
+    IRCCD_EXPORT void unload(const std::string &name);
+
+    /**
+     * Reload a plugin by calling onReload.
+     *
+     * \param name the plugin name
+     * \throw std::exception on failures
+     */
+    IRCCD_EXPORT void reload(const std::string &name);
+};
+
+/*
+ * RuleService.
+ * ------------------------------------------------------------------
+ */
+
+/**
+ * \brief Store and solve rules.
+ * \ingroup services
+ */
+class RuleService {
+private:
+    std::vector<Rule> m_rules;
+
+public:
+    /**
+     * Get the list of rules.
+     *
+     * \return the list of rules
+     */
+    inline const std::vector<Rule> &list() const noexcept
+    {
+        return m_rules;
+    }
+
+    /**
+     * Get the number of rules.
+     *
+     * \return the number of rules
+     */
+    inline std::size_t length() const noexcept
+    {
+        return m_rules.size();
+    }
+
+    /**
+     * Append a rule.
+     *
+     * \param rule the rule to append
+     */
+    IRCCD_EXPORT void add(Rule rule);
+
+    /**
+     * Insert a new rule at the specified position.
+     *
+     * \param rule the rule
+     * \param position the position
+     */
+    IRCCD_EXPORT void insert(Rule rule, unsigned position);
+
+    /**
+     * Remove a new rule from the specified position.
+     *
+     * \pre position must be valid
+     * \param position the position
+     */
+    IRCCD_EXPORT void remove(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
+     */
+    IRCCD_EXPORT bool solve(const std::string &server,
+                            const std::string &channel,
+                            const std::string &origin,
+                            const std::string &plugin,
+                            const std::string &event) noexcept;
+};
+
+/*
+ * ServerService.
+ * ------------------------------------------------------------------
+ */
+
+/**
+ * \brief Manage IRC servers.
+ * \ingroup services
+ */
+class ServerService {
+private:
+    Irccd &m_irccd;
+    std::vector<std::shared_ptr<Server>> m_servers;
+
+    void handleChannelMode(const ChannelModeEvent &);
+    void handleChannelNotice(const ChannelNoticeEvent &);
+    void handleConnect(const ConnectEvent &);
+    void handleInvite(const InviteEvent &);
+    void handleJoin(const JoinEvent &);
+    void handleKick(const KickEvent &);
+    void handleMessage(const MessageEvent &);
+    void handleMe(const MeEvent &);
+    void handleMode(const ModeEvent &);
+    void handleNames(const NamesEvent &);
+    void handleNick(const NickEvent &);
+    void handleNotice(const NoticeEvent &);
+    void handlePart(const PartEvent &);
+    void handleQuery(const QueryEvent &);
+    void handleTopic(const TopicEvent &);
+    void handleWhois(const WhoisEvent &);
+
+public:
+    /**
+     * Create the server service.
+     */
+    IRCCD_EXPORT ServerService(Irccd &instance);
+
+    /**
+     * \copydoc Service::prepare
+     */
+    IRCCD_EXPORT void prepare(fd_set &in, fd_set &out, net::Handle &max);
+
+    /**
+     * \copydoc Service::sync
+     */
+    IRCCD_EXPORT void sync(fd_set &in, fd_set &out);
+
+    /**
+     * Get the list of servers
+     *
+     * \return the servers
+     */
+    inline const std::vector<std::shared_ptr<Server>> &servers() const noexcept
+    {
+        return m_servers;
+    }
+
+    /**
+     * Check if a server exists.
+     *
+     * \param name the name
+     * \return true if exists
+     */
+    IRCCD_EXPORT bool has(const std::string &name) const noexcept;
+
+    /**
+     * Add a new server to the application.
+     *
+     * \pre hasServer must return false
+     * \param sv the server
+     */
+    IRCCD_EXPORT 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
+     */
+    IRCCD_EXPORT std::shared_ptr<Server> get(const std::string &name) const noexcept;
+
+    /**
+     * Find a server by name.
+     *
+     * \param name the server name
+     * \return the server
+     * \throw std::out_of_range if the server does not exist
+     */
+    IRCCD_EXPORT std::shared_ptr<Server> require(const std::string &name) const;
+
+    /**
+     * Remove a server from the irccd instance.
+     *
+     * The server if any, will be disconnected.
+     *
+     * \param name the server name
+     */
+    IRCCD_EXPORT void remove(const std::string &name);
+
+    /**
+     * Remove all servers.
+     *
+     * All servers will be disconnected.
+     */
+    IRCCD_EXPORT void clear() noexcept;
+};
+
+/*
+ * TransportService.
+ * ------------------------------------------------------------------
+ */
+
+class TransportServer;
+class TransportClient;
+
+/**
+ * \brief manage transport servers and clients.
+ * \ingroup services
+ */
+class TransportService {
+private:
+    Irccd &m_irccd;
+
+    std::vector<std::shared_ptr<TransportServer>> m_servers;
+    std::vector<std::shared_ptr<TransportClient>> m_clients;
+
+    void handleCommand(std::weak_ptr<TransportClient>, const nlohmann::json &);
+    void handleDie(std::weak_ptr<TransportClient>);
+
+public:
+    /**
+     * Create the transport service.
+     *
+     * \param irccd the irccd instance
+     */
+    IRCCD_EXPORT TransportService(Irccd &irccd) noexcept;
+
+    /**
+     * \copydoc Service::prepare
+     */
+    IRCCD_EXPORT void prepare(fd_set &in, fd_set &out, net::Handle &max);
+
+    /**
+     * \copydoc Service::sync
+     */
+    IRCCD_EXPORT void sync(fd_set &in, fd_set &out);
+
+    /**
+     * Add a transport server.
+     *
+     * \param ts the transport server
+     */
+    IRCCD_EXPORT void add(std::shared_ptr<TransportServer> ts);
+
+    /**
+     * Send data to all clients.
+     *
+     * \pre object.is_object()
+     * \param object the json object
+     */
+    IRCCD_EXPORT void broadcast(const nlohmann::json &object);
+};
+
+} // !irccd
+
+#endif // !IRCCD_SERVICE_HPP
--- a/tests/cmd-plugin-config/main.cpp	Sun Nov 13 10:00:20 2016 +0100
+++ b/tests/cmd-plugin-config/main.cpp	Sun Nov 13 10:41:28 2016 +0100
@@ -19,7 +19,7 @@
 #include <command.hpp>
 #include <command-tester.hpp>
 #include <server-tester.hpp>
-#include <service-plugin.hpp>
+#include <service.hpp>
 
 using namespace irccd;
 using namespace irccd::command;
--- a/tests/cmd-plugin-info/main.cpp	Sun Nov 13 10:00:20 2016 +0100
+++ b/tests/cmd-plugin-info/main.cpp	Sun Nov 13 10:41:28 2016 +0100
@@ -19,7 +19,7 @@
 #include <command.hpp>
 #include <command-tester.hpp>
 #include <server-tester.hpp>
-#include <service-plugin.hpp>
+#include <service.hpp>
 #include <plugin.hpp>
 
 using namespace irccd;
--- a/tests/cmd-plugin-list/main.cpp	Sun Nov 13 10:00:20 2016 +0100
+++ b/tests/cmd-plugin-list/main.cpp	Sun Nov 13 10:41:28 2016 +0100
@@ -19,7 +19,7 @@
 #include <command.hpp>
 #include <command-tester.hpp>
 #include <server-tester.hpp>
-#include <service-plugin.hpp>
+#include <service.hpp>
 #include <plugin.hpp>
 
 using namespace irccd;
--- a/tests/cmd-plugin-load/main.cpp	Sun Nov 13 10:00:20 2016 +0100
+++ b/tests/cmd-plugin-load/main.cpp	Sun Nov 13 10:41:28 2016 +0100
@@ -19,7 +19,7 @@
 #include <command.hpp>
 #include <command-tester.hpp>
 #include <server-tester.hpp>
-#include <service-plugin.hpp>
+#include <service.hpp>
 #include <plugin.hpp>
 
 using namespace irccd;
--- a/tests/cmd-plugin-reload/main.cpp	Sun Nov 13 10:00:20 2016 +0100
+++ b/tests/cmd-plugin-reload/main.cpp	Sun Nov 13 10:41:28 2016 +0100
@@ -19,7 +19,7 @@
 #include <command.hpp>
 #include <command-tester.hpp>
 #include <server-tester.hpp>
-#include <service-plugin.hpp>
+#include <service.hpp>
 #include <plugin.hpp>
 
 using namespace irccd;
--- a/tests/cmd-plugin-unload/main.cpp	Sun Nov 13 10:00:20 2016 +0100
+++ b/tests/cmd-plugin-unload/main.cpp	Sun Nov 13 10:41:28 2016 +0100
@@ -19,7 +19,7 @@
 #include <command.hpp>
 #include <command-tester.hpp>
 #include <server-tester.hpp>
-#include <service-plugin.hpp>
+#include <service.hpp>
 #include <plugin.hpp>
 
 using namespace irccd;
--- a/tests/cmd-server-connect/main.cpp	Sun Nov 13 10:00:20 2016 +0100
+++ b/tests/cmd-server-connect/main.cpp	Sun Nov 13 10:41:28 2016 +0100
@@ -19,7 +19,7 @@
 #include <command.hpp>
 #include <command-tester.hpp>
 #include <server-tester.hpp>
-#include <service-server.hpp>
+#include <service.hpp>
 
 using namespace irccd;
 using namespace irccd::command;
--- a/tests/cmd-server-disconnect/main.cpp	Sun Nov 13 10:00:20 2016 +0100
+++ b/tests/cmd-server-disconnect/main.cpp	Sun Nov 13 10:41:28 2016 +0100
@@ -19,7 +19,7 @@
 #include <command.hpp>
 #include <command-tester.hpp>
 #include <server-tester.hpp>
-#include <service-server.hpp>
+#include <service.hpp>
 #include <server.hpp>
 
 using namespace irccd;
--- a/tests/cmd-server-info/main.cpp	Sun Nov 13 10:00:20 2016 +0100
+++ b/tests/cmd-server-info/main.cpp	Sun Nov 13 10:41:28 2016 +0100
@@ -19,7 +19,7 @@
 #include <command.hpp>
 #include <command-tester.hpp>
 #include <server-tester.hpp>
-#include <service-server.hpp>
+#include <service.hpp>
 
 using namespace irccd;
 using namespace irccd::command;
--- a/tests/cmd-server-list/main.cpp	Sun Nov 13 10:00:20 2016 +0100
+++ b/tests/cmd-server-list/main.cpp	Sun Nov 13 10:41:28 2016 +0100
@@ -19,7 +19,7 @@
 #include <command.hpp>
 #include <command-tester.hpp>
 #include <server-tester.hpp>
-#include <service-server.hpp>
+#include <service.hpp>
 
 using namespace irccd;
 using namespace irccd::command;
--- a/tests/cmd-server-reconnect/main.cpp	Sun Nov 13 10:00:20 2016 +0100
+++ b/tests/cmd-server-reconnect/main.cpp	Sun Nov 13 10:41:28 2016 +0100
@@ -19,7 +19,7 @@
 #include <command.hpp>
 #include <command-tester.hpp>
 #include <server-tester.hpp>
-#include <service-server.hpp>
+#include <service.hpp>
 
 using namespace irccd;
 using namespace irccd::command;