Software /
code /
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 |
parent | 1114:6c0e1f9926f6 |
child | 1135: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()); |