supercell-wx/scwx-qt/source/scwx/qt/util/q_file_buffer.cpp
2023-01-01 21:44:03 -06:00

313 lines
7.1 KiB
C++

#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;
void QFileBuffer::swap(QFileBuffer& other)
{
// Swap the base class and managed implementation pointer
std::streambuf::swap(other);
p.swap(other.p);
}
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(-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(1);
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