Software /
code /
prosody
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; |