changeset 253:11045c180db9

Irccd: load/open plugins using abstract loaders
author David Demelier <markand@malikania.fr>
date Sun, 28 Aug 2016 14:12:32 +0200
parents e0f58cfbd45a
children 93a227277786
files cmake/IrccdOptions.cmake cmake/internal/sysconfig.hpp.in lib/irccd/path.cpp lib/irccd/path.hpp lib/irccd/plugin-dynlib.cpp lib/irccd/plugin-dynlib.hpp lib/irccd/plugin-js.cpp lib/irccd/plugin-js.hpp lib/irccd/plugin.hpp lib/irccd/service-plugin.cpp lib/irccd/service-plugin.hpp
diffstat 11 files changed, 212 insertions(+), 59 deletions(-) [+]
line wrap: on
line diff
--- a/cmake/IrccdOptions.cmake	Wed Aug 24 22:04:11 2016 +0200
+++ b/cmake/IrccdOptions.cmake	Sun Aug 28 14:12:32 2016 +0200
@@ -39,6 +39,7 @@
 #
 # WITH_BINDIR           Binary directory for irccd, irccdctl
 # WITH_PLUGINDIR        Path where plugins must be installed
+# WITH_NPLUGINDIR       Path where native plugins must be installed
 # WITH_DOCDIR           Path where to install documentation
 # WITH_MANDIR           Path where to install manuals
 # WITH_CONFDIR          Path where to search configuration files
@@ -119,11 +120,13 @@
     set(WITH_DATADIR "share" CACHE STRING "Data directory")
     set(WITH_CACHEDIR "var" CACHE STRING "Temporary files directory")
     set(WITH_PLUGINDIR "share/plugins" CACHE STRING "Module prefix where to install")
+    set(WITH_NPLUGINDIR "lib/irccd/plugins" CACHE STRING "Directory for native plugins")
     set(WITH_DOCDIR "share/doc" CACHE STRING "Documentation directory")
 else ()
     set(WITH_DATADIR "share/irccd" CACHE STRING "Data directory")
     set(WITH_CACHEDIR "var/irccd" CACHE STRING "Temporary files directory")
     set(WITH_PLUGINDIR "share/irccd/plugins" CACHE STRING "Module prefix where to install")
+    set(WITH_NPLUGINDIR "lib/irccd/plugins" CACHE STRING "Directory for native plugins")
     set(WITH_DOCDIR "share/doc/irccd" CACHE STRING "Documentation directory")
 endif ()
 
--- a/cmake/internal/sysconfig.hpp.in	Wed Aug 24 22:04:11 2016 +0200
+++ b/cmake/internal/sysconfig.hpp.in	Sun Aug 28 14:12:32 2016 +0200
@@ -61,6 +61,7 @@
 #define WITH_DATADIR            "@WITH_DATADIR@"
 #define WITH_CONFDIR            "@WITH_CONFDIR@"
 #define WITH_PLUGINDIR          "@WITH_PLUGINDIR@"
+#define WITH_NPLUGINDIR         "@WITH_NPLUGINDIR@"
 #define WITH_CACHEDIR           "@WITH_CACHEDIR@"
 
 #cmakedefine WITH_JS
--- a/lib/irccd/path.cpp	Wed Aug 24 22:04:11 2016 +0200
+++ b/lib/irccd/path.cpp	Sun Aug 28 14:12:32 2016 +0200
@@ -77,7 +77,7 @@
  * supported).
  */
 
-std::string base;
+std::string base{"."};
 
 #if defined(IRCCD_SYSTEM_WINDOWS)
 
@@ -176,30 +176,27 @@
 
 std::string systemConfig()
 {
-    assert(!base.empty());
-
     return base + WITH_CONFDIR;
 }
 
 std::string systemData()
 {
-    assert(!base.empty());
-
     return base + WITH_DATADIR;
 }
 
 std::string systemCache()
 {
-    assert(!base.empty());
-
     return base + WITH_CACHEDIR;
 }
 
 std::string systemPlugins()
 {
-    assert(!base.empty());
+    return base + WITH_PLUGINDIR;
+}
 
