aboutsummaryrefslogtreecommitdiffstats
path: root/core/modulemanager.lua
blob: aec6358f44a41dc9ba4e92e226b08c5fda2c3473 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
-- Prosody IM v0.1
-- Copyright (C) 2008 Matthew Wild
-- Copyright (C) 2008 Waqas Hussain
-- 
-- 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
--



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 loadfile, pcall = loadfile, pcall;
local setmetatable, setfenv, getfenv = setmetatable, setfenv, getfenv;
local pairs, ipairs = pairs, ipairs;
local t_insert = table.insert;
local type = type;

local tostring, print = tostring, print;

-- We need this to let modules access the real global namespace
local _G = _G;

module "modulemanager"

local api = {}; -- Module API container

local modulemap = {};

local m_handler_info = multitable_new();
local m_stanza_handlers = multitable_new();
local handler_info = {};
local stanza_handlers = {};

local modulehelpers = setmetatable({}, { __index = _G });

-- Load modules when a host is activated
function load_modules_for_host(host)
	local modules_enabled = config.get(host, "core", "modules_enabled");
	if modules_enabled then
		for _, module in pairs(modules_enabled) do
			load(host, module);
		end
	end
end
eventmanager.add_event_hook("host-activated", load_modules_for_host);
--

function load(host, module_name, config)
	if not (host and module_name) then
		return nil, "insufficient-parameters";
	end
	local mod, err = loadfile(plugin_dir.."mod_"..module_name..".lua");
	if not mod then
		log("error", "Unable to load module '%s': %s", module_name or "nil", err or "nil");
		return nil, err;
	end
	
	if not modulemap[host] then
		modulemap[host] = {};
		stanza_handlers[host] = {};
	elseif modulemap[host][module_name] then
		log("warn", "%s is already loaded for %s, so not loading again", module_name, host);
		return nil, "module-already-loaded";
	end
	
	local _log = logger.init(host..":"..module_name);
	local api_instance = setmetatable({ name = module_name, host = host, config = config,  _log = _log, log = function (self, ...) return _log(...); end }, { __index = api });

	local pluginenv = setmetatable({ module = api_instance }, { __index = _G });
	
	setfenv(mod, pluginenv);
	
	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;
	end
	
	modulemap[host][module_name] = mod;
	
	return true;
end

function is_loaded(host, name)
	return modulemap[host] and modulemap[host][name] and true;
end

function unload(host, name, ...)
	local mod = modulemap[host] and modulemap[host][name];
	if not mod then return nil, "module-not-loaded"; end
	
	if type(mod.unload) == "function" then
		local ok, err = pcall(mod.unload, ...)
		if (not ok) and err then
			log("warn", "Non-fatal error unloading module '%s' from '%s': %s", name, host, err);
		end
	end
	
end

function _handle_stanza(host, origin, stanza)
	local name, xmlns, origin_type = stanza.name, stanza.attr.xmlns, origin.type;
	
	local handlers = stanza_handlers[host];
	if not handlers then
		log("warn", "No handlers for %s", host);
		return false;
	end
	
	if name == "iq" and xmlns == "jabber:client" and handlers[origin_type] then
		local child = stanza.tags[1];
		if child then
			local xmlns = child.attr.xmlns or xmlns;
			log("debug", "Stanza of type %s from %s has xmlns: %s", name, origin_type, xmlns);
			local handler = handlers[origin_type][name] and handlers[origin_type][name][xmlns];
			if handler then
				log("debug", "Passing stanza to mod_%s", handler_info[handler].name);
				return handler(origin, stanza) or true;
			end
		end
	elseif handlers[origin_type] then
		local handler = handlers[origin_type][name];
		if  handler then
			handler = handler[xmlns];
			if handler then
				log("debug", "Passing stanza to mod_%s", handler_info[handler].name);
				return handler(origin, stanza) or true;
			end
		end
	end
	log("debug", "Stanza unhandled by any modules, xmlns: %s", stanza.attr.xmlns);
	return false; -- we didn't handle it
