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 = {
10 broadcaster = function () end;
11 get_affiliation = function () end;
16 config = config or {};
18 config = setmetatable(config, { __index = default_config });
23 events = events.new();
27 function service:jids_equal(jid1, jid2)
28 local normalize = self.config.normalize_jid;
29 return normalize(jid1) == normalize(jid2);
32 function service:may(node, actor, action)
33 if actor == true then return true; end
35 local node_obj = self.nodes[node];
36 local node_aff = node_obj and node_obj.affiliations[actor];
37 local service_aff = self.affiliations[actor]
38 or self.config.get_affiliation(actor, node, action)
41 -- Check if node allows/forbids it
42 local node_capabilities = node_obj and node_obj.capabilities;
43 if node_capabilities then
44 local caps = node_capabilities[node_aff or service_aff];
46 local can = caps[action];
53 -- Check service-wide capabilities instead
54 local service_capabilities = self.config.capabilities;
55 local caps = service_capabilities[node_aff or service_aff];
57 local can = caps[action];
66 function service:set_affiliation(node, actor, jid, affiliation)
68 if not self:may(node, actor, "set_affiliation") then
69 return false, "forbidden";
72 local node_obj = self.nodes[node];
74 return false, "item-not-found";
76 node_obj.affiliations[jid] = affiliation;
77 local _, jid_sub = self:get_subscription(node, true, jid);
78 if not jid_sub and not self:may(node, jid, "be_unsubscribed") then
79 local ok, err = self:add_subscription(node, true, jid);
83 elseif jid_sub and not self:may(node, jid, "be_subscribed") then
84 local ok, err = self:add_subscription(node, true, jid);
92 function service:add_subscription(node, actor, jid, options)
95 if actor == true or jid == actor or self:jids_equal(actor, jid) then
98 cap = "subscribe_other";
100 if not self:may(node, actor, cap) then
101 return false, "forbidden";
103 if not self:may(node, jid, "be_subscribed") then
104 return false, "forbidden";
107 local node_obj = self.nodes[node];
109 if not self.config.autocreate_on_subscribe then
110 return false, "item-not-found";
112 local ok, err = self:create(node, true);
116 node_obj = self.nodes[node];
119 node_obj.subscribers[jid] = options or true;
120 local normal_jid = self.config.normalize_jid(jid);
121 local subs = self.subscriptions[normal_jid];
123 if not subs[jid] then
124 subs[jid] = { [node] = true };
126 subs[jid][node] = true;
129 self.subscriptions[normal_jid] = { [jid] = { [node] = true } };
131 self.events.fire_event("subscription-added", { node = node, jid = jid, normalized_jid = normal_jid, options = options });
135 function service:remove_subscription(node, actor, jid)
138 if actor == true or jid == actor or self:jids_equal(actor, jid) then
141 cap = "unsubscribe_other";
143 if not self:may(node, actor, cap) then
144 return false, "forbidden";
146 if not self:may(node, jid, "be_unsubscribed") then
147 return false, "forbidden";
150 local node_obj = self.nodes[node];
152 return false, "item-not-found";
154 if not node_obj.subscribers[jid] then
155 return false, "not-subscribed";
157 node_obj.subscribers[jid] = nil;
158 local normal_jid = self.config.normalize_jid(jid);
159 local subs = self.subscriptions[normal_jid];
161 local jid_subs = subs[jid];
163 jid_subs[node] = nil;
164 if next(jid_subs) == nil then
168 if next(subs) == nil then
169 self.subscriptions[normal_jid] = nil;
172 self.events.fire_event("subscription-removed", { node = node, jid = jid, normalized_jid = normal_jid });
176 function service:remove_all_subscriptions(actor, jid)
177 local normal_jid = self.config.normalize_jid(jid);
178 local subs = self.subscriptions[normal_jid]
179 subs = subs and subs[jid];
181 for node in pairs(subs) do
182 self:remove_subscription(node, true, jid);
188 function service:get_subscription(node, actor, jid)
191 if actor == true or jid == actor or self:jids_equal(actor, jid) then
192 cap = "get_subscription";
194 cap = "get_subscription_other";
196 if not self:may(node, actor, cap) then
197 return false, "forbidden";
200 local node_obj = self.nodes[node];
202 return false, "item-not-found";
204 return true, node_obj.subscribers[jid];
207 function service:create(node, actor)
209 if not self:may(node, actor, "create") then
210 return false, "forbidden";
213 if self.nodes[node] then
214 return false, "conflict";
217 self.data[node] = {};
224 setmetatable(self.nodes[node], { __index = { data = self.data[node] } }); -- COMPAT
225 self.events.fire_event("node-created", { node = node, actor = actor });
226 local ok, err = self:set_affiliation(node, true, actor, "owner");
228 self.nodes[node] = nil;
229 self.data[node] = nil;
234 function service:delete(node, actor)
236 if not self:may(node, actor, "delete") then
237 return false, "forbidden";
240 local node_obj = self.nodes[node];
242 return false, "item-not-found";
244 self.nodes[node] = nil;
245 self.data[node] = nil;
246 self.events.fire_event("node-deleted", { node = node, actor = actor });
247 self.config.broadcaster("delete", node, node_obj.subscribers);
251 local function remove_item_by_id(data, id)
252 if not data[id] then return end
254 for i, _id in ipairs(data) do
262 function service:publish(node, actor, id, item)
264 if not self:may(node, actor, "publish") then
265 return false, "forbidden";
268 local node_obj = self.nodes[node];
270 if not self.config.autocreate_on_publish then
271 return false, "item-not-found";
273 local ok, err = self:create(node, true);
277 node_obj = self.nodes[node];
279 local node_data = self.data[node];
280 remove_item_by_id(node_data, id);
281 node_data[#self.data[node] + 1] = id;
282 node_data[id] = item;
283 self.events.fire_event("item-published", { node = node, actor = actor, id = id, item = item });
284 self.config.broadcaster("items", node, node_obj.subscribers, item);
288 function service:retract(node, actor, id, retract)
290 if not self:may(node, actor, "retract") then
291 return false, "forbidden";
294 local node_obj = self.nodes[node];
295 if (not node_obj) or (not self.data[node][id]) then
296 return false, "item-not-found";
298 self.events.fire_event("item-retracted", { node = node, actor = actor, id = id });
299 remove_item_by_id(self.data[node], id);
301 self.config.broadcaster("items", node, node_obj.subscribers, retract);
306 function service:purge(node, actor, notify)
308 if not self:may(node, actor, "retract") then
309 return false, "forbidden";
312 local node_obj = self.nodes[node];
314 return false, "item-not-found";
316 self.data[node] = {}; -- Purge
317 self.events.fire_event("node-purged", { node = node, actor = actor });
319 self.config.broadcaster("purge", node, node_obj.subscribers);
324 function service:get_items(node, actor, id)
326 if not self:may(node, actor, "get_items") then
327 return false, "forbidden";
330 local node_obj = self.nodes[node];
332 return false, "item-not-found";
334 if id then -- Restrict results to a single specific item
335 return true, { id, [id] = self.data[node][id] };
337 return true, self.data[node];
341 function service:get_nodes(actor)
343 if not self:may(nil, actor, "get_nodes") then
344 return false, "forbidden";
347 return true, self.nodes;
350 function service:get_subscriptions(node, actor, jid)
353 if actor == true or jid == actor or self:jids_equal(actor, jid) then
354 cap = "get_subscriptions";
356 cap = "get_subscriptions_other";
358 if not self:may(node, actor, cap) then
359 return false, "forbidden";
364 node_obj = self.nodes[node];
366 return false, "item-not-found";
369 local normal_jid = self.config.normalize_jid(jid);
370 local subs = self.subscriptions[normal_jid];
371 -- We return the subscription object from the node to save
372 -- a get_subscription() call for each node.
375 for jid, subscribed_nodes in pairs(subs) do
376 if node then -- Return only subscriptions to this node
377 if subscribed_nodes[node] then
381 subscription = node_obj.subscribers[jid];
384 else -- Return subscriptions to all nodes
385 local nodes = self.nodes;
386 for subscribed_node in pairs(subscribed_nodes) do
388 node = subscribed_node;
390 subscription = nodes[subscribed_node].subscribers[jid];
399 -- Access models only affect 'none' affiliation caps, service/default access level...
400 function service:set_node_capabilities(node, actor, capabilities)
402 if not self:may(node, actor, "configure") then
403 return false, "forbidden";
406 local node_obj = self.nodes[node];
408 return false, "item-not-found";
410 node_obj.capabilities = capabilities;