// Copyright (c) 2008-2009 Nokia Corporation and/or its subsidiary(-ies).
// All rights reserved.
// This component and the accompanying materials are made available
// under the terms of "Eclipse Public License v1.0"
// which accompanies this distribution, and is available
// at the URL "http://www.eclipse.org/legal/epl-v10.html".
//
// Initial Contributors:
// Nokia Corporation - initial contribution.
//
// Contributors:
//
// Description:
//

#include <s32mem.h>
#include "SqlDb.h"
#include "SqlPanic.h"
#include "SqlDatabaseImpl.h"
#include "IPCBuf.h"

// The maximum time (100 milliseconds) that a block of data is allowed to take to be read/written by TSqlBlob.
// If the time taken is longer than this value then the next block size used will be less
const TInt KTimeThresholdInMicroSecs = 100000; 
// The largest block size used by TSqlBlob is 32Kb
const TInt KLargestBlockSize = 32 * 1024; 
// The client-side buffer will be used instead if the calculated block size is 2Kb or less 
const TInt KUseClientBufferThreshold = 2 * 1024; 

// Prepare an IPC buffer containing the parameter values
static HBufC8* PrepareIpcParamBufLC(const TDesC& aTableName, const TDesC& aColumnName, TInt64 aRowId, 
								  TBool aReadOnly, const TDesC& aDbName)
	{
	HBufC8* ipcPrmBuf = HBufC8::NewLC(aTableName.Size() + sizeof(TDesC) + 
									  aColumnName.Size() + sizeof(TDesC) +
									  sizeof(aRowId) + sizeof(aReadOnly) + 
									  aDbName.Size() + sizeof(TDesC) + sizeof(TInt32) * 3);
	TPtr8 ipcPrmDes = ipcPrmBuf->Des();
	RDesWriteStream strm(ipcPrmDes);
	
	strm << static_cast <TInt32> (aTableName.Length()) 
	     << aTableName 
	     << static_cast <TInt32> (aColumnName.Length()) 
	     << aColumnName 
	     << aRowId 
	     << static_cast<TInt32>(aReadOnly) 
	     << static_cast <TInt32> (aDbName.Length()) 
	     << aDbName;
	strm.CommitL();
	strm.Close();
	return ipcPrmBuf;
	}

// Create an IPC stream, sending the IPC buffer containing the parameter values
static MStreamBuf* CreateIpcStreamL(RSqlDbSession& aDbSession, const TDesC8& aIpcPrmBuf)
	{
	TIpcArgs ipcArgs;
	ipcArgs.Set(0, aIpcPrmBuf.Length());
	ipcArgs.Set(1, &aIpcPrmBuf);
	HIpcBuf* ipcBuf = HIpcBuf::NewL(aDbSession, ::MakeMsgCode(ESqlSrvDbBlobSource, ESqlSrvNoHandle, 0), ipcArgs);
	return ipcBuf;
	}	
	
// Used by TSqlBlob to calculate the appropriate block size to use for the next transfer of blob data
static TInt CalculateBlockSize(TInt aRemainingDataSize, TInt aPreviousBlockSize, TBool aTimeThresholdBreached)
	{
	TInt blockSize = aPreviousBlockSize;
	if(aTimeThresholdBreached)
		{
		// The time threshold was breached using the previous block size
		// so we will reduce the block size by a factor of 2
		blockSize /= 2;
		if(blockSize <= KUseClientBufferThreshold)
			{
			// Just use the client-side IPC buffer size if things get to this stage
			blockSize = KIpcBufSize; 
			}
		}	
	return aRemainingDataSize <= blockSize ? aRemainingDataSize : blockSize;
	}
		
// Retrieve the data in blocks
static void DoReadInBlocksL(RSqlBlobReadStream& aStrm, TDes8& aDestBuffer, TInt aBlobSize)
	{
	TInt remainingDataSize = aBlobSize;	
	TBool timeThresholdBreached = EFalse;
	TInt blockSize = KLargestBlockSize;
	TPtr8 ptr((TUint8*)aDestBuffer.Ptr(), aDestBuffer.MaxSize());
	
	while(remainingDataSize > 0)
		{	
		// Calculate the block size to use for this iteration, based on 
		// how much data is remaining and how fast the previous read was
		blockSize = CalculateBlockSize(remainingDataSize, blockSize, timeThresholdBreached);
		
		// Read the block of data, timing how long it takes
		TTime t1, t2;
		t1.HomeTime();
		aStrm.ReadL(ptr, blockSize);
		t2.HomeTime();
		TTimeIntervalMicroSeconds readTime = t2.MicroSecondsFrom(t1);
		timeThresholdBreached = (readTime.Int64() > KTimeThresholdInMicroSecs);
	
		// Update how much data is still to be read, and the buffer pointer
		remainingDataSize -= blockSize;
		__SQLASSERT(remainingDataSize >= 0, ESqlPanicInternalError);
		ptr.Set((TUint8*)(ptr.Ptr() + blockSize), 0, remainingDataSize);
		}	
	aDestBuffer.SetLength(aBlobSize);
	}
	
