1
0
mirror of https://gitlab.com/OpenMW/openmw.git synced 2024-12-28 18:18:52 +00:00
OpenMW/apps/openmw/mwlua/vfsbindings.cpp
2024-03-21 11:14:01 +01:00

352 lines
13 KiB
C++

#include "vfsbindings.hpp"
#include <sol/object.hpp>
#include <components/files/istreamptr.hpp>
#include <components/lua/luastate.hpp>
#include <components/resource/resourcesystem.hpp>
#include <components/settings/values.hpp>
#include <components/vfs/manager.hpp>
#include <components/vfs/pathutil.hpp>
#include <components/vfs/recursivedirectoryiterator.hpp>
#include "../mwbase/environment.hpp"
#include "context.hpp"
namespace MWLua
{
namespace
{
// Too many arguments may cause stack corruption and crash.
constexpr std::size_t sMaximumReadArguments = 20;
// Print a message if we read a large chunk of file to string.
constexpr std::size_t sFileSizeWarningThreshold = 1024 * 1024;
struct FileHandle
{
public:
FileHandle(Files::IStreamPtr stream, std::string_view fileName)
{
mFilePtr = std::move(stream);
mFileName = fileName;
}
Files::IStreamPtr mFilePtr;
std::string mFileName;
};
std::ios_base::seekdir getSeekDir(FileHandle& self, std::string_view whence)
{
if (whence == "cur")
return std::ios_base::cur;
if (whence == "set")
return std::ios_base::beg;
if (whence == "end")
return std::ios_base::end;
throw std::runtime_error(
"Error when handling '" + self.mFileName + "': invalid seek direction: '" + std::string(whence) + "'.");
}
size_t getBytesLeftInStream(Files::IStreamPtr& file)
{
auto oldPos = file->tellg();
file->seekg(0, std::ios_base::end);
auto newPos = file->tellg();
file->seekg(oldPos, std::ios_base::beg);
return newPos - oldPos;
}
void printLargeDataMessage(FileHandle& file, size_t size)
{
if (!file.mFilePtr || !Settings::lua().mLuaDebug || size < sFileSizeWarningThreshold)
return;
Log(Debug::Verbose) << "Read a large data chunk (" << size << " bytes) from '" << file.mFileName << "'.";
}
sol::object readFile(LuaUtil::LuaState* lua, FileHandle& file)
{
std::ostringstream os;
if (file.mFilePtr && file.mFilePtr->peek() != EOF)
os << file.mFilePtr->rdbuf();
auto result = os.str();
printLargeDataMessage(file, result.size());
return sol::make_object<std::string>(lua->sol(), std::move(result));
}
sol::object readLineFromFile(LuaUtil::LuaState* lua, FileHandle& file)
{
std::string result;
if (file.mFilePtr && std::getline(*file.mFilePtr, result))
{
printLargeDataMessage(file, result.size());
return sol::make_object<std::string>(lua->sol(), result);
}
return sol::nil;
}
sol::object readNumberFromFile(LuaUtil::LuaState* lua, Files::IStreamPtr& file)
{
double number = 0;
if (file && *file >> number)
return sol::make_object<double>(lua->sol(), number);
return sol::nil;
}
sol::object readCharactersFromFile(LuaUtil::LuaState* lua, FileHandle& file, size_t count)
{
if (count <= 0 && file.mFilePtr->peek() != EOF)
return sol::make_object<std::string>(lua->sol(), std::string());
auto bytesLeft = getBytesLeftInStream(file.mFilePtr);
if (bytesLeft <= 0)
return sol::nil;
if (count > bytesLeft)
count = bytesLeft;
std::string result(count, '\0');
if (file.mFilePtr->read(&result[0], count))
{
printLargeDataMessage(file, result.size());
return sol::make_object<std::string>(lua->sol(), result);
}
return sol::nil;
}
void validateFile(const FileHandle& self)
{
if (self.mFilePtr)
return;
throw std::runtime_error("Error when handling '" + self.mFileName + "': attempt to use a closed file.");
}
sol::variadic_results seek(
LuaUtil::LuaState* lua, FileHandle& self, std::ios_base::seekdir dir, std::streamoff off)
{
sol::variadic_results values;
try
{
self.mFilePtr->seekg(off, dir);
if (self.mFilePtr->fail() || self.mFilePtr->bad())
{
auto msg = "Failed to seek in file '" + self.mFileName + "'";
values.push_back(sol::nil);
values.push_back(sol::make_object<std::string>(lua->sol(), msg));
}
else
values.push_back(sol::make_object<std::streampos>(lua->sol(), self.mFilePtr->tellg()));
}
catch (std::exception& e)
{
auto msg = "Failed to seek in file '" + self.mFileName + "': " + std::string(e.what());
values.push_back(sol::nil);
values.push_back(sol::make_object<std::string>(lua->sol(), msg));
}
return values;
}
}
sol::table initVFSPackage(const Context& context)
{
sol::table api(context.mLua->sol(), sol::create);
auto vfs = MWBase::Environment::get().getResourceSystem()->getVFS();
sol::usertype<FileHandle> handle = context.mLua->sol().new_usertype<FileHandle>("FileHandle");
handle["fileName"]
= sol::readonly_property([](const FileHandle& self) -> std::string_view { return self.mFileName; });
handle[sol::meta_function::to_string] = [](const FileHandle& self) {
return "FileHandle{'" + self.mFileName + "'" + (!self.mFilePtr ? ", closed" : "") + "}";
};
handle["seek"] = sol::overload(
[lua = context.mLua](FileHandle& self, std::string_view whence, sol::optional<long> offset) {
validateFile(self);
auto off = static_cast<std::streamoff>(offset.value_or(0));
auto dir = getSeekDir(self, whence);
return seek(lua, self, dir, off);
},
[lua = context.mLua](FileHandle& self, sol::optional<long> offset) {
validateFile(self);
auto off = static_cast<std::streamoff>(offset.value_or(0));
return seek(lua, self, std::ios_base::cur, off);
});
handle["lines"] = [lua = context.mLua](FileHandle& self) {
return sol::as_function([&lua, &self]() mutable {
validateFile(self);
return readLineFromFile(lua, self);
});
};
api["lines"] = [lua = context.mLua, vfs](std::string_view fileName) {
auto normalizedName = VFS::Path::normalizeFilename(fileName);
return sol::as_function(
[lua, file = FileHandle(vfs->getNormalized(normalizedName), normalizedName)]() mutable {
validateFile(file);
auto result = readLineFromFile(lua, file);
if (result == sol::nil)
file.mFilePtr.reset();
return result;
});
};
handle["close"] = [lua = context.mLua](FileHandle& self) {
sol::variadic_results values;
try
{
self.mFilePtr.reset();
if (self.mFilePtr)
{
auto msg = "Can not close file '" + self.mFileName + "': file handle is still opened.";
values.push_back(sol::nil);
values.push_back(sol::make_object<std::string>(lua->sol(), msg));
}
else
values.push_back(sol::make_object<bool>(lua->sol(), true));
}
catch (std::exception& e)
{
auto msg = "Can not close file '" + self.mFileName + "': " + std::string(e.what());
values.push_back(sol::nil);
values.push_back(sol::make_object<std::string>(lua->sol(), msg));
}
return values;
};
handle["read"] = [lua = context.mLua](FileHandle& self, const sol::variadic_args args) {
validateFile(self);
if (args.size() > sMaximumReadArguments)
throw std::runtime_error(
"Error when handling '" + self.mFileName + "': too many arguments for 'read'.");
sol::variadic_results values;
// If there are no arguments, read a string
if (args.size() == 0)
{
values.push_back(readLineFromFile(lua, self));
return values;
}
bool success = true;
size_t i = 0;
for (i = 0; i < args.size() && success; i++)
{
if (args[i].is<std::string_view>())
{
auto format = args[i].as<std::string_view>();
if (format == "*a" || format == "*all")
{
values.push_back(readFile(lua, self));
continue;
}
if (format == "*n" || format == "*number")
{
auto result = readNumberFromFile(lua, self.mFilePtr);
values.push_back(result);
if (result == sol::nil)
success = false;
continue;
}
if (format == "*l" || format == "*line")
{
auto result = readLineFromFile(lua, self);
values.push_back(result);
if (result == sol::nil)
success = false;
continue;
}
throw std::runtime_error("Error when handling '" + self.mFileName + "': bad argument #"
+ std::to_string(i + 1) + " to 'read' (invalid format)");
}
else if (args[i].is<int>())
{
int number = args[i].as<int>();
auto result = readCharactersFromFile(lua, self, number);
values.push_back(result);
if (result == sol::nil)
success = false;
}
}
// We should return nil if we just reached the end of stream
if (!success && self.mFilePtr->eof())
return values;
if (!success && (self.mFilePtr->fail() || self.mFilePtr->bad()))
{
auto msg = "Error when handling '" + self.mFileName + "': can not read data for argument #"
+ std::to_string(i);
values.push_back(sol::make_object<std::string>(lua->sol(), msg));
}
return values;
};
api["open"] = [lua = context.mLua, vfs](std::string_view fileName) {
sol::variadic_results values;
try
{
auto normalizedName = VFS::Path::normalizeFilename(fileName);
auto handle = FileHandle(vfs->getNormalized(normalizedName), normalizedName);
values.push_back(sol::make_object<FileHandle>(lua->sol(), std::move(handle)));
}
catch (std::exception& e)
{
auto msg = "Can not open file: " + std::string(e.what());
values.push_back(sol::nil);
values.push_back(sol::make_object<std::string>(lua->sol(), msg));
}
return values;
};
api["type"] = sol::overload(
[](const FileHandle& handle) -> std::string {
if (handle.mFilePtr)
return "file";
return "closed file";
},
[](const sol::object&) -> sol::object { return sol::nil; });
api["fileExists"]
= [vfs](std::string_view fileName) -> bool { return vfs->exists(VFS::Path::Normalized(fileName)); };
api["pathsWithPrefix"] = [vfs](std::string_view prefix) {
auto iterator = vfs->getRecursiveDirectoryIterator(prefix);
return sol::as_function([iterator, current = iterator.begin()]() mutable -> sol::optional<std::string> {
if (current != iterator.end())
{
const std::string& result = *current;
++current;
return result;
}
return sol::nullopt;
});
};
return LuaUtil::makeReadOnly(api);
}
}