Add a module-scoped util.cache LRU (lastactivity_cache_size default 5000, lastactivity_cache_ttl default 300s) in front of the activity_store / remote_activity_store fallback. Write-through on all set sites (authentication-success, local last-session unavailable, remote unavailable) and read-through in both IQ handlers (batch and standard jabber:iq:last), so repeated last-activity queries for the same contacts no longer hit the synchronous SQL driver, reducing event-loop blocking. Also removes dead code (avatar-hash helpers, dumpTable, unused imports, commented-out debug logging) and the unused remote avatar-update tracking. Part-of: <http://gitlab.vnc.biz/uxf/vnctalk-prosody/-/merge_requests/5>
306 lines
10 KiB
Lua
306 lines
10 KiB
Lua
-- Prosody IM
|
|
-- Copyright (C) 2008-2010 Matthew Wild
|
|
-- Copyright (C) 2008-2010 Waqas Hussain
|
|
--
|
|
-- This project is MIT/X11 licensed. Please see the
|
|
-- COPYING file in the source package for more information.
|
|
--
|
|
|
|
local st = require "util.stanza";
|
|
local jid_bare = require "util.jid".bare;
|
|
local jid_split = require "util.jid".split;
|
|
|
|
local my_host = module:get_host();
|
|
|
|
local remote_act_cache = {};
|
|
local remote_act_sessions = {};
|
|
|
|
local activity_store = module:open_store("activity", "map");
|
|
local remote_activity_store = module:open_store("remote_activity", "map");
|
|
|
|
-- Options:
|
|
-- lastactivity_cache_size max entries in the last-activity LRU cache (default 5000)
|
|
-- lastactivity_cache_ttl max age (seconds) of a cached entry before re-fetch (default 300)
|
|
local cache_lib = require "util.cache";
|
|
local last_activity_cache = cache_lib.new(
|
|
module:get_option_integer("lastactivity_cache_size", 5000, 1));
|
|
local cache_ttl = module:get_option_number("lastactivity_cache_ttl", 300);
|
|
|
|
-- cache stores the persisted "last" timestamp (0 == online) keyed by bare JID,
|
|
-- write-through on every store write and read-through on every store read.
|
|
local function cache_set(bare_jid, stamp)
|
|
last_activity_cache:set(bare_jid, { stamp = stamp, ts = os.time() });
|
|
end
|
|
local function cache_get(bare_jid)
|
|
local entry = last_activity_cache:get(bare_jid);
|
|
if not entry then return nil end
|
|
if os.difftime(os.time(), entry.ts) > cache_ttl then return nil end
|
|
return entry.stamp;
|
|
end
|
|
|
|
local function getTableLng(tbl)
|
|
local getN = 0
|
|
for n in pairs(tbl) do
|
|
getN = getN + 1
|
|
end
|
|
return getN
|
|
end
|
|
|
|
module:add_feature("jabber:iq:last");
|
|
module:add_feature("jabber:iq:batch");
|
|
|
|
local map = {};
|
|
|
|
|
|
module:hook("authentication-success", function(event)
|
|
local session = event.session;
|
|
if session.username then
|
|
local pers = activity_store:set(session.username, "last", 0);
|
|
cache_set(session.username.."@"..my_host, 0);
|
|
end
|
|
end, 10);
|
|
|
|
|
|
module:hook("presence/bare", function(event)
|
|
local origin, stanza = event.origin, event.stanza;
|
|
local typ = stanza.attr.type;
|
|
|
|
if origin.type == "s2sin" and ( typ == nil or typ == "unavailable" ) then
|
|
|
|
local contact_full = stanza.attr.from;
|
|
local contact_bare = jid_bare(contact_full);
|
|
local contact_user, contact_host, contact_res = jid_split(contact_full);
|
|
local contact_sessions = remote_act_sessions[contact_bare] or {};
|
|
|
|
if typ == "unavailable" then -- set current time in cache
|
|
local t = os.time();
|
|
if contact_sessions[contact_full] then
|
|
contact_sessions[contact_full] = nil;
|
|
remote_act_sessions[contact_bare] = contact_sessions;
|
|
local contact_session_count = getTableLng(contact_sessions);
|
|
if ( contact_session_count == 0 ) then
|
|
remote_act_sessions[contact_bare] = nil;
|
|
remote_act_cache[contact_bare] = t;
|
|
remote_activity_store:set(contact_bare, "last", t);
|
|
cache_set(contact_bare, t);
|
|
end
|
|
end
|
|
else
|
|
-- set 0 (online) in cache
|
|
local contact_sessions = remote_act_sessions[contact_bare] or {};
|
|
contact_sessions[contact_full] = 1;
|
|
remote_act_sessions[contact_bare] = contact_sessions;
|
|
end
|
|
end
|
|
|
|
end, 10);
|
|
|
|
module:hook("pre-presence/bare", function(event)
|
|
local stanza = event.stanza;
|
|
if not(stanza.attr.to) and stanza.attr.type == "unavailable" then
|
|
local s = os.time();
|
|
local t = stanza:get_child_text("status");
|
|
map[event.origin.username] = {s = s, t = t};
|
|
local lastsession = true;
|
|
local luser, lhost = jid_split(stanza.attr.from);
|
|
if luser and lhost then
|
|
local user_sessions = hosts[lhost] and hosts[lhost].sessions[luser];
|
|
if user_sessions then
|
|
lastsession = false;
|
|
end
|
|
end
|
|
if lastsession then
|
|
local pers = activity_store:set(event.origin.username, "last", s);
|
|
cache_set(event.origin.username.."@"..my_host, s);
|
|
end
|
|
end
|
|
end, 10);
|
|
|
|
module:hook("iq/bare/jabber:iq:batch:query", function(event)
|
|
local origin, stanza = event.origin, event.stanza;
|
|
if stanza.attr.type == "get" then
|
|
local querychild = stanza:get_child("query", "jabber:iq:batch");
|
|
local reply = st.reply(stanza):tag('query', {xmlns='jabber:iq:last'});
|
|
for ajid in querychild:childtags("jid") do
|
|
local abjid = ajid:get_text();
|
|
local username = jid_split(abjid);
|
|
local luser, lhost = jid_split(abjid);
|
|
|
|
local seconds, text = "-1", "";
|
|
|
|
if map[username] then
|
|
local mt = tonumber(map[username].s);
|
|
seconds = tostring(os.difftime(os.time(), mt));
|
|
text = map[username].s;
|
|
end
|
|
if luser and lhost then
|
|
local sessions_avail = false;
|
|
if (hosts[lhost]) then
|
|
if (hosts[lhost].sessions) then
|
|
sessions_avail = true;
|
|
end
|
|
end
|
|
if sessions_avail then
|
|
local user_sessions = hosts[lhost] and hosts[lhost].sessions[luser];
|
|
local latest_hibernated_stamp = 0;
|
|
local all_hibernated = true;
|
|
if user_sessions then
|
|
for x,s in pairs(user_sessions.sessions) do
|
|
if (s.hibernating) then
|
|
if (s.hibernating > latest_hibernated_stamp) then
|
|
latest_hibernated_stamp = s.hibernating;
|
|
end
|
|
else
|
|
all_hibernated = false;
|
|
end
|
|
end
|
|
if (latest_hibernated_stamp > 0 and all_hibernated) then
|
|
seconds = os.difftime(os.time(), latest_hibernated_stamp)
|
|
else
|
|
seconds = 0
|
|
end
|
|
end
|
|
end
|
|
if not (prosody.hosts[lhost]) then
|
|
local contact_bare = luser.."@"..lhost;
|
|
if remote_act_sessions[contact_bare] then
|
|
local contact_sessions = getTableLng(remote_act_sessions[contact_bare]);
|
|
if ( contact_sessions == 0 ) then
|
|
seconds = tostring(os.difftime(os.time(), remote_act_cache[contact_bare]));
|
|
else
|
|
seconds = 0;
|
|
end
|
|
else
|
|
if remote_act_cache[contact_bare] then
|
|
if ( remote_act_cache[contact_bare] > 0) then
|
|
seconds = tostring(os.difftime(os.time(), remote_act_cache[contact_bare]));
|
|
else
|
|
seconds = 0;
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
if (seconds == "-1") then
|
|
if (prosody.hosts[lhost]) then
|
|
local bare_jid = luser.."@"..lhost;
|
|
local nseconds = cache_get(bare_jid);
|
|
if nseconds == nil then
|
|
local stored_seconds = activity_store:get(luser, "last") or false;
|
|
if stored_seconds then
|
|
nseconds = tonumber(stored_seconds);
|
|
cache_set(bare_jid, nseconds);
|
|
end
|
|
end
|
|
if nseconds then
|
|
if (nseconds == 0) then
|
|
map[event.origin.username] = {s = "0", t = text};
|
|
else
|
|
seconds = tostring(os.difftime(os.time(), nseconds));
|
|
map[event.origin.username] = {s = nseconds, t = text};
|
|
end
|
|
end
|
|
else
|
|
if (luser ~= nil) then
|
|
local contact_bare = luser.."@"..lhost;
|
|
local nseconds = cache_get(contact_bare);
|
|
if nseconds == nil then
|
|
local stored_seconds = remote_activity_store:get(contact_bare, "last") or false;
|
|
if stored_seconds then
|
|
nseconds = tonumber(stored_seconds);
|
|
cache_set(contact_bare, nseconds);
|
|
end
|
|
end
|
|
if nseconds then
|
|
if (nseconds == 0) then
|
|
map[event.origin.username] = {s = "0", t = text};
|
|
else
|
|
seconds = tostring(os.difftime(os.time(), nseconds));
|
|
map[event.origin.username] = {s = nseconds, t = text};
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local rtag = st.stanza("result", { jid=abjid; seconds=tostring(seconds); });
|
|
reply:add_child(rtag);
|
|
end
|
|
origin.send(reply);
|
|
end
|
|
return true;
|
|
end);
|
|
|
|
module:hook("iq/bare/jabber:iq:last:query", function(event)
|
|
local origin, stanza = event.origin, event.stanza;
|
|
if stanza.attr.type == "get" then
|
|
local username = jid_split(stanza.attr.to) or origin.username;
|
|
local luser, lhost = jid_split(stanza.attr.to);
|
|
if stanza.attr.to then
|
|
local seconds, text = "-1", "";
|
|
if map[username] then
|
|
mseconds = map[username].s or 0;
|
|
if (mseconds == 0) then
|
|
seconds=0;
|
|
map[username].s=os.time();
|
|
else
|
|
seconds = tostring(os.difftime(os.time(), map[username].s));
|
|
end
|
|
end
|
|
if luser and lhost then
|
|
local user_sessions = hosts[lhost] and hosts[lhost].sessions[luser];
|
|
if user_sessions then
|
|
local latest_hibernated_stamp = 0;
|
|
local all_hibernated = true;
|
|
for x,s in pairs(user_sessions.sessions) do
|
|
if (s.hibernating) then
|
|
if (s.hibernating > latest_hibernated_stamp) then
|
|
latest_hibernated_stamp = s.hibernating;
|
|
end
|
|
else
|
|
all_hibernated = false;
|
|
end
|
|
end
|
|
if (latest_hibernated_stamp > 0 and all_hibernated) then
|
|
seconds = os.difftime(os.time(), latest_hibernated_stamp)
|
|
else
|
|
seconds = 0
|
|
end
|
|
end
|
|
end
|
|
if (seconds == "-1") then
|
|
local bare_jid = luser.."@"..lhost;
|
|
local stored_seconds = cache_get(bare_jid);
|
|
if stored_seconds == nil then
|
|
stored_seconds = activity_store:get(luser, "last") or -1;
|
|
if stored_seconds ~= -1 then
|
|
cache_set(bare_jid, tonumber(stored_seconds));
|
|
end
|
|
end
|
|
if (stored_seconds ~= 0) then
|
|
if (stored_seconds == -1) then
|
|
seconds = -1
|
|
else
|
|
seconds = tostring(os.difftime(os.time(), stored_seconds));
|
|
end;
|
|
if event.origin.username ~= nil then
|
|
map[event.origin.username] = {s = seconds, t = text};
|
|
end
|
|
end
|
|
end
|
|
origin.send(st.reply(stanza):tag('query', {xmlns='jabber:iq:last', seconds=tostring(seconds)}):text(tostring(text)));
|
|
else
|
|
origin.send(st.error_reply(stanza, 'auth', 'forbidden'));
|
|
end
|
|
return true;
|
|
end
|
|
end);
|
|
|
|
module.save = function()
|
|
return {map = map};
|
|
end
|
|
module.restore = function(data)
|
|
map = data.map or {};
|
|
end
|