Comparison

plugins/mod_mam/mod_mam.lua @ 10563:e8db377a2983

Merge 0.11->trunk
author Kim Alvefur <zash@zash.se>
date Tue, 24 Dec 2019 00:39:45 +0100
parent 10559:cfc05e46b979
parent 10525:9cf7d9761ca2
child 10568:b6f93babebe8
comparison
equal deleted inserted replaced
10562:670afc079f68 10563:e8db377a2983
23 local prefs_from_stanza = module:require"mamprefsxml".fromstanza; 23 local prefs_from_stanza = module:require"mamprefsxml".fromstanza;
24 local jid_bare = require "util.jid".bare; 24 local jid_bare = require "util.jid".bare;
25 local jid_split = require "util.jid".split; 25 local jid_split = require "util.jid".split;
26 local jid_prepped_split = require "util.jid".prepped_split; 26 local jid_prepped_split = require "util.jid".prepped_split;
27 local dataform = require "util.dataforms".new; 27 local dataform = require "util.dataforms".new;
28 local get_form_type = require "util.dataforms".get_type;
28 local host = module.host; 29 local host = module.host;
29 30
30 local rm_load_roster = require "core.rostermanager".load_roster; 31 local rm_load_roster = require "core.rostermanager".load_roster;
31 32
32 local is_stanza = st.is_stanza; 33 local is_stanza = st.is_stanza;
38 local strip_tags = module:get_option_set("dont_archive_namespaces", { "http://jabber.org/protocol/chatstates" }); 39 local strip_tags = module:get_option_set("dont_archive_namespaces", { "http://jabber.org/protocol/chatstates" });
39 40
40 local archive_store = module:get_option_string("archive_store", "archive"); 41 local archive_store = module:get_option_string("archive_store", "archive");
41 local archive = module:open_store(archive_store, "archive"); 42 local archive = module:open_store(archive_store, "archive");
42 43
44 local cleanup_after = module:get_option_string("archive_expires_after", "1w");
45 local cleanup_interval = module:get_option_number("archive_cleanup_interval", 4 * 60 * 60);
46 local archive_item_limit = module:get_option_number("storage_archive_item_limit", archive.caps and archive.caps.quota or 1000);
43 if not archive.find then 47 if not archive.find then
44 error("mod_"..(archive._provided_by or archive.name and "storage_"..archive.name).." does not support archiving\n" 48 error("mod_"..(archive._provided_by or archive.name and "storage_"..archive.name).." does not support archiving\n"
45 .."See https://prosody.im/doc/storage and https://prosody.im/doc/archiving for more information"); 49 .."See https://prosody.im/doc/storage and https://prosody.im/doc/archiving for more information");
46 end 50 end
47 local use_total = module:get_option_boolean("mam_include_total", true); 51 local use_total = module:get_option_boolean("mam_include_total", true);
96 100
97 -- Search query parameters 101 -- Search query parameters
98 local qwith, qstart, qend; 102 local qwith, qstart, qend;
99 local form = query:get_child("x", "jabber:x:data"); 103 local form = query:get_child("x", "jabber:x:data");
100 if form then 104 if form then
101 local err; 105 local form_type, err = get_form_type(form);
106 if not form_type then
107 origin.send(st.error_reply(stanza, "modify", "bad-request", "Invalid dataform: "..err));
108 return true;
109 elseif form_type ~= xmlns_mam then
110 origin.send(st.error_reply(stanza, "modify", "bad-request", "Unexpected FORM_TYPE, expected '"..xmlns_mam.."'"));
111 return true;
112 end
102 form, err = query_form:data(form); 113 form, err = query_form:data(form);
103 if err then 114 if err then
104 origin.send(st.error_reply(stanza, "modify", "bad-request", select(2, next(err)))); 115 origin.send(st.error_reply(stanza, "modify", "bad-request", select(2, next(err))));
105 return true; 116 return true;
106 end 117 end
115 return true; 126 return true;
116 end 127 end
117 qstart, qend = vstart, vend; 128 qstart, qend = vstart, vend;
118 end 129 end
119 130
120 module:log("debug", "Archive query, id %s with %s from %s until %s", 131 module:log("debug", "Archive query by %s id=%s with=%s when=%s...%s",
121 tostring(qid), qwith or "anyone", 132 origin.username,
122 qstart and timestamp(qstart) or "the dawn of time", 133 qid or stanza.attr.id,
123 qend and timestamp(qend) or "now"); 134 qwith or "*",
135 qstart and timestamp(qstart) or "",
136 qend and timestamp(qend) or "");
124 137
125 -- RSM stuff 138 -- RSM stuff
126 local qset = rsm.get(query); 139 local qset = rsm.get(query);
127 local qmax = m_min(qset and qset.max or default_max_items, max_max_items); 140 local qmax = m_min(qset and qset.max or default_max_items, max_max_items);
128 local reverse = qset and qset.before or false; 141 local reverse = qset and qset.before or false;
129 local before, after = qset and qset.before, qset and qset.after; 142 local before, after = qset and qset.before, qset and qset.after;
130 if type(before) ~= "string" then before = nil; end 143 if type(before) ~= "string" then before = nil; end
144 if qset then
145 module:log("debug", "Archive query id=%s rsm=%q", qid or stanza.attr.id, qset);
146 end
131 147
132 -- Load all the data! 148 -- Load all the data!
133 local data, err = archive:find(origin.username, { 149 local data, err = archive:find(origin.username, {
134 start = qstart; ["end"] = qend; -- Time range 150 start = qstart; ["end"] = qend; -- Time range
135 with = qwith; 151 with = qwith;
138 reverse = reverse; 154 reverse = reverse;
139 total = use_total or qmax == 0; 155 total = use_total or qmax == 0;
140 }); 156 });
141 157
142 if not data then 158 if not data then
143 origin.send(st.error_reply(stanza, "cancel", "internal-server-error", err)); 159 module:log("debug", "Archive query id=%s failed: %s", qid or stanza.attr.id, err);
160 if err == "item-not-found" then
161 origin.send(st.error_reply(stanza, "modify", "item-not-found"));
162 else
163 origin.send(st.error_reply(stanza, "cancel", "internal-server-error"));
164 end
144 return true; 165 return true;
145 end 166 end
146 local total = tonumber(err); 167 local total = tonumber(err);
147 168
148 local msg_reply_attr = { to = stanza.attr.from, from = stanza.attr.to }; 169 local msg_reply_attr = { to = stanza.attr.from, from = stanza.attr.to };
187 origin.send(results[i]); 208 origin.send(results[i]);
188 end 209 end
189 first, last = last, first; 210 first, last = last, first;
190 end 211 end
191 212
192 -- That's all folks!
193 module:log("debug", "Archive query %s completed", tostring(qid));
194
195 origin.send(st.reply(stanza) 213 origin.send(st.reply(stanza)
196 :tag("fin", { xmlns = xmlns_mam, queryid = qid, complete = complete }) 214 :tag("fin", { xmlns = xmlns_mam, queryid = qid, complete = complete })
197 :add_child(rsm.generate { 215 :add_child(rsm.generate {
198 first = first, last = last, count = total })); 216 first = first, last = last, count = total }));
217
218 -- That's all folks!
219 module:log("debug", "Archive query id=%s completed, %d items returned", qid or stanza.attr.id, complete and count or count - 1);
199 return true; 220 return true;
200 end); 221 end);
201 222
202 local function has_in_roster(user, who) 223 local function has_in_roster(user, who)
203 local roster = rm_load_roster(user, host); 224 local roster = rm_load_roster(user, host);
211 module:log("debug", "%s@%s does not exist", user, host) 232 module:log("debug", "%s@%s does not exist", user, host)
212 return false; 233 return false;
213 end 234 end
214 local prefs = get_prefs(user); 235 local prefs = get_prefs(user);
215 local rule = prefs[who]; 236 local rule = prefs[who];
216 module:log("debug", "%s's rule for %s is %s", user, who, tostring(rule)); 237 module:log("debug", "%s's rule for %s is %s", user, who, rule);
217 if rule ~= nil then 238 if rule ~= nil then
218 return rule; 239 return rule;
219 end 240 end
220 -- Below could be done by a metatable 241 -- Below could be done by a metatable
221 local default = prefs[false]; 242 local default = prefs[false];
222 module:log("debug", "%s's default rule is %s", user, tostring(default)); 243 module:log("debug", "%s's default rule is %s", user, default);
223 if default == "roster" then 244 if default == "roster" then
224 return has_in_roster(user, who); 245 return has_in_roster(user, who);
225 end 246 end
226 return default; 247 return default;
227 end 248 end
295 -- Check with the users preferences 316 -- Check with the users preferences
296 if shall_store(store_user, with) then 317 if shall_store(store_user, with) then
297 log("debug", "Archiving stanza: %s", stanza:top_tag()); 318 log("debug", "Archiving stanza: %s", stanza:top_tag());
298 319
299 -- And stash it 320 -- And stash it
300 local ok = archive:append(store_user, nil, clone_for_storage, time_now(), with); 321 local time = time_now();
322 local ok, err = archive:append(store_user, nil, clone_for_storage, time, with);
323 if not ok and err == "quota-limit" then
324 if type(cleanup_after) == "number" then
325 module:log("debug", "User '%s' over quota, cleaning archive", store_user);
326 local cleaned = archive:delete(store_user, {
327 ["end"] = (os.time() - cleanup_after);
328 });
329 if cleaned then
330 ok, err = archive:append(store_user, nil, clone_for_storage, time, with);
331 end
332 end
333 if not ok and (archive.caps and archive.caps.truncate) then
334 module:log("debug", "User '%s' over quota, truncating archive", store_user);
335 local truncated = archive:delete(store_user, {
336 truncate = archive_item_limit - 1;
337 });
338 if truncated then
339 ok, err = archive:append(store_user, nil, clone_for_storage, time, with);
340 end
341 end
342 end
301 if ok then 343 if ok then
302 local clone_for_other_handlers = st.clone(stanza); 344 local clone_for_other_handlers = st.clone(stanza);
303 local id = ok; 345 local id = ok;
304 clone_for_other_handlers:tag("stanza-id", { xmlns = xmlns_st_id, by = store_user.."@"..host, id = id }):up(); 346 clone_for_other_handlers:tag("stanza-id", { xmlns = xmlns_st_id, by = store_user.."@"..host, id = id }):up();
305 event.stanza = clone_for_other_handlers; 347 event.stanza = clone_for_other_handlers;
323 end 365 end
324 366
325 module:hook("pre-message/bare", strip_stanza_id_after_other_events, -1); 367 module:hook("pre-message/bare", strip_stanza_id_after_other_events, -1);
326 module:hook("pre-message/full", strip_stanza_id_after_other_events, -1); 368 module:hook("pre-message/full", strip_stanza_id_after_other_events, -1);
327 369
328 local cleanup_after = module:get_option_string("archive_expires_after", "1w");
329 local cleanup_interval = module:get_option_number("archive_cleanup_interval", 4 * 60 * 60);
330 if cleanup_after ~= "never" then 370 if cleanup_after ~= "never" then
331 local cleanup_storage = module:open_store("archive_cleanup"); 371 local cleanup_storage = module:open_store("archive_cleanup");
332 local cleanup_map = module:open_store("archive_cleanup", "map"); 372 local cleanup_map = module:open_store("archive_cleanup", "map");
333 373
334 local day = 86400; 374 local day = 86400;
359 local ok = cleanup_map:set(date, username, true); 399 local ok = cleanup_map:set(date, username, true);
360 if ok then 400 if ok then
361 last_date:set(username, date); 401 last_date:set(username, date);
362 end 402 end
363 end 403 end
404 local cleanup_time = module:measure("cleanup", "times");
364 405
365 cleanup_runner = require "util.async".runner(function () 406 cleanup_runner = require "util.async".runner(function ()
407 local cleanup_done = cleanup_time();
366 local users = {}; 408 local users = {};
367 local cut_off = datestamp(os.time() - cleanup_after); 409 local cut_off = datestamp(os.time() - cleanup_after);
368 for date in cleanup_storage:users() do 410 for date in cleanup_storage:users() do
369 if date <= cut_off then 411 if date <= cut_off then
370 module:log("debug", "Messages from %q should be expired", date); 412 module:log("debug", "Messages from %q should be expired", date);
391 cleanup_map:set(cut_off, user, true); 433 cleanup_map:set(cut_off, user, true);
392 module:log("error", "Could not delete messages for user '%s': %s", user, err); 434 module:log("error", "Could not delete messages for user '%s': %s", user, err);
393 end 435 end
394 end 436 end
395 module:log("info", "Deleted %d expired messages for %d users", sum, num_users); 437 module:log("info", "Deleted %d expired messages for %d users", sum, num_users);
438 cleanup_done();
396 end); 439 end);
397 440
398 cleanup_task = module:add_timer(1, function () 441 cleanup_task = module:add_timer(1, function ()
399 cleanup_runner:run(true); 442 cleanup_runner:run(true);
400 return cleanup_interval; 443 return cleanup_interval;