f825b2e6cd
There are a number of mods in the pack that store metadata about players "inside out", in mod_storage instead of player:get_meta, because player meta is not accessible when the player is not online and that's necessary for some mods. Player meta is also tied to the "player character" and not to the auth account, so inside out storage is necessary when we want data to survive resetting the player character while keeping the account. Unfortunately, this prevents MT from automatically cleaning up the metadata when a player is destroyed, which can happen for various reasons outside the control of the mod. This can cause keys to accumulate in the mod storage database, which may hurt performance over time. To keep things tidy, periodically scan the mod storage keys for each such mod (add a scan cycle, or integrate with an existing one) and automatically clean up old mod storage keys. szutil_xplevel was already doing this on startup and command; convert it to do periodic scans like the other mods.
153 lines
5.3 KiB
Lua
153 lines
5.3 KiB
Lua
-- LUALOCALS < ---------------------------------------------------------
|
|
local io, ipairs, math, minetest, string, tonumber
|
|
= io, ipairs, math, minetest, string, tonumber
|
|
local io_close, io_open, math_random, string_format, string_gsub,
|
|
string_match, string_sub
|
|
= io.close, io.open, math.random, string.format, string.gsub,
|
|
string.match, string.sub
|
|
-- LUALOCALS > ---------------------------------------------------------
|
|
|
|
local modname = minetest.get_current_modname()
|
|
local modstore = minetest.get_mod_storage()
|
|
|
|
local phashkey = minetest.settings:get("szutil_motd_hashkey") or ""
|
|
local function phash(pname)
|
|
if #phashkey < 1 then return "0000" end
|
|
return string_sub(minetest.sha1(phashkey .. pname .. phashkey .. pname), 1, 4)
|
|
end
|
|
|
|
-- Permission to ignore all involuntary MOTD interaction.
|
|
-- The command will still work, but it skips the warnings and
|
|
-- mandatory display.
|
|
local exempt = modname .. "_exempt"
|
|
minetest.register_privilege(exempt, {
|
|
description = "Player will not be alerted about MOTD",
|
|
give_to_admin = false,
|
|
give_to_singleplayer = false
|
|
})
|
|
|
|
-- Function to read current MOTD
|
|
local readmotd
|
|
do
|
|
local motdpath = minetest.get_worldpath() .. "/" .. modname .. ".txt"
|
|
readmotd = function()
|
|
local f = io_open(motdpath, "rb")
|
|
if not f then return end
|
|
local motd = f:read("*all")
|
|
io_close(f)
|
|
if not string_match(motd, "%S") then return end
|
|
return motd
|
|
end
|
|
end
|
|
|
|
-- Periodically scan for MOTD changes, and notify all online
|
|
-- players if there are any.
|
|
do
|
|
local motdinterval = tonumber(minetest.settings:get(modname .. "_interval"))
|
|
if motdinterval then
|
|
minetest.log("info", "polling motd for changes every " .. motdinterval .. "s")
|
|
local alertmotd = readmotd()
|
|
local function alertcheck()
|
|
minetest.after(motdinterval, alertcheck)
|
|
local motd = readmotd()
|
|
if motd == alertmotd then return end
|
|
for _, p in ipairs(minetest.get_connected_players()) do
|
|
if not minetest.check_player_privs(p, exempt) then
|
|
minetest.chat_send_player(p:get_player_name(),
|
|
"Updated MOTD. Please use /motd to review.")
|
|
end
|
|
end
|
|
alertmotd = motd
|
|
end
|
|
minetest.after(motdinterval, alertcheck)
|
|
end
|
|
end
|
|
|
|
-- Function to send the actual MOTD content to the player, in either
|
|
-- automatic mode (on login) or "forced" mode (on player request).
|
|
local function sendmotd(name, force)
|
|
-- Never auto-display to exempt players.
|
|
if (not force) and minetest.check_player_privs(name, exempt)
|
|
then return end
|
|
|
|
-- Load the MOTD fresh on each request, so changes can be
|
|
-- made while the server is running, and take effect immediately.
|
|
local motd = readmotd()
|
|
if not motd then return end
|
|
|
|
-- Compute a hash of the MOTD content, and figure out
|
|
-- if a player has already seen this version.
|
|
local hash = minetest.sha1(motd)
|
|
local seen = (modstore:get_string(name) or "") == hash
|
|
|
|
-- If player has seen this version and did not specifically
|
|
-- request redisplay, just send a chat message reminding them that
|
|
-- it's available if they want.
|
|
if seen and not force then
|
|
minetest.chat_send_player(name,
|
|
"No MOTD changes since your last view. Use /motd command to "
|
|
.. "review it any time.")
|
|
return
|
|
end
|
|
|
|
-- Send MOTD as a nicely-formatted formspec popup.
|
|
local fsw = tonumber(minetest.settings:get(modname .. "_width")) or 8.5
|
|
local fsh = tonumber(minetest.settings:get(modname .. "_height")) or 6
|
|
minetest.show_formspec(name, modname,
|
|
"size[" .. fsw .. "," .. fsh .. ",true]"
|
|
.. "textarea[0.3,0;" .. fsw .. "," .. fsh .. ";;;"
|
|
.. minetest.formspec_escape(string_gsub(motd, "<phash>", phash(name)))
|
|
.. "]button_exit[0," .. (fsh - 0.75) .. ";" .. fsw
|
|
.. ",1;ok;" .. (minetest.settings:get(modname
|
|
.. "_button") or "Continue") .. "]")
|
|
|
|
-- If the player had already seen the MOTD (i.e. this is a
|
|
-- forced request) then we don't need to update the database or
|
|
-- send them a reminder.
|
|
if seen then return end
|
|
|
|
-- Update the seen database in-memory and on-disk
|
|
-- so we don't send another copy of the same content to the
|
|
-- same player automatically.
|
|
modstore:set_string(name, hash)
|
|
|
|
-- Remind the player where they can get the MOTD if they
|
|
-- want it, and explain why it may or may not appear again
|
|
-- automatically on future logins.
|
|
minetest.chat_send_player(name, "Updated MOTD. It will not display again "
|
|
.. "automatically, unless there are changes. Use /motd command to "
|
|
.. "review it at any time.")
|
|
end
|
|
|
|
-- Display an "automatic" MOTD popup on new player joins, i.e. for completely
|
|
-- new players, or those who have not seen the latest version yet.
|
|
minetest.register_on_joinplayer(function(player) sendmotd(player:get_player_name()) end)
|
|
|
|
-- Force popup display for players who request it via the /motd command.
|
|
minetest.register_chatcommand("motd", {func = function(name) sendmotd(name, true) end})
|
|
|
|
-- Periodically scan the database for deleted players and
|
|
-- garbage-collect agreement hashes from them.
|
|
do
|
|
local batch = {}
|
|
local rescan = 0
|
|
minetest.register_globalstep(function(dtime)
|
|
if #batch > 0 then
|
|
local pname = batch[#batch]
|
|
batch[#batch] = nil
|
|
if not minetest.player_exists(pname) then
|
|
minetest.log("info", string_format(
|
|
"%s gc %s", modname, pname))
|
|
modstore:set_string(pname, "")
|
|
end
|
|
return
|
|
end
|
|
if rescan > 0 then
|
|
rescan = rescan - dtime
|
|
return
|
|
end
|
|
rescan = 60 * (math_random() + 0.5)
|
|
batch = modstore:get_keys()
|
|
end)
|
|
end
|