changeset 1952:9d0c33ebbcc5

mod_presence_cache: Cache incoming presence broadcasts in order to get clients up to speed with who is online faster
author Kim Alvefur <zash@zash.se>
date Mon, 16 Nov 2015 18:19:25 +0100
parents 7974a24d29b6
children 0c3ba5ff7a3b f719d5e6c627
files mod_presence_cache/README.markdown mod_presence_cache/mod_presence_cache.lua
diffstat 2 files changed, 152 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /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.
+
+
--- /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");