cbs/CbsServer/ServerSrc/CCbsDbImpTopicList.cpp
author Dremov Kirill (Nokia-D-MSW/Tampere) <kirill.dremov@nokia.com>
Fri, 17 Sep 2010 08:33:29 +0300
changeset 50 2313cb430f28
parent 0 ff3b6d0fd310
permissions -rw-r--r--
Revision: 201035 Kit: 201037

/*
* Copyright (c) 2003 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:  This file contains the implementation of the CCbsDbImpTopicList class
*            member functions.
*    
*                This class represents the topic list contained in the database.
*
*/



// INCLUDE FILES
#include <e32base.h>
#include <e32svr.h>
#include <f32file.h>

#include <shareddataclient.h>   // RequestFreeDiskSpaceLC

#include <barsc.h>              // Resource access
#include <barsread.h>           // Resource access

#include <cbsserver.rsg>

#include "CbsServerPanic.h"
#include "CbsStreamHelper.h"
#include "CbsDbConstants.h"
#include "CbsUtils.h"
#include "CCbsDbImpTopicList.h"
#include "MCbsDbTopicListObserver.H"
#include "CCbsDbImpTopicMessages.h"

#include "CbsLogger.h"

#include <centralrepository.h>  // for local variation
#include "cbsinternalcrkeys.h"  // for local variation
#include "cbsvariant.hrh"       // for local variation
 
// CONSTANTS

// Initial size for topic cache array
const TInt KDefaultTopicListSize = 10;

// Size of the topic stream, used in checking against FFS free space limit
const TInt KTopicStreamSize = 92;

// Size of the topic messages stream, FFS critical level check
const TInt KEmptyTopicMessagesStreamSize = 4;

// Size of topic list stream, FFS critical level check
const TInt KTopicListStreamSize = 85;

// Size of topic list stream (root), FFS critical level check
const TInt KTopicListRootStreamSize = 2;

// Time in microseconds to wait after a critical store exception 
// before a recovery attempt is made.
const TInt KWaitAfterCriticalStoreException = 2000000;

// Used when unsaved message stream ids are deleted from topic messages 
// stream
const TInt KTypicalNumberOfTopicMessages = 6;

// Minimum interval between compacting the stores in minutes
const TInt KMinimumCompactInterval = 30;

// Number of the index topic
const TInt KIndexTopicNumber = 0;

// Space for reading messages
const TInt KReadMessageSize = 92;

const TInt KTopicsGranularity = 1;

const TInt KTopicListsGranularity = 1;

const TInt KTopicIdsGranularity = 5;

// ================= MEMBER FUNCTIONS =======================

// -----------------------------------------------------------------------------
// CCbsDbImpTopicList::CCbsDbImpTopicList
// C++ default constructor can NOT contain any code, that
// might leave.
// -----------------------------------------------------------------------------
//
CCbsDbImpTopicList::CCbsDbImpTopicList( 
    RFs& aFs, 
    CCbsDbImp& aDatabase )
    : iDatabase( aDatabase ), 
    iFs( aFs ),
    iTopicCount( 0 ),
    iInitializing( EFalse ),
    iDeleteAllTopics( EFalse )
    {
    }

// -----------------------------------------------------------------------------
// CCbsDbImpTopicList::ConstructL
// Symbian 2nd phase constructor can leave.
// -----------------------------------------------------------------------------
//
void CCbsDbImpTopicList::ConstructL( 
    const TDesC& aTopicsFile,
    const TDesC& aUnsavedMessagesFile )
    {
    CBSLOGSTRING("CBSSERVER: >>> CCbsDbImpTopicList::ConstructL()");
    
    // Create observer array
    iObservers = new ( ELeave ) CArrayFixFlat< MCbsDbTopicListObserver* >( 
        KCbsDbObserverArraySize );

    // Create topic array, additional memory size in new alloc, 1*192 B = 192 B
    iTopics = new ( ELeave ) CArrayFixFlat< TCbsDbImpTopic >( KTopicsGranularity );

    // Create the root item table, additional memory size in new alloc, 1*184 B = 184 B
    iTopicLists = new ( ELeave ) CArrayFixFlat< TCbsDbImpTopicList >( KTopicListsGranularity ); 

    // Create topic ID array, additional memory size in new alloc, 5*4 B = 20 B
    iTopicIds = new ( ELeave ) CArrayFixFlat< TStreamId >( KTopicIdsGranularity ); 

    // Initialize iPreviousCompact to now
    iPreviousCompact.UniversalTime();

    // Copy datafile names from parameters
    iTopicsFilename = aTopicsFile.AllocL();
    iUnsavedMessagesFilename = aUnsavedMessagesFile.AllocL();
    
    // Fetch local variation bits from CenRep    
    CRepository* repository = CRepository::NewL( KCRUidCbsVariation );    
    TInt err = repository->Get( KCbsVariationFlags, iLVBits );
    CBSLOGSTRING2("CBSSERVER: CCbsRecEtel::ConstructL(): CenRep error: %d", err );    
    if ( err )
        {
        iLVBits = 0;
        }    
    delete repository;

    // If aLoadFactorySettings, then the files are recreated and initialized.
    // If not, then the nonexisting files are created, files opened and
    // data internalized.

    TBool unsavedMsgFileExists( CbsUtils::ExistsL( iFs, *iUnsavedMessagesFilename ) );

    TBool loadFactorySettings( !CbsUtils::ExistsL( iFs, aTopicsFile ) );
    TRAPD( error, OpenFilesL( loadFactorySettings, EFalse ) );
    
    CBSLOGSTRING2("CBSSERVER: CCbsDbImpTopicList::ConstructL(): OpenFilesL() error: %d", error );
    
    if ( error == KErrDiskFull )
        {
        // Delete the store and open is again
        CBSLOGSTRING("CBSSERVER: CCbsDbImpTopicList::ConstructL(): About to delete iTopicStore..." );
        
        delete iTopicStore;
        iTopicStore = NULL;
        
        CBSLOGSTRING("CBSSERVER: CCbsDbImpTopicList::ConstructL(): iTopicStore deletion finished." );

        // Try to open the store again   
        TRAPD( error, iTopicStore = CFileStore::OpenL( iFs, *iTopicsFilename, EFileRead | EFileWrite ) );
        CBSLOGSTRING2("CBSSERVER: CCbsDbImpTopicList::ConstructL(); iTopicStore OpenL() error: %d", error );
        if ( error )
            {
            InitializeListL( ETrue );
            }        
        }
    else
        {
        if ( loadFactorySettings )
            {
            // Create a topic list for standard topics
            if ( iTopicLists->Count() == 0 )
                {
                CreateStandardTopicListL();
                }                
            }

        // Only load the topics, if not already loaded when opening the files
        if ( loadFactorySettings || unsavedMsgFileExists )
            {
            // Load the topics
            LoadDefaultTopicStreamL();
            }
        
        // Compact the topic store
        TopicStoreL()->CompactL();
        TopicStoreL()->CommitL();
        }            
    
    // Reset the unread message count to be sure that UI client gets the correct information,
    // at this point count is always zero.
    for ( TInt i( 0 ); i < iTopics->Count(); ++i )
        {
        iTopics->At( i ).iTopicData.iUnreadMessages = 0;
        }
        
    __TEST_INVARIANT;
    
    CBSLOGSTRING("CBSSERVER: <<< CCbsDbImpTopicList::ConstructL()");
    }

// -----------------------------------------------------------------------------
// CCbsDbImpTopicList::NewL
// Two-phased constructor.
// -----------------------------------------------------------------------------
//
CCbsDbImpTopicList* CCbsDbImpTopicList::NewL( 
    RFs& aFs, 
    const TDesC& aTopicsFile, 
    const TDesC& aUnsavedMessagesFile,
    CCbsDbImp& aDatabase )
    {
    CCbsDbImpTopicList* self = 
        new ( ELeave ) CCbsDbImpTopicList( aFs, aDatabase );
    CleanupStack::PushL( self );
    self->ConstructL( aTopicsFile, aUnsavedMessagesFile );
    CleanupStack::Pop();
    return self;
    }

// Destructor
CCbsDbImpTopicList::~CCbsDbImpTopicList()
    {
    CBSLOGSTRING("CBSSERVER: >>> CCbsDbImpTopicList::~CCbsDbImpTopicList()");
    delete iTopics;
    delete iTopicStore;
    delete iTopicLists;
    delete iTopicIds;
    delete iUnsavedMessageStore;
    delete iObservers;
    delete iUnsavedMessagesFilename;
    if ( iTopicsFilename )
        {
        delete iTopicsFilename;
        }
    CBSLOGSTRING("CBSSERVER: <<< CCbsDbImpTopicList::~CCbsDbImpTopicList()");
    }

#ifndef __SECURE_BACKUP__
// -----------------------------------------------------------------------------
// CCbsDbImpTopicList::ChangeFileLockL
// Closes or reopens the settings file if requested by a backup.
// (other items were commented in a header).
// -----------------------------------------------------------------------------
//
void CCbsDbImpTopicList::ChangeFileLockL( 
    const TDesC& aFileName,
    TFileLockFlags aFlags )
    {
    CBSLOGSTRING2("CBSSERVER: >>> CCbsDbImpTopicList::ChangeFileLockL() (1): flag: %d", aFlags );
    
    if ( aFlags == ETakeLock && iTopicStore == NULL )
        {
        CBSLOGSTRING("CBSSERVER: CCbsDbImpTopicList::ChangeFileLockL() (1): Try to open store...");
        
        // Try to open the store.
        iTopicStore = CPermanentFileStore::OpenL( iFs, 
            aFileName, EFileRead | EFileWrite );
            
        CBSLOGSTRING("CBSSERVER: CCbsDbImpTopicList::ChangeFileLockL() (1): Store opened.");
        }
    else if ( aFlags != ETakeLock )
        {
        CBSLOGSTRING("CBSSERVER: CCbsDbImpTopicList::ChangeFileLockL() (1): Deleting iTopicStore...");
        
        delete iTopicStore; 
        iTopicStore = NULL;
        
        CBSLOGSTRING("CBSSERVER: CCbsDbImpTopicList::ChangeFileLockL() (1): iTopicStore deleted.");
        }
    CBSLOGSTRING("CBSSERVER: <<< CCbsDbImpTopicList::ChangeFileLockL() (1)");
    }
#else

// -----------------------------------------------------------------------------
// CCbsDbImpSettings::ChangeFileLockL
// Closes or reopens the settings file if requested by backup.
// (other items were commented in a header).
// -----------------------------------------------------------------------------
//    
void CCbsDbImpTopicList::ChangeFileLockL( const TCbsBackupRequest& aRequest )
    {
    CBSLOGSTRING2("CBSSERVER: >>> CCbsDbImpTopicList::ChangeFileLockL() (2): aRequest: %d", aRequest );
    
    // If backing up or restoring, release locks
    if ( ( aRequest == ECbsBackup || 
        aRequest == ECbsRestore ) )
        {
        delete iTopicStore; 
        iTopicStore = NULL;
        
        CBSLOGSTRING("CBSSERVER: CCbsDbImpTopicList::ChangeFileLockL() (2): iTopicStore deleted.");
        }
    // Else take files into use again
    else if ( ( aRequest == ECbsNoBackup || 
        aRequest == ECbsBackupNotDefined ) && 
        iTopicStore == NULL)
        {
        CBSLOGSTRING("CBSSERVER: CCbsDbImpTopicList::ChangeFileLockL() (2): Calling CPermanentFileStore::OpenL()...");
        // Try to open the store.
        iTopicStore = CPermanentFileStore::OpenL( iFs, 
            iTopicsFilename->Des(), EFileRead | EFileWrite );
        CBSLOGSTRING("CBSSERVER: CCbsDbImpTopicList::ChangeFileLockL() (2): CPermanentFileStore::OpenL() finished.");
        }
    CBSLOGSTRING("CBSSERVER: <<< CCbsDbImpTopicList::ChangeFileLockL() (2)");
    }

