Initial QFileBuffer implementation

This commit is contained in:
Dan Paulat 2023-01-01 01:33:24 -06:00
parent 779d03c576
commit 08965aa7f3
3 changed files with 501 additions and 2 deletions

View file

@ -0,0 +1,306 @@
#include <scwx/qt/util/q_file_buffer.hpp>
#include <scwx/util/logger.hpp>
#include <QFile>
namespace scwx
{
namespace qt
{
namespace util
{
static const std::string logPrefix_ = "scwx::qt::util::q_file_buffer";
static const auto logger_ = scwx::util::Logger::Create(logPrefix_);
// Adapted from Microsoft filebuf reference implementation
// Copyright (c) Microsoft Corporation.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
class QFileBuffer::Impl
{
public:
explicit Impl(QFileBuffer* self) : self_ {self} {};
~Impl() = default;
void ResetPutback();
void SetPutback();
QFileBuffer* self_;
QFile file_ {};
char_type putbackChar_ {};
char_type* putbackEback_ {nullptr};
char_type* putbackEgptr_ {nullptr};
};
void QFileBuffer::Impl::ResetPutback()
{
if (self_->eback() == &putbackChar_)
{
// Restore get buffer after putback
self_->setg(putbackEback_, putbackEback_, putbackEgptr_);
}
}
void QFileBuffer::Impl::SetPutback()
{
if (self_->eback() != &putbackChar_)
{
// Save current get buffer
putbackEback_ = self_->eback();
putbackEgptr_ = self_->egptr();
}
self_->setg(&putbackChar_, &putbackChar_, &putbackChar_ + 1);
}
QFileBuffer::QFileBuffer() : std::streambuf(), p(std::make_unique<Impl>(this))
{
// Initialize read/write pointers
setg(0, 0, 0);
setp(0, 0);
}
QFileBuffer::QFileBuffer(const std::string& filename,
std::ios_base::openmode mode) :
QFileBuffer()
{
open(filename, mode);
}
QFileBuffer::~QFileBuffer() = default;
QFileBuffer::QFileBuffer(QFileBuffer&&) noexcept = default;
QFileBuffer& QFileBuffer::operator=(QFileBuffer&&) noexcept = default;
bool QFileBuffer::is_open() const
{
return p->file_.isOpen();
}
QFileBuffer* QFileBuffer::open(const std::string& filename,
std::ios_base::openmode mode)
{
// If the associated file is already open, return a null pointer right away
if (is_open())
{
return nullptr;
}
// Validate supported modes
if (mode & std::ios_base::out || mode & std::ios_base::app ||
mode & std::ios_base::trunc)
{
logger_->error("open(): write mode not supported");
return nullptr;
}
// Convert std iostream flags to Qt flags
QIODeviceBase::OpenMode flags {};
if (mode & std::ios_base::in)
{
flags |= QIODeviceBase::OpenModeFlag::ReadOnly;
}
if ((mode & std::ios_base::binary) != std::ios_base::binary)
{
flags |= QIODeviceBase::OpenModeFlag::Text;
}
// Set the filename and open the file
p->file_.setFileName(QString::fromStdString(filename));
bool isOpen = p->file_.open(flags);
if (isOpen)
{
// Seek to end if requested
if (mode & std::ios_base::ate)
{
// Seek to end
p->file_.seek(p->file_.size());
}
// Initialize read/write pointers
setg(0, 0, 0);
setp(0, 0);
}
return isOpen ? this : nullptr;
}
QFileBuffer* QFileBuffer::close()
{
// If the associated file is already closed, return a null pointer right away
if (!p->file_.isOpen())
{
return nullptr;
}
// Close the file
p->file_.close();
return this;
}
QFileBuffer::int_type QFileBuffer::pbackfail(int_type c)
{
if (gptr() && eback() < gptr() &&
(traits_type::eq_int_type(traits_type::eof(), c) ||
traits_type::eq_int_type(traits_type::to_int_type(gptr()[-1]), c)))
{
// Just back up position
gbump(static_cast<int>(sizeof(char_type)) * -1);
return traits_type::not_eof(c);
}
else if (!is_open() || traits_type::eq_int_type(traits_type::eof(), c))
{
// No open QFile or EOF, fail
return traits_type::eof();
}
else if (gptr() != &p->putbackChar_)
{
// Put back to buffer
p->putbackChar_ = traits_type::to_char_type(c);
p->SetPutback();
return c;
}
else
{
// Nowhere to put back
return traits_type::eof();
}
}
QFileBuffer::pos_type QFileBuffer::seekoff(off_type off,
std::ios_base::seekdir dir,
std::ios_base::openmode /* which */)
{
pos_type newPos {pos_type(off_type(-1))};
switch (dir)
{
case std::ios_base::beg:
// Seek using absolute position
newPos = seekpos(off, std::ios_base::in);
break;
case std::ios_base::cur:
{
const pos_type currentPos {p->file_.pos() - (egptr() - gptr())};
pos_type updatePos {currentPos + off};
// If the putback buffer is not empty, decrement the offset
if (gptr() == &p->putbackChar_)
{
updatePos -= static_cast<off_type>(sizeof(char_type));
}
// Seek the file
if (p->file_.seek(updatePos))
{
// Record updated position
newPos = updatePos;
}
break;
}
case std::ios_base::end:
{
const pos_type endPos {p->file_.size()};
const pos_type updatePos {endPos + off};
// Seek the file
if (p->file_.seek(updatePos))
{
// Record updated position
newPos = updatePos;
}
break;
}
}
if (newPos != static_cast<off_type>(-1))
{
p->ResetPutback();
}
return newPos;
}
QFileBuffer::pos_type QFileBuffer::seekpos(pos_type pos,
std::ios_base::openmode /* which */)
{
pos_type newPos {pos_type(off_type(-1))};
// Seek the file
if (p->file_.seek(pos))
{
// Record updated position
newPos = pos;
p->ResetPutback();
}
return newPos;
}
QFileBuffer::int_type QFileBuffer::underflow()
{
// This function is only called if gptr() == nullptr or gptr() > egptr()
// (i.e., all buffer data has been read)
int_type c;
if (gptr() && gptr() < egptr())
{
// Return buffered
return traits_type::to_int_type(*gptr());
}
else if (traits_type::eq_int_type(traits_type::eof(), c = uflow()))
{
// uflow failed, return EOF
return c;
}
else
{
// Get a character, don't point past it
pbackfail(c);
return c;
}
}
QFileBuffer::int_type QFileBuffer::uflow()
{
if (gptr() && gptr() < egptr())
{
// Return buffered
int_type c = traits_type::to_int_type(*gptr());
gbump(sizeof(char_type));
return c;
}
if (!p->file_.isOpen())
{
// No open QFile, fail
return traits_type::eof();
}
p->ResetPutback();
// Read the next character
char_type c;
return p->file_.read(reinterpret_cast<char*>(&c), sizeof(char_type)) > 0 ?
traits_type::to_int_type(c) :
traits_type::eof();
}
std::streamsize QFileBuffer::xsgetn(char_type* s, std::streamsize count)
{
// Read up to count bytes, forwarding the return value from QFile::read
// (return negative values as zero)
return std::max<std::streamsize>(
p->file_.read(reinterpret_cast<char*>(s), count * sizeof(char_type)), 0);
}
} // namespace util
} // namespace qt
} // namespace scwx

