Mercurial > prosody-modules
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()); |