#endif

// -----------------------------------------------------------------------------
// CCbsDbImpTopicList::CheckFileLockL
// Check if the server has a file lock.
// (other items were commented in a header).
// -----------------------------------------------------------------------------
//
void CCbsDbImpTopicList::CheckFileLockL() const
    {
    CBSLOGSTRING("CBSSERVER: >>> CCbsDbImpTopicList::CheckFileLockL()");
    if ( iTopicStore == NULL )
        {
        CBSLOGSTRING("CBSSERVER: CCbsDbImpTopicList::CheckFileLockL(): iTopicStore == NULL, leaving with KErrLocked...");
        User::Leave( KErrLocked );
        }
    CBSLOGSTRING("CBSSERVER: <<< CCbsDbImpTopicList::CheckFileLockL()");
    }

// -----------------------------------------------------------------------------
// CCbsDbImpTopicList::CreateNewTopicListL
// Creates a new topic list.
// (other items were commented in a header).
// -----------------------------------------------------------------------------
//
void CCbsDbImpTopicList::CreateNewTopicListL( const TDesC& aTopicListName )
    {
    // FFS critical level check
    CbsUtils::FFSCriticalLevelCheckL( KTopicListStreamSize + 
        KCbsDbTopicArraySize, iFs );

    TInt topicListCount = iTopicLists->Count();

    // Check if there already is 10 topic lists
    if ( topicListCount < KCbsRootItemsSize )
        {                  
        // Create the stream for this topic list entry
        RStoreWriteStream outstream;
        TStreamId topicListStreamId( 
            outstream.CreateLC( *TopicStoreL() ) ); // on CS        

        // Set the values for this new topic list and set it as "current".
        // Reset the topic count of this topic list.
        iCurrentTopicList.iTopicCount = 0;

        // Topic list stream id for this list
        iCurrentTopicList.iTopicListId = topicListStreamId;
    
        // List name
        iCurrentTopicList.iTopicListName = aTopicListName; 

        // Is this the default list
        iCurrentTopicList.iIsDefaultTopicList = ETrue;

        // Number of this list, which is a int value between 0...9
        iCurrentTopicList.iNumber = iTopicLists->Count();

        // Add this list to the list array        
        TKeyArrayFix key( _FOFF( TCbsDbImpTopicList, iNumber ), ECmpTUint16 );
        iTopicLists->InsertIsqL( iCurrentTopicList, key );
    
        // Write the values to this topic list's stream
        outstream << iCurrentTopicList.iTopicListName;
        CbsStreamHelper::WriteBoolL( outstream, iCurrentTopicList.iIsDefaultTopicList );
        outstream.WriteInt16L( iCurrentTopicList.iNumber );
        outstream.WriteInt16L( iCurrentTopicList.iTopicCount );

        // Write space for topic stream IDs        
        for ( TInt i( 0 ); i < KCbsDbTopicArraySize; i++ )
            {
            outstream << TStreamId( 0 );
            }        
           
        outstream.CommitL();
        CleanupStack::PopAndDestroy(); // outstream    
        
        // Reset the topic array and add an index topic to this
        // topic list
        iTopics->Reset();
        AddIndexTopicL();
            
        // Update the root stream
        UpdateRootStreamL( EFalse );
    
        // Commit file changes
        CommitFilesL();

        // Load the topics (count = 1, Index topic only), 
        // so that the array is up to date
        LoadTopicsL( iCurrentTopicList.iTopicListId );            
        } 
    else
        {
        User::Leave( KErrGeneral );
        }   
    }

// -----------------------------------------------------------------------------
// CCbsDbImpTopicList::SetTopicMessages
// Sets the topic messages db object for this topic list.
// Note that this function does not transfer ownership
// of aMessages.
// (other items were commented in a header).
// -----------------------------------------------------------------------------
//
void CCbsDbImpTopicList::SetTopicMessages( 
    CCbsDbImpTopicMessages* aMessages )
    {
    __ASSERT_ALWAYS( aMessages != NULL,
        CbsServerPanic( ECbsTopicMessagesNull ) );
    iMessages = aMessages;
    }

// -----------------------------------------------------------------------------
// CCbsDbImpTopicList::TopicCount
// Returns the total amount of topics the list contains.
// (other items were commented in a header).
// -----------------------------------------------------------------------------
//
TInt CCbsDbImpTopicList::TopicCount() const
    {
    return iTopics->Count();
    }

// -----------------------------------------------------------------------------
// CCbsDbImpTopicList::TopicListCount
// Get topic list count.
// (other items were commented in a header).
// -----------------------------------------------------------------------------
//
TInt CCbsDbImpTopicList::TopicListCount() const
    {
    return iTopicLists->Count();
    }

// -----------------------------------------------------------------------------
// CCbsDbImpTopicList::TopicStoreL
// Returns pointer to the current store which contains topics
// of the server (i.e., Topics-file) and saved messages. 
// (other items were commented in a header).
// -----------------------------------------------------------------------------
//
CFileStore* CCbsDbImpTopicList::TopicStoreL() const
    {
    CheckFileLockL();
    return iTopicStore;
    }

// ---------------------------------------------------------
// TopicFilename() 
//
// ---------------------------------------------------------
const TDesC& CCbsDbImpTopicList::TopicFilename() const
    {
    return *iTopicsFilename;
    }

// -----------------------------------------------------------------------------
// CCbsDbImpTopicList::UnsavedMessagesStore
// Returns a pointer to the store, which contains unsaved
// messages of the server (Unsaved Messages-file, "cbs4.dat").
// (other items were commented in a header).
// -----------------------------------------------------------------------------
//
CFileStore* CCbsDbImpTopicList::UnsavedMessagesStore() const
    {
    return iUnsavedMessageStore;
    }

// -----------------------------------------------------------------------------
// CCbsDbImpTopicList::UnsavedMessagesFilename
// Returns a reference to the name of the file, which contains unsaved
// messages of the server (Unsaved Messages-file, "cbs4.dat").
// (other items were commented in a header).
// -----------------------------------------------------------------------------
//
const TDesC& CCbsDbImpTopicList::UnsavedMessagesFilename() const
    {
    return *iUnsavedMessagesFilename;
    }

// -----------------------------------------------------------------------------
// CCbsDbImpTopicList::ExtractTopicNumber
// Extracts topic handle from message handle.
// Note that the method does not check that the message exists.
// (other items were commented in a header).
// -----------------------------------------------------------------------------
//
TCbsDbTopicNumber CCbsDbImpTopicList::ExtractTopicNumber( 
    const TCbsDbMessageHandle& aHandle ) const
    {    
    return TCbsDbTopicNumber( aHandle >> 16 );
    }

// -----------------------------------------------------------------------------
// CCbsDbImpTopicList::GetTopicMessagesIdL
// Returns the topic messages stream id by topic handle.
// (other items were commented in a header).
// -----------------------------------------------------------------------------
//
void CCbsDbImpTopicList::GetTopicMessagesIdL( 
    TCbsDbTopicNumber aNumber, TStreamId& aId ) const
    {
    CBSLOGSTRING("CBSSERVER: >>> CCbsDbImpTopicList::GetTopicMessagesIdL()");

    // Find the topic.
    TInt index( TopicIndexInList( aNumber ) );
    CBSLOGSTRING2("CBSSERVER: CCbsDbImpTopicList::GetTopicMessagesIdL(): Leaving if index != 0. Index: %d.", index );
    User::LeaveIfError( index );

    // Return the topic message stream id.
    aId = iTopics->At( index ).iTopicMessagesId;
    
    CBSLOGSTRING("CBSSERVER: <<< CCbsDbImpTopicList::GetTopicMessagesIdL()");
    }

// -----------------------------------------------------------------------------
// CCbsDbImpTopicList::UpdateTopicMessagesIdL
// Updates the topic messages stream id by topic handle. 
// The new identifier is expected not to be a null id.
// Note that the method will not commit changes to the store. 
// It also changes the internal state and thus if the method leaves, 
// it is good to reload the whole root stream.
// The method will delete the old topic messages stream.
// (other items were commented in a header).
// -----------------------------------------------------------------------------
//
void CCbsDbImpTopicList::UpdateTopicMessagesIdL( 
    TCbsDbTopicNumber aNumber, 
    const TStreamId& aNewId )
    {
    __TEST_INVARIANT;
    // Find position.
    TInt index( TopicIndexInList( aNumber ) );
    User::LeaveIfError( index );

    // Get the old id.
    TStreamId oldId( iTopics->At( index ).iTopicMessagesId );

    // Get the topic information.
    TCbsDbTopic topic;
    GetTopicL( index, topic );

    // Write information to the stream.
    RStoreWriteStream outstream;
    outstream.OpenLC( *TopicStoreL(), iTopics->At( index ).iTopicId ); // on CS
    WriteTopicInformationL( outstream, topic, aNewId );
    outstream.CommitL();
    CleanupStack::PopAndDestroy();

    // Delete the old stream.
    TopicStoreL()->DeleteL( oldId );

    // Update the topic messages id.
    iTopics->At( index ).iTopicMessagesId = aNewId;
    __TEST_INVARIANT;
    }

// -----------------------------------------------------------------------------
// CCbsDbImpTopicList::GenerateMessageHandle
// Generates a new message handle using the topic 
// handle of the message and a given random value.
// Note that it must be checked that the message handle is unique 
// in that topic.
// (other items were commented in a header).
// -----------------------------------------------------------------------------
//
TCbsDbMessageHandle CCbsDbImpTopicList::GenerateMessageHandle( 
    const TCbsDbTopicNumber aNumber, 
    TUint16 aRandom ) const
    {
    return ( aNumber << 16 ) + aRandom;
    }

// -----------------------------------------------------------------------------
// CCbsDbImpTopicList::ReloadRootStreamL
// Reloads the root stream to the memory.
// (other items were commented in a header).
// -----------------------------------------------------------------------------
//
void CCbsDbImpTopicList::ReloadRootStreamL()
    {
    LoadRootStreamL();
    __TEST_INVARIANT;
    }

// -----------------------------------------------------------------------------
// CCbsDbImpTopicList::InformNewMessageReceivedL
// Informs that a new message has been received in a topic.
// This method is called by CCbsDbImpTopicMessages. After internal 
// records are changed, the observers are informed of this event.
// Note: leaves changes in stores uncommited.
// (other items were commented in a header).
// -----------------------------------------------------------------------------
//
void CCbsDbImpTopicList::InformNewMessageReceivedL( 
    const TCbsDbMessageHandle& aMessageHandle )
    {
    __TEST_INVARIANT;
    // Find topic by handle.
    TCbsDbTopicNumber number( ExtractTopicNumber( aMessageHandle ) );

    TCbsDbTopic topic;
    FindTopicByNumberL( number, topic );

    topic.iUnreadMessages++;

    // Increase the counter in cache
    TInt position = TopicIndexInList( topic.iNumber );
    iTopics->At( position ).iTopicData.iUnreadMessages++;

    // Write topic information to topic stream but leave changes uncommited.
    UpdateTopicCountersL( topic, EFalse );    

    if ( topic.iHotmarked )
        {
        SetHotmarkedMessage( aMessageHandle );
        }

    __TEST_INVARIANT;
    }

