-
Notifications
You must be signed in to change notification settings - Fork 938
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'lua_files' into 'master'
Implement Lua API for VFS Closes #6864 See merge request OpenMW/openmw!3373
- Loading branch information
Showing
14 changed files
with
541 additions
and
5 deletions.
There are no files selected for viewing
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
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
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
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,346 @@ | ||
#include "vfsbindings.hpp" | ||
|
||
#include <components/files/istreamptr.hpp> | ||
#include <components/resource/resourcesystem.hpp> | ||
#include <components/settings/values.hpp> | ||
#include <components/vfs/manager.hpp> | ||
#include <components/vfs/pathutil.hpp> | ||
|
||
#include "../mwbase/environment.hpp" | ||
|
||
#include "context.hpp" | ||
#include "luamanagerimp.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) { 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(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); | ||
} | ||
} |
Oops, something went wrong.