// Send the data in blocks
static void DoWriteInBlocksL(RSqlBlobWriteStream& aStrm, const TDesC8& aData, TInt aDataSize)
	{
	TInt remainingDataSize = aDataSize;	
	TBool timeThresholdBreached = EFalse;
	TInt blockSize = KLargestBlockSize;
	
	while(remainingDataSize > 0)
		{	
		// Calculate the block size to use for this iteration, based on 
		// how much data is remaining and how fast the previous write was
		blockSize = CalculateBlockSize(remainingDataSize, blockSize, timeThresholdBreached);

		// Write the block of data, timing how long it takes
		TTime t1, t2;
		t1.HomeTime();
		aStrm.WriteL(aData.Right(remainingDataSize), blockSize);
		t2.HomeTime();
		TTimeIntervalMicroSeconds writeTime = t2.MicroSecondsFrom(t1);
		timeThresholdBreached = (writeTime.Int64() > KTimeThresholdInMicroSecs);
	
		// Update how much data is still to be written
		remainingDataSize -= blockSize;
		__SQLASSERT(remainingDataSize >= 0, ESqlPanicInternalError);
		}	
	aStrm.CommitL();	
	}
	
// The data is returned in a UTF-8 buffer that is allocated on the heap
static HBufC8* ReadLC(RSqlDatabase& aDb, 	
				   	  const TDesC& aTableName, 
				   	  const TDesC& aColumnName, 
				   	  TInt64 aRowId,
				   	  const TDesC& aDbName)	
	{
	RSqlBlobReadStream strm;
	CleanupClosePushL(strm);
	strm.OpenL(aDb, aTableName, aColumnName, aRowId, aDbName);		
	TInt blobSize = strm.SizeL(); // size of the blob, in bytes
	HBufC8* buf = HBufC8::NewLC(blobSize);
	TPtr8 ptr = buf->Des();	
	DoReadInBlocksL(strm, ptr, blobSize);
	CleanupStack::Pop(); // buf
	CleanupStack::PopAndDestroy(); // strm
	CleanupStack::PushL(buf);
	return buf;	
	}

// The data is returned in the UTF-8 buffer passed in
static void ReadL(RSqlDatabase& aDb, 	
				  const TDesC& aTableName, 
				  const TDesC& aColumnName, 	
				  TDes8& aBuffer,
				  TInt64 aRowId,
				  const TDesC& aDbName)
	{	
	RSqlBlobReadStream strm;
	CleanupClosePushL(strm);
	strm.OpenL(aDb, aTableName, aColumnName, aRowId, aDbName);		
	TInt blobSize = strm.SizeL(); // size of the blob, in bytes
	if(blobSize > aBuffer.MaxSize())
		{
		__SQLLEAVE(KErrOverflow);
		}	
	DoReadInBlocksL(strm, aBuffer, blobSize);
	CleanupStack::PopAndDestroy(); // strm
	}
	
// The data in the client-specified UTF-8 buffer is written
static void WriteL(RSqlDatabase& aDb, 	
				   const TDesC& aTableName, 
				   const TDesC& aColumnName,
				   const TDesC8& aData,	
				   TInt64 aRowId,
				   const TDesC& aDbName)
	{
	RSqlBlobWriteStream strm;
	CleanupClosePushL(strm);
	strm.OpenL(aDb, aTableName, aColumnName, aRowId, aDbName);
	TInt dataSize = aData.Size(); // size of the data, in bytes
	TInt blobSize = strm.SizeL(); // size of the blob, in bytes
	if(dataSize > blobSize)
		{
		__SQLLEAVE(KErrEof);
		}	
	if(dataSize > 0)
		{
		DoWriteInBlocksL(strm, aData, dataSize);
		}	
	CleanupStack::PopAndDestroy(); // strm
	}

