mirror of
https://github.com/DarkStore-3DS/Project_CTR.git
synced 2026-07-04 16:59:02 +00:00
Put everything back.
This commit is contained in:
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user