// -----------------------------------------------------------------------------
// CCbsDbImpTopicList::InformMessageSavedL
// Informs that a message has been set as saved.
// Updates the saved messages counters
// for the topic of the message and the whole system.
// Called by CCbsDbTopicMessages.
// (other items were commented in a header).
// -----------------------------------------------------------------------------
//
void CCbsDbImpTopicList::InformMessageSavedL( 
    const TCbsDbMessageHandle& aMessageHandle )
    {
    __TEST_INVARIANT;
    // Find topic by handle.
    TCbsDbTopicNumber number( ExtractTopicNumber( aMessageHandle ) );

    TCbsDbTopic topic;
    FindTopicByNumberL( number, topic );

    topic.iSavedMessages++;

    // Increase the counter in cache
    TInt position = TopicIndexInList( topic.iNumber );
    iTopics->At( position ).iTopicData.iSavedMessages++;

    // Write topic information to topic stream but leave changes uncommited.
    UpdateTopicCountersL( topic, EFalse );

    __TEST_INVARIANT;
    }

// -----------------------------------------------------------------------------
// CCbsDbImpTopicList::InformUnreadMessageReadL
// Informs that an unread message has been read.
// Updates the counters when a message is read by the client. 
// Note: leaves changes in stores uncommited.
// (other items were commented in a header).
// -----------------------------------------------------------------------------
//
void CCbsDbImpTopicList::InformUnreadMessageReadL( 
    const TCbsDbMessageHandle& aMessageHandle )
    {
    CBSLOGSTRING("CBSSERVER: >>> CCbsDbImpTopicList::InformUnreadMessageReadL()");
    
    __TEST_INVARIANT;

    // Check for file lock
    CheckFileLockL();

    // Check disk space
    TRAPD( error, CbsUtils::FFSCriticalLevelCheckL( KReadMessageSize, iFs ) );
    CBSLOGSTRING2("CBSSERVER: CCbsDbImpTopicList::InformUnreadMessageReadL(): FFSCriticalLevelCheckL returned: %d", error );
    if ( error == KErrDiskFull )
        {
        return;
        }

    // Find topic by number.
    TCbsDbTopicNumber number( ExtractTopicNumber( aMessageHandle ) );

    TCbsDbTopic topic;
    FindTopicByNumberL( number, topic );

    // Decrease the counter
    topic.iUnreadMessages--;

    // Decrease the counter in cache
    TInt position = TopicIndexInList( topic.iNumber );
    iTopics->At( position ).iTopicData.iUnreadMessages--;

    // Write topic information to topic stream but leave changes uncommited.
    UpdateTopicCountersL( topic, EFalse );

    __TEST_INVARIANT;
    
   CBSLOGSTRING("CBSSERVER: <<< CCbsDbImpTopicList::InformUnreadMessageReadL()");
    }

// -----------------------------------------------------------------------------
// CCbsDbImpTopicList::InformMessageDeletedL
// Informs that an unread message has been deleted.
// Updates the counters when a message is deleted. 
// Note: leaves changes in stores uncommited.
// (other items were commented in a header).
// -----------------------------------------------------------------------------
//
void CCbsDbImpTopicList::InformMessageDeletedL( 
    const TCbsDbMessageHandle& aMessageHandle,
    TBool aPermanent, 
    TBool aRead )
    {
    // Find topic by handle.
    TCbsDbTopicNumber number( ExtractTopicNumber( aMessageHandle ) );

    TCbsDbTopic topic;
    FindTopicByNumberL( number, topic );

    TInt position = TopicIndexInList( topic.iNumber );    

    if ( aPermanent )
        {
        topic.iSavedMessages--;
        iTopics->At( position ).iTopicData.iSavedMessages--;
        }
    
    if ( !aRead )
        {        
        topic.iUnreadMessages--;
        iTopics->At( position ).iTopicData.iUnreadMessages--;
        }

    // Write topic information to topic stream but leave changes uncommited.
    UpdateTopicCountersL( topic, ETrue );
    }

// -----------------------------------------------------------------------------
// CCbsDbImpTopicList::TotalSavedMessages
// Returns the number of saved messages in the current topic list.
// (other items were commented in a header).
// -----------------------------------------------------------------------------
//
TInt CCbsDbImpTopicList::TotalSavedMessages() const
    {
    // Return the total amount of saved messages.
    TInt count( 0 );
    for ( TInt i( 0 ); i < iTopics->Count(); i++ )
        {
        count += iTopics->At( i ).iTopicData.iSavedMessages;
        }

    return count;
    }

// -----------------------------------------------------------------------------
// CCbsDbImpTopicList::GetTopicCount
// Returns the number of topic stored in this topic list.
// (other items were commented in a header).
// -----------------------------------------------------------------------------
//
void CCbsDbImpTopicList::GetTopicCount( 
    TInt& aCount ) const
    {
    // Return the total amount of topics.
    aCount = TopicCount();
    }

// -----------------------------------------------------------------------------
// CCbsDbImpTopicList::InitializeListL
// Initializes the whole topic list. 
// Creates and opens the topic list file, 
// invalidates the cache and informs the observers.
// (other items were commented in a header).
// -----------------------------------------------------------------------------
//
void CCbsDbImpTopicList::InitializeListL( const TBool aFileOpenFailed )
    {
    __TEST_INVARIANT;

    if ( !aFileOpenFailed )
        {
        // Check for file lock
        CheckFileLockL();
        }    

    // About to write to FFS: make critical level check
    CbsUtils::FFSCriticalLevelCheckL( 0, iFs );

    if ( iMessages != NULL && iMessages->IsLockedMessages() )
        {
        User::Leave( KErrAccessDenied );
        }    

    // If only one topic list exists, just delete and recreate the whole file
    if ( iTopicLists->Count() == 1 || aFileOpenFailed == 1 )
        {
        delete iTopicStore; 
        iTopicStore = NULL;
        delete iUnsavedMessageStore; 
        iUnsavedMessageStore = NULL;
        CbsUtils::DeleteFileL( iFs, *iTopicsFilename  );
        CbsUtils::DeleteFileL( iFs, *iUnsavedMessagesFilename );

        iTopicLists->Reset();
       
        // Create new files.
        OpenFilesL( EFalse, ETrue );
        }
    
    iIsHotmarkedMessage = EFalse;
    iLastTopicNumber = 0;
    iMessageHandle = 0;

    // Inform the message manager.
    if ( iMessages )
        {
        iMessages->InvalidateCache();
        }

    // Notify everyone.
    NotifyTopicListInitializedL();

    __TEST_INVARIANT;
    }

// -----------------------------------------------------------------------------
// CCbsDbImpTopicList::GetTopicL
// Returns a topic matching the given index.
// (other items were commented in a header).
// -----------------------------------------------------------------------------
//
void CCbsDbImpTopicList::GetTopicL( 
    TInt aIndex, 
    TCbsDbTopic& aTopic )
    {
    CBSLOGSTRING("CBSSERVER: >>> CCbsDbImpTopicList::GetTopicL()");

    __TEST_INVARIANT;
    
    // Check that aIndex is in proper range.
    if ( ( aIndex < 0 ) || ( aIndex >= iTopics->Count() ) )
        {
        CBSLOGSTRING("CBSSERVER: CCbsDbImpTopicList::GetTopicL(): Leaving with KErrArgument...");
        User::Leave( KErrArgument );
        }

    // And then get the information from the array.
    aTopic = iTopics->At( aIndex ).iTopicData;

    CBSLOGSTRING("CBSSERVER: <<< CCbsDbImpTopicList::GetTopicL()");
    
    __TEST_INVARIANT;
    }

// -----------------------------------------------------------------------------
// CCbsDbImpTopicList::FindTopicByNumberL
// Returns a topic matching the given topic number 
// (in GSM Specs this is called the Message Identifier)
// (other items were commented in a header).
// -----------------------------------------------------------------------------
//
void CCbsDbImpTopicList::FindTopicByNumberL( 
    TCbsDbTopicNumber aNumber, 
    TCbsDbTopic& aTopic )
    {
    CBSLOGSTRING("CBSSERVER: >>> CCbsDbImpTopicList::FindTopicByNumberL()");
    
    __TEST_INVARIANT;

    TInt topicIndex( TopicIndexInList( aNumber ) );
    CBSLOGSTRING2("CBSSERVER: CCbsDbImpTopicList::FindTopicByNumberL(): Leaving if topicIndex < 0: topicIndex: %d.", topicIndex );
    User::LeaveIfError( topicIndex );

    GetTopicL( topicIndex, aTopic );

    CBSLOGSTRING("CBSSERVER: <<< CCbsDbImpTopicList::FindTopicByNumberL()");
    
    __TEST_INVARIANT;
    }

// -----------------------------------------------------------------------------
// CCbsDbImpTopicList::AddTopicL
// Adds a new topic to the list.
// (other items were commented in a header).
// -----------------------------------------------------------------------------
//
void CCbsDbImpTopicList::AddTopicL( 
    TCbsDbTopic& aTopic, const TBool aDetected )
    {
    __TEST_INVARIANT;

    // Check for file lock
    CheckFileLockL();
    
    // Check that topic number is in proper range
    if ( !CheckTopicNumber( aTopic.iNumber ) )
        {
        User::Leave( KErrArgument );
        }    

    // Check if there is a topic with the 
    // same topic number in current topic list.
    if ( TopicIndexInList( aTopic.iNumber ) != KErrNotFound )
        {
        User::Leave( KErrAlreadyExists );
        } 

    // There should not be any saved or unread messages
    aTopic.iSavedMessages = 0;
    aTopic.iUnreadMessages = 0;

    // Variated feature. Also, only topics that were not detected automatically
    // are subscribed by default.
    if ( iLVBits & KCbsLVFlagTopicSubscription && !aDetected )
        {
        aTopic.iSubscribed = ETrue;
        }
    else
        {
        aTopic.iSubscribed = EFalse;        
        }
        
    // Now we have the handle, so let's add the topic.
    TRAPD( error, DoAddTopicL( aTopic ) );
    if ( error != KErrNone )
        {
        RevertFilesL();
        __TEST_INVARIANT;
        User::Leave( error );
        }
    else
        {        
        iLastTopicNumber = aTopic.iNumber;
        NotifyTopicAddedL( aTopic.iNumber );        
        }        
    
    __TEST_INVARIANT;
    }

// -----------------------------------------------------------------------------
// CCbsDbImpTopicList::UpdateTopicNameAndNumberL
// Updates the name and the topic number of a topic 
// matching the given handle to the database.
// (other items were commented in a header).
// -----------------------------------------------------------------------------
//
void CCbsDbImpTopicList::UpdateTopicNameAndNumberL( 
    TCbsDbTopicNumber aOldNumber,
    TCbsDbTopicNumber aNewNumber, 
    const TCbsDbTopicName& aName )
    {
    // Check for file lock
    CheckFileLockL();

    // First, check that the new number is ok.
    if ( !CheckTopicNumber( aNewNumber ) )
        {
        User::Leave( KErrArgument );
        }

    // First find and then update.
    TCbsDbTopic topic;
    FindTopicByNumberL( aOldNumber, topic );

    // If no changes to topic, no need to update
    if ( !( aOldNumber == aNewNumber && topic.iName == aName ) )
        {        
        if ( topic.iProtected )
            {
            User::Leave( KErrAccessDenied );
            }

        topic.iName = aName;
        topic.iNumber = aNewNumber;

        UpdateTopicL( aOldNumber, topic );        
        }    
    }

// -----------------------------------------------------------------------------
// CCbsDbImpTopicList::UpdateTopicSubscriptionStatusL
// Updates the new topic subscription status to the database.
// (other items were commented in a header).
// -----------------------------------------------------------------------------
//
void CCbsDbImpTopicList::UpdateTopicSubscriptionStatusL( 
    TCbsDbTopicNumber aNumber, 
    TBool aStatus )
    {
    __TEST_INVARIANT;

    // Update topic subsciption status.
    TCbsDbTopic topic;
    FindTopicByNumberL( aNumber, topic );

    topic.iSubscribed = aStatus;
    UpdateTopicL( aNumber, topic );
    __TEST_INVARIANT;

    }

