diff mcabber/mcabber/modules.c @ 1735:5093b5ca1572

New modules loading scheme
author Myhailo Danylenko <isbear@ukrpost.net>
date Thu, 04 Mar 2010 13:03:20 +0200
parents
children 15e1f3957786
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mcabber/mcabber/modules.c	Thu Mar 04 13:03:20 2010 +0200
@@ -0,0 +1,352 @@
+/*
+ * modules.c   -- modules handling
+ *
+ * Copyright (C) 2010 Myhailo Danylenko <isbear@ukrpost.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#include <glib.h>
+#include <gmodule.h>
+#include <string.h>
+
+#include "settings.h"
+#include "config.h"
+#include "modules.h"
+#include "logprint.h"
+#include "utils.h"
+
+// Information about loaded module
+typedef struct {
+  guint          refcount;
+  gboolean       locked;
+  gchar         *name;
+  GModule       *module;
+  GSList        *dependencies;
+  module_info_t *info;
+} loaded_module_t;
+
+// Registry of loaded modules
+// FIXME This should be a hash table
+// but this needs long thinking and will not affect external interfaces
+static GSList *loaded_modules = NULL;
+
+static gint module_list_comparator(gconstpointer arg1, gconstpointer arg2)
+{
+  const loaded_module_t *module = arg1;
+  const char *name = arg2;
+  return g_strcmp0(module->name, name);
+}
+
+//  module_load(modulename, manual, force)
+// Tries to load specified module and any modules, that this module
+// depends on. Returns NULL on success or constant error string in a
+// case of error. Error message not necessarily indicates error.
+const gchar *module_load(const gchar *arg, gboolean manual, gboolean force)
+{
+  GModule       *mod;
+  module_info_t *info;
+  GSList        *deps = NULL;
+
+  if (!arg || !*arg)
+    return "Missing module name";
+
+  { // Check if module is already loaded
+    GSList *lmod = g_slist_find_custom(loaded_modules, arg, module_list_comparator);
+
+    if (lmod) {
+      loaded_module_t *module = lmod->data;
+
+      if (manual) {
+        if (!module->locked) {
+          module->locked = TRUE;
+          module->refcount += 1;
+          return force ? NULL : "Module is already automatically loaded, marked as manually loaded";
+        } else
+          return force ? NULL : "Module is already loaded";
+      } else {
+        module->refcount += 1;
+        return NULL;
+      }
+    }
+  }
+
+  { // Load module
+    gchar *mdir = expand_filename(settings_opt_get("modules_dir"));
+    gchar *path = g_module_build_path(mdir ? mdir : PKGLIB_DIR, arg);
+    g_free(mdir);
+    mod = g_module_open(path, G_MODULE_BIND_LAZY);
+    g_free(path);
+    if (!mod)
+      return g_module_error();
+  }
+
+  { // Obtain module information structure
+    gchar *varname = g_strdup_printf("info_%s", arg);
+    gpointer var = NULL;
+
+    // convert to a valid symbol name
+    g_strcanon(varname, "abcdefghijklmnopqrstuvwxyz0123456789", '_');
+
+    if (!g_module_symbol(mod, varname, &var)) {
+      if (!force) {
+        g_free(varname);
+        return "Module provides no information structure";
+      }
+
+      scr_LogPrint(LPRINT_LOGNORM, "Forced to ignore error: Module provides no information structure.");
+    }
+
+    g_free(varname);
+    info = var;
+  }
+
+  // Version check
+  if (info && info->mcabber_version && *(info->mcabber_version)
+      && (strcmp(info->mcabber_version, PACKAGE_VERSION) > 0)) {
+    if (!force) {
+      g_module_close(mod);
+      return "Module requires newer version of mcabber";
+    }
+
+    scr_LogPrint(LPRINT_LOGNORM, "Forced to ignore error: Module requires newer version of mcabber.");
+  }
+
+  // Load dependencies
+  if (info && info->requires) {
+    const gchar **dep;
+
+    for (dep = info->requires; *dep; ++dep) {
+      const gchar *err = module_load(*dep, FALSE, FALSE);
+
+      if (err) {
+        GSList *mel;
+        scr_LogPrint(LPRINT_LOGNORM, "Error loading dependency module %s: %s.", *dep, err);
+
+        // Unload already loaded dependencies
+        for (mel = deps; mel; mel = mel->next) {
+          gchar *ldmname = mel->data;
+          err = module_unload(ldmname, FALSE, FALSE);
+          scr_LogPrint(LPRINT_LOGNORM, "Error unloading dependency module %s: %s.", ldmname, err);
+          g_free(ldmname);
+        }
+        g_slist_free(deps);
+
+        // Unload module
+        if (!g_module_close(mod))
+          scr_LogPrint(LPRINT_LOGNORM, "Error unloading module %s: %s.", arg, g_module_error());
+        return "Dependency problems";
+      }
+
+      deps = g_slist_append(deps, g_strdup(*dep));
+    }
+  }
+
+  { // Register module
+    loaded_module_t *module = g_new(loaded_module_t, 1);
+
+    module->refcount     = 1;
+    module->locked       = manual;
+    module->name         = g_strdup(arg);
+    module->module       = mod;
+    module->info         = info;
+    module->dependencies = deps;
+
+    loaded_modules = g_slist_prepend(loaded_modules, module);
+  }
+
+  // Run initialization routine
+  if (info && info->init)
+    info->init();
+
+  // XXX Run hk_loaded_module hook (and move this line there)
+  scr_LogPrint(LPRINT_LOGNORM, "Loaded module %s.", arg);
+
+  return NULL;
+}
+
+//  module_unload(modulename, manual, force)
+// Unload specified module and any automatically loaded modules
+// that are no more required.
+const gchar *module_unload(const gchar *arg, gboolean manual, gboolean force)
+{
+  GSList          *lmod;
+  loaded_module_t *module;
+  module_info_t   *info;
+
+  if (!arg || !*arg)
+    return "Missing module name";
+
+  lmod = g_slist_find_custom(loaded_modules, arg, module_list_comparator);
+  if (!lmod)
+    return "Module not found";
+
+  module = lmod->data;
+
+  // Check if user can unload this module
+  if (manual) {
+    if (!module->locked) {
+      if (force)
+        scr_LogPrint(LPRINT_LOGNORM, "Forced to ignore error: Manually unloading automatically loaded module.");
+      else
+        return "Module is not loaded manually";
+    }
+    module->locked = FALSE;
+  }
+
+  // Check refcount
+  module->refcount -= 1;
+  if (module->refcount > 0) {
+    if (force)
+      scr_LogPrint(LPRINT_LOGNORM, "Forced to ignore error: Refcount is not zero (%u).", module->refcount);
+    else
+      return manual ? "Module is required by some other modules" : NULL;
+  }
+
+  info = module->info;
+
+  // Run uninitialization routine
+  if (info && info->uninit)
+    info->uninit();
+  // XXX Prevent uninitialization routine to be called again
+  module->info = NULL;
+
+  // Unload module
+  if (!g_module_close(module->module))
+    return g_module_error(); // XXX destroy structure?
+
+  { // Unload dependencies
+    GSList *dep;
+    for (dep = module->dependencies; dep; dep = dep->next) {
+      gchar *ldmname = dep->data;
+      const gchar *err = module_unload(ldmname, FALSE, FALSE);
+      if (err) // XXX
+        scr_LogPrint(LPRINT_LOGNORM, "Error unloading automatically loaded module %s: %s.", ldmname, err);
+      g_free(ldmname);
+    }
+    g_slist_free(module->dependencies);
+    module->dependencies = NULL;
+  }
+
+  // Destroy structure
+  loaded_modules = g_slist_delete_link(loaded_modules, lmod);
+  g_free(module->name);
+  g_free(module);
+
+  scr_LogPrint(LPRINT_LOGNORM, "Unloaded module %s.", arg);
+
+  return NULL;
+}
+
+//  module_list_print(void)
+// Prints into status buffer and log list of the currently loaded
+// modules.
+void module_list_print(void)
+{
+  GSList *mel;
+  gsize maxlen = 0;
+  gchar *format;
+  GString *message;
+
+  if (!loaded_modules) {
+    scr_LogPrint(LPRINT_LOGNORM, "No modules loaded.");
+    return;
+  }
+
+  // Counnt maximum module name length
+  for (mel = loaded_modules; mel; mel = mel -> next) {
+    loaded_module_t *module = mel->data;
+    gsize len = strlen(module->name);
+    if (len > maxlen)
+      maxlen = len;
+  }
+
+  // Create format string
+  format = g_strdup_printf("%%-%us  %%2u (%%c)", maxlen);
+
+  // Fill the message to be printed
+  message = g_string_new("Loaded modules:\n");
+  for (mel = loaded_modules; mel; mel = mel -> next) {
+    loaded_module_t *module = mel->data;
+    GSList *dep;
+
+    g_string_append_printf(message, format, module->name, module->refcount, module->locked ? 'M' : 'A');
+
+    // Append loaded module dependencies
+    if (module->dependencies) {
+      g_string_append(message, " depends: ");
+
+      for (dep = module->dependencies; dep; dep = dep->next) {
+        const gchar *name = dep->data;
+        g_string_append(message, name);
+        g_string_append(message, ", ");
+      }
+
+      // Chop extra ", "
+      g_string_truncate(message, message->len - 2);
+    }
+
+    g_string_append_c(message, '\n');
+  }
+
+  // Chop extra "\n"
+  g_string_truncate(message, message->len - 1);
+
+  scr_LogPrint(LPRINT_LOGNORM, "%s", message->str);
+
+  g_string_free(message, TRUE);
+  g_free(format);
+}
+
+//  modules_init()
+// Initializes module system.
+void modules_init(void)
+{
+}
+
+//  modules_deinit()
+// Unloads all the modules.
+void modules_deinit(void)
+{
+  GSList *mel;
+
+  // We need only manually loaded modules
+  for (mel = loaded_modules; mel; mel = mel->next) {
+    loaded_module_t *module = mel->data;
+    if (module->locked)
+      break;
+  }
+
+  while (mel) {
+    loaded_module_t *module = mel->data;
+    const gchar     *err;
+
+    // Find next manually loaded module to treat
+    for (mel = mel->next; mel; mel = mel->next) {
+      loaded_module_t *module = mel->data;
+      if (module->locked)
+        break;
+    }
+
+    // Unload module
+    scr_LogPrint(LPRINT_LOGNORM, "Unloading module %s.", module->name);
+    err = module_unload(module->name, TRUE, FALSE);
+    if (err)
+      scr_LogPrint(LPRINT_LOGNORM, "* Module unloading failed: %s.", err);
+  }
+}
+
+/* vim: set expandtab cindent cinoptions=>2\:2(0 ts=2 sw=2:  For Vim users... */