Created
November 3, 2019 09:36
-
-
Save megagrump/4befa7f1b9359e48efdff40119cad9bd to your computer and use it in GitHub Desktop.
lovefs_clean
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- by [email protected] | |
-- 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