]> Dogcows Code - chaz/homebank/blobdiff - src/ext.c
add plugin engine (supports C and Perl plugins)
[chaz/homebank] / src / ext.c
diff --git a/src/ext.c b/src/ext.c
new file mode 100644 (file)
index 0000000..b366491
--- /dev/null
+++ b/src/ext.c
@@ -0,0 +1,327 @@
+
+#include <stdarg.h>
+#include <string.h>
+#include <gmodule.h>
+#include <gtk/gtk.h>
+
+#include "ext.h"
+
+extern struct Preferences *PREFS;
+
+
+const int _hook_recursion_soft_limit = 50;
+const int _hook_recursion_hard_limit = 99;
+
+
+struct PluginEngine
+{
+       const gchar*            type;
+       PluginEngineInitializer init;
+       PluginEngineTerminator  term;
+       PluginEngineFileChecker check_file;
+       PluginMetadataReader    read_metadata;
+       PluginLoader            load_plugin;
+       PluginUnloader          unload_plugin;
+       PluginExecutor          execute;
+       PluginHookCaller        call_hook;
+};
+
+
+static GList* _engine_list = NULL;
+static GHashTable* _loaded_plugins = NULL;
+
+
+void ext_init(int* argc, char** argv[], char** env[])
+{
+       GList *list = g_list_first(_engine_list);
+       while (list)
+       {
+               struct PluginEngine* engine = list->data;
+               engine->init(argc, argv, env);
+               list = g_list_next(list);
+       }
+       if (!_loaded_plugins) {
+               _loaded_plugins = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
+       }
+}
+
+void ext_term(void)
+{
+       GList *list = g_list_first(_engine_list);
+       while (list)
+       {
+               struct PluginEngine* engine = list->data;
+               engine->term();
+               list = g_list_next(list);
+       }
+       g_list_free(_engine_list);
+       _engine_list = NULL;
+
+       if (_loaded_plugins) {
+               g_hash_table_unref(_loaded_plugins);
+               _loaded_plugins = NULL;
+       }
+}
+
+void ext_register(const gchar* type,
+               PluginEngineInitializer init,
+               PluginEngineTerminator term,
+               PluginEngineFileChecker check_file,
+               PluginMetadataReader read_metadata,
+               PluginLoader load_plugin,
+               PluginUnloader unload_plugin,
+               PluginExecutor execute,
+               PluginHookCaller call_hook)
+{
+       struct PluginEngine* engine = g_malloc0(sizeof(struct PluginEngine));
+       engine->type = type;
+       engine->init = init;
+       engine->term = term;
+       engine->check_file = check_file;
+       engine->read_metadata = read_metadata;
+       engine->load_plugin = load_plugin;
+       engine->unload_plugin = unload_plugin;
+       engine->execute = execute;
+       engine->call_hook = call_hook;
+    _engine_list = g_list_append(_engine_list, engine);
+}
+
+
+static struct PluginEngine* _get_engine_for_plugin(const gchar* plugin_filename)
+{
+       if (!plugin_filename) {
+               return NULL;
+       }
+
+       GList *list = g_list_first(_engine_list);
+       while (list) {
+               struct PluginEngine* engine = list->data;
+               if (engine->check_file(plugin_filename)) {
+                       return engine;
+               }
+               list = g_list_next(list);
+       }
+       return NULL;
+}
+
+static void _read_directory(const gchar* directory, GHashTable* hash)
+{
+       GDir* dir = g_dir_open(directory, 0, NULL);
+       if (!dir) return;
+
+       const gchar* filename;
+       while ((filename = g_dir_read_name(dir))) {
+               gchar* full = g_build_filename(directory, filename, NULL);
+               if (g_file_test(full, G_FILE_TEST_IS_REGULAR) && _get_engine_for_plugin(filename)) {
+                       g_hash_table_insert(hash, g_strdup(filename), NULL);
+               }
+               g_free(full);
+       }
+       g_dir_close(dir);
+}
+
+gchar** ext_list_plugins()
+{
+       GHashTable* hash = g_hash_table_new(g_str_hash, g_str_equal);
+
+       gchar** it;
+       for (it = PREFS->ext_path; it && *it; ++it) {
+               _read_directory(*it, hash);
+       }
+
+       GList* list = g_list_sort(g_hash_table_get_keys(hash), (GCompareFunc)g_utf8_collate);
+       g_hash_table_unref(hash);
+
+       guint len = g_list_length(list);
+       gchar** strv = g_new0(gchar**, len + 1);
+       int i;
+       for (i = 0; i < len; ++i) {
+               strv[i] = g_list_nth_data(list, i);
+       }
+       g_list_free(list);
+
+       return strv;
+}
+
+gchar* ext_find_plugin(const gchar* plugin_filename)
+{
+       if (!plugin_filename) return NULL;
+
+       gchar** it;
+       for (it = PREFS->ext_path; *it; ++it) {
+               if (!g_path_is_absolute(*it)) continue;
+
+               gchar* full = g_build_filename(*it, plugin_filename, NULL);
+               if (g_file_test(full, G_FILE_TEST_IS_REGULAR)) {
+                       return full;
+               }
+               g_free(full);
+       }
+
+       return NULL;
+}
+
+GHashTable* ext_read_plugin_metadata(const gchar* plugin_filename)
+{
+       gchar* full = ext_find_plugin(plugin_filename);
+       if (!full) return NULL;
+
+       GHashTable* ret = NULL;
+
+       struct PluginEngine* engine = _get_engine_for_plugin(plugin_filename);
+       if (engine && engine->read_metadata) {
+               ret = engine->read_metadata(full);
+       }
+
+       g_free(full);
+       return ret;
+}
+
+gint ext_load_plugin(const gchar* plugin_filename)
+{
+       gchar* full = ext_find_plugin(plugin_filename);
+       if (!full) return -1;
+
+       gint ret = -1;
+
+       struct PluginEngine* engine = _get_engine_for_plugin(plugin_filename);
+       if (engine && engine->load_plugin && engine->load_plugin(full) == 0) {
+               g_hash_table_insert(_loaded_plugins, g_strdup(plugin_filename), NULL);
+               ret = 0;
+       }
+
+       g_free(full);
+       return ret;
+}
+
+void ext_unload_plugin(const gchar* plugin_filename)
+{
+       gchar* full = ext_find_plugin(plugin_filename);
+       if (!full) return;
+
+       struct PluginEngine* engine = _get_engine_for_plugin(plugin_filename);
+       if (engine && engine->unload_plugin) {
+               engine->unload_plugin(full);
+       }
+
+       g_free(full);
+       g_hash_table_remove(_loaded_plugins, plugin_filename);
+}
+
+gboolean ext_is_plugin_loaded(const gchar* plugin_filename)
+{
+       return g_hash_table_contains(_loaded_plugins, plugin_filename);
+}
+
+void ext_execute_action(const gchar* plugin_filename)
+{
+       gchar* full = ext_find_plugin(plugin_filename);
+       if (!full) return;
+
+       struct PluginEngine* engine = _get_engine_for_plugin(plugin_filename);
+       if (engine && engine->execute) {
+               engine->execute(full);
+       }
+
+       g_free(full);
+}
+
+void ext_hook(const gchar* hook_id, ...)
+{
+       GList *list = NULL;
+
+       va_list ap;
+       va_start(ap, hook_id);
+       for (;;) {
+               GValue* val = (GValue*)va_arg(ap, GValue*);
+               if (!val) break;
+               list = g_list_append(list, val);
+       }
+       va_end(ap);
+
+       ext_vhook(hook_id, list);
+       g_list_free(list);
+}
+
+void ext_vhook(const gchar* hook_id, GList* args)
+{
+       static int recursion_level = 0;
+
+       if (_hook_recursion_hard_limit <= recursion_level) {
+               return;
+       } else if (_hook_recursion_soft_limit <= recursion_level) {
+               int level = recursion_level;
+               recursion_level = -1;
+               GValue val_level = G_VALUE_INIT;
+               ext_hook("deep_hook_recursion", EXT_INT(&val_level, level), NULL);
+               recursion_level = level;
+       }
+
+       ++recursion_level;
+
+       g_print("ext_hook: %s (level %d)\n", hook_id, recursion_level);
+       GList *list = g_list_first(_engine_list);
+       while (list)
+       {
+               struct PluginEngine* engine = list->data;
+               engine->call_hook(hook_id, args);
+               list = g_list_next(list);
+       }
+
+       --recursion_level;
+}
+
+gboolean ext_has(const gchar* feature)
+{
+#ifdef OFX_ENABLE
+       if (0 == g_utf8_collate(feature, "libofx")) {
+               return TRUE;
+       }
+#endif
+#ifdef PERL_ENABLE
+       if (0 == g_utf8_collate(feature, "perl")) {
+               return TRUE;
+       }
+#endif
+       return FALSE;
+}
+
+
+void* ext_symbol_lookup(const gchar* symbol)
+{
+       static GModule* module = NULL;
+       if (!module) module = g_module_open(NULL, 0);
+
+       void* ptr;
+       if (module && g_module_symbol(module, symbol, &ptr)) {
+               return ptr;
+       }
+
+       return NULL;
+}
+
+
+void ext_run_modal(const gchar* title, const gchar* text, const gchar* type)
+{
+       GtkMessageType t = GTK_MESSAGE_INFO;
+       if (0 == g_utf8_collate(type, "error")) {
+               t = GTK_MESSAGE_ERROR;
+       }
+       if (0 == g_utf8_collate(type, "warn")) {
+               t = GTK_MESSAGE_WARNING;
+       }
+       if (0 == g_utf8_collate(type, "question")) {
+               t = GTK_MESSAGE_QUESTION;
+       }
+
+       GtkWidget* dialog = gtk_message_dialog_new(NULL,
+                       GTK_DIALOG_DESTROY_WITH_PARENT, t,
+                       GTK_BUTTONS_CLOSE, "%s", text);
+       if (title) {
+               gtk_window_set_title(GTK_WINDOW(dialog), title);
+       }
+
+       gtk_dialog_run(GTK_DIALOG(dialog));
+       gtk_widget_destroy(dialog);
+}
+
This page took 0.02427 seconds and 4 git commands to generate.