// -----------------------------------------------------------------------------
// CCbsDbImpTopicList::UpdateTopicHotmarkStatusL
// Updates the new topic hotmarking status to the database.
// (other items were commented in a header).
// -----------------------------------------------------------------------------
//
void CCbsDbImpTopicList::UpdateTopicHotmarkStatusL( 
    TCbsDbTopicNumber aNumber, 
    TBool aStatus )
    {
    __TEST_INVARIANT;

    // Check for file lock
    CheckFileLockL();

    // Update topic hotmark status.
    TCbsDbTopic topic;
    FindTopicByNumberL( aNumber, topic );

    topic.iHotmarked = aStatus;
    UpdateTopicL( aNumber, topic );
    __TEST_INVARIANT;
    }

// -----------------------------------------------------------------------------
// CCbsDbImpTopicList::DeleteTopicL
// Deletes an existing topic and all its messages.
// (other items were commented in a header).
// -----------------------------------------------------------------------------
//
void CCbsDbImpTopicList::DeleteTopicL( 
    TCbsDbTopicNumber aNumber )
    {
    __TEST_INVARIANT;

    // Try to find the position. If it is not found, leave.
    TInt position( TopicIndexInList( aNumber ) );
    User::LeaveIfError( position );

    // Check that there are no locked messages in the topic.
    if ( iMessages->IsLockedMessagesInTopic( aNumber ) )
        {
        User::Leave( KErrAccessDenied );
        }

    // Topic only in one topic list, not an index topic, so delete the topic.
    if ( aNumber != 0 )
        {
        // Just try to delete
        TRAPD( error, DoDeleteTopicL( position ) );
        if ( error != KErrNone )
            {
            // It failed, so we must revert.
            RevertFilesL();

            // Inform the topic messages.
            iMessages->InvalidateCacheIfTopic( aNumber );

            __TEST_INVARIANT;
            User::Leave( error );
            }
        else
            {               
            // Notify the observers
            NotifyTopicDeletedL( aNumber );
            }
        }
    __TEST_INVARIANT;
    }

// -----------------------------------------------------------------------------
// CCbsDbImpTopicList::GetUnreadMessageCount
// Gets the total amount of unread messages.
// (other items were commented in a header).
// -----------------------------------------------------------------------------
//
void CCbsDbImpTopicList::GetUnreadMessageCount( 
    TInt& aCount ) const
    {
    CBSLOGSTRING("CBSSERVER: >>> CCbsDbImpTopicList::GetUnreadMessageCount()");
    CBSLOGSTRING2("CBSSERVER: CCbsDbImpTopicList::GetUnreadMessageCount(): Topic count: %d", iTopics->Count() );
    
    // Return the total amount of unread messages.
    TInt count( 0 );
    for ( TInt i( 0 ); i < iTopics->Count(); i++ )
        {
        count += iTopics->At( i ).iTopicData.iUnreadMessages;
        }

    aCount = count;
    
    CBSLOGSTRING2("CBSSERVER: CCbsDbImpTopicList::GetUnreadMessageCount(): Unread msgs found: %d", count );
    CBSLOGSTRING("CBSSERVER: <<< CCbsDbImpTopicList::GetUnreadMessageCount()");
    }

// -----------------------------------------------------------------------------
// CCbsDbImpTopicList::GetHotmarkedMessageHandleL
// Returns the handle to the latest hotmarked message.      
// (other items were commented in a header).
// -----------------------------------------------------------------------------
//
void CCbsDbImpTopicList::GetHotmarkedMessageHandleL( 
    TCbsDbMessageHandle& aMessage )
    {
    __TEST_INVARIANT;
    
    // Check if there is a hotmarked message.
    if ( iIsHotmarkedMessage )
        {
        // If there is, then return it.
        aMessage = iMessageHandle;
        iIsHotmarkedMessage = EFalse;
        }
    else
        {
        // Otherwise leave.
        __TEST_INVARIANT;
        User::Leave( KErrNotFound );
        }
    __TEST_INVARIANT;
    }

// -----------------------------------------------------------------------------
// CCbsDbImpTopicList::UnreadHotmarkedMessageCount
// Returns the handle to the latest hotmarked message.      
// (other items were commented in a header).
// -----------------------------------------------------------------------------
//
TInt CCbsDbImpTopicList::UnreadHotmarkedMessageCount() const
    {
    // Return the total amount of unread messages in hotmarked topics.
    TInt count( 0 );
    for ( TInt i( 0 ); i < iTopics->Count(); i++ )
        {
        TCbsDbTopic& topic = iTopics->At( i ).iTopicData;
        if ( topic.iHotmarked )
            {
            count += topic.iUnreadMessages;
            }
        }
    return count;
    }

// -----------------------------------------------------------------------------
// CCbsDbImpTopicList::AddObserverL
// Adds a topic list observer. 
// After an observer is added to the topic list, 
// it will be notified whenever an event occurs.      
// (other items were commented in a header).
// -----------------------------------------------------------------------------
//
void CCbsDbImpTopicList::AddObserverL(
    MCbsDbTopicListObserver* aObserver )
    {
    __ASSERT_DEBUG( aObserver != 0, CbsServerPanic( ECbsObserverNull ) );
    iObservers->AppendL( aObserver );
    }

// -----------------------------------------------------------------------------
// CCbsDbImpTopicList::RemoveObserver
// Removes a topic list observer. 
// If aObserver is not in the list, the method will panic.  
// (other items were commented in a header).
// -----------------------------------------------------------------------------
//
void CCbsDbImpTopicList::RemoveObserver( 
    const MCbsDbTopicListObserver* aObserver )
    {
    __TEST_INVARIANT;
    __ASSERT_DEBUG( aObserver != 0, CbsServerPanic( ECbsObserverNull ) );
    
    TBool observerFound( EFalse );
    TInt amountOfObservers( iObservers->Count() );

    // Check if the observer exists and 
    // find its possible location index in the array.
    for ( TInt index( 0 ); ( index < amountOfObservers ) && !observerFound; index++ )
        {
        if ( aObserver == iObservers->At( index ) )
            {
            iObservers->Delete( index );
            observerFound = ETrue;
            }
        }

    __TEST_INVARIANT;

#ifdef _DEBUG
    if ( !observerFound )
        {
        CbsServerPanic( ECbsObserverNotFound );
        }
#endif

    }

// -----------------------------------------------------------------------------
// CCbsDbImpTopicList::TopicIndexInList
// Finds the index of the topic matching the given topic number
// in the topic list.
// (other items were commented in a header).
// -----------------------------------------------------------------------------
//
TInt CCbsDbImpTopicList::TopicIndexInList( 
    TCbsDbTopicNumber aNumber ) const
    {
    // Create a TCbsDbImpTopic to compare against and use a binary search.
    TKeyArrayFix key( _FOFF( TCbsDbImpTopic, iTopicData.iNumber ), ECmpTUint16 );
    TCbsDbImpTopic dummy;
    TInt position;

    dummy.iTopicData.iNumber = aNumber;
    TInt result( iTopics->FindIsq( dummy, key, position ) );
    
    if ( result != KErrNone )
        {
        position = KErrNotFound;
        }

    return position;
    }

// -----------------------------------------------------------------------------
// CCbsDbImpTopicList::GetNextAndPrevTopicNumberL
// Retrieves numbers of topics that precede and succeed the topic
// with number aCurrentTopic.
// (other items were commented in a header).
// -----------------------------------------------------------------------------
//
void CCbsDbImpTopicList::GetNextAndPrevTopicNumberL( 
    const TCbsTopicNumber& aCurrentTopic,
    TCbsTopicNumber& aNextTopic, 
    TCbsTopicNumber& aPrevTopic,
    TInt& aPosition )
    {
    // Determine position of requested topic in topic list.
    TInt index( TopicIndexInList( aCurrentTopic ) );
    User::LeaveIfError( index );    // if KErrNotFound
    
    // Determine position indications
    aPosition = 0;
    if ( index == 0 )
        {
        aPosition |= ECbsHead;
        }
    else
        {
        aPrevTopic = iTopics->At( index-1 ).iTopicData.iNumber;
        }

    if ( index == iTopics->Count()-1 )
        {
        aPosition |= ECbsTail;
        }
    else
        {
        aNextTopic = iTopics->At( index+1 ).iTopicData.iNumber;
        }
    }

// -----------------------------------------------------------------------------
// CCbsDbImpTopicList::NotifyNewMessageArrivedL
// Notifies each observer that a new message has arrived to a topic.
// (other items were commented in a header).
// -----------------------------------------------------------------------------
//
void CCbsDbImpTopicList::NotifyNewMessageArrivedL(
    const TCbsDbMessageHandle& aHandle )
    {
    // Notify each observer.
    TInt count( iObservers->Count() );
    for ( TInt index( 0 ); index < count; index++ )
        {
        iObservers->At( index )->TopicNewMessageReceivedIndL( aHandle );
        }
    }

// -----------------------------------------------------------------------------
// CCbsDbImpTopicList::AppendSubscribedTopicsL
// Adds numbers of subscribed topics to the given array.
// (other items were commented in a header).
// -----------------------------------------------------------------------------
//
void CCbsDbImpTopicList::AppendSubscribedTopicsL( 
    CArrayFixFlat<TUint16>& aSubscriptions ) const
    {
    TInt count( iTopics->Count() );
    for ( TInt i( 0 ); i < count; i++ ) 
        {
        TCbsDbImpTopic& topic = iTopics->At( i );
        if ( topic.iTopicData.iSubscribed )
            {
            aSubscriptions.AppendL( topic.iTopicData.iNumber );
            }
        }
    }

// -----------------------------------------------------------------------------
// CCbsDbImpTopicList::LoadRootStreamL
// Loads the root stream to the memory.
// (other items were commented in a header).
// -----------------------------------------------------------------------------
//
void CCbsDbImpTopicList::LoadRootStreamL()
    {
    __ASSERT_DEBUG( iTopicLists != NULL, 
        CbsServerPanic( ECbsTopicListArrayNull ) );

    // Get the root stream and open it.
    TStreamId id( TopicStoreL()->Root() );
    RStoreReadStream instream;
    instream.OpenLC( *TopicStoreL(), id ); // on CS

    // Load the topic list count
    TInt topicListCount( instream.ReadInt16L() );

    // Sanity check
    if ( topicListCount < 0 )
        {
        User::Leave( KErrCorrupt );
        }

    // Reset the topic list array
    iTopicLists->Reset();    

    // Load the topic list information
    TCbsDbImpTopicList item; 
    instream >> item.iTopicListId;
    ReadTopicListInformationL( item.iTopicListId, item );
    iTopicLists->AppendL( item );
        
    // Destroy the stream.
    CleanupStack::PopAndDestroy(); // instream
    }

