Compare commits
10 Commits
008df9935b
...
35fae5bb2f
Author | SHA1 | Date | |
---|---|---|---|
|
35fae5bb2f | ||
|
38626c7fd7 | ||
|
097f9e692d | ||
|
0ce05330ec | ||
|
8ffee54ecd | ||
|
0466189818 | ||
|
a8bac37551 | ||
|
a3823985b0 | ||
|
48a18fc5f2 | ||
|
8a902082e5 |
12
README.md
12
README.md
@ -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
201
init.lua
@ -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
1
settingtypes.txt
Normal file
@ -0,0 +1 @@
|
||||
mtfm.read_only (Read only mode) bool false
|
Loading…
x
Reference in New Issue
Block a user