Compare commits

...

10 Commits

Author SHA1 Message Date
Zemtzov7
35fae5bb2f
Working link to contentdb 2024-08-06 16:06:09 +05:00
Zemtzov7
38626c7fd7
Less garbage 2024-08-04 19:34:52 +05:00
Zemtzov7
097f9e692d
Various improvements 2024-08-04 19:31:24 +05:00
Zemtzov7
0ce05330ec
use local function safe_run 2023-07-02 20:21:04 +05:00
Zemtzov7
8ffee54ecd
Update README.md 2023-01-06 20:40:37 +05:00
Zemtzov7
0466189818
hotfix 2022-11-13 15:11:24 +05:00
Zemtzov7
a8bac37551
contentdb downloads counter 2022-11-13 14:30:47 +05:00
Zemtzov7
a3823985b0
Update README.md 2022-11-13 14:22:53 +05:00
Zemtzov7
48a18fc5f2
settingtypes file 2022-11-13 14:21:13 +05:00
Zemtzov7
8a902082e5
Add read-only mode 2022-11-13 14:20:40 +05:00
3 changed files with 128 additions and 86 deletions

View File

@ -1,10 +1,18 @@
# minetest-mod-mtfm
Formspec-based file manager with text editor and image viewer
Formspec-based file manager with text editor and image viewer
[![ContentDB](https://content.minetest.net/packages/Zemtzov7/mtfm/shields/downloads/)](https://content.minetest.net/packages/Zemtzov7/mtfm/)
### Usage
* Chatcommand `/mtfm [path]` - open file manager, optionally path(absolute) can be specified
* Click `Modlist` button to get list of installed mods, then you can browse their files
* `.png` files are opened in image viewer
* `.ogg` files are played by opening
* Empty files can not be auto-opened in text editor, use `Edit` button to force it
* Read-only mode can be enabled in settings or in minetest.conf: `mtfm.read_only = true`
* Set `secure.enable_security = false` in the `minetest.conf` to get full RW access to your homedir
### DISCLAIMER
* Wrong usage of this mod can destroy you local world / server
* Developer of this mod is not responsible for your actions
![](/screenshot.png)

201
init.lua
View File

@ -1,111 +1,133 @@
mtfm = {}
local ro = minetest.settings:get("mtfm.read_only")
local mtfm = {}
mtfm.dirlist = {}
mtfm.last_path = {}
mtfm.last_editor_path = {}
mtfm.dirlist = {}
mtfm.file = {}
mtfm.saved = {}
mtfm.selected = {}
mtfm.selected_mod = {}
local modlist = core.get_modnames() or {"Failed to load modlist"}
local worldpath = core.get_worldpath()
local F = core.formspec_escape
local modlist = minetest.get_modnames() or {"Failed to load modlist"}
local worldpath = minetest.get_worldpath()
local F = minetest.formspec_escape
function safe_run(code)
local func, err = loadstring(code)
if not func then -- Syntax error
return err
local function btoh(val)
if type(val) ~= "number" then
return
end
local good, err = pcall(func)
if not good then -- Runtime error
return err
local lvls = {"", "K", "M", "G", "T"}
local lvl = 1
while val > 1000 and lvl < #lvls do
val = val/1000
lvl = lvl + 1
end
local str = tostring(val):match("%d+%.%d%d")
return (str or val).." "..lvls[lvl].."B"
end
local function isdir(file)
local _, msg = file:read()
if msg == "Is a directory" then
return true
end
return nil
end
local function fm_fs(name, path)
local err = safe_run("mtfm.dirlist['"..name.."'] = core.get_dir_list('"..path.."')")
if err then
core.chat_send_player(name, "Invalid / illegal path")
local _, dirlist = pcall(minetest.get_dir_list, path)
if type(dirlist) ~= "table" then
minetest.chat_send_player(name, "Invalid / illegal path")
return
end
table.sort(mtfm.dirlist[name])
local fs = "size[16,12]" ..
table.sort(dirlist)
mtfm.dirlist[name] = table.copy(dirlist)
for i,elem in ipairs(dirlist) do
local file = io.open(path.."/"..elem, "r")
if file then
dirlist[i] = dirlist[i]..(isdir(file) and ",<dir>" or ","..btoh(file:seek("end")))
file:close()
else
dirlist[i] = dirlist[i]..",<error>"
end
end
mtfm.last_path[name] = path
minetest.show_formspec(name, "mtfm:fm", "size[16,12]" ..
"set_focus[list]" ..
"button[0.1,0;1,1;up;^]" ..
"field[1.2,0.3;14.3,1;path;;"..F(path).."]" ..
"field_close_on_enter[path;false]" ..
"button[15,0;1,1;go;>]" ..
"textlist[0.1,0.9;15.8,10;list;"..table.concat(mtfm.dirlist[name],",").."]" ..
-- "style[list;font=mono]" ..
"tablecolumns[text;text,align=right,padding=1]" ..
"table[0.1,0.9;15.8,10;list;"..table.concat(dirlist,",").."]" ..
"button[0.1,11;1.5,1;modlist;Modlist]" ..
"button[1.4,11;1.5,1;toworldpath;Worldpath]" ..
"button[2.7,11;1.5,1;edit;Edit]" ..
"button[10.6,11;1.5,1;newfile;New file]" ..
"button[13.2,11;1.5,1;edit;Edit]" ..
"button[14.5,11;1.5,1;open;Open]" ..
(not ro and
"button[8,11;1.5,1;newfile;New file]" ..
"button[9.3,11;1.5,1;newdir;New dir]" ..
"button[11.9,11;1.5,1;del;Delete]" ..
"button[13.2,11;1.5,1;copy;Copy]" ..
"button[14.5,11;1.5,1;open;Open]"
mtfm.last_path[name] = path
core.show_formspec(name, "mtfm:fm", fs)
"button[10.6,11;1.5,1;del;Delete]" ..
"button[11.9,11;1.5,1;copy;Copy]" or ""))
end
local function modlist_fs(name)
local fs = "size[16,12]" ..
minetest.show_formspec(name, "mtfm:modlist", "size[16,12]" ..
"button[0.1,0;1,1;up;^]" ..
"textlist[0.1,0.9;15.8,10;list;"..table.concat(modlist,",").."]" ..
"button[14.5,11;1.5,1;open;Open]"
core.show_formspec(name, "mtfm:modlist", fs)
"button[14.5,11;1.5,1;open;Open]")
end
local function editor_fs(name, path, new)
local text = ""
if not new then
local err = safe_run("mtfm.file['"..name.."'] = io.open('"..path.."')")
if err or not mtfm.file[name] then
core.chat_send_player(name, "Invalid/illegal path")
local _, file = pcall(io.open, path)
if not file then
minetest.chat_send_player(name, "Invalid/illegal path")
return
end
text = mtfm.file[name]:read(524288) or "Error loading content" --512kb limit
text = file:read(524288) or "Error loading content" --512kb limit
file:close()
end
local fs = "size[16,12]" ..
mtfm.last_editor_path[name] = path
minetest.show_formspec(name, "mtfm:editor", "size[16,12]" ..
"button[0.1,0;1,1;close;X]" ..
"field[1.2,0.3;13.5,1;path;;"..F(path).."]" ..
"field_close_on_enter[path;false]" ..
"button[14.2,0;1,1;open;Open]" ..
"button[15,0;1,1;save;Save]" ..
"textarea[0.3,1;16,13;content;;"..F(text).."]"
mtfm.last_editor_path[name] = path
core.show_formspec(name, "mtfm:editor", fs)
"style[content;font=mono]" ..
"textarea[0.3,1;16,13;content;;"..F(text).."]" ..
(not ro and "button[15,0;1,1;save;Save]" or ""))
end
local function image_viewer_fs(name, path, image)
local fs = "size[16,12]" ..
minetest.show_formspec(name, "mtfm:image_viewer", "size[16,12]" ..
"button[0.1,0;1,1;close;X]" ..
"field[1.2,0.3;14.9,1;path;;"..path.."]" ..
"field_close_on_enter[path;false]" ..
"image[3,1;12,12;"..image.."]"
core.show_formspec(name, "mtfm:image_viewer", fs)
"image[3,1;12,12;"..image.."]")
end
core.register_chatcommand("mtfm",{
minetest.register_chatcommand("mtfm",{
description = "Open file manager",
privs = {server=true},
params = "[path]",
func = function(name, param)
if not param or param == "" then
param = mtfm.last_path[name] or worldpath
if not minetest.get_player_by_name(name) then
return false, "No player"
end
fm_fs(name, param)
fm_fs(name, param and param ~= "" and param or mtfm.last_path[name] or worldpath)
return true, "File manager opened"
end})
core.register_on_player_receive_fields(function(player, formname, fields)
--core.chat_send_all(dump(fields,''))
minetest.register_on_player_receive_fields(function(player, formname, fields)
if not formname:match("^mtfm:") then return end
local name = player:get_player_name()
if not name then return end
local dirlist = mtfm.dirlist[name]
if formname == "mtfm:fm" then
local dirlist = mtfm.dirlist[name]
if fields.list then
local evnt = core.explode_textlist_event(fields.list)
mtfm.selected[name] = evnt.index
local evnt = minetest.explode_table_event(fields.list)
mtfm.selected[name] = evnt.row
end
if (fields.go or fields.key_enter_field) and fields.path and fields.path ~= "" then
fm_fs(name, fields.path)
@ -119,7 +141,7 @@ core.register_on_player_receive_fields(function(player, formname, fields)
if fields.edit then
local elem = dirlist[mtfm.selected[name]]
if not elem or elem == "" then
core.chat_send_player(name, "Please select file/dir")
minetest.chat_send_player(name, "Please select file/dir")
return
end
editor_fs(name,mtfm.last_path[name].."/"..elem)
@ -127,22 +149,27 @@ core.register_on_player_receive_fields(function(player, formname, fields)
if fields.open or (fields.list and fields.list:match("DCL")) then
local elem = dirlist[mtfm.selected[name]]
if not elem or elem == "" then
core.chat_send_player(name, "Please select file/dir")
minetest.chat_send_player(name, "Please select file/dir")
return
end
if elem:match("%S+%.png") then
if elem:match("%S+%.png$") then
image_viewer_fs(name, mtfm.last_path[name].."/"..elem, elem)
return
end
if elem:match("%S+%.ogg") then
core.sound_play(elem:gsub(".ogg",""), {to_player=name})
if elem:match("%S+%.ogg$") then
minetest.sound_play(elem:gsub(".ogg",""), {to_player=name})
return
end
local file = io.open(mtfm.last_path[name].."/"..elem)
if file:read() then
editor_fs(name,mtfm.last_path[name].."/"..elem)
local _, file = pcall(io.open, mtfm.last_path[name].."/"..elem)
if file then
if isdir(file) then
fm_fs(name, mtfm.last_path[name].."/"..elem)
else
editor_fs(name, mtfm.last_path[name].."/"..elem)
end
file:close()
else
fm_fs(name, mtfm.last_path[name].."/"..elem)
minetest.chat_send_player(name, "Error opening")
end
end
if fields.up and fields.path and fields.path ~= "" then
@ -150,40 +177,46 @@ core.register_on_player_receive_fields(function(player, formname, fields)
local newpath = fields.path:gsub("/"..splitted[#splitted]:gsub('([^%w])','%%%1'),"") or worldpath
fm_fs(name, newpath)
end
if fields.newdir then
core.show_formspec(name, "mtfm:newdir_dialog", "size[8,2]"..
"field[0.3,0.1;8,1;dirname;;NewDir]"..
"field_close_on_enter[path;false]"..
"button[3,1;2,1;accept;Accept]")
if fields.newdir and not ro then
minetest.show_formspec(name, "mtfm:newdir_dialog", "size[8,2]"..
"field[0.3,0.1;8,1;dirname;;NewDir]"..
"field_close_on_enter[path;false]"..
"button[3,1;2,1;accept;Accept]")
end
if fields.newfile then
if fields.newfile and not ro then
editor_fs(name,mtfm.last_path[name].."/new.txt", true)
end
if fields.del then
if fields.del and not ro then
local elem = dirlist[mtfm.selected[name]]
if not elem or elem == "" then
core.chat_send_player(name, "Please select file/dir")
minetest.chat_send_player(name, "Please select file/dir")
return
end
local err = safe_run("core.rmdir(mtfm.last_path['"..name.."']..'/'..'"..elem.."', true)")
core.chat_send_player(name, err and "Error deleting" or "Deleted successfully")
local _, ok = pcall(minetest.rmdir, mtfm.last_path[name].."/"..elem, true)
minetest.chat_send_player(name, ok and "Deleted successfully" or "Error deleting")
fm_fs(name, mtfm.last_path[name])
end
if fields.copy then
if fields.copy and not ro then
local elem = dirlist[mtfm.selected[name]]
if not elem or elem == "" then
core.chat_send_player(name, "Please select file/dir")
minetest.chat_send_player(name, "Please select file/dir")
return
end
local err = safe_run("core.cpdir(mtfm.last_path['"..name.."']..'/'..'"..elem.."', mtfm.last_path['"..name.."']..'/'..'"..elem.."-copy')")
core.chat_send_player(name, err and "Error copying" or "Copied successfully")
local _, file = pcall(io.open, mtfm.last_path[name].."/"..elem, "r")
if file then
local _, ok = isdir(file) and pcall(minetest.cpdir, mtfm.last_path[name].."/"..elem, mtfm.last_path[name].."/"..elem.."-copy")
or pcall(minetest.safe_file_write, mtfm.last_path[name].."/"..elem.."-copy", file:read("*a"))
minetest.chat_send_player(name, ok and "Copied successfully" or "Error copying")
file:close()
else
minetest.chat_send_player(name, "Error copying")
end
fm_fs(name, mtfm.last_path[name])
end
end
if formname == "mtfm:modlist" then
if fields.list then
local evnt = core.explode_textlist_event(fields.list)
local evnt = minetest.explode_textlist_event(fields.list)
mtfm.selected_mod[name] = evnt.index
end
if fields.up then
@ -192,16 +225,16 @@ core.register_on_player_receive_fields(function(player, formname, fields)
if fields.open or (fields.list and fields.list:match("DCL")) then
local modname = modlist[mtfm.selected_mod[name]]
if not modname or modname == "" then
core.chat_send_player(name, "Please select a mod")
minetest.chat_send_player(name, "Please select a mod")
return
end
fm_fs(name, core.get_modpath(modlist[mtfm.selected_mod[name]]))
fm_fs(name, minetest.get_modpath(modlist[mtfm.selected_mod[name]]))
end
end
if formname == "mtfm:newdir_dialog" then
if (fields.accept or fields.key_enter_field) and fields.dirname and fields.dirname ~= "" then
local err = safe_run("core.mkdir(mtfm.last_path['"..name.."']..'/'..'"..fields.dirname.."')")
core.chat_send_player(name, err and "Error creating new directory" or "Successfully created new directory")
if (fields.accept or fields.key_enter_field) and fields.dirname and fields.dirname ~= "" and not ro then
local _, ok = pcall(minetest.mkdir, mtfm.last_path[name].."/"..fields.dirname)
minetest.chat_send_player(name, ok and "Successfully created new directory" or "Error creating new directory")
fm_fs(name, mtfm.last_path[name])
end
end
@ -212,11 +245,11 @@ core.register_on_player_receive_fields(function(player, formname, fields)
if (fields.open or fields.key_enter_field) and fields.path and fields.path ~= "" then
editor_fs(name, fields.path)
end
if fields.save and fields.path and fields.path ~= "" then
if fields.save and fields.path and fields.path ~= "" and not ro then
local content = fields.content
if content then
local err = safe_run("core.safe_file_write('"..fields.path.."', '"..content.."')")
core.chat_send_player(name, err and "Error saving file" or "Saved successfully")
local _, ok = pcall(minetest.safe_file_write, fields.path, content)
minetest.chat_send_player(name, ok and "Saved successfully" or "Erorr saving file")
end
end
end

1
settingtypes.txt Normal file
View File

@ -0,0 +1 @@
mtfm.read_only (Read only mode) bool false