comparison plugins/mod_muc_mam.lua @ 10434:8f709577fe8e

Merge 0.11->trunk
author Kim Alvefur <zash@zash.se>
date Sat, 23 Nov 2019 23:12:01 +0100
parents 360d574517b6
children 40c2500208f4
comparison
equal deleted inserted replaced
10433:7777f25d5266 10434:8f709577fe8e
2 -- Copyright (C) 2011-2017 Kim Alvefur 2 -- Copyright (C) 2011-2017 Kim Alvefur
3 -- 3 --
4 -- This file is MIT/X11 licensed. 4 -- This file is MIT/X11 licensed.
5 5
6 if module:get_host_type() ~= "component" then 6 if module:get_host_type() ~= "component" then
7 module:log("error", "mod_%s should be loaded only on a MUC component, not normal hosts", module.name); 7 module:log_status("error", "mod_%s should be loaded only on a MUC component, not normal hosts", module.name);
8 return; 8 return;
9 end 9 end
10 10
11 local xmlns_mam = "urn:xmpp:mam:2"; 11 local xmlns_mam = "urn:xmpp:mam:2";
12 local xmlns_delay = "urn:xmpp:delay"; 12 local xmlns_delay = "urn:xmpp:delay";
19 local rsm = require "util.rsm"; 19 local rsm = require "util.rsm";
20 local jid_bare = require "util.jid".bare; 20 local jid_bare = require "util.jid".bare;
21 local jid_split = require "util.jid".split; 21 local jid_split = require "util.jid".split;
22 local jid_prep = require "util.jid".prep; 22 local jid_prep = require "util.jid".prep;
23 local dataform = require "util.dataforms".new; 23 local dataform = require "util.dataforms".new;
24 local get_form_type = require "util.dataforms".get_type;
24 25
25 local mod_muc = module:depends"muc"; 26 local mod_muc = module:depends"muc";
26 local get_room_from_jid = mod_muc.get_room_from_jid; 27 local get_room_from_jid = mod_muc.get_room_from_jid;
27 28
28 local is_stanza = st.is_stanza; 29 local is_stanza = st.is_stanza;
30 local time_now = os.time; 31 local time_now = os.time;
31 local m_min = math.min; 32 local m_min = math.min;
32 local timestamp, timestamp_parse, datestamp = import( "util.datetime", "datetime", "parse", "date"); 33 local timestamp, timestamp_parse, datestamp = import( "util.datetime", "datetime", "parse", "date");
33 local default_max_items, max_max_items = 20, module:get_option_number("max_archive_query_results", 50); 34 local default_max_items, max_max_items = 20, module:get_option_number("max_archive_query_results", 50);
34 35
36 local cleanup_after = module:get_option_string("muc_log_expires_after", "1w");
37 local cleanup_interval = module:get_option_number("muc_log_cleanup_interval", 4 * 60 * 60);
38
35 local default_history_length = 20; 39 local default_history_length = 20;
36 local max_history_length = module:get_option_number("max_history_messages", math.huge); 40 local max_history_length = module:get_option_number("max_history_messages", math.huge);
37 41
38 local function get_historylength(room) 42 local function get_historylength(room)
39 return math.min(room._data.history_length or default_history_length, max_history_length); 43 return math.min(room._data.history_length or default_history_length, max_history_length);
46 local log_all_rooms = module:get_option_boolean("muc_log_all_rooms", false); 50 local log_all_rooms = module:get_option_boolean("muc_log_all_rooms", false);
47 local log_by_default = module:get_option_boolean("muc_log_by_default", true); 51 local log_by_default = module:get_option_boolean("muc_log_by_default", true);
48 52
49 local archive_store = "muc_log"; 53 local archive_store = "muc_log";
50 local archive = module:open_store(archive_store, "archive"); 54 local archive = module:open_store(archive_store, "archive");
55
56 local archive_item_limit = module:get_option_number("storage_archive_item_limit", archive.caps and archive.caps.quota or 1000);
51 57
52 if archive.name == "null" or not archive.find then 58 if archive.name == "null" or not archive.find then
53 if not archive.find then 59 if not archive.find then
54 module:log("error", "Attempt to open archive storage returned a driver without archive API support"); 60 module:log("error", "Attempt to open archive storage returned a driver without archive API support");
55 module:log("error", "mod_%s does not support archiving", 61 module:log("error", "mod_%s does not support archiving",
61 return false; 67 return false;
62 end 68 end
63 69
64 local function archiving_enabled(room) 70 local function archiving_enabled(room)
65 if log_all_rooms then 71 if log_all_rooms then
72 module:log("debug", "Archiving all rooms");
66 return true; 73 return true;
67 end 74 end
68 local enabled = room._data.archiving; 75 local enabled = room._data.archiving;
69 if enabled == nil then 76 if enabled == nil then
77 module:log("debug", "Default is %s (for %s)", log_by_default, room.jid);
70 return log_by_default; 78 return log_by_default;
71 end 79 end
80 module:log("debug", "Logging in room %s is %s", room.jid, enabled);
72 return enabled; 81 return enabled;
73 end 82 end
74 83
75 if not log_all_rooms then 84 if not log_all_rooms then
76 module:hook("muc-config-form", function(event) 85 module:hook("muc-config-form", function(event)
133 142
134 -- Search query parameters 143 -- Search query parameters
135 local qstart, qend; 144 local qstart, qend;
136 local form = query:get_child("x", "jabber:x:data"); 145 local form = query:get_child("x", "jabber:x:data");
137 if form then 146 if form then
138 local err; 147 local form_type, err = get_form_type(form);
148 if form_type ~= xmlns_mam then
149 origin.send(st.error_reply(stanza, "modify", "bad-request", "Unexpected FORM_TYPE, expected '"..xmlns_mam.."'"));
150 return true;
151 end
139 form, err = query_form:data(form); 152 form, err = query_form:data(form);
140 if err then 153 if err then
141 origin.send(st.error_reply(stanza, "modify", "bad-request", select(2, next(err)))); 154 origin.send(st.error_reply(stanza, "modify", "bad-request", select(2, next(err))));
142 return true; 155 return true;
143 end 156 end
151 return true; 164 return true;
152 end 165 end
153 qstart, qend = vstart, vend; 166 qstart, qend = vstart, vend;
154 end 167 end
155 168
156 module:log("debug", "Archive query id %s from %s until %s)", 169 module:log("debug", "Archive query by %s id=%s when=%s...%s",
157 tostring(qid), 170 origin.username,
158 qstart and timestamp(qstart) or "the dawn of time", 171 qid or stanza.attr.id,
159 qend and timestamp(qend) or "now"); 172 qstart and timestamp(qstart) or "",
173 qend and timestamp(qend) or "");
160 174
161 -- RSM stuff 175 -- RSM stuff
162 local qset = rsm.get(query); 176 local qset = rsm.get(query);
163 local qmax = m_min(qset and qset.max or default_max_items, max_max_items); 177 local qmax = m_min(qset and qset.max or default_max_items, max_max_items);
164 local reverse = qset and qset.before or false; 178 local reverse = qset and qset.before or false;
165 179
166 local before, after = qset and qset.before, qset and qset.after; 180 local before, after = qset and qset.before, qset and qset.after;
167 if type(before) ~= "string" then before = nil; end 181 if type(before) ~= "string" then before = nil; end
182 if qset then
183 module:log("debug", "Archive query id=%s rsm=%q", qid or stanza.attr.id, qset);
184 end
168 185
169 -- Load all the data! 186 -- Load all the data!
170 local data, err = archive:find(room_node, { 187 local data, err = archive:find(room_node, {
171 start = qstart; ["end"] = qend; -- Time range 188 start = qstart; ["end"] = qend; -- Time range
172 limit = qmax + 1; 189 limit = qmax + 1;
174 reverse = reverse; 191 reverse = reverse;
175 with = "message<groupchat"; 192 with = "message<groupchat";
176 }); 193 });
177 194
178 if not data then 195 if not data then
179 origin.send(st.error_reply(stanza, "cancel", "internal-server-error")); 196 module:log("debug", "Archive query id=%s failed: %s", qid or stanza.attr.id, err);
197 if err == "item-not-found" then
198 origin.send(st.error_reply(stanza, "modify", "item-not-found"));
199 else
200 origin.send(st.error_reply(stanza, "cancel", "internal-server-error"));
201 end
180 return true; 202 return true;
181 end 203 end
182 local total = tonumber(err); 204 local total = tonumber(err);
183 205
184 local msg_reply_attr = { to = stanza.attr.from, from = stanza.attr.to }; 206 local msg_reply_attr = { to = stanza.attr.from, from = stanza.attr.to };
231 origin.send(results[i]); 253 origin.send(results[i]);
232 end 254 end
233 first, last = last, first; 255 first, last = last, first;
234 end 256 end
235 257
236 -- That's all folks!
237 module:log("debug", "Archive query %s completed", tostring(qid));
238 258
239 origin.send(st.reply(stanza) 259 origin.send(st.reply(stanza)
240 :tag("fin", { xmlns = xmlns_mam, queryid = qid, complete = complete }) 260 :tag("fin", { xmlns = xmlns_mam, queryid = qid, complete = complete })
241 :add_child(rsm.generate { 261 :add_child(rsm.generate {
242 first = first, last = last, count = total })); 262 first = first, last = last, count = total }));
263
264 -- That's all folks!
265 module:log("debug", "Archive query id=%s completed, %d items returned", qid or stanza.attr.id, complete and count or count - 1);
243 return true; 266 return true;
244 end); 267 end);
245 268
246 module:hook("muc-get-history", function (event) 269 module:hook("muc-get-history", function (event)
247 local room = event.room; 270 local room = event.room;
272 with = "message<groupchat"; 295 with = "message<groupchat";
273 } 296 }
274 local data, err = archive:find(jid_split(room_jid), query); 297 local data, err = archive:find(jid_split(room_jid), query);
275 298
276 if not data then 299 if not data then
277 module:log("error", "Could not fetch history: %s", tostring(err)); 300 module:log("error", "Could not fetch history: %s", err);
278 return 301 return
279 end 302 end
280 303
281 local history, i = {}, 1; 304 local history, i = {}, 1;
282 305
298 break 321 break
299 end 322 end
300 maxchars = maxchars - chars; 323 maxchars = maxchars - chars;
301 end 324 end
302 history[i], i = item, i+1; 325 history[i], i = item, i+1;
303 -- module:log("debug", tostring(item)); 326 -- module:log("debug", item);
304 end 327 end
305 function event.next_stanza() 328 function event.next_stanza()
306 i = i - 1; 329 i = i - 1;
307 return history[i]; 330 return history[i];
308 end 331 end
350 if stanza.attr.type then 373 if stanza.attr.type then
351 with = with .. "<" .. stanza.attr.type 374 with = with .. "<" .. stanza.attr.type
352 end 375 end
353 376
354 -- And stash it 377 -- And stash it
355 local id = archive:append(room_node, nil, stored_stanza, time_now(), with); 378 local time = time_now();
379 local id, err = archive:append(room_node, nil, stored_stanza, time, with);
380
381 if not id and err == "quota-limit" then
382 if type(cleanup_after) == "number" then
383 module:log("debug", "Room '%s' over quota, cleaning archive", room_node);
384 local cleaned = archive:delete(room_node, {
385 ["end"] = (os.time() - cleanup_after);
386 });
387 if cleaned then
388 id, err = archive:append(room_node, nil, stored_stanza, time, with);
389 end
390 end
391 if not id and (archive.caps and archive.caps.truncate) then
392 module:log("debug", "User '%s' over quota, truncating archive", room_node);
393 local truncated = archive:delete(room_node, {
394 truncate = archive_item_limit - 1;
395 });
396 if truncated then
397 id, err = archive:append(room_node, nil, stored_stanza, time, with);
398 end
399 end
400 end
356 401
357 if id then 402 if id then
358 schedule_cleanup(room_node); 403 schedule_cleanup(room_node);
359 stanza:add_direct_child(st.stanza("stanza-id", { xmlns = xmlns_st_id, by = self.jid, id = id })); 404 stanza:add_direct_child(st.stanza("stanza-id", { xmlns = xmlns_st_id, by = self.jid, id = id }));
360 end 405 end
387 -- And role/affiliation changes? 432 -- And role/affiliation changes?
388 433
389 module:add_feature(xmlns_mam); 434 module:add_feature(xmlns_mam);
390 435
391 module:hook("muc-disco#info", function(event) 436 module:hook("muc-disco#info", function(event)
392 event.reply:tag("feature", {var=xmlns_mam}):up(); 437 if archiving_enabled(event.room) then
438 event.reply:tag("feature", {var=xmlns_mam}):up();
439 end
393 end); 440 end);
394 441
395 -- Cleanup 442 -- Cleanup
396
397 local cleanup_after = module:get_option_string("muc_log_expires_after", "1w");
398 local cleanup_interval = module:get_option_number("muc_log_cleanup_interval", 4 * 60 * 60);
399 443
400 if cleanup_after ~= "never" then 444 if cleanup_after ~= "never" then
401 local cleanup_storage = module:open_store("muc_log_cleanup"); 445 local cleanup_storage = module:open_store("muc_log_cleanup");
402 local cleanup_map = module:open_store("muc_log_cleanup", "map"); 446 local cleanup_map = module:open_store("muc_log_cleanup", "map");
403 447