1 local events = require "util.events";
2 local t_remove = table.remove;
5 local service_mt = { __index = service };
7 local default_config = { __index = {
8 broadcaster = function () end;
9 get_affiliation = function () end;
12 local default_node_config = { __index = {
13 ["pubsub#max_items"] = "20";
16 local function new(config)
17 config = config or {};
19 config = setmetatable(config, default_config);
20 node_defaults = setmetatable(config.node_defaults or {}, default_node_config);
25 events = events.new();
29 function service:jids_equal(jid1, jid2)
30 local normalize = self.config.normalize_jid;
31 return normalize(jid1) == normalize(jid2);
34 function service:may(node, actor, action)
35 if actor == true then return true; end
37 local node_obj = self.nodes[node];
38 local node_aff = node_obj and node_obj.affiliations[actor];
39 local service_aff = self.affiliations[actor]
40 or self.config.get_affiliation(actor, node, action)
43 -- Check if node allows/forbids it
44 local node_capabilities = node_obj and node_obj.capabilities;
45 if node_capabilities then
46 local caps = node_capabilities[node_aff or service_aff];
48 local can = caps[action];
55 -- Check service-wide capabilities instead
56 local service_capabilities = self.config.capabilities;
57 local caps = service_capabilities[node_aff or service_aff];
59 local can = caps[action];
68 function service:set_affiliation(node, actor, jid, affiliation)
70 if not self:may(node, actor, "set_affiliation") then
71 return false, "forbidden";
74 local node_obj = self.nodes[node];
76 return false, "item-not-found";
78 node_obj.affiliations[jid] = affiliation;
79 local _, jid_sub = self:get_subscription(node, true, jid);
80 if not jid_sub and not self:may(node, jid, "be_unsubscribed") then
81 local ok, err = self:add_subscription(node, true, jid);
85 elseif jid_sub and not self:may(node, jid, "be_subscribed") then
86 local ok, err = self:add_subscription(node, true, jid);
94 function service:add_subscription(node, actor, jid, options)
97 if actor == true or jid == actor or self:jids_equal(actor, jid) then
100 cap = "subscribe_other";
102 if not self:may(node, actor, cap) then
103 return false, "forbidden";
105 if not self:may(node, jid, "be_subscribed") then
106 return false, "forbidden";
109 local node_obj = self.nodes[node];
111 if not self.config.autocreate_on_subscribe then
112 return false, "item-not-found";
114 local ok, err = self:create(node, true);
118 node_obj = self.nodes[node];
121 node_obj.subscribers[jid] = options or true;
122 local normal_jid = self.config.normalize_jid(jid);
123 local subs = self.subscriptions[normal_jid];
125 if not subs[jid] then
126 subs[jid] = { [node] = true };
128 subs[jid][node] = true;
131 self.subscriptions[normal_jid] = { [jid] = { [node] = true } };
133 self.events.fire_event("subscription-added", { node = node, jid = jid, normalized_jid = normal_jid, options = options });
137 function service:remove_subscription(node, actor, jid)
140 if actor == true or jid == actor or self:jids_equal(actor, jid) then
143 cap = "unsubscribe_other";
145 if not self:may(node, actor, cap) then
146 return false, "forbidden";
148 if not self:may(node, jid, "be_unsubscribed") then
149 return false, "forbidden";
152 local node_obj = self.nodes[node];
154 return false, "item-not-found";
156 if not node_obj.subscribers[jid] then
157 return false, "not-subscribed";
159 node_obj.subscribers[jid] = nil;
160 local normal_jid = self.config.normalize_jid(jid);
161 local subs = self.subscriptions[normal_jid];
163 local jid_subs = subs[jid];
165 jid_subs[node] = nil;
166 if next(jid_subs) == nil then
170 if next(subs) == nil then
171 self.subscriptions[normal_jid] = nil;
174 self.events.fire_event("subscription-removed", { node = node, jid = jid, normalized_jid = normal_jid });
178 function service:remove_all_subscriptions(actor, jid)
179 local normal_jid = self.config.normalize_jid(jid);
180 local subs = self.subscriptions[normal_jid]
181 subs = subs and subs[jid];
183 for node in pairs(subs) do
184 self:remove_subscription(node, true, jid);
190 function service:get_subscription(node, actor, jid)
193 if actor == true or jid == actor or self:jids_equal(actor, jid) then
194 cap = "get_subscription";
196 cap = "get_subscription_other";
198 if not self:may(node, actor, cap) then
199 return false, "forbidden";
202 local node_obj = self.nodes[node];
204 return false, "item-not-found";
206 return true, node_obj.subscribers[jid];
209 function service:create(node, actor, options)
211 if not self:may(node, actor, "create") then
212 return false, "forbidden";
215 if self.nodes[node] then
216 return false, "conflict";
219 self.data[node] = {};
223 config = setmetatable(options or {}, {__index=self.node_defaults});
226 setmetatable(self.nodes[node], { __index = { data = self.data[node] } }); -- COMPAT
227 self.events.fire_event("node-created", { node = node, actor = actor });
228 local ok, err = self:set_affiliation(node, true, actor, "owner");
230 self.nodes[node] = nil;
231 self.data[node] = nil;
236 function service:delete(node, actor)
238 if not self:may(node, actor, "delete") then
239 return false, "forbidden";
242 local node_obj = self.nodes[node];
244 return false, "item-not-found";
246 self.nodes[node] = nil;
247 self.data[node] = nil;
248 self.events.fire_event("node-deleted", { node = node, actor = actor });
249 self.config.broadcaster("delete", node, node_obj.subscribers);
253 local function remove_item_by_id(data, id)
254 if not data[id] then return end
256 for i, _id in ipairs(data) do
264 local function trim_items(data, max)
266 if not max or #data <= max then return end
268 data[t_remove(data, 1)] = nil;
272 function service:publish(node, actor, id, item)
274 if not self:may(node, actor, "publish") then
275 return false, "forbidden";
278 local node_obj = self.nodes[node];
280 if not self.config.autocreate_on_publish then
281 return false, "item-not-found";
283 local ok, err = self:create(node, true);
287 node_obj = self.nodes[node];
289 local node_data = self.data[node];
290 remove_item_by_id(node_data, id);
291 node_data[#node_data + 1] = id;
292 node_data[id] = item;
293 trim_items(node_data, node_obj.config["pubsub#max_items"]);
294 self.events.fire_event("item-published", { node = node, actor = actor, id = id, item = item });
295 self.config.broadcaster("items", node, node_obj.subscribers, item, actor);
299 function service:retract(node, actor, id, retract)
301 if not self:may(node, actor, "retract") then
302 return false, "forbidden";
305 local node_obj = self.nodes[node];
306 if (not node_obj) or (not self.data[node][id]) then
307 return false, "item-not-found";
309 self.events.fire_event("item-retracted", { node = node, actor = actor, id = id });
310 remove_item_by_id(self.data[node], id);
312 self.config.broadcaster("items", node, node_obj.subscribers, retract);
317 function service:purge(node, actor, notify)
319 if not self:may(node, actor, "retract") then
320 return false, "forbidden";
323 local node_obj = self.nodes[node];
325 return false, "item-not-found";
327 self.data[node] = {}; -- Purge
328 self.events.fire_event("node-purged", { node = node, actor = actor });
330 self.config.broadcaster("purge", node, node_obj.subscribers);
335 function service:get_items(node, actor, id)
337 if not self:may(node, actor, "get_items") then
338 return false, "forbidden";
341 local node_obj = self.nodes[node];
343 return false, "item-not-found";
345 if id then -- Restrict results to a single specific item
346 return true, { id, [id] = self.data[node][id] };
348 return true, self.data[node];
352 function service:get_nodes(actor)
354 if not self:may(nil, actor, "get_nodes") then
355 return false, "forbidden";
358 return true, self.nodes;
361 function service:get_subscriptions(node, actor, jid)
364 if actor == true or jid == actor or self:jids_equal(actor, jid) then
365 cap = "get_subscriptions";
367 cap = "get_subscriptions_other";
369 if not self:may(node, actor, cap) then
370 return false, "forbidden";
375 node_obj = self.nodes[node];
377 return false, "item-not-found";
380 local normal_jid = self.config.normalize_jid(jid);
381 local subs = self.subscriptions[normal_jid];
382 -- We return the subscription object from the node to save
383 -- a get_subscription() call for each node.
386 for jid, subscribed_nodes in pairs(subs) do
387 if node then -- Return only subscriptions to this node
388 if subscribed_nodes[node] then
392 subscription = node_obj.subscribers[jid];
395 else -- Return subscriptions to all nodes
396 local nodes = self.nodes;
397 for subscribed_node in pairs(subscribed_nodes) do
399 node = subscribed_node;
401 subscription = nodes[subscribed_node].subscribers[jid];
410 -- Access models only affect 'none' affiliation caps, service/default access level...
411 function service:set_node_capabilities(node, actor, capabilities)
413 if not self:may(node, actor, "configure") then
414 return false, "forbidden";
417 local node_obj = self.nodes[node];
419 return false, "item-not-found";
421 node_obj.capabilities = capabilities;
425 function service:set_node_config(node, actor, new_config)
426 if not self:may(node, actor, "configure") then
427 return false, "forbidden";
430 local node_obj = self.nodes[node];
432 return false, "item-not-found";
435 for k,v in pairs(new_config) do
436 node_obj.config[k] = v;
438 trim_items(self.data[node], node_obj.config["pubsub#max_items"]);