mod storage (#67)
* persistence in mod-storage wip * remove `travelnet.restore_data` * refactor saving/loading with mod_storage * migration test * fix typo / handle some edge cases --------- Co-authored-by: BuckarooBanzay <BuckarooBanzay@users.noreply.github.com>
This commit is contained in:
parent
cc7e335034
commit
8d1c55da30
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@ -9,7 +9,7 @@ jobs:
|
||||
timeout-minutes: 10
|
||||
strategy:
|
||||
matrix:
|
||||
ENGINE_VERSION: [5.3.0, 5.4.0, 5.5.0, 5.6.0, latest]
|
||||
ENGINE_VERSION: [5.3.0, 5.4.0, 5.5.0, 5.6.0, 5.7.0, latest]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
@ -45,7 +45,12 @@ return function (node_info, fields, player)
|
||||
"the network of someone else. Aborting.")
|
||||
end
|
||||
|
||||
local network = travelnet.get_or_create_network(owner_name, station_network)
|
||||
local travelnets = travelnet.get_travelnets(owner_name)
|
||||
local network = travelnets[station_network]
|
||||
if not network then
|
||||
network = {}
|
||||
travelnets[station_network] = network
|
||||
end
|
||||
|
||||
-- lua doesn't allow efficient counting here
|
||||
local station_count = 1 -- start at one, assume the station about to be created already exists
|
||||
@ -88,7 +93,7 @@ return function (node_info, fields, player)
|
||||
tostring(station_name), tostring(station_network), tostring(owner_name)))
|
||||
|
||||
-- save the updated network data in a savefile over server restart
|
||||
travelnet.save_data(owner_name)
|
||||
travelnet.set_travelnets(owner_name, travelnets)
|
||||
|
||||
return true, { formspec = travelnet.formspecs.primary, options = {
|
||||
station_name = station_name,
|
||||
|
@ -13,7 +13,8 @@ return function (node_info, fields, player)
|
||||
or (minetest.get_player_privs(player_name)[travelnet.attach_priv])
|
||||
)
|
||||
then
|
||||
local network = travelnet.get_network(node_info.props.owner_name, node_info.props.station_network)
|
||||
local travelnets = travelnet.get_travelnets(node_info.props.owner_name)
|
||||
local network = travelnets[node_info.props.station_network]
|
||||
|
||||
if not network then
|
||||
return false, S("This station does not have a network.")
|
||||
@ -61,7 +62,7 @@ return function (node_info, fields, player)
|
||||
end
|
||||
|
||||
-- store the changed order
|
||||
travelnet.save_data(player_name)
|
||||
travelnet.set_travelnets(player_name, travelnets)
|
||||
return true, { formspec = travelnet.formspecs.primary }
|
||||
end
|
||||
end
|
||||
|
@ -15,25 +15,31 @@ return function (node_info, _, player)
|
||||
return false, S("Update failed! Resetting this box on the travelnet.")
|
||||
end
|
||||
|
||||
-- if the station got lost from the network for some reason (savefile corrupted?) then add it again
|
||||
if not travelnet.get_station(owner_name, station_network, station_name) then
|
||||
local network = travelnet.get_or_create_network(owner_name, station_network)
|
||||
local travelnets = travelnet.get_travelnets(owner_name)
|
||||
local network = travelnets[station_network]
|
||||
if not network then
|
||||
network = {}
|
||||
travelnets[station_network] = network
|
||||
end
|
||||
|
||||
local zeit = node_info.meta:get_int("timestamp")
|
||||
if not zeit or type(zeit) ~= "number" or zeit < 100000 then
|
||||
zeit = os.time()
|
||||
-- if the station got lost from the network for some reason (savefile corrupted?) then add it again
|
||||
if not network[station_name] then
|
||||
local timestamp = node_info.meta:get_int("timestamp")
|
||||
if not timestamp or type(timestamp) ~= "number" or timestamp < 100000 then
|
||||
timestamp = os.time()
|
||||
end
|
||||
|
||||
-- add this station
|
||||
network[station_name] = {
|
||||
pos = node_info.pos,
|
||||
timestamp = zeit
|
||||
timestamp = timestamp
|
||||
}
|
||||
|
||||
minetest.chat_send_player(owner_name,
|
||||
S("Station '@1'" .. " " ..
|
||||
"has been reattached to the network '@2'.", station_name, station_network))
|
||||
travelnet.save_data(owner_name)
|
||||
|
||||
travelnet.set_travelnets(owner_name, travelnets)
|
||||
end
|
||||
return true, { formspec = travelnet.formspecs.primary }
|
||||
end
|
||||
|
@ -54,7 +54,13 @@ return function (node_info, fields, player)
|
||||
)
|
||||
end
|
||||
|
||||
local network = travelnet.get_network(owner_name, station_network)
|
||||
local travelnets = travelnet.get_travelnets(owner_name)
|
||||
local network = travelnets[station_network]
|
||||
if not network then
|
||||
network = {}
|
||||
travelnets[station_name] = network
|
||||
end
|
||||
|
||||
-- does a station with the new name already exist?
|
||||
if network[fields.station_name] then
|
||||
return false, S('Station "@1" already exists on network "@2".',
|
||||
@ -84,7 +90,7 @@ return function (node_info, fields, player)
|
||||
tostring(fields.station_name), tostring(station_network), tostring(owner_name)))
|
||||
|
||||
-- save the updated network data in a savefile over server restart
|
||||
travelnet.save_data(owner_name)
|
||||
travelnet.set_travelnets(owner_name, travelnets)
|
||||
|
||||
return true, { formspec = travelnet.formspecs.primary, options = {
|
||||
station_name = fields.station_name
|
||||
|
@ -70,7 +70,6 @@ return function (node_info, fields, player)
|
||||
)
|
||||
end
|
||||
|
||||
local network
|
||||
local timestamp = os.time()
|
||||
if owner_name ~= fields.owner_name then
|
||||
if not minetest.get_player_privs(player_name)[travelnet.attach_priv]
|
||||
@ -82,31 +81,44 @@ return function (node_info, fields, player)
|
||||
-- new owner -> remove station from old network then add to new owner
|
||||
-- but only if there is space on the network
|
||||
-- get the new network
|
||||
network = travelnet.get_or_create_network(fields.owner_name, fields.station_network)
|
||||
|
||||
local old_travelnets = travelnet.get_travelnets(owner_name)
|
||||
local new_travelnets = travelnet.get_travelnets(fields.owner_name)
|
||||
|
||||
local new_network = new_travelnets[fields.station_network]
|
||||
if not new_network then
|
||||
new_network = {}
|
||||
new_travelnets[fields.station_network] = new_network
|
||||
end
|
||||
|
||||
-- does a station with the new name already exist?
|
||||
if network[fields.station_name] then
|
||||
if new_network[fields.station_name] then
|
||||
return false, S('Station "@1" already exists on network "@2" of player "@3".',
|
||||
fields.station_name, fields.station_network, fields.owner_name)
|
||||
end
|
||||
|
||||
-- does the new network have space at all?
|
||||
if travelnet.MAX_STATIONS_PER_NETWORK ~= 0 and 1 + #network > travelnet.MAX_STATIONS_PER_NETWORK then
|
||||
if travelnet.MAX_STATIONS_PER_NETWORK ~= 0 and 1 + #new_network > travelnet.MAX_STATIONS_PER_NETWORK then
|
||||
return false,
|
||||
S('Network "@1", already contains the maximum number (@2) of '
|
||||
.. 'allowed stations per network. Please choose a '
|
||||
.. 'different network name.', fields.station_network,
|
||||
travelnet.MAX_STATIONS_PER_NETWORK)
|
||||
end
|
||||
|
||||
-- get the old network
|
||||
local old_network = travelnet.get_network(owner_name, station_network)
|
||||
local old_network = old_travelnets[station_network]
|
||||
if not old_network then
|
||||
print("TRAVELNET: failed to get old network when re-owning "
|
||||
.. "travelnet/elevator at pos " .. minetest.pos_to_string(pos))
|
||||
return false, S("Station does not have network.")
|
||||
end
|
||||
|
||||
-- remove old station from old network
|
||||
old_network[station_name] = nil
|
||||
-- add new station to new network
|
||||
network[fields.station_name] = { pos = pos, timestamp = timestamp }
|
||||
new_network[fields.station_name] = { pos = pos, timestamp = timestamp }
|
||||
|
||||
-- update meta
|
||||
meta:set_string("station_name", fields.station_name)
|
||||
meta:set_string("station_network", fields.station_network)
|
||||
@ -124,16 +136,27 @@ return function (node_info, fields, player)
|
||||
new_owner_name = fields.owner_name
|
||||
new_station_network = fields.station_network
|
||||
new_station_name = fields.station_name
|
||||
|
||||
travelnet.set_travelnets(owner_name, old_travelnets)
|
||||
travelnet.set_travelnets(fields.owner_name, new_travelnets)
|
||||
|
||||
elseif station_network ~= fields.station_network then
|
||||
-- same owner but different network -> remove station from old network
|
||||
-- but only if there is space on the new network and no other station with that name
|
||||
-- get the new network
|
||||
network = travelnet.get_or_create_network(owner_name, fields.station_network)
|
||||
local travelnets = travelnet.get_travelnets(owner_name)
|
||||
local network = travelnets[fields.station_network]
|
||||
if not network then
|
||||
network = {}
|
||||
travelnets[fields.station_network] = network
|
||||
end
|
||||
|
||||
-- does a station with the new name already exist?
|
||||
if network[fields.station_name] then
|
||||
return false, S('Station "@1" already exists on network "@2".',
|
||||
fields.station_name, fields.station_network)
|
||||
end
|
||||
|
||||
-- does the new network have space at all?
|
||||
if travelnet.MAX_STATIONS_PER_NETWORK ~= 0 and 1 + #network > travelnet.MAX_STATIONS_PER_NETWORK then
|
||||
return false,
|
||||
@ -143,7 +166,7 @@ return function (node_info, fields, player)
|
||||
travelnet.MAX_STATIONS_PER_NETWORK)
|
||||
end
|
||||
-- get the old network
|
||||
local old_network = travelnet.get_network(owner_name, station_network)
|
||||
local old_network = travelnets[station_network]
|
||||
if not old_network then
|
||||
print("TRAVELNET: failed to get old network when re-networking "
|
||||
.. "travelnet/elevator at pos " .. minetest.pos_to_string(pos))
|
||||
@ -166,9 +189,18 @@ return function (node_info, fields, player)
|
||||
|
||||
new_station_network = fields.station_network
|
||||
new_station_name = fields.station_name
|
||||
|
||||
travelnet.set_travelnets(owner_name, travelnets)
|
||||
|
||||
else
|
||||
-- only name changed -> change name but keep timestamp to preserve order
|
||||
network = travelnet.get_network(owner_name, station_network)
|
||||
local travelnets = travelnet.get_travelnets(owner_name)
|
||||
local network = travelnets[station_network]
|
||||
if not network then
|
||||
network = {}
|
||||
travelnets[station_network] = network
|
||||
end
|
||||
|
||||
-- does a station with the new name already exist?
|
||||
if network[fields.station_name] then
|
||||
return false, S('Station "@1" already exists on network "@2".',
|
||||
@ -192,6 +224,7 @@ return function (node_info, fields, player)
|
||||
station_name, fields.station_name, station_network))
|
||||
|
||||
new_station_name = fields.station_name
|
||||
travelnet.set_travelnets(owner_name, travelnets)
|
||||
end
|
||||
|
||||
meta:set_string("infotext",
|
||||
@ -203,12 +236,6 @@ return function (node_info, fields, player)
|
||||
tostring(new_owner_name or owner_name)
|
||||
))
|
||||
|
||||
-- save the updated network data
|
||||
travelnet.save_data(owner_name)
|
||||
if new_owner_name then
|
||||
travelnet.save_data(new_owner_name)
|
||||
end
|
||||
|
||||
return true, { formspec = travelnet.formspecs.primary, options = {
|
||||
station_name = new_station_name or station_name,
|
||||
station_network = new_station_network or station_network,
|
||||
|
19
doc/api.md
19
doc/api.md
@ -21,26 +21,11 @@ Sets and saves the updated travelnet data for the player
|
||||
-- retrieve the player-data
|
||||
local travelnets = travelnet.get_travelnets(playername)
|
||||
-- add a station stub
|
||||
travelnets["my_networks"] = {}
|
||||
-- save the modified data (calls `travelnet.save_data()` to persist the data)
|
||||
travelnets["my_network"] = {}
|
||||
-- save the modified data
|
||||
travelnet.set_travelnets(playername, travelnets)
|
||||
```
|
||||
|
||||
## travelnet.save_data(playername)
|
||||
|
||||
Saves the runtime travelnet data to disk
|
||||
Can be used in place of `travelnet.set_travelnets` to save all player travelnet data that was modified
|
||||
|
||||
```lua
|
||||
-- save
|
||||
local travelnets = travelnet.get_travelnets(playername)
|
||||
-- call other function that might modify the data
|
||||
other_fn(travelnet)
|
||||
-- call "save_data" directly to persist changes of the runtime data
|
||||
travelnet.save_data(playername)
|
||||
```
|
||||
|
||||
|
||||
## travelnet.register_travelnet_box
|
||||
|
||||
Lets you register your own travelnet boxes with a custom color, name and dye ingredient
|
||||
|
3
init.lua
3
init.lua
@ -25,7 +25,6 @@ end
|
||||
travelnet = {}
|
||||
|
||||
travelnet.player_formspec_data = {}
|
||||
travelnet.targets = {}
|
||||
travelnet.path = minetest.get_modpath(minetest.get_current_modname())
|
||||
|
||||
local function mod_dofile(filename)
|
||||
@ -135,9 +134,9 @@ if travelnet.enable_abm then
|
||||
end
|
||||
|
||||
-- upon server start, read the savefile
|
||||
travelnet.restore_data()
|
||||
travelnet.player_formspec_data = nil
|
||||
|
||||
if minetest.get_modpath("mtt") and mtt.enabled then
|
||||
mod_dofile("mtt")
|
||||
mod_dofile("persistence.spec")
|
||||
end
|
@ -2,57 +2,48 @@ local S = minetest.get_translator("travelnet")
|
||||
|
||||
local mod_data_path = minetest.get_worldpath() .. "/mod_travelnet.data"
|
||||
|
||||
-- called whenever a station is added or removed
|
||||
function travelnet.save_data()
|
||||
local data = minetest.write_json(travelnet.targets)
|
||||
local storage = minetest.get_mod_storage()
|
||||
|
||||
local success = minetest.safe_file_write(mod_data_path, data)
|
||||
if not success then
|
||||
print(S("[Mod travelnet] Error: Savefile '@1' could not be written.", mod_data_path))
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function travelnet.restore_data()
|
||||
-- migrate file-based storage to mod-storage
|
||||
local function migrate_file_storage()
|
||||
local file = io.open(mod_data_path, "r")
|
||||
if not file then
|
||||
print(S("[Mod travelnet] Error: Savefile '@1' not found.", mod_data_path))
|
||||
return
|
||||
end
|
||||
|
||||
-- load from file
|
||||
local data = file:read("*all")
|
||||
local old_targets
|
||||
if data:sub(1, 1) == "{" then
|
||||
travelnet.targets = minetest.parse_json(data)
|
||||
minetest.log("info", S("[travelnet] migrating from json-file to mod-storage"))
|
||||
old_targets = minetest.parse_json(data)
|
||||
else
|
||||
travelnet.targets = minetest.deserialize(data)
|
||||
minetest.log("info", S("[travelnet] migrating from serialize-file to mod-storage"))
|
||||
old_targets = minetest.deserialize(data)
|
||||
end
|
||||
|
||||
if not travelnet.targets then
|
||||
local backup_file = mod_data_path .. ".bak"
|
||||
print(S("[Mod travelnet] Error: Savefile '@1' is damaged." .. " " ..
|
||||
"Saved the backup as '@2'.", mod_data_path, backup_file))
|
||||
|
||||
minetest.safe_file_write(backup_file, data)
|
||||
travelnet.targets = {}
|
||||
for playername, player_targets in pairs(old_targets) do
|
||||
storage:set_string(playername, minetest.write_json(player_targets))
|
||||
end
|
||||
file:close()
|
||||
|
||||
-- rename old file
|
||||
os.rename(mod_data_path, mod_data_path .. ".bak")
|
||||
end
|
||||
|
||||
-- getter/setter for the legacy `travelnet.targets` table
|
||||
-- use those methods to access the per-player data, direct table access is deprecated
|
||||
-- and will be removed in the future
|
||||
-- migrate old data as soon as possible
|
||||
migrate_file_storage()
|
||||
|
||||
-- returns the player's travelnets
|
||||
function travelnet.get_travelnets(playername, create)
|
||||
if not travelnet.targets[playername] and create then
|
||||
-- create a new entry
|
||||
travelnet.targets[playername] = {}
|
||||
function travelnet.get_travelnets(playername)
|
||||
local json = storage:get_string(playername)
|
||||
if not json or json == "" or json == "null" then
|
||||
-- default to empty object
|
||||
json = "{}"
|
||||
end
|
||||
return travelnet.targets[playername]
|
||||
return minetest.parse_json(json)
|
||||
end
|
||||
|
||||
-- saves the player's modified travelnets
|
||||
function travelnet.set_travelnets(playername, travelnets)
|
||||
travelnet.targets[playername] = travelnets
|
||||
travelnet.save_data(playername)
|
||||
storage:set_string(playername, minetest.write_json(travelnets))
|
||||
end
|
15
persistence.spec.lua
Normal file
15
persistence.spec.lua
Normal file
@ -0,0 +1,15 @@
|
||||
mtt.register("migration", function(callback)
|
||||
local travelnets = travelnet.get_travelnets("singleplayer")
|
||||
assert(type(travelnets) == "table")
|
||||
assert(type(travelnets["net1"]) == "table")
|
||||
assert(type(travelnets["net1"]["station1"]) == "table")
|
||||
assert(type(travelnets["net1"]["station1"].pos) == "table")
|
||||
assert(travelnets["net1"]["station1"].timestamp == 1663767532)
|
||||
assert(travelnets["net1"]["station1"].pos.x == 1022)
|
||||
assert(travelnets["net1"]["station1"].pos.y == 100)
|
||||
assert(travelnets["net1"]["station1"].pos.z == -856)
|
||||
|
||||
travelnet.set_travelnets("singleplayer", travelnets)
|
||||
|
||||
callback()
|
||||
end)
|
@ -6,4 +6,7 @@ RUN apk add git &&\
|
||||
mkdir -p /root/.minetest/worlds/world/worldmods/ &&\
|
||||
git clone https://github.com/BuckarooBanzay/mtt /root/.minetest/worlds/world/worldmods/mtt
|
||||
|
||||
# old file-based storage to test migration
|
||||
COPY ./mod_travelnet.data.json /root/.minetest/worlds/world/mod_travelnet.data
|
||||
|
||||
ENTRYPOINT minetestserver --config /minetest.conf
|
1
test/mod_travelnet.data.json
Normal file
1
test/mod_travelnet.data.json
Normal file
@ -0,0 +1 @@
|
||||
{"singleplayer":{"net1":{"station1":{"pos":{"x":1022.0,"y":100.0,"z":-856.0},"timestamp":1663767532.0}}}}
|
Loading…
x
Reference in New Issue
Block a user