Put everything back.

This commit is contained in:
jakcron
2022-04-16 21:27:49 +08:00
parent 5d62e839e7
commit bc04de6d09
844 changed files with 114383 additions and 29 deletions
@@ -0,0 +1,97 @@
#include <tc/io/BasicPathResolver.h>
const std::string tc::io::BasicPathResolver::kClassName = "tc::io::BasicPathResolver";
tc::io::BasicPathResolver::BasicPathResolver() :
BasicPathResolver(tc::io::Path("/"), {})
{}
tc::io::BasicPathResolver::BasicPathResolver(const tc::io::Path& current_directory_path) :
BasicPathResolver(current_directory_path, {})
{}
tc::io::BasicPathResolver::BasicPathResolver(const tc::io::Path& current_directory_path, const std::vector<std::string>& root_names) :
mCurrentDirPath(),
mExplicitRootLabels(root_names)
{
setCurrentDirectory(current_directory_path);
}
void tc::io::BasicPathResolver::setCurrentDirectory(const tc::io::Path& path)
{
if (path.empty())
{
throw tc::ArgumentOutOfRangeException(kClassName, "path was empty.");
}
mCurrentDirPath = path;
}
const tc::io::Path& tc::io::BasicPathResolver::getCurrentDirectory() const
{
return mCurrentDirPath;
}
void tc::io::BasicPathResolver::setExplicitRootLabels(const std::vector<std::string>& root_labels)
{
mExplicitRootLabels = root_labels;
}
const std::vector<std::string>& tc::io::BasicPathResolver::getExplicitRootLabels() const
{
return mExplicitRootLabels;
}
void tc::io::BasicPathResolver::resolveCanonicalPath(const tc::io::Path& path, tc::io::Path& canonical_path) const
{
canonical_path = resolveCanonicalPath(path);
}
tc::io::Path tc::io::BasicPathResolver::resolveCanonicalPath(const tc::io::Path& path) const
{
// create output path
tc::io::Path canonical_path;
// get iterator for input path
auto path_itr = path.begin();
// if the begining of the path exists and is a valid root label, then the input path is an absolute (but not necessarily canonical) path
if (path_itr != path.end() && (std::find(mExplicitRootLabels.begin(), mExplicitRootLabels.end(), *path_itr) != mExplicitRootLabels.end() || *path_itr == mCurrentDirPath.front()))
{
// the beginning of canonical_path is the path root name
canonical_path = tc::io::Path(*path_itr + "/");
// increment path iterator
path_itr++;
}
else
{
// the beginning of the canonical_path is the current directory path
canonical_path = mCurrentDirPath;
}
// process relative elements of path, combining with the base canonical_path
for (; path_itr != path.end(); path_itr++)
{
// ignore "current directory" alias
if (*path_itr == ".")
continue;
// ignore empty path elements
else if (*path_itr == "")
continue;
// navigate up for "parent directory" alias
else if (*path_itr == "..")
{
// ".." is the parent directory, so if there are path elements then we remove from the back to "go to the parent directory"
if (canonical_path.size() > 1)
canonical_path.pop_back();
else
continue;
}
else
canonical_path.push_back(*path_itr);
}
return canonical_path;
}
@@ -0,0 +1,370 @@
#include <tc/io/ConcatenatedStream.h>
#include <tc/io/StreamUtil.h>
#include <tc/io/IOUtil.h>
const std::string tc::io::ConcatenatedStream::kClassName = "tc::io::ConcatenatedStream";
tc::io::ConcatenatedStream::ConcatenatedStream() :
mStreamList(),
mStreamListMap(),
mCurrentStream(),
mCanRead(false),
mCanWrite(false),
mCanSeek(false),
mStreamLength(0)
{}
tc::io::ConcatenatedStream::ConcatenatedStream(ConcatenatedStream&& other) :
ConcatenatedStream()
{
*this = std::move(other);
}
tc::io::ConcatenatedStream::ConcatenatedStream(const std::vector<std::shared_ptr<tc::io::IStream>>& stream_list) :
ConcatenatedStream()
{
// track the overall stream properties
bool can_read = true;
bool can_write = true;
bool can_seek = true;
// reset stream length
mStreamLength = 0;
// process stream list
for (auto itr = stream_list.begin(); itr != stream_list.end(); itr++)
{
// skip null streams
if (*itr == nullptr)
continue;
// skip empty streams
if ((*itr)->length() == 0)
continue;
// skip streams that can't be read or writen to (so it's useless)
if ((*itr)->canRead() == false && (*itr)->canWrite() == false)
continue;
// create stream info for the input stream
StreamInfo info;
// range is from the current concatenated stream position for the length of the input stream
info.range = StreamRange(mStreamLength, (*itr)->length());
info.stream = *itr;
// throw an exception if the input stream range overlaps with an existing range (which shouldn't be possible)
if (mStreamListMap.find(info.range) != mStreamListMap.end())
{
throw tc::Exception(kClassName, "Poor state management detected.");
}
if (info.stream->canRead() == false)
can_read = false;
if (info.stream->canWrite() == false)
can_write = false;
if (info.stream->canSeek() == false)
can_seek = false;
mStreamListMap.insert(std::pair<StreamRange, size_t>(info.range, mStreamList.size()));
mStreamList.push_back(info);
mStreamLength += info.range.length;
}
// check the stream is usable
if (!can_read && !can_write)
{
throw tc::NotSupportedException(kClassName, "Stream does not support read or write.");
}
// check the stream is usable
if (mStreamLength == 0)
{
throw tc::NotSupportedException(kClassName, "Stream had no length.");
}
// save iterator to current stream
mCurrentStream = mStreamList.begin();
// save properties
mCanRead = can_read;
mCanWrite = can_write;
mCanSeek = can_seek;
}
tc::io::ConcatenatedStream::~ConcatenatedStream()
{
dispose();
}
tc::io::ConcatenatedStream& tc::io::ConcatenatedStream::operator=(tc::io::ConcatenatedStream&& other)
{
mStreamList = std::move(other.mStreamList);
mStreamListMap = std::move(other.mStreamListMap);
mCurrentStream = std::move(other.mCurrentStream);
mCanRead = other.mCanRead;
mCanWrite = other.mCanWrite;
mCanSeek = other.mCanSeek;
mStreamLength = other.mStreamLength;
// clear other state
other.mStreamList.clear();
other.mStreamListMap.clear();
other.mCurrentStream.makeNull();
other.mCanRead = false;
other.mCanWrite = false;
other.mCanSeek = false;
other.mStreamLength = 0;
return *this;
}
bool tc::io::ConcatenatedStream::canRead() const
{
return isStreamDisposed() ? false : mCanRead;
}
bool tc::io::ConcatenatedStream::canWrite() const
{
return isStreamDisposed() ? false : mCanWrite;
}
bool tc::io::ConcatenatedStream::canSeek() const
{
return isStreamDisposed() ? false : mCanSeek;
}
int64_t tc::io::ConcatenatedStream::length()
{
return isStreamDisposed() ? 0 : mStreamLength;
}
int64_t tc::io::ConcatenatedStream::position()
{
return isStreamDisposed() ? 0 : (mCurrentStream.get()->range.offset + mCurrentStream.get()->stream->position());
}
size_t tc::io::ConcatenatedStream::read(byte_t* ptr, size_t count)
{
if (isStreamDisposed())
{
throw tc::ObjectDisposedException(kClassName+"read()", "Stream wasd.");
}
if (mCanRead == false)
{
throw tc::NotSupportedException(kClassName+"read()", "Stream does not support reading.");
}
// read
size_t readable_count = IOUtil::getReadableCount(mStreamLength, position(), count);
size_t remaining_readable_count = readable_count;
do {
// determine expected readable data count for the current stream
size_t readable_count_for_current_stream = IOUtil::getReadableCount(mCurrentStream.get()->range.length, mCurrentStream.get()->stream->position(), remaining_readable_count);
if (readable_count_for_current_stream != 0)
{
// read data and throw exception if unexpected read count is returned
if (readable_count_for_current_stream != mCurrentStream.get()->stream->read(ptr + (readable_count - remaining_readable_count), readable_count_for_current_stream))
{
throw tc::io::IOException(kClassName+"read()", "Reading from one of the base streams returned less data than expected.");
}
// decrement the remaining readable count
remaining_readable_count -= readable_count_for_current_stream;
}
// if there is more data to be read, increment the current stream
if (remaining_readable_count != 0)
{
updateCurrentStream(mCurrentStream.get() + 1);
// make sure we haven't somehow reached the end before we expected
if (mCurrentStream.get() == mStreamList.end())
{
throw tc::io::IOException(kClassName+"read()", "More data was expected to be readable but end of stream list was reached.");
}
// correct the position the 0x0 if not already
if (mCurrentStream.get()->stream->position() != 0x0)
{
if (!mCanSeek)
{
throw tc::io::IOException(kClassName+"read()", "Tried to continue reading from the next stream but the position was not 0, and seek was not supported.");
}
mCurrentStream.get()->stream->seek(0, tc::io::SeekOrigin::Begin);
}
}
} while (remaining_readable_count > 0);
return readable_count;
}
size_t tc::io::ConcatenatedStream::write(const byte_t* ptr, size_t count)
{
if (isStreamDisposed())
{
throw tc::ObjectDisposedException(kClassName+"write()", "Stream was disposed.");
}
if (mCanWrite == false)
{
throw tc::NotSupportedException(kClassName+"write()", "Stream does not support writing.");
}
// write
size_t writable_count = IOUtil::getWritableCount(mStreamLength, position(), count);
size_t remaining_writable_count = writable_count;
do {
// determine expected writable data count for the current stream
size_t writable_count_for_current_stream = IOUtil::getWritableCount(mCurrentStream.get()->range.length, mCurrentStream.get()->stream->position(), remaining_writable_count);
if (writable_count_for_current_stream != 0)
{
// write data and throw exception if unexpected write count is returned
if (writable_count_for_current_stream != mCurrentStream.get()->stream->write(ptr + (writable_count - remaining_writable_count), writable_count_for_current_stream))
{
throw tc::io::IOException(kClassName+"write()", "Writing from one of the base streams returned less data than expected.");
}
// decrement the remaining writable count
remaining_writable_count -= writable_count_for_current_stream;
}
// if there is more data to be write, increment the current stream
if (remaining_writable_count != 0)
{
updateCurrentStream(mCurrentStream.get() + 1);
// make sure we haven't somehow reached the end before we expected
if (mCurrentStream.get() == mStreamList.end())
{
throw tc::io::IOException(kClassName+"write()", "More data was expected to be writable but end of stream list was reached.");
}
// correct the position the 0x0 if not already
if (mCurrentStream.get()->stream->position() != 0x0)
{
if (!mCanSeek)
{
throw tc::io::IOException(kClassName+"write()", "Tried to continue writing from the next stream but the position was not 0, and seek was not supported.");
}
mCurrentStream.get()->stream->seek(0, tc::io::SeekOrigin::Begin);
}
}
} while (remaining_writable_count > 0);
return writable_count;
}
int64_t tc::io::ConcatenatedStream::seek(int64_t offset, SeekOrigin origin)
{
if (isStreamDisposed())
{
throw tc::ObjectDisposedException(kClassName+"seek()", "Stream was disposed.");
}
if (mCanSeek == false)
{
throw tc::NotSupportedException(kClassName+"seek()", "Stream does not support seeking.");
}
// seek
int64_t absolute_seek_pos = StreamUtil::getSeekResult(offset, origin, position(), mStreamLength);
// seek is <= 0 : we use the first stream and set the position to 0
if (absolute_seek_pos <= 0)
{
updateCurrentStream(mStreamList.begin());
if (mCurrentStream.get() == mStreamList.end())
{
throw tc::io::IOException(kClassName+"seek()", "Failed to seek because underlying stream could not be determined.");
}
mCurrentStream.get()->stream->seek(0, tc::io::SeekOrigin::Begin);
}
// seek is < mStreamLength : we find the stream in the map and set the relative position
else if (absolute_seek_pos < mStreamLength)
{
// before we do a map lookup, check if the current stream has the range the offset sits in
if ((absolute_seek_pos >= mCurrentStream.get()->range.offset) && (absolute_seek_pos < (mCurrentStream.get()->range.offset + mCurrentStream.get()->range.length)))
{
mCurrentStream.get()->stream->seek(absolute_seek_pos - mCurrentStream.get()->range.offset, tc::io::SeekOrigin::Begin);
}
// look up the correct stream in the map
else
{
auto rangeItr = mStreamListMap.find(StreamRange(absolute_seek_pos));
if (rangeItr == mStreamListMap.end())
{
throw tc::io::IOException(kClassName+"seek()", "Failed to seek because underlying stream could not be determined.");
}
if (rangeItr->second > mStreamList.size())
{
throw tc::io::IOException(kClassName+"seek()", "Failed to seek because underlying stream could not be determined.");
}
updateCurrentStream(mStreamList.begin() + rangeItr->second);
mCurrentStream.get()->stream->seek(absolute_seek_pos - mCurrentStream.get()->range.offset, tc::io::SeekOrigin::Begin);
}
}
// seek is >= mStreamLength : we use the end stream and seek to the end of it
else
{
updateCurrentStream(--mStreamList.end());
if (mCurrentStream.get() == mStreamList.end())
{
throw tc::io::IOException(kClassName+"seek()", "Failed to seek because underlying stream could not be determined.");
}
mCurrentStream.get()->stream->seek(0, tc::io::SeekOrigin::End);
}
return position();
}
void tc::io::ConcatenatedStream::setLength(int64_t length)
{
if (isStreamDisposed())
{
throw tc::ObjectDisposedException(kClassName+"setLength()", "Stream was disposed.");
}
throw tc::NotImplementedException(kClassName+"setLength()", "setLength() is not implemented for tc::io::ConcatenatedStream.");
}
void tc::io::ConcatenatedStream::flush()
{
if (isStreamDisposed())
{
throw tc::ObjectDisposedException(kClassName+"flush()", "Stream was disposed.");
}
mCurrentStream.get()->stream->flush();
}
void tc::io::ConcatenatedStream::dispose()
{
if (isStreamDisposed() == false)
mCurrentStream.get()->stream->flush();
mStreamList.clear();
mCurrentStream.makeNull();
mCanRead = false;
mCanWrite = false;
mCanSeek = false;
mStreamLength = 0;
}
void tc::io::ConcatenatedStream::updateCurrentStream(std::vector<StreamInfo>::iterator stream_itr)
{
if (mCurrentStream.isNull())
{
mCurrentStream = stream_itr;
}
else if (mCurrentStream.get() != stream_itr)
{
// if stream itr != end() flush the stream
if (mCurrentStream.get() != mStreamList.end())
mCurrentStream.get()->stream->flush();
mCurrentStream = stream_itr;
}
}
@@ -0,0 +1,752 @@
#include <tc/io/FileStream.h>
#include <tc/PlatformErrorHandlingUtil.h>
#ifdef _WIN32
#include <direct.h>
#include <cstdlib>
#else
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h> /* For O_RDWR */
#include <unistd.h> /* For open(), creat() */
#endif
const std::string tc::io::FileStream::kClassName = "tc::io::FileStream";
tc::io::FileStream::FileHandle::~FileHandle()
{
#ifdef _WIN32
CloseHandle(handle);
#else
::close(handle);
#endif
}
tc::io::FileStream::FileStream() :
mCanRead(false),
mCanWrite(false),
mCanSeek(false),
mIsAppendRestrictSeekCall(false),
mFileHandle()
{}
tc::io::FileStream::FileStream(FileStream&& other) :
FileStream()
{
*this = std::move(other);
}
tc::io::FileStream::FileStream(const tc::io::Path& path, FileMode mode, FileAccess access) :
FileStream()
{
// dispose stream before opening new stream
dispose();
open_impl(path, mode, access);
}
tc::io::FileStream& tc::io::FileStream::operator=(tc::io::FileStream&& other)
{
mCanRead = other.mCanRead;
mCanWrite = other.mCanWrite;
mCanSeek = other.mCanSeek;
mIsAppendRestrictSeekCall = other.mIsAppendRestrictSeekCall;
mFileHandle = std::move(other.mFileHandle);
other.dispose();
return *this;
}
bool tc::io::FileStream::canRead() const
{
return mFileHandle == nullptr ? false : mCanRead;
}
bool tc::io::FileStream::canWrite() const
{
return mFileHandle == nullptr ? false : mCanWrite;
}
bool tc::io::FileStream::canSeek() const
{
return mFileHandle == nullptr || mIsAppendRestrictSeekCall == true ? false : mCanSeek;
}
int64_t tc::io::FileStream::length()
{
return mFileHandle == nullptr ? 0 : length_impl();
}
int64_t tc::io::FileStream::position()
{
if (mFileHandle == nullptr)
{
return 0;
}
if (mCanSeek == false)
{
throw tc::NotSupportedException(kClassName+"::position()", "This method is not supported for streams that do not support seeking");
}
return seek_impl(0, SeekOrigin::Current);
}
size_t tc::io::FileStream::read(byte_t* ptr, size_t count)
{
if (mFileHandle == nullptr)
{
throw tc::ObjectDisposedException(kClassName+"::read()", "Failed to read from stream (stream is disposed)");
}
if (mCanRead == false)
{
throw tc::NotSupportedException(kClassName+"::read()", "Stream does not support reading");
}
if (ptr == nullptr)
{
throw tc::ArgumentNullException(kClassName+"::read()", "ptr was null");
}
if (count < 0)
{
throw tc::ArgumentOutOfRangeException(kClassName+"::read()", "count was negative");
}
return read_impl(ptr, count);
}
size_t tc::io::FileStream::write(const byte_t* ptr, size_t count)
{
if (mFileHandle == nullptr)
{
throw tc::ObjectDisposedException(kClassName+"::write()", "Failed to write to stream (no file open)");
}
if (mCanWrite == false)
{
throw tc::NotSupportedException(kClassName+"::write()", "Stream does not support writing");
}
if (ptr == nullptr)
{
throw tc::ArgumentNullException(kClassName+"::write()", "ptr was null");
}
if (count < 0)
{
throw tc::ArgumentOutOfRangeException(kClassName+"::write()", "count was negative");
}
return write_impl(ptr, count);
}
int64_t tc::io::FileStream::seek(int64_t offset, SeekOrigin origin)
{
if (mFileHandle == nullptr)
{
throw tc::ObjectDisposedException(kClassName+"::seek()", "Failed to set stream position (stream is disposed)");
}
if (mCanSeek == false)
{
throw tc::NotSupportedException(kClassName+"::seek()", "Stream does not support seeking");
}
if (mIsAppendRestrictSeekCall == true)
{
throw tc::io::IOException(kClassName+"::seek()", "Streams opened in Append mode are not allowed to change file position.");
}
return seek_impl(offset, origin);
}
void tc::io::FileStream::setLength(int64_t length)
{
if (mFileHandle == nullptr)
{
throw tc::ObjectDisposedException(kClassName+"::setLength()", "Failed to set stream length (stream is disposed)");
}
if (mCanWrite == false || mCanSeek == false)
{
throw tc::NotSupportedException(kClassName+"::setLength()", "Stream does not support both writing and seeking");
}
setLength_impl(length);
}
void tc::io::FileStream::flush()
{
if (mFileHandle != nullptr)
{
flush_impl();
}
}
void tc::io::FileStream::dispose()
{
if (mFileHandle.get() != nullptr)
{
flush();
mFileHandle.reset();
}
mCanRead = false;
mCanWrite = false;
mCanSeek = false;
}
#ifdef _WIN32
#pragma warning(disable : 4065) // disable warning for switch case with only default case
void tc::io::FileStream::open_impl(const tc::io::Path& path, FileMode mode, FileAccess access)
{
// convert Path to unicode string
std::u16string unicode_path = path.to_u16string(tc::io::Path::Format::Win32);
DWORD access_flag = 0;
DWORD share_mode_flag = 0;
DWORD creation_flag = 0;
// process mode
switch (mode)
{
case (FileMode::CreateNew):
// create file if does not exist | return error if file does not exist
creation_flag = CREATE_NEW;
break;
case (FileMode::Create):
// create file if does not exist | truncate file if it exists
creation_flag = CREATE_ALWAYS;
break;
case (FileMode::Open):
// no flags
creation_flag = OPEN_EXISTING;
break;
case (FileMode::OpenOrCreate):
// create file if does not exist
creation_flag = access == FileAccess::Read ? OPEN_EXISTING : OPEN_ALWAYS;
break;
case (FileMode::Truncate):
// truncate file if file exists
creation_flag = TRUNCATE_EXISTING;
break;
case (FileMode::Append):
// open in append mode
creation_flag = OPEN_ALWAYS;
break;
default:
throw tc::ArgumentOutOfRangeException(kClassName+"::open()", "Illegal value for mode");
}
// process access
switch (access)
{
case (FileAccess::Read):
// read access
access_flag = GENERIC_READ;
// shared read lock
share_mode_flag = FILE_SHARE_READ;
break;
case (FileAccess::Write):
// write access
access_flag = GENERIC_WRITE;
// exclusive lock
share_mode_flag = 0;
break;
case (FileAccess::ReadWrite):
// read/write access
access_flag = GENERIC_READ | GENERIC_WRITE;
// exclusive lock
share_mode_flag = 0;
break;
default:
throw tc::ArgumentOutOfRangeException(kClassName+"::open()", "Illegal value for access");
}
// validate use of write dependent flags (open existing is the only one that supports no write flag)
if (creation_flag != OPEN_EXISTING && !(access_flag & GENERIC_WRITE))
{
throw tc::ArgumentException(kClassName + "::open()", "Stream open mode requires write access, but write access was not allowed");
}
// append can only open in write only mode
if (mode == tc::io::FileMode::Append && (access_flag & GENERIC_READ | GENERIC_WRITE) != GENERIC_WRITE)
{
throw tc::ArgumentException(kClassName + "::open()", "Stream opened in Append mode can only work with Write access. ReadWrite is not permitted");
}
// open file
HANDLE file_handle = CreateFileW((LPCWSTR)unicode_path.c_str(),
access_flag,
share_mode_flag,
0,
creation_flag,
FILE_ATTRIBUTE_NORMAL,
NULL);
// check file handle
if (file_handle == INVALID_HANDLE_VALUE)
{
DWORD error = GetLastError();
switch (error)
{
case (ERROR_FILE_NOT_FOUND):
case (ERROR_PATH_NOT_FOUND):
throw tc::io::FileNotFoundException(kClassName+"::open()", PlatformErrorHandlingUtil::GetLastErrorString(error));
case (ERROR_FILE_EXISTS):
throw tc::io::FileExistsException(kClassName+"::open()", PlatformErrorHandlingUtil::GetLastErrorString(error));
case (ERROR_INVALID_PARAMETER):
throw tc::ArgumentException(kClassName + "::open()", PlatformErrorHandlingUtil::GetLastErrorString(error));
case (ERROR_ACCESS_DENIED):
throw tc::UnauthorisedAccessException(kClassName+"::open()", PlatformErrorHandlingUtil::GetLastErrorString(error));
default:
throw tc::io::IOException(kClassName+"::open()", "Failed to open file stream (" + PlatformErrorHandlingUtil::GetLastErrorString(error) + ")");
}
}
// store file handle
mFileHandle = std::unique_ptr<tc::io::FileStream::FileHandle>(new tc::io::FileStream::FileHandle(file_handle));
// seek to end of file if in append mode
if (mode == FileMode::Append)
{
seek_impl(0, SeekOrigin::End);
mIsAppendRestrictSeekCall = true;
}
// set state flags
mCanRead = (access_flag & GENERIC_READ) ? true : false;
mCanWrite = (access_flag & GENERIC_WRITE) ? true : false;
mCanSeek = GetFileType(mFileHandle->handle) == FILE_TYPE_DISK ? true : false;
}
int64_t tc::io::FileStream::length_impl()
{
LARGE_INTEGER stream_length;
if (GetFileSizeEx(mFileHandle->handle, &stream_length) == false)
{
DWORD error = GetLastError();
switch (error)
{
// TODO: Directly handle usual errors for custom exceptions
default:
throw tc::io::IOException(kClassName+"::length()", "Failed to get stream length (" + PlatformErrorHandlingUtil::GetLastErrorString(error) + ")");
}
}
return (int64_t) stream_length.QuadPart;
}
size_t tc::io::FileStream::read_impl(byte_t* ptr, size_t count)
{
DWORD bytes_read;
if (ReadFile(mFileHandle->handle, ptr, (DWORD)count, &bytes_read, NULL) == false)
{
DWORD error = GetLastError();
switch (error)
{
// TODO: Directly handle usual errors for custom exceptions
default:
throw tc::io::IOException(kClassName+"::read()", "Failed to read from stream (" + PlatformErrorHandlingUtil::GetLastErrorString(error) + ")");
}
}
return bytes_read;
}
size_t tc::io::FileStream::write_impl(const byte_t* ptr, size_t count)
{
DWORD bytes_written;
if (WriteFile(mFileHandle->handle, ptr, (DWORD)count, &bytes_written, NULL) == false)
{
DWORD error = GetLastError();
switch (error)
{
// TODO: Directly handle usual errors for custom exceptions
default:
throw tc::io::IOException(kClassName+"::write()", "Failed to write to stream (" + PlatformErrorHandlingUtil::GetLastErrorString(error) + ")");
}
}
return bytes_written;
}
int64_t tc::io::FileStream::seek_impl(int64_t offset, SeekOrigin origin)
{
DWORD seek_flag = 0;
switch(origin)
{
case (SeekOrigin::Begin):
seek_flag = FILE_BEGIN;
break;
case (SeekOrigin::Current):
seek_flag = FILE_CURRENT;
break;
case (SeekOrigin::End):
seek_flag = FILE_END;
break;
default:
throw tc::ArgumentOutOfRangeException(kClassName+"::seek()", "Unknown SeekOrigin value");
}
LARGE_INTEGER win_pos, out;
win_pos.QuadPart = offset;
if (SetFilePointerEx(
mFileHandle->handle,
win_pos,
&out,
seek_flag
) == false)
{
DWORD error = GetLastError();
switch (error)
{
case (ERROR_NEGATIVE_SEEK):
throw tc::ArgumentOutOfRangeException(kClassName+"::seek()", PlatformErrorHandlingUtil::GetLastErrorString(error));
default:
throw tc::io::IOException(kClassName+"::seek()", "Failed to set stream position (" + PlatformErrorHandlingUtil::GetLastErrorString(error) + ")");
}
}
return out.QuadPart;
}
void tc::io::FileStream::setLength_impl(int64_t length)
{
seek(length, tc::io::SeekOrigin::Begin);
if (SetEndOfFile(
mFileHandle->handle
) == false)
{
DWORD error = GetLastError();
switch (error)
{
default:
throw tc::io::IOException(kClassName+"::setLength()", "Failed to set end of file (" + PlatformErrorHandlingUtil::GetLastErrorString(error) + ")");
}
}
}
void tc::io::FileStream::flush_impl()
{
if (mCanWrite)
{
// flush buffers only applies to written data
FlushFileBuffers(mFileHandle->handle);
}
}
#pragma warning(default : 4065) // reenable warning for switch case with only default case
#else
void tc::io::FileStream::open_impl(const tc::io::Path& path, FileMode mode, FileAccess access)
{
// convert Path to unicode string
std::string unicode_path = path.to_string(tc::io::Path::Format::POSIX);
// open file
int open_flag = 0;
// process mode
switch (mode)
{
case (FileMode::CreateNew):
// create file if does not exist | return error if file does not exist
open_flag |= O_CREAT | O_EXCL;
break;
case (FileMode::Create):
// create file if does not exist | truncate file if it exists
open_flag |= O_CREAT | O_TRUNC;
break;
case (FileMode::Open):
// no flags
open_flag |= 0;
break;
case (FileMode::OpenOrCreate):
// create file if does not exist (however only enable create flag if write access is enabled)
open_flag |= (access == FileAccess::ReadWrite || access == FileAccess::Write) ? O_CREAT : 0;
break;
case (FileMode::Truncate):
// truncate file if file exists
open_flag |= O_TRUNC;
break;
case (FileMode::Append):
// open in append mode (create file if doesn't exist)
open_flag |= O_APPEND | O_CREAT;
break;
default:
throw tc::ArgumentOutOfRangeException(kClassName+"::open()", "Illegal value for mode");
}
// process access
switch (access)
{
case (FileAccess::Read):
// read access
open_flag |= O_RDONLY;
#ifdef O_SHLOCK
// shared lock
open_flag |= O_SHLOCK;
#endif
break;
case (FileAccess::Write):
// write access
open_flag |= O_WRONLY;
#ifdef O_EXLOCK
// exclusive lock
open_flag |= O_EXLOCK;
#endif
break;
case (FileAccess::ReadWrite):
// read/write access
open_flag |= O_RDWR;
#ifdef O_EXLOCK
// exclusive lock
open_flag |= O_EXLOCK;
#endif
break;
default:
throw tc::ArgumentOutOfRangeException(kClassName+"::open()", "Illegal value for access");
}
// validate use of write dependent flags
if ((open_flag & (O_APPEND | O_TRUNC | O_CREAT)) && !(open_flag & (O_WRONLY|O_RDWR)))
{
throw tc::ArgumentException(kClassName+"::open()", "Stream open mode requires write access, but write access was not allowed");
}
// explicitly check APPEND as being write only
if ((open_flag & (O_APPEND)) && (open_flag & (O_RDWR)))
{
throw tc::ArgumentException(kClassName+"::open()", "Stream opened in Append mode can only work with Write access. ReadWrite is not permitted");
}
// open file handle with Read/Write for User, Read for Group, nothing for others
int file_handle = ::open(unicode_path.c_str(), open_flag, S_IRUSR | S_IWUSR | S_IRGRP);
// handle error
if (file_handle == -1)
{
switch (errno)
{
case (EACCES):
case (EROFS):
throw tc::UnauthorisedAccessException(kClassName+"::open()", PlatformErrorHandlingUtil::GetGnuErrorNumString(errno));
case (ENAMETOOLONG):
throw tc::io::PathTooLongException(kClassName+"::open()", PlatformErrorHandlingUtil::GetGnuErrorNumString(errno));
case (ENOENT):
throw tc::io::FileNotFoundException(kClassName+"::open()", PlatformErrorHandlingUtil::GetGnuErrorNumString(errno));
case (EEXIST):
throw tc::io::FileExistsException(kClassName+"::open()", PlatformErrorHandlingUtil::GetGnuErrorNumString(errno));
case (EINVAL):
throw tc::ArgumentOutOfRangeException(kClassName+"::open()", PlatformErrorHandlingUtil::GetGnuErrorNumString(errno));
case (EFAULT):
throw tc::AccessViolationException(kClassName+"::open()", PlatformErrorHandlingUtil::GetGnuErrorNumString(errno));
case (EISDIR):
throw tc::io::FileNotFoundException(kClassName+"::open()", PlatformErrorHandlingUtil::GetGnuErrorNumString(errno));
case (EDQUOT):
case (EFBIG):
case (EINTR):
case (ELOOP):
case (EMFILE):
case (ENFILE):
case (ENOMEM):
case (ENOSPC):
case (ENXIO):
case (EOVERFLOW):
case (EPERM):
case (ETXTBSY):
case (EWOULDBLOCK):
default:
throw tc::io::IOException(kClassName+"::open()", "Failed to open file stream (" + PlatformErrorHandlingUtil::GetGnuErrorNumString(errno) + ")");
}
}
// store file handle
mFileHandle = std::unique_ptr<tc::io::FileStream::FileHandle>(new tc::io::FileStream::FileHandle(file_handle));
// get stat info on file
struct stat stat_buf;
if (fstat(mFileHandle->handle, &stat_buf) == -1)
{
throw tc::io::IOException(kClassName+"::open()", "Failed to check stream properties using fstat() (" + PlatformErrorHandlingUtil::GetGnuErrorNumString(errno) + ")");
}
// if this is a directory throw an exception
if (S_ISDIR(stat_buf.st_mode))
{
throw tc::io::FileNotFoundException(kClassName+"::open()", "Path refers to a directory not a file");
}
// set state flags
// would check O_RDONLY but that resolves to 0 so it can't be bitmask checked
mCanRead = (open_flag & O_RDWR) || !(open_flag & O_WRONLY) ? true : false;
mCanWrite = (open_flag & (O_WRONLY|O_RDWR)) ? true : false;
mCanSeek = S_ISREG(stat_buf.st_mode) ? true : false;
// seek to end of file if in append mode
if (mode == FileMode::Append)
{
seek_impl(0, SeekOrigin::End);
mIsAppendRestrictSeekCall = true;
}
}
int64_t tc::io::FileStream::length_impl()
{
int64_t length;
// get stat info on file
struct stat stat_buf;
if (fstat(mFileHandle->handle, &stat_buf) == -1)
{
throw tc::io::IOException(kClassName+"::length()", "Failed to check stream properties using fstat() (" + PlatformErrorHandlingUtil::GetGnuErrorNumString(errno) + ")");
}
if (S_ISREG(stat_buf.st_mode))
{
length = stat_buf.st_size;
}
else
{
throw tc::NotSupportedException(kClassName+"::length()", "length() cannot be used with device-files or pipes");
}
return length;
}
size_t tc::io::FileStream::read_impl(byte_t* ptr, size_t count)
{
int64_t read_len = ::read(mFileHandle->handle, ptr, count);
// handle error
if (read_len == -1)
{
switch (errno)
{
case (EINVAL):
throw tc::ArgumentOutOfRangeException(kClassName+"::read()", PlatformErrorHandlingUtil::GetGnuErrorNumString(errno));
case (EFAULT):
throw tc::AccessViolationException(kClassName+"::read()", PlatformErrorHandlingUtil::GetGnuErrorNumString(errno));
case (EISDIR):
case (EBADF):
case (EAGAIN):
case (EINTR):
case (EIO):
default:
throw tc::io::IOException(kClassName+"::read()", "Failed to read from stream (" + PlatformErrorHandlingUtil::GetGnuErrorNumString(errno) + ")");
}
}
return size_t(read_len);
}
size_t tc::io::FileStream::write_impl(const byte_t* ptr, size_t count)
{
int64_t write_len = ::write(mFileHandle->handle, ptr, count);
// handle error
if (write_len == -1)
{
switch (errno)
{
case (EINVAL):
throw tc::ArgumentOutOfRangeException(kClassName+"::write()", PlatformErrorHandlingUtil::GetGnuErrorNumString(errno));
case (EFAULT):
throw tc::AccessViolationException(kClassName+"::write()", PlatformErrorHandlingUtil::GetGnuErrorNumString(errno));
case (EFBIG):
case (EAGAIN):
case (EDESTADDRREQ):
case (EDQUOT):
case (EINTR):
case (EIO):
case (ENOSPC):
case (EPERM):
case (EPIPE):
default:
throw tc::io::IOException(kClassName+"::write()", "Failed to write to stream (" + PlatformErrorHandlingUtil::GetGnuErrorNumString(errno) + ")");
}
}
return size_t(write_len);
}
int64_t tc::io::FileStream::seek_impl(int64_t offset, SeekOrigin origin)
{
int seek_flag = 0;
switch(origin)
{
case (SeekOrigin::Begin):
seek_flag = SEEK_SET;
break;
case (SeekOrigin::Current):
seek_flag = SEEK_CUR;
break;
case (SeekOrigin::End):
seek_flag = SEEK_END;
break;
default:
throw tc::ArgumentOutOfRangeException(kClassName+"::seek()", "Unknown SeekOrigin value");
}
#ifdef _LARGEFILE64_SOURCE
int64_t fpos = lseek64(mFileHandle->handle, offset, seek_flag);
#else
int64_t fpos = lseek(mFileHandle->handle, offset, seek_flag);
#endif
// handle error
if (fpos == -1)
{
switch (errno)
{
case (EINVAL):
throw tc::ArgumentOutOfRangeException(kClassName+"::seek()", PlatformErrorHandlingUtil::GetGnuErrorNumString(errno));
case (EOVERFLOW):
throw tc::OverflowException(kClassName+"::seek()", PlatformErrorHandlingUtil::GetGnuErrorNumString(errno));
case (EBADF):
case (ESPIPE):
case (ENXIO):
default:
throw tc::io::IOException(kClassName+"::seek()", "Failed to set stream position (" + PlatformErrorHandlingUtil::GetGnuErrorNumString(errno) + ")");
}
}
return fpos;
}
void tc::io::FileStream::setLength_impl(int64_t length)
{
#ifdef _LARGEFILE64_SOURCE
int trun_res = ftruncate64(mFileHandle->handle, length);
#else
int trun_res = ftruncate(mFileHandle->handle, length);
#endif
if (trun_res == -1)
{
switch (errno)
{
case (EINTR):
case (EINVAL):
case (EFBIG):
case (EIO):
case (EBADF):
default:
throw tc::io::IOException(kClassName+"::seek()", "Failed to set stream position (" + PlatformErrorHandlingUtil::GetGnuErrorNumString(errno) + ")");
}
}
}
void tc::io::FileStream::flush_impl()
{
// open/read/write are non-buffered
}
#endif
@@ -0,0 +1,40 @@
#include <tc/io/IOUtil.h>
int64_t tc::io::IOUtil::castSizeToInt64(size_t size)
{
if (std::numeric_limits<size_t>::digits > std::numeric_limits<int64_t>::digits)
size = std::min<size_t>(size, size_t(std::numeric_limits<int64_t>::max()));
return int64_t(size);
}
size_t tc::io::IOUtil::castInt64ToSize(int64_t length)
{
if (length < 0)
return 0;
if (std::numeric_limits<size_t>::digits < std::numeric_limits<int64_t>::digits)
length = std::min<int64_t>(length, int64_t(std::numeric_limits<size_t>::max()));
return size_t(length);
}
size_t tc::io::IOUtil::getAvailableSize(int64_t data_length, int64_t data_offset)
{
if (data_length < 0 || data_offset < 0)
return 0;
int64_t readable_length = (data_offset < data_length) ? (data_length - data_offset) : 0;
return castInt64ToSize(readable_length);
}
size_t tc::io::IOUtil::getReadableCount(int64_t data_length, int64_t data_offset, size_t requested_read_count)
{
return std::min<size_t>(getAvailableSize(data_length, data_offset), requested_read_count);
}
size_t tc::io::IOUtil::getWritableCount(int64_t data_length, int64_t data_offset, size_t requested_write_count)
{
return getReadableCount(data_length, data_offset, requested_write_count);
}
@@ -0,0 +1,419 @@
#include <tc/io/LocalFileSystem.h>
#include <tc/io/FileStream.h>
#include <tc/PlatformErrorHandlingUtil.h>
#include <tc/Exception.h>
#include <tc/string.h>
#ifdef _WIN32
#include <direct.h>
#include <cstdlib>
#pragma warning(disable : 4065) // disable warning for switch case with only default case
#else
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <dirent.h>
#endif
const std::string tc::io::LocalFileSystem::kClassName = "tc::io::LocalFileSystem";
tc::io::LocalFileSystem::LocalFileSystem() :
mState(1 << tc::RESFLAG_READY)
{
}
tc::ResourceStatus tc::io::LocalFileSystem::state()
{
return mState;
}
void tc::io::LocalFileSystem::dispose()
{
mState = (1 << tc::RESFLAG_NOINIT);
}
void tc::io::LocalFileSystem::createFile(const tc::io::Path& path)
{
tc::io::FileStream file(path, FileMode::Create, FileAccess::Write);
}
void tc::io::LocalFileSystem::removeFile(const tc::io::Path& path)
{
#ifdef _WIN32
// convert Path to unicode string
std::u16string unicode_path = path.to_u16string(tc::io::Path::Format::Win32);
// delete file
if (DeleteFileW((LPCWSTR)unicode_path.c_str()) == false)
{
DWORD error = GetLastError();
switch (error)
{
default:
throw tc::io::IOException(kClassName+"::removeFile()", "Failed to remove file (" + PlatformErrorHandlingUtil::GetLastErrorString(error) + ")");
}
}
#else
// convert Path to unicode string
std::string unicode_path = path.to_string(tc::io::Path::Format::POSIX);
if (unlink(unicode_path.c_str()) == -1)
{
switch (errno)
{
case (EACCES): // Search permission is denied for a component of the path prefix. -OR- Write permission is denied on the directory containing the link to be removed.
case (EROFS): // The named file resides on a read-only file system.
case (EPERM): // The named file is a directory and the effective user ID of the process is not the super-user. -OR- The directory containing the file is marked sticky, and neither the containing directory nor the file to be removed are owned by the effective user ID.
case (EBUSY): // The entry to be unlinked is the mount point for a mounted file system. -OR- The file named by the path argument cannot be unlinked because it is being used by the system or by another process.
throw tc::UnauthorisedAccessException(kClassName+"::removeFile()", PlatformErrorHandlingUtil::GetGnuErrorNumString(errno));
case (ENAMETOOLONG): // A component of a pathname exceeds {NAME_MAX} characters, or an entire path name exceeds {PATH_MAX} characters (possibly as a result of expanding a symlink).
throw tc::io::PathTooLongException(kClassName+"::removeFile()", PlatformErrorHandlingUtil::GetGnuErrorNumString(errno));
case (ENOENT): // The named file does not exist.
throw tc::io::FileNotFoundException(kClassName+"::removeFile()", PlatformErrorHandlingUtil::GetGnuErrorNumString(errno));
case (ENOTDIR): // A component of the path prefix is not a directory.
throw tc::io::DirectoryNotFoundException(kClassName+"::removeFile()", PlatformErrorHandlingUtil::GetGnuErrorNumString(errno));
case (EFAULT): // Path points outside the process's allocated address space.
case (EIO): // An I/O error occurs while deleting the directory entry or deallocating the inode.
case (ELOOP): // Too many symbolic links are encountered in translating the pathname. This is taken to be indicative of a looping symbolic link.
default:
throw tc::io::IOException(kClassName+"::removeFile()", "Failed to remove file (" + PlatformErrorHandlingUtil::GetGnuErrorNumString(errno) + ")");
}
}
#endif
}
void tc::io::LocalFileSystem::openFile(const tc::io::Path& path, tc::io::FileMode mode, tc::io::FileAccess access, std::shared_ptr<tc::io::IStream>& stream)
{
stream = std::shared_ptr<tc::io::FileStream>(new tc::io::FileStream(path, mode, access));
}
void tc::io::LocalFileSystem::createDirectory(const tc::io::Path& path)
{
#ifdef _WIN32
// convert Path to unicode string
std::u16string unicode_path = path.to_u16string(tc::io::Path::Format::Win32);
// create directory
if (CreateDirectoryW((LPCWSTR)unicode_path.c_str(), nullptr) == false && GetLastError() != ERROR_ALREADY_EXISTS)
{
DWORD error = GetLastError();
switch (error)
{
default:
throw tc::io::IOException(kClassName+"::createDirectory()", "Failed to create directory (" + PlatformErrorHandlingUtil::GetLastErrorString(error) + ")");
}
}
#else
// convert Path to unicode string
std::string unicode_path = path.to_string(tc::io::Path::Format::POSIX);
if (mkdir(unicode_path.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) == -1 && errno != EEXIST)
{
switch (errno)
{
case (EACCES): // Search permission is denied for a component of the path prefix. -OR- Write permission is denied for the parent directory.
case (EROFS): // The parent directory resides on a read-only file system.
throw tc::UnauthorisedAccessException(kClassName+"::createDirectory()", PlatformErrorHandlingUtil::GetGnuErrorNumString(errno));
case (ENOTDIR): // A component of the path prefix is not a directory.
case (ENOENT): // A component of the path prefix does not exist or path is an empty string.
throw tc::io::DirectoryNotFoundException(kClassName+"::removeFile()", PlatformErrorHandlingUtil::GetGnuErrorNumString(errno));
case (ENAMETOOLONG): // A component of a pathname exceeded {NAME_MAX} characters, or an entire path name exceeded {PATH_MAX} characters.
throw tc::io::PathTooLongException(kClassName+"::removeFile()", PlatformErrorHandlingUtil::GetGnuErrorNumString(errno));
case (EISDIR): // The named file is the root directory.
case (EDQUOT): // The new directory cannot be created because the user's quota of disk blocks on the file system that will contain the directory has been exhausted. -OR- The user's quota of inodes on the file system on which the directory is being created has been exhausted.
//case (EEXIST): // The named file exists
case (EFAULT): // Path points outside the process's allocated address space.
case (EIO): // An I/O error occurred while reading from or writing to the file system. -OR- An I/O error occurred while making the directory entry or allocating the inode.
case (ELOOP): // Too many symbolic links were encountered in translating the pathname. This is taken to be indicative of a looping symbolic link.
case (EMLINK): // The parent directory already has {LINK_MAX} links.
case (ENOSPC): // The new directory cannot be created because there is no space left on the file system that would contain it. -OR- There are no free inodes on the file system on which the directory is being created.
default:
throw tc::io::IOException(kClassName+"::createDirectory()", "Failed to create directory (" + PlatformErrorHandlingUtil::GetGnuErrorNumString(errno) + ")");
}
}
#endif
}
void tc::io::LocalFileSystem::removeDirectory(const tc::io::Path& path)
{
#ifdef _WIN32
// convert Path to unicode string
std::u16string unicode_path = path.to_u16string(tc::io::Path::Format::Win32);
if (RemoveDirectoryW((wchar_t*)unicode_path.c_str()) == false)
{
DWORD error = GetLastError();
switch (error)
{
case (ERROR_DIR_NOT_EMPTY):
case (ERROR_DIRECTORY):
default:
throw tc::io::IOException(kClassName+"::removeDirectory()", "Failed to remove directory (" + PlatformErrorHandlingUtil::GetLastErrorString(error) + ")");
}
}
#else
// convert Path to unicode string
std::string unicode_path = path.to_string(tc::io::Path::Format::POSIX);
if (rmdir(unicode_path.c_str()) == -1)
{
switch (errno)
{
case (EACCES): // Search permission is denied for a component of the path prefix. -OR- Write permission is denied on the directory containing the link to be removed.
case (EROFS): // The directory entry to be removed resides on a read-only file system.
case (EPERM): // The directory containing the directory to be removed is marked sticky, and neither the containing directory nor the directory to be removed are owned by the effective user ID.
case (EBUSY): // The directory to be removed is the mount point for a mounted file system.
throw tc::UnauthorisedAccessException(kClassName+"::removeDirectory()", PlatformErrorHandlingUtil::GetGnuErrorNumString(errno));
case (ENAMETOOLONG):
throw tc::io::PathTooLongException(kClassName+"::removeDirectory()", PlatformErrorHandlingUtil::GetGnuErrorNumString(errno));
case (ENOENT): // The named directory does not exist.
case (ENOTDIR): // A component of the path prefix is not a directory.
throw tc::io::DirectoryNotFoundException(kClassName+"::removeDirectory()", PlatformErrorHandlingUtil::GetGnuErrorNumString(errno));
case (ENOTEMPTY): // The named directory contains files other than `.' and `..' in it.
throw tc::io::DirectoryNotEmptyException(kClassName+"::removeDirectory()", PlatformErrorHandlingUtil::GetGnuErrorNumString(errno));
case (EFAULT): // Path points outside the process's allocated address space.
case (EIO): // An I/O error occurred while reading from or writing to the file system.
case (ELOOP): // Too many symbolic links are encountered in translating the pathname. This is taken to be indicative of a looping symbolic link.
default:
throw tc::io::IOException(kClassName+"::removeDirectory()", "Failed to remove directory (" + PlatformErrorHandlingUtil::GetGnuErrorNumString(errno) + ")");
}
}
#endif
}
void tc::io::LocalFileSystem::getWorkingDirectory(tc::io::Path& path)
{
#ifdef _WIN32
std::shared_ptr<char16_t> raw_char16_path(new char16_t[MAX_PATH]);
// get current directory
if (GetCurrentDirectoryW(MAX_PATH, (LPWSTR)(raw_char16_path.get())) == false)
{
DWORD error = GetLastError();
switch (error)
{
default:
throw tc::io::IOException(kClassName+"::getWorkingDirectory()", "Failed to get current working directory (" + PlatformErrorHandlingUtil::GetLastErrorString(error) + ")");
}
}
path = Path(raw_char16_path.get());
#else
setWorkingDirectory(Path("."));
std::shared_ptr<char> raw_current_working_directory(new char[PATH_MAX]);
if (getcwd(raw_current_working_directory.get(), PATH_MAX) == nullptr)
{
switch (errno)
{
case (EACCES): // Read or search permission was denied for a component of the pathname. This is only checked in limited cases, depending on implementation details.
throw tc::UnauthorisedAccessException(kClassName+"::getWorkingDirectory()", PlatformErrorHandlingUtil::GetGnuErrorNumString(errno));
case (EINVAL): // The size argument is zero.
case (ENOENT): // A component of the pathname no longer exists.
case (ENOMEM): // Insufficient memory is available.
case (ERANGE): // The size argument is greater than zero but smaller than the length of the pathname plus 1.
default:
throw tc::io::IOException(kClassName+"::getWorkingDirectory()", "Failed to get current working directory (getcwd) (" + PlatformErrorHandlingUtil::GetGnuErrorNumString(errno) + ")");
}
}
path = Path(raw_current_working_directory.get());
#endif
}
void tc::io::LocalFileSystem::setWorkingDirectory(const tc::io::Path& path)
{
#ifdef _WIN32
// convert Path to unicode string
std::u16string unicode_path = path.to_u16string(tc::io::Path::Format::Win32);
// delete file
if (SetCurrentDirectoryW((LPCWSTR)unicode_path.c_str()) == false)
{
DWORD error = GetLastError();
switch (error)
{
default:
throw tc::io::IOException(kClassName+"::setWorkingDirectory()", "Failed to set current working directory (" + PlatformErrorHandlingUtil::GetLastErrorString(error) + ")");
}
}
#else
// convert Path to unicode string
std::string unicode_path = path.to_string(tc::io::Path::Format::POSIX);
// get full path to directory
if (chdir(unicode_path.c_str()) != 0)
{
switch (errno)
{
case (EACCES): // Search permission is denied for any component of the path name.
throw tc::UnauthorisedAccessException(kClassName+"::setWorkingDirectory()", PlatformErrorHandlingUtil::GetGnuErrorNumString(errno));
case (ENAMETOOLONG): // A component of a pathname exceeded {NAME_MAX} characters, or an entire path name exceeded {PATH_MAX} characters.
throw tc::io::PathTooLongException(kClassName+"::setWorkingDirectory()", PlatformErrorHandlingUtil::GetGnuErrorNumString(errno));
case (ENOENT): // The named directory does not exist.
case (ENOTDIR): // A component of the path prefix is not a directory.
throw tc::io::DirectoryNotFoundException(kClassName+"::setWorkingDirectory()", PlatformErrorHandlingUtil::GetGnuErrorNumString(errno));
case (EFAULT): // Path points outside the process's allocated address space.
case (EIO): // An I/O error occurred while reading from or writing to the file system.
case (ELOOP): // Too many symbolic links were encountered in translating the pathname. This is taken to be indicative of a looping symbolic link.
default:
throw tc::io::IOException(kClassName+"::setWorkingDirectory()", "Failed to get directory info (chdir)(" + PlatformErrorHandlingUtil::GetGnuErrorNumString(errno) + ")");
}
}
#endif
}
void tc::io::LocalFileSystem::getDirectoryListing(const tc::io::Path& path, sDirectoryListing& info)
{
std::vector<std::string> child_dir_name_list;
std::vector<std::string> child_file_name_list;
Path current_directory_path;
#ifdef _WIN32
Path wildcard_path = path + tc::io::Path("*");
// convert Path to unicode string
std::u16string unicode_path = wildcard_path.to_u16string(tc::io::Path::Format::Win32);
HANDLE dir_handle = INVALID_HANDLE_VALUE;
WIN32_FIND_DATAW dir_entry;
dir_handle = FindFirstFileW((LPCWSTR)unicode_path.c_str(), &dir_entry);
if (dir_handle == INVALID_HANDLE_VALUE)
{
DWORD error = GetLastError();
switch (error)
{
default:
throw tc::io::IOException(kClassName+"::getDirectoryListing()", "Failed to open directory (" + PlatformErrorHandlingUtil::GetLastErrorString(error) + ")");
}
}
do {
std::string utf8_name;
tc::string::TranscodeUtil::UTF16ToUTF8((char16_t*)dir_entry.cFileName, utf8_name);
if (dir_entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
{
child_dir_name_list.push_back(utf8_name);
}
else
{
child_file_name_list.push_back(utf8_name);
}
} while (FindNextFileW(dir_handle, &dir_entry) != 0);
// throw error where GetLastError() isn't just that there were no more files
if (GetLastError() != ERROR_NO_MORE_FILES)
{
FindClose(dir_handle);
DWORD error = GetLastError();
switch (error)
{
default:
throw tc::io::IOException(kClassName+"::getDirectoryListing()", "Failed to open directory (" + PlatformErrorHandlingUtil::GetLastErrorString(error) + ")");
}
}
FindClose(dir_handle);
// save current dir for later
Path prev_current_dir;
getWorkingDirectory(prev_current_dir);
// change the directory
setWorkingDirectory(path);
// save the path
getWorkingDirectory(current_directory_path);
// restore current directory
setWorkingDirectory(prev_current_dir);
#else
// convert Path to unicode string
std::string unicode_path = path.to_string(tc::io::Path::Format::POSIX);
// open directory
DIR *dp;
dp = opendir(unicode_path.c_str());
if (dp == nullptr)
{
switch (errno)
{
case (EACCES): // Permission denied.
throw tc::UnauthorisedAccessException(kClassName+"::getDirectoryListing()", PlatformErrorHandlingUtil::GetGnuErrorNumString(errno));
case (ENOTDIR): // A component of the path prefix is not a directory. // name is not a directory.
case (ENOENT): // Directory does not exist, or name is an empty string.
throw tc::io::DirectoryNotFoundException(kClassName+"::getDirectoryListing()", PlatformErrorHandlingUtil::GetGnuErrorNumString(errno));
case (EBADF): // fd is not a valid file descriptor open for reading.
case (EMFILE):
case (ENFILE):
case (ENOMEM):
default:
throw tc::io::IOException(kClassName+"::getDirectoryListing()", "Failed to get directory info (opendir)(" + PlatformErrorHandlingUtil::GetGnuErrorNumString(errno) + ")");
}
}
// get file and directory names
child_dir_name_list.clear();
child_file_name_list.clear();
// since errno can be set by external sources it will be cleared, since the conditions for checking errno being set aren't specific to failure
errno = 0;
for (struct dirent *ep = readdir(dp); ep != nullptr && errno == 0; ep = readdir(dp))
{
if (ep->d_type == DT_DIR)
{
child_dir_name_list.push_back(std::string(ep->d_name));
}
else if (ep->d_type == DT_REG)
{
child_file_name_list.push_back(std::string(ep->d_name));
}
}
// throw an error if necessary
if (errno != 0)
{
switch (errno)
{
case (EBADF): // fd is not a valid file descriptor open for reading.
case (EIO): // An I/O error occurred while reading from or writing to the file system.
default:
throw tc::io::IOException(kClassName+"::getDirectoryListing()", "Failed to get directory info (readdir)(" + PlatformErrorHandlingUtil::GetGnuErrorNumString(errno) + ")");
}
}
// close dp
closedir(dp);
// save current dir for later
Path prev_current_dir;
getWorkingDirectory(prev_current_dir);
// change the directory
setWorkingDirectory(path);
// save the path
getWorkingDirectory(current_directory_path);
// restore current directory
setWorkingDirectory(prev_current_dir);
#endif
info.abs_path = current_directory_path;
info.dir_list = child_dir_name_list;
info.file_list = child_file_name_list;
}
#ifdef _WIN32
#pragma warning(default : 4065) // reenable warning for switch case with only default case
#endif
@@ -0,0 +1,49 @@
#include <tc/io/MemorySource.h>
#include <tc/io/IOUtil.h>
const std::string tc::io::MemorySource::kClassName = "tc::io::MemorySource";
tc::io::MemorySource::MemorySource() :
mData()
{
}
tc::io::MemorySource::MemorySource(const tc::ByteData& byte_data) :
mData(byte_data)
{
}
tc::io::MemorySource::MemorySource(tc::ByteData&& byte_data) :
mData(std::move(byte_data))
{
}
tc::io::MemorySource::MemorySource(const byte_t* data, size_t len) :
mData(data, len)
{
}
int64_t tc::io::MemorySource::length()
{
return int64_t(mData.size());
}
tc::ByteData tc::io::MemorySource::pullData(int64_t offset, size_t count)
{
size_t read_len = IOUtil::getReadableCount(this->length(), offset, count);
// if the read length is zero then return now.
if (read_len == 0)
return tc::ByteData();
tc::ByteData out(read_len);
memcpy(out.data(), mData.data() + offset, read_len);
return out;
}
@@ -0,0 +1,155 @@
#include <tc/io/MemoryStream.h>
#include <limits>
#include <tc/io/StreamUtil.h>
#include <tc/io/IOUtil.h>
const std::string tc::io::MemoryStream::kClassName = "tc::io::MemoryStream";
tc::io::MemoryStream::MemoryStream() :
MemoryStream(0)
{}
tc::io::MemoryStream::MemoryStream(size_t length) :
mData(),
mPosition(0)
{
setLength(length);
}
tc::io::MemoryStream::MemoryStream(const tc::ByteData& byte_data) :
mData(byte_data),
mPosition(0)
{
}
tc::io::MemoryStream::MemoryStream(tc::ByteData&& byte_data) :
mData(std::move(byte_data)),
mPosition(0)
{
}
tc::io::MemoryStream::MemoryStream(const byte_t* data, size_t len) :
mData(data, len),
mPosition(0)
{
}
bool tc::io::MemoryStream::canRead() const
{
return true;
}
bool tc::io::MemoryStream::canWrite() const
{
return true;
}
bool tc::io::MemoryStream::canSeek() const
{
return true;
}
int64_t tc::io::MemoryStream::length()
{
return mData.size();
}
int64_t tc::io::MemoryStream::position()
{
return mPosition;
}
size_t tc::io::MemoryStream::read(byte_t* ptr, size_t count)
{
if (ptr == nullptr)
{
throw tc::ArgumentNullException(kClassName+"::read()", "ptr is null.");
}
count = IOUtil::getReadableCount(IOUtil::castSizeToInt64(mData.size()), mPosition, count);
memcpy(ptr, mData.data() + mPosition, count);
mPosition += IOUtil::castSizeToInt64(count);
return count;
}
size_t tc::io::MemoryStream::write(const byte_t* ptr, size_t count)
{
if (ptr == nullptr)
{
throw tc::ArgumentNullException(kClassName+"::write()", "ptr is null.");
}
// check if the position is past the end of stream, enlarge stream in this case
if ((IOUtil::castInt64ToSize(mPosition) + count) > mData.size())
{
setLength(mPosition + IOUtil::castSizeToInt64(count));
}
count = IOUtil::getWritableCount(IOUtil::castSizeToInt64(mData.size()), mPosition, count);
memcpy(mData.data() + mPosition, ptr, count);
mPosition += IOUtil::castSizeToInt64(count);
return count;
}
int64_t tc::io::MemoryStream::seek(int64_t offset, SeekOrigin origin)
{
int64_t new_pos = StreamUtil::getSeekResult(offset, origin, mPosition, (int64_t)mData.size());
if (new_pos < 0)
{
throw tc::ArgumentOutOfRangeException(kClassName+"::seek()", "New position is negative.");
}
mPosition = new_pos;
return mPosition;
}
void tc::io::MemoryStream::setLength(int64_t length)
{
// check length isn't too large (int64_t could be larger than size_t)
if (IOUtil::castInt64ToSize(length) > std::numeric_limits<size_t>::max())
{
throw tc::ArgumentOutOfRangeException(kClassName+"::setLength()", "Length greater than maxium possible length for MemoryStream");
}
// check length isn't negative
if (length < 0)
{
throw tc::ArgumentOutOfRangeException(kClassName+"::setLength()", "Length is negative.");
}
// create new ByteData
ByteData data(IOUtil::castInt64ToSize(length));
// determine copy length (between old and new ByteData)
size_t copy_len = std::min<size_t>(data.size(), mData.size());
// copy from old to new ByteData
memcpy(data.data(), mData.data(), copy_len);
// re-assign mData (this frees the old mData)
mData = data;
// reduce position if shrunk
mPosition = std::min<int64_t>(mPosition, int64_t(mData.size()));
}
void tc::io::MemoryStream::flush()
{
// do nothing
}
void tc::io::MemoryStream::dispose()
{
mData = ByteData();
}
@@ -0,0 +1,143 @@
#include <tc/io/OverlayedSource.h>
#include <tc/io/IOUtil.h>
const std::string tc::io::OverlayedSource::kClassName = "tc::io::OverlayedSource";
tc::io::OverlayedSource::OverlayedSource() :
mBaseSource(),
mOverlaySourceInfos()
{
}
tc::io::OverlayedSource::OverlayedSource(const std::shared_ptr<tc::io::ISource>& base_source, const std::shared_ptr<tc::io::ISource>& overlay_source, int64_t offset, int64_t length) :
OverlayedSource(base_source, {OverlaySourceInfo{overlay_source, offset, length}})
{
}
tc::io::OverlayedSource::OverlayedSource(const std::shared_ptr<tc::io::ISource>& base_source, const std::vector<OverlaySourceInfo>& overlay_source_infos)
{
// throw exception if the base source is null
if (base_source == nullptr)
{
throw tc::ArgumentNullException(kClassName+"::OverlayedSource()", "base_source was null.");
}
// copy base source ptr
mBaseSource = base_source;
// check/import overlay sources
for (auto itr = overlay_source_infos.begin(); itr != overlay_source_infos.end(); itr++)
{
// skip regions with no length
if (itr->length == 0)
{
continue;
}
// throw exception if a overlay source is null
if (itr->overlay_source == nullptr)
{
throw tc::ArgumentNullException(kClassName+"::OverlayedSource()", "overlay_source was null.");
}
// throw exception if overly region offset is negative
if (itr->offset < 0)
{
throw tc::ArgumentOutOfRangeException(kClassName+"::OverlayedSource()", "Invalid overlay region. Overlay offset is negative.");
}
// throw exception if the overlay region offset is beyond the length of the base source
if (itr->offset > mBaseSource->length())
{
throw tc::ArgumentOutOfRangeException(kClassName+"::OverlayedSource()", "Invalid overlay region. Overlay offset beyond length of base_source.");
}
// throw exception if the overlay region exceeds the length of the base source
if ((itr->offset + itr->length) > mBaseSource->length())
{
throw tc::ArgumentOutOfRangeException(kClassName+"::OverlayedSource()", "Invalid overlay region. Overlay region exceeds the length of base_source.");
}
// throw exception if the overlay region exceeds the length of the overlay source
if (itr->length > itr->overlay_source->length())
{
throw tc::ArgumentOutOfRangeException(kClassName+"::OverlayedSource()", "Invalid overlay region. Overlay region exceeds the length of overlay_source.");
}
// save overlay source info
mOverlaySourceInfos.push_back(*itr);
}
}
int64_t tc::io::OverlayedSource::length()
{
// return 0 if mBaseSource is null, otherwise deref the pointer and get the length
return mBaseSource == nullptr ? 0 : mBaseSource->length();
}
tc::ByteData tc::io::OverlayedSource::pullData(int64_t offset, size_t count)
{
// return empty byte_data if the base is empty
if (mBaseSource == nullptr)
return tc::ByteData();
size_t read_len = IOUtil::getReadableCount(this->length(), offset, count);
// if the read length is zero then return now.
if (read_len == 0)
return tc::ByteData();
// get base source byte_data, this will be overwritten with regions
tc::ByteData out = mBaseSource->pullData(offset, count);
// iterate thru the overlays
for (auto itr = mOverlaySourceInfos.begin(); itr != mOverlaySourceInfos.end(); itr++)
{
int64_t overlay_pull_offset = 0;
size_t overlay_pull_count = 0;
// skip overlay if the pullable count is 0
getOverlaySourcePullableRegion(offset, count, *itr, overlay_pull_offset, overlay_pull_count);
if (overlay_pull_count == 0)
{
continue;
}
// pull data from overlay
ByteData overlay_pull = itr->overlay_source->pullData(overlay_pull_offset, overlay_pull_count);
// adjust the outsize to be the minimum of the ByteData & attempted pull_count
overlay_pull_count = std::min<size_t>(overlay_pull.size(), overlay_pull_count);
// copy into out buffer
int64_t overlay_offset_in_out = (overlay_pull_offset + itr->offset) - offset;
memcpy(out.data() + overlay_offset_in_out, overlay_pull.data(), overlay_pull_count);
}
return out;
}
void tc::io::OverlayedSource::getOverlaySourcePullableRegion(int64_t base_pull_offset, size_t base_pull_count, const OverlaySourceInfo& overlay_info, int64_t& overlay_pull_offset, size_t& overlay_pull_count)
{
int64_t overlay_relative_start_offset = base_pull_offset - overlay_info.offset;
int64_t overlay_relative_end_offset = overlay_relative_start_offset + int64_t(base_pull_count);
// if the start offset > overlay length: then the data starts after the overlay ends
// if the end offset < 0: then the data ends before the overlay begins
if (overlay_relative_start_offset > overlay_info.length || overlay_relative_end_offset < 0)
{
overlay_pull_offset = 0;
overlay_pull_count = 0;
}
// otherwise some or all of the overlay can be used
else
{
// the offset must be reset to zero if it is negative
overlay_pull_offset = overlay_relative_start_offset > 0 ? overlay_relative_start_offset : 0;
// getReadableSize will cap the amount read if it exceeds the base_length
overlay_pull_count = IOUtil::getReadableCount(overlay_info.length, overlay_pull_offset, size_t(overlay_relative_end_offset - overlay_pull_offset));
}
}
@@ -0,0 +1,34 @@
#include <tc/io/PaddingSource.h>
#include <tc/io/IOUtil.h>
const std::string tc::io::PaddingSource::kClassName = "tc::io::PaddingSource";
tc::io::PaddingSource::PaddingSource() :
mSourceLength(0),
mPaddingByte(0)
{
}
tc::io::PaddingSource::PaddingSource(byte_t padding_byte, int64_t size) :
mSourceLength(size),
mPaddingByte(padding_byte)
{
if (size < 0)
{
throw tc::ArgumentOutOfRangeException(kClassName, "length is negative");
}
}
int64_t tc::io::PaddingSource::length()
{
return mSourceLength;
}
tc::ByteData tc::io::PaddingSource::pullData(int64_t offset, size_t count)
{
tc::ByteData data(IOUtil::getReadableCount(mSourceLength, offset, count));
memset(data.data(), mPaddingByte, data.size());
return data;
}
+331
View File
@@ -0,0 +1,331 @@
#include <tc/io/Path.h>
#include <tc/string.h>
#include <tc/Exception.h>
#include <fmt/core.h>
#include <sstream>
#include <iostream>
#include <regex>
static const char kWindowsPathSeparator = '\\'; /**< Path separator used on Microsoft Windows based systems */
static const char kPosixPathSeparator = '/'; /**< Path separator used on POSIX based systems */
#ifdef _WIN32
static const char kNativePathSeparator = kWindowsPathSeparator; /**< Path separator for the native environment */
#else
static const char kNativePathSeparator = kPosixPathSeparator; /**< Path separator for the native environment */
#endif
const std::string tc::io::Path::kClassName = "tc::io::Path";
tc::io::Path::Path()
{}
tc::io::Path::Path(std::initializer_list<std::string> list) :
mUnicodePath(list)
{
}
tc::io::Path::Path(const std::string& path)
{
initializePath(path);
}
tc::io::Path::Path(const std::u16string& path)
{
std::string utf8_path;
string::TranscodeUtil::UTF16ToUTF8(path, utf8_path);
initializePath(utf8_path);
}
tc::io::Path::Path(const std::u32string& path)
{
std::string utf8_path;
string::TranscodeUtil::UTF32ToUTF8(path, utf8_path);
initializePath(utf8_path);
}
tc::io::Path tc::io::Path::operator+(const Path& other) const
{
Path new_path = *this;
new_path.appendPath(other.mUnicodePath);
return new_path;
}
void tc::io::Path::operator+=(const Path& other)
{
appendPath(other.mUnicodePath);
}
bool tc::io::Path::operator==(const Path& other) const
{
return mUnicodePath == other.mUnicodePath;
}
bool tc::io::Path::operator!=(const Path& other) const
{
return !(this->operator==(other));
}
bool tc::io::Path::operator<(const Path& other) const
{
int cmp_score = 0;
auto self_itr = this->begin();
auto other_itr = other.begin();
// in this loop for as long as both path has an itr, it'll compare them
for (; self_itr != this->end() && other_itr != other.end(); self_itr++, other_itr++)
{
cmp_score = self_itr->compare(*other_itr);
if (cmp_score != 0)
break;
}
// if one of the itrs isn't the end, then that one is "larger"
// it can't be both or the prior loop won't have ended
if (cmp_score == 0 && (self_itr != this->end() || other_itr != other.end()))
{
cmp_score = self_itr == this->end() ? -1 : 1;
}
return cmp_score < 0;
}
tc::io::Path::iterator tc::io::Path::begin()
{
return mUnicodePath.begin();
}
std::string& tc::io::Path::front()
{
return mUnicodePath.front();
}
const std::string& tc::io::Path::front() const
{
return mUnicodePath.front();
}
std::string& tc::io::Path::back()
{
return mUnicodePath.back();
}
const std::string& tc::io::Path::back() const
{
return mUnicodePath.back();
}
tc::io::Path::const_iterator tc::io::Path::begin() const
{
return mUnicodePath.begin();
}
tc::io::Path::iterator tc::io::Path::end()
{
return mUnicodePath.end();
}
tc::io::Path::const_iterator tc::io::Path::end() const
{
return mUnicodePath.end();
}
void tc::io::Path::pop_front()
{
mUnicodePath.pop_front();
}
void tc::io::Path::pop_back()
{
mUnicodePath.pop_back();
}
void tc::io::Path::push_front(const std::string& str)
{
mUnicodePath.push_front(str);
}
void tc::io::Path::push_back(const std::string& str)
{
mUnicodePath.push_back(str);
}
void tc::io::Path::clear()
{
mUnicodePath.clear();
}
size_t tc::io::Path::size() const
{
return mUnicodePath.size();
}
bool tc::io::Path::empty() const
{
return mUnicodePath.empty();
}
tc::io::Path tc::io::Path::subpath(size_t pos, size_t len) const
{
tc::io::Path out_path;
auto itr = begin();
size_t index = 0;
// while the out_path size is less than len and the iterator hasn't ended
while ( out_path.size() < len && itr != end() )
{
// provided the index >= pos save the element
if (index >= pos)
{
out_path.push_back(*itr);
}
itr++;
index++;
}
return out_path;
}
tc::io::Path tc::io::Path::subpath(const_iterator begin, const_iterator end) const
{
tc::io::Path out_path;
for (auto itr = begin; itr != end && itr != this->end(); itr++)
{
out_path.push_back(*itr);
}
return out_path;
}
std::string tc::io::Path::to_string(Format format) const
{
std::string path_str = "";
if (format == Path::Format::Native)
{
#ifdef _WIN32
format = Path::Format::Win32;
#else
format = Path::Format::POSIX;
#endif
}
std::string path_separator_str;
switch (format)
{
case (Path::Format::POSIX):
path_separator_str = fmt::format("{:c}", kPosixPathSeparator);
break;
case (Path::Format::Win32):
path_separator_str = fmt::format("{:c}", kWindowsPathSeparator);
break;
default:
throw tc::ArgumentException(kClassName, "Invalid Format type.");
}
// special case where the path has one element and its the root path
if (this->size() == 1)
{
// for POSIX style this is the empty string ("/")
if (format == Path::Format::POSIX && this->front() == "")
return path_separator_str;
// for Win32 style this is a drive letter followed by ':' e.g. ("C:\")
else if (format == Path::Format::Win32 && std::regex_match(this->front(), std::regex("^([A-Z,a-z]):")))
return fmt::format("{}{}", this->front(), path_separator_str);
}
for (const_iterator itr = this->begin(); itr != this->end(); itr++)
{
path_str += *itr;
// don't print path separator where it would be trailing character
if (itr != --(this->end()))
path_str += path_separator_str;
}
return path_str;
}
std::u16string tc::io::Path::to_u16string(Format format) const
{
std::string u8string = to_string(format);
std::u16string u16string;
// convert
string::TranscodeUtil::UTF8ToUTF16(u8string, u16string);
// return
return u16string;
}
std::u32string tc::io::Path::to_u32string(Format format) const
{
std::string u8string = to_string(format);
std::u32string u32string;
// convert
string::TranscodeUtil::UTF8ToUTF32(u8string, u32string);
// return
return u32string;
}
tc::io::Path::operator std::string() const
{
return to_string(Format::Native);
}
tc::io::Path::operator std::u16string() const
{
return to_u16string(Format::Native);
}
tc::io::Path::operator std::u32string() const
{
return to_u32string(Format::Native);
}
void tc::io::Path::initializePath(const std::string& src)
{
size_t windows_slash_count = 0;
size_t posix_slash_count = 0;
for (size_t i = 0; i < src.size(); i++)
{
if (src[i] == kWindowsPathSeparator)
windows_slash_count += 1;
else if (src[i] == kPosixPathSeparator)
posix_slash_count += 1;
}
if (windows_slash_count != 0 && posix_slash_count != 0)
{
throw tc::ArgumentException(kClassName, "Path literal has both forward ('/') and backward ('\\') path separators.");
}
char path_delimiter = kNativePathSeparator;
if (windows_slash_count > 0)
path_delimiter = kWindowsPathSeparator;
else if (posix_slash_count > 0)
path_delimiter = kPosixPathSeparator;
std::stringstream src_stream(src);
std::string element;
while (std::getline(src_stream, element, path_delimiter))
{
mUnicodePath.push_back(element);
}
}
void tc::io::Path::appendPath(const std::list<std::string>& other)
{
for (std::list<std::string>::const_iterator itr = other.begin(); itr != other.end(); itr++)
{
mUnicodePath.push_back(*itr);
}
}
@@ -0,0 +1,12 @@
#include <tc/io/PathUtil.h>
#include <tc/string.h>
void tc::io::PathUtil::pathToWindowsUTF16(const tc::io::Path& path, std::u16string& out)
{
out = path.to_u16string(tc::io::Path::Format::Win32);
}
void tc::io::PathUtil::pathToUnixUTF8(const tc::io::Path& path, std::string& out)
{
out = path.to_string(tc::io::Path::Format::POSIX);
}
@@ -0,0 +1,54 @@
#include <tc/io/StreamSink.h>
const std::string tc::io::StreamSink::kClassName = "tc::io::StreamSink";
tc::io::StreamSink::StreamSink() :
mBaseStream(nullptr)
{
}
tc::io::StreamSink::StreamSink(const std::shared_ptr<tc::io::IStream>& stream) :
mBaseStream(stream)
{
if (mBaseStream == nullptr)
{
throw tc::ArgumentNullException(kClassName, "The base stream is null.");
}
if (mBaseStream->canWrite() == false)
{
throw tc::NotSupportedException(kClassName, "The base stream does not support writing.");
}
if (mBaseStream->canSeek() == false)
{
throw tc::NotSupportedException(kClassName, "The base stream does not support seeking.");
}
}
int64_t tc::io::StreamSink::length()
{
return mBaseStream == nullptr ? 0 :mBaseStream->length();
}
void tc::io::StreamSink::setLength(int64_t length)
{
if (mBaseStream == nullptr)
{
throw tc::ObjectDisposedException(kClassName+"::setLength()", "The base stream was not initialized.");
}
mBaseStream->setLength(length);
}
size_t tc::io::StreamSink::pushData(const tc::ByteData& data, int64_t offset)
{
if (mBaseStream == nullptr)
{
throw tc::ObjectDisposedException(kClassName+"::pushData()", "The base stream was not initialized.");
}
mBaseStream->seek(offset, tc::io::SeekOrigin::Begin);
return mBaseStream->write(data.data(), data.size());
}
@@ -0,0 +1,55 @@
#include <tc/io/StreamSource.h>
#include <tc/io/IOUtil.h>
const std::string tc::io::StreamSource::kClassName = "tc::io::StreamSource";
tc::io::StreamSource::StreamSource() :
mBaseStream(nullptr)
{
}
tc::io::StreamSource::StreamSource(const std::shared_ptr<tc::io::IStream>& stream) :
mBaseStream(stream)
{
if (mBaseStream == nullptr)
{
throw tc::ArgumentNullException(kClassName, "The base stream is null.");
}
if (mBaseStream->canRead() == false)
{
throw tc::NotSupportedException(kClassName, "The base stream does not support reading.");
}
if (mBaseStream->canSeek() == false)
{
throw tc::NotSupportedException(kClassName, "The base stream does not support seeking.");
}
}
int64_t tc::io::StreamSource::length()
{
return mBaseStream == nullptr ? 0 : mBaseStream->length();
}
tc::ByteData tc::io::StreamSource::pullData(int64_t offset, size_t count)
{
// get readable count
size_t read_count = IOUtil::getReadableCount(this->length(), offset, count);
// return if nothing is to be read
if (read_count == 0)
{
return tc::ByteData();
}
// allocate ByteData
ByteData data(read_count);
// read from stream (note this will not be called if mBaseStream is null, as in that case read_count == 0, and this code won't be reached)
mBaseStream->seek(offset, tc::io::SeekOrigin::Begin);
mBaseStream->read(data.data(), data.size());
// return populated ByteData
return data;
}
@@ -0,0 +1,22 @@
#include <tc/io/StreamUtil.h>
int64_t tc::io::StreamUtil::getSeekResult(int64_t offset, tc::io::SeekOrigin origin, int64_t current_position, int64_t stream_length)
{
int64_t new_pos = 0;
switch (origin)
{
case (SeekOrigin::Begin):
new_pos = offset;
break;
case (SeekOrigin::Current):
new_pos = current_position + offset;
break;
case (SeekOrigin::End):
new_pos = stream_length + offset;
break;
default:
throw tc::ArgumentOutOfRangeException("Illegal value for origin.");
}
return new_pos;
}
@@ -0,0 +1,216 @@
#include <tc/io/SubFileSystem.h>
const std::string tc::io::SubFileSystem::kClassName = "tc::io::SubFileSystem";
tc::io::SubFileSystem::SubFileSystem() :
mBaseFileSystem(),
mBasePathResolver(),
mSubPathResolver()
{
}
tc::io::SubFileSystem::SubFileSystem(const std::shared_ptr<tc::io::IFileSystem>& file_system, const tc::io::Path& base_path) :
SubFileSystem()
{
// copy IFileSystem ptr
mBaseFileSystem = file_system;
if (mBaseFileSystem == nullptr)
{
throw tc::ArgumentNullException(kClassName, "file_system is null");
}
else if (mBaseFileSystem->state().test(RESFLAG_READY) == false)
{
throw tc::InvalidOperationException(kClassName, "file_system is not ready");
}
// save current path
tc::io::Path prev_canonical_base_path;
mBaseFileSystem->getWorkingDirectory(prev_canonical_base_path);
// get full path of root
tc::io::Path canonical_base_path;
mBaseFileSystem->setWorkingDirectory(base_path);
mBaseFileSystem->getWorkingDirectory(canonical_base_path);
// restore current path
mBaseFileSystem->setWorkingDirectory(prev_canonical_base_path);
// set state for path resolvers
mBasePathResolver.setCurrentDirectory(canonical_base_path);
mSubPathResolver.setCurrentDirectory(tc::io::Path("/"));
}
tc::ResourceStatus tc::io::SubFileSystem::state()
{
return mBaseFileSystem.get() ? mBaseFileSystem->state() : tc::ResourceStatus(1 << tc::RESFLAG_NOINIT);
}
void tc::io::SubFileSystem::dispose()
{
if (mBaseFileSystem.get() != nullptr)
mBaseFileSystem->dispose();
mBasePathResolver = tc::io::BasicPathResolver();
mSubPathResolver = tc::io::BasicPathResolver();
}
void tc::io::SubFileSystem::createFile(const tc::io::Path& path)
{
if (mBaseFileSystem == nullptr)
{
throw tc::ObjectDisposedException(kClassName+"::createFile()", "Failed to create file (no base file system)");
}
// convert sub filesystem path to real path
tc::io::Path real_path;
subPathToRealPath(path, real_path);
// create file
mBaseFileSystem->createFile(real_path);
}
void tc::io::SubFileSystem::removeFile(const tc::io::Path& path)
{
if (mBaseFileSystem == nullptr)
{
throw tc::ObjectDisposedException(kClassName+"::removeFile()", "Failed to remove file (no base file system)");
}
// convert sub filesystem path to real path
tc::io::Path real_path;
subPathToRealPath(path, real_path);
// delete file
mBaseFileSystem->removeFile(real_path);
}
void tc::io::SubFileSystem::openFile(const tc::io::Path& path, tc::io::FileMode mode, tc::io::FileAccess access, std::shared_ptr<tc::io::IStream>& stream)
{
if (mBaseFileSystem == nullptr)
{
throw tc::ObjectDisposedException(kClassName+"::openFile()", "Failed to open file (no base file system)");
}
// convert sub filesystem path to real path
tc::io::Path real_path;
subPathToRealPath(path, real_path);
// open file
return mBaseFileSystem->openFile(real_path, mode, access, stream);
}
void tc::io::SubFileSystem::createDirectory(const tc::io::Path& path)
{
if (mBaseFileSystem == nullptr)
{
throw tc::ObjectDisposedException(kClassName+"::createDirectory()", "Failed to create directory (no base file system)");
}
// convert sub filesystem path to real path
tc::io::Path real_path;
subPathToRealPath(path, real_path);
// create directory
mBaseFileSystem->createDirectory(real_path);
}
void tc::io::SubFileSystem::removeDirectory(const tc::io::Path& path)
{
if (mBaseFileSystem == nullptr)
{
throw tc::ObjectDisposedException(kClassName+"::removeDirectory()", "Failed to remove directory (no base file system)");
}
// convert sub filesystem path to real path
tc::io::Path real_path;
subPathToRealPath(path, real_path);
// remove directory
mBaseFileSystem->removeDirectory(real_path);
}
void tc::io::SubFileSystem::getWorkingDirectory(tc::io::Path& path)
{
if (mBaseFileSystem == nullptr)
{
throw tc::ObjectDisposedException(kClassName+"::getWorkingDirectory()", "Failed to get current working directory (no base file system)");
}
path = mSubPathResolver.getCurrentDirectory();
}
void tc::io::SubFileSystem::setWorkingDirectory(const tc::io::Path& path)
{
if (mBaseFileSystem == nullptr)
{
throw tc::ObjectDisposedException(kClassName+"::setWorkingDirectory()", "Failed to set current working directory (no base file system)");
}
// convert sub filesystem path to real path
tc::io::Path canonical_base_path;
subPathToRealPath(path, canonical_base_path);
// save previous basefs working directory path
tc::io::Path prev_canonical_base_path;
mBaseFileSystem->getWorkingDirectory(prev_canonical_base_path);
// set and get working directory path so that canonical_base_path is populated with the full real path
mBaseFileSystem->setWorkingDirectory(canonical_base_path);
mBaseFileSystem->getWorkingDirectory(canonical_base_path);
// restore previous basefs working directory path
mBaseFileSystem->setWorkingDirectory(prev_canonical_base_path);
// save current directory
tc::io::Path canonical_sub_path;
realPathToSubPath(canonical_base_path, canonical_sub_path);
mSubPathResolver.setCurrentDirectory(canonical_sub_path);
}
void tc::io::SubFileSystem::getDirectoryListing(const tc::io::Path& path, sDirectoryListing& info)
{
if (mBaseFileSystem == nullptr)
{
throw tc::ObjectDisposedException(kClassName+"::getDirectoryListing()", "Failed to get directory listing (no base file system)");
}
// convert sub filesystem path to real path
tc::io::Path canonical_base_path;
subPathToRealPath(path, canonical_base_path);
// get real directory info
tc::io::sDirectoryListing dir_info;
mBaseFileSystem->getDirectoryListing(canonical_base_path, dir_info);
// convert directory absolute path
tc::io::Path canonical_sub_path;
realPathToSubPath(dir_info.abs_path, canonical_sub_path);
// update info with sub filesystem path
dir_info.abs_path = canonical_sub_path;
// write object to output
info = dir_info;
}
void tc::io::SubFileSystem::subPathToRealPath(const tc::io::Path& sub_path, tc::io::Path& real_path)
{
// get canonical sub path
tc::io::Path canonical_sub_path = mSubPathResolver.resolveCanonicalPath(sub_path);
// get canonical base path
real_path = mBasePathResolver.resolveCanonicalPath(canonical_sub_path.subpath(1, tc::io::Path::npos));
}
void tc::io::SubFileSystem::realPathToSubPath(const tc::io::Path& real_path, tc::io::Path& sub_path)
{
tc::io::Path canonical_base_path = mBasePathResolver.getCurrentDirectory();
if (real_path.subpath(0, canonical_base_path.size()) != canonical_base_path)
{
throw tc::UnauthorisedAccessException(kClassName, "Sub filesystem escape detected");
}
sub_path = tc::io::Path("/") + real_path.subpath(canonical_base_path.size(), tc::io::Path::npos);
}
@@ -0,0 +1,73 @@
#include <tc/io/SubSink.h>
#include <tc/io/IOUtil.h>
const std::string tc::io::SubSink::kClassName = "tc::io::SubSink";
tc::io::SubSink::SubSink() :
mBaseSink(nullptr),
mBaseSinkOffset(0),
mSubSinkLength(0)
{
}
tc::io::SubSink::SubSink(const std::shared_ptr<tc::io::ISink>& sink, int64_t offset, int64_t length) :
SubSink()
{
mBaseSink = sink;
// validate arguments
if (mBaseSink == nullptr)
{
throw tc::ArgumentNullException(kClassName, "The base sink is null.");
}
if (offset < 0)
{
throw tc::ArgumentOutOfRangeException(kClassName, "offset is negative");
}
if (length < 0)
{
throw tc::ArgumentOutOfRangeException(kClassName, "length is negative");
}
int64_t base_length = mBaseSink->length();
// validate arguments against sink length
// sub sink length should not be greater than the base sink length
if (length > base_length)
{
throw tc::ArgumentOutOfRangeException(kClassName, "The sub sink length is greater than base sink length.");
}
// Base length - length is the maximum possible offset for the sub sink
if (offset > (base_length - length))
{
throw tc::ArgumentOutOfRangeException(kClassName, "The sub sink offset is greater than the maximum possible offset given the base sink length and sub sink length.");
}
// set class state
mBaseSinkOffset = offset;
mSubSinkLength = length;
}
int64_t tc::io::SubSink::length()
{
return mBaseSink == nullptr ? 0 : mSubSinkLength;
}
void tc::io::SubSink::setLength(int64_t length)
{
throw tc::NotImplementedException(kClassName+"::setLength()", "setLength is not implemented for SubSink.");
}
size_t tc::io::SubSink::pushData(const tc::ByteData& data, int64_t offset)
{
if (mBaseSink == nullptr)
{
throw tc::ObjectDisposedException(kClassName+"::pushData()", "Failed to push data (no base sink)");
}
auto new_data = tc::ByteData(data.data(), IOUtil::getWritableCount(mSubSinkLength, offset, data.size()));
return mBaseSink->pushData(new_data, mBaseSinkOffset + offset);
}
@@ -0,0 +1,66 @@
#include <tc/io/SubSource.h>
#include <tc/io/IOUtil.h>
const std::string tc::io::SubSource::kClassName = "tc::io::SubSource";
tc::io::SubSource::SubSource() :
mBaseSource(nullptr),
mBaseSourceOffset(0),
mSubSourceLength(0)
{
}
tc::io::SubSource::SubSource(const std::shared_ptr<tc::io::ISource>& source, int64_t offset, int64_t length) :
SubSource()
{
mBaseSource = source;
// validate arguments
if (mBaseSource == nullptr)
{
throw tc::ArgumentNullException(kClassName, "source is null");
}
if (offset < 0)
{
throw tc::ArgumentOutOfRangeException(kClassName, "offset is negative");
}
if (length < 0)
{
throw tc::ArgumentOutOfRangeException(kClassName, "length is negative");
}
int64_t base_length = mBaseSource->length();
// validate arguments against source length
// sub source length should not be greater than the base source length
if (length > base_length)
{
throw tc::ArgumentOutOfRangeException(kClassName, "sub source length is greater than base source length");
}
// Base length - length is the maximum possible offset for the sub source
if (offset > (base_length - length))
{
throw tc::ArgumentOutOfRangeException(kClassName, "sub source offset is greater than the maximum possible offset given the base source size and sub source size");
}
// set class state
mBaseSourceOffset = offset;
mSubSourceLength = length;
}
int64_t tc::io::SubSource::length()
{
return mBaseSource == nullptr ? 0 : mSubSourceLength;
}
tc::ByteData tc::io::SubSource::pullData(int64_t offset, size_t count)
{
size_t pull_count = IOUtil::getReadableCount(length(), offset, count);
if (pull_count == 0)
return tc::ByteData();
return mBaseSource->pullData(mBaseSourceOffset + offset, pull_count);
}
@@ -0,0 +1,183 @@
#include <tc/io/SubStream.h>
#include <tc/io/IOUtil.h>
#include <tc/io/StreamUtil.h>
#include <algorithm>
const std::string tc::io::SubStream::kClassName = "tc::io::SubStream";
tc::io::SubStream::SubStream() :
mBaseStream(),
mBaseStreamOffset(0),
mSubStreamLength(0),
mSubStreamPosition(0)
{}
tc::io::SubStream::SubStream(const std::shared_ptr<tc::io::IStream>& stream, int64_t offset, int64_t length) :
SubStream()
{
// copy stream
mBaseStream = stream;
// validate the stream exists
if (mBaseStream == nullptr)
{
throw tc::ArgumentNullException(kClassName, "stream is null");
}
// check if the stream supports seeking
if (mBaseStream->canSeek() == false)
{
tc::NotSupportedException(kClassName, "Streams that do not support seeking are not supported");
}
// validate arguments
if (offset < 0)
{
throw tc::ArgumentOutOfRangeException(kClassName, "offset is negative");
}
if (length < 0)
{
throw tc::ArgumentOutOfRangeException(kClassName, "length is negative");
}
int64_t base_length = mBaseStream->length();
// validate arguments against stream length
// substream length should not be greater than the base stream length
if (length > base_length)
{
throw tc::ArgumentOutOfRangeException(kClassName, "SubStream length is greater than base stream length");
}
// Base length - length is the maximum possible offset for the substream
if (offset > (base_length - length))
{
throw tc::ArgumentOutOfRangeException(kClassName, "SubStream offset is greater than the maximum possible offset given the base stream size and SubStream size");
}
// set class state
mBaseStreamOffset = offset;
mSubStreamLength = length;
mSubStreamPosition = 0;
}
bool tc::io::SubStream::canRead() const
{
return mBaseStream == nullptr ? false : mBaseStream->canRead();
}
bool tc::io::SubStream::canWrite() const
{
return mBaseStream == nullptr ? false : mBaseStream->canWrite();
}
bool tc::io::SubStream::canSeek() const
{
return mBaseStream == nullptr ? false : mBaseStream->canSeek();
}
int64_t tc::io::SubStream::length()
{
return mBaseStream == nullptr ? 0 : mSubStreamLength;
}
int64_t tc::io::SubStream::position()
{
return mBaseStream == nullptr ? 0 : mSubStreamPosition;
}
size_t tc::io::SubStream::read(byte_t* ptr, size_t count)
{
if (mBaseStream == nullptr)
{
throw tc::ObjectDisposedException(kClassName+"::read()", "Failed to read from stream (stream is disposed)");
}
count = IOUtil::getReadableCount(mSubStreamLength, mSubStreamPosition, count);
// assert proper position in file
mBaseStream->seek(mBaseStreamOffset + mSubStreamPosition, SeekOrigin::Begin);
// read data
size_t data_read_size = mBaseStream->read(ptr, count);
// update sub stream position
seek(data_read_size, SeekOrigin::Current);
return data_read_size;
}
size_t tc::io::SubStream::write(const byte_t* ptr, size_t count)
{
if (mBaseStream == nullptr)
{
throw tc::ObjectDisposedException(kClassName+"::write()", "Failed to write to stream (stream is disposed)");
}
count = IOUtil::getWritableCount(mSubStreamLength, mSubStreamPosition, count);
// assert proper position in file
mBaseStream->seek(mBaseStreamOffset + mSubStreamPosition, SeekOrigin::Begin);
// write data
size_t data_written_size = mBaseStream->write(ptr, count);
// update sub stream position
seek(data_written_size, SeekOrigin::Current);
return data_written_size;
}
int64_t tc::io::SubStream::seek(int64_t offset, SeekOrigin origin)
{
if (mBaseStream == nullptr)
{
throw tc::ObjectDisposedException(kClassName+"::seek()", "Failed to set stream position (stream is disposed)");
}
mSubStreamPosition = StreamUtil::getSeekResult(offset, origin, mSubStreamPosition, mSubStreamLength);
if (mSubStreamPosition < 0)
{
throw tc::InvalidOperationException(kClassName+"::seek()", "Negative seek result determined");
}
return mSubStreamPosition;
}
void tc::io::SubStream::setLength(int64_t length)
{
if (mBaseStream == nullptr)
{
throw tc::ObjectDisposedException(kClassName+"::setLength()", "Failed to set stream length (stream is disposed)");
}
throw tc::NotImplementedException(kClassName+"::setLength()", "setLength is not implemented for SubStream");
}
void tc::io::SubStream::flush()
{
if (mBaseStream == nullptr)
{
throw tc::ObjectDisposedException(kClassName+"::seek()", "Failed to flush stream (stream is disposed)");
}
mBaseStream->flush();
}
void tc::io::SubStream::dispose()
{
if (mBaseStream.get() != nullptr)
{
// dispose base stream
mBaseStream->dispose();
// release ptr
mBaseStream.reset();
}
// clear state
mBaseStreamOffset = 0;
mSubStreamLength = 0;
mSubStreamPosition = 0;
}
@@ -0,0 +1,192 @@
#include <tc/io/VirtualFileSystem.h>
const std::string tc::io::VirtualFileSystem::kClassName = "tc::io::VirtualFileSystem";
tc::io::VirtualFileSystem::VirtualFileSystem() :
mCurDir(nullptr),
mFsSnapshot(),
mPathResolver()
{
}
tc::io::VirtualFileSystem::VirtualFileSystem(const FileSystemSnapshot& fs_snapshot, const std::shared_ptr<tc::io::IPortablePathResolver>& path_resolver) :
VirtualFileSystem()
{
mFsSnapshot = fs_snapshot;
mPathResolver = path_resolver;
// Use default path resolver if none was provided
if (mPathResolver == nullptr)
{
mPathResolver = std::shared_ptr<tc::io::BasicPathResolver>(new tc::io::BasicPathResolver());
}
// get root directory
tc::io::Path canonical_root_path = mPathResolver->resolveCanonicalPath(tc::io::Path("/"));
auto root_itr = mFsSnapshot.dir_entry_path_map.find(canonical_root_path);
// if the path was not found in the map, throw exception
if (root_itr == mFsSnapshot.dir_entry_path_map.end())
{
throw tc::InvalidOperationException(kClassName, "Failed to located root directory");
}
// if the dir_entry index isn't valid, throw exception
if (root_itr->second >= mFsSnapshot.dir_entries.size())
{
throw tc::InvalidOperationException(kClassName, "Failed to located root directory");
}
mCurDir = &mFsSnapshot.dir_entries.at(root_itr->second);
}
tc::ResourceStatus tc::io::VirtualFileSystem::state()
{
return mCurDir == nullptr? tc::ResourceStatus(1 << tc::RESFLAG_NOINIT) : tc::ResourceStatus(1 << tc::RESFLAG_READY);
}
void tc::io::VirtualFileSystem::dispose()
{
mCurDir = nullptr;
mFsSnapshot.dir_entries.clear();
mFsSnapshot.file_entries.clear();
mFsSnapshot.dir_entry_path_map.clear();
mFsSnapshot.file_entry_path_map.clear();
mPathResolver.reset();
}
void tc::io::VirtualFileSystem::createFile(const tc::io::Path& path)
{
if (mCurDir == nullptr)
{
throw tc::ObjectDisposedException(kClassName+"::createFile()", "VirtualFileSystem not initialized");
}
throw tc::NotImplementedException(kClassName+"::createFile()", "createFile is not supported for VirtualFileSystem");
}
void tc::io::VirtualFileSystem::removeFile(const tc::io::Path& path)
{
if (mCurDir == nullptr)
{
throw tc::ObjectDisposedException(kClassName+"::removeFile()", "VirtualFileSystem not initialized");
}
throw tc::NotImplementedException(kClassName+"::removeFile()", "removeFile is not supported for VirtualFileSystem");
}
void tc::io::VirtualFileSystem::openFile(const tc::io::Path& path, tc::io::FileMode mode, tc::io::FileAccess access, std::shared_ptr<tc::io::IStream>& stream)
{
if (mCurDir == nullptr)
{
throw tc::ObjectDisposedException(kClassName+"::openFile()", "VirtualFileSystem not initialized");
}
tc::io::Path resolved_path = mPathResolver->resolveCanonicalPath(path);
if (mode != tc::io::FileMode::Open)
{
throw tc::NotSupportedException(kClassName+"::openFile()", "This file-system is read-only, only FileMode::Open is supported.");
}
if (access != tc::io::FileAccess::Read)
{
throw tc::NotSupportedException(kClassName+"::openFile()", "This file-system is read-only, only FileAccess::Read is supported.");
}
auto file_itr = mFsSnapshot.file_entry_path_map.find(resolved_path);
// if resolved_path does not exist in the map, throw exception
if (file_itr == mFsSnapshot.file_entry_path_map.end())
{
throw tc::io::FileNotFoundException(kClassName+"::openFile()", "File does not exist.");
}
// if the file_entry index isn't valid or leads to a null IStream pointer, throw exception
if (file_itr->second >= mFsSnapshot.file_entries.size() || mFsSnapshot.file_entries.at(file_itr->second).stream == nullptr)
{
throw tc::io::FileNotFoundException(kClassName+"::openFile()", "File does not exist.");
}
// if the stream has invalid properties, throw exception
if ( !(mFsSnapshot.file_entries.at(file_itr->second).stream->canRead() == true && mFsSnapshot.file_entries.at(file_itr->second).stream->canWrite() == false) )
{
throw tc::io::FileNotFoundException(kClassName+"::openFile()", "File does not exist.");
}
stream = mFsSnapshot.file_entries.at(file_itr->second).stream;
}
void tc::io::VirtualFileSystem::createDirectory(const tc::io::Path& path)
{
if (mCurDir == nullptr)
{
throw tc::ObjectDisposedException(kClassName+"::createDirectory()", "VirtualFileSystem not initialized");
}
throw tc::NotImplementedException(kClassName+"::createDirectory()", "createDirectory is not supported for VirtualFileSystem");
}
void tc::io::VirtualFileSystem::removeDirectory(const tc::io::Path& path)
{
if (mCurDir == nullptr)
{
throw tc::ObjectDisposedException(kClassName+"::removeDirectory()", "VirtualFileSystem not initialized");
}
throw tc::NotImplementedException(kClassName+"::removeDirectory()", "removeDirectory is not supported for VirtualFileSystem");
}
void tc::io::VirtualFileSystem::getWorkingDirectory(tc::io::Path& path)
{
if (mCurDir == nullptr)
{
throw tc::ObjectDisposedException(kClassName+"::getWorkingDirectory()", "VirtualFileSystem not initialized");
}
path = mCurDir->dir_listing.abs_path;
}
void tc::io::VirtualFileSystem::setWorkingDirectory(const tc::io::Path& path)
{
if (mCurDir == nullptr)
{
throw tc::ObjectDisposedException(kClassName+"::setWorkingDirectory()", "VirtualFileSystem not initialized");
}
tc::io::Path resolved_path = mPathResolver->resolveCanonicalPath(path);
auto dir_itr = mFsSnapshot.dir_entry_path_map.find(resolved_path);
// if the path was not found in the map, throw exception
if (dir_itr == mFsSnapshot.dir_entry_path_map.end())
{
throw tc::io::DirectoryNotFoundException(kClassName+"::setWorkingDirectory()", "Directory does not exist.");
}
// if the dir_entry index isn't valid, throw exception
if (dir_itr->second >= mFsSnapshot.dir_entries.size())
{
throw tc::io::DirectoryNotFoundException(kClassName+"::setWorkingDirectory()", "Directory does not exist.");
}
mCurDir = &mFsSnapshot.dir_entries.at(dir_itr->second);
mPathResolver->setCurrentDirectory(mCurDir->dir_listing.abs_path);
}
void tc::io::VirtualFileSystem::getDirectoryListing(const tc::io::Path& path, tc::io::sDirectoryListing& info)
{
if (mCurDir == nullptr)
{
throw tc::ObjectDisposedException(kClassName+"::getDirectoryListing()", "VirtualFileSystem not initialized");
}
tc::io::Path resolved_path = mPathResolver->resolveCanonicalPath(path);
auto dir_itr = mFsSnapshot.dir_entry_path_map.find(resolved_path);
// if the path was not found in the map, throw exception
if (dir_itr == mFsSnapshot.dir_entry_path_map.end())
{
throw tc::io::DirectoryNotFoundException(kClassName+"::getDirectoryListing()", "Directory does not exist.");
}
// if the dir_entry index isn't valid, throw exception
if (dir_itr->second >= mFsSnapshot.dir_entries.size())
{
throw tc::io::DirectoryNotFoundException(kClassName+"::getDirectoryListing()", "Directory does not exist.");
}
info = mFsSnapshot.dir_entries.at(dir_itr->second).dir_listing;
}