]> Dogcows Code - chaz/homebank/blobdiff - src/ext-native.c
add plugin engine (supports C and Perl plugins)
[chaz/homebank] / src / ext-native.c
diff --git a/src/ext-native.c b/src/ext-native.c
new file mode 100644 (file)
index 0000000..fce6315
--- /dev/null
@@ -0,0 +1,192 @@
+
+#include <glib/gstdio.h>
+#include <gmodule.h>
+
+#include "ext.h"
+
+
+static gint ext_native_init(int* argc, char** argv[], char** env[]);
+static void ext_native_term(void);
+static gboolean ext_native_check_file(const gchar* plugin_filename);
+static GHashTable* ext_native_read_plugin_metadata(const gchar* plugin_filepath);
+static gint ext_native_load_plugin(const gchar* plugin_filepath);
+static void ext_native_unload_plugin(const gchar* plugin_filepath);
+static void ext_native_execute_action(const gchar* plugin_filepath);
+static void ext_native_call_hook(const gchar* hook_id, GList* args);
+
+static gchar* _read_data_for_keyword(const gchar* keyword, const gchar* bytes, gsize len);
+
+
+static GHashTable* _loaded_plugins = NULL;
+
+
+static gint ext_native_init(int* argc, char** argv[], char** env[])
+{
+       if (!_loaded_plugins) {
+               _loaded_plugins = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)g_module_close);
+       }
+       return 0;
+}
+
+static void ext_native_term(void)
+{
+       if (_loaded_plugins) {
+               ext_native_call_hook("unload", NULL);
+               g_hash_table_unref(_loaded_plugins);
+               _loaded_plugins = NULL;
+       }
+}
+
+static gboolean ext_native_check_file(const gchar* plugin_filename)
+{
+       if (g_str_has_suffix(plugin_filename, "."G_MODULE_SUFFIX)) {
+               return TRUE;
+       }
+       if (g_str_has_suffix(plugin_filename, ".la")) {
+               // allow a .la file only if no actual native plugin is found
+               gboolean check = FALSE;
+               gchar* copy = g_strdup(plugin_filename);
+               gchar* ext  = g_strrstr(copy, ".la");
+               if (ext) {
+                       *ext = '\0';
+                       gchar* native_filename = g_strconcat(copy, "."G_MODULE_SUFFIX, NULL);
+                       gchar* native_filepath = ext_find_plugin(native_filename);
+                       check = !native_filepath;
+                       g_free(native_filepath);
+                       g_free(native_filename);
+               }
+               g_free(copy);
+               return check;
+       }
+       return FALSE;
+}
+
+static GHashTable* ext_native_read_plugin_metadata(const gchar* plugin_filepath)
+{
+       GMappedFile* file = g_mapped_file_new(plugin_filepath, FALSE, NULL);
+       if (!file) {
+               g_printerr("mapping plugin file at %s failed\n", plugin_filepath);
+               return NULL;
+       }
+
+       gchar* bytes = g_mapped_file_get_contents(file);
+       gsize len = g_mapped_file_get_length(file);
+       if (len == 0 || !bytes) {
+               g_mapped_file_unref(file);
+               g_printerr("no data in plugin file at %s failed\n", plugin_filepath);
+               return NULL;
+       }
+
+       GHashTable* table = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
+
+       const gchar* keywords[] = { "name", "version", "abstract", "author", "website", NULL };
+       const gchar** it;
+       for (it = keywords; *it; ++it) {
+               gchar* value = _read_data_for_keyword(*it, bytes, len);
+               g_hash_table_insert(table, g_strdup(*it), value);
+       }
+
+       g_mapped_file_unref(file);
+
+       return table;
+}
+
+static gint ext_native_load_plugin(const gchar* plugin_filepath)
+{
+       if (g_hash_table_contains(_loaded_plugins, plugin_filepath)) {
+               return 0;
+       }
+
+       GModule* module = g_module_open(plugin_filepath, G_MODULE_BIND_LAZY | G_MODULE_BIND_LOCAL);
+       if (!module) {
+               g_printerr("Could not load native plugin: %s\n", g_module_error());
+               return -1;
+       }
+
+       g_hash_table_insert(_loaded_plugins, g_strdup(plugin_filepath), module);
+
+       void (*symbol)();
+       if (g_module_symbol(module, "load", (gpointer)&symbol)) {
+               symbol();
+       }
+
+       return 0;
+}
+
+static void ext_native_unload_plugin(const gchar* plugin_filepath)
+{
+       GModule* module = g_hash_table_lookup(_loaded_plugins, plugin_filepath);
+       if (module) {
+               void (*symbol)();
+               if (g_module_symbol(module, "unload", (gpointer)&symbol)) {
+                       symbol();
+               }
+       }
+
+       g_hash_table_remove(_loaded_plugins, plugin_filepath);
+}
+
+static void ext_native_execute_action(const gchar* plugin_filepath)
+{
+       GModule* module = g_hash_table_lookup(_loaded_plugins, plugin_filepath);
+       if (module) {
+               void (*symbol)();
+               if (g_module_symbol(module, "execute", (gpointer)&symbol)) {
+                       symbol();
+               }
+       }
+}
+
+static void ext_native_call_hook(const gchar* hook_id, GList* args)
+{
+       gchar* symbol_name = g_strconcat("on_", hook_id, NULL);
+       void (*symbol)(GList*);
+
+       GHashTableIter it;
+       g_hash_table_iter_init(&it, _loaded_plugins);
+       GModule* module;
+
+       while (g_hash_table_iter_next(&it, NULL, (gpointer*)&module)) {
+               if (g_module_symbol(module, symbol_name, (gpointer)&symbol)) {
+                       symbol(args);
+               }
+       }
+
+       g_free(symbol_name);
+}
+
+
+static gchar* _read_data_for_keyword(const gchar* keyword, const gchar* bytes, gsize len)
+{
+       gchar* value = NULL;
+
+       gchar* pattern = g_strdup_printf("[\\x00\\t\\n ]%s\\s*[=:]\\s*([^\\x00]+)", keyword);
+       GRegex* r = g_regex_new(pattern, G_REGEX_CASELESS, 0, NULL);
+       g_free(pattern);
+
+       GMatchInfo* match = NULL;
+       if (g_regex_match_full(r, bytes, len, 0, 0, &match, NULL)) {
+               value = g_match_info_fetch(match, 1);
+       }
+
+       g_match_info_free(match);
+       g_regex_unref(r);
+
+       return value;
+}
+
+
+static void _register(void) __attribute__((constructor));
+static void _register()
+{
+       ext_register("native",
+                       ext_native_init,
+                       ext_native_term,
+                       ext_native_check_file,
+                       ext_native_read_plugin_metadata,
+                       ext_native_load_plugin,
+                       ext_native_unload_plugin,
+                       ext_native_execute_action,
+                       ext_native_call_hook);
+}
+
This page took 0.022486 seconds and 4 git commands to generate.