mirror of
https://gitlab.com/OpenMW/openmw.git
synced 2025-02-20 15:40:32 +00:00
Implement Lua API for VFS
This commit is contained in:
parent
3e99103beb
commit
c04a0ca3a5
@ -77,6 +77,7 @@
|
||||
Feature #6491: Add support for Qt6
|
||||
Feature #6556: Lua API for sounds
|
||||
Feature #6726: Lua API for creating new objects
|
||||
Feature #6864: Lua file access API
|
||||
Feature #6922: Improve launcher appearance
|
||||
Feature #6933: Support high-resolution cursor textures
|
||||
Feature #6945: Support S3TC-compressed and BGR/BGRA NiPixelData
|
||||
|
@ -71,7 +71,7 @@ message(STATUS "Configuring OpenMW...")
|
||||
set(OPENMW_VERSION_MAJOR 0)
|
||||
set(OPENMW_VERSION_MINOR 49)
|
||||
set(OPENMW_VERSION_RELEASE 0)
|
||||
set(OPENMW_LUA_API_REVISION 45)
|
||||
set(OPENMW_LUA_API_REVISION 46)
|
||||
|
||||
set(OPENMW_VERSION_COMMITHASH "")
|
||||
set(OPENMW_VERSION_TAGHASH "")
|
||||
|
@ -61,7 +61,7 @@ add_openmw_dir (mwscript
|
||||
add_openmw_dir (mwlua
|
||||
luamanagerimp object objectlists userdataserializer luaevents engineevents objectvariant
|
||||
context globalscripts localscripts playerscripts luabindings objectbindings cellbindings mwscriptbindings
|
||||
camerabindings uibindings soundbindings inputbindings nearbybindings postprocessingbindings stats debugbindings
|
||||
camerabindings vfsbindings uibindings soundbindings inputbindings nearbybindings postprocessingbindings stats debugbindings
|
||||
types/types types/door types/item types/actor types/container types/lockable types/weapon types/npc types/creature types/player types/activator types/book types/lockpick types/probe types/apparatus types/potion types/ingredient types/misc types/repair types/armor types/light types/static types/clothing types/levelledlist types/terminal
|
||||
worker magicbindings
|
||||
)
|
||||
|
@ -43,6 +43,7 @@
|
||||
#include "soundbindings.hpp"
|
||||
#include "types/types.hpp"
|
||||
#include "uibindings.hpp"
|
||||
#include "vfsbindings.hpp"
|
||||
|
||||
namespace MWLua
|
||||
{
|
||||
@ -331,6 +332,7 @@ namespace MWLua
|
||||
{ "openmw.core", initCorePackage(context) },
|
||||
{ "openmw.types", initTypesPackage(context) },
|
||||
{ "openmw.util", LuaUtil::initUtilPackage(lua) },
|
||||
{ "openmw.vfs", initVFSPackage(context) },
|
||||
};
|
||||
}
|
||||
|
||||
|
354
apps/openmw/mwlua/vfsbindings.cpp
Normal file
354
apps/openmw/mwlua/vfsbindings.cpp
Normal file
@ -0,0 +1,354 @@
|
||||
#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<VFS::Manager::StatefulIterator> vfsIterator
|
||||
= context.mLua->sol().new_usertype<VFS::Manager::StatefulIterator>("VFSIterator");
|
||||
vfsIterator[sol::meta_function::to_string] = [](const VFS::Manager::StatefulIterator& vfsIterator) {
|
||||
return "VFSIterator{'" + vfsIterator.getPath() + "'}";
|
||||
};
|
||||
vfsIterator["path"] = sol::readonly_property(
|
||||
[](const VFS::Manager::StatefulIterator& vfsIterator) { return vfsIterator.getPath(); });
|
||||
|
||||
auto createIter = [](VFS::Manager::StatefulIterator& vfsIterator) {
|
||||
return sol::as_function([vfsIterator, i = 1]() mutable {
|
||||
if (auto v = vfsIterator.next())
|
||||
return std::tuple<sol::optional<int>, sol::optional<std::string_view>>(i++, *v);
|
||||
else
|
||||
return std::tuple<sol::optional<int>, sol::optional<std::string_view>>(sol::nullopt, sol::nullopt);
|
||||
});
|
||||
};
|
||||
vfsIterator["__pairs"] = createIter;
|
||||
vfsIterator["__ipairs"] = createIter;
|
||||
|
||||
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["getIterator"]
|
||||
= [vfs](std::string_view path) -> VFS::Manager::StatefulIterator { return vfs->getStatefulIterator(path); };
|
||||
|
||||
return LuaUtil::makeReadOnly(api);
|
||||
}
|
||||
}
|
13
apps/openmw/mwlua/vfsbindings.hpp
Normal file
13
apps/openmw/mwlua/vfsbindings.hpp
Normal file
@ -0,0 +1,13 @@
|
||||
#ifndef MWLUA_VFSBINDINGS_H
|
||||
#define MWLUA_VFSBINDINGS_H
|
||||
|
||||
#include <sol/forward.hpp>
|
||||
|
||||
#include "context.hpp"
|
||||
|
||||
namespace MWLua
|
||||
{
|
||||
sol::table initVFSPackage(const Context&);
|
||||
}
|
||||
|
||||
#endif // MWLUA_VFSBINDINGS_H
|
@ -89,4 +89,17 @@ namespace VFS
|
||||
++normalized.back();
|
||||
return { it, mIndex.lower_bound(normalized) };
|
||||
}
|
||||
|
||||
Manager::StatefulIterator Manager::getStatefulIterator(std::string_view path) const
|
||||
{
|
||||
if (path.empty())
|
||||
return { mIndex.begin(), mIndex.end(), std::string() };
|
||||
std::string normalized = Path::normalizeFilename(path);
|
||||
const auto it = mIndex.lower_bound(normalized);
|
||||
if (it == mIndex.end() || !startsWith(it->first, normalized))
|
||||
return { it, it, normalized };
|
||||
std::string upperBound = normalized;
|
||||
++upperBound.back();
|
||||
return { it, mIndex.lower_bound(upperBound), normalized };
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@
|
||||
#include <filesystem>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@ -48,11 +49,18 @@ namespace VFS
|
||||
const std::string& operator*() const { return mIt->first; }
|
||||
const std::string* operator->() const { return &mIt->first; }
|
||||
bool operator!=(const RecursiveDirectoryIterator& other) { return mIt != other.mIt; }
|
||||
bool operator==(const RecursiveDirectoryIterator& other) const { return mIt == other.mIt; }
|
||||
RecursiveDirectoryIterator& operator++()
|
||||
{
|
||||
++mIt;
|
||||
return *this;
|
||||
}
|
||||
RecursiveDirectoryIterator operator++(int)
|
||||
{
|
||||
RecursiveDirectoryIterator old = *this;
|
||||
mIt++;
|
||||
return old;
|
||||
}
|
||||
|
||||
private:
|
||||
std::map<std::string, File*>::const_iterator mIt;
|
||||
@ -61,6 +69,31 @@ namespace VFS
|
||||
using RecursiveDirectoryRange = IteratorPair<RecursiveDirectoryIterator>;
|
||||
|
||||
public:
|
||||
class StatefulIterator : RecursiveDirectoryRange
|
||||
{
|
||||
public:
|
||||
StatefulIterator(RecursiveDirectoryIterator first, RecursiveDirectoryIterator last, const std::string& path)
|
||||
: RecursiveDirectoryRange(first, last)
|
||||
, mCurrent(first)
|
||||
, mPath(path)
|
||||
{
|
||||
}
|
||||
|
||||
const std::string& getPath() const { return mPath; }
|
||||
|
||||
std::optional<std::string_view> next()
|
||||
{
|
||||
if (mCurrent == end())
|
||||
return std::nullopt;
|
||||
|
||||
return *mCurrent++;
|
||||
}
|
||||
|
||||
private:
|
||||
RecursiveDirectoryIterator mCurrent;
|
||||
std::string mPath;
|
||||
};
|
||||
|
||||
// Empty the file index and unregister archives.
|
||||
void reset();
|
||||
|
||||
@ -93,6 +126,13 @@ namespace VFS
|
||||
/// @note May be called from any thread once the index has been built.
|
||||
RecursiveDirectoryRange getRecursiveDirectoryIterator(std::string_view path) const;
|
||||
|
||||
/// Recursively iterate over the elements of the given path
|
||||
/// In practice it return all files of the VFS starting with the given path
|
||||
/// Stores iterator to current element.
|
||||
/// @note the path is normalized
|
||||
/// @note May be called from any thread once the index has been built.
|
||||
StatefulIterator getStatefulIterator(std::string_view path) const;
|
||||
|
||||
/// Retrieve the absolute path to the file
|
||||
/// @note Throws an exception if the file can not be found.
|
||||
/// @note May be called from any thread once the index has been built.
|
||||
|
@ -17,6 +17,7 @@ Lua API reference
|
||||
openmw_core
|
||||
openmw_types
|
||||
openmw_async
|
||||
openmw_vfs
|
||||
openmw_world
|
||||
openmw_self
|
||||
openmw_nearby
|
||||
|
7
docs/source/reference/lua-scripting/openmw_vfs.rst
Normal file
7
docs/source/reference/lua-scripting/openmw_vfs.rst
Normal file
@ -0,0 +1,7 @@
|
||||
Package openmw.vfs
|
||||
==================
|
||||
|
||||
.. include:: version.rst
|
||||
|
||||
.. raw:: html
|
||||
:file: generated_html/openmw_vfs.html
|
@ -15,6 +15,8 @@
|
||||
+------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|
||||
|:ref:`openmw.async <Package openmw.async>` | everywhere | | Timers and callbacks. |
|
||||
+------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|
||||
|:ref:`openmw.vfs <Package openmw.vfs>` | everywhere | | Read-only access to data directories via VFS. |
|
||||
+------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|
||||
|:ref:`openmw.world <Package openmw.world>` | by global scripts | | Read-write access to the game world. |
|
||||
+------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|
||||
|:ref:`openmw.self <Package openmw.self>` | by local scripts | | Full access to the object the script is attached to. |
|
||||
|
@ -12,14 +12,15 @@ set(LUA_API_FILES
|
||||
openmw/ambient.lua
|
||||
openmw/async.lua
|
||||
openmw/core.lua
|
||||
openmw/debug.lua
|
||||
openmw/nearby.lua
|
||||
openmw/postprocessing.lua
|
||||
openmw/self.lua
|
||||
openmw/types.lua
|
||||
openmw/ui.lua
|
||||
openmw/util.lua
|
||||
openmw/vfs.lua
|
||||
openmw/world.lua
|
||||
openmw/types.lua
|
||||
openmw/postprocessing.lua
|
||||
openmw/debug.lua
|
||||
)
|
||||
|
||||
foreach (f ${LUA_API_FILES})
|
||||
|
159
files/lua_api/openmw/vfs.lua
Normal file
159
files/lua_api/openmw/vfs.lua
Normal file
@ -0,0 +1,159 @@
|
||||
---
|
||||
-- `openmw.vfs` provides read-only access to data directories via VFS.
|
||||
-- Interface is very similar to "io" library.
|
||||
-- @module vfs
|
||||
-- @usage local vfs = require('openmw.vfs')
|
||||
|
||||
|
||||
|
||||
---
|
||||
-- @type VFSIterator
|
||||
-- @field #string path VFS prefix path
|
||||
|
||||
---
|
||||
-- @type FileHandle
|
||||
-- @field #string fileName VFS path to related file
|
||||
|
||||
---
|
||||
-- Close a file handle
|
||||
-- @function [parent=#FileHandle] close
|
||||
-- @param self
|
||||
-- @return #boolean true if a call succeeds without errors.
|
||||
-- @return #nil, #string nil plus the error message in case of any error.
|
||||
|
||||
---
|
||||
-- Get an iterator function to fetch the next line from given file.
|
||||
-- Throws an exception if file is closed.
|
||||
--
|
||||
-- Hint: since garbage collection works once per frame,
|
||||
-- you will get the whole file in RAM if you read it in one frame.
|
||||
-- So if you need to read a really large file, it is better to split reading
|
||||
-- between different frames (e.g. by keeping a current position in file
|
||||
-- and using a "seek" to read from saved position).
|
||||
-- @function [parent=#FileHandle] lines
|
||||
-- @param self
|
||||
-- @return #function Iterator function to get next line
|
||||
-- @usage f = vfs.open("Test\\test.txt");
|
||||
-- for line in f:lines() do
|
||||
-- print(line);
|
||||
-- end
|
||||
|
||||
---
|
||||
-- Set new position in file.
|
||||
-- Throws an exception if file is closed or seek base is incorrect.
|
||||
-- @function [parent=#FileHandle] seek
|
||||
-- @param self
|
||||
-- @param #string whence Seek base (optional, "cur" by default). Can be:
|
||||
--
|
||||
-- * "set" - seek from beginning of file;
|
||||
-- * "cur" - seek from current position;
|
||||
-- * "end" - seek from end of file (offset needs to be <= 0);
|
||||
-- @param #number offset Offset from given base (optional, 0 by default)
|
||||
-- @return #number new position in file if a call succeeds without errors.
|
||||
-- @return #nil, #string nil plus the error message in case of any error.
|
||||
-- @usage -- set pointer to beginning of file
|
||||
-- f = vfs.open("Test\\test.txt");
|
||||
-- f:seek("set");
|
||||
-- @usage -- print current position in file
|
||||
-- f = vfs.open("Test\\test.txt");
|
||||
-- print(f:seek());
|
||||
-- @usage -- print file size
|
||||
-- f = vfs.open("Test\\test.txt");
|
||||
-- print(f:seek("end"));
|
||||
|
||||
---
|
||||
-- Read data from file to strings.
|
||||
-- Throws an exception if file is closed, if there is too many arguments or if an invalid format encountered.
|
||||
--
|
||||
-- Hint: since garbage collection works once per frame,
|
||||
-- you will get the whole file in RAM if you read it in one frame.
|
||||
-- So if you need to read a really large file, it is better to split reading
|
||||
-- between different frames (e.g. by keeping a current position in file
|
||||
-- and using a "seek" to read from saved position).
|
||||
-- @function [parent=#FileHandle] read
|
||||
-- @param self
|
||||
-- @param ... Read formats (up to 20 arguments, default value is one "*l"). Can be:
|
||||
--
|
||||
-- * "\*a" (or "*all") - reads the whole file, starting at the current position as #string. On end of file, it returns the empty string.
|
||||
-- * "\*l" (or "*line") - reads the next line (skipping the end of line), returning nil on end of file (nil and error message if error occured);
|
||||
-- * "\*n" (or "*number") - read a floating point value as #number (nil and error message if error occured);
|
||||
-- * number - reads a #string with up to this number of characters, returning nil on end of file (nil and error message if error occured). If number is 0 and end of file is not reached, it reads nothing and returns an empty string;
|
||||
-- @return #string One #string for every format if a call succeeds without errors. One #string for every successfully handled format, nil for first failed format.
|
||||
-- @usage -- read three numbers from file
|
||||
-- f = vfs.open("Test\\test.txt");
|
||||
-- local n1, n2, n3 = f:read("*number", "*number", "*number");
|
||||
-- @usage -- read 10 bytes from file
|
||||
-- f = vfs.open("Test\\test.txt");
|
||||
-- local n4 = f:read(10);
|
||||
-- @usage -- read until end of file
|
||||
-- f = vfs.open("Test\\test.txt");
|
||||
-- local n5 = f:read("*all");
|
||||
-- @usage -- read a line from file
|
||||
-- f = vfs.open("Test\\test.txt");
|
||||
-- local n6 = f:read();
|
||||
-- @usage -- try to read three numbers from file with "1" content
|
||||
-- f = vfs.open("one.txt");
|
||||
-- print(f:read("*number", "*number", "*number"));
|
||||
-- -- prints(1, nil)
|
||||
|
||||
---
|
||||
-- Check if file exists in VFS
|
||||
-- @function [parent=#vfs] fileExists
|
||||
-- @param #string fileName Path to file in VFS
|
||||
-- @return #boolean (true - exists, false - does not exist)
|
||||
-- @usage local exists = vfs.fileExists("Test\\test.txt");
|
||||
|
||||
---
|
||||
-- Open a file
|
||||
-- @function [parent=#vfs] open
|
||||
-- @param #string fileName Path to file in VFS
|
||||
-- @return #FileHandle Opened file handle if a call succeeds without errors.
|
||||
-- @return #nil, #string nil plus the error message in case of any error.
|
||||
-- @usage f, msg = vfs.open("Test\\test.txt");
|
||||
-- -- print file name or error message
|
||||
-- if (f == nil)
|
||||
-- print(msg);
|
||||
-- else
|
||||
-- print(f.fileName);
|
||||
-- end
|
||||
|
||||
---
|
||||
-- Get an iterator function to fetch the next line from file with given path.
|
||||
-- Throws an exception if file is closed or file with given path does not exist.
|
||||
-- Closes file automatically when it fails to read any more bytes.
|
||||
--
|
||||
-- Hint: since garbage collection works once per frame,
|
||||
-- you will get the whole file in RAM if you read it in one frame.
|
||||
-- So if you need to read a really large file, it is better to split reading
|
||||
-- between different frames (e.g. by keeping a current position in file
|
||||
-- and using a "seek" to read from saved position).
|
||||
-- @function [parent=#vfs] lines
|
||||
-- @param #string fileName Path to file in VFS
|
||||
-- @return #function Iterator function to get next line
|
||||
-- @usage for line in vfs.lines("Test\\test.txt") do
|
||||
-- print(line);
|
||||
-- end
|
||||
|
||||
---
|
||||
-- Get iterator to fetch file names with given path prefix from VFS
|
||||
-- @function [parent=#vfs] getIterator
|
||||
-- @param #string path Path prefix
|
||||
-- @return #VFSIterator Opened iterator
|
||||
-- @usage local dir = vfs.getIterator("Music\\Explore");
|
||||
-- for _, fileName in pairs(dir) do
|
||||
-- print(fileName);
|
||||
-- end
|
||||
|
||||
---
|
||||
-- Detect a file handle type
|
||||
-- @function [parent=#vfs] type
|
||||
-- @param #any handle Object to check
|
||||
-- @return #string File handle type. Can be:
|
||||
--
|
||||
-- * "file" - an argument is a valid opened @{openmw.vfs#FileHandle};
|
||||
-- * "closed file" - an argument is a valid closed @{openmw.vfs#FileHandle};
|
||||
-- * nil - an argument is not a @{openmw.vfs#FileHandle};
|
||||
-- @usage f = vfs.open("Test\\test.txt");
|
||||
-- print(vfs.type(f));
|
||||
|
||||
return nil
|
Loading…
x
Reference in New Issue
Block a user