storage rewrite wip

This commit is contained in:
BuckarooBanzay 2023-03-27 19:40:37 +02:00 committed by Athozus
parent e08238f50e
commit 894e5df4b1
No known key found for this signature in database
GPG key ID: B50895022E8484BF
7 changed files with 62 additions and 148 deletions

View file

@ -29,6 +29,18 @@ To provide a web-based interface to receive/send mails you can use the [mtui](ht
To access your mail click on the inventory mail button or use the "/mail" command To access your mail click on the inventory mail button or use the "/mail" command
Mails can be deleted, marked as read or unread, replied to and forwarded to another player Mails can be deleted, marked as read or unread, replied to and forwarded to another player
# Compatibility / Migration
Overview:
* `v1` all the data is in the `<worldfolder>/mails.db` file
* `v2` every player has its own (in-) mailbox in the `<worldfolder>/mails/<playername>.json` file
* `v3` every player has an entry in the `<playername>` modstorage (inbox, outbox, contacts)
Mails in the v1 format are supported until commit `b0a5bc7e47ec1c75339e65ec07d0a0ac2b17288b`.
Everything after that assumes either the v2 or v3 is used.
For a v1 to v3 migration the version in `b0a5bc7e47ec1c75339e65ec07d0a0ac2b17288b` has to be at leas run once (startup).
# Dependencies # Dependencies
* None * None

15
gui.lua
View file

@ -109,7 +109,8 @@ end
function mail.show_inbox(name) function mail.show_inbox(name)
local formspec = { mail.inbox_formspec } local formspec = { mail.inbox_formspec }
local messages = mail.getPlayerInboxMessages(name) local entry = mail.get_storage_entry(name)
local messages = entry.inbox
message_drafts[name] = nil message_drafts[name] = nil
@ -509,8 +510,10 @@ function mail.handle_receivefields(player, formname, fields)
elseif formname == "mail:inbox" or formname == "mail:sent" then elseif formname == "mail:inbox" or formname == "mail:sent" then
local name = player:get_player_name() local name = player:get_player_name()
-- split inbox and sent msgs for different tests -- split inbox and sent msgs for different tests
local messagesInbox = mail.getPlayerInboxMessages(name) local entry = mail.get_storage_entry(name)
local messagesSent = mail.getPlayerSentMessages(name)
local messagesInbox = entry.inbox
local messagesSent = entry.outbox
if fields.inbox then -- inbox table if fields.inbox then -- inbox table
local evt = minetest.explode_table_event(fields.inbox) local evt = minetest.explode_table_event(fields.inbox)
@ -629,8 +632,10 @@ function mail.handle_receivefields(player, formname, fields)
elseif formname == "mail:message" then elseif formname == "mail:message" then
local name = player:get_player_name() local name = player:get_player_name()
local messagesInbox = mail.getPlayerInboxMessages(name) local entry = mail.get_storage_entry(name)
local messagesSent = mail.getPlayerSentMessages(name)
local messagesInbox = entry.inbox
local messagesSent = entry.outbox
if fields.back then if fields.back then
if boxtab_index == 1 then if boxtab_index == 1 then

View file

@ -7,7 +7,10 @@ mail = {
-- mail directory -- mail directory
maildir = minetest.get_worldpath().."/mails", maildir = minetest.get_worldpath().."/mails",
contactsdir = minetest.get_worldpath().."/mails/contacts" contactsdir = minetest.get_worldpath().."/mails/contacts",
-- mod storage
storage = minetest.get_mod_storage()
} }

View file

