# HG changeset patch # User Kim Alvefur # Date 1339118011 -7200 # Node ID cc5805f83583f459cfce0f300d2cc13478e59642 # Parent 0c130c45b7c17bf257bc95ef9ca872c86f908708 mod_mam: Implement support for Result Set Management in queries. diff -r 0c130c45b7c1 -r cc5805f83583 mod_mam/mod_mam.lua --- a/mod_mam/mod_mam.lua Thu Jun 07 23:41:25 2012 +0200 +++ b/mod_mam/mod_mam.lua Fri Jun 08 03:13:31 2012 +0200 @@ -8,6 +8,7 @@ local xmlns_forward = "urn:xmpp:forward:0"; local st = require "util.stanza"; +local rsm = module:require "rsm"; local jid_bare = require "util.jid".bare; local jid_split = require "util.jid".split; local host = module.host; @@ -62,7 +63,6 @@ local default = prefs[false]; default = default ~= nil and default_attrs[default] or global_default_policy; local reply = st.reply(stanza):tag("prefs", { xmlns = xmlns_mam, default = default }) - --module:log("debug", "get_prefs(%q) => %s", user, require"util.serialization".serialize(prefs)); local always = st.stanza("always"); local never = st.stanza("never"); for k,v in pairs(prefs) do @@ -97,7 +97,6 @@ end end - --module:log("debug", "set_prefs(%q, %s)", user, require"util.serialization".serialize(prefs)); local ok, err = set_prefs(user, prefs); if not ok then origin.send(st.error_reply(stanza, "cancel", "internal-server-error", "Error storing preferences: "..tostring(err))); @@ -119,6 +118,7 @@ local qwith = query:get_child_text("with"); local qstart = query:get_child_text("start"); local qend = query:get_child_text("end"); + local qset = rsm.get(query); module:log("debug", "Archive query, id %s with %s from %s until %s)", tostring(qid), qwith or "anyone", qstart or "the dawn of time", qend or "now"); @@ -136,19 +136,35 @@ return true end + -- RSM stuff + local qset_matches = not (qset and qset.after); + local first, last, index; + local n = 0; + local start = qset and qset.index or 1; + module:log("debug", "Loaded %d items, about to filter", #data); - for i=1,#data do + for i=start,#data do local item = data[i]; local when, with, with_bare = item.when, item.with, item.with_bare; local ts = item.timestamp; local id = item.id; + --module:log("debug", "id is %s", id); + + -- RSM pre-send-checking + if qset then + if qset.before == id then + module:log("debug", "End of matching range found"); + qset_matches = false; + break; + end + end + --module:log("debug", "message with %s at %s", with, when or "???"); -- Apply query filter if (not qwith or ((qwith == with) or (qwith == with_bare))) and (not qstart or when >= qstart) - and (not qend or when <= qend) then - -- Optimizable? Do this when archiving? - --module:log("debug", "sending"); + and (not qend or when <= qend) + and (not qset or qset_matches) then local fwd_st = st.message{ to = origin.full_jid } :tag("result", { xmlns = xmlns_mam, queryid = qid, id = id }):up() :tag("forwarded", { xmlns = xmlns_forward }) @@ -157,13 +173,32 @@ orig_stanza.attr.xmlns = "jabber:client"; fwd_st:add_child(orig_stanza); origin.send(fwd_st); - elseif qend and when > qend then + if not first then + index = i; + first = id; + end + last = id; + n = n + 1; + elseif (qend and when > qend) then + module:log("debug", "We have passed into messages more recent than requested"); break -- We have passed into messages more recent than requested end + + -- RSM post-send-checking + if qset then + if qset.after == id then + module:log("debug", "Start of matching range found"); + qset_matches = true; + end + if qset.max and n >= qset.max then + module:log("debug", "Max number of items matched"); + break + end + end end -- That's all folks! module:log("debug", "Archive query %s completed", tostring(qid)); - origin.send(st.reply(stanza)); + origin.send(st.reply(stanza):add_child(rsm.generate{first = { index = index; first }, last = last})); return true end end); @@ -221,8 +256,6 @@ local target_jid = c2s and orig_to or orig_from; local target_bare = jid_bare(target_jid); - assert(store_host == host, "This should not happen."); - if shall_store(store_user, target_bare) then module:log("debug", "Archiving stanza: %s", stanza:top_tag()); @@ -232,8 +265,7 @@ local ok, err = dm_list_append(store_user, store_host, archive_store, { -- WARNING This format may change. id = id, - when = when, -- This might be an UNIX timestamp. Probably. - timestamp = timestamp(when), -- Textual timestamp. But I'll assume that comparing numbers is faster and less annoying in case of timezones. + when = when, with = target_jid, with_bare = target_bare, -- Optimization, to avoid loads of jid_bare() calls when filtering. stanza = st.preserialize(stanza) diff -r 0c130c45b7c1 -r cc5805f83583 mod_mam/rsm.lib.lua --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_mam/rsm.lib.lua Fri Jun 08 03:13:31 2012 +0200 @@ -0,0 +1,82 @@ +local stanza = require"util.stanza".stanza; +local tostring, tonumber = tostring, tonumber; +local type = type; +local pairs = pairs; + +local xmlns_rsm = 'http://jabber.org/protocol/rsm'; + +local element_parsers; + +do + local function xs_int(st) + return tonumber(st:get_text()); + end + local function xs_string(st) + return st:get_text(); + end + + element_parsers = { + after = xs_string; + before = function(st) + return st:get_text() or true; + end; + max = xs_int; + index = xs_int; + + first = function(st) + return { index = tonumber(st.attr.index); st:get_text() }; + end; + last = xs_string; + count = xs_int; + } +end + +local element_generators = setmetatable({ + first = function(st, data) + if type(data) == "table" then + st:tag("first", { index = data.index }):text(data[1]):up(); + else + st:tag("first"):text(tostring(data)):up(); + end + end; +}, { + __index = function(_, name) + return function(st, data) + st:tag(name):text(tostring(data)):up(); + end + end; +}); + + +local function parse(stanza) + local rs = {}; + for tag in stanza:childtags() do + local name = tag.name; + local parser = name and element_parsers[name]; + if parser then + rs[name] = parser(tag); + end + end + return rs; +end + +local function generate(t) + local st = stanza("rsm", { xmlns = xmlns_rsm }); + for k,v in pairs(t) do + if element_parsers[k] then + element_generators[k](st, v); + end + end + return st; +end + +local function get(st) + local set = st:get_child("set", xmlns_rsm); + if set and #set.tags > 0 then + return parse(set); + else + module:log("debug", "RSM parse failed, %s", tostring(st)); + end +end + +return { parse = parse, generate = generate, get = get };