--- a/mcabber/doc/HOWTO_modules.txt Tue Mar 02 13:47:43 2010 +0100
+++ b/mcabber/doc/HOWTO_modules.txt Thu Mar 04 13:03:20 2010 +0200
@@ -5,18 +5,48 @@
===========================================
-Mcabber loads modules via glib's GModule.
-
-Thus, in your module you can provide functions
+To obtain information on module mcabber uses struct
+module_info_t, that module should provide in public
+variable with name info_<modulename>. If module name
+contains any extra symbols except [a-z0-9_] they should
+be replaced with '_'.
--------------------------------------------------------
+ #include <mcabber/modules.h>
+
+ typedef void (*module_init_t)(void);
+ typedef void (*module_uninit_t)(void);
+
+ typedef struct {
+ const gchar *mcabber_version;
+ module_init_t init;
+ module_uninit_t uninit;
+ const gchar **requires;
+ } module_info_t;
+--------------------------------------------------------
+
+Callbacks init and uninit will be called after module
+and it's dependencies loading. 'requires' should contain
+NULL-terminated list of module names, that should be loaded
+before this. 'mcabber_version' is required and should contain
+mcabber version, that this module is designed to work with.
+Three other fields may be NULL.
+
+To load modules, mcabber uses glib's GModule, thus, in your
+module you can also use functions
+
+--------------------------------------------------------
+ #include <glib.h>
+ #include <gmodule.h>
+
const gchar* g_module_check_init (GModule *module);
void g_module_unload (GModule *module);
--------------------------------------------------------
-to do something when module is loaded and unloaded. On
-success g_module_check_init should return NULL, and
-error message otherwise.
+to do something before any version/dependency checks will
+be performed when module is loaded/unloaded. On success
+g_module_check_init should return NULL, and error message
+otherwise.
As module is loaded, you can use mcabber functions,
declared in mcabber's header files (though you should
@@ -28,6 +58,46 @@
especially for module writers.
--------------------------------------------------------
+ #include <mcabber/modules.h>
+
+ const gchar *module_load (const gchar *name,
+ gboolean manual,
+ gboolean force);
+ const gchar *module_unload (const gchar *name,
+ gboolean manual,
+ gboolean force);
+--------------------------------------------------------
+
+These functions load and unload modules respectively.
+You can use them to handle optional dependencies. What
+happens, when module is loaded:
+ - check if module is present, and if present just
+ increase it's reference count
+ - load .so via glib (and call g_module_check_init, if
+ present)
+ - check for information structure presence
+ - check target mcabber version compatibility
+ - load modules, that this module requires (note, that
+ dependency problems will be reported as error
+ invariably, force flag have no effect on this check)
+ - module placed into a list of modules
+ - module init routine is called
+And when unloaded:
+ - check if module is present
+ - decrease reference count, if it is not zero, return
+ - run module uninit routine
+ - unload modules, that were loaded as dependencies for
+ this
+ - remove from modules list
+They return error message or NULL in case of success.
+'manual' flag indicates, that module will be loaded by
+direct user request. It serves the purpose of tracking
+user and automatic references (user can have only one).
+'force' flag on module loading causes mcabber to ignore
+most of the loading errors. On unload it forces
+unloading even if reference count is not zero.
+
+--------------------------------------------------------
#include <mcabber/commands.h>
void cmd_add (const char *name, const char *help,
@@ -200,10 +270,10 @@
Now, compile this file (hello.c) with
libtool --mode=compile gcc `pkg-config --cflags glib-2.0 \
- gmodule-2.0` -c hello.c
+ gmodule-2.0 mcabber` -c hello.c
libtool --mode=link gcc -module -rpath /usr/lib/mcabber/ \
- `pkg-config --libs glib-2.0 gmodule-2.0` -o libhello.la \
- hello.lo
+ `pkg-config --libs glib-2.0 gmodule-2.0 mcabber` \
+ -o libhello.la hello.lo
(you should substitute /usr/lib/mcabber to directory, where
your modules are located) and then install obtained module with
@@ -216,13 +286,68 @@
wide library, but maybe some systems require that.
Now, set modules_dir mcabber variable to point to your modules
-dir, and try to run /load hello. If all goes well, you should
-see in status buffer message "Hello World!".
-Now unload module by running command /unload hello, that
-should bring up message "Bye, World!".
+dir, and try to run /module -f load hello. If all goes well,
+you should see in status buffer message "Hello World!" (as
+well as some complaints, as we forced module loading).
+Now unload module by running command /module unload hello,
+that should bring up message "Bye, World!".
That's it, you just created very simple dynamically loadable
-mcabber module.
+mcabber module. But, as you noticed, it needs force to be
+loaded. Now, let's add information structure, that mcabber
+wants.
+
+==========================
+
+ Example: info struct
+
+==========================
+
+--------------------------------------------------------
+#include <mcabber/logprint.h>
+/* module_info_t definition */
+#include <mcabber/modules.h>
+
+/* Print something on module loading */
+void hello_init (void)
+{
+ scr_LogPrint (LPRINT_NORMAL, "Hello, World!");
+}
+
+/* ... and unloading */
+void hello_uninit (void)
+{
+ scr_LogPrint (LPRINT_NORMAL, "Bye, World!");
+}
+
+module_info_t info_hello = {
+ .mcabber_version = "0.10.0",
+ .requires = NULL,
+ .init = hello_init,
+ .uninit = hello_uninit,
+};
+
+/* The End */
+--------------------------------------------------------
+
+Here we now do not use glib nor gmodule, so, we can omit
+them in compilation lines:
+
+libtool --mode=compile gcc `pkg-config --cflags mcabber` \
+ -c hello.c
+libtool --mode=link gcc -module -rpath /usr/lib/mcabber/ \
+ `pkg-config --libs mcabber` -o libhello.la hello.lo
+
+Again compile it, copy, and try to load, now without -f
+flag. As you may notice, when loading previous example,
+mcabber first printed "Hello, World!", and only then
+complaint about module not having information struct.
+That's because g_module_check_init is called right
+after module loading, before mcabber even have a chance
+to look at module, while .init from info struct is
+called afterwards by mcabber itself. You can try to
+introduce some error (eg too high or missing target
+mcabber version) and see the difference.
=======================
@@ -233,11 +358,9 @@
Now, let's allow our module to do some real work.
--------------------------------------------------------
-#include <glib.h>
-#include <gmodule.h>
-
#include <mcabber/logprint.h>
#include <mcabber/commands.h>
+#include <mcabber/modules.h>
/* Handler for command */
void do_hello (char *args)
@@ -249,18 +372,24 @@
}
/* Register command */
-const gchar* g_module_check_init (GModule *module)
+void hello_init (void)
{
cmd_add ("hello", "", 0, 0, do_hello, NULL);
- return NULL;
}
/* Unregister command */
-void g_module_unload (GModule *module)
+void hello_uninit (void)
{
cmd_del ("hello");
}
+module_info_t hello_info = {
+ .mcabber_version = "0.10.0",
+ .requires = NULL,
+ .init = hello_init,
+ .uninit = hello_uninit,
+}
+
/* The End */
--------------------------------------------------------
@@ -281,11 +410,9 @@
their IDs are listed in compl.h.
--------------------------------------------------------
-#include <glib.h>
-#include <gmodule.h>
-
#include <mcabber/logprint.h>
#include <mcabber/commands.h>
+#include <mcabber/modules.h>
#include <mcabber/compl.h>
static guint hello_cid = 0;
@@ -303,7 +430,7 @@
}
/* Initialization */
-const gchar* g_module_check_init (GModule *module)
+void hello_init (void)
{
/* Obtain handle for our completion
* category */
@@ -315,11 +442,10 @@
"World");
cmd_add ("hello", "", hello_cid, 0, do_hello,
NULL);
- return NULL;
}
/* Deinitialization */
-void g_module_unload (GModule *module)
+void hello_uninit (void)
{
/* Give back category handle */
if (hello_cid)
@@ -327,6 +453,13 @@
cmd_del ("hello");
}
+module_info_t hello_info = {
+ .mcabber_version = "0.10.0",
+ .requires = NULL,
+ .init = hello_init,
+ .uninit = hello_uninit,
+}
+
/* The End */
--------------------------------------------------------
@@ -349,8 +482,6 @@
muc conference message, not just ones, directed to me.
--------------------------------------------------------
-#include <glib.h>
-#include <gmodule.h>
#include <string.h>
#include <mcabber/logprint.h>
@@ -359,6 +490,7 @@
#include <mcabber/hooks.h>
#include <mcabber/screen.h>
#include <mcabber/settings.h>
+#include <mcabber/module.h>
static guint beep_cid = 0;
@@ -400,7 +532,7 @@
}
/* Initialization */
-const gchar* g_module_check_init (GModule *module)
+void beep_init (void)
{
/* Create completions */
beep_cid = compl_new_category ();
@@ -414,11 +546,10 @@
* We are only interested in incoming message events
*/
hk_add_handler (beep_hh, HOOK_MESSAGE_IN, NULL);
- return NULL;
}
/* Deinitialization */
-void g_module_unload (GModule *module)
+void beep_uninit (void)
{
/* Unregister event handler */
hk_del_handler (beep_hh, NULL);
@@ -429,20 +560,16 @@
compl_del_category (beep_cid);
}
+module_info_t beep_info = {
+ .mcabber_version = "0.10.0",
+ .requires = NULL,
+ .init = beep_init,
+ .uninit = beep_uninit,
+}
+
/* The End */
--------------------------------------------------------
-Note, that to compile this we also need to add loudmouth-1.0
-to pkg-config command line, so, you will have something like
-
-libtool --mode=compile gcc `pkg-config --cflags glib-2.0 \
- gmodule-2.0 loudmouth-1.0` -c beep.c
-libtool --mode=link gcc -module -rpath /usr/lib/mcabber/ \
- `pkg-config --cflags glib-2.0 gmodule-2.0` -o libbeep.la \
- beep.lo
-libtool --mode=install install libbeep.la \
- /usr/lib/mcabber/libbeep.la
-
If you use CMake (as do I), corresponding CMakeLists.txt
snippet:
@@ -450,31 +577,86 @@
cmake_minimum_required(VERSION 2.6)
project(beep C)
-set(MCABBER_INCLUDE_DIR "/usr/include" CACHE FILEPATH
- "Path to mcabber headers")
-
find_package(PkgConfig REQUIRED)
-pkg_check_modules(GLIB REQUIRED glib-2.0)
-pkg_check_modules(GMODULE REQUIRED gmodule-2.0)
-pkg_check_modules(LM REQUIRED loudmouth-1.0)
+pkg_check_modules(MCABBER REQUIRED mcabber)
# this one should be before any target definitions
-link_directories(${GLIB_LIBRARY_DIRS}
- ${GMODULE_LIBRARY_DIRS})
+link_directories(${MCABBER_LIBRARY_DIRS})
add_library(beep MODULE beep.c)
-include_directories(SYSTEM ${GLIB_INCLUDE_DIRS}
- ${GMODULE_INCLUDE_DIRS}
- ${LM_INCLUDE_DIRS}
- ${MCABBER_INCLUDE_DIR})
-target_link_libraries(beep ${GLIB_LIBRARIES}
- ${GMODULE_LIBRARIES})
+include_directories(SYSTEM ${MCABBER_INCLUDE_DIRS})
+target_link_libraries(beep ${MCABBER_LIBRARIES)
include_directories(${beep_SOURCE_DIR}
${beep_BINARY_DIR})
install(TARGETS beep DESTINATION lib/mcabber)
--------------------------------------------------------
+===========================
+
+ Example: dependencies
+
+===========================
+
+I will not provide here a complete example of two
+modules, one of which depends on other, only some
+use cases.
+
+Info struct for module, that depends on two other
+modules:
+
+--------------------------------------------------------
+#include <mcabber/modules.h>
+
+const gchar *a_deps[] = { "b", "c", NULL };
+
+module_info_t info_a = {
+ .mcabber_version = "0.10.0",
+ .requires = a_deps,
+ .init = a_init,
+ .uninit = a_uninit,
+};
+--------------------------------------------------------
+
+If your module needs to "authenticate" mcabber version
+too, this can be done in g_module_check_init:
+
+--------------------------------------------------------
+#include <glib.h>
+#include <gmodule.h>
+
+#include <mcabber/main.h>
+
+const gchar *g_module_check_init (GModule *module)
+{
+ char *ver = mcabber_version ();
+
+ // ver now contains version in format
+ // X.X.X[-xxx][ (XXXXXXXXX)]
+ if (...)
+ return "Incompatible mcabber version";
+
+ g_free (ver);
+ return NULL;
+}
+--------------------------------------------------------
+
+Also you can use glib check_init routine to modify
+module information, that will be checked by mcabber,
+eg. if you want your module to always pass mcabber
+version check, you can assign version, obtained from
+mcabber_version() to corresponding field in your struct.
+Or you can modify your module's dependencies, though
+direct module_load() will have the same effect, and
+can be used for optional dependencies, that your module
+can work without.
+
+Note: remember, that g_module_check_init will be always
+called, even if later module will not pass checks, thus:
+ - do not use functions from other modules there;
+ - provide g_module_unload to undo anything, check_init
+ has done.
+
==============
Further
@@ -509,5 +691,5 @@
-- Myhailo Danylenko
-- mailto:isbear@ukrpost.net
-- xmpp:isbear@unixzone.org.ua
- -- Mon, 18 Jan 2010 15:52:40 +0200
+ -- Thu, 04 Mar 2010 09:32:38 +0200
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mcabber/doc/help/en/hlp_module.txt Thu Mar 04 13:03:20 2010 +0200
@@ -0,0 +1,13 @@
+
+ /MODULE [-f] load|unload module
+ /MODULE [list]
+
+Load or unload module.
+
+/module [-f] load module
+ Loads specified module. If -f flag is specified, most of module loading errors will be ignored.
+/module [-f] unload module
+ Unloads specified module.
+ Note: The force flag will not remove any dependent on this modules!
+/module [list]
+ Lists modules in a format: [modulename] [reference count] ([manually/automatically loaded]) [loaded modules, that this module depends on]
--- a/mcabber/mcabber/Makefile.am Tue Mar 02 13:47:43 2010 +0100
+++ b/mcabber/mcabber/Makefile.am Thu Mar 04 13:03:20 2010 +0200
@@ -32,6 +32,7 @@
endif
if INSTALL_HEADERS
+mcabber_SOURCES += modules.c modules.h
mcabberinclude_HEADERS = main.h roster.h events.h \
commands.h compl.h \
hbuf.h screen.h logprint.h \
@@ -40,7 +41,8 @@
xmpp.h xmpp_helper.h xmpp_defines.h \
xmpp_iq.h xmpp_iqrequest.h \
xmpp_muc.h xmpp_s10n.h \
- caps.h fifo.h help.h $(top_srcdir)/include/config.h
+ caps.h fifo.h help.h modules.h \
+ $(top_srcdir)/include/config.h
if OTR
mcabberinclude_HEADERS += otr.h nohtml.h
--- a/mcabber/mcabber/commands.c Tue Mar 02 13:47:43 2010 +0100
+++ b/mcabber/mcabber/commands.c Thu Mar 04 13:03:20 2010 +0200
@@ -98,17 +98,9 @@
static GSList *Commands;
#ifdef MODULES_ENABLE
-#include <gmodule.h>
-
-static void do_load(char *arg);
-static void do_unload(char *arg);
-
-typedef struct {
- char *name;
- GModule *module;
-} loaded_module_t;
-
-GSList *loaded_modules = NULL;
+#include "modules.h"
+
+static void do_module(char *arg);
gpointer cmd_del(const char *name)
{
@@ -205,8 +197,7 @@
COMPL_JID, COMPL_STATUS, &do_status_to);
cmd_add("version", "Show mcabber version", 0, 0, &do_version);
#ifdef MODULES_ENABLE
- cmd_add("load", "Load module", 0, 0, &do_load);
- cmd_add("unload", "Unload module", 0, 0, &do_unload);
+ cmd_add("module", "Manipulations with modules", 0, 0, &do_module);
#endif
// Status category
@@ -343,23 +334,6 @@
compl_add_category_word(COMPL_COLOR, "mucnick");
}
-#ifdef MODULES_ENABLE
-void cmd_deinit ()
-{
- GSList *el = loaded_modules;
- while (el) {
- loaded_module_t *module = el->data;
- if (!g_module_close ((GModule *) module->module))
- scr_LogPrint (LPRINT_LOGNORM, "* Module unloading failed: %s",
- g_module_error ());
- g_free (module->name);
- g_free (module);
- el = g_slist_next (el);
- }
- g_slist_free (loaded_modules);
-}
-#endif
-
// expandalias(line)
// If there is one, expand the alias in line and returns a new allocated line
// If no alias is found, returns line
@@ -2980,65 +2954,33 @@
}
#ifdef MODULES_ENABLE
-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);
-}
-
-static void do_load(char *arg)
+static void do_module(char *arg)
{
- GModule *mod;
- GSList *lmod;
- char *mdir, *path;
- if (!arg || !*arg) {
- scr_LogPrint(LPRINT_LOGNORM, "Missing modulename.");
- return;
- }
- lmod = g_slist_find_custom(loaded_modules, arg, module_list_comparator);
- if (lmod) {
- scr_LogPrint(LPRINT_LOGNORM, "Module %s is already loaded.", arg);
- return;
+ gboolean force = FALSE;
+ char **args;
+
+ if (arg[0] == '-' && arg[1] == 'f') {
+ force = TRUE;
+ arg +=2;
+ while (*arg && *arg == ' ')
+ ++arg;
}
- mdir = expand_filename(settings_opt_get("modules_dir"));
- path = g_module_build_path(mdir ? mdir : PKGLIB_DIR, arg);
- mod = g_module_open(path, G_MODULE_BIND_LAZY);
- if (!mod)
- scr_LogPrint(LPRINT_LOGNORM, "Module loading failed: %s",
- g_module_error());
- else {
- loaded_module_t *module = g_new(loaded_module_t, 1);
- module->name = g_strdup(arg);
- module->module = mod;
- loaded_modules = g_slist_prepend(loaded_modules, module);
- scr_LogPrint(LPRINT_LOGNORM, "Loaded module %s.", arg);
+
+ args = split_arg(arg, 2, 0);
+ if (!args[0] || !strcmp(args[0], "list")) {
+ module_list_print();
+ } else {
+ const gchar *error;
+ if (!strcmp(args[0], "load"))
+ error = module_load(args[1], TRUE, force);
+ else if (!strcmp(args[0], "unload"))
+ error = module_unload(args[1], TRUE, force);
+ else
+ error = "Unknown subcommand";
+ if (error)
+ scr_LogPrint(LPRINT_LOGNORM, "Error: %s.", error);
}
- g_free(path);
- g_free(mdir);
-}
-
-static void do_unload(char *arg)
-{
- GSList *module;
- if (!arg || !*arg) {
- scr_LogPrint(LPRINT_LOGNORM, "Missing modulename.");
- return;
- }
- module = g_slist_find_custom(loaded_modules, arg, module_list_comparator);
- if (module) {
- loaded_module_t *mod = module->data;
- if (!g_module_close(mod->module))
- scr_LogPrint(LPRINT_LOGNORM, "Module unloading failed: %s",
- g_module_error());
- else {
- g_free(mod->name);
- g_free(mod);
- loaded_modules = g_slist_delete_link(loaded_modules, module);
- scr_LogPrint(LPRINT_LOGNORM, "Unloaded module %s.", arg);
- }
- } else
- scr_LogPrint(LPRINT_LOGNORM, "Module %s not loaded.", arg);
+ free_arg_lst(args);
}
#endif
--- a/mcabber/mcabber/main.c Tue Mar 02 13:47:43 2010 +0100
+++ b/mcabber/mcabber/main.c Thu Mar 04 13:03:20 2010 +0200
@@ -47,6 +47,10 @@
#include "help.h"
#include "events.h"
+#ifdef MODULES_ENABLE
+# include "modules.h"
+#endif
+
#ifdef ENABLE_HGCSET
# include "hgcset.h"
#endif
@@ -361,6 +365,9 @@
settings_init();
scr_init_bindings();
caps_init();
+#ifdef MODULES_ENABLE
+ modules_init();
+#endif
/* Initialize charset */
scr_InitLocaleCharSet();
ut_InitDebug();
@@ -471,7 +478,7 @@
evs_deinit();
scr_TerminateCurses();
#ifdef MODULES_ENABLE
- cmd_deinit();
+ modules_deinit();
#endif
fifo_deinit();
#ifdef HAVE_LIBOTR
--- /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... */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mcabber/mcabber/modules.h Thu Mar 04 13:03:20 2010 +0200
@@ -0,0 +1,39 @@
+#ifndef __MCABBER_MODULES_H__
+#define __MCABBER_MODULES_H__ 1
+
+#include <glib.h>
+
+// Module loading process looks like this:
+// check, if module is loaded
+// load module (+ run g_module_check_init)
+// check <modulename>_info variable
+// check version
+// load dependencies
+// run initialization callback
+// module loaded
+// ...
+// run uninitialization callback
+// unload module (+ run g_module_unload)
+// unload dependencies
+// module unloaded
+
+typedef void (*module_init_t)(void);
+typedef void (*module_uninit_t)(void);
+
+// public module-describing structure
+typedef struct {
+ const gchar *mcabber_version; // Contains mcabber version string, that this module is written to work with
+ module_init_t init; // Initialization callback to be called after all dependencies will be loaded
+ module_uninit_t uninit; // Uninitialization callback to be called before module unloading
+ const gchar **requires; // NULL-terminated list of module names, that must be loaded before this module
+} module_info_t;
+
+const gchar *module_load(const gchar *name, gboolean manual, gboolean force);
+const gchar *module_unload(const gchar *name, gboolean manual, gboolean force);
+
+void module_list_print(void);
+
+void modules_init(void);
+void modules_deinit(void);
+
+#endif
--- a/mcabber/mcabber/settings.c Tue Mar 02 13:47:43 2010 +0100
+++ b/mcabber/mcabber/settings.c Thu Mar 04 13:03:20 2010 +0200
@@ -187,7 +187,7 @@
startswith(line, "source ", FALSE) ||
startswith(line, "color ", FALSE) ||
#ifdef MODULES_ENABLE
- startswith(line, "load ", FALSE) ||
+ startswith(line, "module ", FALSE) ||
#endif
startswith(line, "status ", FALSE) ||
startswith(line, "otrpolicy", FALSE)) {
@@ -201,7 +201,7 @@
!startswith(line, "status ", FALSE) &&
!startswith(line, "color ", FALSE) &&
#ifdef MODULES_ENABLE
- !startswith(line, "load ", FALSE) &&
+ !startswith(line, "module ", FALSE) &&
#endif
!startswith(line, "otrpolicy ", FALSE)) {
scr_LogPrint(LPRINT_LOGNORM, "Error in configuration file (l. %d): "
--- a/mcabber/modules/beep/beep.c Tue Mar 02 13:47:43 2010 +0100
+++ b/mcabber/modules/beep/beep.c Thu Mar 04 13:03:20 2010 +0200
@@ -16,8 +16,6 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
-#include <glib.h>
-#include <gmodule.h>
#include <string.h>
#include <mcabber/logprint.h>
@@ -26,11 +24,23 @@
#include <mcabber/hooks.h>
#include <mcabber/screen.h>
#include <mcabber/settings.h>
+#include <mcabber/modules.h>
+
+static void beep_init (void);
+static void beep_uninit (void);
+
+/* Module description */
+module_info_t info_beep = {
+ .mcabber_version = "0.10.0",
+ .requires = NULL,
+ .init = beep_init,
+ .uninit = beep_uninit,
+};
static guint beep_cid = 0;
/* Event handler */
-void beep_hh (guint32 hid, hk_arg_t *args, gpointer userdata)
+static void beep_hh (guint32 hid, hk_arg_t *args, gpointer userdata)
{
/* Check if beeping is enabled */
if (settings_opt_get_int ("beep_enable"))
@@ -39,7 +49,7 @@
}
/* beep command handler */
-void do_beep (char *args)
+static void do_beep (char *args)
{
/* Check arguments, and if recognized,
* set mcabber option accordingly */
@@ -67,7 +77,7 @@
}
/* Initialization */
-const gchar* g_module_check_init (GModule *module)
+static void beep_init (void)
{
/* Create completions */
beep_cid = compl_new_category ();
@@ -81,11 +91,10 @@
* We are only interested in incoming message events
*/
hk_add_handler (beep_hh, HOOK_MESSAGE_IN, NULL);
- return NULL;
}
/* Deinitialization */
-void g_module_unload (GModule *module)
+static void beep_uninit (void)
{
/* Unregister event handler */
hk_del_handler (beep_hh, NULL);