end
function handle_stanza(host, origin, stanza)
	local name, xmlns, origin_type = stanza.name, stanza.attr.xmlns, origin.type;
	if name == "iq" and xmlns == "jabber:client" then
		xmlns = stanza.tags[1].attr.xmlns;
		log("debug", "Stanza of type %s from %s has xmlns: %s", name, origin_type, xmlns);
	end
	local handlers = m_stanza_handlers:get(host, origin_type, name, xmlns);
	if handlers then
		log("debug", "Passing stanza to mod_%s", handler_info[handlers[1]].name);
		(handlers[1])(origin, stanza);
		return true;
	else
		log("debug", "Stanza unhandled by any modules, xmlns: %s", stanza.attr.xmlns); -- we didn't handle it
	end
end

----- API functions exposed to modules -----------
-- Must all be in api.* 

-- Returns the name of the current module
function api:get_name()
	return self.name;
end

-- Returns the host that the current module is serving
function api:get_host()
	return self.host;
end


local function __add_iq_handler(module, origin_type, xmlns, handler)
	local handlers = stanza_handlers[module.host];
	handlers[origin_type] = handlers[origin_type] or {};
	handlers[origin_type].iq = handlers[origin_type].iq or {};
	if not handlers[origin_type].iq[xmlns] then
		handlers[origin_type].iq[xmlns]= handler;
		handler_info[handler] = module;
		module:log("debug", "I now handle tag 'iq' [%s] with payload namespace '%s'", origin_type, xmlns);
	else
		module:log("warn", "I wanted to handle tag 'iq' [%s] with payload namespace '%s' but mod_%s already handles that", origin_type, xmlns, handler_info[handlers[origin_type].iq[xmlns]].name);
	end
end
local function _add_iq_handler(module, origin_type, xmlns, handler)
	local handlers = m_stanza_handlers:get(module.host, origin_type, "iq", xmlns);
	if not handlers then
		m_stanza_handlers:add(module.host, origin_type, "iq", xmlns, handler);
		handler_info[handler] = module;
		module:log("debug", "I now handle tag 'iq' [%s] with payload namespace '%s'", origin_type, xmlns);
	else
		module:log("warn", "I wanted to handle tag 'iq' [%s] with payload namespace '%s' but mod_%s already handles that", origin_type, xmlns, handler_info[handlers[1]].name);
	end
end

function api:add_iq_handler(origin_type, xmlns, handler)
	if not (origin_type and handler and xmlns) then return false; end
	if type(origin_type) == "table" then
		for _, origin_type in ipairs(origin_type) do
			_add_iq_handler(self, origin_type, xmlns, handler);
		end
		return;
	end
	_add_iq_handler(self, origin_type, xmlns, handler);
end

function api:add_feature(xmlns)
	addDiscoInfoHandler(self.host, function(reply, to, from, node)
		if #node == 0 then
			reply:tag("feature", {var = xmlns}):up();
			return true;
		end
	end);
end

api.add_event_hook = eventmanager.add_event_hook;

local function __add_handler(module, origin_type, tag, xmlns, handler)
	local handlers = stanza_handlers[module.host];
	handlers[origin_type] = handlers[origin_type] or {};
	if not handlers[origin_type][tag] then
		handlers[origin_type][tag] = handlers[origin_type][tag] or {};
		handlers[origin_type][tag][xmlns]= handler;
		handler_info[handler] = module;
		module:log("debug", "I now handle tag '%s' [%s] with xmlns '%s'", tag, origin_type, xmlns);
	elseif handler_info[handlers[origin_type][tag]] then
		log("warning", "I wanted to handle tag '%s' [%s] but mod_%s already handles that", tag, origin_type, handler_info[handlers[origin_type][tag]].module.name);
	end
end
local function _add_handler(module, origin_type, tag, xmlns, handler)
	local handlers = m_stanza_handlers:get(module.host, origin_type, tag, xmlns);
	if not handlers then
		m_stanza_handlers:add(module.host, origin_type, tag, xmlns, handler);
		handler_info[handler] = module;
		module:log("debug", "I now handle tag '%s' [%s] with xmlns '%s'", tag, origin_type, xmlns);
	else
		module:log("warning", "I wanted to handle tag '%s' [%s] but mod_%s already handles that", tag, origin_type, handler_info[handlers[1]].module.name);
	end
end

function api:add_handler(origin_type, tag, xmlns, handler)
	if not (origin_type and tag and xmlns and handler) then return false; end
	if type(origin_type) == "table" then
		for _, origin_type in ipairs(origin_type) do
			_add_handler(self, origin_type, tag, xmlns, handler);
		end
		return;
	end
	_add_handler(self, origin_type, tag, xmlns, handler);
end

--------------------------------------------------------------------

return _M;