Skip to content
Permalink
Browse files

Better interface for filesystem.lua

Add documentation to filesystem.lua

Expose fileExists and fileSize functions to lua so that we do not
have to read the contents of an iso file  when we only care if the
file exists.

Doesn't leak into bare lfs calls when checking corruption, so that
function works for ISO as well now.

Explicit function for checking file's existence which doesn't leak
implementation details.
  • Loading branch information...
TheCycoONE committed Jun 2, 2019
1 parent 8a050f9 commit 3c1a31748658c06aaa2c9210d4874307bb32de8a
@@ -1252,14 +1252,9 @@ function App:checkInstallFolder()
-- Check for file corruption for local files.
-- No check is done if the game is loaded from an ISO
local function check_corrupt(path, correct_size)
if self.fs.provider then
return true
end

local real_path = self.fs:getFilePath(path)
-- If the file exists but is smaller than usual it is probably corrupt
if real_path then
local real_size = lfs.attributes(real_path, "size")
if self.fs:fileExists(path) then
local real_size = self.fs:fileSize(path)
if real_size + 1024 < correct_size or real_size - 1024 > correct_size then
corrupt[#corrupt + 1] = path .. " (Size: " .. math.floor(real_size/1024) .. " kB / Correct: about " .. math.floor(correct_size/1024) .. " kB)"
end
@@ -81,11 +81,11 @@ function UINewGame:UINewGame(ui)
local avail_diff = {
{text = _S.new_game_window.medium, tooltip = _S.tooltip.new_game_window.medium, param = "full"},
}
if TheApp.fs:getFilePath("Levels", "Easy01.SAM") then
if TheApp.fs:fileExists("Levels", "Easy01.SAM") then
table.insert(avail_diff, 1, {text = _S.new_game_window.easy, tooltip = _S.tooltip.new_game_window.easy, param = "easy"})
self.difficulty = 2
end
if TheApp.fs:getFilePath("Levels", "Hard01.SAM") then
if TheApp.fs:fileExists("Levels", "Hard01.SAM") then
avail_diff[#avail_diff + 1] = {text = _S.new_game_window.hard, tooltip = _S.tooltip.new_game_window.hard, param = "hard"}
end
self.available_difficulties = avail_diff
@@ -23,19 +23,44 @@ local part_pattern = "[^" .. pathsep .. "]+"

local ISO_FS = require("TH").iso_fs

--! Layer for abstracting away differences in file systems
--! Layer for abstracting away differences in file systems.
--
-- In the traditional case, the FileSystem is associated with a path on the
-- actual filesystem, from which all other operations are considered relative.
--
-- The FileSystem is also able to delegate operations to the ISO_FS library
-- in the engine for reading / listing files off an ISO image instead of a
-- file system.
class "FileSystem"

---@type FileSystem
local FileSystem = _G["FileSystem"]

function FileSystem:FileSystem()
-- A mapping of normalized file names in the current directory to their
-- actual path names.
self.files = nil

-- A mapping of normalized directory names in the current directory to
-- an object containing their actual physical path.
self.sub_dirs = nil

-- The actual filesystem path that is the basis/root of this FileSystem. Not
-- used when a provider is specified.
self.physical_path = nil

-- The ISO_FS provider if we are reading from an ISO instead of a
-- filesystem, otherwise nil.
self.provider = nil
end

--! Convert a file name to a canonical, case insensitive format.
-- The format is based on the limitations of the ISO filesystem.
local function normalise(str)
return str:upper():gsub("_", "-")
end

--! Populate the files and sub_dirs values for the current FileSystem path.
function FileSystem:_enumerate()
self.sub_dirs = {}
self.files = {}
@@ -49,6 +74,12 @@ function FileSystem:_enumerate()
end
end

--! Set the root physical path for this FileSystem.
-- If the path is an ISO then set the provider. If the path is a directory
-- then set the pysical_path and populate the files and sub_dirs.
--
--!param physical_path (string) a path on the filesystem to either a directory
-- or theme hospital ISO file.
function FileSystem:setRoot(physical_path)
if physical_path:match"%.[iI][sS][oO]$" or physical_path:match"%.[iI][sS][oO]9660$" then
self.provider = ISO_FS()
@@ -58,9 +89,8 @@ function FileSystem:setRoot(physical_path)
return nil, err
end
return self.provider:setRoot(file)
else
self.provider = nil
end

if physical_path:sub(-1) == pathsep then
-- Trim off the trailing separator (lfs doesn't like querying the mode of a
-- directory with a trailing slash on win32)
@@ -69,11 +99,20 @@ function FileSystem:setRoot(physical_path)
if lfs.attributes(physical_path, "mode") ~= "directory" then
return nil, "Specified path ('" .. physical_path .. "') is not a directory"
end

self.provider = nil
self.physical_path = physical_path
self:_enumerate()

return true
end

--! list the files in the given path.
--
--!param virtual_path (string) a path relative to the FileSystem root.
--!param ... (string) the virtual_path may be split into separate arguments
-- for each level in the filesystem.
--!return (object) a map of normalized names to actual names of files.
function FileSystem:listFiles(virtual_path, ...)
if ... then
virtual_path = table.concat({virtual_path, ...}, pathsep)
@@ -99,20 +138,29 @@ function FileSystem:listFiles(virtual_path, ...)
return self.files
end

--! Combine the given path segments into a single path string.
--!param virtual_path (string) a path relative to the FileSystem root.
--!param ... (string) the virtual_path may be split into separate arguments
-- for each level in the filesystem.
local function getFullPath(virtual_path, ...)
if ... then
virtual_path = table.concat({virtual_path, ...}, pathsep)
end
return virtual_path
end

--! Return the contents of the file at the given path.
--
--!param virtual_path (string) a path relative to the FileSystem root.
--!param ... (string) the virtual_path may be split into separate arguments
-- for each level in the filesystem.
function FileSystem:readContents(virtual_path, ...)
virtual_path = getFullPath(virtual_path, ...)
if self.provider then
return self.provider:readContents(virtual_path)
end

local file, err = self:getFilePath(virtual_path)
local file, err = self:_getFilePath(virtual_path)
if not file then
return file, err
end
@@ -125,11 +173,55 @@ function FileSystem:readContents(virtual_path, ...)
return data
end

function FileSystem:getFilePath(virtual_path, ...)
--! Determines if the given path points to a real file.
--
--!param virtual_path (string) a path relative to the FileSystem root.
--!param ... (string) the virtual_path may be split into separate arguments
-- for each level in the filesystem.
function FileSystem:fileExists(virtual_path, ...)
virtual_path = getFullPath(virtual_path, ...)
if self.provider then
return self.provider:readContents(virtual_path)
elseif not self.sub_dirs then
local found, err = self.provider:fileExists(virtual_path)
if found then
return true
else
return nil, err
end
end

local s, e = self:_getFilePath(virtual_path)
return (not not s), e
end

--! Get the size of the file at the given path.
--
--!param virtual_path (string) a path relative to the FileSystem root.
--!param ... (string) the virtual_path may be split into separate arguments
-- for each level in the filesystem.
--!return (numeric) Number of bytes in the given file. Nil if the file doesn't
-- exist.
function FileSystem:fileSize(virtual_path, ...)
virtual_path = getFullPath(virtual_path, ...)
if self.provider then
return self.provider:fileSize(virtual_path)
end

local s, e = self:_getFilePath(virtual_path)
if not s then
return nil, e
end
return lfs.attributes(s, "size")
end

-- If the file exists and we are not using a provider then return a FileSystem
-- rooted in the directory that the directory the file is contained in.
-- Otherwise return nil and an error message.
function FileSystem:_getFilePath(virtual_path, ...)
virtual_path = getFullPath(virtual_path, ...)
if self.provider then
return nil, "This function is not supported for providers"
end
if not self.sub_dirs then
return nil, "Filesystem layer not initialised"
end
local is_file = false
@@ -352,7 +352,7 @@ bool iso_filesystem::initialise(std::FILE* fRawFile)
return false;
}

bool iso_filesystem::filename_compare(const file_metadata& lhs, const file_metadata& rhs)
bool iso_filesystem::file_metadata_less(const file_metadata& lhs, const file_metadata& rhs)
{
return lhs.path < rhs.path;
}
@@ -454,8 +454,8 @@ void iso_filesystem::build_file_lookup_table(uint32_t iSector, int iDirEntsSize,
if(prefix.size() == 0)
{
// The lookup table will be ordered by the underlying ordering of the
// disk, which isn't quite the ordering we want.
std::sort(files.begin(), files.end(), filename_compare);
// disk. we want it sorted by the path for ease of lookup.
std::sort(files.begin(), files.end(), file_metadata_less);
}
}

@@ -611,6 +611,36 @@ int l_isofs_set_root(lua_State *L)
}
}

