comparison mod_mam_muc/mod_mam_muc.lua @ 1141:1091be1c3aba

mod_mam_muc: Switch to new stanza storage API
author Kim Alvefur <zash@zash.se>
date Sat, 10 Aug 2013 21:15:25 +0200
parents 402cb9b604eb
children fabdaa0d99e3
comparison
equal deleted inserted replaced
1140:402cb9b604eb 1141:1091be1c3aba
1 -- XEP-0313: Message Archive Management for Prosody 1 -- XEP-0313: Message Archive Management for Prosody
2 -- Copyright (C) 2011-2012 Kim Alvefur 2 -- Copyright (C) 2011-2013 Kim Alvefur
3 -- 3 --
4 -- This file is MIT/X11 licensed. 4 -- This file is MIT/X11 licensed.
5 5
6 local xmlns_mam = "urn:xmpp:mam:tmp"; 6 local xmlns_mam = "urn:xmpp:mam:tmp";
7 local xmlns_delay = "urn:xmpp:delay"; 7 local xmlns_delay = "urn:xmpp:delay";
9 9
10 local st = require "util.stanza"; 10 local st = require "util.stanza";
11 local rsm = module:require "mod_mam/rsm"; 11 local rsm = module:require "mod_mam/rsm";
12 local jid_bare = require "util.jid".bare; 12 local jid_bare = require "util.jid".bare;
13 local jid_split = require "util.jid".split; 13 local jid_split = require "util.jid".split;
14 local jid_prep = require "util.jid".prep;
15 local host = module.host;
16 14
17 local dm_list_load = require "util.datamanager".list_load; 15 local getmetatable = getmetatable;
18 local dm_list_append = require "util.datamanager".list_append; 16 local function is_stanza(x)
17 return getmetatable(x) == st.stanza_mt;
18 end
19 19
20 local tostring = tostring; 20 local tostring = tostring;
21 local time_now = os.time; 21 local time_now = os.time;
22 local m_min = math.min; 22 local m_min = math.min;
23 local timestamp, timestamp_parse = require "util.datetime".datetime, require "util.datetime".parse; 23 local timestamp, timestamp_parse = require "util.datetime".datetime, require "util.datetime".parse;
24 local uuid = require "util.uuid".generate;
25 local default_max_items, max_max_items = 20, module:get_option_number("max_archive_query_results", 50); 24 local default_max_items, max_max_items = 20, module:get_option_number("max_archive_query_results", 50);
26 --local rooms_to_archive = module:get_option_set("rooms_to_archive",{});
27 -- TODO Should be possible to enforce it too
28 25
26 local archive = module:open_store("archive2", "archive");
29 local rooms = hosts[module.host].modules.muc.rooms; 27 local rooms = hosts[module.host].modules.muc.rooms;
30 local archive_store = "archive2";
31 28
32 -- Handle archive queries 29 -- Handle archive queries
33 module:hook("iq-get/bare/"..xmlns_mam..":query", function(event) 30 module:hook("iq-get/bare/"..xmlns_mam..":query", function(event)
34 local origin, stanza = event.origin, event.stanza; 31 local origin, stanza = event.origin, event.stanza;
35 local room = jid_split(stanza.attr.to); 32 local room = jid_split(stanza.attr.to);
48 end 45 end
49 46
50 local qid = query.attr.queryid; 47 local qid = query.attr.queryid;
51 48
52 -- Search query parameters 49 -- Search query parameters
53 local qwith = query:get_child_text("with");
54 local qstart = query:get_child_text("start"); 50 local qstart = query:get_child_text("start");
55 local qend = query:get_child_text("end"); 51 local qend = query:get_child_text("end");
56 local qset = rsm.get(query); 52 module:log("debug", "Archive query, id %s from %s until %s)",
57 module:log("debug", "Archive query, id %s with %s from %s until %s)", 53 tostring(qid), qstart or "the dawn of time", qend or "now");
58 tostring(qid), qwith or "anyone", qstart or "the dawn of time", qend or "now");
59 54
60 if qstart or qend then -- Validate timestamps 55 if qstart or qend then -- Validate timestamps
61 local vstart, vend = (qstart and timestamp_parse(qstart)), (qend and timestamp_parse(qend)) 56 local vstart, vend = (qstart and timestamp_parse(qstart)), (qend and timestamp_parse(qend))
62 if (qstart and not vstart) or (qend and not vend) then 57 if (qstart and not vstart) or (qend and not vend) then
63 origin.send(st.error_reply(stanza, "modify", "bad-request", "Invalid timestamp")) 58 origin.send(st.error_reply(stanza, "modify", "bad-request", "Invalid timestamp"))
64 return true 59 return true
65 end 60 end
66 qstart, qend = vstart, vend; 61 qstart, qend = vstart, vend;
67 end 62 end
68 63
69 local qres; 64 -- RSM stuff
70 if qwith then -- Validate the 'with' jid 65 local qset = rsm.get(query);
71 local pwith = qwith and jid_prep(qwith); 66 local qmax = m_min(qset and qset.max or default_max_items, max_max_items);
72 if pwith and not qwith then -- it failed prepping 67 local reverse = qset and qset.before or false;
73 origin.send(st.error_reply(stanza, "modify", "bad-request", "Invalid JID")) 68
74 return true 69 local before, after = qset and qset.before, qset and qset.after;
75 end 70 if type(before) ~= "string" then before = nil; end
76 local _, _, resource = jid_split(qwith);
77 qwith = jid_bare(pwith);
78 qres = resource;
79 end
80 71
81 -- Load all the data! 72 -- Load all the data!
82 local data, err = dm_list_load(room, module.host, archive_store); 73 local data, err = archive:find(origin.username, {
74 start = qstart; ["end"] = qend; -- Time range
75 limit = qmax;
76 before = before; after = after;
77 reverse = reverse;
78 total = true;
79 });
80
83 if not data then 81 if not data then
84 if (not err) then 82 return origin.send(st.error_reply(stanza, "cancel", "internal-server-error"));
85 module:log("debug", "The archive was empty.");
86 origin.send(st.reply(stanza));
87 else
88 origin.send(st.error_reply(stanza, "cancel", "internal-server-error", "Error loading archive: "..tostring(err)));
89 end
90 return true
91 end 83 end
84 local count = err;
92 85
93 -- RSM stuff 86 -- Wrap it in stuff and deliver
94 local qmax = m_min(qset and qset.max or default_max_items, max_max_items); 87 local first, last;
95 local qset_matches = not (qset and qset.after); 88 for id, item, when in data do
96 local first, last, index; 89 local fwd_st = st.message{ to = origin.full_jid }
97 local n = 0; 90 :tag("result", { xmlns = xmlns_mam, queryid = qid, id = id })
98 local start = qset and qset.index or 1;
99
100 module:log("debug", "Loaded %d items, about to filter", #data);
101 for i=start,#data do
102 local item = data[i];
103 local when, nick = item.when, item.resource;
104 local id = item.id;
105 --module:log("debug", "id is %s", id);
106
107 -- RSM pre-send-checking
108 if qset then
109 if qset.before == id then
110 module:log("debug", "End of matching range found");
111 qset_matches = false;
112 break;
113 end
114 end
115
116 --module:log("debug", "message with %s at %s", with, when or "???");
117 -- Apply query filter
118 if (not qres or (qres == nick))
119 and (not qstart or when >= qstart)
120 and (not qend or when <= qend)
121 and (not qset or qset_matches) then
122 local fwd_st = st.message{ to = stanza.attr.from }
123 :tag("result", { xmlns = xmlns_mam, queryid = qid, id = id }):up()
124 :tag("forwarded", { xmlns = xmlns_forward }) 91 :tag("forwarded", { xmlns = xmlns_forward })
125 :tag("delay", { xmlns = xmlns_delay, stamp = timestamp(when) }):up(); 92 :tag("delay", { xmlns = xmlns_delay, stamp = timestamp(when) }):up();
126 local orig_stanza = st.deserialize(item.stanza); 93
127 orig_stanza.attr.xmlns = "jabber:client"; 94 if not is_stanza(item) then
128 fwd_st:add_child(orig_stanza); 95 item = st.deserialize(item);
129 origin.send(fwd_st);
130 if not first then
131 index = i;
132 first = id;
133 end
134 last = id;
135 n = n + 1;
136 elseif (qend and when > qend) then
137 module:log("debug", "We have passed into messages more recent than requested");
138 break -- We have passed into messages more recent than requested
139 end 96 end
97 item.attr.xmlns = "jabber:client";
98 fwd_st:add_child(item);
140 99
141 -- RSM post-send-checking 100 if not first then first = id; end
142 if qset then 101 last = id;
143 if qset.after == id then 102
144 module:log("debug", "Start of matching range found"); 103 origin.send(fwd_st);
145 qset_matches = true;
146 end
147 end
148 if n >= qmax then
149 module:log("debug", "Max number of items matched");
150 break
151 end
152 end 104 end
153 -- That's all folks! 105 -- That's all folks!
154 module:log("debug", "Archive query %s completed", tostring(qid)); 106 module:log("debug", "Archive query %s completed", tostring(qid));
155 107
156 local reply = st.reply(stanza); 108 if reverse then first, last = last, first; end
157 if last then 109 return origin.send(st.reply(stanza)
158 -- This is a bit redundant, isn't it? 110 :query(xmlns_mam):add_child(rsm.generate {
159 reply:query(xmlns_mam):add_child(rsm.generate{first = first, last = last, count = n}); 111 first = first, last = last, count = count }));
160 end
161 origin.send(reply);
162 return true
163 end); 112 end);
164 113
165 -- Handle messages 114 -- Handle messages
166 local function message_handler(event) 115 local function message_handler(event)
167 local origin, stanza = event.origin, event.stanza; 116 local stanza = event.stanza;
168 local orig_type = stanza.attr.type or "normal"; 117 local orig_type = stanza.attr.type or "normal";
169 local orig_to = stanza.attr.to; 118 local orig_to = stanza.attr.to;
170 local orig_from = stanza.attr.from; 119 local orig_from = stanza.attr.from;
171 120
172 -- Still needed?
173 if not orig_from then
174 orig_from = origin.full_jid;
175 end
176
177 -- Only store groupchat messages 121 -- Only store groupchat messages
178 if not (orig_type == "groupchat" and (stanza:get_child("body") or stanza:get_child("subject"))) then 122 if not (orig_type == "groupchat" and (stanza:get_child("body") or stanza:get_child("subject"))) then
179 return; 123 return;
124 -- Chat states and other non-content messages, what TODO?
180 end 125 end
181 126
182 local room = jid_split(orig_to); 127 local room = jid_split(orig_to);
183 local room_obj = hosts[host].modules.muc.rooms[orig_to] 128 local room_obj = rooms[orig_to]
184 if not room_obj then return end 129 if not room_obj then return end -- No such room
185 130
186 local id = uuid();
187 local when = time_now();
188 local stanza = st.clone(stanza); -- Private copy
189 --stanza.attr.to = nil;
190 local nick = room_obj._jid_nick[orig_from]; 131 local nick = room_obj._jid_nick[orig_from];
191 if not nick then return end 132 if not nick then return end -- Message from someone not in the room?
133
192 stanza.attr.from = nick; 134 stanza.attr.from = nick;
193 local _, _, nick = jid_split(nick);
194 -- And stash it 135 -- And stash it
195 local ok, err = dm_list_append(room, host, archive_store, { 136 archive:append(room, time_now(), "", stanza);
196 -- WARNING This format may change. 137 stanza.attr.from = orig_from;
197 id = id,
198 when = when,
199 resource = nick,
200 stanza = st.preserialize(stanza)
201 });
202 --[[ This was dropped from the spec
203 if ok then
204 stanza:tag("archived", { xmlns = xmlns_mam, by = host, id = id }):up();
205 end
206 --]]
207 end 138 end
208 139
209 module:hook("message/bare", message_handler, 2); 140 module:hook("message/bare", message_handler, 2);
210 141
142 -- TODO should we perhaps log presence as well?
143 -- And role/affiliation changes?
144
211 module:add_feature(xmlns_mam); 145 module:add_feature(xmlns_mam);