mirror of
https://github.com/ciphervance/supercell-wx.git
synced 2025-10-30 16:40:05 +00:00
Initial QFileBuffer implementation
This commit is contained in:
parent
779d03c576
commit
08965aa7f3
3 changed files with 501 additions and 2 deletions
306
scwx-qt/source/scwx/qt/util/q_file_buffer.cpp
Normal file
306
scwx-qt/source/scwx/qt/util/q_file_buffer.cpp
Normal 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
|
||||
191
scwx-qt/source/scwx/qt/util/q_file_buffer.hpp
Normal file
191
scwx-qt/source/scwx/qt/util/q_file_buffer.hpp
Normal 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
|
||||
Loading…
Add table
Add a link
Reference in a new issue