-- The functions and variables in this file are only for use in the mod itself. -- Those that do real work should be local and wrapped in public functions local debug = yl_scheduler.settings.debug or true local function say(text) if debug then minetest.log("action", "[MOD] yl_scheduler : " .. text) end end -- Helpers local function filename2uuid(filename) return filename:sub(1, -6) end local function is_visible(filename) return (string.sub(filename, 1, 1) ~= ".") end local function is_json(filename) return (filename:match("%.json$")) end local function generate_uuid() local template = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx' return string.gsub(template, '[xy]', function(c) local v = (c == 'x') and math.random(0, 15) or math.random(8, 11) return string.format('%x', v) end) end local function is_uuid_duplicate(UUID) for i, task in ipairs(yl_scheduler.tasks) do say("action", "task.id=" .. dump(task.id) .. ", UUID=" .. dump(UUID)) if task.id == UUID then return true end end return false end local function create_uuid() local max_attempts = 10 local UUID repeat UUID = generate_uuid() max_attempts = max_attempts - 1 if max_attempts < 0 then return false, "Cannot find non-duplicate UUID" end until (is_uuid_duplicate(UUID) == false) if UUID == "" then return false, "UUID empty" end return true, UUID end function yl_scheduler.create_uuid() return create_uuid() end --- ### local function sort_by_timestamp(tasks) local function compare(task1, task2) return task1.at < task2.at end table.sort(tasks, compare) return tasks end function yl_scheduler.sort_by_timestamp(tasks) return sort_by_timestamp(tasks) end local function split(str) local parts = {} for part in str:gmatch("[^,%s]+") do table.insert(parts, part) end return parts end local function ends_with(str, suffix) return str:sub(-suffix:len()) == suffix end -- Validate values local function validate_at(at) -- Let's assume the calcuation already happened and we're dealing with a -- unix epoch timestamp in utc. We can't detect utc though. if (at == nil) then return false, "at: No time given" elseif type(at) ~= "number" then return false, "at: Wrong type" elseif at > os.time(os.date("!*t")) + (10 * 365 * 24 * 60 * 60) then return false, "at: Not within 10 years" else return true, "at: all good" end end local function validate_func(func) -- Should we check existence of the function at set time?? No. -- Functions may be retrofitted. We need to check their existance -- at runtime, not during storing if (func == nil) then return false, "func: No func given" elseif type(func) ~= "string" then return false, "func: Wrong type" else return true, "func: all good" end end local function validate_params(params) -- We don't know much about params. -- Could be nil, could be a table if (type(params) ~= "nil") and (type(params) ~= "table") then return false, "params: Wrong type" else return true, "params: all good" end end local function validate_owner(owner) -- Owner could be an ingame player but also a mod or mechanic if (owner == nil) then return false, "func: No owner given" elseif type(owner) ~= "string" then return false, "owner: Wrong type" else return true, "owner: all good" end end local function validate_notes(notes) -- Notes are optional -- Could be nil, could be a string if (type(notes) ~= "nil") and (type(notes) ~= "string") then return false, "notes: Wrong type" else return true, "notes: all good" end end local function validate(at, func, params, owner, notes) local at_succes, at_message = validate_at(at) if (at_succes == false) then return false, at_message end local func_succes, func_message = validate_func(func) if (func_succes == false) then return false, func_message end local params_succes, params_message = validate_params(params) if (params_succes == false) then return false, params_message end local owner_succes, owner_message = validate_owner(owner) if (owner_succes == false) then return false, owner_message end local notes_succes, notes_message = validate_notes(notes) if (notes_succes == false) then return false, notes_message end return true, "All good" end function yl_scheduler.validate(at, func, params, owner, notes) return validate(at, func, params, owner, notes) end -- Loading and Saving local function get_savepath() -- TODO: Can we assume the path exists? local savepath = yl_scheduler.worldpath .. yl_scheduler.settings.save_path say("savepath : " .. dump(savepath)) return savepath end local function get_filepath(UUID) local path_to_file = yl_scheduler.worldpath .. yl_scheduler.settings.save_path .. DIR_DELIM .. UUID .. ".json" say(UUID .. ":" .. dump(path_to_file)) return path_to_file end local function save_json(UUID, content) if type(UUID) ~= "string" or type(content) ~= "table" then return false end local savepath = get_filepath(UUID) local savecontent = minetest.write_json(content) return minetest.safe_file_write(savepath, savecontent) end local function load_json(path) local file = io.open(path, "r") if not file then return false, "Error opening file: " .. path end local content = file:read("*all") file:close() if not content then return false, "Error reading file: " .. path end return true, minetest.parse_json(content) end -- Public functions wrap the private ones, so they can be exchanged easily function yl_scheduler.load_json(filename, ...) return load_json(filename, ...) end function yl_scheduler.save_json(filename, content, ...) return save_json(filename, content, ...) end -- ### yl_scheduler.load_all_tasks ### local function load_all_tasks() -- Get all json files from savepath -- Excluding invisible -- Excluding non-json files local save_path = get_savepath() local files = minetest.get_dir_list(save_path, false) or {} local tasks = {} local total = 0 local good = 0 local bad = 0 for key, filename in ipairs(files) do total = total + 1 if is_visible(filename) and is_json(filename) then local UUID = filename2uuid(filename) local filepath = get_filepath(UUID) local success, content = load_json(filepath) if success then good = good + 1 table.insert(tasks, content) else bad = bad + 1 end end end -- Sort table for "at" yl_scheduler.tasks = sort_by_timestamp(tasks) if bad == 0 then minetest.log("action", "[MOD] yl_scheduler : bad = " .. tostring(bad) .. ", good = " .. tostring(good) .. ", total = " .. tostring(total)) return true, good, bad else minetest.log("warning", "[MOD] yl_scheduler : bad = " .. tostring(bad) .. ", good = " .. tostring(good) .. ", total = " .. tostring(total)) return false, good, bad end end function yl_scheduler.load_all_tasks() return load_all_tasks() end -- ### priv exists ### local function priv_exists(priv) return (minetest.registered_privileges[priv] ~= nil) or false end function yl_scheduler.priv_exists(priv) return priv_exists(priv) end -- ### get_privs ### -- {[yl_scheduler.settings.admin_priv] = true} local cmds = {} cmds["scheduler_add"] = "taskadd_privs" cmds["scheduler_remove"] = "taskremove_privs" cmds["scheduler_list"] = "tasklist_privs" cmds["scheduler_clean"] = "taskclean_privs" local function get_privs(chatcommand_cmd) -- scheduler_add -- taskadd_privs local privs = split(yl_scheduler.settings[cmds[chatcommand_cmd]]) local ret = {} for _, priv in ipairs(privs) do ret[priv] = true end return ret end function yl_scheduler.get_privs(chatcommand_cmd) return get_privs(chatcommand_cmd) end -- ### check privs ### function check_privs() for key, value in pairs(yl_scheduler.settings) do if ends_with(key, "_privs") then local parts = split(value) for _, part in ipairs(parts) do assert(priv_exists(part), "yl_scheduler : configured priv " .. dump(part) .. " doesn not exist.") end end end say("PASS priv check") end function yl_scheduler.check_privs() return check_privs() end -- Remove file local function remove_file(UUID) local path = yl_scheduler.get_filepath(UUID) return os.remove(path) end function yl_scheduler.remove_file(UUID) return remove_file(UUID) end -- ### Chatcommands ### -- ### scheduler_add ### local function cmd_scheduler_add(name, c_params) -- This is what a chatcommand may look like: -- /scheduler_add 384756378465$minetest.log$action, text to be logged$$some notes -- /scheduler_add 384756378465$minetest.log$action, text to be logged -- /scheduler_add 384756378465$switch_maze -- /scheduler_add 384756378465$switch_maze$$$some more notes -- cast: -- /scheduler_add 384756378465$switch_maze$(int)456, (string)mystring$some more notes -- mask: -- /scheduler_add 384756378465$switch_maze$456,mystring$int, string$some more notes -- The mask should be optional, if none is given we assume strings if not c_params or c_params == "help" then return true, "Adds a new task that executes a function with params at the given time.\n" .. "Separate time, function, parameters, mask and notes with $\n" .. "Example: /scheduler_add 1712679465$minetest.log$action,mytext$string,string$mynotes\n" elseif c_params == "helpmask" then return true, "Provide a parameter mask to convert the parameters to number or table" .. "Allowed values are \"string\",\"number\",\"table\"" .. "If your use case requires other types, please create a wrapper function." end local t_parameters = string.split(c_params, "$", true) local at = tonumber(t_parameters[1]) local func = t_parameters[2] -- local string_params = string.split(t_parameters[3], ",") -- optional local params = string.split(t_parameters[3], ",") -- optional local mask = string.split(t_parameters[4], ",") or {} -- optional local owner = name or "N/A" local notes = t_parameters[5] or "" -- optional -- local param_success, params = unmask_params(string_params, mask) if (param_success == false) then return false, params end local success, message = yl_scheduler.set_task(at, func, params, owner, notes) if success == true then yl_scheduler.tasks = sort_by_timestamp(yl_scheduler.tasks) end return success, message end function yl_scheduler.cmd_scheduler_add(name, params) return cmd_scheduler_add(name, params) end -- ### scheduler_remove ### local function cmd_scheduler_remove(name, params) -- Defense: Overlap with yl_scheduler.remove_task(UUID) -- Store -- Re-sort tasks table end function yl_scheduler.cmd_scheduler_remove(name, params) return cmd_scheduler_remove(name, params) end -- ### scheduler_list ### local function cmd_scheduler_list(name, params) -- Defense: Overlap with yl_scheduler.list_all_tasks() and yl_scheduler.list_task(UUID) -- Display nicely. Steal from ... I mean INSPIRE ... by yl_cinema list? Or formspec output? end function yl_scheduler.cmd_scheduler_list(name, params) return cmd_scheduler_list(name, params) end -- ### scheduler_clean ### local function cmd_scheduler_clean(name, params) -- Just do it and at best return how many were removed, how many remain and how many the list had before. -- Use yl_scheduler.clean_executed_tasks() and yl_scheduler.clean_past_tasks() end function yl_scheduler.cmd_scheduler_clean(name, params) return cmd_scheduler_clean(name, params) end