--- Prosody IM v0.4
+-- Prosody IM
-- Copyright (C) 2008-2009 Matthew Wild
-- Copyright (C) 2008-2009 Waqas Hussain
--
-- COPYING file in the source package for more information.
--
-
-
local plugin_dir = CFG_PLUGINDIR or "./plugins/";
local logger = require "util.logger";
local log = logger.init("modulemanager");
-local addDiscoInfoHandler = require "core.discomanager".addDiscoInfoHandler;
local eventmanager = require "core.eventmanager";
local config = require "core.configmanager";
local multitable_new = require "util.multitable".new;
local register_actions = require "core.actions".register;
local st = require "util.stanza";
+local pluginloader = require "util.pluginloader";
local hosts = hosts;
local prosody = prosody;
local type = type;
local next = next;
local rawget = rawget;
-
+local error = error;
local tostring = tostring;
+local autoload_modules = {"presence", "message", "iq"};
+
-- We need this to let modules access the real global namespace
local _G = _G;
module "modulemanager"
-local api = {}; -- Module API container
+api = {};
+local api = api; -- Module API container
local modulemap = { ["*"] = {} };
local modulehelpers = setmetatable({}, { __index = _G });
-local features_table = multitable_new();
local handler_table = multitable_new();
local hooked = multitable_new();
local hooks = multitable_new();
-- Load modules when a host is activated
function load_modules_for_host(host)
- if config.get(host, "core", "modules_enable") == false then
- return; -- Only load for hosts, not components, etc.
- end
-
- -- Load modules from global section
- local modules_enabled = config.get("*", "core", "modules_enabled");
- local modules_disabled = config.get(host, "core", "modules_disabled");
local disabled_set = {};
- if modules_enabled then
- if modules_disabled then
- for _, module in ipairs(modules_disabled) do
- disabled_set[module] = true;
- end
+ local modules_disabled = config.get(host, "core", "modules_disabled");
+ if modules_disabled then
+ for _, module in ipairs(modules_disabled) do
+ disabled_set[module] = true;
end
- for _, module in ipairs({"presence", "message", "iq"}) do
+ end
+
+ -- Load auto-loaded modules for this host
+ if hosts[host].type == "local" then
+ for _, module in ipairs(autoload_modules) do
if not disabled_set[module] then
load(host, module);
end
end
- for _, module in ipairs(modules_enabled) do
- if not disabled_set[module] and not is_loaded(host, module) then
- load(host, module);
+ end
+
+ -- Load modules from global section
+ if config.get(host, "core", "load_global_modules") ~= false then
+ local modules_enabled = config.get("*", "core", "modules_enabled");
+ if modules_enabled then
+ for _, module in ipairs(modules_enabled) do
+ if not disabled_set[module] and not is_loaded(host, module) then
+ load(host, module);
+ end
end
end
end
-
+
-- Load modules from just this host
local modules_enabled = config.get(host, "core", "modules_enabled");
- if modules_enabled then
+ if modules_enabled and modules_enabled ~= config.get("*", "core", "modules_enabled") then
for _, module in pairs(modules_enabled) do
if not is_loaded(host, module) then
load(host, module);
end
end
eventmanager.add_event_hook("host-activated", load_modules_for_host);
+eventmanager.add_event_hook("component-activated", load_modules_for_host);
--
function load(host, module_name, config)
end
- local mod, err = loadfile(get_module_filename(module_name));
+ local mod, err = pluginloader.load_code(module_name);
if not mod then
log("error", "Unable to load module '%s': %s", module_name or "nil", err or "nil");
return nil, err;
local pluginenv = setmetatable({ module = api_instance }, { __index = _G });
setfenv(mod, pluginenv);
- if not hosts[host] then hosts[host] = { type = "component", host = host, connected = false, s2sout = {} }; end
-
- local success, ret = pcall(mod);
- if not success then
- log("error", "Error initialising module '%s': %s", name or "nil", ret or "nil");
- return nil, ret;
+ if not hosts[host] then
+ local create_component = _G.require "core.componentmanager".create_component;
+ hosts[host] = create_component(host);
+ hosts[host].connected = false;
+ log("debug", "Created new component: %s", host);
end
+ hosts[host].modules = modulemap[host];
+ modulemap[host][module_name] = pluginenv;
- -- Use modified host, if the module set one
- modulemap[api_instance.host][module_name] = pluginenv;
-
- if api_instance.host == "*" and host ~= "*" then
- api_instance:set_global();
+ local success, err = pcall(mod);
+ if success then
+ if module_has_method(pluginenv, "load") then
+ success, err = call_module_method(pluginenv, "load");
+ if not success then
+ log("warn", "Error loading module '%s' on '%s': %s", module_name, host, err or "nil");
+ end
+ end
+
+ -- Use modified host, if the module set one
+ if api_instance.host == "*" and host ~= "*" then
+ modulemap[host][module_name] = nil;
+ modulemap["*"][module_name] = pluginenv;
+ api_instance:set_global();
+ end
+ else
+ log("error", "Error initializing module '%s' on '%s': %s", module_name, host, err or "nil");
+ end
+ if success then
+ return true;
+ else -- load failed, unloading
+ unload(api_instance.host, module_name);
+ return nil, err;
end
-
- return true;
end
function get_module(host, name)
log("warn", "Non-fatal error unloading module '%s' on '%s': %s", name, host, err);
end
end
- modulemap[host][name] = nil;
- features_table:remove(host, name);
local params = handler_table:get(host, name); -- , {module.host, origin_type, tag, xmlns}
for _, param in pairs(params or NULL) do
local handlers = stanza_handlers:get(param[1], param[2], param[3], param[4]);
end
end
hooks:remove(host, name);
+ modulemap[host][name] = nil;
return true;
end
local mod = get_module(host, name);
if not mod then return nil, "module-not-loaded"; end
- local _mod, err = loadfile(get_module_filename(name)); -- checking for syntax errors
+ local _mod, err = pluginloader.load_code(name); -- checking for syntax errors
if not _mod then
- log("error", "Unable to load module '%s': %s", module_name or "nil", err or "nil");
+ log("error", "Unable to load module '%s': %s", name or "nil", err or "nil");
return nil, err;
end
end
function handle_stanza(host, origin, stanza)
- local name, xmlns, origin_type = stanza.name, stanza.attr.xmlns, origin.type;
+ local name, xmlns, origin_type = stanza.name, stanza.attr.xmlns or "jabber:client", origin.type;
if name == "iq" and xmlns == "jabber:client" then
if stanza.attr.type == "get" or stanza.attr.type == "set" then
xmlns = stanza.tags[1].attr.xmlns or "jabber:client";
(handlers[1])(origin, stanza);
return true;
else
- log("debug", "Unhandled %s stanza: %s; xmlns=%s", origin.type, stanza.name, xmlns); -- we didn't handle it
if stanza.attr.xmlns == "jabber:client" then
+ log("debug", "Unhandled %s stanza: %s; xmlns=%s", origin.type, stanza.name, xmlns); -- we didn't handle it
if stanza.attr.type ~= "error" and stanza.attr.type ~= "result" then
origin.send(st.error_reply(stanza, "cancel", "service-unavailable"));
end
- elseif not(name == "features" and xmlns == "http://etherx.jabber.org/streams") then -- FIXME remove check once we handle S2S features
+ elseif not((name == "features" or name == "error") and xmlns == "http://etherx.jabber.org/streams") then -- FIXME remove check once we handle S2S features
+ log("warn", "Unhandled %s stream element: %s; xmlns=%s: %s", origin.type, stanza.name, xmlns, tostring(stanza)); -- we didn't handle it
origin:close("unsupported-stanza-type");
end
end
end
end
-local _modulepath = { plugin_dir, "mod_", nil, ".lua"};
-function get_module_filename(name)
- _modulepath[3] = name;
- return t_concat(_modulepath);
-end
-
----- API functions exposed to modules -----------
-- Must all be in api.*
self:add_handler(origin_type, "iq", xmlns, handler);
end
-addDiscoInfoHandler("*host", function(reply, to, from, node)
- if #node == 0 then
- local done = {};
- for module, features in pairs(features_table:get(to) or NULL) do -- for each module
- for feature in pairs(features) do
- if not done[feature] then
- reply:tag("feature", {var = feature}):up(); -- TODO cache
- done[feature] = true;
- end
- end
- end
- for module, features in pairs(features_table:get("*") or NULL) do -- for each module
- for feature in pairs(features) do
- if not done[feature] then
- reply:tag("feature", {var = feature}):up(); -- TODO cache
- done[feature] = true;
- end
- end
- end
- return next(done) ~= nil;
- end
-end);
-
function api:add_feature(xmlns)
- features_table:set(self.host, self.name, xmlns, true);
+ self:add_item("feature", xmlns);
+end
+function api:add_identity(category, type, name)
+ self:add_item("identity", {category = category, type = type, name = name});
end
local event_hook = function(host, mod_name, event_name, ...)
return api.hook(self, "stanza/"..(xmlns and (xmlns..":") or "")..name, function (data) return handler(data.origin, data.stanza, data); end, priority);
end
+function api:require(lib)
+ local f, n = pluginloader.load_code(self.name, lib..".lib.lua");
+ if not f then
+ f, n = pluginloader.load_code(lib, lib..".lib.lua");
+ end
+ if not f then error("Failed to load plugin library '"..lib.."', error: "..n); end -- FIXME better error message
+ setfenv(f, setmetatable({ module = self }, { __index = _G }));
+ return f();
+end
+
+function api:get_option(name, default_value)
+ local value = config.get(self.host, self.name, name);
+ if value == nil then
+ value = config.get(self.host, "core", name);
+ if value == nil then
+ value = default_value;
+ end
+ end
+ return value;
+end
+
+local t_remove = _G.table.remove;
+local module_items = multitable_new();
+function api:add_item(key, value)
+ self.items = self.items or {};
+ self.items[key] = self.items[key] or {};
+ t_insert(self.items[key], value);
+ self:fire_event("item-added/"..key, {source = self, item = value});
+end
+function api:remove_item(key, value)
+ local t = self.items and self.items[key] or NULL;
+ for i = #t,1,-1 do
+ if t[i] == value then
+ t_remove(self.items[key], i);
+ self:fire_event("item-removed/"..key, {source = self, item = value});
+ return value;
+ end
+ end
+end
+
+function api:get_host_items(key)
+ local result = {};
+ for mod_name, module in pairs(modulemap[self.host]) do
+ module = module.module;
+ if module.items then
+ for _, item in ipairs(module.items[key] or NULL) do
+ t_insert(result, item);
+ end
+ end
+ end
+ for mod_name, module in pairs(modulemap["*"]) do
+ module = module.module;
+ if module.items then
+ for _, item in ipairs(module.items[key] or NULL) do
+ t_insert(result, item);
+ end
+ end
+ end
+ return result;
+end
+
--------------------------------------------------------------------
local actions = {};