Mercurial > prosody-hg
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 |
| parents | cfc05e46b979 9cf7d9761ca2 |
| children | 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; |