-    return base + WITH_PLUGINDIR;
+std::string systemNativePlugins()
+{
+    return base + WITH_NPLUGINDIR;
 }
 
 /*
@@ -372,7 +369,7 @@
         base = executablePath();
     } catch (const std::exception &) {
         /*
-         * If an exception is thrown, that means the operatin system supports a
+         * If an exception is thrown, that means the operating system supports a
          * function to get the executable path but it failed.
          *
          * TODO: show a waning
@@ -447,7 +444,7 @@
 
 std::string get(Path path, Owner owner)
 {
-    assert(path >= PathConfig && path <= PathPlugins);
+    assert(path >= PathConfig && path <= PathNativePlugins);
     assert(owner >= OwnerSystem && owner <= OwnerUser);
 
     std::string result;
@@ -467,6 +464,9 @@
         case PathPlugins:
             result = clean(systemPlugins());
             break;
+        case PathNativePlugins:
+            result = clean(systemNativePlugins());
+            break;
         default:
             break;
         }
@@ -481,6 +481,7 @@
         case PathData:
             result = clean(userData());
             break;
+        case PathNativePlugins:
         case PathPlugins:
             result = clean(userPlugins());
             break;
@@ -496,7 +497,7 @@
 
 std::vector<std::string> list(Path path)
 {
-    assert(path >= PathConfig && path <= PathPlugins);
+    assert(path >= PathConfig && path <= PathNativePlugins);
 
     std::vector<std::string> list;
 
@@ -518,6 +519,9 @@
         list.push_back(clean(userPlugins()));
         list.push_back(clean(systemPlugins()));
         break;
+    case PathNativePlugins:
+        list.push_back(clean(systemPlugins()));
+        break;
     default:
         break;
     }
--- a/lib/irccd/path.hpp	Wed Aug 24 22:04:11 2016 +0200
+++ b/lib/irccd/path.hpp	Sun Aug 28 14:12:32 2016 +0200
@@ -49,7 +49,8 @@
     PathConfig,         //!< Configuration files
     PathData,           //!< Data directory
     PathCache,          //!< Cache files
-    PathPlugins         //!< Path to the plugins
+    PathPlugins,        //!< Path to the plugins
+    PathNativePlugins   //!< Path to native plugins
 };
 
 /**
--- a/lib/irccd/plugin-dynlib.cpp	Wed Aug 24 22:04:11 2016 +0200
+++ b/lib/irccd/plugin-dynlib.cpp	Sun Aug 28 14:12:32 2016 +0200
@@ -16,6 +16,9 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
+#include "fs.hpp"
+#include "logger.hpp"
+#include "path.hpp"
 #include "plugin-dynlib.hpp"
 
 namespace irccd {
@@ -173,4 +176,35 @@
     call(m_onWhois, irccd, ev);
 }
 
+std::shared_ptr<Plugin> DynlibPluginLoader::open(const std::string &id,
+                                                 const std::string &path) noexcept
+{
+    if (path.rfind(DYNLIB_SUFFIX) == std::string::npos)
+        return nullptr;
+
+    try {
+        return std::make_shared<DynlibPlugin>(id, path);
+    } catch (const std::exception &ex) {
+        log::warning() << "plugin " << id << ": " << ex.what() << std::endl;
+    }
+
+    return nullptr;
+}
+
+std::shared_ptr<Plugin> DynlibPluginLoader::find(const std::string &id) noexcept
+{
+    for (const auto &dir : path::list(path::PathNativePlugins)) {
+        auto path = dir + id + DYNLIB_SUFFIX;
+
+        if (!fs::isReadable(path))
+            continue;
+
+        log::info() << "plugin " << id << ": trying " << path << std::endl;
+
+        return open(id, path);
+    }
+
+    return nullptr;
+}
+
 } // !irccd
--- a/lib/irccd/plugin-dynlib.hpp	Wed Aug 24 22:04:11 2016 +0200
+++ b/lib/irccd/plugin-dynlib.hpp	Sun Aug 28 14:12:32 2016 +0200
@@ -200,6 +200,23 @@
     IRCCD_EXPORT void onWhois(Irccd &irccd, const WhoisEvent &event) override;
 };
 
+/**
+ * \brief Implementation for searching native plugins.
+ */
+class DynlibPluginLoader : public PluginLoader {
+public:
+    /**
+     * \copydoc PluginLoader::find
+     */
+    std::shared_ptr<Plugin> open(const std::string &id,
+                                 const std::string &path) noexcept override;
+
+    /**
+     * \copydoc PluginLoader::find
+     */
+    std::shared_ptr<Plugin> find(const std::string &id) noexcept override;
+};
+
 } // !irccd
 
 #endif // !IRCCD_PLUGIN_DYNLIB_HPP
--- a/lib/irccd/plugin-js.cpp	Wed Aug 24 22:04:11 2016 +0200
+++ b/lib/irccd/plugin-js.cpp	Sun Aug 28 14:12:32 2016 +0200
@@ -433,4 +433,35 @@
     call("onWhois", 2);
 }
 
+std::shared_ptr<Plugin> JsPluginLoader::open(const std::string &id,
+                                             const std::string &path) noexcept
+{
+    if (path.rfind(".js") == std::string::npos)
+        return nullptr;
+
+    try {
+        return std::make_shared<JsPlugin>(id, path);
+    } catch (const std::exception &ex) {
+        log::warning() << "plugin " << id << ": " << ex.what() << std::endl;
+    }
+
+    return nullptr;
+}
+
+std::shared_ptr<Plugin> JsPluginLoader::find(const std::string &id) noexcept
+{
+    for (const auto &dir : path::list(path::PathPlugins)) {
+        auto path = dir + id + ".js";
+
+        if (!fs::isReadable(path))
+            continue;
+
+        log::info() << "plugin " << id << ": trying " << path << std::endl;
+
+        return open(id, path);
+    }
+
+    return nullptr;
+}
+
 } // !irccd
