Mercurial > prosody-modules
view mod_csi_battery_saver/mod_csi_battery_saver.lua @ 2757:595d9d68fd11
mod_csi_battery_saver: Fix #921
See also https://mail.jabber.org/pipermail/standards/2017-May/032773.html
author | tmolitor <thilo@eightysoft.de> |
---|---|
date | Sun, 27 Aug 2017 21:35:08 +0200 |
parents | d1aa5fc005f7 |
children | dfac28504e86 |
line wrap: on
line source
-- Copyright (C) 2016 Kim Alvefur -- Copyright (C) 2017 Thilo Molitor -- local filter_muc = module:get_option_boolean("csi_battery_saver_filter_muc", false); module:depends"csi" if filter_muc then module:depends"track_muc_joins"; end -- only depend on this module if we actually use it local s_match = string.match; local s_sub = string.sub; local jid = require "util.jid"; local new_queue = require "util.queue".new; local datetime = require "util.datetime"; local st = require "util.stanza"; local xmlns_delay = "urn:xmpp:delay"; -- a log id for this module instance local id = s_sub(require "util.hashes".sha256(datetime.datetime(), true), 1, 4); -- Patched version of util.stanza:find() that supports giving stanza names -- without their namespace, allowing for every namespace. local function find(self, path) local pos = 1; local len = #path + 1; repeat local xmlns, name, text; local char = s_sub(path, pos, pos); if char == "@" then return self.attr[s_sub(path, pos + 1)]; elseif char == "{" then xmlns, pos = s_match(path, "^([^}]+)}()", pos + 1); end name, text, pos = s_match(path, "^([^@/#]*)([/#]?)()", pos); name = name ~= "" and name or nil; if pos == len then if text == "#" then local child = xmlns ~= nil and self:get_child(name, xmlns) or self:child_with_name(name); return child and child:get_text() or nil; end return xmlns ~= nil and self:get_child(name, xmlns) or self:child_with_name(name); end self = xmlns ~= nil and self:get_child(name, xmlns) or self:child_with_name(name); until not self return nil; end local function new_pump(output, ...) -- luacheck: ignore 212/self local q = new_queue(...); local flush = true; function q:pause() flush = false; end function q:resume() flush = true; return q:flush(); end local push = q.push; function q:push(item) local ok = push(self, item); if not ok then q:flush(); output(item, self); elseif flush then return q:flush(); end return true; end function q:flush(alternative_output) local out = alternative_output or output; local item = self:pop(); while item do out(item, self); item = self:pop(); end return true; end return q; end local function is_stamp_needed(stanza, session) local st_name = stanza and stanza.name or nil; if st_name == "presence" then return true; elseif st_name == "message" then if stanza:get_child("delay", xmlns_delay) then return false; end if stanza.attr.type == "chat" or stanza.attr.type == "groupchat" then return true; end end return false; end local function add_stamp(stanza, session) local bare_jid = jid.bare(session.full_jid or session.host); stanza = stanza:tag("delay", { xmlns = xmlns_delay, from = bare_jid, stamp = datetime.datetime()}); return stanza; end local function is_important(stanza, session) local st_name = stanza and stanza.name or nil; if not st_name then return true; end -- nonzas are always important if st_name == "presence" then -- TODO check for MUC status codes? return false; elseif st_name == "message" then -- unpack carbon copies local stanza_direction = "in"; local carbon; local st_type; -- support carbon copied message stanzas having an arbitrary message-namespace or no message-namespace at all if not carbon then carbon = find(stanza, "{urn:xmpp:carbons:2}/forwarded/message"); end if not carbon then carbon = find(stanza, "{urn:xmpp:carbons:1}/forwarded/message"); end stanza_direction = carbon and stanza:child_with_name("sent") and "out" or "in"; --session.log("debug", "mod_csi_battery_saver(%s): stanza_direction = %s, carbon = %s, stanza = %s", id, stanza_direction, carbon and "true" or "false", tostring(stanza)); if carbon then stanza = carbon; end st_type = stanza.attr.type; -- headline message are always not important if st_type == "headline" then return false; end -- chat markers (XEP-0333) are important, too, because some clients use them to update their notifications if find(stanza, "{urn:xmpp:chat-markers:0}") then return true; end; -- carbon copied outgoing messages are important (some clients update their notifications upon receiving those) --> don't return false here --if carbon and stanza_direction == "out" then return false; end -- We can't check for body contents in encrypted messages, so let's treat them as important -- Some clients don't even set a body or an empty body for encrypted messages -- check omemo https://xmpp.org/extensions/inbox/omemo.html if stanza:get_child("encrypted", "eu.siacs.conversations.axolotl") or stanza:get_child("encrypted", "urn:xmpp:omemo:0") then return true; end -- check xep27 pgp https://xmpp.org/extensions/xep-0027.html if stanza:get_child("x", "jabber:x:encrypted") then return true; end -- check xep373 pgp (OX) https://xmpp.org/extensions/xep-0373.html if stanza:get_child("openpgp", "urn:xmpp:openpgp:0") then return true; end local body = stanza:get_child_text("body"); if st_type == "groupchat" then if stanza:get_child_text("subject") then return true; end if body == nil or body == "" then return false; end -- body contains text, let's see if we want to process it further if filter_muc then if body:find(session.username, 1, true) then return true; end local rooms = session.rooms_joined; if not rooms then return false; end local room_nick = rooms[jid.bare(stanza_direction == "in" and stanza.attr.from or stanza.attr.to)]; if room_nick and body:find(room_nick, 1, true) then return true; end return false; else return true; end end return body ~= nil and body ~= ""; end return true; end module:hook("csi-client-inactive", function (event) local session = event.origin; if session.pump then session.log("debug", "mod_csi_battery_saver(%s): Client is inactive, buffering unimportant outgoing stanzas", id); session.pump:pause(); else session.log("debug", "mod_csi_battery_saver(%s): Client is inactive the first time, initializing module for this session", id); local pump = new_pump(session.send, 100); pump:pause(); session.pump = pump; session._pump_orig_send = session.send; function session.send(stanza) session.log("debug", "mod_csi_battery_saver(%s): Got outgoing stanza: <%s>", id, tostring(stanza.name or stanza)); local important = is_important(stanza, session); -- clone stanzas before adding delay stamp and putting them into the queue if st.is_stanza(stanza) then stanza = st.clone(stanza); end -- add delay stamp to unimportant (buffered) stanzas that can/need be stamped if not important and is_stamp_needed(stanza, session) then stanza = add_stamp(stanza, session); end -- add stanza to outgoing queue and flush the buffer if needed pump:push(stanza); if important then session.log("debug", "mod_csi_battery_saver(%s): Encountered important stanza, flushing buffer: <%s>", id, tostring(stanza.name or stanza)); pump:flush(); end return true; end end end); module:hook("csi-client-active", function (event) local session = event.origin; if session.pump then session.log("debug", "mod_csi_battery_saver(%s): Client is active, resuming direct delivery", id); session.pump:resume(); end end); -- clean up this session on hibernation start module:hook("smacks-hibernation-start", function (event) local session = event.origin; if session.pump then session.log("debug", "mod_csi_battery_saver(%s): Hibernation started, flushing buffer and afterwards disabling for this session", id); session.pump:flush(); session.send = session._pump_orig_send; session.pump = nil; session._pump_orig_send = nil; end end); -- clean up this session on hibernation end as well -- but don't change resumed.send(), it is already overwritten with session.send() by the smacks module module:hook("smacks-hibernation-end", function (event) local session = event.resumed; if session.pump then session.log("debug", "mod_csi_battery_saver(%s): Hibernation ended without being started, flushing buffer and afterwards disabling for this session", id); session.pump:flush(session.send); -- use the fresh session.send() introduced by the smacks resume -- don't reset session.send() because this is not the send previously overwritten by this module, but a fresh one -- session.send = session._pump_orig_send; session.pump = nil; session._pump_orig_send = nil; end end); function module.unload() module:log("info", "%s: Unloading module, flushing all buffers", id); local host_sessions = prosody.hosts[module.host].sessions; for _, user in pairs(host_sessions) do for _, session in pairs(user.sessions) do if session.pump then session.log("debug", "mod_csi_battery_saver(%s): Flushing buffer and restoring to original session.send()", id); session.pump:flush(); session.send = session._pump_orig_send; session.pump = nil; session._pump_orig_send = nil; end end end end module:log("info", "%s: Successfully loaded module", id);