--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/cbs/CbsServer/ServerSrc/CCbsDbImpTopicList.cpp Tue Feb 02 01:11:09 2010 +0200
@@ -0,0 +1,2757 @@
+/*
+* 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