diff --git a/builtin/common/misc_helpers.lua b/builtin/common/misc_helpers.lua
index 9c25b826fffd..2ad9b10afea8 100644
--- a/builtin/common/misc_helpers.lua
+++ b/builtin/common/misc_helpers.lua
@@ -235,6 +235,16 @@ function core.formspec_escape(text)
end
+local hypertext_escapes = {
+ ["\\"] = "\\\\",
+ ["<"] = "\\<",
+ [">"] = "\\>",
+}
+function core.hypertext_escape(text)
+ return text and text:gsub("[\\<>]", hypertext_escapes)
+end
+
+
function core.wrap_text(text, max_length, as_table)
local result = {}
local line = {}
diff --git a/builtin/mainmenu/content/contentdb.lua b/builtin/mainmenu/content/contentdb.lua
index e0479cb4c265..4d59826dd6a3 100644
--- a/builtin/mainmenu/content/contentdb.lua
+++ b/builtin/mainmenu/content/contentdb.lua
@@ -182,6 +182,23 @@ function contentdb.get_package_by_id(id)
end
+function contentdb.calculate_package_id(type, author, name)
+ local id = author:lower() .. "/"
+ if (type == nil or type == "game") and #name > 5 and name:sub(#name - 4) == "_game" then
+ id = id .. name:sub(1, #name - 5)
+ else
+ id = id .. name
+ end
+ return id
+end
+
+
+function contentdb.get_package_by_info(author, name)
+ local id = contentdb.calculate_package_id(nil, author, name)
+ return contentdb.package_by_id[id]
+end
+
+
-- Create a coroutine from `fn` and provide results to `callback` when complete (dead).
-- Returns a resumer function.
local function make_callback_coroutine(fn, callback)
@@ -415,15 +432,7 @@ local function fetch_pkgs(params)
local aliases = {}
for _, package in pairs(packages) do
- local name_len = #package.name
- -- This must match what contentdb.update_paths() does!
- package.id = package.author:lower() .. "/"
- if package.type == "game" and name_len > 5 and package.name:sub(name_len - 4) == "_game" then
- package.id = package.id .. package.name:sub(1, name_len - 5)
- else
- package.id = package.id .. package.name
- end
-
+ package.id = params.calculate_package_id(package.type, package.author, package.name)
package.url_part = core.urlencode(package.author) .. "/" .. core.urlencode(package.name)
if package.aliases then
@@ -443,7 +452,7 @@ end
function contentdb.fetch_pkgs(callback)
contentdb.loading = true
- core.handle_async(fetch_pkgs, nil, function(result)
+ core.handle_async(fetch_pkgs, { calculate_package_id = contentdb.calculate_package_id }, function(result)
if result then
contentdb.load_ok = true
contentdb.load_error = false
@@ -581,3 +590,54 @@ function contentdb.filter_packages(query, by_type)
end
end
end
+
+
+function contentdb.get_full_package_info(package, callback)
+ assert(package)
+ if package.full_info then
+ callback(package.full_info)
+ return
+ end
+
+ local function fetch(params)
+ local version = core.get_version()
+ local base_url = core.settings:get("contentdb_url")
+
+ local languages
+ local current_language = core.get_language()
+ if current_language ~= "" then
+ languages = { current_language, "en;q=0.8" }
+ else
+ languages = { "en" }
+ end
+
+ local url = base_url ..
+ "/api/packages/" .. params.package.url_part .. "/for-client/?" ..
+ "protocol_version=" .. core.urlencode(core.get_max_supp_proto()) ..
+ "&engine_version=" .. core.urlencode(version.string) ..
+ "&formspec_version=" .. core.urlencode(core.get_formspec_version()) ..
+ "&include_images=false"
+ local http = core.get_http_api()
+ local response = http.fetch_sync({
+ url = url,
+ extra_headers = {
+ "Accept-Language: " .. table.concat(languages, ", ")
+ },
+ })
+ if not response.succeeded then
+ return nil
+ end
+
+ return core.parse_json(response.data)
+ end
+
+ local function my_callback(value)
+ package.full_info = value
+ callback(value)
+ end
+
+ if not core.handle_async(fetch, { package = package }, my_callback) then
+ core.log("error", "ERROR: async event failed")
+ callback(nil)
+ end
+end
diff --git a/builtin/mainmenu/content/dlg_contentdb.lua b/builtin/mainmenu/content/dlg_contentdb.lua
index bcc89f7cdb9c..025430bfa353 100644
--- a/builtin/mainmenu/content/dlg_contentdb.lua
+++ b/builtin/mainmenu/content/dlg_contentdb.lua
@@ -46,48 +46,6 @@ local filter_types_type = {
}
-local function install_or_update_package(this, package)
- local install_parent
- if package.type == "mod" then
- install_parent = core.get_modpath()
- elseif package.type == "game" then
- install_parent = core.get_gamepath()
- elseif package.type == "txp" then
- install_parent = core.get_texturepath()
- else
- error("Unknown package type: " .. package.type)
- end
-
- if package.queued or package.downloading then
- return
- end
-
- local function on_confirm()
- local dlg = create_install_dialog(package)
- dlg:set_parent(this)
- this:hide()
- dlg:show()
-
- dlg:load_deps()
- end
-
- if package.type == "mod" and #pkgmgr.games == 0 then
- local dlg = messagebox("install_game",
- fgettext("You need to install a game before you can install a mod"))
- dlg:set_parent(this)
- this:hide()
- dlg:show()
- elseif not package.path and core.is_dir(install_parent .. DIR_DELIM .. package.name) then
- local dlg = create_confirm_overwrite(package, on_confirm)
- dlg:set_parent(this)
- this:hide()
- dlg:show()
- else
- on_confirm()
- end
-end
-
-
-- Resolves the package specification stored in auto_install_spec into an actual package.
-- May only be called after the package list has been loaded successfully.
local function resolve_auto_install_spec()
@@ -291,7 +249,7 @@ local function get_formspec(dlgdata)
-- image
formspec[#formspec + 1] = "image[0,0;1.5,1;"
- formspec[#formspec + 1] = core.formspec_escape(get_screenshot(package))
+ formspec[#formspec + 1] = core.formspec_escape(get_screenshot(package, package.thumbnail, 1))
formspec[#formspec + 1] = "]"
-- title
@@ -301,52 +259,17 @@ local function get_formspec(dlgdata)
core.colorize("#BFBFBF", " by " .. package.author))
formspec[#formspec + 1] = "]"
- -- buttons
- local description_width = W - 2.625 - 2 * 0.7 - 2 * 0.15
-
- local second_base = "image_button[-1.55,0;0.7,0.7;" .. core.formspec_escape(defaulttexturedir)
- local third_base = "image_button[-2.4,0;0.7,0.7;" .. core.formspec_escape(defaulttexturedir)
- formspec[#formspec + 1] = "container["
- formspec[#formspec + 1] = W - 0.375*2
- formspec[#formspec + 1] = ",0.1]"
-
- if package.downloading then
- formspec[#formspec + 1] = "animated_image[-1.7,-0.15;1,1;downloading;"
- formspec[#formspec + 1] = core.formspec_escape(defaulttexturedir)
- formspec[#formspec + 1] = "cdb_downloading.png;3;400;]"
- elseif package.queued then
- formspec[#formspec + 1] = second_base
- formspec[#formspec + 1] = "cdb_queued.png;queued;]"
- elseif not package.path then
- local elem_name = "install_" .. i .. ";"
- formspec[#formspec + 1] = "style[" .. elem_name .. "bgcolor=#71aa34]"
- formspec[#formspec + 1] = second_base .. "cdb_add.png;" .. elem_name .. "]"
- formspec[#formspec + 1] = "tooltip[" .. elem_name .. fgettext("Install") .. tooltip_colors
- else
- if package.installed_release < package.release then
- -- The install_ action also handles updating
- local elem_name = "install_" .. i .. ";"
- formspec[#formspec + 1] = "style[" .. elem_name .. "bgcolor=#28ccdf]"
- formspec[#formspec + 1] = third_base .. "cdb_update.png;" .. elem_name .. "]"
- formspec[#formspec + 1] = "tooltip[" .. elem_name .. fgettext("Update") .. tooltip_colors
-
- description_width = description_width - 0.7 - 0.15
- end
-
- local elem_name = "uninstall_" .. i .. ";"
- formspec[#formspec + 1] = "style[" .. elem_name .. "bgcolor=#a93b3b]"
- formspec[#formspec + 1] = second_base .. "cdb_clear.png;" .. elem_name .. "]"
- formspec[#formspec + 1] = "tooltip[" .. elem_name .. fgettext("Uninstall") .. tooltip_colors
- end
-
- local web_elem_name = "view_" .. i .. ";"
- formspec[#formspec + 1] = "image_button[-0.7,0;0.7,0.7;" ..
- core.formspec_escape(defaulttexturedir) .. "cdb_viewonline.png;" .. web_elem_name .. "]"
- formspec[#formspec + 1] = "tooltip[" .. web_elem_name ..
- fgettext("View more information in a web browser") .. tooltip_colors
- formspec[#formspec + 1] = "container_end[]"
+ -- button
+ formspec[#formspec + 1] = "button["
+ formspec[#formspec + 1] = W-0.375*2-2
+ formspec[#formspec + 1] = ",0.1;2,0.7;view_"
+ formspec[#formspec + 1] = i
+ formspec[#formspec + 1] = ";"
+ formspec[#formspec + 1] = fgettext("View")
+ formspec[#formspec + 1] = "]"
-- description
+ local description_width = W - 2.625 - 2 * 0.7 - 2 * 0.15
formspec[#formspec + 1] = "textarea[1.855,0.3;"
formspec[#formspec + 1] = tostring(description_width)
formspec[#formspec + 1] = ",0.8;;;"
@@ -434,26 +357,13 @@ local function handle_submit(this, fields)
local package = contentdb.packages[i]
assert(package)
- if fields["install_" .. i] then
- install_or_update_package(this, package)
- return true
- end
-
- if fields["uninstall_" .. i] then
- local dlg = create_delete_content_dlg(package)
+ if fields["view_" .. i] then
+ local dlg = create_package_dialog(package)
dlg:set_parent(this)
this:hide()
dlg:show()
return true
end
-
- if fields["view_" .. i] then
- local url = ("%s/packages/%s?protocol_version=%d"):format(
- core.settings:get("contentdb_url"), package.url_part,
- core.get_max_supp_proto())
- core.open_url(url)
- return true
- end
end
return false
diff --git a/builtin/mainmenu/content/dlg_install.lua b/builtin/mainmenu/content/dlg_install.lua
index 89819be2a719..3f43bd23cbb7 100644
--- a/builtin/mainmenu/content/dlg_install.lua
+++ b/builtin/mainmenu/content/dlg_install.lua
@@ -244,3 +244,45 @@ function create_install_dialog(package)
return dlg
end
+
+
+function install_or_update_package(parent, package)
+ local install_parent
+ if package.type == "mod" then
+ install_parent = core.get_modpath()
+ elseif package.type == "game" then
+ install_parent = core.get_gamepath()
+ elseif package.type == "txp" then
+ install_parent = core.get_texturepath()
+ else
+ error("Unknown package type: " .. package.type)
+ end
+
+ if package.queued or package.downloading then
+ return
+ end
+
+ local function on_confirm()
+ local dlg = create_install_dialog(package)
+ dlg:set_parent(parent)
+ parent:hide()
+ dlg:show()
+
+ dlg:load_deps()
+ end
+
+ if package.type == "mod" and #pkgmgr.games == 0 then
+ local dlg = messagebox("install_game",
+ fgettext("You need to install a game before you can install a mod"))
+ dlg:set_parent(parent)
+ parent:hide()
+ dlg:show()
+ elseif not package.path and core.is_dir(install_parent .. DIR_DELIM .. package.name) then
+ local dlg = create_confirm_overwrite(package, on_confirm)
+ dlg:set_parent(parent)
+ parent:hide()
+ dlg:show()
+ else
+ on_confirm()
+ end
+end
diff --git a/builtin/mainmenu/content/dlg_package.lua b/builtin/mainmenu/content/dlg_package.lua
new file mode 100644
index 000000000000..78bdf2e71f8a
--- /dev/null
+++ b/builtin/mainmenu/content/dlg_package.lua
@@ -0,0 +1,325 @@
+--Minetest
+--Copyright (C) 2018-24 rubenwardy
+--
+--This program is free software; you can redistribute it and/or modify
+--it under the terms of the GNU Lesser General Public License as published by
+--the Free Software Foundation; either version 2.1 of the License, or
+--(at your option) any later version.
+--
+--This program is distributed in the hope that it will be useful,
+--but WITHOUT ANY WARRANTY; without even the implied warranty of
+--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+--GNU Lesser General Public License for more details.
+--
+--You should have received a copy of the GNU Lesser General Public License along
+--with this program; if not, write to the Free Software Foundation, Inc.,
+--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+
+local function get_info_formspec(size, padding, text)
+ return table.concat({
+ "formspec_version[6]",
+ "size[", size.x, ",", size.y, "]",
+ "padding[0,0]",
+ "bgcolor[;true]",
+
+ "label[4,4.35;", text, "]",
+ "container[", padding.x, ",", size.y - 0.8 - padding.y, "]",
+ "button[0,0;2,0.8;back;", fgettext("Back"), "]",
+ "container_end[]",
+ })
+end
+
+
+local function get_formspec(data)
+ -- Padding is increased on Android to account for notches
+ -- TODO: use Android API to determine size of cut outs
+ local window_padding = { x = PLATFORM == "Android" and 1 or 0.5, y = PLATFORM == "Android" and 0.25 or 0.5 }
+ local window = core.get_window_info()
+ local size = { x = window.max_formspec_size.x, y = window.max_formspec_size.y }
+ size.x = math.min(size.x, 20)
+ local W = size.x - window_padding.x * 2
+ local H = size.y - window_padding.y * 2
+
+ if not data.info then
+ if not data.loading and not data.loading_error then
+ data.loading = true
+
+ contentdb.get_full_package_info(data.package, function(info)
+ data.loading = false
+
+ if info == nil then
+ data.loading_error = true
+ ui.update()
+ return
+ end
+
+ if info.forums then
+ info.forums = "https://forum.minetest.net/viewtopic.php?t=" .. info.forums
+ end
+
+ assert(data.package.name == info.name)
+ data.info = info
+ ui.update()
+ end)
+ end
+
+ -- get_full_package_info can return cached info immediately, so
+ -- check to see if that happened
+ if not data.info then
+ if data.loading_error then
+ return get_info_formspec(size, window_padding, fgettext("No packages could be retrieved"))
+ end
+ return get_info_formspec(size, window_padding, fgettext("Loading..."))
+ end
+ end
+
+ -- Check installation status
+ contentdb.update_paths()
+
+ local info = data.info
+
+ local info_line =
+ fgettext("by $1 — $2 downloads — +$3 / $4 / -$5",
+ info.author, info.downloads,
+ info.reviews.positive, info.reviews.neutral, info.reviews.negative)
+
+ local bottom_buttons_y = H - 0.8
+
+ local formspec = {
+ "formspec_version[7]",
+ "size[", size.x, ",", size.y, "]",
+ "padding[0,0]",
+ "bgcolor[;true]",
+
+ "container[", window_padding.x, ",", window_padding.y, "]",
+
+ "button[0,", bottom_buttons_y, ";2,0.8;back;", fgettext("Back"), "]",
+ "button[", W - 3, ",", bottom_buttons_y, ";3,0.8;open_contentdb;", fgettext("ContentDB page"), "]",
+
+ "style_type[label;font_size=+24;font=bold]",
+ "label[0,0.4;", core.formspec_escape(info.title), "]",
+ "style_type[label;font_size=;font=]",
+
+ "label[0,1.2;", core.formspec_escape(info_line), "]",
+ }
+
+ table.insert_all(formspec, {
+ "container[", W - 6, ",0]"
+ })
+
+ local left_button_rect = "0,0;2.875,1"
+ local right_button_rect = "3.125,0;2.875,1"
+ if data.package.downloading then
+ formspec[#formspec + 1] = "animated_image[5,0;1,1;downloading;"
+ formspec[#formspec + 1] = core.formspec_escape(defaulttexturedir)
+ formspec[#formspec + 1] = "cdb_downloading.png;3;400;]"
+ elseif data.package.queued then
+ formspec[#formspec + 1] = "style[queued;border=false]"
+ formspec[#formspec + 1] = "image_button[5,0;1,1;" .. core.formspec_escape(defaulttexturedir)
+ formspec[#formspec + 1] = "cdb_queued.png;queued;]"
+ elseif not data.package.path then
+ formspec[#formspec + 1] = "style[install;bgcolor=green]"
+ formspec[#formspec + 1] = "button["
+ formspec[#formspec + 1] = right_button_rect
+ formspec[#formspec + 1] =";install;"
+ formspec[#formspec + 1] = fgettext("Install [$1]", info.download_size)
+ formspec[#formspec + 1] = "]"
+ else
+ if data.package.installed_release < data.package.release then
+ -- The install_ action also handles updating
+ formspec[#formspec + 1] = "style[install;bgcolor=#28ccdf]"
+ formspec[#formspec + 1] = "button["
+ formspec[#formspec + 1] = left_button_rect
+ formspec[#formspec + 1] = ";install;"
+ formspec[#formspec + 1] = fgettext("Update")
+ formspec[#formspec + 1] = "]"
+ end
+
+ formspec[#formspec + 1] = "style[uninstall;bgcolor=#a93b3b]"
+ formspec[#formspec + 1] = "button["
+ formspec[#formspec + 1] = right_button_rect
+ formspec[#formspec + 1] = ";uninstall;"
+ formspec[#formspec + 1] = fgettext("Uninstall")
+ formspec[#formspec + 1] = "]"
+ end
+
+ local current_tab = data.current_tab or 1
+ local tab_titles = {
+ fgettext("Description"),
+ fgettext("Information"),
+ }
+
+ local tab_body_height = bottom_buttons_y - 2.8
+
+ table.insert_all(formspec, {
+ "container_end[]",
+
+ "box[0,2.55;", W, ",", tab_body_height, ";#ffffff11]",
+
+ "tabheader[0,2.55;", W, ",0.8;tabs;",
+ table.concat(tab_titles, ","), ";", current_tab, ";true;true]",
+
+ "container[0,2.8]",
+ })
+
+ if current_tab == 1 then
+ -- Screenshots and description
+ local hypertext = "" .. core.hypertext_escape(info.short_description) .. "\n"
+ local winfo = core.get_window_info()
+ local fs_to_px = winfo.size.x / winfo.max_formspec_size.x
+ for i, ss in ipairs(info.screenshots) do
+ local path = get_screenshot(data.package, ss.url, 2)
+ hypertext = hypertext .. ""
+ if i ~= #info.screenshots then
+ hypertext = hypertext .. ""
+ end
+ end
+ hypertext = hypertext .. "\n" .. info.long_description.head
+
+ local first = true
+ local function add_link_button(label, name)
+ if info[name] then
+ if not first then
+ hypertext = hypertext .. " | "
+ end
+ hypertext = hypertext .. "" .. core.hypertext_escape(label) .. ""
+ info.long_description.links["link_" .. name] = info[name]
+ first = false
+ end
+ end
+
+ add_link_button(fgettext("Donate"), "donate_url")
+ add_link_button(fgettext("Website"), "website")
+ add_link_button(fgettext("Source"), "repo")
+ add_link_button(fgettext("Issue Tracker"), "issue_tracker")
+ add_link_button(fgettext("Translate"), "translation_url")
+ add_link_button(fgettext("Forum Topic"), "forums")
+
+ hypertext = hypertext .. "\n\n" .. info.long_description.body
+
+ hypertext = hypertext:gsub(""] = "\\>",
- }
- string = string:gsub("[\\<>]", hypertext_escapes)
+ string = core.hypertext_escape(string)
string = string:gsub("%[.-%]", "%1")
table.insert(dest, string)
diff --git a/doc/lua_api.md b/doc/lua_api.md
index 57c99ef9df48..2fb2950d3368 100644
--- a/doc/lua_api.md
+++ b/doc/lua_api.md
@@ -6581,6 +6581,9 @@ Formspec
* `minetest.formspec_escape(string)`: returns a string
* escapes the characters "[", "]", "\", "," and ";", which cannot be used
in formspecs.
+* `minetest.hypertext_escape(string)`: returns a string
+ * escapes the characters "\", "<", and ">" to show text in a hypertext element.
+ * not safe for use with tag attributes.
* `minetest.explode_table_event(string)`: returns a table
* returns e.g. `{type="CHG", row=1, column=2}`
* `type` is one of:
diff --git a/doc/menu_lua_api.md b/doc/menu_lua_api.md
index be63af904966..c03c0501e096 100644
--- a/doc/menu_lua_api.md
+++ b/doc/menu_lua_api.md
@@ -57,7 +57,10 @@ Functions
* returns the maximum supported network protocol version
* `core.open_url(url)`
* opens the URL in a web browser, returns false on failure.
- * Must begin with http:// or https://
+ * `url` must begin with http:// or https://
+* `core.open_url_dialog(url)`
+ * shows a dialog to allow the user to choose whether to open a URL.
+ * `url` must begin with http:// or https://
* `core.open_dir(path)`
* opens the path in the system file browser/explorer, returns false on failure.
* Must be an existing directory.
@@ -65,6 +68,8 @@ Functions
* Android only. Shares file using the share popup
* `core.get_version()` (possible in async calls)
* returns current core version
+* `core.get_formspec_version()`
+ * returns maximum supported formspec version
diff --git a/src/script/lua_api/l_mainmenu.cpp b/src/script/lua_api/l_mainmenu.cpp
index 65e69d7e4a3a..20faec9e01c7 100644
--- a/src/script/lua_api/l_mainmenu.cpp
+++ b/src/script/lua_api/l_mainmenu.cpp
@@ -41,6 +41,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "content/mod_configuration.h"
#include "threading/mutex_auto_lock.h"
#include "common/c_converter.h"
+#include "gui/guiOpenURL.h"
/******************************************************************************/
std::string ModApiMainMenu::getTextData(lua_State *L, const std::string &name)
@@ -1038,6 +1039,13 @@ int ModApiMainMenu::l_get_max_supp_proto(lua_State *L)
return 1;
}
+/******************************************************************************/
+int ModApiMainMenu::l_get_formspec_version(lua_State *L)
+{
+ lua_pushinteger(L, FORMSPEC_API_VERSION);
+ return 1;
+}
+
/******************************************************************************/
int ModApiMainMenu::l_open_url(lua_State *L)
{
@@ -1046,6 +1054,22 @@ int ModApiMainMenu::l_open_url(lua_State *L)
return 1;
}
+/******************************************************************************/
+int ModApiMainMenu::l_open_url_dialog(lua_State *L)
+{
+ GUIEngine* engine = getGuiEngine(L);
+ sanity_check(engine != NULL);
+
+ std::string url = luaL_checkstring(L, 1);
+
+ GUIOpenURLMenu* openURLMenu =
+ new GUIOpenURLMenu(engine->m_rendering_engine->get_gui_env(),
+ engine->m_parent, -1, engine->m_menumanager,
+ engine->m_texture_source.get(), url);
+ openURLMenu->drop();
+ return 1;
+}
+
/******************************************************************************/
int ModApiMainMenu::l_open_dir(lua_State *L)
{
@@ -1136,7 +1160,9 @@ void ModApiMainMenu::Initialize(lua_State *L, int top)
API_FCT(get_active_irrlicht_device);
API_FCT(get_min_supp_proto);
API_FCT(get_max_supp_proto);
+ API_FCT(get_formspec_version);
API_FCT(open_url);
+ API_FCT(open_url_dialog);
API_FCT(open_dir);
API_FCT(share_file);
API_FCT(do_async_callback);
@@ -1166,6 +1192,7 @@ void ModApiMainMenu::InitializeAsync(lua_State *L, int top)
API_FCT(download_file);
API_FCT(get_min_supp_proto);
API_FCT(get_max_supp_proto);
+ API_FCT(get_formspec_version);
API_FCT(get_language);
API_FCT(gettext);
}
diff --git a/src/script/lua_api/l_mainmenu.h b/src/script/lua_api/l_mainmenu.h
index 5535d21707cb..cb3e7f9ca53c 100644
--- a/src/script/lua_api/l_mainmenu.h
+++ b/src/script/lua_api/l_mainmenu.h
@@ -159,9 +159,13 @@ class ModApiMainMenu: public ModApiBase
static int l_get_max_supp_proto(lua_State *L);
+ static int l_get_formspec_version(lua_State *L);
+
// other
static int l_open_url(lua_State *L);
+ static int l_open_url_dialog(lua_State *L);
+
static int l_open_dir(lua_State *L);
static int l_share_file(lua_State *L);