// -----------------------------------------------------------------------------
// CCbsDbImpTopicList::LoadDefaultTopicStreamL
// Loads the default topic list to the memory.
// (other items were commented in a header).
// -----------------------------------------------------------------------------
//
void CCbsDbImpTopicList::LoadDefaultTopicStreamL()
    {
    __ASSERT_DEBUG( iTopics != NULL, 
        CbsServerPanic( ECbsTopicListArrayNull ) );

    // Read root item count
    TInt topicListCount = iTopicLists->Count();

    // If there isn't any, create a topic list for standard topics here
    if ( topicListCount == 0 )
        {
        CreateStandardTopicListL();
        topicListCount = iTopicLists->Count();
        }

    TStreamId defaultTopicListId( 0 );
    TBool quitSearch( EFalse );

    TInt i;
    // Find the default topic list      
    for ( i = 0; ( i < topicListCount ) && !quitSearch; ++i )
        {
        if ( iTopicLists->At( i ).iIsDefaultTopicList )
            {            
            defaultTopicListId = iTopicLists->At( i ).iTopicListId;
            quitSearch = ETrue;
            }        
        }
        
    CArrayFixFlat< TStreamId >* topicIds = 
        new ( ELeave ) CArrayFixFlat< TStreamId >( KTopicIdsGranularity );

    // Open the default topic list stream
    RStoreReadStream instream;
    instream.OpenLC( *TopicStoreL(), defaultTopicListId ); // on CS

    // Set the ID
    iCurrentTopicList.iTopicListId = defaultTopicListId;
    
    // Set the name
    HBufC* topicListName = HBufC::NewL( instream, KCbsDbTopicNameLength );
    iCurrentTopicList.iTopicListName.Copy( topicListName->Des() );
    delete topicListName;
    topicListName = NULL;

    // Skip default list status, since it is always true in default topic list
    CbsStreamHelper::ReadBoolL( instream );
    iCurrentTopicList.iIsDefaultTopicList = ETrue;

    // Set the topic list number
    iCurrentTopicList.iNumber = instream.ReadInt16L();
    
    // Read the amount of topics
    TInt topicCount = instream.ReadInt16L();
    iCurrentTopicList.iTopicCount = topicCount;

    // Clear the topic array.
    iTopics->ResizeL( 0 );

    TStreamId id( 0 );

    // Load the topic IDs       
    for ( i = 0; i < topicCount; i++ )
        {
        instream >> id;
        topicIds->AppendL( id );
        }   
    
    // Destroy the stream.
    CleanupStack::PopAndDestroy();
       
    // Load necessary information for each topic
    TInt count = topicIds->Count();
    TCbsDbImpTopic topic;
    for ( i = 0; i < count; i++ )
        {
        ReadTopicInformationL( topicIds->At( i ), topic );

        if ( topic.iTopicData.iNumber == KIndexTopicNumber )
            {
            HBufC* indexName = ReadIndexTopicNameLC(); // on CS
            topic.iTopicData.iName.Copy( *indexName );
            CleanupStack::PopAndDestroy(); // indexName
            }

        iTopics->AppendL( topic );
        }
    delete topicIds;
    topicIds = NULL;
    }

// -----------------------------------------------------------------------------
// CCbsDbImpTopicList::ReadTopicListInformationL
// Reads topic list information from stream.
// (other items were commented in a header).
// -----------------------------------------------------------------------------
//
void CCbsDbImpTopicList::ReadTopicListInformationL( 
    const TStreamId& aId, 
    TCbsDbImpTopicList& aTopicList ) const
    {
    RStoreReadStream instream;
    instream.OpenLC( *TopicStoreL(), aId ); // on CS

    // Topic List name
    HBufC* topicListName = HBufC::NewL( instream, KCbsDbTopicNameLength );
    aTopicList.iTopicListName.Copy( topicListName->Des() );
    delete topicListName;
    topicListName = NULL;

    // Default list status
    aTopicList.iIsDefaultTopicList = CbsStreamHelper::ReadBoolL( instream );

    // Topic List number
    aTopicList.iNumber = instream.ReadInt16L();

    // Topic count of this topic list
    aTopicList.iTopicCount = instream.ReadInt16L();   

    CleanupStack::PopAndDestroy(); // instream
    }

// -----------------------------------------------------------------------------
// CCbsDbImpTopicList::ReadTopicInformationL
// Reads all information on topic found in stream aId
// into aTopic.
// (other items were commented in a header).
// -----------------------------------------------------------------------------
//
void CCbsDbImpTopicList::ReadTopicInformationL( 
    const TStreamId& aId, 
    TCbsDbImpTopic& aTopic ) const
    {
    RStoreReadStream instream;
    instream.OpenLC( *TopicStoreL(), aId ); // on CS

    // Topic ID
    aTopic.iTopicId = aId;

    // Read saved messages.
    aTopic.iTopicData.iSavedMessages = instream.ReadInt16L();

    // Read name.
    instream >> aTopic.iTopicData.iName;
    
    // Read topic number (message identifier)
    aTopic.iTopicData.iNumber = instream.ReadInt16L();

    // Read statuses
    aTopic.iTopicData.iProtected = CbsStreamHelper::ReadBoolL( instream ); // Protected
    aTopic.iTopicData.iSubscribed = CbsStreamHelper::ReadBoolL( instream );// Subscribed
    aTopic.iTopicData.iHotmarked = CbsStreamHelper::ReadBoolL( instream ); // Hotmarked
    
    // Read unread messages count
    aTopic.iTopicData.iUnreadMessages = instream.ReadInt16L();

    // Topic messages' stream ID
    instream >> aTopic.iTopicMessagesId;

    // Sanity check
    if ( aTopic.iTopicData.iSavedMessages > KCbsDbMaxSavedMessages
        || aTopic.iTopicData.iNumber > KCbsMaxValidTopicNumber
        || aTopic.iTopicData.iUnreadMessages > KCbsDbMaxReceivedMessages )
        {
        User::Leave( KErrCorrupt );
        }

    CleanupStack::PopAndDestroy(); // instream
    }

// -----------------------------------------------------------------------------
// CCbsDbImpTopicList::DoAddTopicL
// Adds a topic into the database.
// Assumes aTopic is not a duplicate.
// (other items were commented in a header).
// -----------------------------------------------------------------------------
//
void CCbsDbImpTopicList::DoAddTopicL( 
    const TCbsDbTopic& aTopic )
    {
    // About to write to FFS: make critical level check
    CbsUtils::FFSCriticalLevelCheckL( KTopicStreamSize + 
        KEmptyTopicMessagesStreamSize, iFs );  

    // Generate general information about topic.
    TCbsDbImpTopic topic;
    topic.iTopicData = aTopic;
    
    // Write stream for messages.
    topic.iTopicMessagesId = 
        CCbsDbImpTopicMessages::CreateDefaultTopicMessagesStreamL( 
            *TopicStoreL() );

    // Create stream for topic information.
    RStoreWriteStream outstream;
    topic.iTopicId = outstream.CreateLC( *TopicStoreL() ); // on CS
    
    WriteTopicInformationL( outstream, aTopic, topic.iTopicMessagesId );

    outstream.CommitL();
    CleanupStack::PopAndDestroy(); // outstream

    // Now, insert the new topic information to the array
    TKeyArrayFix key( _FOFF( TCbsDbImpTopic, iTopicData.iNumber ), ECmpTUint16 );
    iTopics->InsertIsqL( topic, key );    

    // Update the topic list stream
    UpdateTopicListStreamL( iCurrentTopicList, EFalse );
  
    // We have modified only iTopicStore, so if CommitFilesL() leaves,
    // we either get all committed or nothing committed.
    CommitFilesL();
    }

// -----------------------------------------------------------------------------
// CCbsDbImpTopicList::DoUpdateTopicL
// Updates the data for a topic into the database.
// The position of the topic in the list is
// changed if necessary. 
// If the position has to be changed, the handles of messages
// contained in the topic have to be changed as well because
// the high word of the handle identifies the topic by it's
// number.
// Please note that standard EPOC DoSomethingL protocol
// is not followed here as this function does NOT commit
// changes made to the store. Instead, the caller must ensure
// use of proper commit/revert operations.
// (other items were commented in a header).
// -----------------------------------------------------------------------------
//
void CCbsDbImpTopicList::DoUpdateTopicL( 
    const TCbsDbTopic& aTopic, 
    TBool aNeedToChange, 
    TInt aOldPosition,
    TBool aDeleting )
    {
    CBSLOGSTRING("CBSSERVER: >>> CCbsDbImpTopicList::DoUpdateTopicL()");
    
    RSharedDataClient sharedData;
    // About to write to FFS: make critical level check
    if ( aDeleting )
        {
        User::LeaveIfError( sharedData.Connect() );
        sharedData.RequestFreeDiskSpaceLC( KTopicStreamSize ); // on CS
        }
    else
        {
        CbsUtils::FFSCriticalLevelCheckL( KTopicStreamSize, iFs );
        }

    TInt newPosition( aOldPosition );

    // If there is need to change position, change it.
    if ( aNeedToChange )
        {
        TCbsDbImpTopic topic( iTopics->At( aOldPosition ) );

        // Adjust handles of topic messages to match the changed topic number.
        iMessages->UpdateHandlesOfTopicMessagesL( 
            topic.iTopicData.iNumber, aTopic.iNumber );
            
        // Delete topic from the array.
        iTopics->Delete( aOldPosition );        

        // Set the number and reinsert into array
        topic.iTopicData.iNumber = aTopic.iNumber;
        TKeyArrayFix key( _FOFF( TCbsDbImpTopic, iTopicData.iNumber ), ECmpTUint16 );
        newPosition = iTopics->InsertIsqL( topic, key );
        }

    iTopics->At( newPosition ).iTopicData.iSubscribed = aTopic.iSubscribed;
    iTopics->At( newPosition ).iTopicData.iHotmarked = aTopic.iHotmarked;
    iTopics->At( newPosition ).iTopicData.iName = aTopic.iName;
    iTopics->At( newPosition ).iTopicData.iSavedMessages = aTopic.iSavedMessages;
    iTopics->At( newPosition ).iTopicData.iUnreadMessages = aTopic.iUnreadMessages;
    
    // Replace existing stream.
    RStoreWriteStream outstream;
    outstream.ReplaceLC( *TopicStoreL(), 
        iTopics->At( newPosition ).iTopicId ); // on CS
    WriteTopicInformationL( outstream, aTopic, 
        iTopics->At( newPosition ).iTopicMessagesId );

    outstream.CommitL();
    CleanupStack::PopAndDestroy(); // outstream

    // Free the reserved space
    if ( aDeleting )
        {
        CleanupStack::PopAndDestroy(); // disk space
        sharedData.Close();
        }
    
    // Update topic list stream, if necessary.
    if ( aNeedToChange )
        {
        UpdateTopicListStreamL( iCurrentTopicList, EFalse );
        }
    
    CBSLOGSTRING("CBSSERVER: <<< CCbsDbImpTopicList::DoUpdateTopicL()");
    }

// -----------------------------------------------------------------------------
// CCbsDbImpTopicList::DoDeleteTopicL
// Deletes a topic from the given position in the database.
// (other items were commented in a header).
// -----------------------------------------------------------------------------
//
void CCbsDbImpTopicList::DoDeleteTopicL( 
    TInt aPosition )
    {
    // Check that the topic is not protected.
    TCbsDbTopic topic;
    GetTopicL( aPosition, topic );

    // It is not allowed to delete a topic that is protected. It must
    // first be updated to be not protected.
    if ( topic.iProtected )
        {
        User::Leave( KErrAccessDenied );
        }

    // First delete all messages the topic contains.
    RStoreReadStream instream;    
    instream.OpenLC( *TopicStoreL(), 
        iTopics->At( aPosition ).iTopicMessagesId ); // on CS
    CCbsDbImpTopicMessages::DeleteAllTopicMessagesL( *TopicStoreL(), 
        *iUnsavedMessageStore, instream );
    iMessages->InvalidateCache();
    CleanupStack::PopAndDestroy(); // instream

    // Delete topic and topic messages streams.
    TopicStoreL()->DeleteL( iTopics->At( aPosition ).iTopicMessagesId );
    TopicStoreL()->DeleteL( iTopics->At( aPosition ).iTopicId );    

    // Remove from the internal cache.
    iTopics->Delete( aPosition );

    iCurrentTopicList.iTopicCount--;                
    UpdateTopicListStreamL( iCurrentTopicList, ETrue );
    CommitFilesL();
    }

