    /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
    /*
     * This file is part of the LibreOffice project.
     *
     * This Source Code Form is subject to the terms of the Mozilla Public
     * License, v. 2.0. If a copy of the MPL was not distributed with this
     * file, You can obtain one at http://mozilla.org/MPL/2.0/.
     */

    #include "Blob.hxx"
    #include "Connection.hxx"
    #include "Util.hxx"

    #include <com/sun/star/io/BufferSizeExceededException.hpp>
    #include <com/sun/star/io/NotConnectedException.hpp>
    #include <com/sun/star/io/IOException.hpp>
    #include <com/sun/star/lang/IllegalArgumentException.hpp>
    #include <com/sun/star/lang/WrappedTargetRuntimeException.hpp>
    #include <connectivity/dbexception.hxx>
    #include <cppuhelper/exc_hlp.hxx>
    #include <sal/log.hxx>

    using namespace ::connectivity::firebird;

    using namespace ::cppu;
    using namespace ::osl;

    using namespace ::com::sun::star;
    using namespace ::com::sun::star::io;
    using namespace ::com::sun::star::sdbc;
    using namespace ::com::sun::star::uno;

    Blob::Blob(isc_db_handle* pDatabaseHandle,
               isc_tr_handle* pTransactionHandle,
               ISC_QUAD const & aBlobID):
        Blob_BASE(m_aMutex),
        m_pDatabaseHandle(pDatabaseHandle),
        m_pTransactionHandle(pTransactionHandle),
        m_blobID(aBlobID),
    #if SAL_TYPES_SIZEOFPOINTER == 8
        m_blobHandle(0),
    #else
        m_blobHandle(nullptr),
    #endif
        m_bBlobOpened(false),
        m_nBlobLength(0),
        m_nMaxSegmentSize(0),
        m_nBlobPosition(0)
    {
    }

    void Blob::ensureBlobIsOpened()
    {
        MutexGuard aGuard(m_aMutex);

        if (m_bBlobOpened)
            return;

        ISC_STATUS aErr;
        aErr = isc_open_blob2(m_statusVector,
                              m_pDatabaseHandle,
                              m_pTransactionHandle,
                              &m_blobHandle,
                              &m_blobID,
                              0,
                              nullptr);

        if (aErr)
            evaluateStatusVector(m_statusVector, "isc_open_blob2", *this);

        m_bBlobOpened = true;
        m_nBlobPosition = 0;

        char aBlobItems[] = {
            isc_info_blob_total_length,
            isc_info_blob_max_segment
        };

        // Assuming a data (e.g. legth of blob) is maximum 64 bit.
        // That means we need 8 bytes for data + 2 for length of data + 1 for item
        // identifier for each item.
        char aResultBuffer[11 + 11];

        aErr = isc_blob_info(m_statusVector,
                      &m_blobHandle,
                      sizeof(aBlobItems),
                      aBlobItems,
                      sizeof(aResultBuffer),
                      aResultBuffer);

        if (aErr)
            evaluateStatusVector(m_statusVector, "isc_blob_info", *this);

        char* pIt = aResultBuffer;
        while( *pIt != isc_info_end ) // info is in clusters
        {
            char item = *pIt++;
            short aResultLength = static_cast<short>(isc_vax_integer(pIt, 2));

            pIt += 2;
            switch(item)
            {
                case isc_info_blob_total_length:
                    m_nBlobLength = isc_vax_integer(pIt, aResultLength);
                    break;
                case isc_info_blob_max_segment:
                    m_nMaxSegmentSize = isc_vax_integer(pIt, aResultLength);
                    break;
                default:
                    assert(false);
                    break;
            }
            pIt += aResultLength;
        }
    }

    sal_uInt16 Blob::getMaximumSegmentSize()
    {
        ensureBlobIsOpened();

        return m_nMaxSegmentSize;
    }

    bool Blob::readOneSegment(uno::Sequence< sal_Int8 >& rDataOut)
    {
        checkDisposed(Blob_BASE::rBHelper.bDisposed);
        ensureBlobIsOpened();

        sal_uInt16 nMaxSize = getMaximumSegmentSize();

        if(rDataOut.getLength() < nMaxSize)
            rDataOut.realloc(nMaxSize);

        sal_uInt16 nActualSize = 0;
        ISC_STATUS aRet = isc_get_segment(m_statusVector,
                &m_blobHandle,
                &nActualSize,
                nMaxSize,
                reinterpret_cast<char*>(rDataOut.getArray()) );

        if (aRet && aRet != isc_segstr_eof && IndicatesError(m_statusVector))
        {
            OUString sError(StatusVectorToString(m_statusVector, "isc_get_segment"));
            throw IOException(sError, *this);
        }
        m_nBlobPosition += nActualSize;
        return aRet == isc_segstr_eof;  // last segment read
    }

    void Blob::closeBlob()
    {
        MutexGuard aGuard(m_aMutex);

        if (m_bBlobOpened)
        {
            ISC_STATUS aErr;
            aErr = isc_close_blob(m_statusVector,
                                  &m_blobHandle);
            if (aErr)
                evaluateStatusVector(m_statusVector, "isc_close_blob", *this);

            m_bBlobOpened = false;
    #if SAL_TYPES_SIZEOFPOINTER == 8
            m_blobHandle = 0;
    #else
            m_blobHandle = nullptr;
    #endif
        }
    }

    void SAL_CALL Blob::disposing()
    {
        try
        {
             closeBlob();
        }
        catch (SQLException &e)
        {
            // we cannot throw any exceptions here...
            SAL_WARN("connectivity.firebird", "isc_close_blob failed " << e);
            assert(false);
        }
        Blob_BASE::disposing();
    }

    sal_Int64 SAL_CALL Blob::length()
    {
        MutexGuard aGuard(m_aMutex);
        checkDisposed(Blob_BASE::rBHelper.bDisposed);
        ensureBlobIsOpened();

        return m_nBlobLength;
    }

    uno::Sequence< sal_Int8 > SAL_CALL  Blob::getBytes(sal_Int64 nPosition,
                                                       sal_Int32 nBytes)
    {
        MutexGuard aGuard(m_aMutex);
        checkDisposed(Blob_BASE::rBHelper.bDisposed);
        ensureBlobIsOpened();

        if (nPosition > m_nBlobLength || nPosition < 1)
            throw lang::IllegalArgumentException("nPosition out of range", *this, 0);
        // We only have to read as many bytes as are available, i.e. nPosition+nBytes
        // can legally be greater than the total length, hence we don't bother to check.

        if (nPosition -1 < m_nBlobPosition)
        {
            // Resets to the beginning (we can't seek these blobs)
            closeBlob();
            ensureBlobIsOpened();
        }

        // nPosition is indexed from 1.
        skipBytes(nPosition - m_nBlobPosition -1 );

        // Don't bother preallocating: readBytes does the appropriate calculations
        // and reallocates for us.
        uno::Sequence< sal_Int8 > aBytes;
        readBytes(aBytes, nBytes);
        return aBytes;
    }

    uno::Reference< XInputStream > SAL_CALL  Blob::getBinaryStream()
    {
        return this;
    }

    sal_Int64 SAL_CALL  Blob::position(const uno::Sequence< sal_Int8 >& /*rPattern*/,
                                       sal_Int64 /*nStart*/)
    {
        ::dbtools::throwFeatureNotImplementedSQLException("Blob::position", *this);
        return 0;
    }

    sal_Int64 SAL_CALL  Blob::positionOfBlob(const uno::Reference< XBlob >& /*rPattern*/,
                                             sal_Int64 /*aStart*/)
    {
        ::dbtools::throwFeatureNotImplementedSQLException("Blob::positionOfBlob", *this);
        return 0;
    }

    // ---- XInputStream ----------------------------------------------------------

    sal_Int32 SAL_CALL Blob::readBytes(uno::Sequence< sal_Int8 >& rDataOut,
                                       sal_Int32 nBytes)
    {
        MutexGuard aGuard(m_aMutex);

        try
        {
            checkDisposed(Blob_BASE::rBHelper.bDisposed);
            ensureBlobIsOpened();
        }
        catch (const NotConnectedException&)
        {
            throw;
        }
        catch (const BufferSizeExceededException&)
        {
            throw;
        }
        catch (const IOException&)
        {
            throw;
        }
        catch (const RuntimeException&)
        {
            throw;
        }
        catch (const Exception& e)
        {
            css::uno::Any a(cppu::getCaughtException());
            throw css::lang::WrappedTargetRuntimeException(
                "wrapped Exception " + e.Message,
                css::uno::Reference<css::uno::XInterface>(), a);
        }

        // Ensure we have enough space for the amount of data we can actually read.
        const sal_Int64 nBytesAvailable = m_nBlobLength - m_nBlobPosition;
        const sal_Int32 nBytesToRead = std::min<sal_Int64>(nBytes, nBytesAvailable);

        if (rDataOut.getLength() < nBytesToRead)
            rDataOut.realloc(nBytesToRead);

        sal_Int32 nTotalBytesRead = 0;
        ISC_STATUS aErr;
        while (nTotalBytesRead < nBytesToRead)
        {
            sal_uInt16 nBytesRead = 0;
            sal_uInt64 nDataRemaining = nBytesToRead - nTotalBytesRead;
            sal_uInt16 nReadSize = std::min<sal_uInt64>(nDataRemaining, SAL_MAX_UINT16);
            aErr = isc_get_segment(m_statusVector,
                                   &m_blobHandle,
                                   &nBytesRead,
                                   nReadSize,
                                   reinterpret_cast<char*>(rDataOut.getArray()) + nTotalBytesRead);
            if (aErr && IndicatesError(m_statusVector))
            {
                OUString sError(StatusVectorToString(m_statusVector, "isc_get_segment"));
                throw IOException(sError, *this);
            }
            nTotalBytesRead += nBytesRead;
            m_nBlobPosition += nBytesRead;
        }

        return nTotalBytesRead;
    }

    sal_Int32 SAL_CALL Blob::readSomeBytes(uno::Sequence< sal_Int8 >& rDataOut,
                                    sal_Int32 nMaximumBytes)
    {
        // We don't have any way of verifying how many bytes are immediately available,
        // hence we just pass through direct to readBytes
        // (Spec: "reads the available number of bytes, at maximum nMaxBytesToRead.")
        return readBytes(rDataOut, nMaximumBytes);
    }

    void SAL_CALL Blob::skipBytes(sal_Int32 nBytesToSkip)
    {
        // There is no way of directly skipping, hence we have to pretend to skip
        // by reading & discarding the data.
        uno::Sequence< sal_Int8 > aBytes;
        readBytes(aBytes, nBytesToSkip);
    }

    sal_Int32 SAL_CALL Blob::available()
    {
        MutexGuard aGuard(m_aMutex);

        try
        {
            checkDisposed(Blob_BASE::rBHelper.bDisposed);
            ensureBlobIsOpened();
        }
        catch (const NotConnectedException&)
        {
            throw;
        }
        catch (const IOException&)
        {
            throw;
        }
        catch (const RuntimeException&)
        {
            throw;
        }
        catch (const Exception& e)
        {
            css::uno::Any a(cppu::getCaughtException());
            throw css::lang::WrappedTargetRuntimeException(
                "wrapped Exception " + e.Message,
                css::uno::Reference<css::uno::XInterface>(), a);
        }

        return m_nBlobLength - m_nBlobPosition;
    }

    void SAL_CALL Blob::closeInput()
    {
        try
        {
            closeBlob();
        }
        catch (const NotConnectedException&)
        {
            throw;
        }
        catch (const IOException&)
        {
            throw;
        }
        catch (const RuntimeException&)
        {
            throw;
        }
        catch (const Exception& e)
        {
            css::uno::Any a(cppu::getCaughtException());
            throw css::lang::WrappedTargetRuntimeException(
                "wrapped Exception " + e.Message,
                css::uno::Reference<css::uno::XInterface>(), a);
        }
    }

    /* vim:set shiftwidth=4 softtabstop=4 expandtab: */

↑ V730 Not all members of a class are initialized inside the constructor. Consider inspecting: m_statusVector.