int l_isofs_file_exists(lua_State *L)
{
iso_filesystem *pSelf = luaT_testuserdata<iso_filesystem>(L);
const char* sFilename = luaL_checkstring(L, 2);
iso_filesystem::file_handle iFile = pSelf->find_file(sFilename);
if(!iso_filesystem::isHandleGood(iFile))
{
lua_pushnil(L);
lua_pushfstring(L, "Could not find \'%s\' in .iso image", sFilename);
return 2;
}
lua_pushboolean(L, true);
return 1;
}

int l_isofs_file_size(lua_State *L)
{
iso_filesystem *pSelf = luaT_testuserdata<iso_filesystem>(L);
const char* sFilename = luaL_checkstring(L, 2);
iso_filesystem::file_handle iFile = pSelf->find_file(sFilename);
if(!iso_filesystem::isHandleGood(iFile))
{
lua_pushnil(L);
lua_pushfstring(L, "Could not find \'%s\' in .iso image", sFilename);
return 2;
}
lua_pushinteger(L, pSelf->get_file_size(iFile));
return 1;
}

int l_isofs_read_contents(lua_State *L)
{
iso_filesystem *pSelf = luaT_testuserdata<iso_filesystem>(L);
@@ -658,6 +688,8 @@ void lua_register_iso_fs(const lua_register_state* pState)
lua_class_binding<iso_filesystem> lcb(pState, "iso_fs", l_isofs_new, lua_metatable::iso_fs);
lcb.add_function(l_isofs_set_path_separator, "setPathSeparator");
lcb.add_function(l_isofs_set_root, "setRoot");
lcb.add_function(l_isofs_file_exists, "fileExists");
lcb.add_function(l_isofs_file_size, "fileSize");
lcb.add_function(l_isofs_read_contents, "readContents");
lcb.add_function(l_isofs_list_files, "listFiles");
}
@@ -143,5 +143,6 @@ class iso_filesystem
*/
void build_file_lookup_table(uint32_t iSector, int iDirEntsSize, const std::string& prefix);

static bool filename_compare(const file_metadata& lhs, const file_metadata& rhs);
//! std:less like implementation for file_metadata. Based on the path.
static bool file_metadata_less(const file_metadata& lhs, const file_metadata& rhs);
};

0 comments on commit 3c1a317

Please sign in to comment.
You can’t perform that action at this time.