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
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
* None

15
gui.lua
View file

@ -109,7 +109,8 @@ end
function mail.show_inbox(name)
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
@ -509,8 +510,10 @@ function mail.handle_receivefields(player, formname, fields)
elseif formname == "mail:inbox" or formname == "mail:sent" then
local name = player:get_player_name()
-- split inbox and sent msgs for different tests
local messagesInbox = mail.getPlayerInboxMessages(name)
local messagesSent = mail.getPlayerSentMessages(name)
local entry = mail.get_storage_entry(name)
local messagesInbox = entry.inbox
local messagesSent = entry.outbox
if fields.inbox then -- inbox table
local evt = minetest.explode_table_event(fields.inbox)
@ -629,8 +632,10 @@ function mail.handle_receivefields(player, formname, fields)
elseif formname == "mail:message" then
local name = player:get_player_name()
local messagesInbox = mail.getPlayerInboxMessages(name)
local messagesSent = mail.getPlayerSentMessages(name)
local entry = mail.get_storage_entry(name)
local messagesInbox = entry.inbox
local messagesSent = entry.outbox
if fields.back then
if boxtab_index == 1 then

View file

@ -7,7 +7,10 @@ mail = {
-- mail directory
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()
local gen_file_v1 = io.open(minetest.get_worldpath().."/mail.db", "r")
if gen_file_v1 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
local version = mail.storage:get_int(STORAGE_VERSION_KEY)
if version < 3 then
mail.migrate_v2_to_v3()
mail.storage:set_int(STORAGE_VERSION_KEY, 3)
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
function mail.migrate_v2_to_v3()
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()
for playername, _ in minetest.get_auth_handler().iterate() do
mail.migrate_contacts_v2_to_v3(playername)
end
end)
mail.migrate_messages_v2_to_v3()
mail.write_json_file(mail.maildir .. "/mail.info.json", { dbversion = 3.0 })
local player_contacts = mail.read_json_file(mail.maildir .. "/contacts/" .. playername .. ".json")
local entry = mail.get_storage_entry(playername)
for _, c in pairs(player_contacts) do
table.insert(entry.contacts, { name = c.name, note = c.note })
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 player_inbox = mail.read_json_file(mail.maildir .. "/" .. saneplayername .. ".json")
for _, msg in ipairs(player_inbox) do
@ -79,46 +49,8 @@ function mail.migrate_messages_v2_to_v3()
table.insert(already_processed, msg_id)
end
end
mail.set_storage_entry(playername, entry)
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"})
-- check the receivers inbox
local list2 = mail.getPlayerInboxMessages("player2")
assert(list2 ~= nil and #list2 > 0)
local entry = mail.get_storage_entry("player2")
assert(entry ~= nil and #entry.inbox > 0)
callback()
end)

View file

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

View file

@ -1,26 +1,20 @@
function mail.getMailFile(playername)
local saneplayername = string.gsub(playername, "[.|/]", "")
return mail.maildir .. "/" .. saneplayername .. ".json"
end
function mail.getMessages(playername)
if (playername) then
return mail.getPlayerMessages(playername)
end
local messages = mail.read_json_file(mail.maildir .. "/mail.messages.json")
if messages then
for _, msg in ipairs(messages) do
if not msg.time then
-- add missing time field if not available (happens with old data)
msg.time = 0
function mail.get_storage_entry(playername)
local str = mail.storage:get_string(playername)
if str == "" then
-- new entry
return {
contacts = {},
inbox = {},
outbox = {}
}
else
-- deserialize existing entry
return minetest.parse_json(str)
end
end
-- sort by received date descending
table.sort(messages, function(a,b) return a.time > b.time end)
end
return messages
function mail.set_storage_entry(playername, entry)
mail.storage:get_string(playername, minetest.write_json(entry))
end
function mail.getMessage(msg_id)
@ -34,7 +28,8 @@ function mail.getMessage(msg_id)
end
end
function mail.getPlayerMessages(playername)
-- api in use by the `fancyvend` mod
function mail.getMessages(playername)
local messages = mail.getMessages()
local playerMessages = {}
if messages then
@ -68,37 +63,6 @@ function mail.getPlayerMessages(playername)
return playerMessages
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)
local messages = mail.getMessages()
local playerSentMessages = {}