////////////////////////////////////////////////////////////////////////////////	
			
/**
Gives access to a blob as a read-only stream of bytes.

@param aDb        	  A connection to the database that contains the blob
@param aTableName     The name of the table that contains the blob
@param aColumnName    The name of the column that contains the blob
@param aRowId         The ROWID of the record that contains the blob,
					  or KSqlLastInsertedRowId if the last inserted ROWID 
					  of the specified database connection is to be used
@param aDbName  	  The name of the attached database if the blob is 
					  contained in an attached database

@leave KSqlErrGeneral, 		 Invalid database connection or table name or column name 
							 or column type or ROWID or database name;							 
	   KErrNoMemory, 		 An out of memory condition occurred;	   
	   KErrArgument, 		 The ROWID is zero or negative or a UTF-16 to UTF-8 string conversion failed;
  	   KErrBadName,  		 The table name, column name or database name has an invalid length;
	   KErrPermissionDenied, The client does not have the required security capabilites for this operation; 						 
       						 Note that the function may also leave with some other system wide errors or 
                     		 database specific errors categorised as ESqlDbError.
	     
@panic SqlDb 2 The database object is not yet created
@panic SqlDb 3 Server failed to create a handle to the blob
@panic SqlDb 4 In _DEBUG mode. Bad parameter value
@panic SqlDb 7 In _DEBUG mode. NULL blob handle

@capability None, if the aDb parameter represents a handle which operates on a non-secure database;
            RSqlSecurityPolicy::EReadPolicy or 
            RSqlSecurityPolicy::ESchemaPolicy database policy type, if the blob belongs to a secure database; 
*/		
EXPORT_C void RSqlBlobReadStream::OpenL(RSqlDatabase& aDb, const TDesC& aTableName, const TDesC& aColumnName, 
										TInt64 aRowId, const TDesC& aDbName)
	{
	SQLUTRACE_PROFILER(this);
	SYMBIAN_TRACE_SQL_EVENTS_ONLY(UTF::Printf(UTF::TTraceContext(UTF::EInternals), KRSqlBlobParam16, &aDb, &aTableName, 
			&aColumnName, aRowId, &aDbName));
	
	HBufC8* ipcPrmBuf = ::PrepareIpcParamBufLC(aTableName, aColumnName, aRowId, ETrue, aDbName);
	MStreamBuf* strm = ::CreateIpcStreamL(aDb.Impl().Session(), ipcPrmBuf->Des());
	Attach(strm);
	CleanupStack::PopAndDestroy(ipcPrmBuf);	
	}
	
/**
Returns the size of the blob object, in bytes.

@return The size of the blob object, in bytes

@leave One of the system-wide error codes

@panic SqlDb 2 The stream buffer is NULL
	
@capability None
*/
EXPORT_C TInt RSqlBlobReadStream::SizeL()
	{
	SQLUTRACE_PROFILER(this);
	
	MStreamBuf* src = Source();
	__SQLASSERT_ALWAYS(src != NULL, ESqlPanicInvalidObj);
	return src->SizeL();
	}

/**
Gives access to a blob as a writeable stream of bytes.

@param aDb        	  A connection to the database that contains the blob
@param aTableName     The name of the table that contains the blob
@param aColumnName    The name of the column that contains the blob
@param aRowId         The ROWID of the record that contains the blob,
					  or KSqlLastInsertedRowId if the last inserted ROWID 
					  of the specified database connection is to be used
@param aDbName  	  The name of the attached database if the blob is 
					  contained in an attached database

@leave KSqlErrGeneral, 		 Invalid database connection or table name or column name or column 
							 type or ROWID or database name, or the specified column is indexed;
	   KErrNoMemory, 		 An out of memory condition occurred;	   
	   KErrArgument, 		 The ROWID is zero or negative or a UTF-16 to UTF-8 string conversion failed;
  	   KErrBadName,  		 The table name, column name or database name has an invalid length;
	   KErrPermissionDenied, The client does not have the required security capabilites for this operation; 						 
       						 Note that the function may also leave with some other system wide errors or 
                     		 database specific errors categorised as ESqlDbError.
	     
@panic SqlDb 2 The database object is not yet created
@panic SqlDb 3 Server failed to create a handle to the blob
@panic SqlDb 4 In _DEBUG mode. Bad parameter value
@panic SqlDb 7 In _DEBUG mode. NULL blob handle

@capability None, if the aDb parameter represents a handle which operates on a non-secure database;
            RSqlSecurityPolicy::EWritePolicy or 
            RSqlSecurityPolicy::ESchemaPolicy database policy type, if the blob belongs to a secure database; 
*/							
EXPORT_C void RSqlBlobWriteStream::OpenL(RSqlDatabase& aDb, const TDesC& aTableName, const TDesC& aColumnName, 
										 TInt64 aRowId, const TDesC& aDbName)
	{
	SQLUTRACE_PROFILER(this);
	SYMBIAN_TRACE_SQL_EVENTS_ONLY(UTF::Printf(UTF::TTraceContext(UTF::EInternals), KRSqlBlobParam16, &aDb, &aTableName, 
			&aColumnName, aRowId, &aDbName));
	
	HBufC8* ipcPrmBuf = ::PrepareIpcParamBufLC(aTableName, aColumnName, aRowId, EFalse, aDbName);
	MStreamBuf* strm = ::CreateIpcStreamL(aDb.Impl().Session(), ipcPrmBuf->Des());
	Attach(strm);
	CleanupStack::PopAndDestroy(ipcPrmBuf);		
	}

