ncdengine/provider/purchasehistory/src/ncdpurchasehistorydbimpl.cpp
changeset 0 ba25891c3a9e
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ncdengine/provider/purchasehistory/src/ncdpurchasehistorydbimpl.cpp	Thu Dec 17 08:51:10 2009 +0200
@@ -0,0 +1,1263 @@
+/*
+* Copyright (c) 2006 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 <eikenv.h>
+#include <f32file.h>
+#include <bautils.h>
+#include <s32mem.h>
+
+#include "ncdpurchasehistorydbimpl.h"
+#include "ncdpurchasedownloadinfo.h"
+#include "ncdpurchaseinstallinfo.h"
+#include "ncdpurchasehistoryfilter.h"
+#include "ncdnodefunctionids.h"
+#include "catalogssession.h"
+#include "catalogsbasemessage.h"
+#include "catalogsbigdes.h"
+#include "catalogsconstants.h"
+#include "catalogsutils.h"
+#include "ncdutils.h"
+
+#include "catalogsdebug.h"
+
+// Create a purchases table.
+// This column order is always used when inserting data or in queries.
+// Column numbering is stored in CNcdPurchaseHistoryDb::PurchaseColumns.
+_LIT( KSqlCreatePurchasesTable, "\
+ CREATE TABLE purchases ( \
+ purchase_id COUNTER,\
+ event_id UNSIGNED INTEGER,\
+ client_uid INTEGER,\
+ namespace LONG VARCHAR,\
+ entity_id LONG VARCHAR,\
+ item_name LONG VARCHAR,\
+ item_purpose UNSIGNED INTEGER,\
+ catalog_name LONG VARCHAR,\
+ download_info LONG VARBINARY,\
+ purchase_option_id LONG VARCHAR,\
+ purchase_option_name LONG VARCHAR,\
+ purchase_option_price LONG VARCHAR,\
+ final_price LONG VARCHAR,\
+ payment_method_name LONG VARCHAR,\
+ purchase_time BIGINT,\
+ downloaded_files LONG VARBINARY,\
+ file_install_infos LONG VARBINARY,\
+ icon LONG VARBINARY,\
+ downloadaccesspoint LONG VARCHAR,\
+ description LONG VARCHAR,\
+ version LONG VARCHAR,\
+ server_uri LONG VARCHAR,\
+ item_type INTEGER, \
+ total_content_size INTEGER, \
+ origin_node_id LONG VARCHAR, \
+ last_operation_time BIGINT, \
+ last_operation_error_code INTEGER, \
+ has_icon INTEGER, \
+ attributes LONG VARBINARY )" );
+
+
+// Create a event counter table.
+_LIT( KSqlCreateEventCounterTable,
+    "CREATE TABLE event_counter_table ( event_counter UNSIGNED INTEGER )" );
+
+// Event counter column number.
+const TInt KEventCounterColumnNumber = 1;
+
+
+// Select purchases.
+_LIT( KSqlPurchasesStart,
+     "SELECT purchase_id FROM purchases WHERE event_id >= " );
+_LIT( KSqlPurchasesNamespace, " AND namespace = '" );
+_LIT( KSqlPurchasesEntityId, " AND entity_id = '" );
+_LIT( KSqlPurchasesClientUid, " AND ( client_uid = " );
+_LIT( KSqlPurchasesOrClientUid, " OR client_uid = " );
+_LIT( KSqlPurchasesEndClientUid, " )" );
+_LIT( KSqlPurchasesEnd, "'" );
+_LIT( KSqlPurchasesOrderNewestFirst, " ORDER BY event_id DESC" );
+_LIT( KSqlPurchasesOrderOldestFirst, " ORDER BY event_id ASC" );
+
+// Select purchase to be updated.
+_LIT( KSqlPurchaseUpdateStart,
+     "SELECT * FROM purchases WHERE client_uid = " );
+_LIT( KSqlPurchasesUpdateNamespace, " AND namespace = '" );
+_LIT( KSqlPurchasesUpdateEntityId, "' AND entity_id = '" );
+_LIT( KSqlPurchasesUpdatePurchaseTime, "' AND purchase_time = " );
+_LIT( KSqlPurchasesUpdateEnd, "" );
+
+// Select purchase.
+_LIT( KSqlPurchaseByPurchaseIdStart,
+    "SELECT * FROM purchases WHERE purchase_id = " );
+_LIT( KSqlPurchaseByPurchaseIdEnd, "" );
+
+// Select all purchases.
+_LIT( KSqlPurchasesAllNone, "SELECT * FROM purchases" );
+
+// Delete specified purchase event.
+_LIT( KSqlPurchasesDeleteStart,
+    "DELETE FROM purchases WHERE purchase_id = " );
+_LIT( KSqlPurchasesDeleteEnd,   "" );
+
+
+// Select current event count.
+_LIT( KSqlCurrentEventCount,
+    "SELECT event_counter FROM event_counter_table" );
+
+// Delete event count from event count table.
+_LIT( KSqlEventCountDelete, "DELETE FROM event_counter_table" );
+
+// "-2147483648"
+const TInt KMaxLengthOfInt = 11;
+
+// "-9223372036854775808"
+const TInt KMaxLengthOfTint64 = 20;
+
+static void AppendWithQuotesDuplicatedL(
+    CCatalogsBigDes* aOutput,
+    const TDesC& aInput )
+    {
+    _LIT( KQuote, "'" );
+    
+    if ( aOutput == NULL || &aInput == NULL )
+        {
+        return;
+        }
+
+    TPtrC in = aInput.Mid( 0 );
+    while ( in.Length() > 0 )
+        {
+        TInt i = in.Locate( '\'' );
+        if ( i == KErrNotFound )
+            {
+            // Quote not found, all done
+            aOutput->AppendL( in );
+            return;
+            }
+        aOutput->AppendL( in.Mid( 0, i + 1 ) );
+        // Append extra quote
+        aOutput->AppendL( KQuote );
+        if ( i + 1 < in.Length() )
+            {
+            // Get end part of the input descriptor
+            in.Set( in.Mid( i + 1 ) );
+            }
+        else
+            {
+            // End of the input descriptor reached
+            return;
+            }
+        
+        }
+    }
+
+static HBufC* ReadLongTextColumnL( RDbRowSet aView, TDbColNo aCol )
+    {
+    TInt length = aView.ColLength( aCol );
+    if ( length == 0 )
+        {
+        return KNullDesC().Alloc();
+        }
+    RDbColReadStream readStream;
+    readStream.OpenLC( aView, aCol );
+    HBufC* result = HBufC::NewLC( length );
+    TPtr resultPtr = result->Des();
+    readStream.ReadL( resultPtr, length );
+    readStream.Close();
+    CleanupStack::Pop( result );
+    CleanupStack::Pop(); //readStream
+    return result;
+    }
+
+static void WriteLongTextColumnL(
+    RDbRowSet aView,
+    TDbColNo aCol,
+    const TDesC& aValue )
+    {
+    RDbColWriteStream writeStream;
+    writeStream.OpenLC( aView, aCol );
+    if ( &aValue )
+        {
+        writeStream.WriteL( aValue );
+        }
+    else
+        {
+        writeStream.WriteL( KNullDesC );
+        }
+    writeStream.Close();
+    CleanupStack::Pop( &writeStream );
+    }
+
+static HBufC8* ReadLongTextColumn8L( RDbRowSet aView, TDbColNo aCol )
+    {
+    TInt length = aView.ColLength( aCol );
+    if ( length == 0 )
+        {
+        return KNullDesC8().Alloc();
+        }
+    RDbColReadStream readStream;
+    readStream.OpenLC( aView, aCol );
+    HBufC8* result = HBufC8::NewLC( length );
+    TPtr8 resultPtr = result->Des();
+    readStream.ReadL( resultPtr, length );
+    readStream.Close();
+    CleanupStack::Pop( result );
+    CleanupStack::Pop(); //readStream
+    return result;
+    }
+
+static void WriteLongTextColumn8L(
+    RDbRowSet aView,
+    TDbColNo aCol,
+    const TDesC8& aValue )
+    {
+    RDbColWriteStream writeStream;
+    writeStream.OpenLC( aView, aCol );
+    if ( &aValue )
+        {
+        writeStream.WriteL( aValue );
+        }
+    else
+        {
+        writeStream.WriteL( KNullDesC8 );
+        }
+    writeStream.Close();
+    CleanupStack::Pop( &writeStream );
+    }
+
+
+CNcdPurchaseHistoryDb* CNcdPurchaseHistoryDb::NewL(
+    const TDesC& aDbFilename )
+    {
+    CNcdPurchaseHistoryDb* self = NewLC( aDbFilename );
+    CleanupStack::Pop( self );
+    return self;
+    }
+
+CNcdPurchaseHistoryDb* CNcdPurchaseHistoryDb::NewLC(
+    const TDesC& aDbFilename )
+    {
+    CNcdPurchaseHistoryDb* self =
+        new ( ELeave ) CNcdPurchaseHistoryDb();
+    CleanupClosePushL( *self );
+    self->ConstructL( aDbFilename );
+    return self;    
+    }
+
+CNcdPurchaseHistoryDb::~CNcdPurchaseHistoryDb()
+    {
+    iDatabase.Close();
+    delete iDbFilename;
+    iFs.Close();
+    }
+
+void CNcdPurchaseHistoryDb::ReceiveMessage(
+    MCatalogsBaseMessage* aMessage,
+    TInt aFunctionNumber )
+    {
+    DLTRACEIN((""));
+
+    DASSERT( aMessage );
+    
+    // Now, we can be sure that rest of the time iMessage exists.
+    // This member variable is set for the CounterPartLost function.
+    iMessage = aMessage;
+        
+    TInt trapError( KErrNone );
+        
+    switch( aFunctionNumber )
+        {
+        case NcdNodeFunctionIds::ENcdPurchaseHistorySavePurchase:
+            DLTRACE(("Insert purchase event"));
+            TRAP( trapError, SavePurchaseRequestL( *aMessage ) );
+            break;
+
+        case NcdNodeFunctionIds::ENcdPurchaseHistorySavePurchaseWithOldIcon:
+            DLTRACE(("Insert purchase event with old icon"));
+            TRAP( trapError, SavePurchaseRequestL( *aMessage, EFalse ) );
+            break;
+            
+        case NcdNodeFunctionIds::ENcdPurchaseHistoryRemovePurchase:
+            DLTRACE(("Remove purchase"));
+            TRAP( trapError, RemovePurchaseRequestL( *aMessage ) );
+            break;
+            
+        case NcdNodeFunctionIds::ENcdPurchaseHistoryGetPurchaseIds:
+            DLTRACE(("Get purchase IDs"));
+            TRAP( trapError, GetPurchaseIdsRequestL( *aMessage ) );
+            break;
+            
+        case NcdNodeFunctionIds::ENcdPurchaseHistoryGetPurchase:
+            DLTRACE(("Get purchase"));
+            TRAP( trapError, GetPurchaseRequestL( *aMessage, ETrue ) );
+            break;
+            
+        case NcdNodeFunctionIds::ENcdPurchaseHistoryGetPurchaseNoIcon:
+            DLTRACE(("Get purchase"));
+            TRAP( trapError, GetPurchaseRequestL( *aMessage, EFalse ) );
+            break;
+            
+        case NcdNodeFunctionIds::ENcdPurchaseHistoryEventCount:
+            DLTRACE(("Get purchase event count"));
+            TRAP( trapError, EventCountRequestL( *aMessage ) );
+            break;
+            
+        case NcdNodeFunctionIds::ENcdRelease:
+            DLTRACE(("Release purchase history"));
+            ReleaseRequest( *aMessage );
+            break;
+            
+        default:
+            break;
+        }
+
+    if ( trapError != KErrNone )
+        {
+        // Because something went wrong the complete has not been
+        // yet called for the message.
+        // So, inform the client about the error.
+        DLTRACE(("ERROR, Complete and release %d", trapError));
+        
+        aMessage->CompleteAndRelease( trapError );
+        }
+
+    // Because the message should not be used after this, set it NULL.
+    // So, CounterPartLost function will know that no messages are
+    // waiting the response at the moment.
+    iMessage = NULL;        
+            
+    DLTRACEOUT((""));
+    }
+
+void CNcdPurchaseHistoryDb::CounterPartLost(
+    const MCatalogsSession& aSession )
+    {
+    // This function may be called whenever -- when the message is waiting
+    // response or when the message does not exist.
+    // iMessage may be NULL here, because in the end of the
+    // ReceiveMessage it is set to NULL. The life time of the message
+    // ends shortly after CompleteAndRelease is called.
+    if ( iMessage != NULL )
+        {
+        iMessage->CounterPartLost( aSession );
+        }    
+    }
+
+void CNcdPurchaseHistoryDb::ConstructL( const TDesC& aDbFilename )
+    {
+    if ( aDbFilename == KNullDesC )
+        {
+        User::Leave( KErrBadName );
+        }
+        
+    User::LeaveIfError( iFs.Connect() );
+
+    // Set database name.
+    iDbFilename = aDbFilename.AllocL();
+    BaflUtils::EnsurePathExistsL( iFs, *iDbFilename );
+    TInt err = iDatabase.Open( iFs, *iDbFilename );
+
+    if ( err == KErrNotFound || err == KErrCorrupt )
+        {
+        // Delete existing database file. This should happen only if
+        // database file is corrupted. Ignoring errors as database file
+        // may not be present.
+        BaflUtils::DeleteFile( iFs, *iDbFilename );
+
+        // See if the database can be found from another drive
+        TFindFile finder( iFs );
+        CDir* dir = NULL;
+
+        err = finder.FindWildByDir( *iDbFilename, KNullDesC, dir );
+
+        if ( err == KErrNone )
+            {
+            // Move old database to correct folder and try to open it
+            CleanupStack::PushL( dir );
+            CFileMan* fileman = CFileMan::NewL( iFs );
+            CleanupStack::PushL( fileman );
+            User::LeaveIfError( fileman->Move( finder.File(),
+                                               *iDbFilename,
+                                               CFileMan::EOverWrite ) );
+            CleanupStack::PopAndDestroy( fileman );
+            CleanupStack::PopAndDestroy( dir );
+
+            // NOTE: Error ignored. If folder contains files,
+            // it will not be deleted.
+            iFs.RmDir( finder.File() );
+
+            err = iDatabase.Open( iFs, *iDbFilename );
+            if ( err == KErrNotFound || err == KErrCorrupt )
+                {
+                BaflUtils::DeleteFile( iFs, *iDbFilename );
+                // Trying to create the database
+                User::LeaveIfError( iDatabase.Create( iFs, *iDbFilename ) );
+                User::LeaveIfError(
+                    iDatabase.Execute( KSqlCreatePurchasesTable ) );
+                User::LeaveIfError(
+                    iDatabase.Execute( KSqlCreateEventCounterTable ) );
+                }
+            else if ( err != KErrNone )
+                {
+                User::Leave( err );
+                }
+            }
+        else
+            {
+            // Trying to create the database
+            User::LeaveIfError( iDatabase.Create( iFs, *iDbFilename ) );
+            User::LeaveIfError(
+                iDatabase.Execute( KSqlCreatePurchasesTable ) );
+            User::LeaveIfError(
+                iDatabase.Execute( KSqlCreateEventCounterTable ) );
+            }
+        }
+    else if ( err != KErrNone )
+        {
+        User::Leave( err );
+        }
+
+    if ( iDatabase.IsDamaged() )
+        {
+        // Database has been partly damaged. Try to recover it.
+        iDatabase.Recover();
+        }
+    }
+
+CNcdPurchaseHistoryDb::CNcdPurchaseHistoryDb()
+    : CCatalogsCommunicable()
+    {    
+    }
+
+void CNcdPurchaseHistoryDb::SavePurchaseL(
+    CNcdPurchaseDetails& aPurchase, 
+    TBool aSaveIcon )
+    {
+    DLTRACEIN((""));
+    // Ensure that purchase history handling bugs in PCD! don't crash the client :)
+    ValidatePurchaseDetailsL( aPurchase );
+    
+    CCatalogsBigDes* sqlStatement = CCatalogsBigDes::NewLC();
+    
+    sqlStatement->AppendL( KSqlPurchaseUpdateStart );
+    TBuf<KMaxLengthOfInt> intBuf;
+    intBuf.Num( aPurchase.ClientUid().iUid );
+    sqlStatement->AppendL( intBuf );
+    sqlStatement->AppendL( KSqlPurchasesUpdateNamespace );
+    AppendWithQuotesDuplicatedL( sqlStatement, aPurchase.Namespace() );
+    sqlStatement->AppendL( KSqlPurchasesUpdateEntityId );
+    AppendWithQuotesDuplicatedL( sqlStatement, aPurchase.EntityId() );
+    sqlStatement->AppendL( KSqlPurchasesUpdatePurchaseTime );
+    TBuf<KMaxLengthOfTint64> bigIntBuf;
+    bigIntBuf.Num( aPurchase.PurchaseTime().Int64() );
+    sqlStatement->AppendL( bigIntBuf );
+    sqlStatement->AppendL( KSqlPurchasesUpdateEnd );
+    
+    TUint newEventCount = EventCountL() + 1;
+
+    RDbView view;
+    CleanupClosePushL( view );
+
+    HBufC* sqlStatementBuf = sqlStatement->DesLC();
+    User::LeaveIfError(
+        view.Prepare( iDatabase, TDbQuery( *sqlStatementBuf ) ) );
+    CleanupStack::PopAndDestroy( sqlStatementBuf );
+
+    User::LeaveIfError( view.EvaluateAll() );
+    view.FirstL();
+
+    if ( view.AtRow() )
+        {
+        UpdatePurchaseL( view, aPurchase, newEventCount, aSaveIcon );
+        }
+    else
+        {
+        view.Close();
+        NewPurchaseL( aPurchase, newEventCount );
+        }
+        
+    // Set new purchase count
+    SetEventCountL( newEventCount );
+
+    CleanupStack::PopAndDestroy( &view );
+    CleanupStack::PopAndDestroy( sqlStatement );
+    DLTRACEOUT((""));
+    }
+
+void CNcdPurchaseHistoryDb::RemovePurchaseL( TUint aPurchaseId )
+    {
+    if ( ! PurchaseExistsL( aPurchaseId ) )
+        {
+        // Purchase didn't exist in the database.
+        User::Leave( KErrNotFound );
+        }
+
+    TInt maxLength = KSqlPurchasesDeleteStart().Length() + KMaxLengthOfInt
+        + KSqlPurchasesDeleteEnd().Length();
+
+    HBufC* sqlStatement = HBufC::NewLC( maxLength );
+    TPtr statementPtr( sqlStatement->Des() );
+
+    statementPtr.Copy( KSqlPurchasesDeleteStart );
+    statementPtr.AppendNum( aPurchaseId );
+    statementPtr.Append( KSqlPurchasesDeleteEnd );
+
+    // Remove purchase from the database using event ID.
+    User::LeaveIfError( iDatabase.Execute( *sqlStatement ) );
+    // Remove all unnecessary data from the database.
+    User::LeaveIfError( iDatabase.Compact() );
+
+    CleanupStack::PopAndDestroy( sqlStatement );
+    }
+
+RArray<TUint> CNcdPurchaseHistoryDb::PurchaseIdsL(
+    const CNcdPurchaseHistoryFilter& aFilter,
+    const TSortingOrder aSortingOrder )
+    {
+    CCatalogsBigDes* sqlStatement = CCatalogsBigDes::NewLC();
+
+    // Construct the SQL query.
+    sqlStatement->AppendL( KSqlPurchasesStart );
+    TBuf<KMaxLengthOfInt> eventId;
+    eventId.Num( aFilter.EventId() );
+    sqlStatement->AppendL( eventId );
+    
+    if ( aFilter.Namespace().Compare( KNullDesC ) != 0 )
+        {
+        sqlStatement->AppendL( KSqlPurchasesNamespace );
+        AppendWithQuotesDuplicatedL( sqlStatement, aFilter.Namespace() );
+        sqlStatement->AppendL( KSqlPurchasesEnd );
+        }
+        
+    if ( aFilter.EntityId().Compare( KNullDesC ) != 0 )
+        {
+        sqlStatement->AppendL( KSqlPurchasesEntityId );
+        AppendWithQuotesDuplicatedL( sqlStatement, aFilter.EntityId() );
+        sqlStatement->AppendL( KSqlPurchasesEnd );
+        }
+    
+    if ( aFilter.ClientUids().Count() > 0 )
+        {
+        TArray< TUid > uids = aFilter.ClientUids();
+        TInt count = uids.Count();
+        sqlStatement->AppendL( KSqlPurchasesClientUid );
+        for ( TInt i = 0; i < count; i++ )
+            {
+            TBuf<KMaxLengthOfInt> clientUid;
+            clientUid.Num( uids[i].iUid );
+            sqlStatement->AppendL( clientUid );
+            if ( i + 1 < count )
+                {
+                // More UIDs in the filter.
+                sqlStatement->AppendL( KSqlPurchasesOrClientUid );
+                }
+            }
+        sqlStatement->AppendL( KSqlPurchasesEndClientUid );
+        }
+
+    switch ( aSortingOrder )
+        {
+        case CNcdPurchaseHistoryDb::ENewestFirst:
+            sqlStatement->AppendL( KSqlPurchasesOrderNewestFirst );
+            break;
+        case CNcdPurchaseHistoryDb::EOldestFirst:
+            sqlStatement->AppendL( KSqlPurchasesOrderOldestFirst );
+            break;
+        case CNcdPurchaseHistoryDb::ENone:
+        default:
+            break;
+        }
+
+    RDbView view;
+    CleanupClosePushL( view );
+
+    HBufC* sqlStatementBuf = sqlStatement->DesLC();
+    User::LeaveIfError(
+        view.Prepare( iDatabase, TDbQuery( *sqlStatementBuf ) ) );
+    CleanupStack::PopAndDestroy( sqlStatementBuf );
+
+    User::LeaveIfError( view.EvaluateAll() );
+    view.FirstL();
+
+    RArray<TUint> purchaseIds;
+    CleanupClosePushL( purchaseIds );
+
+    while( view.AtRow() )
+        {
+        view.GetL();
+        purchaseIds.Append(
+            view.ColUint32( CNcdPurchaseHistoryDb::EPurchaseId ) );
+        view.NextL();
+        }
+
+    CleanupStack::Pop( &purchaseIds );
+    CleanupStack::PopAndDestroy( &view );
+    CleanupStack::PopAndDestroy( sqlStatement );
+    
+    return purchaseIds;
+    }
+
+CNcdPurchaseDetails* CNcdPurchaseHistoryDb::PurchaseL( 
+    TUint aPurchaseId,
+    TBool aLoadIcon )
+    {
+    TInt maxLength = KSqlPurchaseByPurchaseIdStart().Length()
+        + KMaxLengthOfInt
+        + KSqlPurchaseByPurchaseIdEnd().Length();
+
+    HBufC* sqlStatement = HBufC::NewLC( maxLength );
+    TPtr statementPtr( sqlStatement->Des() );
+
+    statementPtr.Copy( KSqlPurchaseByPurchaseIdStart );
+    statementPtr.AppendNum( aPurchaseId );
+    statementPtr.Append( KSqlPurchaseByPurchaseIdEnd );
+
+    RDbView view;
+    CleanupClosePushL( view );
+
+    User::LeaveIfError(
+        view.Prepare( iDatabase, TDbQuery( *sqlStatement ) ) );
+
+    User::LeaveIfError( view.EvaluateAll() );
+    view.FirstL();
+
+    CNcdPurchaseDetails* details = NULL;
+
+    if ( view.AtRow() )
+        {
+        view.GetL();
+        
+        details = CNcdPurchaseDetails::NewLC();
+        
+        details->SetClientUid( TUid::Uid( view.ColInt32( EClientUid ) ) );
+        details->SetNamespace( ReadLongTextColumnL( view, ENamespace ) );
+        details->SetEntityId( ReadLongTextColumnL( view, EEntityId ) );
+        details->SetItemName( ReadLongTextColumnL( view, EItemName ) );
+        details->SetItemPurpose( view.ColUint32( EItemPurpose ) );
+        details->SetCatalogSourceName(
+            ReadLongTextColumnL( view, ECatalogName ) );
+
+        // Get download info buffer from database
+        HBufC8* downloadBuf = ReadLongTextColumn8L( view, EDownloadInfo );
+        CleanupStack::PushL( downloadBuf );
+
+        if ( downloadBuf->Length() > 0 )
+            {
+            // There is some data.
+            RDesReadStream readStream( *downloadBuf );
+            CleanupClosePushL( readStream );
+
+            // Get count of download infos.
+            TInt count = readStream.ReadInt32L();
+            for ( TInt i = 0; i < count; i++ )
+                {
+                // Internalize download infos from the buffer.
+
+                CNcdPurchaseDownloadInfo* info =
+                    CNcdPurchaseDownloadInfo::NewLC();
+
+                info->InternalizeL( readStream );
+                details->AddDownloadInfoL( info );
+
+                CleanupStack::Pop( info );
+                }
+
+            CleanupStack::PopAndDestroy( &readStream );
+            }
+
+        CleanupStack::PopAndDestroy( downloadBuf );
+
+        details->SetPurchaseOptionId(
+            ReadLongTextColumnL( view, EPurchaseOptionId ) );
+        details->SetPurchaseOptionName(
+            ReadLongTextColumnL( view, EPurchaseOptionName ) );
+        details->SetPurchaseOptionPrice(
+            ReadLongTextColumnL( view, EPurchaseOptionPrice ) );
+        details->SetFinalPrice( ReadLongTextColumnL( view, EFinalPrice ) );
+        details->SetPaymentMethodName(
+            ReadLongTextColumnL( view, EPaymentMethodName ) );
+        details->SetPurchaseTime( view.ColInt64( EPurchaseTime ) );
+
+        HBufC8* filesBuf = ReadLongTextColumn8L( view, EDownloadedFiles );
+        CleanupStack::PushL( filesBuf );
+        CDesCArray* downloadedFiles =
+            new (ELeave) CDesCArrayFlat( KListGranularity );
+        CleanupStack::PushL( downloadedFiles );
+        
+        if ( filesBuf->Length() > 0 )
+            {
+            // There is some data.
+            RDesReadStream readStream( *filesBuf );
+            CleanupClosePushL( readStream );
+
+            TInt filesCount = readStream.ReadInt32L();
+            for ( TInt i = 0; i < filesCount; i++ )
+                {
+                HBufC* buf = NULL;
+                InternalizeDesL( buf, readStream );
+                CleanupStack::PushL( buf );
+                downloadedFiles->AppendL( *buf );
+                CleanupStack::PopAndDestroy( buf );
+                }
+
+            CleanupStack::PopAndDestroy( &readStream );
+            }
+
+        details->SetDownloadedFiles( downloadedFiles );
+        CleanupStack::Pop( downloadedFiles );
+        CleanupStack::PopAndDestroy( filesBuf );
+        
+        // Get install info buffer from database
+        HBufC8* installBuf = ReadLongTextColumn8L( view, EFileInstallInfos );
+        CleanupStack::PushL( installBuf );
+
+        if ( installBuf->Length() > 0 )
+            {
+            // There is some data.
+            RDesReadStream readStream( *installBuf );
+            CleanupClosePushL( readStream );
+
+            // Get count of install infos.
+            TInt count = readStream.ReadInt32L();
+            for ( TInt i = 0; i < count; i++ )
+                {
+                // Internalize install infos from the buffer.
+
+                CNcdPurchaseInstallInfo* info =
+                    CNcdPurchaseInstallInfo::NewLC();
+
+                info->InternalizeL( readStream );
+                details->AddInstallInfoL( info );
+
+                CleanupStack::Pop( info );
+                }
+
+            CleanupStack::PopAndDestroy( &readStream );
+            }
+
+        CleanupStack::PopAndDestroy( installBuf );
+        
+        if ( aLoadIcon ) 
+            {
+            DLTRACE(("Loading icon from the purchase history"));            
+            details->SetIcon( ReadLongTextColumn8L( view, EIcon ) );
+            }
+        details->SetDownloadAccessPoint( ReadLongTextColumnL( view, EDownloadAccessPoint ) );
+        details->SetDescription( ReadLongTextColumnL( view, EDescription ) );
+        details->SetVersion( ReadLongTextColumnL( view, EVersion ) );
+        details->SetServerUri( ReadLongTextColumnL( view, EServerUri ) );
+        details->SetItemType( (MNcdPurchaseDetails::TItemType)view.ColInt32( EItemType ) );
+        details->SetTotalContentSize( view.ColInt32( ETotalContentSize ) );
+        details->SetOriginNodeId( ReadLongTextColumnL( view, EOriginNodeId ) );
+        details->SetLastOperationTime( view.ColInt64( ELastOperationTime ) );
+        details->SetLastOperationErrorCode( view.ColInt32( ELastOperationErrorCode ) );
+        details->SetHasIcon( view.ColInt32( EHasIcon ) );
+        
+        // Get attributes buffer from database
+        HBufC8* attributesBuf = ReadLongTextColumn8L( view, EAttributes );
+        CleanupStack::PushL( attributesBuf );
+
+        if ( attributesBuf->Length() > 0 )
+            {
+            // There is some data.
+            RDesReadStream readStream( *attributesBuf );
+            CleanupClosePushL( readStream );
+            details->InternalizeAttributesL( readStream );
+            CleanupStack::PopAndDestroy( &readStream );
+            }
+
+        CleanupStack::PopAndDestroy( attributesBuf );
+        }
+
+    if ( ! details )
+        {
+        User::Leave( KErrNotFound );
+        }
+
+    // Ensure that purchase history handling bugs in PCD! don't crash the client :)
+    ValidatePurchaseDetailsL( *details );
+    
+    CleanupStack::Pop( details );
+    CleanupStack::PopAndDestroy( &view );
+    CleanupStack::PopAndDestroy( sqlStatement );
+    
+    return details;
+    }
+
+TUint CNcdPurchaseHistoryDb::EventCountL()
+    {
+    TUint eventCount = 0;
+
+    RDbView view;
+    CleanupClosePushL( view );
+
+    User::LeaveIfError( view.Prepare(
+        iDatabase,
+        TDbQuery( KSqlCurrentEventCount ),
+        RDbView::EReadOnly ) );
+
+    view.EvaluateAll();
+    view.FirstL();
+
+    if ( view.AtRow() )
+        {
+        view.GetL();
+        eventCount = view.ColUint32( KEventCounterColumnNumber );
+        }
+
+    CleanupStack::PopAndDestroy( &view );
+    
+    return eventCount;
+    }
+
+void CNcdPurchaseHistoryDb::SavePurchaseRequestL( MCatalogsBaseMessage& aMessage, 
+                                                  TBool aSaveIcon )
+    {
+    TInt inputLength = aMessage.InputLength();
+    User::LeaveIfError( inputLength );
+    
+    HBufC8* message = HBufC8::NewLC( inputLength );
+    TPtr8 ptr = message->Des();
+    User::LeaveIfError( aMessage.ReadInput( ptr ) );
+    
+    CNcdPurchaseDetails* details = CNcdPurchaseDetails::NewLC();
+    
+    RDesReadStream stream( *message );
+    CleanupClosePushL( stream );
+    
+    details->InternalizeL( stream );
+    
+    CleanupStack::PopAndDestroy( &stream );
+    
+    SavePurchaseL( *details, aSaveIcon );
+    
+    CleanupStack::PopAndDestroy( details );
+    CleanupStack::PopAndDestroy( message );
+    
+    aMessage.CompleteAndRelease( KErrNone );
+    }
+
+void CNcdPurchaseHistoryDb::RemovePurchaseRequestL( MCatalogsBaseMessage& aMessage )
+    {
+    TInt inputLength = aMessage.InputLength();
+    User::LeaveIfError( inputLength );
+    
+    HBufC8* message = HBufC8::NewLC( inputLength );
+    TPtr8 ptr = message->Des();
+    User::LeaveIfError( aMessage.ReadInput( ptr ) );
+    
+    TUint purchaseId = Des8ToUint( *message );
+    
+    CleanupStack::PopAndDestroy( message );
+    
+    RemovePurchaseL( purchaseId );
+    
+    aMessage.CompleteAndRelease( KErrNone );
+    }
+
+void CNcdPurchaseHistoryDb::GetPurchaseIdsRequestL( MCatalogsBaseMessage& aMessage )
+    {
+    TInt inputLength = aMessage.InputLength();
+    User::LeaveIfError( inputLength );
+    
+    HBufC8* message = HBufC8::NewLC( inputLength );
+    TPtr8 ptr = message->Des();
+    User::LeaveIfError( aMessage.ReadInput( ptr ) );
+    
+    CNcdPurchaseHistoryFilter* filter = CNcdPurchaseHistoryFilter::NewLC();
+    
+    RDesReadStream stream( *message );
+    CleanupClosePushL( stream );
+
+    filter->InternalizeL( stream );
+    
+    CleanupStack::PopAndDestroy( &stream );
+    
+    RArray<TUint> purchaseIds = PurchaseIdsL( *filter, ENewestFirst );
+    
+    CleanupStack::PopAndDestroy( filter );
+    CleanupStack::PopAndDestroy( message );
+    
+    CleanupClosePushL( purchaseIds );
+
+    TUint count = purchaseIds.Count();
+
+    message = HBufC8::NewLC( ( count + 1 ) * sizeof(TUint) );
+    
+    HBufC8* temp = UintToDes8LC( count );
+    message->Des().Copy( *temp );
+    CleanupStack::PopAndDestroy( temp );
+    
+    for ( TInt i = 0; i < count; i++ )
+        {
+        temp = UintToDes8LC( purchaseIds[i] );
+        message->Des().Append( *temp );
+        CleanupStack::PopAndDestroy( temp );
+        }
+
+    aMessage.CompleteAndReleaseL( *message, KErrNone );
+    
+    CleanupStack::PopAndDestroy( message );
+    
+    CleanupStack::PopAndDestroy( &purchaseIds );
+    }
+
+void CNcdPurchaseHistoryDb::GetPurchaseRequestL( 
+    MCatalogsBaseMessage& aMessage,
+    TBool aLoadIcon )
+    {
+    DLTRACEIN(("aLoadIcon: %d", aLoadIcon));
+    TInt inputLength = aMessage.InputLength();
+    User::LeaveIfError( inputLength );
+    
+    HBufC8* message = HBufC8::NewLC( inputLength );
+    TPtr8 ptr = message->Des();
+    User::LeaveIfError( aMessage.ReadInput( ptr ) );
+
+    TUint purchaseId = Des8ToUint( *message );
+
+    CleanupStack::PopAndDestroy( message );
+
+    CNcdPurchaseDetails* details = PurchaseL( purchaseId, aLoadIcon );
+    if ( details )
+        {
+        CleanupStack::PushL( details );
+        
+        CBufBase* buf = CBufFlat::NewL( KBufExpandSize );
+        CleanupStack::PushL( buf );
+        
+        RBufWriteStream stream( *buf );
+        CleanupClosePushL( stream );
+        
+        details->ExternalizeL( stream );
+        
+        CleanupStack::PopAndDestroy( &stream );
+
+        aMessage.CompleteAndReleaseL( buf->Ptr( 0 ), KErrNone );
+        
+        CleanupStack::PopAndDestroy( buf );
+        CleanupStack::PopAndDestroy( details );
+        }
+    else
+        {
+        aMessage.CompleteAndRelease( KErrNotFound );
+        }
+    }
+
+void CNcdPurchaseHistoryDb::EventCountRequestL( MCatalogsBaseMessage& aMessage )
+    {
+    TUint count = EventCountL();
+    
+    HBufC8* message = UintToDes8LC( count );
+    
+    aMessage.CompleteAndReleaseL( *message, KErrNone );
+    
+    CleanupStack::PopAndDestroy( message );
+    }
+
+void CNcdPurchaseHistoryDb::ReleaseRequest( MCatalogsBaseMessage& aMessage )
+    {
+    DLTRACEIN((""));
+
+    // Decrease the reference count for this object.
+    // When the reference count reaches zero, this object will be destroyed
+    // and removed from the session.
+    MCatalogsSession& requestSession( aMessage.Session() );
+    TInt handle( aMessage.Handle() );
+
+    // Send complete information back to proxy.
+    aMessage.CompleteAndRelease( KErrNone );
+        
+    // Remove this object from the session.
+    requestSession.RemoveObject( handle );
+        
+    DLTRACEOUT((""));
+    }
+
+void CNcdPurchaseHistoryDb::NewPurchaseL(
+    CNcdPurchaseDetails& aPurchase,
+    TUint aNewEventCount )
+    {
+    // Construct database view.
+    RDbView view;
+    CleanupClosePushL( view );
+    User::LeaveIfError( view.Prepare(
+        iDatabase,
+        TDbQuery( KSqlPurchasesAllNone ),
+        RDbView::EInsertOnly ) );
+    view.InsertL();
+    
+    // Set initial value for EHasIcon here. This is needed because in SetPurchaseViewL
+    // the flag is never unset after being set to true. The reason for this is that
+    // the flag is unkwnown to PCD! Adapter and when it syncs PH data with the engine
+    // it cannot set the flag correctly. Therefore the flag is always kept as true 
+    // regardless of what PCD! Adapter sends in the sync. This is correct because
+    // once set, the icon data is never removed from the purchase detail entry.
+    view.SetColL( EHasIcon, EFalse );
+    
+    SetPurchaseViewL( view, aPurchase, aNewEventCount, ETrue );
+    
+    
+    // Insert purchase into the database
+    view.PutL();
+    view.Close();
+    CleanupStack::PopAndDestroy( &view );
+    }
+
+
+void CNcdPurchaseHistoryDb::UpdatePurchaseL(
+    RDbRowSet& aView,
+    CNcdPurchaseDetails& aPurchase,
+    TUint aNewEventCount,
+    TBool aSaveIcon )
+    {
+    aView.UpdateL();
+    
+    SetPurchaseViewL( aView, aPurchase, aNewEventCount, aSaveIcon );
+
+    // Update purchase details.
+    aView.PutL();
+    }
+
+
+void CNcdPurchaseHistoryDb::SetPurchaseViewL(
+    RDbRowSet& aView,
+    CNcdPurchaseDetails& aPurchase,
+    TUint aNewEventCount,
+    TBool aSaveIcon )
+    {
+    aView.SetColL( EEventId, aNewEventCount );
+    aView.SetColL( EClientUid, aPurchase.ClientUid().iUid );
+    WriteLongTextColumnL( aView, ENamespace, aPurchase.Namespace() );
+    WriteLongTextColumnL( aView, EEntityId, aPurchase.EntityId() );
+    WriteLongTextColumnL( aView, EItemName, aPurchase.ItemName() );
+    aView.SetColL( EItemPurpose, aPurchase.ItemPurpose() );
+    WriteLongTextColumnL(
+        aView,
+        ECatalogName,
+        aPurchase.CatalogSourceName() );
+
+    CBufBase* downloadBuf = CBufFlat::NewL( KBufExpandSize );
+    CleanupStack::PushL( downloadBuf );
+
+    RBufWriteStream downloadStream( *downloadBuf );
+    CleanupClosePushL( downloadStream );
+
+    TInt downloadCount = aPurchase.DownloadInfoCount();
+    downloadStream.WriteInt32L( downloadCount );
+    for ( TInt i = 0; i < downloadCount; i++ )
+        {
+        // Download information present.
+
+        CNcdPurchaseDownloadInfo& info = aPurchase.DownloadInfo( i );
+        info.ExternalizeL( downloadStream );
+        }
+
+    CleanupStack::PopAndDestroy( &downloadStream );
+    TPtr8 downloadPtr = downloadBuf->Ptr( 0 );
+    WriteLongTextColumn8L( aView, EDownloadInfo, downloadPtr );
+    CleanupStack::PopAndDestroy( downloadBuf );
+
+    WriteLongTextColumnL( aView, EPurchaseOptionId, aPurchase.PurchaseOptionId() );
+    WriteLongTextColumnL( aView, EPurchaseOptionName, aPurchase.PurchaseOptionName() );
+    WriteLongTextColumnL( aView, EPurchaseOptionPrice, aPurchase.PurchaseOptionPrice() );
+    WriteLongTextColumnL( aView, EFinalPrice, aPurchase.FinalPrice() );
+    WriteLongTextColumnL( aView, EPaymentMethodName, aPurchase.PaymentMethodName() );
+    aView.SetColL( EPurchaseTime, aPurchase.PurchaseTime().Int64() );
+
+    const MDesCArray& downloadedFiles = aPurchase.DownloadedFiles();
+
+    CBufBase* filesBuf = CBufFlat::NewL( KBufExpandSize );
+    CleanupStack::PushL( filesBuf );
+
+    RBufWriteStream filesStream( *filesBuf );
+    CleanupClosePushL( filesStream );
+
+    TInt filesCount = downloadedFiles.MdcaCount();
+    filesStream.WriteInt32L( filesCount );
+    for ( TInt i = 0; i < filesCount; i++ )
+        {
+        // Downloaded files present.
+
+        ExternalizeDesL( downloadedFiles.MdcaPoint( i ), filesStream );
+        }
+
+    CleanupStack::PopAndDestroy( &filesStream );
+    TPtr8 filesPtr = filesBuf->Ptr( 0 );
+    WriteLongTextColumn8L( aView, EDownloadedFiles, filesPtr );
+    CleanupStack::PopAndDestroy( filesBuf );
+
+    CBufBase* installBuf = CBufFlat::NewL( KBufExpandSize );
+    CleanupStack::PushL( installBuf );
+
+    RBufWriteStream installStream( *installBuf );
+    CleanupClosePushL( installStream );
+
+    TInt installCount = aPurchase.InstallInfoCount();
+    installStream.WriteInt32L( installCount );
+    for ( TInt i = 0; i < installCount; i++ )
+        {
+        // Install information present.
+
+        CNcdPurchaseInstallInfo& info = aPurchase.InstallInfo( i );
+        info.ExternalizeL( installStream );
+        }
+
+    CleanupStack::PopAndDestroy( &installStream );
+    TPtr8 installPtr = installBuf->Ptr( 0 );
+    WriteLongTextColumn8L( aView, EFileInstallInfos, installPtr );
+    CleanupStack::PopAndDestroy( installBuf );
+    
+    if ( aSaveIcon ) 
+        {
+        DLTRACE(("Saving the icon to purchase history"));
+        WriteLongTextColumn8L( aView, EIcon, aPurchase.Icon() );
+        }
+    WriteLongTextColumnL(
+        aView,
+        EDownloadAccessPoint,
+        aPurchase.DownloadAccessPoint() );
+    WriteLongTextColumnL( aView, EDescription, aPurchase.Description() );
+    WriteLongTextColumnL( aView, EVersion, aPurchase.Version() );
+    WriteLongTextColumnL( aView, EServerUri, aPurchase.ServerUri() );
+    aView.SetColL( EItemType, aPurchase.ItemType() );
+    aView.SetColL( ETotalContentSize, aPurchase.TotalContentSize() );
+    WriteLongTextColumnL( aView, EOriginNodeId, aPurchase.OriginNodeId() );
+    aView.SetColL( ELastOperationTime, aPurchase.LastOperationTime().Int64() );
+    aView.SetColL( ELastOperationErrorCode, aPurchase.LastOperationErrorCode() );
+    
+    if( aPurchase.HasIcon() )
+        {
+        // HasIcon flag can be set but never unset.
+        aView.SetColL( EHasIcon, ETrue );
+        }
+    
+    // Externalize attributes
+    RCatalogsBufferWriter attributeWriter;
+    attributeWriter.OpenLC();
+    aPurchase.ExternalizeAttributesL( attributeWriter() );    
+    WriteLongTextColumn8L( aView, EAttributes, attributeWriter.PtrL() );
+    CleanupStack::PopAndDestroy( &attributeWriter );
+    
+    }
+
+void CNcdPurchaseHistoryDb::SetEventCountL( TUint aEventCount )
+    {
+    // Remove old event count from the database.
+    User::LeaveIfError( iDatabase.Execute( KSqlEventCountDelete ) );
+    
+    // Construct database view.
+    RDbView view;
+    User::LeaveIfError( view.Prepare(
+        iDatabase,
+        TDbQuery( KSqlCurrentEventCount ),
+        RDbView::EInsertOnly ) );
+    view.InsertL();
+
+    view.SetColL( KEventCounterColumnNumber, aEventCount );
+
+    // Insert event count into the database
+    view.PutL();
+    view.Close();
+    
+    // Remove all unnecessary data from the database.
+    User::LeaveIfError( iDatabase.Compact() );
+    }
+
+TBool CNcdPurchaseHistoryDb::PurchaseExistsL( TUint aPurchaseId )
+    {
+    TInt maxLength = KSqlPurchaseByPurchaseIdStart().Length()
+        + KMaxLengthOfInt
+        + KSqlPurchaseByPurchaseIdEnd().Length();
+
+    HBufC* sqlStatement = HBufC::NewLC( maxLength );
+    TPtr statementPtr( sqlStatement->Des() );
+
+    statementPtr.Copy( KSqlPurchaseByPurchaseIdStart );
+    statementPtr.AppendNum( aPurchaseId );
+    statementPtr.Append( KSqlPurchaseByPurchaseIdEnd );
+
+    RDbView view;
+    CleanupClosePushL( view );
+
+    User::LeaveIfError(
+        view.Prepare( iDatabase, TDbQuery( *sqlStatement ) ) );
+
+    User::LeaveIfError( view.EvaluateAll() );
+    view.FirstL();
+
+    TBool exists = EFalse;
+    
+    if ( view.AtRow() )
+        {
+        exists = ETrue;
+        }
+
+    CleanupStack::PopAndDestroy( &view );
+    CleanupStack::PopAndDestroy( sqlStatement );
+    
+    return exists;
+    }
+
+
+void CNcdPurchaseHistoryDb::ValidatePurchaseDetailsL( 
+    CNcdPurchaseDetails& aDetails )
+    {
+    DLTRACEIN((""));
+    
+    TInt dlInfoCount = aDetails.DownloadInfoCount();
+    
+    // Add/remove filepaths so that there's as many filepaths as there are
+    // download infos. Download infos determine the real count because they
+    // are received from the protocol
+    TInt filePathDiff = dlInfoCount - aDetails.DownloadedFiles().MdcaCount();
+    if ( filePathDiff > 0 ) 
+        {
+        DLTRACE(("Adding %d empty filepaths", filePathDiff ));
+        while ( filePathDiff-- ) 
+            {
+            aDetails.AddDownloadedFileL( KNullDesC() );
+            }        
+        }
+    else if ( filePathDiff < 0 ) // This shouldn't really ever happen
+        {        
+        DLTRACE(("Removing %d filepaths", -filePathDiff ));
+        // Start from the end
+        TInt index = aDetails.DownloadedFiles().MdcaCount() - 1;
+        
+        filePathDiff = index + filePathDiff;
+        for ( ; index > filePathDiff; index-- ) 
+            {
+            aDetails.RemoveDownloadedFile( index );            
+            }
+        }
+    
+    // install info count must be <= download info count
+    TInt installDiff = dlInfoCount - aDetails.InstallInfoCount();
+    if ( installDiff < 0 ) // This shouldn't really ever happen
+        {
+        DLTRACE(("Removing %d install infos", -installDiff ));
+        // Start from the end
+        TInt index = aDetails.InstallInfoCount() - 1;
+        
+        installDiff = index + installDiff;
+        for ( ; index > installDiff; index-- ) 
+            {
+            aDetails.RemoveInstallInfo( index );            
+            }        
+        }
+    }
+