changeset 3055:6abee021d9db

mod_cloud_notify: Limit number of devices to 5 and change some default settings
author tmolitor <thilo@eightysoft.de>
date Mon, 28 May 2018 05:28:07 +0200
parents 5e94061c1aa7
children 6fce9a935b38
files mod_cloud_notify/README.markdown mod_cloud_notify/mod_cloud_notify.lua
diffstat 2 files changed, 80 insertions(+), 11 deletions(-) [+]
line wrap: on
line diff
--- a/mod_cloud_notify/README.markdown	Mon May 28 05:27:03 2018 +0200
+++ b/mod_cloud_notify/README.markdown	Mon May 28 05:28:07 2018 +0200
@@ -46,12 +46,13 @@
 Configuration
 =============
 
-  Option                               Default   Description
-  ------------------------------------ --------- ---------------------------------------------------------------------------------------------------------------
-  `push_notification_with_body`        `false`   Whether or not to send the message body to remote pubsub node.
-  `push_notification_with_sender`      `false`   Whether or not to send the message sender to remote pubsub node.
-  `push_max_errors`                    `50`      How much persistent push errors are tolerated before notifications for the identifier in question are disabled
-  `push_notification_important_body`   ``        The body text to use when the stanza is important (see above), no message body is sent if this is empty
+  Option                               Default           Description
+  ------------------------------------ ----------------- -------------------------------------------------------------------------------------------------------------------
+  `push_notification_with_body`        `false`           Whether or not to send the message body to remote pubsub node.
+  `push_notification_with_sender`      `false`           Whether or not to send the message sender to remote pubsub node.
+  `push_max_errors`                    `16`              How much persistent push errors are tolerated before notifications for the identifier in question are disabled
+  `push_notification_important_body`   `New Message!`    The body text to use when the stanza is important (see above), no message body is sent if this is empty
+  `push_max_devices`                   `5`               The number of allowed devices per user (the oldest devices are automatically removed if this threshold is reached)
 
 There are privacy implications for enabling these options because
 plaintext content and metadata will be shared with centralized servers
--- a/mod_cloud_notify/mod_cloud_notify.lua	Mon May 28 05:27:03 2018 +0200
+++ b/mod_cloud_notify/mod_cloud_notify.lua	Mon May 28 05:28:07 2018 +0200
@@ -1,12 +1,13 @@
 -- XEP-0357: Push (aka: My mobile OS vendor won't let me have persistent TCP connections)
 -- Copyright (C) 2015-2016 Kim Alvefur
--- Copyright (C) 2017 Thilo Molitor
+-- Copyright (C) 2017-2018 Thilo Molitor
 --
 -- This file is MIT/X11 licensed.
 
 local t_insert = table.insert;
 local s_match = string.match;
 local s_sub = string.sub;
+local os_time = os.time;
 local st = require"util.stanza";
 local jid = require"util.jid";
 local dataform = require"util.dataforms".new;
@@ -18,13 +19,79 @@
 -- configuration
 local include_body = module:get_option_boolean("push_notification_with_body", false);
 local include_sender = module:get_option_boolean("push_notification_with_sender", false);
-local max_push_errors = module:get_option_number("push_max_errors", 50);
-local dummy_body = module:get_option_string("push_notification_important_body", "");
+local max_push_errors = module:get_option_number("push_max_errors", 16);
+local max_push_devices = module:get_option_number("push_max_devices", 5);
+local dummy_body = module:get_option_string("push_notification_important_body", "New Message!");
 
 local host_sessions = prosody.hosts[module.host].sessions;
 local push_errors = {};
 local id2node = {};
 
+-- ordered table iterator, allow to iterate on the natural order of the keys of a table,
+-- see http://lua-users.org/wiki/SortedIteration
+local function __genOrderedIndex( t )
+	local orderedIndex = {}
+	for key in pairs(t) do
+		table.insert( orderedIndex, key )
+	end
+	-- sort in reverse order (newest one first)
+	table.sort( orderedIndex, function(a, b)
+		if a == nil or t[a] == nil or b == nil or t[b] == nil then return false end
+		-- only one timestamp given, this is the newer one
+		if t[a].timestamp ~= nil and t[b].timestamp == nil then return true end
+		if t[a].timestamp == nil and t[b].timestamp ~= nil then return false end
+		-- both timestamps given, sort normally
+		if t[a].timestamp ~= nil and t[b].timestamp ~= nil then return t[a].timestamp > t[b].timestamp end
+		return false	-- normally not reached
+	end)
+	return orderedIndex
+end
+local function orderedNext(t, state)
+	-- Equivalent of the next function, but returns the keys in timestamp
+	-- order. We use a temporary ordered key table that is stored in the
+	-- table being iterated.
+
+	local key = nil
+	--print("orderedNext: state = "..tostring(state) )
+	if state == nil then
+		-- the first time, generate the index
+		t.__orderedIndex = __genOrderedIndex( t )
+		key = t.__orderedIndex[1]
+	else
+		-- fetch the next value
+		for i = 1,table.getn(t.__orderedIndex) do
+			if t.__orderedIndex[i] == state then
+				key = t.__orderedIndex[i+1]
+			end
+		end
+	end
+
+	if key then
+		return key, t[key]
+	end
+
+	-- no more value to return, cleanup
+	t.__orderedIndex = nil
+	return
+end
+local function orderedPairs(t)
+	-- Equivalent of the pairs() function on tables. Allows to iterate
+	-- in order
+	return orderedNext, t, nil
+end
+
+-- small helper function to return new table with only "maximum" elements containing only the newest entries
+local function reduce_table(table, maximum)
+	local count = 0;
+	local result = {};
+	for key, value in orderedPairs(table) do
+		count = count + 1;
+		if count > maximum then break end
+		result[key] = value;
+	end
+	return result;
+end
+
 -- For keeping state across reloads while caching reads
 local push_store = (function()
 	local store = module:open_store();
@@ -44,7 +111,7 @@
 		return push_services[user], true;
 	end
 	function api:set(user, data)
-		push_services[user] = data;
+		push_services[user] = reduce_table(data, max_push_devices);
 		local ok, err = store:set(user, push_services[user]);
 		if not ok then
 			module:log("error", "Error writing push notification storage for user '%s': %s", user, tostring(err));
@@ -160,6 +227,7 @@
 		node = push_node;
 		include_payload = include_payload;
 		options = publish_options and st.preserialize(publish_options);
+		timestamp = os_time();
 	};
 	local ok = push_store:set_identifier(origin.username, push_identifier, push_service);
 	if not ok then
@@ -418,7 +486,7 @@
 -- archive message added
 local function archive_message_added(event)
 	-- event is: { origin = origin, stanza = stanza, for_user = store_user, id = id }
-	-- only notify for new mam messages when at least one device is only
+	-- only notify for new mam messages when at least one device is online
 	if not event.for_user or not host_sessions[event.for_user] then return; end
 	local stanza = event.stanza;
 	local user_session = host_sessions[event.for_user].sessions;