/**
Returns the size of the blob object, in bytes.

@return The size of the blob object, in bytes

@leave One of the system-wide error codes

@panic SqlDb 2 The stream buffer is NULL
	
@capability None
*/
EXPORT_C TInt RSqlBlobWriteStream::SizeL()
	{
	SQLUTRACE_PROFILER(this);

	MStreamBuf* sink = Sink();
	__SQLASSERT_ALWAYS(sink != NULL, ESqlPanicInvalidObj);
	return sink->SizeL();
	}

/**
Retrieves the entire content of a blob and returns it to the client 
in a heap allocated buffer which has been placed on the cleanup stack. 
It is the responsibility of the client to destroy the returned buffer.

@param aDb        	  A connection to the database that contains the blob
@param aTableName     The name of the table that contains the blob
@param aColumnName    The name of the column that contains the blob
@param aRowId         The ROWID of the record that contains the blob,
					  or KSqlLastInsertedRowId if the last inserted ROWID 
					  of the specified database connection is to be used
@param aDbName  	  The name of the attached database if the blob is 
					  contained in an attached database

@leave KSqlErrGeneral, 		 Invalid database connection or table name or column name 
							 or column type or ROWID or database name;							 
	   KErrNoMemory, 		 An out of memory condition occurred;	   
	   KErrArgument, 		 The ROWID is zero or negative or a UTF-16 to UTF-8 string conversion failed;
  	   KErrBadName,  		 The table name, column name or database name has an invalid length;
	   KErrPermissionDenied, The client does not have the required security capabilites for this operation; 						 
       						 Note that the function may also leave with some other system wide errors or 
                     		 database specific errors categorised as ESqlDbError.
	     
@panic SqlDb 2 The database object is not yet created
@panic SqlDb 3 Server failed to create a handle to the blob
@panic SqlDb 4 In _DEBUG mode. Bad parameter value
@panic SqlDb 7 In _DEBUG mode. NULL blob handle or miscalculation of remaining data to be read

@capability None, if the aDb parameter represents a handle which operates on a non-secure database;
            RSqlSecurityPolicy::EReadPolicy or 
            RSqlSecurityPolicy::ESchemaPolicy database policy type, if the blob belongs to a secure database; 
*/
EXPORT_C HBufC8* TSqlBlob::GetLC(RSqlDatabase& aDb, 	
					     	     const TDesC& aTableName, 
					     		 const TDesC& aColumnName, 	
					     		 TInt64 aRowId,
					     		 const TDesC& aDbName)
	{
	SQLUTRACE_PROFILER(0);
	SYMBIAN_TRACE_SQL_EVENTS_ONLY(UTF::Printf(UTF::TTraceContext(UTF::EInternals), KRSqlBlobParam16, &aDb, &aTableName, 
			&aColumnName, aRowId, &aDbName));
	
	return ReadLC(aDb, aTableName, aColumnName, aRowId, aDbName);
	}
	