@ -1,58 +1,28 @@
local STORAGE_VERSION_KEY = "@@version"
function mail.migrate() function mail.migrate()
local gen_file_v1 = io.open(minetest.get_worldpath().."/mail.db", "r") local version = mail.storage:get_int(STORAGE_VERSION_KEY)
if gen_file_v1 then if version < 3 then
mail.migrate_v1_to_v2()
end
local info_file_v3 = mail.read_json_file(mail.maildir .. "/mail.info.json")
if not info_file_v3.dbversion then
mail.migrate_v2_to_v3() mail.migrate_v2_to_v3()
mail.storage:set_int(STORAGE_VERSION_KEY, 3)
end end
end end
-- migrate from mail.db to player-file-based mailbox
function mail.migrate_v1_to_v2()
-- create directory, just in case
minetest.mkdir(mail.maildir)
minetest.mkdir(mail.contactsdir)
local file = io.open(minetest.get_worldpath().."/mail.db", "r")
if file then
print("[mail] migrating to new per-player storage")
local data = file:read("*a")
local oldmails = minetest.deserialize(data)
file:close()
for name, oldmessages in pairs(oldmails) do
mail.setMessages(name, oldmessages)
end
-- rename file
print("[mail] migration done, renaming old mail.db")
os.rename(minetest.get_worldpath().."/mail.db", minetest.get_worldpath().."/mail.db.old")
end
end
-- migrate from v2 to v3 database -- migrate from v2 to v3 database
function mail.migrate_v2_to_v3() function mail.migrate_v2_to_v3()
minetest.mkdir(mail.maildir) -- if necessary (eg. first login) minetest.mkdir(mail.maildir) -- if necessary (eg. first login)
minetest.log("info", "[mail] Migration from v2 to v3 database") print("[mail] Migration from v2 to v3 database")
local already_processed = {} -- store messages that are already process to avoid duplicates
minetest.after(0,function() minetest.after(0,function()
for playername, _ in minetest.get_auth_handler().iterate() do for playername, _ in minetest.get_auth_handler().iterate() do
mail.migrate_contacts_v2_to_v3(playername) local player_contacts = mail.read_json_file(mail.maildir .. "/contacts/" .. playername .. ".json")
end local entry = mail.get_storage_entry(playername)
end) for _, c in pairs(player_contacts) do
mail.migrate_messages_v2_to_v3() table.insert(entry.contacts, { name = c.name, note = c.note })
mail.write_json_file(mail.maildir .. "/mail.info.json", { dbversion = 3.0 })
end end
function mail.migrate_messages_v2_to_v3()
local already_processed = {} -- store messages that are already process to avoid duplicates
minetest.after(0,function()
-- check in every inbox to fetch messages
for playername, _ in minetest.get_auth_handler().iterate() do
local saneplayername = string.gsub(playername, "[.|/]", "") local saneplayername = string.gsub(playername, "[.|/]", "")
local player_inbox = mail.read_json_file(mail.maildir .. "/" .. saneplayername .. ".json") local player_inbox = mail.read_json_file(mail.maildir .. "/" .. saneplayername .. ".json")
for _, msg in ipairs(player_inbox) do for _, msg in ipairs(player_inbox) do
@ -79,46 +49,8 @@ function mail.migrate_messages_v2_to_v3()
table.insert(already_processed, msg_id) table.insert(already_processed, msg_id)
end end
end end
mail.set_storage_entry(playername, entry)
end end
end) end)
end end
function mail.migrate_contacts(playername)
local gen_file_v1 = io.open(minetest.get_worldpath().."/mail.db", "r")
if gen_file_v1 then
mail.migrate_contacts_v1_to_v2(playername)
end
-- v2 to v3 directly in general function
end
function mail.migrate_contacts_v1_to_v2(playername)
local file = io.open(mail.getContactsFile(playername), 'r')
if not file then
-- file doesn't exist! This is a case for Migrate Man!
local messages = mail.getMessages(playername)
local contacts = {}
if messages and not contacts then
for _, message in pairs(messages) do
mail.ensure_new_format(message)
if contacts[string.lower(message.from)] == nil then
contacts[string.lower(message.from)] = {
name = message.from,
note = "",
}
end
end
end
else
file:close() -- uh, um, nope, let's leave those alone, shall we?
end
end
function mail.migrate_contacts_v2_to_v3(playername)
local player_contacts = mail.read_json_file(mail.maildir .. "/contacts/" .. playername .. ".json")
for _, c in pairs(player_contacts) do
mail.addContact(playername, { name = c.name, note = c.note })
end
end