// -----------------------------------------------------------------------------
// CCbsDbImpTopicList::UpdateRootStreamL
// Updates the root stream. 
// (other items were commented in a header).
// -----------------------------------------------------------------------------
//
void CCbsDbImpTopicList::UpdateRootStreamL(
    TBool aDeleting )
    {      
    // Check the free space
    TInt neededSpace( KTopicListRootStreamSize + iTopicLists->Count() * 2 );        

    RSharedDataClient sharedData;
    // About to write to FFS: make critical level check
    if ( aDeleting )
        {
        User::LeaveIfError( sharedData.Connect() );
        sharedData.RequestFreeDiskSpaceLC( neededSpace ); // on CS
        }
    else
        {
        CbsUtils::FFSCriticalLevelCheckL( neededSpace, iFs );
        }

    // Now there is room for all topics. So we can just replace.
    RStoreWriteStream outstream;
    outstream.ReplaceLC( *TopicStoreL(), TopicStoreL()->Root() ); // on CS        

    // Write root stream
    WriteRootStreamL( outstream );
    CleanupStack::PopAndDestroy(); // outstream

    // Free the reserved space
    if ( aDeleting )
        {
        CleanupStack::PopAndDestroy(); // disk space
        sharedData.Close();
        }
    }

// -----------------------------------------------------------------------------
// CCbsDbImpTopicList::UpdateTopicListStreamL
// Updates topic list stream. 
// (other items were commented in a header).
// -----------------------------------------------------------------------------
//
void CCbsDbImpTopicList::UpdateTopicListStreamL( 
    TCbsDbImpTopicList& aTopicList,
    TBool aDeleting )
    {        
    TInt neededSpace( KTopicListStreamSize + iTopics->Count() * 2 );
    
    RSharedDataClient sharedData;
    // About to write to FFS: make critical level check
    if ( aDeleting )
        {
        User::LeaveIfError( sharedData.Connect() );
        sharedData.RequestFreeDiskSpaceLC( neededSpace ); // on CS
        }
    else
        {
        CbsUtils::FFSCriticalLevelCheckL( neededSpace, iFs );
        }
    
    // Replace the stream
    RStoreWriteStream outstream;
    outstream.ReplaceLC( *TopicStoreL(), aTopicList.iTopicListId ); // on CS
    
    // Write root stream
    WriteTopicListStreamL( outstream, aTopicList );
    CleanupStack::PopAndDestroy(); // outstream
    
    // Free the reserved space
    if ( aDeleting )
        {
        CleanupStack::PopAndDestroy(); // disk space
        sharedData.Close();
        }
    }

// -----------------------------------------------------------------------------
// CCbsDbImpTopicList::UpdateTopicL
// Updates the information for a topic already existing 
// in the database.
// (other items were commented in a header).
// -----------------------------------------------------------------------------
//
void CCbsDbImpTopicList::UpdateTopicL( 
    TCbsDbTopicNumber aTopicNumber, 
    TCbsDbTopic& aTopic )
    {
    // Check that the new topic number is unique.
    TBool needToChangePosition( ETrue );

    // Try to find the topic and leave if it was not found.
    TInt oldPosition( TopicIndexInList( aTopicNumber ) );
    User::LeaveIfError( oldPosition );

    // Check if we have to change the position of the topic in the 
    // internal array
    if ( aTopic.iNumber == aTopicNumber )
        {
        needToChangePosition = EFalse;
        }
    else
        {
        TInt topicWithTheNewNumber( TopicIndexInList( aTopic.iNumber ) );
        if ( topicWithTheNewNumber != KErrNotFound && 
             topicWithTheNewNumber != oldPosition )
            {
            User::Leave( KErrAlreadyExists );
            }
        }

    // Write data to store.
    // Deviation from EPOC standards: DoUpdateTopicL does NOT commit 
    // the store.
    TRAPD( result, DoUpdateTopicL( aTopic, needToChangePosition, 
        oldPosition, EFalse ) );

    // Commit both topic and unsaved msgs store
    if ( result == KErrNone )
        {
        TRAP( result, CommitFilesL() );
        }

    // If either DoUpdateTopicL or CommitFilesL fails => revert.
    if ( result != KErrNone )
        {
        TopicStoreL()->Revert();
        ReloadRootStreamL();
        User::Leave( result );
        }    
    else
        {
        // Notify the observers.
        NotifyTopicModifiedL( aTopicNumber );
        }
    }

// -----------------------------------------------------------------------------
// CCbsDbImpTopicList::CheckTopicNumber
// Checks if a topic number is valid.
// The valid topic number range in this implementation is 
// 000..999, with 000 indicating an index message.
// (other items were commented in a header).
// -----------------------------------------------------------------------------
//
TBool CCbsDbImpTopicList::CheckTopicNumber( 
    TCbsDbTopicNumber aNumber ) const
    {
    // Check that the number is in proper range
    return aNumber <= KCbsMaxValidTopicNumber;        
    }

// -----------------------------------------------------------------------------
// CCbsDbImpTopicList::WriteRootStreamL
// Write the root stream.
// (other items were commented in a header).
// -----------------------------------------------------------------------------
//
void CCbsDbImpTopicList::WriteRootStreamL( 
    RWriteStream& aOut ) const
    {
    // Write the total amount of topic lists 
    TInt topicListCount( iTopicLists->Count() );
    aOut.WriteInt16L( topicListCount );
    
    // Write space for topic list stream ids
    TInt index;
    for ( index = 0; index < topicListCount; index++ )
        {
        aOut << iTopicLists->At( index ).iTopicListId;
        }
    
    // Write null stream ids
    for ( ; index < KCbsRootItemsSize; index++ )
        {
        aOut << TStreamId( 0 );
        }
    
    aOut.CommitL();
    }

// -----------------------------------------------------------------------------
// CCbsDbImpTopicList::WriteTopicListStreamL
// Writes topic list information to specified stream.
// (other items were commented in a header).
// -----------------------------------------------------------------------------
//
void CCbsDbImpTopicList::WriteTopicListStreamL( 
    RWriteStream& aOut, 
    TCbsDbImpTopicList& aTopicList ) const
    {
    // Write the values to this topic list's stream

    // Topic List name
    aOut << aTopicList.iTopicListName;

    // Is this the default list
    CbsStreamHelper::WriteBoolL( aOut, aTopicList.iIsDefaultTopicList );

    // Topic List number
    aOut.WriteInt16L( aTopicList.iNumber );

    // NUmber of topics in this list
    aOut.WriteInt16L( iTopics->Count() );

    // Write the stream IDs of the topics belonging to this list    
    TInt i;
    for ( i = 0; i < iTopics->Count(); i++ )
        {
        aOut << iTopics->At( i ).iTopicId;
        }

    // Write space for the rest topic stream IDs    
    for ( ; i < KCbsDbTopicArraySize; i++ )
        {
        aOut << TStreamId( 0 );
        }        
           
    aOut.CommitL();
    }

// -----------------------------------------------------------------------------
// CCbsDbImpTopicList::NotifyTopicListInitializedL
// Notifies each observer that the topic list has been initialized.
// (other items were commented in a header).
// -----------------------------------------------------------------------------
//
void CCbsDbImpTopicList::NotifyTopicListInitializedL()
    {
    // Notify each observer.
    TInt count( iObservers->Count() );
    for( TInt index( 0 ); index < count; index++ )
        {
        iObservers->At( index )->TopicListInitializedIndL();
        }
    }

// -----------------------------------------------------------------------------
// CCbsDbImpTopicList::NotifyTopicAddedL
// Notifies each observer that a topic has been added.
// (other items were commented in a header).
// -----------------------------------------------------------------------------
//
void CCbsDbImpTopicList::NotifyTopicAddedL( 
    TCbsDbTopicNumber aNumber )
    {
    // Notify each observer.
    TInt count( iObservers->Count() );
    for ( TInt index( 0 ); index < count; index++ )
        {
        iObservers->At( index )->TopicAddedIndL( aNumber );
        }
    }

// -----------------------------------------------------------------------------
// CCbsDbImpTopicList::NotifyTopicModifiedL
// Notifies each observer that a topic has been modified.
// (other items were commented in a header).
// -----------------------------------------------------------------------------
//
void CCbsDbImpTopicList::NotifyTopicModifiedL( 
    TCbsDbTopicNumber aNumber )
    {
    // Notify each observer.
    TInt count( iObservers->Count() );
    for( TInt index( 0 ); index < count; index++ )
        {
        iObservers->At( index )->TopicModifiedIndL( aNumber );
        }
    }

// -----------------------------------------------------------------------------
// CCbsDbImpTopicList::NotifyTopicDeletedL
// Notifies each observer that a topic has been deleted.
// (other items were commented in a header).
// -----------------------------------------------------------------------------
//
void CCbsDbImpTopicList::NotifyTopicDeletedL( 
    TCbsDbTopicNumber aNumber )
    {
    // Notify each observer.
    TInt count( iObservers->Count() );
    for( TInt index( 0 ); index < count; index++ )
        {
        iObservers->At( index )->TopicDeletedIndL( aNumber );
        }
    }

// -----------------------------------------------------------------------------
// CCbsDbImpTopicList::SetHotmarkedMessage
// Sets the hotmarked message handle.
// (other items were commented in a header).
// -----------------------------------------------------------------------------
//
void CCbsDbImpTopicList::SetHotmarkedMessage( 
    const TCbsDbMessageHandle& aMessageHandle )
    {
    // Set the hotmarked message.
    iIsHotmarkedMessage = ETrue;
    iMessageHandle = aMessageHandle;
    }

// -----------------------------------------------------------------------------
// CCbsDbImpTopicList::CreateDefaultRootStreamL
// Creates a root stream and writes default values into it. 
// (other items were commented in a header).
// -----------------------------------------------------------------------------
//
TStreamId CCbsDbImpTopicList::CreateDefaultRootStreamL( 
    CStreamStore& aStore ) const
    {
    // Create the stream
    RStoreWriteStream outstream;
    TStreamId id( outstream.CreateLC( aStore ) ); // on CS

    // Write the amount of topic lists
    outstream.WriteInt16L( 0 );

    // Write space for topic list stream ids
    for ( TInt index( 0 ); index < KCbsRootItemsSize; index++ )
        {
        outstream << TStreamId( 0 );
        } 

    outstream.CommitL();
    CleanupStack::PopAndDestroy(); // aStore

    // Return the stream id.
    return id;
    }

// -----------------------------------------------------------------------------
// CCbsDbImpTopicList::WriteTopicInformationL
// Writes topic data into a stream.
// This includes number of saved messages, 
// number of unread messages, topic handle, 
// topic name, topic number, protection status, 
// subscription status and hotmark status.
// (other items were commented in a header).
// -----------------------------------------------------------------------------
//
void CCbsDbImpTopicList::WriteTopicInformationL( 
    RWriteStream& aOut, 
    const TCbsDbTopic& aTopic, 
    const TStreamId& aTopicMessagesId ) const
    {
    // Write saved messages.
    aOut.WriteInt16L( aTopic.iSavedMessages );

    // Write name.
    aOut << aTopic.iName;
    
    // Write topic number (message identifier)
    aOut.WriteInt16L( aTopic.iNumber );

    // Write statuses
    CbsStreamHelper::WriteBoolL( aOut, aTopic.iProtected ); // Protected
    CbsStreamHelper::WriteBoolL( aOut, aTopic.iSubscribed ); // Subscribed
    CbsStreamHelper::WriteBoolL( aOut, aTopic.iHotmarked ); // Hotmarked
    
    // Write unread messages count
    aOut.WriteInt16L( aTopic.iUnreadMessages );

    // And then write the topic messages stream id.
    aOut << aTopicMessagesId;
    }


// -----------------------------------------------------------------------------
// CCbsDbImpTopicList::RevertFilesL
// Reverts all changes made to three datafiles handled by this class.
// (other items were commented in a header).
// -----------------------------------------------------------------------------
//
void CCbsDbImpTopicList::RevertFilesL()
    {

    // Check for file lock
    CheckFileLockL();

    iTopicStore->Revert();   
    iUnsavedMessageStore->Revert();
    ReloadRootStreamL();
    }

