mirror of
https://gitlab.com/OpenMW/openmw.git
synced 2024-12-28 18:18:52 +00:00
352 lines
13 KiB
C++
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);
|
|
}
|
|
}
|