Added memoryfilesystem and working on c wrapper
This commit is contained in:
666
src/Filesystem/MemoryFilesystem.cpp
Normal file
666
src/Filesystem/MemoryFilesystem.cpp
Normal file
@ -0,0 +1,666 @@
|
||||
#include "TessesFramework/Filesystem/MemoryFilesystem.hpp"
|
||||
#include <iostream>
|
||||
|
||||
namespace Tesses::Framework::Filesystem
|
||||
{
|
||||
MemoryFilesystemStream::MemoryFilesystemStream(std::shared_ptr<Tesses::Framework::Threading::Mutex> mtx, std::shared_ptr<MemoryFileData> data,bool canRead, bool canWrite, bool canSeek)
|
||||
{
|
||||
this->mtx = mtx;
|
||||
this->data = data;
|
||||
this->canRead = canRead;
|
||||
this->canWrite = canWrite;
|
||||
this->canSeek = canSeek;
|
||||
this->pos=0;
|
||||
}
|
||||
size_t MemoryFilesystemStream::Read(uint8_t* buff, size_t sz)
|
||||
{
|
||||
if(!this->canRead) return 0;
|
||||
if(this->pos >= this->data->file.size()) return 0;
|
||||
size_t toRead = std::min(sz, (size_t)(this->data->file.size()-this->pos));
|
||||
memcpy(buff, this->data->file.data() + this->pos, toRead);
|
||||
|
||||
this->pos += toRead;
|
||||
return toRead;
|
||||
}
|
||||
size_t MemoryFilesystemStream::Write(const uint8_t* buff, size_t sz)
|
||||
{
|
||||
if(!this->canWrite) return 0;
|
||||
if(this->canSeek)
|
||||
{
|
||||
|
||||
if(this->pos > this->data->file.size())
|
||||
{
|
||||
this->data->file.resize(this->pos+sz);
|
||||
}
|
||||
this->data->file.insert(this->data->file.begin()+this->pos, buff, buff+sz);
|
||||
this->pos+=sz;
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
this->data->file.insert(this->data->file.end(), buff, buff+sz);
|
||||
}
|
||||
return sz;
|
||||
}
|
||||
bool MemoryFilesystemStream::CanRead()
|
||||
{
|
||||
return canRead;
|
||||
}
|
||||
bool MemoryFilesystemStream::CanWrite()
|
||||
{
|
||||
return canWrite;
|
||||
}
|
||||
bool MemoryFilesystemStream::CanSeek()
|
||||
{
|
||||
return canSeek;
|
||||
}
|
||||
int64_t MemoryFilesystemStream::GetPosition()
|
||||
{
|
||||
if(!this->canSeek) return (int64_t)this->data->file.size();
|
||||
return pos;
|
||||
}
|
||||
void MemoryFilesystemStream::Flush()
|
||||
{
|
||||
//its already flushed
|
||||
}
|
||||
void MemoryFilesystemStream::Seek(int64_t pos, Streams::SeekOrigin whence)
|
||||
{
|
||||
if(canSeek) return;
|
||||
switch(whence)
|
||||
{
|
||||
case Streams::SeekOrigin::Begin:
|
||||
this->pos = (size_t)pos;
|
||||
break;
|
||||
case Streams::SeekOrigin::Current:
|
||||
this->pos += (size_t)pos;
|
||||
break;
|
||||
case Streams::SeekOrigin::End:
|
||||
this->pos = (size_t)(this->data->file.size() + pos);
|
||||
break;
|
||||
}
|
||||
}
|
||||
MemoryFilesystemStream::~MemoryFilesystemStream()
|
||||
{
|
||||
mtx->Lock();
|
||||
if(this->canWrite) this->data->canAccess=true;
|
||||
this->data->readers--;
|
||||
mtx->Unlock();
|
||||
}
|
||||
|
||||
MemoryEntry* MemoryFilesystem::GetEntry(VFSPath path,bool followSymlink)
|
||||
{
|
||||
if(path.relative) return nullptr;
|
||||
if(path.path.empty()) return &this->root;
|
||||
|
||||
auto entry = GetEntry(path.GetParent(),true);
|
||||
if(entry == nullptr) return nullptr;
|
||||
|
||||
auto dir = dynamic_cast<MemoryDirectory*>(entry);
|
||||
if(dir != nullptr)
|
||||
{
|
||||
for(auto item : dir->entries)
|
||||
{
|
||||
if(item->name == path.GetFileName())
|
||||
{
|
||||
auto link = dynamic_cast<MemorySymlink*>(item);
|
||||
if(followSymlink && link != nullptr)
|
||||
{
|
||||
item = GetEntry(link->linkedTo,true);
|
||||
}
|
||||
return item;
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Streams::Stream* MemoryFilesystem::OpenFile(VFSPath path, std::string mode)
|
||||
{
|
||||
bool canRead=false;
|
||||
bool canWrite=false;
|
||||
bool canSeek=true;
|
||||
bool mustExist=false;
|
||||
bool truncate=false;
|
||||
|
||||
|
||||
if(mode.size() >= 1)
|
||||
{
|
||||
if(mode[0] == 'r')
|
||||
{
|
||||
canRead = true;
|
||||
mustExist=true;
|
||||
}
|
||||
else if(mode[0] == 'w')
|
||||
{
|
||||
canWrite = true;
|
||||
truncate=true;
|
||||
}
|
||||
else if(mode[0] == 'a')
|
||||
{
|
||||
canSeek = false;
|
||||
canWrite = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(((mode.size() >= 2 && mode[1] == '+') || (mode.size() >= 2 && mode[1] == 'b' && mode[2] == '+')))
|
||||
{
|
||||
canRead = true;
|
||||
canWrite = true;
|
||||
}
|
||||
|
||||
mtx->Lock();
|
||||
if(mustExist)
|
||||
{
|
||||
auto file = GetEntry(path,true);
|
||||
|
||||
if(file == nullptr)
|
||||
{
|
||||
mtx->Unlock();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto f = dynamic_cast<MemoryFile*>(file);
|
||||
if(f == nullptr)
|
||||
{
|
||||
mtx->Unlock();
|
||||
return nullptr;
|
||||
}
|
||||
if(!f->data->canAccess)
|
||||
{
|
||||
mtx->Unlock();
|
||||
return nullptr;
|
||||
}
|
||||
if(canWrite && f->data->readers > 0)
|
||||
{
|
||||
mtx->Unlock();
|
||||
return nullptr;
|
||||
}
|
||||
f->data->readers++;
|
||||
if(canWrite) f->data->canAccess=false;
|
||||
|
||||
mtx->Unlock();
|
||||
return new MemoryFilesystemStream(mtx,f->data,canRead,canWrite,canSeek);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto file = dynamic_cast<MemoryFile*>(GetEntry(path,true));
|
||||
|
||||
|
||||
if(file != nullptr)
|
||||
{
|
||||
if(!file->data->canAccess)
|
||||
{
|
||||
mtx->Unlock();
|
||||
return nullptr;
|
||||
}
|
||||
if(file->data->readers > 0)
|
||||
{
|
||||
mtx->Unlock();
|
||||
return nullptr;
|
||||
}
|
||||
file->data->canAccess=false;
|
||||
file->data->readers++;
|
||||
if(truncate) {file->data->file.clear(); file->data->lastWrite=time(NULL);}
|
||||
mtx->Unlock();
|
||||
return new MemoryFilesystemStream(mtx,file->data,canRead,canWrite,canSeek);
|
||||
|
||||
}
|
||||
|
||||
|
||||
auto dir = GetEntry(path.GetParent(),true);
|
||||
|
||||
if(dir == nullptr)
|
||||
{
|
||||
mtx->Unlock();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto myDir = dynamic_cast<MemoryDirectory*>(dir);
|
||||
|
||||
|
||||
if(myDir == nullptr)
|
||||
{
|
||||
mtx->Unlock();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
MemoryFile* thefile=nullptr;
|
||||
|
||||
for(auto f : myDir->entries)
|
||||
{
|
||||
if(f->name == path.GetFileName())
|
||||
{
|
||||
auto symlink = dynamic_cast<MemorySymlink*>(f);
|
||||
while(symlink != nullptr)
|
||||
{
|
||||
auto ent = GetEntry(symlink->name,false);
|
||||
auto sym = dynamic_cast<MemorySymlink*>(f);
|
||||
if(sym != nullptr)
|
||||
symlink = sym;
|
||||
else
|
||||
{
|
||||
auto myDir0 = dynamic_cast<MemoryDirectory*>(GetEntry(symlink->linkedTo.GetParent(),true));
|
||||
if(myDir0 != nullptr)
|
||||
{
|
||||
for(auto f2 : myDir0->entries)
|
||||
{
|
||||
if(f2->name == symlink->linkedTo.GetFileName())
|
||||
{
|
||||
auto myFile = dynamic_cast<MemoryFile*>(f2);
|
||||
if(myFile != nullptr)
|
||||
{
|
||||
thefile = myFile;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
mtx->Unlock();
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
myDir = myDir0;
|
||||
}
|
||||
else
|
||||
{
|
||||
mtx->Unlock();
|
||||
return nullptr;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(thefile == nullptr)
|
||||
{
|
||||
|
||||
MemoryFile* f = new MemoryFile();
|
||||
f->name = path.GetFileName();
|
||||
f->data = std::make_shared<MemoryFileData>();
|
||||
f->data->canAccess=false;
|
||||
f->data->readers++;
|
||||
|
||||
myDir->entries.push_back(f);
|
||||
|
||||
mtx->Unlock();
|
||||
return new MemoryFilesystemStream(mtx,f->data,canRead,canWrite,canSeek);
|
||||
}
|
||||
if(thefile != nullptr)
|
||||
{
|
||||
if(!thefile->data->canAccess)
|
||||
{
|
||||
mtx->Unlock();
|
||||
return nullptr;
|
||||
}
|
||||
if(thefile->data->readers > 0)
|
||||
{
|
||||
mtx->Unlock();
|
||||
return nullptr;
|
||||
}
|
||||
thefile->data->canAccess=false;
|
||||
thefile->data->readers++;
|
||||
if(truncate) {thefile->data->file.clear(); thefile->data->lastWrite=time(NULL);}
|
||||
mtx->Unlock();
|
||||
return new MemoryFilesystemStream(mtx,thefile->data,canRead,canWrite,canSeek);
|
||||
}
|
||||
}
|
||||
|
||||
mtx->Unlock();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void MemoryFilesystem::CreateDirectory(VFSPath path)
|
||||
{
|
||||
if(path.relative) return;
|
||||
if(path.path.empty()) return;
|
||||
mtx->Lock();
|
||||
MemoryDirectory* dir=&root;
|
||||
for(auto part : path.path)
|
||||
{
|
||||
bool have=false;
|
||||
for(auto dirent : dir->entries)
|
||||
{
|
||||
if(dirent->name == part)
|
||||
{
|
||||
auto symlink = dynamic_cast<MemorySymlink*>(dirent);
|
||||
if(symlink != nullptr)
|
||||
{
|
||||
dirent = GetEntry(symlink->linkedTo,true);
|
||||
}
|
||||
auto dirdirent = dynamic_cast<MemoryDirectory*>(dirent);
|
||||
if(dirdirent != nullptr)
|
||||
{
|
||||
dir = dirdirent;
|
||||
have=true;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
mtx->Unlock();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(!have)
|
||||
{
|
||||
MemoryDirectory* dir2 = new MemoryDirectory();
|
||||
dir2->name = part;
|
||||
|
||||
dir2->lastWrite=time(NULL);
|
||||
|
||||
dir->entries.push_back(dir2);
|
||||
dir->lastWrite=time(NULL);
|
||||
|
||||
dir=dir2;
|
||||
}
|
||||
}
|
||||
mtx->Unlock();
|
||||
}
|
||||
|
||||
void MemoryFilesystem::DeleteFile(VFSPath path)
|
||||
{
|
||||
if(path.relative || path.path.empty()) return;
|
||||
mtx->Lock();
|
||||
MemoryDirectory* dir=&root;
|
||||
if(path.path.size() > 1)
|
||||
{
|
||||
dir = dynamic_cast<MemoryDirectory*>(GetEntry(path.GetParent(),true));
|
||||
}
|
||||
if(dir == nullptr)
|
||||
{
|
||||
mtx->Unlock();
|
||||
return;
|
||||
}
|
||||
std::string fname = path.GetFileName();
|
||||
for(auto index = dir->entries.begin(); index < dir->entries.end(); index++)
|
||||
{
|
||||
auto item = *index;
|
||||
if(item->name == fname)
|
||||
{
|
||||
auto p = dynamic_cast<MemoryDirectory*>(item);
|
||||
if(p == nullptr)
|
||||
{
|
||||
delete item;
|
||||
dir->entries.erase(index);
|
||||
|
||||
dir->lastWrite=time(NULL);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
mtx->Unlock();
|
||||
}
|
||||
|
||||
bool MemoryFilesystem::RegularFileExists(VFSPath path)
|
||||
{
|
||||
|
||||
if(path.relative) return false;
|
||||
if(path.path.empty()) return false;
|
||||
mtx->Lock();
|
||||
auto f = GetEntry(path,false);
|
||||
mtx->Unlock();
|
||||
return dynamic_cast<MemoryFile*>(f) != nullptr;
|
||||
}
|
||||
bool MemoryFilesystem::SymlinkExists(VFSPath path)
|
||||
{
|
||||
|
||||
if(path.relative) return false;
|
||||
if(path.path.empty()) return false;
|
||||
mtx->Lock();
|
||||
auto f = GetEntry(path,false);
|
||||
mtx->Unlock();
|
||||
return dynamic_cast<MemorySymlink*>(f) != nullptr;
|
||||
}
|
||||
bool MemoryFilesystem::DirectoryExists(VFSPath path)
|
||||
{
|
||||
if(path.relative) return false;
|
||||
if(path.path.empty()) return true;
|
||||
mtx->Lock();
|
||||
auto f = GetEntry(path,false);
|
||||
mtx->Unlock();
|
||||
return dynamic_cast<MemoryDirectory*>(f) != nullptr;
|
||||
}
|
||||
|
||||
void MemoryFilesystem::DeleteDirectory(VFSPath path)
|
||||
{
|
||||
if(path.relative || path.path.empty()) return;
|
||||
mtx->Lock();
|
||||
MemoryDirectory* dir=&root;
|
||||
if(path.path.size() > 1)
|
||||
{
|
||||
dir = dynamic_cast<MemoryDirectory*>(GetEntry(path.GetParent(),true));
|
||||
}
|
||||
if(dir == nullptr)
|
||||
{
|
||||
mtx->Unlock();
|
||||
return;
|
||||
}
|
||||
std::string fname = path.GetFileName();
|
||||
for(auto index = dir->entries.begin(); index < dir->entries.end(); index++)
|
||||
{
|
||||
auto item = *index;
|
||||
if(item->name == fname)
|
||||
{
|
||||
auto p = dynamic_cast<MemoryDirectory*>(item);
|
||||
if(p != nullptr)
|
||||
{
|
||||
delete item;
|
||||
dir->entries.erase(index);
|
||||
dir->lastWrite=time(NULL);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
mtx->Unlock();
|
||||
}
|
||||
|
||||
void MemoryFilesystem::CreateSymlink(VFSPath existingFile, VFSPath symlinkFile)
|
||||
{
|
||||
if(existingFile.relative || symlinkFile.relative || symlinkFile.path.empty()) return;
|
||||
mtx->Lock();
|
||||
MemoryDirectory* dir=&root;
|
||||
if(symlinkFile.path.size() > 1)
|
||||
{
|
||||
dir = dynamic_cast<MemoryDirectory*>(GetEntry(symlinkFile.GetParent(),true));
|
||||
}
|
||||
if(dir == nullptr)
|
||||
{
|
||||
mtx->Unlock();
|
||||
return;
|
||||
}
|
||||
std::string fname = symlinkFile.GetFileName();
|
||||
for(auto index = dir->entries.begin(); index < dir->entries.end(); index++)
|
||||
{
|
||||
auto item = *index;
|
||||
if(item->name == fname)
|
||||
{
|
||||
auto p = dynamic_cast<MemorySymlink*>(item);
|
||||
if(p != nullptr)
|
||||
{
|
||||
p->linkedTo = existingFile;
|
||||
p->lastWrite = time(NULL);
|
||||
}
|
||||
mtx->Unlock();
|
||||
return;
|
||||
}
|
||||
}
|
||||
MemorySymlink* symlink = new MemorySymlink();
|
||||
symlink->name = fname;
|
||||
symlink->linkedTo = existingFile;
|
||||
symlink->lastWrite = time(NULL);
|
||||
dir->entries.push_back(symlink);
|
||||
dir->lastWrite = time(NULL);
|
||||
|
||||
mtx->Unlock();
|
||||
}
|
||||
VFSPathEnumerator MemoryFilesystem::EnumeratePaths(VFSPath path)
|
||||
{
|
||||
std::pair<size_t,std::vector<std::string>>* paths=new std::pair<size_t,std::vector<std::string>>();
|
||||
paths->first=0;
|
||||
mtx->Lock();
|
||||
auto dir = dynamic_cast<MemoryDirectory*>(GetEntry(path,true));
|
||||
if(dir != nullptr)
|
||||
{
|
||||
for(auto item : dir->entries) paths->second.push_back(item->name);
|
||||
}
|
||||
mtx->Unlock();
|
||||
|
||||
return VFSPathEnumerator([paths,path](VFSPath& _path)->bool{
|
||||
if(paths->first < paths->second.size())
|
||||
{
|
||||
_path = path / paths->second[paths->first++];
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},[paths]()->void{
|
||||
delete paths;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
void MemoryFilesystem::CreateHardlink(VFSPath existingFile, VFSPath newName)
|
||||
{
|
||||
mtx->Lock();
|
||||
auto existing = dynamic_cast<MemoryFile*>(GetEntry(existingFile,true));
|
||||
if(existing == nullptr)
|
||||
{
|
||||
mtx->Unlock();
|
||||
return;
|
||||
}
|
||||
MemoryDirectory* dir=&root;
|
||||
if(newName.path.size() > 1)
|
||||
{
|
||||
dir = dynamic_cast<MemoryDirectory*>(GetEntry(newName.GetParent(),true));
|
||||
}
|
||||
if(dir == nullptr)
|
||||
{
|
||||
mtx->Unlock();
|
||||
return;
|
||||
}
|
||||
|
||||
std::string fname = newName.GetFileName();
|
||||
for(auto index = dir->entries.begin(); index < dir->entries.end(); index++)
|
||||
{
|
||||
auto item = *index;
|
||||
if(item->name == fname)
|
||||
{
|
||||
|
||||
mtx->Unlock();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
MemoryFile* memFile = new MemoryFile();
|
||||
memFile->name = fname;
|
||||
memFile->data = existing->data;
|
||||
dir->entries.push_back(memFile);
|
||||
dir->lastWrite=time(NULL);
|
||||
|
||||
mtx->Unlock();
|
||||
}
|
||||
void MemoryFilesystem::MoveFile(VFSPath src, VFSPath dest)
|
||||
{
|
||||
DeleteFile(dest);
|
||||
CreateHardlink(src,dest);
|
||||
DeleteFile(src);
|
||||
}
|
||||
void MemoryFilesystem::MoveDirectory(VFSPath src, VFSPath dest)
|
||||
{
|
||||
CreateDirectory(dest);
|
||||
for(auto ent : EnumeratePaths(src))
|
||||
{
|
||||
VFSPath destPath = dest / ent.GetFileName();
|
||||
if(FileExists(ent)) MoveFile(ent,destPath);
|
||||
if(DirectoryExists(ent)) MoveDirectory(ent,destPath);
|
||||
|
||||
}
|
||||
DeleteDirectory(src);
|
||||
}
|
||||
|
||||
VFSPath MemoryFilesystem::ReadLink(VFSPath p)
|
||||
{
|
||||
mtx->Lock();
|
||||
VFSPath p2;
|
||||
auto symlink = dynamic_cast<MemorySymlink*>(GetEntry(p,false));
|
||||
if(symlink != nullptr)
|
||||
{
|
||||
p2 = symlink->linkedTo;
|
||||
}
|
||||
mtx->Unlock();
|
||||
return p2;
|
||||
}
|
||||
std::string MemoryFilesystem::VFSPathToSystem(VFSPath path)
|
||||
{
|
||||
return path.ToString();
|
||||
}
|
||||
VFSPath MemoryFilesystem::SystemToVFSPath(std::string path)
|
||||
{
|
||||
return path;
|
||||
}
|
||||
void MemoryFilesystem::GetDate(VFSPath path, time_t& lastWrite, time_t& lastAccess)
|
||||
{
|
||||
|
||||
mtx->Lock();
|
||||
auto node = GetEntry(path,false);
|
||||
auto dir = dynamic_cast<MemoryDirectory*>(node);
|
||||
|
||||
auto file = dynamic_cast<MemoryFile*>(node);
|
||||
|
||||
auto sym = dynamic_cast<MemorySymlink*>(node);
|
||||
|
||||
if(dir != nullptr) lastWrite = dir->lastWrite;
|
||||
if(file != nullptr) lastWrite = file->data->lastWrite;
|
||||
if(sym != nullptr) lastWrite = sym->lastWrite;
|
||||
mtx->Unlock();
|
||||
lastAccess = lastWrite;
|
||||
}
|
||||
void MemoryFilesystem::SetDate(VFSPath path, time_t lastWrite, time_t lastAccess)
|
||||
{
|
||||
mtx->Lock();
|
||||
auto node = GetEntry(path,false);
|
||||
auto dir = dynamic_cast<MemoryDirectory*>(node);
|
||||
|
||||
auto file = dynamic_cast<MemoryFile*>(node);
|
||||
|
||||
auto sym = dynamic_cast<MemorySymlink*>(node);
|
||||
|
||||
if(dir != nullptr) dir->lastWrite = lastWrite;
|
||||
if(file != nullptr) file->data->lastWrite = lastWrite;
|
||||
if(sym != nullptr) sym->lastWrite = lastWrite;
|
||||
mtx->Unlock();
|
||||
}
|
||||
MemoryFilesystem::~MemoryFilesystem()
|
||||
{
|
||||
|
||||
}
|
||||
MemoryFilesystem::MemoryFilesystem()
|
||||
{
|
||||
mtx = std::make_shared<Threading::Mutex>();
|
||||
}
|
||||
|
||||
MemoryEntry::~MemoryEntry()
|
||||
{
|
||||
|
||||
}
|
||||
MemoryFile::~MemoryFile()
|
||||
{
|
||||
|
||||
}
|
||||
MemoryDirectory::MemoryDirectory()
|
||||
{
|
||||
this->lastWrite = time(NULL);
|
||||
}
|
||||
MemoryDirectory::~MemoryDirectory()
|
||||
{
|
||||
for(auto item : this->entries) delete item;
|
||||
}
|
||||
MemoryFileData::MemoryFileData()
|
||||
{
|
||||
this->lastWrite = time(NULL);
|
||||
this->canAccess=true;
|
||||
this->readers=0;
|
||||
|
||||
}
|
||||
}
|
||||
@ -266,7 +266,9 @@ namespace Tesses::Framework::Filesystem
|
||||
std::vector<std::string> paths;
|
||||
if(this->path.empty()) return VFSPath();
|
||||
paths.insert(paths.begin(), this->path.begin(), this->path.end()-1);
|
||||
return VFSPath(paths);
|
||||
auto res= VFSPath(paths);
|
||||
res.relative = this->relative;
|
||||
return res;
|
||||
}
|
||||
|
||||
std::string VFSPath::GetFileName()
|
||||
|
||||
Reference in New Issue
Block a user