// -----------------------------------------------------------------------------
// CCbsDbImpTopicList::CommitFilesL
// Commits all changes made to two datafiles handled by this class.
// (other items were commented in a header).
// -----------------------------------------------------------------------------
//
void CCbsDbImpTopicList::CommitFilesL()
    {
    TopicStoreL()->CommitL();
    TInt errorCode( iUnsavedMessageStore->Commit() );

    // If committing of the unsaved msg store fails, remove all
    // messages in the store and rebuild the topic list
    if ( errorCode != KErrNone )
        {
        TRAPD( errorCode2, RebuildUnsavedMessageStoreL() );
        if ( errorCode2 != KErrNone )
            {
            CActiveScheduler::Stop();
            User::Leave( KErrServerTerminated );
            }

        // Tell the caller that something went wrong
        User::Leave( errorCode );
        }

    // Check if we should compact
    TTime now;
    TTimeIntervalMinutes interval;
    now.UniversalTime();
    now.MinutesFrom( iPreviousCompact, interval );
    if ( interval.Int() >= KMinimumCompactInterval )
        {
        TopicStoreL()->CompactL();
        iUnsavedMessageStore->CompactL();
        iPreviousCompact = now;
        }

    }

// -----------------------------------------------------------------------------
// CCbsDbImpTopicList::RebuildUnsavedMessageStoreL
// Deletes the unsaved messages' store and rebuilds it.
// (other items were commented in a header).
// -----------------------------------------------------------------------------
//
void CCbsDbImpTopicList::RebuildUnsavedMessageStoreL() 
    {
    // Close the stores and delete the unsaved store
    delete iUnsavedMessageStore; iUnsavedMessageStore = NULL;
    delete iTopicStore; iTopicStore = NULL;
    CbsUtils::DeleteFileL( iFs, *iUnsavedMessagesFilename );

    // Re-create the store
    DoCreateStoreL( *iUnsavedMessagesFilename );
    TryToOpenFilesL( ETrue, EFalse );

    // Remove the stream ids to unsaved messages, because
    // they were all just deleted.
    HandleDeletionOfUnsavedMessagesFileL();

    // Compact the topic list
    TopicStoreL()->CommitL();
    TopicStoreL()->CompactL();
    }

// -----------------------------------------------------------------------------
// CCbsDbImpTopicList::RebuildTopicAndUnsavedStoresL
// Deletes and rebuilds topic/topic list and unsaved message stores.
// Loads Standard Topic List into memory.
// (other items were commented in a header).
// -----------------------------------------------------------------------------
//
void CCbsDbImpTopicList::RebuildTopicAndUnsavedStoresL()
    {
    __TEST_INVARIANT;

    // Check for file lock
    CheckFileLockL();

    // About to write to FFS: make critical level check
    CbsUtils::FFSCriticalLevelCheckL( KTopicListRootStreamSize + 
        KDefaultTopicListSize * 2, iFs );
    
    if ( iMessages != NULL && iMessages->IsLockedMessages() )
        {
        User::Leave( KErrAccessDenied );
        }

    delete iTopicStore; 
    iTopicStore = NULL;
    delete iUnsavedMessageStore; 
    iUnsavedMessageStore = NULL;
    CbsUtils::DeleteFileL( iFs, *iTopicsFilename  );
    CbsUtils::DeleteFileL( iFs, *iUnsavedMessagesFilename );

    // Create new files.
    OpenFilesL( ETrue, EFalse );

    // Add standard index topic.
    AddIndexTopicL();
    
    // Load the Standard Topic list
    LoadDefaultTopicStreamL();

    iIsHotmarkedMessage = EFalse;
    iLastTopicNumber = 0;
    iMessageHandle = 0;

    // Inform the message manager.
    if ( iMessages )
        {
        iMessages->InvalidateCache();
        }

    // Notify everyone.
    NotifyTopicListInitializedL();

    __TEST_INVARIANT;
    }

// -----------------------------------------------------------------------------
// CCbsDbImpTopicList::GetLatestTopicNumber
// Returns the number of the topic that was added last
// to the database by topic detection feature.
// (other items were commented in a header).
// -----------------------------------------------------------------------------
//
TInt CCbsDbImpTopicList::GetLatestTopicNumber( 
    TCbsTopicNumber& aNumber ) const
    {
    TInt result( KErrNone );
    if ( iLastTopicNumber == 0 )
        {
        result = KErrNotFound;
        }
    aNumber = iLastTopicNumber;
    return result;
    }

// -----------------------------------------------------------------------------
// CCbsDbImpTopicList::OpenFilesL
// After a call to this function, the file stores can be assumed
// to be open and initialized.
// (other items were commented in a header).
// -----------------------------------------------------------------------------
//
void CCbsDbImpTopicList::OpenFilesL( 
    TBool aDeleteExistingFiles,
    TBool aCreateNewTopicList )
    {
    __ASSERT_DEBUG( iTopicsFilename->Length() > 0, 
        CbsServerPanic( ECbsInvalidFilenameDescriptor ) );
    __ASSERT_DEBUG( iUnsavedMessagesFilename->Length() > 0, 
        CbsServerPanic( ECbsInvalidFilenameDescriptor ) );

    // if LFS, delete files, create files, open files, write root stream
    // if not LFS, create file if it doesn't exist, open files

    // Close file stores.
    delete iTopicStore; 
    iTopicStore = NULL;
    delete iUnsavedMessageStore; 
    iUnsavedMessageStore = NULL;

    // If any of files doesn't exist, create the file. Also writes the root 
    // stream of the topic file, if necessary.
    // It is possible that this operation fails because FFS is full.
    // In this case the server will take a break and retry. If the second
    // attempt fails, the server is shut down.
    TBool unsavedMsgFileExists( EFalse );
    TRAPD( result, CreateFilesIfNecessaryL( unsavedMsgFileExists ) );
    if ( result != KErrNone )
        {
        // Critical exception: wait for a while and retry.
        User::After( KWaitAfterCriticalStoreException );
        TBool ignoreThis( EFalse ); // value of unsavedMsgFileExists preserved
        TRAP( result, CreateFilesIfNecessaryL( ignoreThis ) );
        if ( result != KErrNone )
            {
            __DEBUGGER();
            // Recovery is not possible: shut the server down.
            CActiveScheduler::Stop();
            User::Leave( KErrServerTerminated );
            }
        }

    // Open the files for use. Also reads the topic file root stream.
    TryToOpenFilesL( unsavedMsgFileExists == EFalse && 
                     aDeleteExistingFiles == EFalse, 
                     aCreateNewTopicList );

    }

// -----------------------------------------------------------------------------
// CCbsDbImpTopicList::CreateFilesIfNecessaryL
// Creates CBS files, if appropriate.
// (other items were commented in a header).
// -----------------------------------------------------------------------------
//
void CCbsDbImpTopicList::CreateFilesIfNecessaryL( 
    TBool& aUnsavedMsgFileExisted )
    {    
    if ( CbsUtils::ExistsL( iFs, *iTopicsFilename ) == EFalse )
        {
        CbsUtils::FFSCriticalLevelCheckL( KTopicListRootStreamSize + 
            KDefaultTopicListSize * 2, iFs );
   
        CPermanentFileStore* store = CPermanentFileStore::CreateLC( iFs,
            *iTopicsFilename, EFileWrite ); // on CS
    
        store->SetTypeL( store->Layout() );
        TStreamId id( CreateDefaultRootStreamL( *store ) );
        store->SetRootL( id );
        store->CommitL();
        CleanupStack::PopAndDestroy(); // store
        }    

    aUnsavedMsgFileExisted = CbsUtils::ExistsL( iFs, 
        *iUnsavedMessagesFilename );
    if ( aUnsavedMsgFileExisted == EFalse )
        {
        // No critical level check, because unsaved msgs reside on ramdisk
        DoCreateStoreL( *iUnsavedMessagesFilename );
        }
    }

// -----------------------------------------------------------------------------
// CCbsDbImpTopicList::DoCreateStoreL
// Creates an empty file store with the given filename.
// (other items were commented in a header).
// -----------------------------------------------------------------------------
//
void CCbsDbImpTopicList::DoCreateStoreL( 
    const TDesC& aFilename )
    {
    CFileStore* store = CPermanentFileStore::CreateLC( iFs, 
        aFilename, EFileWrite ); // on CS
    store->SetTypeL( store->Layout() );
    store->CommitL();
    CleanupStack::PopAndDestroy();
    }

// -----------------------------------------------------------------------------
// CCbsDbImpTopicList::TryToOpenFilesL
// Tries to open topic and unsaved messages files.
// (other items were commented in a header).
// -----------------------------------------------------------------------------
//
void CCbsDbImpTopicList::TryToOpenFilesL( 
    TBool aDeleteUnsavedMsgStreamIds,
    TBool aCreateNewTopicList )
    {
    CBSLOGSTRING("CBSSERVER: >>> CCbsDbImpTopicList::TryToOpenFilesL()" );
    
    if ( !iTopicStore )
        {
        CBSLOGSTRING("CBSSERVER: CCbsDbImpTopicList::TryToOpenFilesL(): *** NO iTopicStore (1) ***" );    
        }
    
    // Try to open the store    
    TRAPD( error, iTopicStore = CFileStore::OpenL( iFs, *iTopicsFilename, EFileRead | EFileWrite ) );
    CBSLOGSTRING2("CBSSERVER: CCbsDbImpTopicList::TryToOpenFilesL(); iTopicStore OpenL() error: %d", error );
    
    TRAPD( error2, iUnsavedMessageStore = CFileStore::OpenL( iFs, *iUnsavedMessagesFilename, EFileRead | EFileWrite ) );
    CBSLOGSTRING2("CBSSERVER: CCbsDbImpTopicList::TryToOpenFilesL(); iUnsavedMessageStore OpenL() error: %d", error2 );
       
    if ( error || error2 )
        {
        CBSLOGSTRING("CBSSERVER: CCbsDbImpTopicList::TryToOpenFilesL(): Calling InitializeListL( ETrue )..." );
        
        InitializeListL( ETrue );
        
        CBSLOGSTRING("CBSSERVER: CCbsDbImpTopicList::TryToOpenFilesL(): Calling InitializeListL( ETrue ) finished." );
        }
    else
        {
        if ( iTopicLists->Count() == 0 && 
            aDeleteUnsavedMsgStreamIds && 
            aCreateNewTopicList )
            {
            // Create first topic list, since it was deleted with the file
            CreateStandardTopicListL();
            }
                                               
        // Load the root stream for topic store.
        LoadRootStreamL();

        if ( aDeleteUnsavedMsgStreamIds )
            {
            // Load the topics and repair the topic file 
            // since unsaved msg file has been deleted
            LoadDefaultTopicStreamL();
            HandleDeletionOfUnsavedMessagesFileL();
            }
        }
    CBSLOGSTRING("CBSSERVER: <<< CCbsDbImpTopicList::TryToOpenFilesL()" );
    }

// ---------------------------------------------------------
// CCbsDbImpTopicList::ReadIndexTopicNameLC()
// Reads the localized index topic name
// (other items were commented in a header).
// ---------------------------------------------------------
HBufC* CCbsDbImpTopicList::ReadIndexTopicNameLC()
    {
    // Open localized resource file.
    RResourceFile resourceFile;

    CbsUtils::FindAndOpenDefaultResourceFileLC( iFs, resourceFile ); // on CS
    // Read "Index"-string.
    TResourceReader reader;
    reader.SetBuffer( resourceFile.AllocReadLC( R_TEXT_INDEX_TOPIC ) );//on CS
    HBufC* text = reader.ReadHBufCL();
    CleanupStack::PopAndDestroy(2); // readerBuf, resourceFile
    CleanupStack::PushL( text );

    return text;
    }