View file

@ -0,0 +1,191 @@
#pragma once
#include <streambuf>
namespace scwx
{
namespace qt
{
namespace util
{
/**
* The QFileBuffer class is a std::streambuf interface to a QFile, allowing use
* with C++ stream-based I/O.
*
* QFileBuffer has read-only support. Locales are ignored, and no conversions
* are performed.
*
* Documentation for functions derived from
* https://en.cppreference.com/ SPDX-License-Identifier: CC BY-SA 3.0
*/
class QFileBuffer : public std::streambuf
{
public:
/**
* Constructs a new QFileBuffer object. The created object is not associated
* with a file, and is_open() returns false.
*/
explicit QFileBuffer();
/**
* Constructs a new QFileBuffer object, then associated the object with a
* file by calling open(filename, mode). If the open call is successful,
* is_open() returns true.
*/
explicit QFileBuffer(const std::string& filename,
std::ios_base::openmode mode = std::ios_base::in);
~QFileBuffer();
QFileBuffer(const QFileBuffer&) = delete;
QFileBuffer& operator=(const QFileBuffer&) = delete;
QFileBuffer(QFileBuffer&&) noexcept;
QFileBuffer& operator=(QFileBuffer&&) noexcept;
/**
* @brief Checks if the associated file is open
*
* Returns true if the most recent call to open() succeeded and there has
* been no call to close() since then.
*
* @return true if the associated file is open, false otherwise
*/
bool is_open() const;
/**
* @brief Opens a file and configures it as the associated character sequence
*
* Opens the file with the given name. If the associated file was already
* open, returns a null pointer right away.
*
* @param filename The file name to open
* @param openmode The file opening mode, a binary OR of the std::ios_base
* modes
*
* @return this on success, a null pointer on failure
*/
QFileBuffer* open(const std::string& filename,
std::ios_base::openmode mode = std::ios_base::in);
/**
* @brief Flushes the put area buffer and closes the associated file
*
* Closes the file, regardless of whether any of the preceding calls
* succeeded or failed.
*
* If any of the function calls made fails, returns a null pointer. If any of
* the function calls made throws an exception, the exception is caught and
* rethrown after closing. If the file is already closed, returns a null
* pointer right away.
*
* @return this on success, a null pointer on failure
*/
QFileBuffer* close();
protected:
/**
* @brief Backs out the input sequence to unget a character, not affecting
* the associated file
*
* @param c The character to put back, or Traits::eof() to indicate that
* backing up of the get area is requested
*
* @return c on success except if c was Traits::eof(), in which case
* Traits::not_eof(c) is returned. Traits::eof() on failure.
*/
virtual int_type pbackfail(int_type c = traits_type::eof()) override;
/**
* @brief Repositions the file position, using relative addressing
*
* Sets the position indicator of the input and/or output sequence relative
* to some other position.
*
* @param off Relative position to set the position indicator to
* @param dir Defines base position to apply the relative offset to
* @param which Defines which of the input and/or output sequences to affect
*
* @return The resulting absolute position as defined by the position
* indicator.
*/
virtual pos_type
seekoff(off_type off,
std::ios_base::seekdir dir,
std::ios_base::openmode which = std::ios_base::in |
std::ios_base::out) override;
/**
* @brief Repositions the file position, using absolute addressing
*
* Sets the position indicator of the input and/or output sequence to an
* absolute position.
*
* @param pos Absolute position to set the position indicator to
* @param which Defines which of the input and/or output sequences to affect
*
* @return The resulting absolute position as defined by the position
* indicator.
*/
virtual pos_type
seekpos(pos_type pos,
std::ios_base::openmode which = std::ios_base::in |
std::ios_base::out) override;
/**
* @brief Reads from the associated file
*
* Ensures that at least one character is available in the input area by
* updating the pointers to the input area (if needed) and reading more data
* in from the input sequence (if applicable). Returns the value of that
* character (converted to int_type with Traits::to_int_type(c)) on success
* or Traits::eof() on failure.
*
* The function may update gptr, egptr and eback pointers to define the
* location of newly loaded data (if any). On failure, the function ensures
* that either gptr() == nullptr or gptr() == egptr.
*
* @return The value of the character pointed to by the get pointer after the
* call on success, or Traits::eof() otherwise.
*/
virtual int_type underflow() override;
/**
* @brief Reads from the associated file and advances the next pointer in the
* get area
*
* Behaves like the underflow(), except that if underflow() succeeds (does
* not return Traits::eof()), then advances the next pointer for the get
* area. In orther words, consumes one of the characters obtained by
* underflow().
*
* @return The value of the character that was read and consumed in case of
* success, or Traits::eof() in case of failure.
*/
virtual int_type uflow() override;
/**
* @brief Reads multiple characters from the input sequence
*
* Reads count characters from the input sequence and stores them into a
* character array pointed to by s. The characters are read as if by repeated
* calls to sbumpc(). That is, if less than count characters are immediately
* available, the function calls uflow() to provide more until Traits::eof()
* is returned.
*
* @param s Pointer to the beginning of a char_type array
* @param count Maximum number of characters to read
*
* @return The number of characters successfully read. If it is less than
* count the input sequence has reached the end.
*/
virtual std::streamsize xsgetn(char_type* s, std::streamsize count) override;
private:
class Impl;
std::unique_ptr<Impl> p;
};
} // namespace util
} // namespace qt
} // namespace scwx