# HG changeset patch # User Kim Alvefur # Date 1447694365 -3600 # Node ID 9d0c33ebbcc54b72cc41e6549e879acc14ae0083 # Parent 7974a24d29b6754e13528d57bf1977aa596f1c4b mod_presence_cache: Cache incoming presence broadcasts in order to get clients up to speed with who is online faster diff -r 7974a24d29b6 -r 9d0c33ebbcc5 mod_presence_cache/README.markdown --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_presence_cache/README.markdown Mon Nov 16 18:19:25 2015 +0100 @@ -0,0 +1,45 @@ +--- +summary: Cache presence incoming presence +... + +Introduction +============ + +This module stores presence from users contact even when they are +offline, so that the client can see who is online faster when they sign +in, and won't have to wait for remote servers to reply. + +Note that in its current form, the number of presence stanzas sent to a +client is doubled, as the client would get both the cached stanzas and +replies to presence probes. Also see [mod\_throttle\_presence]. + +By default, only binary (online or offline) state is stored. It can +optionally store the full presence but this requires much more memory. + +Configuration +============= + +Just enable the module. + + modules_enabled = { + -- more modules + "presence_cache"; + } + +Advanced configuration +====================== + +To enable full stanza caching: + + presence_cache_full = false + +TODO +==== + +- Deduplication, i.e don's send stanzas that are identical to the last + seen. +- Cache invalidation or expiry, eg if a remote server goes down or is + gone a long time. +- Sending probes at some interval to keep the cache reasonably fresh. + + diff -r 7974a24d29b6 -r 9d0c33ebbcc5 mod_presence_cache/mod_presence_cache.lua --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_presence_cache/mod_presence_cache.lua Mon Nov 16 18:19:25 2015 +0100 @@ -0,0 +1,107 @@ +local is_contact_subscribed = require"core.rostermanager".is_contact_subscribed; +local jid_split = require"util.jid".split; +local jid_bare = require"util.jid".bare; +local st = require"util.stanza"; +local datetime = require"util.datetime"; + +local presence_cache = {}; -- Reload to empty + +local cache_full = module:get_option_boolean(module.name.."_full", false); + +local function cache_hook(event) + local origin, stanza = event.origin, event.stanza; + local typ = stanza.attr.type; + module:log("debug", "Cache hook, got %s from a %s", stanza:top_tag(), origin.type); + if origin.type:match"^s2s" and ( typ == nil or typ == "unavailable" ) then + local from_jid = stanza.attr.from; + local from_bare = jid_bare(from_jid); + local username = jid_split(stanza.attr.to); + + if not is_contact_subscribed(username, module.host, from_bare) then + module:log("debug", "Not in their roster", origin.username); + return; + end + + local user_presence_cache = presence_cache[username]; + if not user_presence_cache then + user_presence_cache = {}; + presence_cache[username] = user_presence_cache; + end + + local contact_presence_cache = user_presence_cache[from_bare]; + if not contact_presence_cache then + contact_presence_cache = {}; + user_presence_cache[from_bare] = contact_presence_cache; + end + + if typ == "unavailable" then + contact_presence_cache[from_jid] = nil; + if next(contact_presence_cache) == nil or from_jid == from_bare then + user_presence_cache[from_bare] = nil; + if next(user_presence_cache) == nil then + presence_cache[username] = nil; + end + end + elseif cache_full then + stanza = st.clone(stanza); + stanza:tag("delay", { xmlns = "urn:xmpp:delay", from = module.host, stamp = datetime.datetime() }):up(); + contact_presence_cache[from_jid] = stanza; + else -- only cache binary state + contact_presence_cache[from_jid] = datetime.datetime(); + end + end +end + +module:hook("presence/bare", cache_hook, 10); +-- module:hook("presence/full", cache_hook, 10); + +local function answer_probe_from_cache(event) + local origin, stanza = event.origin, event.stanza; + if stanza.attr.type ~= "probe" then return; end + local contact_bare = stanza.attr.to; + + local user_presence_cache = presence_cache[origin.username]; + if not user_presence_cache then return; end + + local contact_presence_cache = user_presence_cache[contact_bare]; + if not contact_presence_cache then return; end + + local user_jid = stanza.attr.from; + for jid, presence in pairs(contact_presence_cache) do + module:log("debug", "Sending cached presence from %s", jid); + if presence == true then + presence = st.presence({ from = user_jid, from = jid }); + elseif type(presence) == "string" then -- a timestamp + presence = st.presence({ from = user_jid, from = jid }) + :tag("delay", { xmlns = "urn:xmpp:delay", from = module.host, stamp = presence }):up(); + end + origin.send(presence); + end + if cache_full then + return true; + end +end + +module:hook("pre-presence/bare", answer_probe_from_cache, 10); + +module:add_timer(3600, function (now) + local older = datetime.datetime(now - 7200); + for username, user_presence_cache in pairs(presence_cache) do + for contact, contact_presence_cache in pairs(user_presence_cache) do + for jid, presence in pairs(contact_presence_cache) do + if presence == true or (type(presence) == "string" and presence < older) then + contact_presence_cache[jid] = nil; + end + end + if next(contact_presence_cache) == nil then + user_presence_cache[contact] = nil; + end + end + if next(user_presence_cache) == nil then + presence_cache[username] = nil; + end + end + return 3600; +end); + +module:log("info", "Loaded");