1 local events = require "util.events";
2 local t_remove = table.remove;
4 module("pubsub", package.seeall);
7 local service_mt = { __index = service };
9 local default_config = { __index = {
10 broadcaster = function () end;
11 get_affiliation = function () end;
14 local default_node_config = { __index = {
15 ["pubsub#max_items"] = "20";
19 config = config or {};
21 config = setmetatable(config, default_config);
22 node_defaults = setmetatable(config.node_defaults or {}, default_node_config);
27 events = events.new();
31 function service:jids_equal(jid1, jid2)
32 local normalize = self.config.normalize_jid;
33 return normalize(jid1) == normalize(jid2);
36 function service:may(node, actor, action)
37 if actor == true then return true; end
39 local node_obj = self.nodes[node];
40 local node_aff = node_obj and node_obj.affiliations[actor];
41 local service_aff = self.affiliations[actor]
42 or self.config.get_affiliation(actor, node, action)
45 -- Check if node allows/forbids it
46 local node_capabilities = node_obj and node_obj.capabilities;
47 if node_capabilities then
48 local caps = node_capabilities[node_aff or service_aff];
50 local can = caps[action];
57 -- Check service-wide capabilities instead
58 local service_capabilities = self.config.capabilities;
59 local caps = service_capabilities[node_aff or service_aff];
61 local can = caps[action];
70 function service:set_affiliation(node, actor, jid, affiliation)
72 if not self:may(node, actor, "set_affiliation") then
73 return false, "forbidden";
76 local node_obj = self.nodes[node];
78 return false, "item-not-found";
80 node_obj.affiliations[jid] = affiliation;
81 local _, jid_sub = self:get_subscription(node, true, jid);
82 if not jid_sub and not self:may(node, jid, "be_unsubscribed") then
83 local ok, err = self:add_subscription(node, true, jid);
87 elseif jid_sub and not self:may(node, jid, "be_subscribed") then
88 local ok, err = self:add_subscription(node, true, jid);
96 function service:add_subscription(node, actor, jid, options)
99 if actor == true or jid == actor or self:jids_equal(actor, jid) then
102 cap = "subscribe_other";
104 if not self:may(node, actor, cap) then
105 return false, "forbidden";
107 if not self:may(node, jid, "be_subscribed") then
108 return false, "forbidden";
111 local node_obj = self.nodes[node];
113 if not self.config.autocreate_on_subscribe then
114 return false, "item-not-found";
116 local ok, err = self:create(node, true);
120 node_obj = self.nodes[node];
123 node_obj.subscribers[jid] = options or true;
124 local normal_jid = self.config.normalize_jid(jid);
125 local subs = self.subscriptions[normal_jid];
127 if not subs[jid] then
128 subs[jid] = { [node] = true };
130 subs[jid][node] = true;
133 self.subscriptions[normal_jid] = { [jid] = { [node] = true } };
135 self.events.fire_event("subscription-added", { node = node, jid = jid, normalized_jid = normal_jid, options = options });
139 function service:remove_subscription(node, actor, jid)
142 if actor == true or jid == actor or self:jids_equal(actor, jid) then
145 cap = "unsubscribe_other";
147 if not self:may(node, actor, cap) then
148 return false, "forbidden";
150 if not self:may(node, jid, "be_unsubscribed") then
151 return false, "forbidden";
154 local node_obj = self.nodes[node];
156 return false, "item-not-found";
158 if not node_obj.subscribers[jid] then
159 return false, "not-subscribed";
161 node_obj.subscribers[jid] = nil;
162 local normal_jid = self.config.normalize_jid(jid);
163 local subs = self.subscriptions[normal_jid];
165 local jid_subs = subs[jid];
167 jid_subs[node] = nil;
168 if next(jid_subs) == nil then
172 if next(subs) == nil then
173 self.subscriptions[normal_jid] = nil;
176 self.events.fire_event("subscription-removed", { node = node, jid = jid, normalized_jid = normal_jid });
180 function service:remove_all_subscriptions(actor, jid)
181 local normal_jid = self.config.normalize_jid(jid);
182 local subs = self.subscriptions[normal_jid]
183 subs = subs and subs[jid];
185 for node in pairs(subs) do
186 self:remove_subscription(node, true, jid);
192 function service:get_subscription(node, actor, jid)
195 if actor == true or jid == actor or self:jids_equal(actor, jid) then
196 cap = "get_subscription";
198 cap = "get_subscription_other";
200 if not self:may(node, actor, cap) then
201 return false, "forbidden";
204 local node_obj = self.nodes[node];
206 return false, "item-not-found";
208 return true, node_obj.subscribers[jid];
211 function service:create(node, actor, options)
213 if not self:may(node, actor, "create") then
214 return false, "forbidden";
217 if self.nodes[node] then
218 return false, "conflict";
221 self.data[node] = {};
225 config = setmetatable(options or {}, {__index=self.node_defaults});
228 setmetatable(self.nodes[node], { __index = { data = self.data[node] } }); -- COMPAT
229 self.events.fire_event("node-created", { node = node, actor = actor });
230 local ok, err = self:set_affiliation(node, true, actor, "owner");
232 self.nodes[node] = nil;
233 self.data[node] = nil;
238 function service:delete(node, actor)
240 if not self:may(node, actor, "delete") then
241 return false, "forbidden";
244 local node_obj = self.nodes[node];
246 return false, "item-not-found";
248 self.nodes[node] = nil;
249 self.data[node] = nil;
250 self.events.fire_event("node-deleted", { node = node, actor = actor });
251 self.config.broadcaster("delete", node, node_obj.subscribers);
255 local function remove_item_by_id(data, id)
256 if not data[id] then return end
258 for i, _id in ipairs(data) do
266 local function trim_items(data, max)
268 if not max or #data <= max then return end
270 data[t_remove(data, 1)] = nil;
274 function service:publish(node, actor, id, item)
276 if not self:may(node, actor, "publish") then
277 return false, "forbidden";
280 local node_obj = self.nodes[node];
282 if not self.config.autocreate_on_publish then
283 return false, "item-not-found";
285 local ok, err = self:create(node, true);
289 node_obj = self.nodes[node];
291 local node_data = self.data[node];
292 remove_item_by_id(node_data, id);
293 node_data[#node_data + 1] = id;
294 node_data[id] = item;
295 trim_items(node_data, node_obj.config["pubsub#max_items"]);
296 self.events.fire_event("item-published", { node = node, actor = actor, id = id, item = item });
297 self.config.broadcaster("items", node, node_obj.subscribers, item, actor);
301 function service:retract(node, actor, id, retract)
303 if not self:may(node, actor, "retract") then
304 return false, "forbidden";
307 local node_obj = self.nodes[node];
308 if (not node_obj) or (not self.data[node][id]) then
309 return false, "item-not-found";
311 self.events.fire_event("item-retracted", { node = node, actor = actor, id = id });
312 remove_item_by_id(self.data[node], id);
314 self.config.broadcaster("items", node, node_obj.subscribers, retract);
319 function service:purge(node, actor, notify)
321 if not self:may(node, actor, "retract") then
322 return false, "forbidden";
325 local node_obj = self.nodes[node];
327 return false, "item-not-found";
329 self.data[node] = {}; -- Purge
330 self.events.fire_event("node-purged", { node = node, actor = actor });
332 self.config.broadcaster("purge", node, node_obj.subscribers);
337 function service:get_items(node, actor, id)
339 if not self:may(node, actor, "get_items") then
340 return false, "forbidden";
343 local node_obj = self.nodes[node];
345 return false, "item-not-found";
347 if id then -- Restrict results to a single specific item
348 return true, { id, [id] = self.data[node][id] };
350 return true, self.data[node];
354 function service:get_nodes(actor)
356 if not self:may(nil, actor, "get_nodes") then
357 return false, "forbidden";
360 return true, self.nodes;
363 function service:get_subscriptions(node, actor, jid)
366 if actor == true or jid == actor or self:jids_equal(actor, jid) then
367 cap = "get_subscriptions";
369 cap = "get_subscriptions_other";
371 if not self:may(node, actor, cap) then
372 return false, "forbidden";
377 node_obj = self.nodes[node];
379 return false, "item-not-found";
382 local normal_jid = self.config.normalize_jid(jid);
383 local subs = self.subscriptions[normal_jid];
384 -- We return the subscription object from the node to save
385 -- a get_subscription() call for each node.
388 for jid, subscribed_nodes in pairs(subs) do
389 if node then -- Return only subscriptions to this node
390 if subscribed_nodes[node] then
394 subscription = node_obj.subscribers[jid];
397 else -- Return subscriptions to all nodes
398 local nodes = self.nodes;
399 for subscribed_node in pairs(subscribed_nodes) do
401 node = subscribed_node;
403 subscription = nodes[subscribed_node].subscribers[jid];
412 -- Access models only affect 'none' affiliation caps, service/default access level...
413 function service:set_node_capabilities(node, actor, capabilities)
415 if not self:may(node, actor, "configure") then
416 return false, "forbidden";
419 local node_obj = self.nodes[node];
421 return false, "item-not-found";
423 node_obj.capabilities = capabilities;
427 function service:set_node_config(node, actor, new_config)
428 if not self:may(node, actor, "configure") then
429 return false, "forbidden";
432 local node_obj = self.nodes[node];
434 return false, "item-not-found";
437 for k,v in pairs(new_config) do
438 node_obj.config[k] = v;
440 trim_items(self.data[node], node_obj.config["pubsub#max_items"]);