Skip to content

Instantly share code, notes, and snippets.

@megagrump

megagrump/fs.lua

Created Nov 3, 2019
Embed
What would you like to do?
lovefs_clean
-- by megagrump@pm.me
-- contains some code by Caldas Lopes: https://github.com/linux-man/lovefs
-- License: MIT
local ffi = require("ffi")
local bit = require("bit")
local fs
if ffi.os == "Windows" then
local lib = ffi.C
ffi.cdef[[
#pragma pack(push)
#pragma pack(1)
struct WIN32_FIND_DATAW {
uint32_t dwFileWttributes;
uint64_t ftCreationTime;
uint64_t ftLastAccessTime;
uint64_t ftLastWriteTime;
uint32_t dwReserved[4];
char cFileName[520];
char cAlternateFileName[28];
};
#pragma(pop)
int MultiByteToWideChar(unsigned int CodePage, uint32_t dwFlags, const char* lpMultiByteStr, int cbMultiByte, const char* lpWideCharStr, int cchWideChar);
int WideCharToMultiByte(unsigned int CodePage, uint32_t dwFlags, const char* lpWideCharStr, int cchWideChar, const char* lpMultiByteStr, int cchMultiByte, const char* default, int* used);
int _wchdir(const char* path);
char* _wgetcwd(char* buffer, int maxlen);
void* FindFirstFileW(const char* pattern, struct WIN32_FIND_DATAW* fd);
bool FindNextFileW(void* ff, struct WIN32_FIND_DATAW* fd);
bool FindClose(void* ff);
void* CreateFileW(const char* lpFileName, unsigned long dwDesiredAccess, unsigned long dwShareMode, void* lpSecurityAttributes, unsigned long dwCreationDisposition, unsigned long dwFlagsAndAttributes, void* hTemplateFile);
long WriteFile(void* hFile, void* lpBuffer, unsigned long nNumberOfBytesToWrite, unsigned long* lpNumberOfBytesWritten, void* lpOverlapped);
long ReadFile(void* hFile, void* lpBuffer, unsigned long nNumberOfBytesToRead, unsigned long* lpNumberOfBytesRead, void* lpOverlapped);
long GetFileSize(void* hFile, void* lpFileSizeHigh);
void CloseHandle(void* hHandle);
int GetLogicalDrives(void);
]]
local GENERIC_READ = 0x80000000
local GENERIC_WRITE = 0x40000000
local WIN32_FIND_DATA = ffi.typeof('struct WIN32_FIND_DATAW')
local INVALID_HANDLE = ffi.cast('void*', -1)
local MAX_PATH = 260
local _cwdBuffer = ffi.new("char[?]", MAX_PATH * 2 + 2)
local function u2w(str, code)
local size = lib.MultiByteToWideChar(code or 65001, 0, str, #str, nil, 0)
local buf = ffi.new("char[?]", size * 2 + 2)
lib.MultiByteToWideChar(code or 65001, 0, str, #str, buf, size * 2)
return buf
end
local function w2u(wstr, code)
local size = lib.WideCharToMultiByte(code or 65001, 0, wstr, -1, nil, 0, nil, nil)
local buf = ffi.new("char[?]", size + 1)
size = lib.WideCharToMultiByte(code or 65001, 0, wstr, -1, buf, size, nil, nil)
return ffi.string(buf)
end
local function getWorkingDirectory()
return ffi.string(w2u(lib._wgetcwd(_cwdBuffer, MAX_PATH)))
end
local function getFileList(dir)
dir = dir or getWorkingDirectory()
local fd = ffi.new(WIN32_FIND_DATA)
local hFile = lib.FindFirstFileW(u2w(dir..'\\*'), fd)
ffi.gc(hFile, lib.FindClose)
local files = {}
if hFile ~= INVALID_HANDLE then
repeat
local fn = w2u(fd.cFileName)
if bit.band(fd.dwFileWttributes, 2) == 0 then
table.insert(files, {
name = fn,
type = bit.band(fd.dwFileWttributes, 16) == 16 and "directory" or "file"
})
end
until not lib.FindNextFileW(hFile, fd)
end
lib.FindClose(ffi.gc(hFile, nil))
return files
end
local function changeDirectory(dir)
return lib._wchdir(u2w(dir)) == 0
end
local function getDriveList(dir)
local drives = {}
local bits = lib.GetLogicalDrives()
for i = 0, 25 do
if bit.band(bits, 2 ^ i) > 0 then
table.insert(drives, string.char(65 + i))
end
end
return drives
end
local function readFile(path)
local handle = lib.CreateFileW(u2w(path), GENERIC_READ, 1, nil, 3, 128, nil)
if handle == INVALID_HANDLE then error("Could not open file") end
local size = lib.GetFileSize(handle, nil)
local buffer = ffi.new("char[?]", size)
local nread = ffi.new('unsigned long[?]', 1)
local result = lib.ReadFile(handle, buffer, size, nread, nil) ~= 0
lib.CloseHandle(handle)
if not result then
error("Could not read file")
end
return ffi.string(buffer, size)
end
local function writeFile(path, data)
local handle = lib.CreateFileW(u2w(path), GENERIC_WRITE, 0, nil, 2, 128, nil)
if handle == INVALID_HANDLE then error("Could not create file") end
local nwritten = ffi.new('unsigned long[?]', 1)
local result = lib.WriteFile(handle, ffi.cast("void*", data), #data, nwritten, nil) ~= 0
lib.CloseHandle(handle)
if not result then
error("Could not write file")
end
return result
end
local function writeTextFile(path, text)
local rn = text:gsub("([^\r\n]*)\r?\n", "%1\r\n")
return writeFile(path, rn)
end
fs = {
fileList = getFileList,
driveList = getDriveList,
currentDirectory = getWorkingDirectory,
changeDirectory = changeDirectory,
readFile = readFile,
writeFile = writeFile,
writeTextFile = writeTextFile,
pathSeparator = "\\",
maxPath = MAX_PATH,
}
else
ffi.cdef[[
struct dirent {
unsigned long d_ino; /* inode number */
unsigned long d_off; /* not an offset */
unsigned short d_reclen; /* length of this record */
unsigned char d_type; /* type of file; not supported by all filesystem types */
char d_name[256]; /* filename */
};
char* getcwd(char *buffer, int maxlen);
int chdir(const char* path);
struct DIR *opendir(const char *name);
struct dirent *readdir(struct DIR *dirstream);
int closedir(struct DIR *dirstream);
char *realpath(const char *name, char *resolved);
]]
local MAX_PATH = 4096
local _pathBuffer = ffi.new("char[?]", MAX_PATH)
local function getWorkingDirectory()
return ffi.string(ffi.C.getcwd(_pathBuffer, MAX_PATH))
end
local function getFileList(dir)
dir = dir or getWorkingDirectory()
local files = {}
local hDir = ffi.C.opendir(dir)
ffi.gc(hDir, ffi.C.closedir)
local hasDotDot = false
if hDir ~= nil then
local dirent = ffi.C.readdir(hDir)
while dirent ~= nil do
if dirent.d_type == 10 then
local realpath = ffi.C.realpath(dirent.d_name, _pathBuffer)
if realpath ~= nil then
realpath = ffi.string(realpath)
local isdir = ffi.C.opendir(realpath)
if isdir ~= nil then
dirent.d_type = 4
ffi.C.closedir(isdir)
else
dirent.d_type = 8
end
end
end
if dirent.d_type == 4 or dirent.d_type == 8 then
local file = {
name = ffi.string(dirent.d_name),
type = dirent.d_type == 4 and "directory" or "file"
}
table.insert(files, file)
hasDotDot = hasDotDot or file.name == ".."
end
dirent = ffi.C.readdir(hDir)
end
end
ffi.C.closedir(ffi.gc(hDir, nil))
if not hasDotDot then
table.insert(files, { name = "..", type = "directory" })
end
return files
end
function changeDirectory(dir)
return ffi.C.chdir(dir) == 0
end
function getDriveList()
return {}
end
function readFile(path, mode)
local file, err = io.open(path, "rb")
if not file then error(err) end
local data = file:read("*all")
file:close()
return data
end
function writeFile(path, data)
local file, err = io.open(path, "wb")
if not file then error(err) end
local okay = file:write(data)
file:close()
return okay
end
fs = {
fileList = getFileList,
driveList = getDriveList,
currentDirectory = getWorkingDirectory,
changeDirectory = changeDirectory,
readFile = readFile,
writeFile = writeFile,
writeTextFile = writeFile,
pathSeparator = "/",
maxPath = MAX_PATH,
}
end
function fs.splitPath(filepath)
local dir, name, ext
dir = filepath:match("(.*[\\/]).*$")
filepath = dir and filepath:sub(#dir + 1) or filepath
name = filepath:match("(.*)$")
if name then
local splitname
splitname, ext = name:match("(.*)%.(.*)$")
if splitname then name = splitname end
end
if ext and name == "" then
name, ext = "." .. ext, nil
end
if name == "" then name = nil end
if ext == "" then ext = nil end
return dir, name, ext
end
function fs.makePath(dir, name, ext)
local format = ""
if dir then
if dir:match("[/\\]$") then
format = format .. "%{dir}"
else
format = format .. "%{dir}%{sep}"
end
end
if name then format = format .. "%{name}" end
if ext and ext ~= "" then format = format .. ".%{ext}" end
return malib.utils.interpolate(format, {
dir = dir,
name = name,
ext = ext,
sep = fs.pathSeparator
})
end
return fs
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment