/* 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 #include #include #include 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 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 explicit FileCursor(mpt::span bytedata, shared_filename_type filename = shared_filename_type{}) : m_data(DataInitializer(mpt::byte_cast(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 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 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(); } 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 MPT_FILECURSOR_DEPRECATED mpt::span GetRawData() const { // deprecated because in case of an unseekable std::istream, this triggers caching of the whole file return mpt::span(mpt::byte_cast(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 Tspan GetRawWithOffset(std::size_t offset, Tspan dst) const { return mpt::byte_cast(DataContainer().Read(streamPos + offset, mpt::byte_cast(dst))); } mpt::byte_span GetRaw(mpt::byte_span dst) const { return DataContainer().Read(streamPos, dst); } template Tspan GetRaw(Tspan dst) const { return mpt::byte_cast(DataContainer().Read(streamPos, mpt::byte_cast(dst))); } mpt::byte_span ReadRaw(mpt::byte_span dst) { mpt::byte_span result = DataContainer().Read(streamPos, dst); streamPos += result.size(); return result; } template Tspan ReadRaw(Tspan dst) { Tspan result = mpt::byte_cast(DataContainer().Read(streamPos, mpt::byte_cast(dst))); streamPos += result.size(); return result; } std::vector GetRawDataAsByteVector() const { PinnedView view = GetPinnedView(); return mpt::make_vector(view.span()); } std::vector ReadRawDataAsByteVector() { PinnedView view = ReadPinnedView(); return mpt::make_vector(view.span()); } std::vector GetRawDataAsByteVector(std::size_t size) const { PinnedView view = GetPinnedView(size); return mpt::make_vector(view.span()); } std::vector 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