ee960709e117f1683e2d0f5d05e32d42d6c3504b
[prosody.git] / core / moduleapi.lua
1 -- Prosody IM
2 -- Copyright (C) 2008-2012 Matthew Wild
3 -- Copyright (C) 2008-2012 Waqas Hussain
4 -- 
5 -- This project is MIT/X11 licensed. Please see the
6 -- COPYING file in the source package for more information.
7 --
8
9 local config = require "core.configmanager";
10 local modulemanager = require "modulemanager";
11 local array = require "util.array";
12 local set = require "util.set";
13 local logger = require "util.logger";
14 local pluginloader = require "util.pluginloader";
15
16 local multitable_new = require "util.multitable".new;
17
18 local t_insert, t_remove, t_concat = table.insert, table.remove, table.concat;
19 local error, setmetatable, setfenv, type = error, setmetatable, setfenv, type;
20 local ipairs, pairs, select, unpack = ipairs, pairs, select, unpack;
21 local tonumber, tostring = tonumber, tostring;
22
23 local prosody = prosody;
24 local hosts = prosody.hosts;
25
26 -- Registry of shared module data
27 local shared_data = setmetatable({}, { __mode = "v" });
28
29 local NULL = {};
30
31 local api = {};
32
33 -- Returns the name of the current module
34 function api:get_name()
35         return self.name;
36 end
37
38 -- Returns the host that the current module is serving
39 function api:get_host()
40         return self.host;
41 end
42
43 function api:get_host_type()
44         return hosts[self.host].type;
45 end
46
47 function api:set_global()
48         self.host = "*";
49         -- Update the logger
50         local _log = logger.init("mod_"..self.name);
51         self.log = function (self, ...) return _log(...); end;
52         self._log = _log;
53 end
54
55 function api:add_feature(xmlns)
56         self:add_item("feature", xmlns);
57 end
58 function api:add_identity(category, type, name)
59         self:add_item("identity", {category = category, type = type, name = name});
60 end
61 function api:add_extension(data)
62         self:add_item("extension", data);
63 end
64
65 function api:fire_event(...)
66         return (hosts[self.host] or prosody).events.fire_event(...);
67 end
68
69 function api:hook(event, handler, priority)
70         hooks:set(self.host, self.name, event, handler, true);
71         (hosts[self.host] or prosody).events.add_handler(event, handler, priority);
72 end
73
74 function api:hook_global(event, handler, priority)
75         hooks:set("*", self.name, event, handler, true);
76         prosody.events.add_handler(event, handler, priority);
77 end
78
79 function api:hook_stanza(xmlns, name, handler, priority)
80         if not handler and type(name) == "function" then
81                 -- If only 2 options then they specified no xmlns
82                 xmlns, name, handler, priority = nil, xmlns, name, handler;
83         elseif not (handler and name) then
84                 self:log("warn", "Error: Insufficient parameters to module:hook_stanza()");
85                 return;
86         end
87         return self:hook("stanza/"..(xmlns and (xmlns..":") or "")..name, function (data) return handler(data.origin, data.stanza, data); end, priority);
88 end
89
90 function api:require(lib)
91         local f, n = pluginloader.load_code(self.name, lib..".lib.lua");
92         if not f then
93                 f, n = pluginloader.load_code(lib, lib..".lib.lua");
94         end
95         if not f then error("Failed to load plugin library '"..lib.."', error: "..n); end -- FIXME better error message
96         setfenv(f, self.environment);
97         return f();
98 end
99
100 function api:get_option(name, default_value)
101         local value = config.get(self.host, self.name, name);
102         if value == nil then
103                 value = config.get(self.host, "core", name);
104                 if value == nil then
105                         value = default_value;
106                 end
107         end
108         return value;
109 end
110
111 function api:get_option_string(name, default_value)
112         local value = self:get_option(name, default_value);
113         if type(value) == "table" then
114                 if #value > 1 then
115                         self:log("error", "Config option '%s' does not take a list, using just the first item", name);
116                 end
117                 value = value[1];
118         end
119         if value == nil then
120                 return nil;
121         end
122         return tostring(value);
123 end
124
125 function api:get_option_number(name, ...)
126         local value = self:get_option(name, ...);
127         if type(value) == "table" then
128                 if #value > 1 then
129                         self:log("error", "Config option '%s' does not take a list, using just the first item", name);
130                 end
131                 value = value[1];
132         end
133         local ret = tonumber(value);
134         if value ~= nil and ret == nil then
135                 self:log("error", "Config option '%s' not understood, expecting a number", name);
136         end
137         return ret;
138 end
139
140 function api:get_option_boolean(name, ...)
141         local value = self:get_option(name, ...);
142         if type(value) == "table" then
143                 if #value > 1 then
144                         self:log("error", "Config option '%s' does not take a list, using just the first item", name);
145                 end
146                 value = value[1];
147         end
148         if value == nil then
149                 return nil;
150         end
151         local ret = value == true or value == "true" or value == 1 or nil;
152         if ret == nil then
153                 ret = (value == false or value == "false" or value == 0);
154                 if ret then
155                         ret = false;
156                 else
157                         ret = nil;
158                 end
159         end
160         if ret == nil then
161                 self:log("error", "Config option '%s' not understood, expecting true/false", name);
162         end
163         return ret;
164 end
165
166 function api:get_option_array(name, ...)
167         local value = self:get_option(name, ...);
168
169         if value == nil then
170                 return nil;
171         end
172         
173         if type(value) ~= "table" then
174                 return array{ value }; -- Assume any non-list is a single-item list
175         end
176         
177         return array():append(value); -- Clone
178 end
179
180 function api:get_option_set(name, ...)
181         local value = self:get_option_array(name, ...);
182         
183         if value == nil then
184                 return nil;
185         end
186         
187         return set.new(value);
188 end
189
190 local module_items = multitable_new();
191 function api:add_item(key, value)
192         self.items = self.items or {};
193         self.items[key] = self.items[key] or {};
194         t_insert(self.items[key], value);
195         self:fire_event("item-added/"..key, {source = self, item = value});
196 end
197 function api:remove_item(key, value)
198         local t = self.items and self.items[key] or NULL;
199         for i = #t,1,-1 do
200                 if t[i] == value then
201                         t_remove(self.items[key], i);
202                         self:fire_event("item-removed/"..key, {source = self, item = value});
203                         return value;
204                 end
205         end
206 end
207
208 function api:get_host_items(key)
209         local result = {};
210         for mod_name, module in pairs(modulemanager.get_modules(self.host)) do
211                 module = module.module;
212                 if module.items then
213                         for _, item in ipairs(module.items[key] or NULL) do
214                                 t_insert(result, item);
215                         end
216                 end
217         end
218         for mod_name, module in pairs(modulemanager.get_modules("*")) do
219                 module = module.module;
220                 if module.items then
221                         for _, item in ipairs(module.items[key] or NULL) do
222                                 t_insert(result, item);
223                         end
224                 end
225         end
226         return result;
227 end
228
229 function api:handle_items(type, added_cb, removed_cb, existing)
230         self:hook("item-added/"..type, added_cb);
231         self:hook("item-removed/"..type, removed_cb);
232         if existing ~= false then
233                 for _, item in ipairs(self:get_host_items(type)) do
234                         added_cb({ item = item });
235                 end
236         end
237 end
238
239 return api;