419 lines
12 KiB
C++
419 lines
12 KiB
C++
/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
|
|
|
|
#ifndef MPT_IO_READ_FILECURSOR_HPP
|
|
#define MPT_IO_READ_FILECURSOR_HPP
|
|
|
|
|
|
|
|
#include "mpt/base/alloc.hpp"
|
|
#include "mpt/base/memory.hpp"
|
|
#include "mpt/base/namespace.hpp"
|
|
#include "mpt/base/span.hpp"
|
|
|
|
#include <algorithm>
|
|
#include <optional>
|
|
#include <vector>
|
|
|
|
#include <cstddef>
|
|
|
|
|
|
|
|
namespace mpt {
|
|
inline namespace MPT_INLINE_NS {
|
|
|
|
|
|
|
|
namespace IO {
|
|
|
|
|
|
|
|
// change to show warnings for functions which trigger pre-caching the whole file for unseekable streams
|
|
//#define MPT_FILECURSOR_DEPRECATED [[deprecated]]
|
|
#define MPT_FILECURSOR_DEPRECATED
|
|
|
|
|
|
|
|
template <typename Ttraits, typename Tfilenametraits>
|
|
class FileCursor {
|
|
|
|
private:
|
|
using traits_type = Ttraits;
|
|
using filename_traits_type = Tfilenametraits;
|
|
|
|
public:
|
|
using pos_type = typename traits_type::pos_type;
|
|
|
|
using data_type = typename traits_type::data_type;
|
|
using ref_data_type = typename traits_type::ref_data_type;
|
|
using shared_data_type = typename traits_type::shared_data_type;
|
|
using value_data_type = typename traits_type::value_data_type;
|
|
|
|
using filename_type = typename filename_traits_type::filename_type;
|
|
using shared_filename_type = typename filename_traits_type::shared_filename_type;
|
|
|
|
protected:
|
|
shared_data_type SharedDataContainer() const {
|
|
return traits_type::get_shared(m_data);
|
|
}
|
|
ref_data_type DataContainer() const {
|
|
return traits_type::get_ref(m_data);
|
|
}
|
|
|
|
static value_data_type DataInitializer() {
|
|
return traits_type::make_data();
|
|
}
|
|
static value_data_type DataInitializer(mpt::const_byte_span data) {
|
|
return traits_type::make_data(data);
|
|
}
|
|
|
|
static value_data_type CreateChunkImpl(shared_data_type data, pos_type position, pos_type size) {
|
|
return traits_type::make_chunk(data, position, size);
|
|
}
|
|
|
|
private:
|
|
data_type m_data;
|
|
|
|
pos_type streamPos; // Cursor location in the file
|
|
|
|
shared_filename_type m_fileName; // Filename that corresponds to this FileCursor. It is only set if this FileCursor represents the whole contents of fileName. May be nullopt.
|
|
|
|
public:
|
|
// Initialize invalid file reader object.
|
|
FileCursor()
|
|
: m_data(DataInitializer())
|
|
, streamPos(0)
|
|
, m_fileName(nullptr) {
|
|
return;
|
|
}
|
|
|
|
// Initialize file reader object with pointer to data and data length.
|
|
template <typename Tbyte>
|
|
explicit FileCursor(mpt::span<Tbyte> bytedata, shared_filename_type filename = shared_filename_type{})
|
|
: m_data(DataInitializer(mpt::byte_cast<mpt::const_byte_span>(bytedata)))
|
|
, streamPos(0)
|
|
, m_fileName(std::move(filename)) {
|
|
return;
|
|
}
|
|
|
|
// Initialize file reader object based on an existing file reader object window.
|
|
explicit FileCursor(value_data_type other, shared_filename_type filename = shared_filename_type{})
|
|
: m_data(std::move(other))
|
|
, streamPos(0)
|
|
, m_fileName(std::move(filename)) {
|
|
return;
|
|
}
|
|
|
|
public:
|
|
std::optional<filename_type> GetOptionalFileName() const {
|
|
return filename_traits_type::get_optional_filename(m_fileName);
|
|
}
|
|
|
|
// Returns true if the object points to a valid (non-empty) stream.
|
|
operator bool() const {
|
|
return IsValid();
|
|
}
|
|
|
|
// Returns true if the object points to a valid (non-empty) stream.
|
|
bool IsValid() const {
|
|
return DataContainer().IsValid();
|
|
}
|
|
|
|
// Reset cursor to first byte in file.
|
|
void Rewind() {
|
|
streamPos = 0;
|
|
}
|
|
|
|
// Seek to a position in the mapped file.
|
|
// Returns false if position is invalid.
|
|
bool Seek(pos_type position) {
|
|
if (position <= streamPos) {
|
|
streamPos = position;
|
|
return true;
|
|
}
|
|
if (DataContainer().CanRead(0, position)) {
|
|
streamPos = position;
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Increases position by skipBytes.
|
|
// Returns true if skipBytes could be skipped or false if the file end was reached earlier.
|
|
bool Skip(pos_type skipBytes) {
|
|
if (CanRead(skipBytes)) {
|
|
streamPos += skipBytes;
|
|
return true;
|
|
} else {
|
|
streamPos = DataContainer().GetLength();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Decreases position by skipBytes.
|
|
// Returns true if skipBytes could be skipped or false if the file start was reached earlier.
|
|
bool SkipBack(pos_type skipBytes) {
|
|
if (streamPos >= skipBytes) {
|
|
streamPos -= skipBytes;
|
|
return true;
|
|
} else {
|
|
streamPos = 0;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Returns cursor position in the mapped file.
|
|
pos_type GetPosition() const {
|
|
return streamPos;
|
|
}
|
|
|
|
// Return true IFF seeking and GetLength() is fast.
|
|
// In particular, it returns false for unseekable stream where GetLength()
|
|
// requires pre-caching.
|
|
bool HasFastGetLength() const {
|
|
return DataContainer().HasFastGetLength();
|
|
}
|
|
|
|
// Returns size of the mapped file in bytes.
|
|
MPT_FILECURSOR_DEPRECATED pos_type GetLength() const {
|
|
// deprecated because in case of an unseekable std::istream, this triggers caching of the whole file
|
|
return DataContainer().GetLength();
|
|
}
|
|
|
|
// Return byte count between cursor position and end of file, i.e. how many bytes can still be read.
|
|
MPT_FILECURSOR_DEPRECATED pos_type BytesLeft() const {
|
|
// deprecated because in case of an unseekable std::istream, this triggers caching of the whole file
|
|
return DataContainer().GetLength() - streamPos;
|
|
}
|
|
|
|
bool EndOfFile() const {
|
|
return !CanRead(1);
|
|
}
|
|
|
|
bool NoBytesLeft() const {
|
|
return !CanRead(1);
|
|
}
|
|
|
|
// Check if "amount" bytes can be read from the current position in the stream.
|
|
bool CanRead(pos_type amount) const {
|
|
return DataContainer().CanRead(streamPos, amount);
|
|
}
|
|
|
|
// Check if file size is at least size, without potentially caching the whole file to query the exact file length.
|
|
bool LengthIsAtLeast(pos_type size) const {
|
|
return DataContainer().CanRead(0, size);
|
|
}
|
|
|
|
// Check if file size is exactly size, without potentially caching the whole file if it is larger.
|
|
bool LengthIs(pos_type size) const {
|
|
return DataContainer().CanRead(0, size) && !DataContainer().CanRead(size, 1);
|
|
}
|
|
|
|
protected:
|
|
FileCursor CreateChunk(pos_type position, pos_type length) const {
|
|
pos_type readableLength = DataContainer().GetReadableLength(position, length);
|
|
if (readableLength == 0) {
|
|
return FileCursor();
|
|
}
|
|
return FileCursor(CreateChunkImpl(SharedDataContainer(), position, std::min(length, DataContainer().GetLength() - position)));
|
|
}
|
|
|
|
public:
|
|
// Create a new FileCursor object for parsing a sub chunk at a given position with a given length.
|
|
// The file cursor is not modified.
|
|
FileCursor GetChunkAt(pos_type position, pos_type length) const {
|
|
return CreateChunk(position, length);
|
|
}
|
|
|
|
// Create a new FileCursor object for parsing a sub chunk at the current position with a given length.
|
|
// The file cursor is not advanced.
|
|
FileCursor GetChunk(pos_type length) {
|
|
return CreateChunk(streamPos, length);
|
|
}
|
|
// Create a new FileCursor object for parsing a sub chunk at the current position with a given length.
|
|
// The file cursor is advanced by "length" bytes.
|
|
FileCursor ReadChunk(pos_type length) {
|
|
pos_type position = streamPos;
|
|
Skip(length);
|
|
return CreateChunk(position, length);
|
|
}
|
|
|
|
class PinnedView {
|
|
private:
|
|
std::size_t size_;
|
|
const std::byte * pinnedData;
|
|
std::vector<std::byte> cache;
|
|
|
|
private:
|
|
void Init(const FileCursor & file, std::size_t size) {
|
|
size_ = 0;
|
|
pinnedData = nullptr;
|
|
if (!file.CanRead(size)) {
|
|
size = file.BytesLeft();
|
|
}
|
|
size_ = size;
|
|
if (file.DataContainer().HasPinnedView()) {
|
|
pinnedData = file.DataContainer().GetRawData() + file.GetPosition();
|
|
} else {
|
|
cache.resize(size_);
|
|
if (!cache.empty()) {
|
|
file.GetRaw(mpt::as_span(cache));
|
|
}
|
|
}
|
|
}
|
|
|
|
public:
|
|
PinnedView()
|
|
: size_(0)
|
|
, pinnedData(nullptr) {
|
|
}
|
|
PinnedView(const FileCursor & file) {
|
|
Init(file, file.BytesLeft());
|
|
}
|
|
PinnedView(const FileCursor & file, std::size_t size) {
|
|
Init(file, size);
|
|
}
|
|
PinnedView(FileCursor & file, bool advance) {
|
|
Init(file, file.BytesLeft());
|
|
if (advance)
|
|
{
|
|
file.Skip(size_);
|
|
}
|
|
}
|
|
PinnedView(FileCursor & file, std::size_t size, bool advance) {
|
|
Init(file, size);
|
|
if (advance) {
|
|
file.Skip(size_);
|
|
}
|
|
}
|
|
|
|
public:
|
|
mpt::const_byte_span GetSpan() const {
|
|
if (pinnedData) {
|
|
return mpt::as_span(pinnedData, size_);
|
|
} else if (!cache.empty()) {
|
|
return mpt::as_span(cache);
|
|
} else {
|
|
return mpt::const_byte_span();
|
|
}
|
|
}
|
|
mpt::const_byte_span span() const {
|
|
return GetSpan();
|
|
}
|
|
void invalidate() {
|
|
size_ = 0;
|
|
pinnedData = nullptr;
|
|
cache = std::vector<std::byte>();
|
|
}
|
|
const std::byte * data() const {
|
|
return span().data();
|
|
}
|
|
std::size_t size() const {
|
|
return size_;
|
|
}
|
|
mpt::const_byte_span::pointer begin() const {
|
|
return span().data();
|
|
}
|
|
mpt::const_byte_span::pointer end() const {
|
|
return span().data() + span().size();
|
|
}
|
|
mpt::const_byte_span::const_pointer cbegin() const {
|
|
return span().data();
|
|
}
|
|
mpt::const_byte_span::const_pointer cend() const {
|
|
return span().data() + span().size();
|
|
}
|
|
};
|
|
|
|
// Returns a pinned view into the remaining raw data from cursor position.
|
|
PinnedView GetPinnedView() const {
|
|
return PinnedView(*this);
|
|
}
|
|
// Returns a pinned view into the remeining raw data from cursor position, clamped at size.
|
|
PinnedView GetPinnedView(std::size_t size) const {
|
|
return PinnedView(*this, size);
|
|
}
|
|
|
|
// Returns a pinned view into the remeining raw data from cursor position.
|
|
// File cursor is advaned by the size of the returned pinned view.
|
|
PinnedView ReadPinnedView() {
|
|
return PinnedView(*this, true);
|
|
}
|
|
// Returns a pinned view into the remeining raw data from cursor position, clamped at size.
|
|
// File cursor is advaned by the size of the returned pinned view.
|
|
PinnedView ReadPinnedView(std::size_t size) {
|
|
return PinnedView(*this, size, true);
|
|
}
|
|
|
|
// Returns raw stream data at cursor position.
|
|
// Should only be used if absolutely necessary, for example for sample reading, or when used with a small chunk of the file retrieved by ReadChunk().
|
|
// Use GetPinnedView(size) whenever possible.
|
|
MPT_FILECURSOR_DEPRECATED mpt::const_byte_span GetRawData() const {
|
|
// deprecated because in case of an unseekable std::istream, this triggers caching of the whole file
|
|
return mpt::span(DataContainer().GetRawData() + streamPos, DataContainer().GetLength() - streamPos);
|
|
}
|
|
template <typename T>
|
|
MPT_FILECURSOR_DEPRECATED mpt::span<const T> GetRawData() const {
|
|
// deprecated because in case of an unseekable std::istream, this triggers caching of the whole file
|
|
return mpt::span(mpt::byte_cast<const T *>(DataContainer().GetRawData() + streamPos), DataContainer().GetLength() - streamPos);
|
|
}
|
|
|
|
mpt::byte_span GetRawWithOffset(std::size_t offset, mpt::byte_span dst) const {
|
|
return DataContainer().Read(streamPos + offset, dst);
|
|
}
|
|
template <typename Tspan>
|
|
Tspan GetRawWithOffset(std::size_t offset, Tspan dst) const {
|
|
return mpt::byte_cast<Tspan>(DataContainer().Read(streamPos + offset, mpt::byte_cast<mpt::byte_span>(dst)));
|
|
}
|
|
|
|
mpt::byte_span GetRaw(mpt::byte_span dst) const {
|
|
return DataContainer().Read(streamPos, dst);
|
|
}
|
|
template <typename Tspan>
|
|
Tspan GetRaw(Tspan dst) const {
|
|
return mpt::byte_cast<Tspan>(DataContainer().Read(streamPos, mpt::byte_cast<mpt::byte_span>(dst)));
|
|
}
|
|
|
|
mpt::byte_span ReadRaw(mpt::byte_span dst) {
|
|
mpt::byte_span result = DataContainer().Read(streamPos, dst);
|
|
streamPos += result.size();
|
|
return result;
|
|
}
|
|
template <typename Tspan>
|
|
Tspan ReadRaw(Tspan dst) {
|
|
Tspan result = mpt::byte_cast<Tspan>(DataContainer().Read(streamPos, mpt::byte_cast<mpt::byte_span>(dst)));
|
|
streamPos += result.size();
|
|
return result;
|
|
}
|
|
|
|
std::vector<std::byte> GetRawDataAsByteVector() const {
|
|
PinnedView view = GetPinnedView();
|
|
return mpt::make_vector(view.span());
|
|
}
|
|
std::vector<std::byte> ReadRawDataAsByteVector() {
|
|
PinnedView view = ReadPinnedView();
|
|
return mpt::make_vector(view.span());
|
|
}
|
|
std::vector<std::byte> GetRawDataAsByteVector(std::size_t size) const {
|
|
PinnedView view = GetPinnedView(size);
|
|
return mpt::make_vector(view.span());
|
|
}
|
|
std::vector<std::byte> ReadRawDataAsByteVector(std::size_t size) {
|
|
PinnedView view = ReadPinnedView(size);
|
|
return mpt::make_vector(view.span());
|
|
}
|
|
};
|
|
|
|
|
|
|
|
} // namespace IO
|
|
|
|
|
|
|
|
} // namespace MPT_INLINE_NS
|
|
} // namespace mpt
|
|
|
|
|
|
|
|
#endif // MPT_IO_READ_FILECURSOR_HPP
|