added missing functions

This commit is contained in:
Grump
2020-05-08 10:12:04 +02:00
parent bdf6a2fbe1
commit f97d0d60b6
3 changed files with 219 additions and 64 deletions

View File

@@ -8,7 +8,7 @@ ffi.cdef([[
typedef struct FILE FILE;
FILE* fopen(const char* pathname, const char* mode);
FILE* fopen(const char* path, const char* mode);
size_t fread(void* ptr, size_t size, size_t nmemb, FILE* stream);
size_t fwrite(const void* ptr, size_t size, size_t nmemb, FILE* stream);
int fclose(FILE* stream);
@@ -20,9 +20,9 @@ ffi.cdef([[
]])
local C = ffi.C
local fclose, ftell, fseek, fflush, feof = C.fclose, C.ftell, C.fseek, C.fflush
local fread, fwrite, feof, setvbuf = C.fread, C.fwrite, C.feof, C.setvbuf
local fopen, getcwd, chdir, unlink -- system specific
local fclose, ftell, fseek, fflush = C.fclose, C.ftell, C.fseek, C.fflush
local fread, fwrite, setvbuf = C.fread, C.fwrite, C.setvbuf
local fopen, getcwd, chdir, unlink, mkdir, rmdir, feof -- system specific
local loveC = ffi.os == 'Windows' and ffi.load('love') or C
local BUFFERMODE = {
@@ -37,10 +37,12 @@ if ffi.os == 'Windows' then
int WideCharToMultiByte(unsigned int cp, uint32_t flags, const wchar_t* wc, int cwc, const char* mb,
int cmb, const char* def, int* used);
int GetLogicalDrives(void);
int CreateDirectoryW(const wchar_t* path, void*);
int _wchdir(const wchar_t* path);
wchar_t* _wgetcwd(wchar_t* buffer, int maxlen);
FILE* _wfopen(const wchar_t* name, const wchar_t* mode);
int _wunlink(const wchar_t* name);
FILE* _wfopen(const wchar_t* path, const wchar_t* mode);
int _wunlink(const wchar_t* path);
int _wrmdir(const wchar_t* path);
]])
BUFFERMODE.line, BUFFERMODE.none = 64, 4
@@ -62,37 +64,54 @@ if ffi.os == 'Windows' then
local MAX_PATH = 260
local nameBuffer = ffi.new("wchar_t[?]", MAX_PATH + 1)
fopen = function(name, mode) return C._wfopen(towidestring(name), towidestring(mode)) end
fopen = function(path, mode) return C._wfopen(towidestring(path), towidestring(mode)) end
getcwd = function() return toutf8string(C._wgetcwd(nameBuffer, MAX_PATH)) end
chdir = function(path) return C._wchdir(towidestring(path)) end
unlink = function(name) return C._wunlink(towidestring(name)) end
chdir = function(path) return C._wchdir(towidestring(path)) == 0 end
unlink = function(path) return C._wunlink(towidestring(path)) == 0 end
mkdir = function(path) return C.CreateDirectoryW(towidestring(path), nil) ~= 0 end
rmdir = function(path) return C._wrmdir(towidestring(path)) == 0 end
else
ffi.cdef([[
char* getcwd(char *buffer, int maxlen);
int chdir(const char* path);
int unlink(const char* path);
int mkdir(const char* path, int mode);
int rmdir(const char* path);
]])
local MAX_PATH = 4096
local nameBuffer = ffi.new("char[?]", MAX_PATH)
fopen, unlink, chdir = C.fopen, C.unlink, C.chdir
fopen = C.fopen
unlink = function(path) return ffi.C.unlink(path) == 0 end
chdir = function(path) return ffi.C.chdir(path) == 0 end
mkdir = function(path) return ffi.C.mkdir(path, 0x1ed) == 0 end
rmdir = function(path) return ffi.C.rmdir(path) == 0 end
getcwd = function()
local cwd = C.getcwd(nameBuffer, MAX_PATH)
return cwd ~= nil and ffi.string(cwd) or nil
end
end
feof = function(stream) return C.feof(stream) ~= 0 end
-----------------------------------------------------------------------------
-- NOTE: nil checks on file handles MUST be explicit (_handle == nil)
-- due to ffi's NULL semantics!
local File = {}
local File = {
getBuffer = function(self) return self._bufferMode, self._bufferSize end,
getFilename = function(self) return self._name end,
getMode = function(self) return self._mode end,
isEOF = function(self) return not self:isOpen() or feof(self._handle) or self:tell() == self:getSize() end,
isOpen = function(self) return self._mode ~= 'c' and self._handle ~= nil end,
}
File.__index = File
function File:open(mode)
if self._mode ~= 'c' then return false, "File is already open" end
if mode ~= 'r' and mode ~= 'w' and mode ~= 'a' then
return false, "Invalid file open mode: " .. mode
end
@@ -111,10 +130,7 @@ function File:open(mode)
end
function File:close()
if self._handle == nil or self._mode == 'c' then
return false, "File is not open"
end
if self._handle == nil or self._mode == 'c' then return false, "File is not open" end
ffi.gc(self._handle, nil)
fclose(self._handle)
self._handle, self._mode = nil, 'c'
@@ -122,29 +138,30 @@ function File:close()
end
function File:setBuffer(mode, size)
bufferMode = BUFFERMODE[mode]
local bufferMode = BUFFERMODE[mode]
if not bufferMode then
return false, "Invalid buffer mode: " .. mode .. " (expected 'none', 'full', or 'line')"
return false, "Invalid buffer mode " .. mode .. " (expected 'none', 'full', or 'line')"
end
if mode == 'line' or mode == 'full' then
size = math.max(2, size or 2) -- Windows requires buffer to be at least 2 bytes
else
size = math.max(0, size or 0)
end
if self._mode == 'c' then
self._bufferMode, self._bufferSize = mode, size
if self._mode == 'c' then return true end
return true
end
return C.setvbuf(self._handle, nil, bufferMode, size) == 0
local success = C.setvbuf(self._handle, nil, bufferMode, size) == 0
if success then
self._bufferMode, self._bufferSize = mode, size
return true
end
return false, "Could not set buffer mode"
end
function File:getBuffer()
return self._bufferMode, self._bufferSize
end
function File:getFilename()
return self._name
end
function File:getMode()
return self._mode
end
function File:getSize()
-- NOTE: The correct way to do this would be a stat() call, which requires a
@@ -166,14 +183,6 @@ function File:getSize()
return size;
end
function File:isEOF()
return not self:isOpen() or feof(self._handle) ~= 0
end
function File:isOpen()
return self._mode ~= 'c' and self._handle ~= nil
end
function File:read(containerOrBytes, bytes)
if self._handle == nil or self._mode ~= 'r' then
return nil, 0
@@ -208,6 +217,38 @@ function File:read(containerOrBytes, bytes)
end
function File:lines()
if self._mode ~= 'r' then
error("File is not opened for reading")
end
local BUFFERSIZE = 4096
local buffer = ffi.new('unsigned char[?]', BUFFERSIZE)
local bytesRead = tonumber(fread(buffer, 1, BUFFERSIZE, self._handle))
local bufferPos = 0
local offset = self:tell()
return function()
self:seek(offset)
local line = {}
data = data
while true do
for i = bufferPos, bytesRead - 1 do
if buffer[i] ~= 10 and buffer[i] ~= 13 then
table.insert(line, string.char(buffer[i]))
elseif buffer[i] == 10 then
bufferPos = i + 1
return table.concat(line)
end
end
bytesRead = tonumber(fread(buffer, 1, BUFFERSIZE, self._handle))
if bytesRead == 0 then break end
offset, bufferPos = offset + bytesRead, 0
end
if #line > 0 then
return table.concat(line)
end
end
end
function File:write(data, size)
@@ -241,8 +282,8 @@ function File:tell()
end
function File:flush()
if self._handle == nil then return end
return fflush(self._handle)
if self._handle == nil then return false, "File is not open" end
return fflush(self._handle) == 0
end
function File:release()
@@ -366,7 +407,7 @@ function nativefs.getWorkingDirectory()
end
function nativefs.setWorkingDirectory(path)
if chdir(path) ~= 0 then
if not chdir(path) then
return false, "Could not set working directory"
end
return true
@@ -398,12 +439,28 @@ function nativefs.getInfo(path, filtertype)
end
function nativefs.createDirectory(path)
local current = ''
for dir in path:gmatch('[^/\\]+') do
current = (current == '' and current or current .. '/') .. dir
local info = nativefs.getInfo(current, 'directory')
if not info and not mkdir(current) then
return false, "Could not create directory " .. current
end
end
return true
end
function nativefs.remove(name)
if unlink(name) ~= 0 then
local info = nativefs.getInfo(name)
if info.type == 'directory' then
if not rmdir(name) then
return false, "Could not remove directory " .. name
end
else
if not unlink(name) then
return false, "Could not remove file " .. name
end
end
return true
end

4
test/data/𠆢ßЩ.txt Normal file
View File

@@ -0,0 +1,4 @@
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

View File

@@ -6,11 +6,11 @@ local lu = require('luaunit')
local fs
local equals, notEquals = lu.assertEquals, lu.assertNotEquals
local isError, containsError = lu.assertErrorMsgEquals, lu.assertErrorMsgContains
local contains = lu.assertStrContains
local function notFailed(ok, err)
equals(ok, true)
if err then print("ERROR: " .. err) end
equals(err, nil)
end
@@ -23,6 +23,9 @@ end
-----------------------------------------------------------------------------
local testFile1, testSize1 = 'data/ümläüt.txt', 446
local testFile2, testSize2 = 'data/𠆢ßЩ.txt', 450
function test_fs_newFile()
local file = fs.newFile('test.file')
notEquals(file, nil)
@@ -30,37 +33,37 @@ function test_fs_newFile()
end
function test_fs_newFileData()
local fd = fs.newFileData('data/ümläüt.txt')
local filesize = love.filesystem.newFile('data/ümläüt.txt'):getSize()
local fd = fs.newFileData(testFile1)
local filesize = love.filesystem.newFile(testFile1):getSize()
equals(fd:getSize(), filesize)
local d = love.filesystem.read('data/ümläüt.txt')
local d = love.filesystem.read(testFile1)
equals(fd:getString(), d)
end
function test_fs_mount()
equals(fs.mount('data', 'test_data'), true)
local data, size = love.filesystem.read('test_data/ümläüt.txt')
local data, size = love.filesystem.read('test_data/' .. love.path.leaf(testFile1))
notEquals(data, nil)
notEquals(size, nil)
equals(fs.unmount('data'), true)
end
function test_fs_read()
local text, textSize = love.filesystem.read('data/ümläüt.txt')
local data, size = fs.read('data/ümläüt.txt')
local text, textSize = love.filesystem.read(testFile1)
local data, size = fs.read(testFile1)
equals(size, textSize)
equals(data, text)
local data, size = fs.read('data/ümläüt.txt', 'all')
local data, size = fs.read(testFile1, 'all')
equals(size, textSize)
equals(data, text)
local data, size = fs.read('string', 'data/ümläüt.txt')
local data, size = fs.read('string', testFile1)
equals(size, textSize)
equals(data, text)
local data, size = fs.read('data', 'data/ümläüt.txt', 'all')
local data, size = fs.read('data', testFile1, 'all')
equals(size, textSize)
equals(data:type(), 'FileData')
equals(data:getSize(), size)
@@ -72,7 +75,7 @@ function test_fs_read()
end
function test_fs_write()
local data, size = fs.read('data/ümläüt.txt')
local data, size = fs.read(testFile1)
notFailed(fs.write('data/write.test', data))
local data2, size2 = love.filesystem.read('data', 'data/write.test')
@@ -84,7 +87,7 @@ function test_fs_write()
end
function test_fs_append()
local text, textSize = love.filesystem.read('data/ümläüt.txt')
local text, textSize = love.filesystem.read(testFile1)
fs.write('data/append.test', text)
fs.append('data/append.test', text)
@@ -96,6 +99,20 @@ function test_fs_append()
end
function test_fs_lines()
local count, bytes = 0, 0
for line in fs.lines(testFile1) do
count = count + 1
bytes = bytes + #line
end
equals(count, 4)
equals(bytes, testSize1 - count)
local code = ""
for line in fs.lines('main.lua') do
code = code .. line .. '\n'
end
local r = fs.read('main.lua')
equals(code, r)
end
function test_fs_load()
@@ -155,27 +172,40 @@ function test_fs_getInfo()
notEquals(info, nil)
equals(info.type, 'file')
local info = fs.getInfo('data/ümläüt.txt')
local info = fs.getInfo(testFile1)
notEquals(info, nil)
equals(info.type, 'file')
equals(info.size, 446)
equals(info.size, testSize1)
notEquals(info.modtime, nil)
end
function test_fs_createDirectory()
notFailed(fs.createDirectory('data/a/b/c/defg/h'))
fs.remove('data/a/b/c/defg/h')
fs.remove('data/a/b/c/defg')
fs.remove('data/a/b/c')
fs.remove('data/a/b')
fs.remove('data/a')
end
function test_fs_remove()
local text = love.filesystem.read('data/ümläüt.txt')
local text = love.filesystem.read(testFile1)
fs.write('data/remove.test', text)
notFailed(fs.remove('data/remove.test'))
equals(love.filesystem.getInfo('data/remove.test'), nil)
fs.createDirectory('data/test1')
fs.createDirectory('data/test1/test2')
notFailed(fs.remove('data/test1/test2'))
equals(love.filesystem.getInfo('data/test1/test2'), nil)
notFailed(fs.remove('data/test1'))
equals(love.filesystem.getInfo('data/test1'), nil)
end
-----------------------------------------------------------------------------
function test_File_open()
local f = fs.newFile('data/ümläüt.txt')
local f = fs.newFile(testFile1)
notEquals(f, nil)
equals(f:isOpen(), false)
equals(f:getMode(), 'c')
@@ -195,16 +225,23 @@ function test_File_open()
equals(f:getMode(), 'c')
f:close()
local f = fs.newFile('data/𠆢ßЩ.txt')
local f = fs.newFile(testFile2)
notFailed(f:open('r'))
f:close()
end
function test_File_setBuffer()
local f = fs.newFile('data/test.test')
f:open('w')
notFailed(f:setBuffer('none', 0))
notFailed(f:setBuffer('line', 0))
notFailed(f:setBuffer('full', 0))
f:close()
fs.remove('data/test.test')
end
function test_File_isEOF()
local f = love.filesystem.newFile('data/ümläüt.txt')
local f = fs.newFile(testFile1)
f:open('r')
f:read(f:getSize() - 1)
equals(f:isEOF(), false)
@@ -214,27 +251,84 @@ function test_File_isEOF()
end
function test_File_read()
local f = fs.newFile(testFile1)
f:open('r')
local data, size = f:read(5)
equals(data, 'Lorem')
local data, size = f:read(6)
equals(data, ' ipsum')
f:close()
end
function test_File_lines()
local text = fs.read(testFile2)
local f = fs.newFile(testFile2)
local lines = ""
f:open('r')
for line in f:lines() do lines = lines .. line .. '\r\n' end
f:close()
equals(lines, text)
local text = fs.read(testFile1)
local f = fs.newFile(testFile1)
local lines = ""
f:open('r')
for line in f:lines() do lines = lines .. line .. '\n' end
f:close()
equals(lines, text)
end
function test_File_write()
local f = fs.newFile('data/write.test')
notFailed(f:open('w'))
notFailed(f:write('hello'))
f:close()
f:open('a')
notFailed(f:write('world'))
f:close()
notFailed(f:open('r'))
local hello, size = f:read()
equals(hello, 'helloworld')
equals(size, #'helloworld')
f:close()
fs.remove('data/write.test')
end
function test_File_seek()
local f = fs.newFile(testFile1)
f:open('r')
f:seek(72)
local data, size = f:read(6)
equals(data, 'tempor')
f:close()
end
function test_File_tell()
local f = fs.newFile(testFile1)
f:open('r')
f:read(172)
equals(f:tell(), 172)
f:close()
end
function test_File_flush()
local f = fs.newFile('data/write.test')
f:open('w')
f:write('hello')
notFailed(f:flush())
f:close()
end
local _globals = {}
function test_xxx_globalsCheck()
for k, v in pairs(_G) do
if v ~= _globals[k] then
print("LEAKED GLOBAL: " .. k)
end
equals(v, _globals[k])
end
end