/**
Retrieves the entire content of a blob into a client specified buffer.

@param aDb        	  A connection to the database that contains the blob
@param aTableName     The name of the table that contains the blob
@param aColumnName    The name of the column that contains the blob
@param aRowId         The ROWID of the record that contains the blob,
					  or KSqlLastInsertedRowId if the last inserted ROWID 
					  of the specified database connection is to be used
@param aDbName  	  The name of the attached database if the blob is 
					  contained in an attached database

@return KSqlErrGeneral, 	  Invalid database connection or table name or column name 
							  or column type or ROWID or database name;							 
	    KErrNoMemory, 		  An out of memory condition occurred;	   
	    KErrArgument, 		  The ROWID is zero or negative or a UTF-16 to UTF-8 string conversion failed;
 	    KErrOverflow, 		  The specified buffer is not big enough to hold the entire blob;
  	    KErrBadName,  		  The table name, column name or database name has an invalid length;
	    KErrPermissionDenied, The client does not have the required security capabilites for this operation; 						 
       						  Note that the function may also leave with some other system wide errors or 
                     		  database specific errors categorised as ESqlDbError.                    		 
	     
@panic SqlDb 2 The database object is not yet created
@panic SqlDb 3 Server failed to create a handle to the blob
@panic SqlDb 4 In _DEBUG mode. Bad parameter value
@panic SqlDb 7 In _DEBUG mode. NULL blob handle or miscalculation of remaining data to be read

@capability None, if the aDb parameter represents a handle which operates on a non-secure database;
            RSqlSecurityPolicy::EReadPolicy or 
            RSqlSecurityPolicy::ESchemaPolicy database policy type, if the blob belongs to a secure database; 
*/
EXPORT_C TInt TSqlBlob::Get(RSqlDatabase& aDb, 	
					 		const TDesC& aTableName, 
					 	    const TDesC& aColumnName, 	
					 	    TDes8& aBuffer,
					 		TInt64 aRowId,
					 	    const TDesC& aDbName)
	{
	SQLUTRACE_PROFILER(0);
	SYMBIAN_TRACE_SQL_EVENTS_ONLY(UTF::Printf(UTF::TTraceContext(UTF::EInternals), KRSqlBlobParam16, &aDb, &aTableName, 
			&aColumnName, aRowId, &aDbName));
	
	TRAPD(err, ReadL(aDb, aTableName, aColumnName, aBuffer, aRowId, aDbName));
	return err;	
	}

/**
Writes the data in a client specified buffer to a blob.

@param aDb        	  A connection to the database that contains the blob
@param aTableName     The name of the table that contains the blob
@param aColumnName    The name of the column that contains the blob
@param aRowId         The ROWID of the record that contains the blob,
					  or KSqlLastInsertedRowId if the last inserted ROWID 
					  of the specified database connection is to be used
@param aDbName  	  The name of the attached database if the blob is 
					  contained in an attached database

@leave KSqlErrGeneral, 		 Invalid database connection or table name or column name or column 
							 type or ROWID or database name, or the specified column is indexed;
	   KErrNoMemory, 		 An out of memory condition occurred;	   
	   KErrArgument, 		 The ROWID is zero or negative or a UTF-16 to UTF-8 string conversion failed;
	   KErrEof,	     		 The data in the specified buffer is larger in size than the blob object;
  	   KErrBadName,  		 The table name, column name or database name has an invalid length;
	   KErrPermissionDenied, The client does not have the required security capabilites for this operation; 						 
       						 Note that the function may also leave with some other system wide errors or 
                     		 database specific errors categorised as ESqlDbError.
	     
@panic SqlDb 2 The database object is not yet created
@panic SqlDb 3 Server failed to create a handle to the blob
@panic SqlDb 4 In _DEBUG mode. Bad parameter value
@panic SqlDb 7 In _DEBUG mode. NULL blob handle or miscalculation of remaining data to be written

@capability None, if the aDb parameter represents a handle which operates on a non-secure database;
            RSqlSecurityPolicy::EWritePolicy or 
            RSqlSecurityPolicy::ESchemaPolicy database policy type, if the blob belongs to a secure database; 
*/
EXPORT_C void TSqlBlob::SetL(RSqlDatabase& aDb, 	
					  		 const TDesC& aTableName, 
					  		 const TDesC& aColumnName,
					  		 const TDesC8& aData,	
					  		 TInt64 aRowId,
					  		 const TDesC& aDbName)
	{
	SQLUTRACE_PROFILER(0);
	SYMBIAN_TRACE_SQL_EVENTS_ONLY(UTF::Printf(UTF::TTraceContext(UTF::EInternals), KRSqlBlobParam16, &aDb, &aTableName, 
			&aColumnName, aRowId, &aDbName));
	
	WriteL(aDb, aTableName, aColumnName, aData,	aRowId, aDbName);
	}

