2024-12-11 18:00:30 +08:00
|
|
|
local ffi = require("ffi")
|
2025-11-13 16:35:40 +08:00
|
|
|
local bit = require("bit")
|
2024-12-11 18:00:30 +08:00
|
|
|
|
|
|
|
|
local dirtools = {}
|
|
|
|
|
|
|
|
|
|
local tinsert = table.insert
|
|
|
|
|
local tconcat = table.concat
|
|
|
|
|
|
|
|
|
|
---------------------------------------------------------------------------------------
|
|
|
|
|
-- Dirtools is about to become a little more complex and become a Virtual File System.
|
|
|
|
|
--
|
|
|
|
|
-- Scope: The VFS will handle all file management, thus io calls should be replaced
|
|
|
|
|
-- with the equivalent vfs calls (same name, different lib).
|
|
|
|
|
-- There are unique examples where this might not be needed, this is why the io
|
|
|
|
|
-- lib is not being overridden.
|
|
|
|
|
--
|
|
|
|
|
-- File Support: VFS will handle unique file types and resource conversion, which may
|
|
|
|
|
-- end up a separate module. Initially file types supported by the builder will be:
|
|
|
|
|
-- - image files (png, jpg)
|
|
|
|
|
-- - lua scripts (.lua and .lcc bytecode)
|
|
|
|
|
-- - shaders (only .glsl - these will be converted to bytecode at build)
|
|
|
|
|
-- - data files (extension must be registered with vfs, and custom handler can be added)
|
|
|
|
|
--
|
|
|
|
|
-- Resrouces: VFS is most important for building and packaging resource for release
|
|
|
|
|
-- creation. The build tool (rbuilder) shall have options for how resources are
|
|
|
|
|
-- packaged. For example: Various custom data file types may need to exist
|
|
|
|
|
-- standalone, and not be packaged into the resource files.
|
|
|
|
|
--
|
|
|
|
|
-- Require: VFS will manage loading lua and modules via search_packages system in lua.
|
|
|
|
|
-- If an example or project runs from a sokol-luajit repo then the build system
|
|
|
|
|
-- shall replicate the same loading within the bytecode generation.
|
|
|
|
|
-- Build script combine, build and this file dirtools will include functions to handle
|
|
|
|
|
-- the specific pathing and file types as part of the require system.
|
|
|
|
|
---------------------------------------------------------------------------------------
|
|
|
|
|
|
2024-12-24 09:39:08 +08:00
|
|
|
|
2025-11-13 16:35:40 +08:00
|
|
|
local allfolders_cmd = [[dir /ON /AD /B "%s" 2>nul]]
|
|
|
|
|
if(ffi.os ~= "Windows") then allfolders_cmd = [[find "%s" -mindepth 1 -maxdepth 1 -type d -exec basename {} \;]] end
|
|
|
|
|
local allfiles_cmd = [[dir /ON /A-D /B "%s" 2>nul]]
|
|
|
|
|
if(ffi.os ~= "Windows") then allfiles_cmd = [[find "%s" -mindepth 1 -maxdepth 1 -type f -exec basename {} \;]] end
|
2024-12-24 09:39:08 +08:00
|
|
|
|
|
|
|
|
-- --------------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
local list_folders_cache = {}
|
|
|
|
|
local list_cache = {}
|
|
|
|
|
|
2024-12-11 18:00:30 +08:00
|
|
|
local sep = "\\"
|
|
|
|
|
if(ffi.os ~= "Windows") then sep = "/" end
|
2024-12-16 14:48:31 +08:00
|
|
|
dirtools.sep = sep
|
2025-11-13 16:35:40 +08:00
|
|
|
dirtools.sep2 = sep..sep
|
|
|
|
|
|
|
|
|
|
---------------------------------------------------------------------------------------
|
|
|
|
|
-- Why didnt I have this before.. uggh
|
|
|
|
|
local function run_popen( cmd, callback, readstr )
|
|
|
|
|
|
|
|
|
|
local fh = io.popen(cmd, readstr or "r")
|
|
|
|
|
if(fh and callback) then
|
|
|
|
|
local res = callback(fh)
|
|
|
|
|
fh:close()
|
|
|
|
|
return res
|
|
|
|
|
else
|
|
|
|
|
print("[Error] dirtools.run_popen "..tostring(cmd))
|
|
|
|
|
end
|
|
|
|
|
return nil
|
|
|
|
|
end
|
2024-12-11 18:00:30 +08:00
|
|
|
|
|
|
|
|
---------------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
dirtools.add_default_paths = function(path)
|
|
|
|
|
local folders = {
|
|
|
|
|
["Linux"] = "linux",
|
|
|
|
|
["Windows"] = "win64",
|
|
|
|
|
["MacOSX"] = "macos",
|
2025-05-06 08:04:36 +08:00
|
|
|
["OSX"] = "macos",
|
2024-12-11 18:00:30 +08:00
|
|
|
}
|
|
|
|
|
local extensions = {
|
|
|
|
|
["Linux"] = "so",
|
|
|
|
|
["Windows"] = "dll",
|
|
|
|
|
["MacOSX"] = "so",
|
2025-05-06 08:04:36 +08:00
|
|
|
["OSX"] = "so",
|
2024-12-11 18:00:30 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
package.cpath = package.cpath..";"..path.."bin/"..folders[ffi.os].."/?."..extensions[ffi.os]
|
|
|
|
|
package.path = package.path..";"..path.."/ffi/sokol/?.lua"
|
|
|
|
|
package.path = package.path..";"..path.."lua/?.lua"
|
2024-12-12 20:56:46 +08:00
|
|
|
package.path = package.path..";"..path.."lua/?/init.lua"
|
2024-12-11 18:00:30 +08:00
|
|
|
package.path = package.path..";"..path.."/?.lua"
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
---------------------------------------------------------------------------------------
|
|
|
|
|
|
2024-12-24 09:39:08 +08:00
|
|
|
local function log_info( str )
|
|
|
|
|
print(string.format("[Info] %s", str))
|
2024-12-16 14:48:31 +08:00
|
|
|
end
|
|
|
|
|
|
2024-12-24 09:39:08 +08:00
|
|
|
local function log_error( str )
|
|
|
|
|
print(string.format("[Error] %s", str))
|
2024-12-16 14:48:31 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
|
|
---------------------------------------------------------------------------------------
|
|
|
|
|
|
2024-12-24 09:39:08 +08:00
|
|
|
dirtools.add_cpath = function( new_path )
|
|
|
|
|
package.cpath = package.cpath..";"..new_path
|
2024-12-11 18:00:30 +08:00
|
|
|
end
|
|
|
|
|
|
2024-12-24 09:39:08 +08:00
|
|
|
---------------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
dirtools.add_package_path = function( new_path )
|
|
|
|
|
package.path = package.path..";"..new_path.."/?.lua"
|
2024-12-11 18:00:30 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
|
|
---------------------------------------------------------------------------------------
|
|
|
|
|
|
2025-11-13 16:35:40 +08:00
|
|
|
dirtools.add_init_package_path = function( new_path )
|
|
|
|
|
package.path = package.path..";"..new_path.."/init.lua"
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
---------------------------------------------------------------------------------------
|
|
|
|
|
|
2024-12-11 18:00:30 +08:00
|
|
|
dirtools.compare_paths = function(p1, p2)
|
|
|
|
|
-- Remove minus, they seem to cause the most problems
|
|
|
|
|
p1 = string.gsub(p1, "%-", "_")
|
|
|
|
|
p2 = string.gsub(p2, "%-", "_")
|
|
|
|
|
return string.match(p1, p2)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
---------------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
dirtools.combine_path = function( base, addition )
|
2024-12-16 14:48:31 +08:00
|
|
|
-- remove any returns and tabs from both
|
|
|
|
|
base = string.gsub(base, "[\n\t\r]+","")
|
|
|
|
|
addition = string.gsub(addition, "[\n\t\r]+","")
|
|
|
|
|
return string.format("%s%s%s",base,sep,addition)
|
2024-12-11 18:00:30 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
|
|
---------------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
dirtools.get_drives = function()
|
|
|
|
|
|
|
|
|
|
local drives = {}
|
|
|
|
|
if(ffi.os == "Windows") then
|
|
|
|
|
|
|
|
|
|
local cmd = "fsutil fsinfo drives"
|
2025-11-13 16:35:40 +08:00
|
|
|
run_popen(cmd, function(fh)
|
2024-12-11 18:00:30 +08:00
|
|
|
local data = fh:read("*a")
|
|
|
|
|
if(string.match(data, "Drives: ")) then
|
|
|
|
|
data = string.sub(data, 10, -1)
|
|
|
|
|
for f in string.gmatch(data, "([^%s]+)") do
|
|
|
|
|
f = string.sub(f, 1, -2)
|
|
|
|
|
tinsert(drives, f)
|
|
|
|
|
end
|
|
|
|
|
end
|
2025-11-13 16:35:40 +08:00
|
|
|
end)
|
2024-12-11 18:00:30 +08:00
|
|
|
else
|
|
|
|
|
-- Note: Add more -t <filetype> to support different file system mounts
|
|
|
|
|
local cmd = "df -h -t ext4 --output=target"
|
|
|
|
|
|
2025-11-13 16:35:40 +08:00
|
|
|
run_popen(cmd, function(fh)
|
2024-12-11 18:00:30 +08:00
|
|
|
local data = fh:read("*a")
|
|
|
|
|
local count = 0
|
|
|
|
|
for f in string.gmatch(data, "(.-)\n") do
|
|
|
|
|
f = string.gsub(f, "%s+", "")
|
|
|
|
|
if(count > 0) then
|
|
|
|
|
if(#f > 0) then
|
|
|
|
|
tinsert(drives, f)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
count = count + 1
|
|
|
|
|
end
|
2025-11-13 16:35:40 +08:00
|
|
|
end)
|
2024-12-11 18:00:30 +08:00
|
|
|
end
|
|
|
|
|
return drives
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
---------------------------------------------------------------------------------------
|
|
|
|
|
-- Much safer way to build folder than pattern match (very unstable)
|
|
|
|
|
dirtools.get_folder = function(path)
|
|
|
|
|
local parts = {}
|
|
|
|
|
local patt = "(.-)[\\]"
|
|
|
|
|
if(ffi.os ~= "Windows") then patt = "(.-)[/]" end
|
|
|
|
|
for pseg in string.gmatch(path, patt) do
|
|
|
|
|
tinsert(parts, pseg)
|
|
|
|
|
end
|
|
|
|
|
if(#parts < 1) then return path end
|
|
|
|
|
return tconcat(parts, sep)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
---------------------------------------------------------------------------------------
|
|
|
|
|
-- Check if the path is folder or file
|
|
|
|
|
if(ffi.os == "Windows") then
|
|
|
|
|
dirtools.is_folder = function(path)
|
|
|
|
|
|
2024-12-27 10:29:58 +08:00
|
|
|
if(path == nil) then return nil end
|
|
|
|
|
|
2025-11-13 16:35:40 +08:00
|
|
|
run_popen([[attrib "]]..path..[["]], function(fh)
|
2024-12-11 18:00:30 +08:00
|
|
|
local res = fh:read("*a")
|
|
|
|
|
local fileattr = string.sub(res, 1, 20)
|
|
|
|
|
fileattr = string.gsub(fileattr, " ", "")
|
2024-12-27 10:29:58 +08:00
|
|
|
if(string.match(res, "^File not found")) then return nil end
|
2024-12-11 18:00:30 +08:00
|
|
|
return (#fileattr == 0)
|
2025-11-13 16:35:40 +08:00
|
|
|
end)
|
2024-12-27 10:29:58 +08:00
|
|
|
return nil
|
2024-12-11 18:00:30 +08:00
|
|
|
end
|
|
|
|
|
else
|
|
|
|
|
dirtools.is_folder = function(path)
|
|
|
|
|
|
2024-12-27 10:29:58 +08:00
|
|
|
if(path == nil) then return nil end
|
|
|
|
|
|
2026-07-25 13:50:59 +08:00
|
|
|
run_popen("file "..path, function(fh)
|
2024-12-11 18:00:30 +08:00
|
|
|
local res = fh:read("*a")
|
|
|
|
|
local folder = string.match(res, ".-: directory$")
|
|
|
|
|
return (folder ~= nil)
|
2026-07-25 13:50:59 +08:00
|
|
|
end, "r")
|
2024-12-27 10:29:58 +08:00
|
|
|
return nil
|
2024-12-11 18:00:30 +08:00
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
---------------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
dirtools.make_folder = function(folderpath)
|
|
|
|
|
|
2024-12-27 21:27:01 +08:00
|
|
|
local found = dirtools.is_folder(folderpath)
|
|
|
|
|
if(found == nil) then
|
2024-12-25 20:52:09 +08:00
|
|
|
local cmd = [[mkdir -f "]]..folderpath..[["]]
|
|
|
|
|
if(ffi.os == "Windows") then cmd = [[mkdir "]]..folderpath..[["]] end
|
2025-11-13 16:35:40 +08:00
|
|
|
run_popen(cmd, function(fh)
|
2024-12-11 18:00:30 +08:00
|
|
|
local data = fh:read("*a")
|
|
|
|
|
log_info(data)
|
2024-12-27 21:27:01 +08:00
|
|
|
return true
|
2025-11-13 16:35:40 +08:00
|
|
|
end)
|
2024-12-27 21:27:01 +08:00
|
|
|
else
|
|
|
|
|
log_info("Folder already exists: "..folderpath)
|
2024-12-11 18:00:30 +08:00
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
---------------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
dirtools.get_relative_path = function( fullpath, parent_path )
|
|
|
|
|
|
|
|
|
|
local rpath = nil
|
|
|
|
|
local str_st, str_end = string.find( fullpath, parent_path, 1, true )
|
|
|
|
|
if(str_st and str_end) then
|
|
|
|
|
rpath = string.sub(fullpath, str_end+2, -1)
|
|
|
|
|
end
|
|
|
|
|
return rpath
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
---------------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
dirtools.get_app_path = function( expected_root_folder )
|
|
|
|
|
|
|
|
|
|
local base_dir = "."
|
|
|
|
|
if(ffi.os == "Windows") then
|
2025-11-13 16:35:40 +08:00
|
|
|
base_dir = run_popen("cd", function(cmdh)
|
|
|
|
|
return cmdh:read("*a")
|
|
|
|
|
end)
|
|
|
|
|
base_dir = string.gsub(base_dir, "\n", "")
|
2024-12-11 18:00:30 +08:00
|
|
|
else
|
2025-11-13 16:35:40 +08:00
|
|
|
base_dir = run_popen("pwd", function(cmdh)
|
|
|
|
|
return cmdh:read("*a")
|
|
|
|
|
end)
|
|
|
|
|
base_dir = string.gsub(base_dir, "\n", "")
|
2024-12-11 18:00:30 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
|
|
if(expected_root_folder) then
|
|
|
|
|
local folder_name = expected_root_folder
|
|
|
|
|
local last_folder, remain = string.match(base_dir, "(.-"..folder_name..")(.-)")
|
|
|
|
|
last_folder = last_folder or ""
|
|
|
|
|
remain = remain or ""
|
|
|
|
|
|
|
|
|
|
remain = remain:gsub("%s+", "")
|
|
|
|
|
if(string.len(last_folder) > 0) then
|
|
|
|
|
base_dir = last_folder..sep
|
|
|
|
|
else
|
|
|
|
|
base_dir = ""
|
|
|
|
|
end
|
|
|
|
|
end
|
2024-12-25 20:52:09 +08:00
|
|
|
-- log_info("Base Directory: "..base_dir)
|
2024-12-11 18:00:30 +08:00
|
|
|
return base_dir
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
-- --------------------------------------------------------------------------------------
|
|
|
|
|
|
2025-11-13 16:35:40 +08:00
|
|
|
dirtools.get_absolute_path = function( relative_path )
|
|
|
|
|
|
|
|
|
|
if(ffi.os == "Windows") then
|
|
|
|
|
|
|
|
|
|
ffi.cdef[[
|
|
|
|
|
uint32_t GetFullPathNameA(
|
|
|
|
|
const char *lpFileName,
|
|
|
|
|
uint32_t nBufferLength,
|
|
|
|
|
char *lpBuffer,
|
|
|
|
|
char **lpFilePart
|
|
|
|
|
);
|
|
|
|
|
]]
|
|
|
|
|
|
|
|
|
|
-- Convert the string to a wide-character (UTF-16) string for Windows API compatibility
|
|
|
|
|
local path = ffi.new("char[?]", #relative_path + 1)
|
|
|
|
|
ffi.fill(path, 0, #relative_path + 1)
|
|
|
|
|
ffi.copy(path, relative_path)
|
|
|
|
|
|
|
|
|
|
-- Allocate buffer for the result (Windows typically expects a 260-char buffer for file paths)
|
|
|
|
|
local buffer = ffi.new("char[260]")
|
|
|
|
|
|
|
|
|
|
-- Call GetFullPathNameW
|
|
|
|
|
local result_len = ffi.C.GetFullPathNameA(path, 260, buffer, nil)
|
|
|
|
|
-- Return the absolute path as a Lua string
|
|
|
|
|
return ffi.string(buffer, result_len) -- Since it's UTF-16, multiply length by 2
|
|
|
|
|
|
|
|
|
|
else
|
|
|
|
|
-- Try realpath directly (file or directory)
|
|
|
|
|
local abs_path = nil
|
|
|
|
|
run_popen(string.format('realpath "%s" 2>/dev/null', relative_path), function(handle)
|
|
|
|
|
abs_path = handle:read("*a")
|
|
|
|
|
end)
|
|
|
|
|
|
|
|
|
|
abs_path = abs_path and abs_path:match("^%s*(.-)%s*$")
|
|
|
|
|
if abs_path and abs_path ~= "" then
|
|
|
|
|
return abs_path
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
-- If that failed, try resolving parent dir and appending filename
|
|
|
|
|
local parent, filename = path:match("^(.*)/([^/]+)$")
|
|
|
|
|
if not parent or not filename then
|
|
|
|
|
-- No slashes — assume it's a file in current directory
|
|
|
|
|
local cwd = run_popen("pwd" , function(fh)
|
|
|
|
|
return fh:read("*l")
|
|
|
|
|
end)
|
|
|
|
|
return cwd .. "/" .. path
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
-- Resolve parent dir
|
|
|
|
|
local abs_parent = run_popen(string.format('realpath "%s" 2>/dev/null', parent), function(h2)
|
|
|
|
|
return h2:read("*a")
|
|
|
|
|
end)
|
|
|
|
|
|
|
|
|
|
abs_parent = abs_parent and abs_parent:match("^%s*(.-)%s*$")
|
|
|
|
|
|
|
|
|
|
if abs_parent and abs_parent ~= "" then
|
|
|
|
|
return abs_parent .. "/" .. filename
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
-- Fallback: return original path
|
|
|
|
|
return path
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
-- --------------------------------------------------------------------------------------
|
|
|
|
|
|
2024-12-11 18:00:30 +08:00
|
|
|
dirtools.get_folderslist = function(path, cache_update)
|
|
|
|
|
|
|
|
|
|
-- Check path first. If its a drive on windows then no caching
|
|
|
|
|
if(ffi.os == "Windows") then
|
|
|
|
|
local colon = string.sub(path, 2,-1)
|
|
|
|
|
if(colon == ":") then cache_update = true; path = path..sep end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
if(list_folders_cache[path] and cache_update == nil) then
|
|
|
|
|
return list_folders_cache[path]
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
local files = {}
|
|
|
|
|
table.insert(files, 1, { name = ".." })
|
|
|
|
|
|
|
|
|
|
-- Fill with temp file list of dir /b
|
2026-07-25 13:50:59 +08:00
|
|
|
local res = run_popen(string.format(allfolders_cmd, path), function(fh)
|
2025-11-13 16:35:40 +08:00
|
|
|
return fh:read("*a")
|
2026-07-25 13:50:59 +08:00
|
|
|
end, "r")
|
2025-11-13 16:35:40 +08:00
|
|
|
if(res == nil) then return files end
|
2024-12-11 18:00:30 +08:00
|
|
|
|
|
|
|
|
for f in string.gmatch(res, "(.-)\n") do
|
|
|
|
|
local newfile = { name = ffi.string(f), folder = true }
|
|
|
|
|
newfile.select = ffi.new("int[1]")
|
|
|
|
|
newfile.select[0] = 0
|
|
|
|
|
table.insert(files, newfile)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
list_folders_cache[path] = files
|
|
|
|
|
return files
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
---------------------------------------------------------------------------------------
|
|
|
|
|
|
2024-12-25 20:52:09 +08:00
|
|
|
dirtools.get_dirlist = function(path, cache_update, extfilter)
|
2024-12-11 18:00:30 +08:00
|
|
|
|
|
|
|
|
-- Check path first. If its a drive on windows then no caching
|
|
|
|
|
if(ffi.os == "Windows") then
|
|
|
|
|
local colon = string.match(path, "(.)$")
|
|
|
|
|
if(colon == ":") then path = path..sep end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
if(list_cache[path] and cache_update == nil) then
|
|
|
|
|
return list_cache[path]
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
-- Get all the folders first
|
2024-12-25 20:52:09 +08:00
|
|
|
local files = dirtools.get_folderslist(path, cache_update)
|
2024-12-11 18:00:30 +08:00
|
|
|
|
|
|
|
|
-- Add the files to the list.
|
|
|
|
|
-- Fill with temp file list of dir /b
|
2025-11-13 16:35:40 +08:00
|
|
|
local res = run_popen(string.format(allfiles_cmd, path), function(fh)
|
2024-12-11 18:00:30 +08:00
|
|
|
res = fh:read("*a")
|
|
|
|
|
if(#res == 0) then
|
|
|
|
|
list_cache[path] = files
|
2025-11-13 16:35:40 +08:00
|
|
|
return nil
|
2024-12-11 18:00:30 +08:00
|
|
|
end
|
2025-11-13 16:35:40 +08:00
|
|
|
return res
|
|
|
|
|
end)
|
|
|
|
|
if(res == nil) then return files end
|
2024-12-11 18:00:30 +08:00
|
|
|
|
|
|
|
|
for f in string.gmatch(res, "(.-)\n") do
|
2025-11-13 16:35:40 +08:00
|
|
|
if(not string.match(f, "File Not Found")) then
|
|
|
|
|
-- Only match with filter on end of filename
|
|
|
|
|
local add_file = true
|
|
|
|
|
if(extfilter) then
|
|
|
|
|
add_file = string.match(f, extfilter.."$")
|
|
|
|
|
end
|
|
|
|
|
if(add_file) then
|
|
|
|
|
local newfile = { name = ffi.string(f), folder = nil }
|
|
|
|
|
newfile.select = ffi.new("int[1]")
|
|
|
|
|
newfile.select[0] = 0
|
|
|
|
|
table.insert(files, newfile)
|
|
|
|
|
end
|
2024-12-25 20:52:09 +08:00
|
|
|
end
|
2024-12-11 18:00:30 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
|
|
list_cache[path] = files
|
|
|
|
|
return files
|
|
|
|
|
end
|
|
|
|
|
|
2025-11-13 16:35:40 +08:00
|
|
|
---------------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
dirtools.get_dir_names = function(path)
|
|
|
|
|
local filelist = {}
|
|
|
|
|
local res = run_popen(string.format(allfolders_cmd, path), function(fh)
|
|
|
|
|
return fh:read("*a")
|
|
|
|
|
end)
|
|
|
|
|
if(res) then
|
|
|
|
|
for f in string.gmatch(res, "(.-)\n") do
|
|
|
|
|
tinsert(filelist, f)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
local res = run_popen(string.format(allfiles_cmd, path), function(fh)
|
|
|
|
|
return fh:read("*a")
|
|
|
|
|
end)
|
|
|
|
|
if(res) then
|
|
|
|
|
for f in string.gmatch(res, "(.-)\n") do
|
|
|
|
|
tinsert(filelist, f)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
return filelist
|
|
|
|
|
end
|
|
|
|
|
|
2024-12-11 18:00:30 +08:00
|
|
|
---------------------------------------------------------------------------------------
|
|
|
|
|
dirtools.path_match = function(list, path)
|
|
|
|
|
|
|
|
|
|
for i,v in ipairs(list) do
|
|
|
|
|
if(dirtools.compare_paths(v.name, path)) then
|
|
|
|
|
return true
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
return false
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
---------------------------------------------------------------------------------------
|
|
|
|
|
dirtools.get_parent = function( path )
|
|
|
|
|
if(path == nil or path == "") then path = "." end
|
|
|
|
|
local parentpath = dirtools.get_folder(path)
|
|
|
|
|
return parentpath
|
|
|
|
|
end
|
|
|
|
|
|
2024-12-24 09:39:08 +08:00
|
|
|
------------------------------------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
local function windows_dir( folder )
|
2025-11-13 16:35:40 +08:00
|
|
|
local result = run_popen("dir /AD /b \""..tostring(folder).."\"", function(f)
|
|
|
|
|
return f:read("*a")
|
|
|
|
|
end)
|
2024-12-24 09:39:08 +08:00
|
|
|
return result
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
------------------------------------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
local function unix_dir( folder )
|
2025-11-13 16:35:40 +08:00
|
|
|
local result = run_popen("ls -d -A -G -N -1 * \""..tostring(folder).."\"", function(f)
|
|
|
|
|
return f:read("*a")
|
|
|
|
|
end)
|
2024-12-24 09:39:08 +08:00
|
|
|
return result
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
------------------------------------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
dirtools.getdirs = function ( folder )
|
|
|
|
|
local dirstr = ""
|
|
|
|
|
if ffi.os == "HTML5" then
|
|
|
|
|
|
|
|
|
|
elseif ffi.os == "Windows" then
|
|
|
|
|
dirstr = windows_dir(folder)
|
|
|
|
|
else
|
|
|
|
|
dirstr = unix_dir(folder)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
-- split string by line endings into a nice table
|
|
|
|
|
local restbl = nil
|
|
|
|
|
if(dirstr) then
|
|
|
|
|
restbl = csplit(dirstr, "\n")
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
return restbl
|
|
|
|
|
end
|
|
|
|
|
|
2024-12-11 18:00:30 +08:00
|
|
|
---------------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
dirtools.find_folder = function( start_path, parent_up, folder_name )
|
|
|
|
|
|
|
|
|
|
-- If starting in current application folder, convert it to a full path
|
|
|
|
|
if(start_path == ".") then
|
|
|
|
|
start_path = dirtools.get_app_path()
|
|
|
|
|
end
|
|
|
|
|
local list = dirtools.get_folderslist(start_path)
|
|
|
|
|
if(dirtools.path_match(list, "sokol-luajit") == true) then
|
|
|
|
|
return start_path
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
local current = start_path
|
|
|
|
|
for i=1, parent_up do
|
|
|
|
|
current = dirtools.get_parent(current)
|
|
|
|
|
list = dirtools.get_folderslist(current)
|
|
|
|
|
if(dirtools.path_match(list, "sokol-luajit") == true) then
|
|
|
|
|
return current
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
return nil
|
|
|
|
|
end
|
|
|
|
|
|
2024-12-24 09:39:08 +08:00
|
|
|
---------------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
dirtools.fileparts = function( path )
|
2025-11-13 16:35:40 +08:00
|
|
|
return string.match(path, "^(.-[\\/])([^\\/]-)%.([^\\/]+)$")
|
2024-12-24 09:39:08 +08:00
|
|
|
end
|
|
|
|
|
|
2025-11-13 16:35:40 +08:00
|
|
|
|
2024-12-11 18:00:30 +08:00
|
|
|
---------------------------------------------------------------------------------------
|
|
|
|
|
dirtools.change_folder = function( path, child )
|
|
|
|
|
|
|
|
|
|
local newpath = path..sep..child
|
|
|
|
|
return newpath
|
|
|
|
|
end
|
|
|
|
|
|
2025-11-13 16:35:40 +08:00
|
|
|
---------------------------------------------------------------------------------------
|
|
|
|
|
dirtools.change_dir = function( path )
|
|
|
|
|
if(ffi.os == "Windows") then
|
|
|
|
|
os.execute("chdir /D "..path)
|
|
|
|
|
else
|
|
|
|
|
os.execute("cd "..path)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2024-12-11 18:00:30 +08:00
|
|
|
---------------------------------------------------------------------------------------
|
|
|
|
|
-- By default the paths are added
|
|
|
|
|
|
|
|
|
|
dirtools.init = function( base_path )
|
|
|
|
|
local path = dirtools.get_app_path(base_path)
|
|
|
|
|
dirtools.add_default_paths(path)
|
2025-01-29 17:52:15 +08:00
|
|
|
return dirtools
|
2024-12-11 18:00:30 +08:00
|
|
|
end
|
|
|
|
|
|
2025-11-13 16:35:40 +08:00
|
|
|
---------------------------------------------------------------------------------------
|
|
|
|
|
-- Windows is posix compliant but there are some subtle difference of course.
|
|
|
|
|
|
|
|
|
|
if(ffi.os == "Windows") then
|
|
|
|
|
ffi.cdef[[
|
|
|
|
|
typedef uint64_t __time64_t;
|
|
|
|
|
|
|
|
|
|
typedef unsigned int _dev_t;
|
|
|
|
|
typedef unsigned short _ino_t;
|
|
|
|
|
typedef unsigned short _mode_t;
|
|
|
|
|
|
|
|
|
|
struct __stat64
|
|
|
|
|
{
|
|
|
|
|
_dev_t st_dev;
|
|
|
|
|
_ino_t st_ino;
|
|
|
|
|
_mode_t st_mode;
|
|
|
|
|
short st_nlink;
|
|
|
|
|
short st_uid;
|
|
|
|
|
short st_gid;
|
|
|
|
|
_dev_t st_rdev;
|
|
|
|
|
uint64_t st_size;
|
|
|
|
|
__time64_t st_atime;
|
|
|
|
|
__time64_t st_mtime;
|
|
|
|
|
__time64_t st_ctime;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
int _stat64(const char *path, struct __stat64 *buf);
|
|
|
|
|
]]
|
|
|
|
|
else
|
|
|
|
|
ffi.cdef[[
|
|
|
|
|
|
|
|
|
|
typedef uint64_t __time64_t;
|
|
|
|
|
|
|
|
|
|
typedef uint64_t _dev_t;
|
|
|
|
|
typedef uint64_t _ino_t;
|
|
|
|
|
typedef unsigned int _mode_t;
|
|
|
|
|
|
|
|
|
|
struct __stat {
|
|
|
|
|
_dev_t st_dev;
|
|
|
|
|
_ino_t st_ino;
|
|
|
|
|
uint64_t st_nlink;
|
|
|
|
|
_mode_t st_mode;
|
|
|
|
|
unsigned int st_uid;
|
|
|
|
|
unsigned int st_gid;
|
|
|
|
|
int __pad0;
|
|
|
|
|
_dev_t st_rdev;
|
|
|
|
|
int64_t st_size;
|
|
|
|
|
__time64_t st_atime;
|
|
|
|
|
__time64_t st_mtime;
|
|
|
|
|
__time64_t st_ctime;
|
|
|
|
|
char pad[62];
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
int stat(const char *path, struct __stat *buf);
|
|
|
|
|
]]
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
dirtools.get_fileinfo = function(path)
|
|
|
|
|
if string.len(path) == 0 then return nil end
|
|
|
|
|
|
|
|
|
|
local buf
|
|
|
|
|
local S_IFDIR = 0x4000
|
|
|
|
|
local S_IFREG = 0x8000
|
|
|
|
|
|
|
|
|
|
-- Different handling for Windows vs Linux
|
|
|
|
|
if ffi.os == "Windows" then
|
|
|
|
|
buf = ffi.new("struct __stat64[1]")
|
|
|
|
|
if ffi.C._stat64(path, buf) ~= 0 then
|
|
|
|
|
print("[Error] dirtools.get_fileinfo | File not found or inaccessible: ", path)
|
|
|
|
|
return nil
|
|
|
|
|
end
|
|
|
|
|
else
|
|
|
|
|
buf = ffi.new("struct __stat[1]")
|
|
|
|
|
if ffi.C.stat(path, buf) ~= 0 then
|
|
|
|
|
print("[Error] dirtools.get_fileinfo | File not found or inaccessible: ", path)
|
|
|
|
|
return nil
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
-- Fill the file info
|
|
|
|
|
local info = {
|
|
|
|
|
size = tonumber(buf[0].st_size),
|
|
|
|
|
modified = tonumber(buf[0].st_mtime),
|
|
|
|
|
type = nil,
|
|
|
|
|
path = path,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
-- Check file type using st_mode bits
|
|
|
|
|
local mode = tonumber(buf[0].st_mode)
|
|
|
|
|
|
|
|
|
|
if bit.band(mode, S_IFDIR) == S_IFDIR then
|
|
|
|
|
info.type = "dir"
|
|
|
|
|
elseif bit.band(mode, S_IFREG) == S_IFREG then
|
|
|
|
|
info.type = "file"
|
|
|
|
|
else
|
|
|
|
|
info.type = "other"
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
return info
|
|
|
|
|
end
|
|
|
|
|
|
2024-12-11 18:00:30 +08:00
|
|
|
---------------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
return dirtools
|
|
|
|
|
|
2024-10-27 16:11:43 +08:00
|
|
|
---------------------------------------------------------------------------------------
|