comparison mod_csi_battery_saver/mod_csi_battery_saver.lua @ 2606:538c54d2dab3

mod_csi_battery_saver: CSI module to save battery on mobile devices, based on mod_csi_pump
author tmolitor <thilo@eightysoft.de>
date Fri, 10 Mar 2017 01:32:53 +0100
parents
children b5fae17e4403
comparison
equal deleted inserted replaced
2605:8908d001faf3 2606:538c54d2dab3
1 -- Copyright (C) 2016 Kim Alvefur
2 -- Copyright (C) 2017 Thilo Molitor
3 --
4
5 module:depends"csi"
6 module:depends"track_muc_joins"
7 local s_match = string.match;
8 local s_sub = string.sub;
9 local jid = require "util.jid";
10 local new_queue = require "util.queue".new;
11 local datetime = require "util.datetime";
12
13 local xmlns_delay = "urn:xmpp:delay";
14
15 -- a log id for this module instance
16 local id = s_sub(require "util.hashes".sha256(datetime.datetime(), true), 1, 4);
17
18 -- Patched version of util.stanza:find() that supports giving stanza names
19 -- without their namespace, allowing for every namespace.
20 local function find(self, path)
21 local pos = 1;
22 local len = #path + 1;
23
24 repeat
25 local xmlns, name, text;
26 local char = s_sub(path, pos, pos);
27 if char == "@" then
28 return self.attr[s_sub(path, pos + 1)];
29 elseif char == "{" then
30 xmlns, pos = s_match(path, "^([^}]+)}()", pos + 1);
31 end
32 name, text, pos = s_match(path, "^([^@/#]*)([/#]?)()", pos);
33 name = name ~= "" and name or nil;
34 if pos == len then
35 if text == "#" then
36 local child = xmlns ~= nil and self:get_child(name, xmlns) or self:child_with_name(name);
37 return child and child:get_text() or nil;
38 end
39 return xmlns ~= nil and self:get_child(name, xmlns) or self:child_with_name(name);
40 end
41 self = xmlns ~= nil and self:get_child(name, xmlns) or self:child_with_name(name);
42 until not self
43 return nil;
44 end
45
46 local function new_pump(output, ...)
47 -- luacheck: ignore 212/self
48 local q = new_queue(...);
49 local flush = true;
50 function q:pause()
51 flush = false;
52 end
53 function q:resume()
54 flush = true;
55 return q:flush();
56 end
57 local push = q.push;
58 function q:push(item)
59 local ok = push(self, item);
60 if not ok then
61 q:flush();
62 output(item, self);
63 elseif flush then
64 return q:flush();
65 end
66 return true;
67 end
68 function q:flush()
69 local item = self:pop();
70 while item do
71 output(item, self);
72 item = self:pop();
73 end
74 return true;
75 end
76 return q;
77 end
78
79 local function is_stamp_needed(stanza, session)
80 local st_name = stanza and stanza.name or nil;
81 if st_name == "presence" then
82 return true;
83 elseif st_name == "message" then
84 if stanza:get_child("delay", xmlns_delay) then return false; end
85 if stanza.attr.type == "chat" or stanza.attr.type == "groupchat" then return true; end
86 end
87 return false;
88 end
89
90 local function add_stamp(stanza, session)
91 stanza = stanza:tag("delay", { xmlns = xmlns_delay, from = session.host, stamp = datetime.datetime()});
92 return stanza;
93 end
94
95 local function is_important(stanza, session)
96 local st_name = stanza and stanza.name or nil;
97 if not st_name then return false; end
98 if st_name == "presence" then
99 -- TODO check for MUC status codes?
100 return false;
101 elseif st_name == "message" then
102 -- unpack carbon copies
103 local stanza_direction = "in";
104 local carbon;
105 -- support carbon copied message stanzas having an arbitrary message-namespace or no message-namespace at all
106 if not carbon then carbon = find(stanza, "{urn:xmpp:carbons:2}/forwarded/message"); end
107 if not carbon then carbon = find(stanza, "{urn:xmpp:carbons:1}/forwarded/message"); end
108 stanza_direction = carbon and stanza:child_with_name("sent") and "out" or "in";
109 --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));
110 if carbon then stanza = carbon; end
111 -- carbon copied outgoing messages aren't important (but incoming carbon copies are!)
112 if carbon and stanza_direction == "out" then return false; end
113
114 local st_type = stanza.attr.type;
115 if st_type == "headline" then
116 return false;
117 end
118 local body = stanza:get_child_text("body");
119 if st_type == "groupchat" then
120 if stanza:get_child_text("subject") then return true; end
121 if not body then return false; end
122 if body:find(session.username, 1, true) then return true; end
123 local rooms = session.rooms_joined;
124 if not rooms then return false; end
125 local room_nick = rooms[jid.bare(stanza_direction == "in" and stanza.attr.from or stanza.attr.to)];
126 if room_nick and body:find(room_nick, 1, true) then return true; end
127 return false;
128 end
129 return body ~= nil and body ~= "";
130 end
131 return true;
132 end
133
134 module:hook("csi-client-inactive", function (event)
135 local session = event.origin;
136 if session.pump then
137 session.pump:pause();
138 else
139 session.log("debug", "mod_csi_battery_saver(%s): Client is inactive the first time, initializing module for this session", id);
140 local pump = new_pump(session.send, 100);
141 pump:pause();
142 session.pump = pump;
143 session._pump_orig_send = session.send;
144 function session.send(stanza)
145 session.log("debug", "mod_csi_battery_saver(%s): Got stanza: <%s>", id, tostring(stanza.name));
146 local important = is_important(stanza, session);
147 -- add delay stamp to unimported (buffered) stanzas that can/need be stamped
148 if not important and is_stamp_needed(stanza, session) then stanza = add_stamp(stanza, session); end
149 pump:push(stanza);
150 if important then
151 session.log("debug", "mod_csi_battery_saver(%s): Encountered important stanza, flushing buffer: <%s>", id, tostring(stanza.name));
152 pump:flush();
153 end
154 return true;
155 end
156 end
157 session.log("debug", "mod_csi_battery_saver(%s): Client is inactive, buffering unimportant stanzas", id);
158 end);
159
160 module:hook("csi-client-active", function (event)
161 local session = event.origin;
162 if session.pump then
163 session.log("debug", "mod_csi_battery_saver(%s): Client is active, resuming direct delivery", id);
164 session.pump:resume();
165 end
166 end);
167
168 function module.unload()
169 module:log("info", "%s: Unloading module, flushing all buffers", id);
170 local host_sessions = prosody.hosts[module.host].sessions;
171 for _, user in pairs(host_sessions) do
172 for _, session in pairs(user.sessions) do
173 if session.pump then
174 session.pump:flush();
175 session.send = session._pump_orig_send;
176 session.pump = nil;
177 session._pump_orig_send = nil;
178 end
179 end
180 end
181 end
182
183 module:log("info", "%s: Successfully loaded module", id);