--- a/lib/irccd/plugin-js.hpp	Wed Aug 24 22:04:11 2016 +0200
+++ b/lib/irccd/plugin-js.hpp	Sun Aug 28 14:12:32 2016 +0200
@@ -27,7 +27,6 @@
 #include "duktape.hpp"
 #include "path.hpp"
 #include "plugin.hpp"
-#include "signals.hpp"
 
 namespace irccd {
 
@@ -221,6 +220,23 @@
     IRCCD_EXPORT void onWhois(Irccd &irccd, const WhoisEvent &event) override;
 };
 
+/**
+ * \brief Implementation for searching Javascript plugins.
+ */
+class JsPluginLoader : public PluginLoader {
+public:
+    /**
+     * \copydoc PluginLoader::find
+     */
+    std::shared_ptr<Plugin> open(const std::string &id,
+                                 const std::string &path) noexcept override;
+
+    /**
+     * \copydoc PluginLoader::find
+     */
+    std::shared_ptr<Plugin> find(const std::string &id) noexcept override;
+};
+
 } // !irccd
 
 #endif // !IRCCD_PLUGIN_JS_HPP
--- a/lib/irccd/plugin.hpp	Wed Aug 24 22:04:11 2016 +0200
+++ b/lib/irccd/plugin.hpp	Sun Aug 28 14:12:32 2016 +0200
@@ -461,6 +461,40 @@
     }
 };
 
+/**
+ * \brief Abstract interface for searching plugins.
+ *
+ * This class is used to make loading of plugins extensible, the PluginService
+ * knows some predefined plugins loaders and use them to search for available
+ * plugins.
+ *
+ * This makes easier to implement new plugins or new ways of loading them.
+ *
+ * \see DynlibPluginLoader
+ * \see JsPluginLoader
+ */
+class PluginLoader {
+public:
+    /**
+     * Try to open the plugin specified by path.
+     *
+     * The implementation must test if the plugin is suitable for opening, by
+     * testing extension for example.
+     *
+     * \param file the file
+     */
+    virtual std::shared_ptr<Plugin> open(const std::string &id,
+                                         const std::string &file) noexcept = 0;
+
+    /**
+     * Search for a plugin named by this id.
+     *
+     * \param id the plugin id
+     * \return the plugin
+     */
+    virtual std::shared_ptr<Plugin> find(const std::string &id) noexcept = 0;
+};
+
 } // !irccd
 
 #endif // !IRCCD_PLUGIN_HPP
--- a/lib/irccd/service-plugin.cpp	Wed Aug 24 22:04:11 2016 +0200
+++ b/lib/irccd/service-plugin.cpp	Sun Aug 28 14:12:32 2016 +0200
@@ -34,45 +34,11 @@
 
 namespace irccd {
 
-namespace {
-
-std::shared_ptr<Plugin> find(std::string name)
-{
-    for (const auto &path : path::list(path::PathPlugins)) {
-        std::string jspath = path + name + ".js";
-        std::string dynlibpath = path + name + DYNLIB_SUFFIX;
-
-        if (fs::isReadable(jspath))
-            return std::make_shared<JsPlugin>(std::move(name), std::move(jspath));
-        if (fs::isReadable(dynlibpath))
-            return std::make_shared<DynlibPlugin>(std::move(name), std::move(dynlibpath));
-    }
-
-    throw std::runtime_error("no suitable plugin found");
-}
-
-std::shared_ptr<Plugin> open(std::string name, std::string path)
-{
-    std::regex regex(".*(\\..*)$");
-    std::smatch match;
-    std::shared_ptr<Plugin> plugin;
-
-    if (std::regex_match(path, match, regex)) {
-        if (match[1] == DYNLIB_SUFFIX)
-            plugin = std::make_shared<DynlibPlugin>(name, path);
-        else
-            plugin = std::make_shared<JsPlugin>(name, path);
-    } else
-        throw std::runtime_error("could not deduce plugin type from {}"_format(path));
-
-    return plugin;
-}
-
-} // !namespace
-
 PluginService::PluginService(Irccd &irccd) noexcept
     : m_irccd(irccd)
 {
+    m_loaders.push_back(std::make_unique<DynlibPluginLoader>());
+    m_loaders.push_back(std::make_unique<JsPluginLoader>());
 }
 
 PluginService::~PluginService()
@@ -145,13 +111,34 @@
     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)
 {
-    auto it = std::find_if(m_plugins.begin(), m_plugins.end(), [&] (const auto &plugin) {
-        return plugin->name() == name;
-    });
-
-    if (it != m_plugins.end())
+    if (has(name))
         return;
 
     try {
@@ -162,10 +149,13 @@
         else
             plugin = open(name, std::move(path));
 
-        plugin->setConfig(m_config[name]);
-        plugin->setFormats(m_formats[name]);
-        plugin->onLoad(m_irccd);
-        add(std::move(plugin));
+        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()));
     }
--- a/lib/irccd/service-plugin.hpp	Wed Aug 24 22:04:11 2016 +0200
+++ b/lib/irccd/service-plugin.hpp	Sun Aug 28 14:12:32 2016 +0200
@@ -42,6 +42,7 @@
 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;
 
@@ -137,6 +138,27 @@
     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.
      *