diff --git a/formspecs.lua b/formspecs.lua index fcc575c..c884deb 100644 --- a/formspecs.lua +++ b/formspecs.lua @@ -255,17 +255,16 @@ local get_info_formspec = function(market, account) "size[10,10]", "tabheader[0,0;tabs;"..market.def.description..",Your Inventory,Market Orders;1;false;true]", "textarea[0.5,0.5;9.5,1.5;;Description:;"..market.def.long_description.."]", - -- TODO: logging temporarily disabled, it was causing minetest.serialize to generate invalid output for some reason - --"textarea[0.5,2.5;9.5,6;;Your Recent Purchases and Sales:;", + "textarea[0.5,2.5;9.5,6;;Your Recent Purchases and Sales:;", } --- if next(account.log) then --- for _, log_entry in ipairs(account.log) do --- formspec[#formspec+1] = log_to_string(market, log_entry) .. "\n" --- end --- else --- formspec[#formspec+1] = "No logged activites in this market yet" --- end --- formspec[#formspec+1] = "]" + if next(account.log) then + for _, log_entry in ipairs(account.log) do + formspec[#formspec+1] = log_to_string(market, log_entry) .. "\n" + end + else + formspec[#formspec+1] = "No logged activites in this market yet" + end + formspec[#formspec+1] = "]" return table.concat(formspec) end diff --git a/market.lua b/market.lua index fa85ae1..784bb72 100644 --- a/market.lua +++ b/market.lua @@ -77,21 +77,23 @@ local get_account = function(market, player_name) return account end +-- Caution: the data structures produced by sale logging caused me to discover +-- issue https://github.com/minetest/minetest/issues/8719 with minetest.serialize() +-- I'm working around it by using the code in persistence.lua instead local log_sale = function(item, quantity, price, purchaser, seller) --- TODO: disabled temporarily, the log code should work in theory but in practice minetest.serialize was generating invalid output for some reason. --- local log_entry = {item=item, quantity=quantity, price=price, purchaser=purchaser, seller=seller, timestamp = minetest.get_gametime()} --- local purchaser_log = purchaser.log --- local seller_log = seller.log --- table.insert(purchaser_log, log_entry) --- if #purchaser_log > log_length_limit then --- table.remove(purchaser_log, 1) --- end --- if (purchaser ~= seller) then --- table.insert(seller_log, log_entry) --- if #seller_log > log_length_limit then --- table.remove(seller_log, 1) --- end --- end + local log_entry = {item=item, quantity=quantity, price=price, purchaser=purchaser, seller=seller, timestamp = minetest.get_gametime()} + local purchaser_log = purchaser.log + local seller_log = seller.log + table.insert(purchaser_log, log_entry) + if #purchaser_log > log_length_limit then + table.remove(purchaser_log, 1) + end + if (purchaser ~= seller) then + table.insert(seller_log, log_entry) + if #seller_log > log_length_limit then + table.remove(seller_log, 1) + end + end end local remove_orders_by_account = function(orders, account) @@ -379,29 +381,23 @@ local buy = function(self, player_name, item, quantity, price) return add_buy(self, get_account(self, player_name), item, price, quantity) end +-- Using this instead of minetest.serialize because of https://github.com/minetest/minetest/issues/8719 +local MP = minetest.get_modpath(minetest.get_current_modname()) +local persistence_store, persistence_load = dofile(MP.."/persistence.lua") + local load_market_data = function(marketname) local path = minetest.get_worldpath() local filename = path .. "\\market_"..marketname..".lua" - local file = loadfile(filename) -- returns nil if the file doesn't exist - if file then - return file() - else - return nil - end + return persistence_load(filename) end local save_market_data = function(market) local path = minetest.get_worldpath() local filename = path .. "\\market_"..market.name..".lua" - local file, err = io.open(filename, "w") - if err ~= nil then - minetest.log("error", "[commoditymarket] Could not save market to \"" .. filename .. "\"") - return false - end local data = {} data.player_accounts = market.player_accounts data.orders_for_items = market.orders_for_items - file:write(minetest.serialize(data)) + persistence_store(filename, data) return true end @@ -483,8 +479,14 @@ commoditymarket.register_market = function(market_name, market_def) return 0 end, allow_put = function(inv, listname, index, stack, player) - -- Currency items are always allowed local item = stack:get_name() + + -- reject unknown items + if minetest.registered_items[item] == nil then + return 0 + end + + -- Currency items are always allowed if new_market.def.currency[item] then return stack:get_count() end diff --git a/persistence.lua b/persistence.lua new file mode 100644 index 0000000..edff9b1 --- /dev/null +++ b/persistence.lua @@ -0,0 +1,200 @@ +-- Internal persistence library + +--[[ Provides ]] +-- persistence.store(path, ...): Stores arbitrary items to the file at the given path +-- persistence.load(path): Loads files that were previously stored with store and returns them + +--[[ Limitations ]] +-- Does not export userdata, threads or most function values +-- Function export is not portable + +--[[ License: MIT (see bottom) ]] + +-- Private methods +local write, writeIndent, writers, refCount; + +-- write thing (dispatcher) +write = function (file, item, level, objRefNames) + writers[type(item)](file, item, level, objRefNames); +end; + +-- write indent +writeIndent = function (file, level) + for i = 1, level do + file:write("\t"); + end; +end; + +-- recursively count references +refCount = function (objRefCount, item) + -- only count reference types (tables) + if type(item) == "table" then + -- Increase ref count + if objRefCount[item] then + objRefCount[item] = objRefCount[item] + 1; + else + objRefCount[item] = 1; + -- If first encounter, traverse + for k, v in pairs(item) do + refCount(objRefCount, k); + refCount(objRefCount, v); + end; + end; + end; +end; + +-- Format items for the purpose of restoring +writers = { + ["nil"] = function (file, item) + file:write("nil"); + end; + ["number"] = function (file, item) + file:write(tostring(item)); + end; + ["string"] = function (file, item) + file:write(string.format("%q", item)); + end; + ["boolean"] = function (file, item) + if item then + file:write("true"); + else + file:write("false"); + end + end; + ["table"] = function (file, item, level, objRefNames) + local refIdx = objRefNames[item]; + if refIdx then + -- Table with multiple references + file:write("multiRefObjects["..refIdx.."]"); + else + -- Single use table + file:write("{\n"); + for k, v in pairs(item) do + writeIndent(file, level+1); + file:write("["); + write(file, k, level+1, objRefNames); + file:write("] = "); + write(file, v, level+1, objRefNames); + file:write(";\n"); + end + writeIndent(file, level); + file:write("}"); + end; + end; + ["function"] = function (file, item) + -- Does only work for "normal" functions, not those + -- with upvalues or c functions + local dInfo = debug.getinfo(item, "uS"); + if dInfo.nups > 0 then + file:write("nil --[[functions with upvalue not supported]]"); + elseif dInfo.what ~= "Lua" then + file:write("nil --[[non-lua function not supported]]"); + else + local r, s = pcall(string.dump,item); + if r then + file:write(string.format("loadstring(%q)", s)); + else + file:write("nil --[[function could not be dumped]]"); + end + end + end; + ["thread"] = function (file, item) + file:write("nil --[[thread]]\n"); + end; + ["userdata"] = function (file, item) + file:write("nil --[[userdata]]\n"); + end; +} + +return function (path, ...) + local file, e; + if type(path) == "string" then + -- Path, open a file + file, e = io.open(path, "w"); + if not file then + return error(e); + end + else + -- Just treat it as file + file = path; + end + local n = select("#", ...); + -- Count references + local objRefCount = {}; -- Stores reference that will be exported + for i = 1, n do + refCount(objRefCount, (select(i,...))); + end; + -- Export Objects with more than one ref and assign name + -- First, create empty tables for each + local objRefNames = {}; + local objRefIdx = 0; + file:write("-- Persistent Data\n"); + file:write("local multiRefObjects = {\n"); + for obj, count in pairs(objRefCount) do + if count > 1 then + objRefIdx = objRefIdx + 1; + objRefNames[obj] = objRefIdx; + file:write("{};"); -- table objRefIdx + end; + end; + file:write("\n} -- multiRefObjects\n"); + -- Then fill them (this requires all empty multiRefObjects to exist) + for obj, idx in pairs(objRefNames) do + for k, v in pairs(obj) do + file:write("multiRefObjects["..idx.."]["); + write(file, k, 0, objRefNames); + file:write("] = "); + write(file, v, 0, objRefNames); + file:write(";\n"); + end; + end; + -- Create the remaining objects + for i = 1, n do + file:write("local ".."obj"..i.." = "); + write(file, (select(i,...)), 0, objRefNames); + file:write("\n"); + end + -- Return them + if n > 0 then + file:write("return obj1"); + for i = 2, n do + file:write(" ,obj"..i); + end; + file:write("\n"); + else + file:write("return\n"); + end; + file:close(); +end, function (path) + local f, e = loadfile(path); + if f then + return f(); + else + return nil, e; + end; +end + +--[[ + Copyright (c) 2010 Gerhard Roethlin + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. +]]