// -----------------------------------------------------------------------------
// CCbsDbImpTopicList::AddIndexTopicL
// Adds index topic to topic list.
// (other items were commented in a header).
// -----------------------------------------------------------------------------
//
void CCbsDbImpTopicList::AddIndexTopicL()
    {
    // Open localized resource file.
    HBufC* text = ReadIndexTopicNameLC();

    TCbsDbTopic topic;
    topic.iName.Copy( *text );
    topic.iNumber = 0;
    topic.iHotmarked = EFalse;
    topic.iProtected = ETrue;
    topic.iSubscribed = ETrue;
    topic.iSavedMessages = 0;
    topic.iUnreadMessages = 0;

    // Add the topic without notifying anybody.
    TRAPD( error, DoAddTopicL( topic ) );
    if ( error != KErrNone )
        {
        RevertFilesL();
        __TEST_INVARIANT;
        User::Leave( error );
        }

    CleanupStack::PopAndDestroy();  // text
    }

// -----------------------------------------------------------------------------
// CCbsDbImpTopicList::HandleDeletionOfUnsavedMessagesFileL
// Called to repair the database when Unsaved messages -file
// has been deleted, but Topics-file still contains information
// on unsaved messages.
//  
// Things to do here: 
//      - Reset unread message counters, because a saved message
//        is always also read.
//      - Remove stream ids to unsaved messages from 
//        Topic messages -streams.
// (other items were commented in a header).
// -----------------------------------------------------------------------------
//
void CCbsDbImpTopicList::HandleDeletionOfUnsavedMessagesFileL()
    {
    TRAPD( result, DoHandleDeletionOfUnsavedMessagesFileL() );
    if ( result != KErrNone )
        {
        // Recovery impossible -> reset the database.
        // Note that this function is not called when the database
        // is initialized.
        RebuildTopicAndUnsavedStoresL();
        }
    }

// -----------------------------------------------------------------------------
// CCbsDbImpTopicList::DoHandleDeletionOfUnsavedMessagesFileL
// Does the work for HandleDeletionOfUnsavedMessagesFileL().
// (other items were commented in a header).
// -----------------------------------------------------------------------------
//
void CCbsDbImpTopicList::DoHandleDeletionOfUnsavedMessagesFileL()
    {
    // Reset the unread counter of each topic and delete stream ids of 
    // unsaved messages.
    TInt numberOfTopics( iTopics->Count() );
    for ( TInt i( 0 ); i < numberOfTopics; i++ )
        {
        TCbsDbTopic topic;
        FindTopicByNumberL( iTopics->At( i ).iTopicData.iNumber, topic );
        topic.iUnreadMessages = 0;
        DoUpdateTopicL( topic, EFalse, i, EFalse );
        DeleteUnsavedMessageStreamIdsL( iTopics->At( i ).iTopicMessagesId );
        }

    UpdateTopicListStreamL( iCurrentTopicList, ETrue );
    TopicStoreL()->CommitL();
    }

// -----------------------------------------------------------------------------
// CCbsDbImpTopicList::DeleteUnsavedMessageStreamIdsL
// This function is called when the unsaved messages file has
// been deleted but the Topic messages -stream still contains
// stream ids to deleted message streams.
// All stream ids found in the given topic messages -stream 
// pointing to the unsaved messages file are removed.
// (other items were commented in a header).
// -----------------------------------------------------------------------------
//
void CCbsDbImpTopicList::DeleteUnsavedMessageStreamIdsL( 
    const TStreamId& aMsgStreamId ) const
    {
    CArrayFixFlat< TStreamId >* streamIds = 
        new ( ELeave ) CArrayFixFlat< TStreamId >( KTypicalNumberOfTopicMessages );
    CleanupStack::PushL( streamIds );

    RStoreReadStream instream;
    instream.OpenLC( *TopicStoreL(), aMsgStreamId ); // on CS

    // Read msg count
    TInt numberOfMsgs( instream.ReadInt16L() );

    // Read max msg count
    TInt maxNumberOfMsgs( instream.ReadInt16L() );

    TInt index( 0 );

    for ( ; index < numberOfMsgs; index++ )
        {
        TBool saved( CbsStreamHelper::ReadBoolL( instream ) );
        TStreamId id( 0 );
        instream >> id;
        if ( saved )
            {
            streamIds->AppendL( id );
            }
        }

    CleanupStack::PopAndDestroy();  // instream

    // Write stream ids of saved messages into the new topic message stream
    // which replaces the stream identified with aMsgStreamId.
    RStoreWriteStream outstream;    
    outstream.ReplaceLC( *TopicStoreL(), aMsgStreamId ); // on CS

    TInt numberOfSavedmessages( streamIds->Count() ); 

    // Number of messages = number of saved messages
    outstream.WriteInt16L( numberOfSavedmessages );

    // Number of saved messages <= number of messages <= maxNumberOfMsgs
    outstream.WriteInt16L( maxNumberOfMsgs );

    for ( index = 0; index < numberOfSavedmessages; index++ )
        {
        // All messages are saved (i.e., permanent)
        CbsStreamHelper::WriteBoolL( outstream, ETrue );
        outstream << streamIds->At( index );
        }

    for ( ; index < maxNumberOfMsgs; index ++ )
        {
        CbsStreamHelper::WriteBoolL( outstream, EFalse );
        outstream << TStreamId( 0 );
        }

    outstream.CommitL();
    CleanupStack::PopAndDestroy(2);  // outstream, streamIds
    
    }

// -----------------------------------------------------------------------------
// CCbsDbImpTopicList::UpdateTopicCountersL
// Resolves the topic position in topic list and uses this information
// to call DoUpdateTopicL.       
// Changes to stores are NOT commited.
// Call to DoUpdateTopicL is not trapped.
// (other items were commented in a header).
// -----------------------------------------------------------------------------
//
void CCbsDbImpTopicList::UpdateTopicCountersL( 
    const TCbsDbTopic& aTopic,
    const TBool aDeleting )
    {
    CBSLOGSTRING("CBSSERVER: >>> CCbsDbImpTopicList::UpdateTopicCountersL()");
    
    // Find out the position of the topic in topic list.    
    TInt index( TopicIndexInList( aTopic.iNumber ) );
    if ( index == KErrNotFound )
        {
        User::Leave( KErrNotFound );
        }   

    // DoUpdateTopicL leaves changes uncommited. EFalse for not having to
    // change topic position in topic list.
    DoUpdateTopicL( aTopic, EFalse, index, aDeleting );
    
    CBSLOGSTRING("CBSSERVER: <<< CCbsDbImpTopicList::UpdateTopicCountersL()");
    }

// -----------------------------------------------------------------------------
// CCbsDbImpTopicList::CreateStandardTopicListL
// Creates the Standard topic list (topic list no. 0)
// (other items were commented in a header).
// -----------------------------------------------------------------------------
//
void CCbsDbImpTopicList::CreateStandardTopicListL()
    {
    // Read standard topic list name from the resource file
    RResourceFile resourceFile;
    CbsUtils::FindAndOpenDefaultResourceFileLC( iFs, resourceFile ); // on CS

    TResourceReader reader;
    reader.SetBuffer( resourceFile.AllocReadLC( R_TEXT_STANDARD_TOPIC_LIST ) ); // on CS
    HBufC* standardTopicListName = reader.ReadHBufCL();
    CleanupStack::PushL( standardTopicListName );

    // Create a new topic list
    CreateNewTopicListL( standardTopicListName->Des() );

    CleanupStack::PopAndDestroy( 3 ); // resourceFile, reader, standardTopicListName
    }

// -----------------------------------------------------------------------------
// CCbsDbImpTopicList::LoadTopicsIdsL
// Reads topic IDs of a topic list from the stream.
// (other items were commented in a header).
// -----------------------------------------------------------------------------
//
void CCbsDbImpTopicList::LoadTopicsIdsL( 
    const TStreamId& aTopicListStreamId )
    {
    // Open the topic list stream
    RStoreReadStream instream;
    instream.OpenLC( *TopicStoreL(), aTopicListStreamId ); // on CS

    // Skip topic list name
    delete HBufC::NewL( instream, KCbsDbTopicNameLength );

    // Skip default list status
    CbsStreamHelper::ReadBoolL( instream );

    // Skip Topic List number
    instream.ReadInt16L();
    
    // Skip the amount of topics
    TInt topicCount = instream.ReadInt16L();

    // Clear the array
    iTopicIds->ResizeL( 0 );

    TStreamId id( 0 );

    // Load the topic IDs       
    for ( TInt i = 0; i < topicCount; i++ )
        {
        instream >> id;
        iTopicIds->AppendL( id );
        }   
    
    CleanupStack::PopAndDestroy(); // instream
    }

// -----------------------------------------------------------------------------
// CCbsDbImpTopicList::LoadTopicsL
// Loads topics of a specified topic list.
// (other items were commented in a header).
// -----------------------------------------------------------------------------
//
void CCbsDbImpTopicList::LoadTopicsL( 
    const TStreamId& aTopicListStreamId )
    {
    // Open the topic list stream
    RStoreReadStream instream;
    instream.OpenLC( *TopicStoreL(), aTopicListStreamId ); // on CS

    // Skip topic list name
    delete HBufC::NewL( instream, KCbsDbTopicNameLength );

    // Skip default list status
    CbsStreamHelper::ReadBoolL( instream );

    // Skip Topic List number
    instream.ReadInt16L();
    
    // Skip the amount of topics
    TInt topicCount = instream.ReadInt16L();

    // Clear the arrays
    iTopics->ResizeL( 0 );
    iTopicIds->ResizeL( 0 );

    TStreamId id( 0 );

    // Load the topic IDs 
    TInt i;
    for ( i = 0; i < topicCount; i++ )
        {
        instream >> id;
        iTopicIds->AppendL( id );
        }   
    
    CleanupStack::PopAndDestroy(); // instream
   
    // Load necessary information for each topic
    TInt count = iTopicIds->Count();
    TCbsDbImpTopic topic;
    for ( i = 0; i < count; i++ )
        {
        ReadTopicInformationL( iTopicIds->At( i ), topic );

        if ( topic.iTopicData.iNumber == KIndexTopicNumber )
            {
            HBufC* indexName = ReadIndexTopicNameLC(); // on CS
            topic.iTopicData.iName.Copy( *indexName );
            CleanupStack::PopAndDestroy(); // indexName
            }
        iTopics->AppendL( topic );
        }    
    }

// -----------------------------------------------------------------------------
// CCbsDbImpTopicList::UpdateTopicStreamIdsL
// Updates the topic stream IDs.
// (other items were commented in a header).
// -----------------------------------------------------------------------------
//
void CCbsDbImpTopicList::UpdateTopicStreamIdsL( 
    const TCbsDbTopicNumber aTopicNumber )
    {
    // Try to find the topic from current topic list
    TInt position( TopicIndexInList( aTopicNumber ) );
    if ( position >= 0 )
        {
        // Remove from the internal cache of this topic list
        iTopics->Delete( position );

        iCurrentTopicList.iTopicCount--;

        // Update the stream
        UpdateTopicListStreamL( iCurrentTopicList, EFalse );
        CommitFilesL();
        }
    }

// -----------------------------------------------------------------------------
// CCbsDbImpTopicList::__DbgTestInvariant
// Checks that the object is in a valid state, and panics if it is not.
// (other items were commented in a header).
// -----------------------------------------------------------------------------
//
void CCbsDbImpTopicList::__DbgTestInvariant() const
    {
    /*
#if defined(_DEBUG)
#endif
    */
    }

// ========================== OTHER EXPORTED FUNCTIONS =========================

//  End of File