cog/Frameworks/OpenMPT/OpenMPT/src/mpt/io_read/filecursor.hpp

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