rostermanager: Use storagemanager
[prosody.git] / core / rostermanager.lua
1 -- Prosody IM
2 -- Copyright (C) 2008-2010 Matthew Wild
3 -- Copyright (C) 2008-2010 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
10
11
12 local log = require "util.logger".init("rostermanager");
13
14 local pairs = pairs;
15 local tostring = tostring;
16 local type = type;
17
18 local hosts = hosts;
19 local bare_sessions = prosody.bare_sessions;
20
21 local um_user_exists = require "core.usermanager".user_exists;
22 local st = require "util.stanza";
23
24 local _ENV = nil;
25
26 local save_roster; -- forward declaration
27
28 local function add_to_roster(session, jid, item)
29         if session.roster then
30                 local old_item = session.roster[jid];
31                 session.roster[jid] = item;
32                 if save_roster(session.username, session.host) then
33                         return true;
34                 else
35                         session.roster[jid] = old_item;
36                         return nil, "wait", "internal-server-error", "Unable to save roster";
37                 end
38         else
39                 return nil, "auth", "not-authorized", "Session's roster not loaded";
40         end
41 end
42
43 local function remove_from_roster(session, jid)
44         if session.roster then
45                 local old_item = session.roster[jid];
46                 session.roster[jid] = nil;
47                 if save_roster(session.username, session.host) then
48                         return true;
49                 else
50                         session.roster[jid] = old_item;
51                         return nil, "wait", "internal-server-error", "Unable to save roster";
52                 end
53         else
54                 return nil, "auth", "not-authorized", "Session's roster not loaded";
55         end
56 end
57
58 local function roster_push(username, host, jid)
59         local roster = jid and hosts[host] and hosts[host].sessions[username] and hosts[host].sessions[username].roster;
60         if roster then
61                 local item = hosts[host].sessions[username].roster[jid];
62                 local stanza = st.iq({type="set"});
63                 stanza:tag("query", {xmlns = "jabber:iq:roster", ver = tostring(roster[false].version or "1")  });
64                 if item then
65                         stanza:tag("item", {jid = jid, subscription = item.subscription, name = item.name, ask = item.ask});
66                         for group in pairs(item.groups) do
67                                 stanza:tag("group"):text(group):up();
68                         end
69                 else
70                         stanza:tag("item", {jid = jid, subscription = "remove"});
71                 end
72                 stanza:up(); -- move out from item
73                 stanza:up(); -- move out from stanza
74                 -- stanza ready
75                 for _, session in pairs(hosts[host].sessions[username].sessions) do
76                         if session.interested then
77                                 -- FIXME do we need to set stanza.attr.to?
78                                 session.send(stanza);
79                         end
80                 end
81         end
82 end
83
84 local function roster_metadata(roster, err)
85         local metadata = roster[false];
86         if not metadata then
87                 metadata = { broken = err or nil };
88                 roster[false] = metadata;
89         end
90         if roster.pending and type(roster.pending.subscription) ~= "string" then
91                 metadata.pending = roster.pending;
92                 roster.pending = nil;
93         elseif not metadata.pending then
94                 metadata.pending = {};
95         end
96         return metadata;
97 end
98
99 local function load_roster(username, host)
100         local jid = username.."@"..host;
101         log("debug", "load_roster: asked for: %s", jid);
102         local user = bare_sessions[jid];
103         local roster;
104         if user then
105                 roster = user.roster;
106                 if roster then return roster; end
107                 log("debug", "load_roster: loading for new user: %s@%s", username, host);
108         else -- Attempt to load roster for non-loaded user
109                 log("debug", "load_roster: loading for offline user: %s@%s", username, host);
110         end
111         local roster_store = require "core.storagemanager".open(host, "roster", "keyval");
112         local data, err = roster_store:get(username);
113         roster = data or {};
114         if user then user.roster = roster; end
115         roster_metadata(roster, err);
116         if roster[jid] then
117                 roster[jid] = nil;
118                 log("warn", "roster for %s has a self-contact", jid);
119         end
120         if not err then
121                 hosts[host].events.fire_event("roster-load", { username = username, host = host, roster = roster });
122         end
123         return roster, err;
124 end
125
126 function save_roster(username, host, roster)
127         if not um_user_exists(username, host) then
128                 log("debug", "not saving roster for %s@%s: the user doesn't exist", username, host);
129                 return nil;
130         end
131
132         log("debug", "save_roster: saving roster for %s@%s", username, host);
133         if not roster then
134                 roster = hosts[host] and hosts[host].sessions[username] and hosts[host].sessions[username].roster;
135                 --if not roster then
136                 --      --roster = load_roster(username, host);
137                 --      return true; -- roster unchanged, no reason to save
138                 --end
139         end
140         if roster then
141                 local metadata = roster_metadata(roster);
142                 if metadata.version ~= true then
143                         metadata.version = (metadata.version or 0) + 1;
144                 end
145                 if metadata.broken then return nil, "Not saving broken roster" end
146                 local roster_store = require "core.storagemanager".open(host, "roster", "keyval");
147                 return roster_store:set(username, roster);
148         end
149         log("warn", "save_roster: user had no roster to save");
150         return nil;
151 end
152
153 local function process_inbound_subscription_approval(username, host, jid)
154         local roster = load_roster(username, host);
155         local item = roster[jid];
156         if item and item.ask then
157                 if item.subscription == "none" then
158                         item.subscription = "to";
159                 else -- subscription == from
160                         item.subscription = "both";
161                 end
162                 item.ask = nil;
163                 return save_roster(username, host, roster);
164         end
165 end
166
167 local is_contact_pending_out -- forward declaration
168
169 local function process_inbound_subscription_cancellation(username, host, jid)
170         local roster = load_roster(username, host);
171         local item = roster[jid];
172         local changed = nil;
173         if is_contact_pending_out(username, host, jid) then
174                 item.ask = nil;
175                 changed = true;
176         end
177         if item then
178                 if item.subscription == "to" then
179                         item.subscription = "none";
180                         changed = true;
181                 elseif item.subscription == "both" then
182                         item.subscription = "from";
183                         changed = true;
184                 end
185         end
186         if changed then
187                 return save_roster(username, host, roster);
188         end
189 end
190
191 local is_contact_pending_in -- forward declaration
192
193 local function process_inbound_unsubscribe(username, host, jid)
194         local roster = load_roster(username, host);
195         local item = roster[jid];
196         local changed = nil;
197         if is_contact_pending_in(username, host, jid) then
198                 roster[false].pending[jid] = nil;
199                 changed = true;
200         end
201         if item then
202                 if item.subscription == "from" then
203                         item.subscription = "none";
204                         changed = true;
205                 elseif item.subscription == "both" then
206                         item.subscription = "to";
207                         changed = true;
208                 end
209         end
210         if changed then
211                 return save_roster(username, host, roster);
212         end
213 end
214
215 local function _get_online_roster_subscription(jidA, jidB)
216         local user = bare_sessions[jidA];
217         local item = user and (user.roster[jidB] or { subscription = "none" });
218         return item and item.subscription;
219 end
220 local function is_contact_subscribed(username, host, jid)
221         do
222                 local selfjid = username.."@"..host;
223                 local user_subscription = _get_online_roster_subscription(selfjid, jid);
224                 if user_subscription then return (user_subscription == "both" or user_subscription == "from"); end
225                 local contact_subscription = _get_online_roster_subscription(jid, selfjid);
226                 if contact_subscription then return (contact_subscription == "both" or contact_subscription == "to"); end
227         end
228         local roster, err = load_roster(username, host);
229         local item = roster[jid];
230         return item and (item.subscription == "from" or item.subscription == "both"), err;
231 end
232
233 function is_contact_pending_in(username, host, jid)
234         local roster = load_roster(username, host);
235         return roster[false].pending[jid];
236 end
237 local function set_contact_pending_in(username, host, jid)
238         local roster = load_roster(username, host);
239         local item = roster[jid];
240         if item and (item.subscription == "from" or item.subscription == "both") then
241                 return; -- false
242         end
243         roster[false].pending[jid] = true;
244         return save_roster(username, host, roster);
245 end
246 function is_contact_pending_out(username, host, jid)
247         local roster = load_roster(username, host);
248         local item = roster[jid];
249         return item and item.ask;
250 end
251 local function set_contact_pending_out(username, host, jid) -- subscribe
252         local roster = load_roster(username, host);
253         local item = roster[jid];
254         if item and (item.ask or item.subscription == "to" or item.subscription == "both") then
255                 return true;
256         end
257         if not item then
258                 item = {subscription = "none", groups = {}};
259                 roster[jid] = item;
260         end
261         item.ask = "subscribe";
262         log("debug", "set_contact_pending_out: saving roster; set %s@%s.roster[%q].ask=subscribe", username, host, jid);
263         return save_roster(username, host, roster);
264 end
265 local function unsubscribe(username, host, jid)
266         local roster = load_roster(username, host);
267         local item = roster[jid];
268         if not item then return false; end
269         if (item.subscription == "from" or item.subscription == "none") and not item.ask then
270                 return true;
271         end
272         item.ask = nil;
273         if item.subscription == "both" then
274                 item.subscription = "from";
275         elseif item.subscription == "to" then
276                 item.subscription = "none";
277         end
278         return save_roster(username, host, roster);
279 end
280 local function subscribed(username, host, jid)
281         if is_contact_pending_in(username, host, jid) then
282                 local roster = load_roster(username, host);
283                 local item = roster[jid];
284                 if not item then -- FIXME should roster item be auto-created?
285                         item = {subscription = "none", groups = {}};
286                         roster[jid] = item;
287                 end
288                 if item.subscription == "none" then
289                         item.subscription = "from";
290                 else -- subscription == to
291                         item.subscription = "both";
292                 end
293                 roster[false].pending[jid] = nil;
294                 return save_roster(username, host, roster);
295         end -- TODO else implement optional feature pre-approval (ask = subscribed)
296 end
297 local function unsubscribed(username, host, jid)
298         local roster = load_roster(username, host);
299         local item = roster[jid];
300         local pending = is_contact_pending_in(username, host, jid);
301         if pending then
302                 roster[false].pending[jid] = nil;
303         end
304         local is_subscribed;
305         if item then
306                 if item.subscription == "from" then
307                         item.subscription = "none";
308                         is_subscribed = true;
309                 elseif item.subscription == "both" then
310                         item.subscription = "to";
311                         is_subscribed = true;
312                 end
313         end
314         local success = (pending or is_subscribed) and save_roster(username, host, roster);
315         return success, pending, subscribed;
316 end
317
318 local function process_outbound_subscription_request(username, host, jid)
319         local roster = load_roster(username, host);
320         local item = roster[jid];
321         if item and (item.subscription == "none" or item.subscription == "from") then
322                 item.ask = "subscribe";
323                 return save_roster(username, host, roster);
324         end
325 end
326
327 --[[function process_outbound_subscription_approval(username, host, jid)
328         local roster = load_roster(username, host);
329         local item = roster[jid];
330         if item and (item.subscription == "none" or item.subscription == "from" then
331                 item.ask = "subscribe";
332                 return save_roster(username, host, roster);
333         end
334 end]]
335
336
337
338 return {
339         add_to_roster = add_to_roster;
340         remove_from_roster = remove_from_roster;
341         roster_push = roster_push;
342         load_roster = load_roster;
343         save_roster = save_roster;
344         process_inbound_subscription_approval = process_inbound_subscription_approval;
345         process_inbound_subscription_cancellation = process_inbound_subscription_cancellation;
346         process_inbound_unsubscribe = process_inbound_unsubscribe;
347         is_contact_subscribed = is_contact_subscribed;
348         is_contact_pending_in = is_contact_pending_in;
349         set_contact_pending_in = set_contact_pending_in;
350         is_contact_pending_out = is_contact_pending_out;
351         set_contact_pending_out = set_contact_pending_out;
352         unsubscribe = unsubscribe;
353         subscribed = subscribed;
354         unsubscribed = unsubscribed;
355         process_outbound_subscription_request = process_outbound_subscription_request;
356 };