View file

@ -7,7 +7,7 @@ mtt.register("send mail", function(callback)
mail.send({from = "player1", to = "player2", subject = "something", body = "blah"}) mail.send({from = "player1", to = "player2", subject = "something", body = "blah"})
-- check the receivers inbox -- check the receivers inbox
local list2 = mail.getPlayerInboxMessages("player2") local entry = mail.get_storage_entry("player2")
assert(list2 ~= nil and #list2 > 0) assert(entry ~= nil and #entry.inbox > 0)
callback() callback()
end) end)

View file

@ -1,6 +1,6 @@
minetest.register_on_joinplayer(function(player) minetest.register_on_joinplayer(function(player)
minetest.after(2, function(name) minetest.after(2, function(name)
local messages = mail.getPlayerMessages(name) local messages = mail.getMessages(name)
local unreadcount = 0 local unreadcount = 0
@ -16,6 +16,4 @@ minetest.register_on_joinplayer(function(player)
end end
end, player:get_player_name()) end, player:get_player_name())
mail.migrate_contacts(player:get_player_name())
end) end)

View file

@ -1,26 +1,20 @@
function mail.getMailFile(playername) function mail.get_storage_entry(playername)
local saneplayername = string.gsub(playername, "[.|/]", "") local str = mail.storage:get_string(playername)
return mail.maildir .. "/" .. saneplayername .. ".json" if str == "" then
end -- new entry
return {
function mail.getMessages(playername) contacts = {},
if (playername) then inbox = {},
return mail.getPlayerMessages(playername) outbox = {}
end }
local messages = mail.read_json_file(mail.maildir .. "/mail.messages.json") else
if messages then -- deserialize existing entry
for _, msg in ipairs(messages) do return minetest.parse_json(str)
if not msg.time then
-- add missing time field if not available (happens with old data)
msg.time = 0
end end
end end
-- sort by received date descending function mail.set_storage_entry(playername, entry)
table.sort(messages, function(a,b) return a.time > b.time end) mail.storage:get_string(playername, minetest.write_json(entry))
end
return messages
end end
function mail.getMessage(msg_id) function mail.getMessage(msg_id)
@ -34,7 +28,8 @@ function mail.getMessage(msg_id)
end end
end end
function mail.getPlayerMessages(playername) -- api in use by the `fancyvend` mod
function mail.getMessages(playername)
local messages = mail.getMessages() local messages = mail.getMessages()
local playerMessages = {} local playerMessages = {}
if messages then if messages then
@ -68,37 +63,6 @@ function mail.getPlayerMessages(playername)
return playerMessages return playerMessages
end end
function mail.getPlayerInboxMessages(playername)
local messages = mail.getMessages()
local playerInboxMessages = {}
if messages then
for _, msg in ipairs(messages) do
local cc = ""
local bcc = ""
if msg.cc then
cc = msg.cc
end
if msg.bcc then
bcc = msg.bcc
end
local receivers = (msg.to .. "," .. cc .. "," .. bcc):split(",")
for _, receiver in ipairs(receivers) do
receiver = string.gsub(receiver, " ", "") -- avoid blank spaces (ex : " singleplayer" instead of "singleplayer")
if receiver == playername then -- check if player is a receiver
if mail.getMessageStatus(receiver, msg.id) ~= "deleted" then -- do not return if the message was deleted by player
table.insert(playerInboxMessages, msg)
break
end
end
end
end
-- show hud notification
mail.hud_update(playername, playerInboxMessages)
end
return playerInboxMessages
end
function mail.getPlayerSentMessages(playername) function mail.getPlayerSentMessages(playername)
local messages = mail.getMessages() local messages = mail.getMessages()
local playerSentMessages = {} local playerSentMessages = {}