fdo#70664 Allow reading firebird Blob as InputStream.

Change-Id: Ie0cb93a902961b3f37daf435828263478c9d2997
This commit is contained in:
Andrzej Hunt 2013-12-07 20:32:55 +00:00
parent cb4b290bcf
commit 2b24eec3d4
2 changed files with 198 additions and 107 deletions

View File

@ -13,6 +13,8 @@
#include "connectivity/dbexception.hxx" #include "connectivity/dbexception.hxx"
#include <com/sun/star/lang/IllegalArgumentException.hpp>
using namespace ::connectivity::firebird; using namespace ::connectivity::firebird;
using namespace ::cppu; using namespace ::cppu;
@ -32,7 +34,8 @@ Blob::Blob(isc_db_handle* pDatabaseHandle,
m_blobID(aBlobID), m_blobID(aBlobID),
m_blobHandle(0), m_blobHandle(0),
m_bBlobOpened(false), m_bBlobOpened(false),
m_blobData(0) m_nBlobLength(0),
m_nBlobPosition(0)
{ {
} }
@ -52,44 +55,12 @@ void Blob::ensureBlobIsOpened()
&m_blobID, &m_blobID,
0, 0,
NULL); NULL);
if (aErr) if (aErr)
evaluateStatusVector(m_statusVector, "isc_open_blob2", *this); evaluateStatusVector(m_statusVector, "isc_open_blob2", *this);
} m_bBlobOpened = true;
m_nBlobPosition = 0;
void SAL_CALL Blob::disposing(void)
{
MutexGuard aGuard(m_aMutex);
if (m_bBlobOpened)
{
ISC_STATUS aErr;
aErr = isc_close_blob(m_statusVector,
&m_blobHandle);
if (aErr)
{
try
{
evaluateStatusVector(m_statusVector, "isc_close_blob", *this);
}
catch (SQLException e)
{
// we cannot throw any exceptions here anyway
SAL_WARN("connectivity.firebird", "isc_close_blob failed\n" <<
e.Message);
}
}
}
Blob_BASE::disposing();
}
sal_Int64 SAL_CALL Blob::length()
throw(SQLException, RuntimeException)
{
MutexGuard aGuard(m_aMutex);
checkDisposed(Blob_BASE::rBHelper.bDisposed);
ensureBlobIsOpened();
char aBlobItems[] = { char aBlobItems[] = {
isc_info_blob_total_length isc_info_blob_total_length
@ -103,106 +74,186 @@ sal_Int64 SAL_CALL Blob::length()
sizeof(aResultBuffer), sizeof(aResultBuffer),
aResultBuffer); aResultBuffer);
if (aErr)
evaluateStatusVector(m_statusVector, "isc_blob_info", *this); evaluateStatusVector(m_statusVector, "isc_blob_info", *this);
if (*aResultBuffer == isc_info_blob_total_length) if (*aResultBuffer == isc_info_blob_total_length)
{ {
short aResultLength = (short) isc_vax_integer(aResultBuffer, 2); short aResultLength = (short) isc_vax_integer(aResultBuffer+1, 2);
return isc_vax_integer(aResultBuffer+2, aResultLength); m_nBlobLength = isc_vax_integer(aResultBuffer+3, aResultLength);
}
else
{
assert(false);
} }
return 0;
} }
uno::Sequence< sal_Int8 > SAL_CALL Blob::getBytes(sal_Int64 aPosition, sal_Int32 aLength) void Blob::closeBlob()
throw (SQLException)
{
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;
m_blobHandle = 0;
}
}
void SAL_CALL Blob::disposing(void)
{
try
{
closeBlob();
}
catch (SQLException e)
{
// we cannot throw any exceptions here...
SAL_WARN("connectivity.firebird", "isc_close_blob failed\n" <<
e.Message);
assert(false);
}
Blob_BASE::disposing();
}
sal_Int64 SAL_CALL Blob::length()
throw(SQLException, RuntimeException) throw(SQLException, RuntimeException)
{ {
MutexGuard aGuard(m_aMutex); MutexGuard aGuard(m_aMutex);
checkDisposed(Blob_BASE::rBHelper.bDisposed); checkDisposed(Blob_BASE::rBHelper.bDisposed);
ensureBlobIsOpened(); ensureBlobIsOpened();
sal_Int64 aTotalLength = length(); return m_nBlobLength;
if (!(aPosition + aLength < aTotalLength))
{
throw SQLException("Byte array requested outwith valid range", *this, OUString(), 1, Any() );
} }
if (aTotalLength != m_blobData.getLength()) uno::Sequence< sal_Int8 > SAL_CALL Blob::getBytes(sal_Int64 nPosition,
sal_Int32 nBytes)
throw(SQLException, RuntimeException)
{ {
m_blobData = uno::Sequence< sal_Int8 >(aTotalLength); MutexGuard aGuard(m_aMutex);
char* pArray = (char*) m_blobData.getArray(); checkDisposed(Blob_BASE::rBHelper.bDisposed);
sal_Int64 aBytesRead = 0; ensureBlobIsOpened();
unsigned short aLengthRead; // The amount read in in a isc_get_segment call if (nPosition > m_nBlobLength)
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.
ISC_STATUS aErr; if (nPosition > m_nBlobPosition)
do
{ {
aErr = isc_get_segment(m_statusVector, // Resets to the beginning (we can't seek these blobs)
&m_blobHandle, closeBlob();
&aLengthRead, ensureBlobIsOpened();
aTotalLength - aBytesRead,
pArray + aBytesRead);
}
while (aErr == 0 || m_statusVector[1] == isc_segment);
// Denotes either sucessful read, or only part of segment read successfully.
if (aErr)
{
m_blobData = uno::Sequence< sal_Int8 >(0);
evaluateStatusVector(m_statusVector, "isc_get_segment", *this);
}
} }
if (aLength<aTotalLength) skipBytes(nPosition - m_nBlobPosition);
{
uno::Sequence< sal_Int8 > aRet(aLength); // Don't bother preallocating: readBytes does the appropriate calculations
memcpy(aRet.getArray(), m_blobData.getArray() + aLength, aLength); // and reallocates for us.
return aRet; uno::Sequence< sal_Int8 > aBytes;
} readBytes(aBytes, nBytes);
else return aBytes;
{
return m_blobData; // TODO: subsequence
}
} }
uno::Reference< XInputStream > SAL_CALL Blob::getBinaryStream() uno::Reference< XInputStream > SAL_CALL Blob::getBinaryStream()
throw(SQLException, RuntimeException) throw(SQLException, RuntimeException)
{ {
// MutexGuard aGuard(m_aMutex); return this;
// checkDisposed(Blob_BASE::rBHelper.bDisposed);
// ensureBlobIsOpened();
::dbtools::throwFeatureNotImplementedException("Blob::positionOfBlob", *this);
return NULL;
} }
sal_Int64 SAL_CALL Blob::position(const uno::Sequence< sal_Int8 >& rPattern, sal_Int64 SAL_CALL Blob::position(const uno::Sequence< sal_Int8 >& /*rPattern*/,
sal_Int64 aStart) sal_Int64 /*nStart*/)
throw(SQLException, RuntimeException) throw(SQLException, RuntimeException)
{ {
// MutexGuard aGuard(m_aMutex); ::dbtools::throwFeatureNotImplementedException("Blob::position", *this);
// checkDisposed(Blob_BASE::rBHelper.bDisposed); return 0;
// ensureBlobIsOpened(); }
(void) rPattern; sal_Int64 SAL_CALL Blob::positionOfBlob(const uno::Reference< XBlob >& /*rPattern*/,
(void) aStart; sal_Int64 /*aStart*/)
throw(SQLException, RuntimeException)
{
::dbtools::throwFeatureNotImplementedException("Blob::positionOfBlob", *this); ::dbtools::throwFeatureNotImplementedException("Blob::positionOfBlob", *this);
return 0; return 0;
} }
sal_Int64 SAL_CALL Blob::positionOfBlob(const uno::Reference< XBlob >& rPattern, // ---- XInputStream ----------------------------------------------------------
sal_Int64 aStart)
throw(SQLException, RuntimeException)
{
// MutexGuard aGuard(m_aMutex);
// checkDisposed(Blob_BASE::rBHelper.bDisposed);
// ensureBlobIsOpened();
(void) rPattern; sal_Int32 SAL_CALL Blob::readBytes(uno::Sequence< sal_Int8 >& rDataOut,
(void) aStart; sal_Int32 nBytes)
::dbtools::throwFeatureNotImplementedException("Blob::positionOfBlob", *this); throw (NotConnectedException, BufferSizeExceededException, IOException, RuntimeException)
return 0; {
MutexGuard aGuard(m_aMutex);
checkDisposed(Blob_BASE::rBHelper.bDisposed);
ensureBlobIsOpened();
// 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 = nBytes < nBytesAvailable ? 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 = (nDataRemaining > SAL_MAX_UINT16) ? SAL_MAX_UINT16 : nDataRemaining;
aErr = isc_get_segment(m_statusVector,
&m_blobHandle,
&nBytesRead,
nReadSize,
(char*) rDataOut.getArray() + nTotalBytesRead);
if (aErr)
evaluateStatusVector(m_statusVector, "isc_get_segment", *this);
nTotalBytesRead += nBytesRead;
m_nBlobPosition += nBytesRead;
} }
return nTotalBytesRead;
}
sal_Int32 SAL_CALL Blob::readSomeBytes(uno::Sequence< sal_Int8 >& rDataOut,
sal_Int32 nMaximumBytes)
throw (NotConnectedException, BufferSizeExceededException, IOException, RuntimeException)
{
// 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)
throw (NotConnectedException, BufferSizeExceededException, IOException, RuntimeException)
{
// 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()
throw (NotConnectedException, IOException, RuntimeException)
{
MutexGuard aGuard(m_aMutex);
checkDisposed(Blob_BASE::rBHelper.bDisposed);
ensureBlobIsOpened();
return m_nBlobLength - m_nBlobPosition;
}
void SAL_CALL Blob::closeInput()
throw(NotConnectedException, IOException, RuntimeException)
{
closeBlob();
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ /* vim:set shiftwidth=4 softtabstop=4 expandtab: */

View File

@ -12,15 +12,17 @@
#include <ibase.h> #include <ibase.h>
#include <cppuhelper/compbase1.hxx> #include <cppuhelper/compbase2.hxx>
#include <com/sun/star/io/XInputStream.hpp>
#include <com/sun/star/sdbc/XBlob.hpp> #include <com/sun/star/sdbc/XBlob.hpp>
namespace connectivity namespace connectivity
{ {
namespace firebird namespace firebird
{ {
typedef ::cppu::WeakComponentImplHelper1< ::com::sun::star::sdbc::XBlob > typedef ::cppu::WeakComponentImplHelper2< ::com::sun::star::sdbc::XBlob,
::com::sun::star::io::XInputStream >
Blob_BASE; Blob_BASE;
class Blob : class Blob :
@ -38,13 +40,19 @@ namespace connectivity
isc_blob_handle m_blobHandle; isc_blob_handle m_blobHandle;
bool m_bBlobOpened; bool m_bBlobOpened;
sal_Int64 m_nBlobLength;
sal_Int64 m_nBlobPosition;
ISC_STATUS_ARRAY m_statusVector; ISC_STATUS_ARRAY m_statusVector;
::com::sun::star::uno::Sequence< sal_Int8 > m_blobData;
void ensureBlobIsOpened() void ensureBlobIsOpened()
throw(::com::sun::star::sdbc::SQLException); throw(::com::sun::star::sdbc::SQLException);
/**
* Closes the blob and cleans up resources -- can be used to reset
* the blob if we e.g. want to read from the beginning again.
*/
void closeBlob()
throw(::com::sun::star::sdbc::SQLException);
public: public:
Blob(isc_db_handle* pDatabaseHandle, Blob(isc_db_handle* pDatabaseHandle,
@ -75,6 +83,38 @@ namespace connectivity
throw(::com::sun::star::sdbc::SQLException, throw(::com::sun::star::sdbc::SQLException,
::com::sun::star::uno::RuntimeException); ::com::sun::star::uno::RuntimeException);
// ---- XInputStream ----------------------------------------------
virtual sal_Int32 SAL_CALL
readBytes(::com::sun::star::uno::Sequence< sal_Int8 >& rDataOut,
sal_Int32 nBytes)
throw(::com::sun::star::io::NotConnectedException,
::com::sun::star::io::BufferSizeExceededException,
::com::sun::star::io::IOException,
::com::sun::star::uno::RuntimeException);
virtual sal_Int32 SAL_CALL
readSomeBytes(::com::sun::star::uno::Sequence< sal_Int8 >& rDataOut,
sal_Int32 nMaximumBytes)
throw(::com::sun::star::io::NotConnectedException,
::com::sun::star::io::BufferSizeExceededException,
::com::sun::star::io::IOException,
::com::sun::star::uno::RuntimeException);
virtual void SAL_CALL
skipBytes(sal_Int32 nBytes)
throw(::com::sun::star::io::NotConnectedException,
::com::sun::star::io::BufferSizeExceededException,
::com::sun::star::io::IOException,
::com::sun::star::uno::RuntimeException);
virtual sal_Int32 SAL_CALL
available()
throw(::com::sun::star::io::NotConnectedException,
::com::sun::star::io::IOException,
::com::sun::star::uno::RuntimeException);
virtual void SAL_CALL
closeInput()
throw(::com::sun::star::io::NotConnectedException,
::com::sun::star::io::IOException,
::com::sun::star::uno::RuntimeException);
// ---- OComponentHelper ------------------------------------------ // ---- OComponentHelper ------------------------------------------
virtual void SAL_CALL disposing(); virtual void SAL_CALL disposing();
}; };