comparison mod_mam/mod_mam.lua @ 1116:2345a30dd8b4

mod_mam: Update to use 'archive' storage type. Note: this breaks support with 0.9 and older.
author Kim Alvefur <zash@zash.se>
date Sat, 13 Jul 2013 17:43:30 +0200
parents 6c0e1f9926f6
children 0d6ab5e4bc30
comparison
equal deleted inserted replaced
1115:91d210b6106a 1116:2345a30dd8b4
14 local jid_bare = require "util.jid".bare; 14 local jid_bare = require "util.jid".bare;
15 local jid_split = require "util.jid".split; 15 local jid_split = require "util.jid".split;
16 local jid_prep = require "util.jid".prep; 16 local jid_prep = require "util.jid".prep;
17 local host = module.host; 17 local host = module.host;
18 18
19 local dm_list_load = require "util.datamanager".list_load;
20 local dm_list_append = require "util.datamanager".list_append;
21 local rm_load_roster = require "core.rostermanager".load_roster; 19 local rm_load_roster = require "core.rostermanager".load_roster;
20
21 local getmetatable = getmetatable;
22 local function is_stanza(x)
23 return getmetatable(x) == st.stanza_mt;
24 end
22 25
23 local tostring = tostring; 26 local tostring = tostring;
24 local time_now = os.time; 27 local time_now = os.time;
25 local m_min = math.min; 28 local m_min = math.min;
26 local t_insert = table.insert;
27 local timestamp, timestamp_parse = require "util.datetime".datetime, require "util.datetime".parse; 29 local timestamp, timestamp_parse = require "util.datetime".datetime, require "util.datetime".parse;
28 local uuid = require "util.uuid".generate;
29 local default_max_items, max_max_items = 20, module:get_option_number("max_archive_query_results", 50); 30 local default_max_items, max_max_items = 20, module:get_option_number("max_archive_query_results", 50);
30 local global_default_policy = module:get_option("default_archive_policy", false); 31 local global_default_policy = module:get_option("default_archive_policy", false);
31 -- TODO Should be possible to enforce it too 32 -- TODO Should be possible to enforce it too
32
33 33
34 -- For translating preference names from string to boolean and back 34 -- For translating preference names from string to boolean and back
35 local default_attrs = { 35 local default_attrs = {
36 always = true, [true] = "always", 36 always = true, [true] = "always",
37 never = false, [false] = "never", 37 never = false, [false] = "never",
38 roster = "roster", 38 roster = "roster",
39 } 39 }
40 40
41
41 local archive_store = "archive2"; 42 local archive_store = "archive2";
43 local archive = module:open_store(archive_store, "archive");
42 44
43 -- Handle prefs. 45 -- Handle prefs.
44 module:hook("iq/self/"..xmlns_mam..":prefs", function(event) 46 module:hook("iq/self/"..xmlns_mam..":prefs", function(event)
45 local origin, stanza = event.origin, event.stanza; 47 local origin, stanza = event.origin, event.stanza;
46 local user = origin.username; 48 local user = origin.username;
101 103
102 -- Search query parameters 104 -- Search query parameters
103 local qwith = query:get_child_text("with"); 105 local qwith = query:get_child_text("with");
104 local qstart = query:get_child_text("start"); 106 local qstart = query:get_child_text("start");
105 local qend = query:get_child_text("end"); 107 local qend = query:get_child_text("end");
106 local qset = rsm.get(query);
107 module:log("debug", "Archive query, id %s with %s from %s until %s)", 108 module:log("debug", "Archive query, id %s with %s from %s until %s)",
108 tostring(qid), qwith or "anyone", qstart or "the dawn of time", qend or "now"); 109 tostring(qid), qwith or "anyone", qstart or "the dawn of time", qend or "now");
109 110
110 if qstart or qend then -- Validate timestamps 111 if qstart or qend then -- Validate timestamps
111 local vstart, vend = (qstart and timestamp_parse(qstart)), (qend and timestamp_parse(qend)) 112 local vstart, vend = (qstart and timestamp_parse(qstart)), (qend and timestamp_parse(qend))
114 return true 115 return true
115 end 116 end
116 qstart, qend = vstart, vend; 117 qstart, qend = vstart, vend;
117 end 118 end
118 119
119 local qres;
120 if qwith then -- Validate the 'with' jid 120 if qwith then -- Validate the 'with' jid
121 local pwith = qwith and jid_prep(qwith); 121 local pwith = qwith and jid_prep(qwith);
122 if pwith and not qwith then -- it failed prepping 122 if pwith and not qwith then -- it failed prepping
123 origin.send(st.error_reply(stanza, "modify", "bad-request", "Invalid JID")) 123 origin.send(st.error_reply(stanza, "modify", "bad-request", "Invalid JID"))
124 return true 124 return true
125 end 125 end
126 local _, _, resource = jid_split(qwith);
127 qwith = jid_bare(pwith); 126 qwith = jid_bare(pwith);
128 qres = resource; 127 end
129 end 128
129 -- RSM stuff
130 local qset = rsm.get(query);
131 local qmax = m_min(qset and qset.max or default_max_items, max_max_items);
132 local reverse = qset and qset.before or false;
133 local before, after = qset and qset.before, qset and qset.after;
134 if type(before) ~= "string" then before = nil; end
135
130 136
131 -- Load all the data! 137 -- Load all the data!
132 local data, err = dm_list_load(origin.username, origin.host, archive_store); 138 local data, err = archive:find(origin.username, {
139 start = qstart; ["end"] = qend; -- Time range
140 with = qwith;
141 limit = qmax;
142 before = before; after = after;
143 reverse = reverse;
144 total = true;
145 });
146
133 if not data then 147 if not data then
134 if (not err) then 148 return origin.send(st.error_reply(stanza, "cancel", "internal-server-error"));
135 module:log("debug", "The archive was empty."); 149 end
136 origin.send(st.reply(stanza)); 150 local count = err;
137 else 151
138 origin.send(st.error_reply(stanza, "cancel", "internal-server-error", "Error loading archive: "..tostring(err))); 152 -- Wrap it in stuff and deliver
139 end 153 local first, last;
140 return true 154 for id, item, when in data do
141 end 155 local fwd_st = st.message{ to = origin.full_jid }
142 156 :tag("result", { xmlns = xmlns_mam, queryid = qid, id = id })
143 -- RSM stuff 157 :tag("forwarded", { xmlns = xmlns_forward })
144 local qmax = m_min(qset and qset.max or default_max_items, max_max_items); 158 :tag("delay", { xmlns = xmlns_delay, stamp = timestamp(when) }):up();
145 local qset_matches = not (qset and qset.after); 159
146 local first, last, index; 160 if not is_stanza(item) then
147 local n = 0; 161 item = st.deserialize(item);
148 local start = qset and qset.index or 1; 162 end
149 local results = {}; 163 item.attr.xmlns = "jabber:client";
150 -- An empty <before/> means: give the last n items. So we loop backwards. 164 fwd_st:add_child(item);
151 local reverse = qset and qset.before or false; 165
152 166 if not first then first = id; end
153 module:log("debug", "Loaded %d items, about to filter", #data); 167 last = id;
154 for i=(reverse and #data or start),(reverse and start or #data),(reverse and -1 or 1) do 168
155 local item = data[i]; 169 origin.send(fwd_st);
156 local when, with, resource = item.when, item.with, item.resource;
157 local id = item.id;
158 --module:log("debug", "id is %s", id);
159
160 -- RSM pre-send-checking
161 if qset then
162 if qset.before == id then
163 module:log("debug", "End of matching range found");
164 qset_matches = false;
165 break;
166 end
167 end
168
169 --module:log("debug", "message with %s at %s", with, when or "???");
170 -- Apply query filter
171 if (not qwith or ((qwith == with) and (not qres or qres == resource)))
172 and (not qstart or when >= qstart)
173 and (not qend or when <= qend)
174 and (not qset or qset_matches) then
175 local fwd_st = st.message{ to = origin.full_jid }
176 :tag("result", { xmlns = xmlns_mam, queryid = qid, id = id })
177 :tag("forwarded", { xmlns = xmlns_forward })
178 :tag("delay", { xmlns = xmlns_delay, stamp = timestamp(when) }):up();
179 local orig_stanza = st.deserialize(item.stanza);
180 orig_stanza.attr.xmlns = "jabber:client";
181 fwd_st:add_child(orig_stanza);
182 if reverse then
183 t_insert(results, 1, fwd_st);
184 else
185 results[#results + 1] = fwd_st;
186 end
187 if not first then
188 index = i;
189 first = id;
190 end
191 last = id;
192 n = n + 1;
193 elseif (qend and when > qend) then
194 module:log("debug", "We have passed into messages more recent than requested");
195 break -- We have passed into messages more recent than requested
196 end
197
198 -- RSM post-send-checking
199 if qset then
200 if qset.after == id then
201 module:log("debug", "Start of matching range found");
202 qset_matches = true;
203 end
204 end
205 if n >= qmax then
206 module:log("debug", "Max number of items matched");
207 break
208 end
209 end
210 for _,v in pairs(results) do
211 origin.send(v);
212 end 170 end
213 -- That's all folks! 171 -- That's all folks!
214 module:log("debug", "Archive query %s completed", tostring(qid)); 172 module:log("debug", "Archive query %s completed", tostring(qid));
215 173
216 if reverse then first, last = last, first; end 174 if reverse then first, last = last, first; end
217 return origin.send(st.reply(stanza) 175 return origin.send(st.reply(stanza)
218 :query(xmlns_mam):add_child(rsm.generate { 176 :query(xmlns_mam):add_child(rsm.generate {
219 first = first, last = last, count = #data })); 177 first = first, last = last, count = count }));
220 end); 178 end);
221 179
222 local function has_in_roster(user, who) 180 local function has_in_roster(user, who)
223 local roster = rm_load_roster(user, host); 181 local roster = rm_load_roster(user, host);
224 module:log("debug", "%s has %s in roster? %s", user, who, roster[who] and "yes" or "no"); 182 module:log("debug", "%s has %s in roster? %s", user, who, roster[who] and "yes" or "no");
262 if orig_type == "error" 220 if orig_type == "error"
263 or orig_type == "headline" 221 or orig_type == "headline"
264 or orig_type == "groupchat" 222 or orig_type == "groupchat"
265 or not stanza:get_child("body") then 223 or not stanza:get_child("body") then
266 return; 224 return;
267 -- TODO Maybe headlines should be configurable? 225 end
268 -- TODO Write a mod_mam_muc for groupchat messages. 226
269 end 227 local store_user = jid_split(c2s and orig_from or orig_to);
270
271 local store_user, store_host = jid_split(c2s and orig_from or orig_to);
272 local target_jid = c2s and orig_to or orig_from; 228 local target_jid = c2s and orig_to or orig_from;
273 local target_bare = jid_bare(target_jid); 229 local target_bare = jid_bare(target_jid);
274 local _, _, target_resource = jid_split(target_jid);
275 230
276 if shall_store(store_user, target_bare) then 231 if shall_store(store_user, target_bare) then
277 module:log("debug", "Archiving stanza: %s", stanza:top_tag()); 232 module:log("debug", "Archiving stanza: %s", stanza:top_tag());
278 233
279 local id = uuid();
280 local when = time_now();
281 -- And stash it 234 -- And stash it
282 local ok, err = dm_list_append(store_user, store_host, archive_store, { 235 local ok, id = archive:append(store_user, time_now(), target_bare, stanza);
283 -- WARNING This format may change.
284 id = id,
285 when = when,
286 with = target_bare,
287 resource = target_resource,
288 stanza = st.preserialize(stanza)
289 });
290 if ok and not c2s then 236 if ok and not c2s then
291 stanza:tag("archived", { xmlns = xmlns_mam, by = jid_bare(orig_to), id = id }):up(); 237 stanza:tag("archived", { xmlns = xmlns_mam, by = jid_bare(orig_to), id = id }):up();
292 end 238 end
293 else 239 else
294 module:log("debug", "Not archiving stanza: %s", stanza:top_tag()); 240 module:log("debug", "Not archiving stanza: %s", stanza:top_tag());