mirror of
				https://github.com/ciphervance/supercell-wx.git
				synced 2025-10-31 23:10:06 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			313 lines
		
	
	
	
		
			7.1 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			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
 | 
