diff --git a/api.lua b/api.lua index bd79ad2..49af911 100644 --- a/api.lua +++ b/api.lua @@ -97,8 +97,17 @@ function mail.send(...) time = os.time(), } - -- insert in global storage - mail.addMessage(msg) + -- add in senders outbox + local entry = mail.get_storage_entry(m.from) + table.insert(entry.outbox, msg) + mail.set_storage_entry(m.from, entry) + + -- add in every receivers inbox + for recipient in pairs(recipients) do + entry = mail.get_storage_entry(recipient) + table.insert(entry.inbox, msg) + mail.set_storage_entry(recipient, entry) + end -- notify recipients that happen to be online local mail_alert = f(mail.receive_mail_message, m.from, m.subject) diff --git a/init.lua b/init.lua index 8f7057d..2f635f2 100644 --- a/init.lua +++ b/init.lua @@ -1,9 +1,6 @@ mail = { - -- api version - apiversion = 1.1, - - -- database version - dbversion = 3.0, + -- version + version = 3, -- mail directory maildir = minetest.get_worldpath().."/mails", @@ -39,6 +36,7 @@ end local MP = minetest.get_modpath(minetest.get_current_modname()) dofile(MP .. "/util/normalize.lua") +dofile(MP .. "/util/uuid.lua") dofile(MP .. "/chatcommands.lua") dofile(MP .. "/migrate.lua") dofile(MP .. "/hud.lua") diff --git a/migrate.lua b/migrate.lua index b6829e8..2072b02 100644 --- a/migrate.lua +++ b/migrate.lua @@ -13,12 +13,12 @@ end function mail.migrate_v2_to_v3() minetest.mkdir(mail.maildir) -- if necessary (eg. first login) 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 - local player_contacts = mail.read_json_file(mail.maildir .. "/contacts/" .. playername .. ".json") local entry = mail.get_storage_entry(playername) + + local player_contacts = mail.read_json_file(mail.maildir .. "/contacts/" .. playername .. ".json") for _, c in pairs(player_contacts) do table.insert(entry.contacts, { name = c.name, note = c.note }) end @@ -26,27 +26,16 @@ function mail.migrate_v2_to_v3() local saneplayername = string.gsub(playername, "[.|/]", "") local player_inbox = mail.read_json_file(mail.maildir .. "/" .. saneplayername .. ".json") for _, msg in ipairs(player_inbox) do - -- id like "123456789.0singleplayer" -- it presumes that a same sender cannot send two mails within a second - local msg_id = tostring(msg.time) .. msg.sender - local new_msg = true -- check if that mail was already processed with another player - for _, cur_id in ipairs(already_processed) do - if cur_id == msg_id then - new_msg = false - break - end - end - -- add if valid and "to" field populated (missing in ancient storage formats) - if new_msg and msg.to then - local msg_table = { + if msg.to then + table.insert(entry.inbox, { + id = mail.new_uuid(), sender = msg.sender, to = msg.to, cc = msg.cc, subject = msg.subject, body = msg.body, time = msg.time, - } - mail.addMessage(msg_table) - table.insert(already_processed, msg_id) + }) end end diff --git a/onjoin.lua b/onjoin.lua index 21b47d5..0bf36d2 100644 --- a/onjoin.lua +++ b/onjoin.lua @@ -1,6 +1,7 @@ minetest.register_on_joinplayer(function(player) minetest.after(2, function(name) - local messages = mail.getMessages(name) + local entry = mail.get_storage_entry(name) + local messages = entry.inbox local unreadcount = 0 diff --git a/storage.lua b/storage.lua index df1d450..7e9f195 100644 --- a/storage.lua +++ b/storage.lua @@ -59,157 +59,65 @@ function mail.set_storage_entry(playername, entry) mail.storage:get_string(playername, minetest.write_json(entry)) end -function mail.getMessage(msg_id) - local messages = mail.getMessages() - if messages then - for _, msg in ipairs(messages) do - if msg.id == msg_id then - return msg - end +-- get a mail by id from the players in- or outbox +function mail.get_message(playername, msg_id) + local entry = mail.get_storage_entry(playername) + for _, msg in ipairs(entry.inbox) do + if msg.id == msg_id then + return msg + end + end + for _, msg in ipairs(entry.outbox) do + if msg.id == msg_id then + return msg end end end --- api in use by the `fancyvend` mod -function mail.getMessages(playername) - local messages = mail.getMessages() - local playerMessages = {} - 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(playerMessages, msg) - break - end - elseif msg.sender == playername then - if mail.getMessageStatus(receiver, msg.id) ~= "deleted" then -- do not return if the message was deleted by player - table.insert(playerMessages, msg) - break - end - end - end - end - end - - return playerMessages -end - -function mail.getPlayerSentMessages(playername) - local messages = mail.getMessages() - local playerSentMessages = {} - if messages[1] then - for _, msg in ipairs(messages) do - if msg.sender == playername then -- check if player is the sender - -- do not return if the message was deleted from player - if mail.getMessageStatus(playername, msg.id) ~= "deleted" then - table.insert(playerSentMessages, msg) - end - end - end - end - - return playerSentMessages -end - -function mail.setMessages(playername, messages) - if mail.write_json_file(mail.getMailFile(playername), messages) then - mail.hud_update(playername, messages) - return true - else - minetest.log("error","[mail] Save failed - messages may be lost! ("..playername..")") - return false - end -end - -function mail.addMessage(message) - local messages = mail.getMessages() - if messages[1] then - local previousMsg = messages[1] - message.id = previousMsg.id + 1 - table.insert(messages, message) - else - message.id = 1 - messages = {message} - end - if mail.write_json_file(mail.maildir .. "/mail.messages.json", messages) then - -- add default status (unread for receivers) of this message - local isSenderAReceiver = false - - -- extracted maillists from all receivers - local receivers = mail.extractMaillists((message.to .. "," .. (message.cc or "") - .. "," .. (message.bcc or "")), message.sender) - - for _, receiver in ipairs(receivers) do - if minetest.player_exists(receiver) then -- avoid blank names - mail.addStatus(receiver, message.id, "unread") - if message.sender == receiver then - isSenderAReceiver = true - end - end - end - - if isSenderAReceiver == false then - mail.addStatus(message.sender, message.id, "read") - end - return true - else - minetest.log("error","[mail] Save failed - messages may be lost!") - return false - end -end - -function mail.getStatus() - local messagesStatus = mail.read_json_file(mail.maildir .. "/mail.status.json") - return messagesStatus -end - -function mail.getMessageStatus(player, msg_id) - local messagesStatus = mail.getStatus() - for _, msg in ipairs(messagesStatus) do - if msg.id == msg_id and msg.player == player then - return msg.status +-- marks a mail read by its id +function mail.mark_read(playername, msg_id) + local entry = mail.get_storage_entry(playername) + for _, msg in ipairs(entry.inbox) do + if msg.id == msg_id then + msg.read = true + mail.set_storage_entry(playername, entry) + return end end end -function mail.addStatus(player, msg_id, status) - local messagesStatus = mail.getStatus() - local msg_status = {id = msg_id, player = player, status = status} - table.insert(messagesStatus, msg_status) - if mail.write_json_file(mail.maildir .. "/mail.status.json", messagesStatus) then - return true - else - minetest.log("error","[mail] Save failed - messages status may be lost!") - return false +-- marks a mail unread by its id +function mail.mark_unread(playername, msg_id) + local entry = mail.get_storage_entry(playername) + for _, msg in ipairs(entry.inbox) do + if msg.id == msg_id then + msg.read = false + mail.set_storage_entry(playername, entry) + return + end end end -function mail.setStatus(player, msg_id, status) - local messagesStatus = mail.getStatus() - for _, msg_status in ipairs(messagesStatus) do - if msg_status.id == msg_id and msg_status.player == player then - messagesStatus[_] = {id = msg_id, player = player, status = status} +-- deletes a mail by its id +function mail.delete_mail(playername, msg_id) + local entry = mail.get_storage_entry(playername) + for i, msg in ipairs(entry.inbox) do + if msg.id == msg_id then + table.remove(entry.outbox, i) + mail.set_storage_entry(playername, entry) + return end end - if mail.write_json_file(mail.maildir .. "/mail.status.json", messagesStatus) then - return true - else - minetest.log("error","[mail] Save failed - messages status may be lost!") - return false + for i, msg in ipairs(entry.outbox) do + if msg.id == msg_id then + table.remove(entry.outbox, i) + mail.set_storage_entry(playername, entry) + return + end end end + function mail.getContactsFile() return mail.maildir .. "/mail.contacts.json" end diff --git a/ui/inbox.lua b/ui/inbox.lua index 3396a4b..4302179 100644 --- a/ui/inbox.lua +++ b/ui/inbox.lua @@ -28,7 +28,7 @@ function mail.show_inbox(name) if messages[1] then for _, message in ipairs(messages) do - if mail.getMessageStatus(name, message.id) == "unread" then + if not message.read then if not mail.player_in_list(name, message.to) then formspec[#formspec + 1] = ",#FFD788" else @@ -114,9 +114,9 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) elseif fields.delete then if formname == "mail:inbox" and messagesInbox[mail.selected_idxs.inbox[name]] then -- inbox table - mail.setStatus(name, messagesInbox[mail.selected_idxs.inbox[name]].id, "deleted") + mail.delete_mail(name, messagesInbox[mail.selected_idxs.inbox[name]].id) elseif formname == "mail:sent" and messagesSent[mail.selected_idxs.sent[name]] then -- sent table - mail.setStatus(name, messagesSent[mail.selected_idxs.sent[name]].id, "deleted") + mail.delete_mail(name, messagesSent[mail.selected_idxs.sent[name]].id) end mail.show_mail_menu(name) @@ -150,18 +150,18 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) elseif fields.markread then if formname == "mail:inbox" and messagesInbox[mail.selected_idxs.inbox[name]] then - mail.setStatus(name, messagesInbox[mail.selected_idxs.inbox[name]].id, "read") + mail.mark_read(name, messagesInbox[mail.selected_idxs.inbox[name]].id) elseif formname == "mail:sent" and messagesSent[mail.selected_idxs.sent[name]] then - mail.setStatus(name, messagesSent[mail.selected_idxs.sent[name]].id, "read") + mail.mark_read(name, messagesSent[mail.selected_idxs.sent[name]].id) end mail.show_mail_menu(name) elseif fields.markunread then if formname == "mail:inbox" and messagesInbox[mail.selected_idxs.inbox[name]] then - mail.setStatus(name, messagesInbox[mail.selected_idxs.inbox[name]].id, "unread") + mail.mark_unread(name, messagesInbox[mail.selected_idxs.inbox[name]].id) elseif formname == "mail:sent" and messagesSent[mail.selected_idxs.sent[name]] then - mail.setStatus(name, messagesSent[mail.selected_idxs.sent[name]].id, "unread") + mail.mark_unread(name, messagesSent[mail.selected_idxs.sent[name]].id) end mail.show_mail_menu(name) diff --git a/ui/message.lua b/ui/message.lua index a7b3839..e869667 100644 --- a/ui/message.lua +++ b/ui/message.lua @@ -1,7 +1,8 @@ local FORMNAME = "mail:message" -function mail.show_message(name, msgnumber) - local message = mail.getMessage(msgnumber) +function mail.show_message(name, id) + local message = mail.get_message(name, id) + local formspec = [[ size[8,9] @@ -32,10 +33,9 @@ function mail.show_message(name, msgnumber) local body = minetest.formspec_escape(message.body) or "" formspec = string.format(formspec, from, to, cc, date, subject, body) - local message_status = mail.getMessageStatus(name, message.id) - - if message_status == "unread" then - mail.setStatus(name, message.id, "read") + if not message.read then + -- mark as read + mail.mark_read(name, id) end minetest.show_formspec(name, FORMNAME, formspec) @@ -125,9 +125,9 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) elseif fields.delete then if messagesInbox[mail.selected_idxs.inbox[name]] then - mail.setStatus(name, messagesInbox[mail.selected_idxs.inbox[name]].id, "deleted") + mail.delete_mail(name, messagesInbox[mail.selected_idxs.inbox[name]].id) elseif messagesSent[mail.selected_idxs.sent[name]] then - mail.setStatus(name, messagesSent[mail.selected_idxs.sent[name]].id, "deleted") + mail.delete_mail(name, messagesSent[mail.selected_idxs.sent[name]].id) end mail.show_mail_menu(name) end diff --git a/ui/outbox.lua b/ui/outbox.lua index 466bf3e..ae980a7 100644 --- a/ui/outbox.lua +++ b/ui/outbox.lua @@ -19,7 +19,8 @@ local sent_formspec = "size[8,10;]" .. mail.theme .. [[ function mail.show_sent(name) local formspec = { sent_formspec } - local messages = mail.getPlayerSentMessages(name) + local entry = mail.get_storage_entry(name) + local messages = entry.outbox mail.message_drafts[name] = nil diff --git a/util/uuid.lua b/util/uuid.lua new file mode 100644 index 0000000..571aa4a --- /dev/null +++ b/util/uuid.lua @@ -0,0 +1,9 @@ +-- source: https://gist.github.com/jrus/3197011 +local random = math.random +function mail.new_uuid() + local template ='xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx' + return string.gsub(template, '[xy]', function (c) + local v = (c == 'x') and random(0, 0xf) or random(8, 0xb) + return string.format('%x', v) + end) +end \ No newline at end of file