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

#include <logclientchangeobserver.h>
#include "LogServDatabaseMarshall.h"
#include "logservpanic.h"
#include "LogServCacheConfig.h"
#include "LogServCacheStrings.h"
#include "LogServCacheTypes.h"
#include "LogServBackupInterface.h"
#include "LogServResourceInterpreter.h"
#include "LogServDatabaseChangeInterface.h"
#include <logserv.rsg>
#include "LogServSqlStrings.h"
#include "LOGREPDEFS.H"


// Constants
const TInt KExpectedNumberOfTables = 4; // Should match the code in CreateTablesL

// Literal constants
_LIT(KLogDatabaseName,"Logdbu.dat");



/////////////////////////////////////////////////////////////////////////////////////////
// -----> CLogServDatabaseMarshall (source)
/////////////////////////////////////////////////////////////////////////////////////////

CLogServDatabaseMarshall::CLogServDatabaseMarshall(RFs& aFsSession, 
												   CLogServResourceInterpreter& aResourceInterface, 
												   MLogServBackupInterface& aBackupInterface)
:	iFsSession(aFsSession), iResourceInterface(aResourceInterface), iBackupInterface(aBackupInterface)
	{
	}

CLogServDatabaseMarshall::~CLogServDatabaseMarshall()
	{
	iBackupInterface.BIObserverRemove(*this);
	//
	delete iDatabaseName;
	delete iCacheStrings;
	delete iCacheTypes;
	delete iCacheConfig;
	delete iSecurity;
	delete iEventType;
	//
	iDatabase.Close();
	iStandardTypeUids.Close();
	}

void CLogServDatabaseMarshall::ConstructL()
	{
	iBackupInterface.BIObserverAddL(*this, MLogServBackupInterface::EObjectDatabaseMarshall);
	//
	iSecurity = CLogServSecurity::NewL(iResourceInterface);
	//
	iEventType = CLogEventType::NewL();
	
	DatabaseLocateL();
	DatabaseOpenL();
	RestoreStandardTypesL();
	
	iCacheTypes->CopyStandardTypeUidsL(iStandardTypeUids);
	}

CLogServDatabaseMarshall* CLogServDatabaseMarshall::NewL(RFs& aFsSession, 
														 CLogServResourceInterpreter& aResourceInterface, 
														 MLogServBackupInterface& aBackupInterface)
	{
	CLogServDatabaseMarshall* self = new(ELeave) CLogServDatabaseMarshall(aFsSession, aResourceInterface, aBackupInterface);
	CleanupStack::PushL(self);
	self->ConstructL();
	CleanupStack::Pop(self);
	return self;
	}

/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////

TInt CLogServDatabaseMarshall::DTIBegin()
	{
	const TInt backupError = iBackupInterface.BIErrorValueForCurrentState();
	if	(backupError != KErrNone)
		return backupError;

	__ASSERT_DEBUG(!iDatabase.InTransaction(), Panic(ELogBeginInTransaction));
	return iDatabase.Begin();
	}

TInt CLogServDatabaseMarshall::DTICommitAndEnd()
	{
	__ASSERT_DEBUG(iDatabase.InTransaction(), Panic(ELogCommitNotInTransaction));
	TInt err = iDatabase.Commit();
	if (err == KErrNone && iCacheStrings != NULL)
		{
		iCacheStrings->Commit();
		}
	return err;
	}

void CLogServDatabaseMarshall::DTIRollBack()
	{
	__ASSERT_DEBUG(iDatabase.InTransaction(), Panic(ELogRollbackNotInTransaction));
	iDatabase.Rollback();
	if (iCacheStrings != NULL)
		{
		iCacheStrings->Rollback();
		}
	}

TInt CLogServDatabaseMarshall::DTIExecuteSql(const TDesC& aStatement, TDbTextComparison aComparison)
	{
	return iDatabase.Execute(aStatement, aComparison);
	}

TBool CLogServDatabaseMarshall::DTIInTransaction() const
	{
	return iDatabase.InTransaction();
	}

TBool CLogServDatabaseMarshall::DTIDatabaseIsDamaged() const
	{
	return iDatabase.IsDamaged();
	}

CLogServResourceInterpreter& CLogServDatabaseMarshall::DTIResourceInterface() const
	{
	return iResourceInterface;
	}

MLogServDatabaseChangeInterface& CLogServDatabaseMarshall::DTIChangeInterface() const
	{
	__ASSERT_ALWAYS(iChangeInterface, Panic(ELogNoChangeInterfacePointer));
	return *iChangeInterface;
	}

CLogServCacheStrings& CLogServDatabaseMarshall::DTICacheStrings() const
	{
	__ASSERT_ALWAYS(iCacheStrings, Panic(ELogCacheAccessDuringBackupStrings));
	return *iCacheStrings;
	}

CLogServCacheTypes& CLogServDatabaseMarshall::DTICacheTypes() const
	{
	__ASSERT_ALWAYS(iCacheTypes, Panic(ELogCacheAccessDuringBackupTypes));
	return *iCacheTypes;
	}

CLogServCacheConfig& CLogServDatabaseMarshall::DTICacheConfig() const
	{
	__ASSERT_ALWAYS(iCacheConfig, Panic(ELogCacheAccessDuringBackupConfig));
	return *iCacheConfig;
	}

RDbDatabase& CLogServDatabaseMarshall::DTIDatabase()
	{
	return iDatabase;
	}

TBool CLogServDatabaseMarshall::DTIIsAllowed(TEventOp aEventOp, const RMessage2& aMessage, TUid aEventType, const char* aDiagnostic) const
	{
	return iSecurity->IsAllowed(aMessage, aEventType, aEventOp, aDiagnostic);
	}
	
const RArray<TUid>& CLogServDatabaseMarshall::DTIUidsOfStandardTypes()
	{
	return iStandardTypeUids;
	}

/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////

void CLogServDatabaseMarshall::BOHandleEventL(TLogServBackupEvent aEvent)
	{
	switch(aEvent)
		{
		case EBackupStarting:
			{
			//Destroy config, types and strings caches
			delete iCacheConfig;
			iCacheConfig = NULL;
			delete iCacheStrings;
			iCacheStrings = NULL;
			delete iCacheTypes;
			iCacheTypes = NULL;
			//Close the database
			iDatabase.Close();
			}
			break;
			
		case EBackupEnded:
			{
			// Re-open the database and create config, types and strings caches 
			DatabaseOpenL();
			// reset views as a different database is being restored
			DTIChangeInterface().DCISubmitGlobalChangeContextL(KLogClientChangeEventRefreshView);
			}
			break;
		default:
			break;
		}
	}

/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////

void CLogServDatabaseMarshall::DatabaseLocateL()
	{
	// Get drive for database
	TDriveUnit driveUnit(static_cast<TInt>(RFs::GetSystemDrive()));
	TDriveName name(driveUnit.Name());
	
	TFileName path;
	iFsSession.PrivatePath(path);
	
	// Ensure database path exists
	TParse parse;
	User::LeaveIfError(parse.Set(path, &name, NULL));
	path = parse.FullName();
	
	TInt error = iFsSession.MkDirAll(path);
	if	(error != KErrAlreadyExists)
		User::LeaveIfError(error);
	
	path += KLogDatabaseName;
	iDatabaseName = path.AllocL();
	}

/**
Opens the LogEng database.
@return KErrNone,    If the "database open" operation completes successfully.
                     If the "database open" operation fails the function returns the repported error code. 
        KErrCorrupt, If the database is opened successfully but is damaged, then the function returns KErrCorrupt.
*/
TInt CLogServDatabaseMarshall::DoDbOpen()
    {
    LOGTEXT2("CLogServDatabaseMarshall::DatabaseOpenL() - attempting to open db: %S", iDatabaseName);
#ifdef LOGGING_ENABLED
    TEntry entry;
    if  (iFsSession.Entry(*iDatabaseName, entry) == KErrNone)
        {
        LOGTEXT2("CLogServDatabaseMarshall::DatabaseOpenL() - Database file: %S DOES exist", iDatabaseName);        
        }
    else
        {
        LOGTEXT2("CLogServDatabaseMarshall::DatabaseOpenL() - Database file: %S NOT FOUND", iDatabaseName);     
        }
#endif
    // Open database
    TInt err = iDatabase.Open(iFsSession, DatabaseName());
    LOGTEXT2("CLogServDatabaseMarshall::DatabaseOpenL() - attempting to open DMBS database resulted in error: %d", error);
    // Check if the database is damaged. If it is set the error to KErrCorrupt so that it 
    // will be deleted.
    if ((err == KErrNone) && iDatabase.IsDamaged()) 
        {
        err =  KErrCorrupt;
        }
    return err;
    }

/**
Check if the database table count is the expected one -  KExpectedNumberOfTables.
@return True,   The database tables count is KExpectedNumberOfTables,
        False,  The database tables count is not KExpectedNumberOfTables;
@leave  KErrNoMemory, an out of memory condition has occurred;
                      Note that the function may leave with database specific errors or 
                      other system-wide error codes.
*/
TBool CLogServDatabaseMarshall::DbTableCntCheckL()
    {
    CDbTableNames* tables = iDatabase.TableNamesL();
    TInt numberOfTables = tables->Count();
    delete tables;
    LOGTEXT2("CLogServDatabaseMarshall::DatabaseOpenL() - Number of tables: %d", numberOfTables);
    return numberOfTables == KExpectedNumberOfTables;
    }

/**
Alters the "Event" table if the number column length is not KLogMaxNumberLength.
@return KErrNone, The "alter" operation has completed successfully, system wide or database specific error code otherwise.  
*/
TInt CLogServDatabaseMarshall::AlterEventTblIfOldFmt(CDbColSet& aEventTblColSet)
    {
    const TDbCol* numberCol = aEventTblColSet.Col(KLogFieldEventNumberString);
    __ASSERT_DEBUG(numberCol != NULL, User::Invariant());
    TInt err = KErrNone;
    // check the column width is correct
    if(numberCol->iMaxLength != KLogMaxNumberLength)
        {
        //The column width is not correct, so this is an old format database.
        //Modify the database so the number length is KLogMaxNumberLength.
        (const_cast <TDbCol*> (numberCol))->iMaxLength = KLogMaxNumberLength;
        err = iDatabase.AlterTable(KLogNameEventString, aEventTblColSet);
        }
    return err;
    }

#ifdef SYMBIAN_ENABLE_EVENTLOGGER_DUALSIM	

/**
Alters the "Event" table if the the table does not have "SimId" column.
@return KErrNone, The "alter" operation has completed successfully, system wide or database specific error code otherwise.  
@leave  KErrNoMemory, an out of memory condition has occurred;
                      Some other failure codes, not related to the "alter" opertaion.
*/
TInt CLogServDatabaseMarshall::AlterEventTblIfNoSimIdL(CDbColSet& aEventTblColSet)
    {//Compiled only when SYMBIAN_ENABLE_EVENTLOGGER_DUALSIM macro is defined
    const TDbCol* simIdCol = aEventTblColSet.Col(KLogFieldEventSimId);
    TInt err = KErrNone;
    if(!simIdCol)
        {
        TDbCol col(KLogFieldEventSimId, EDbColUint32);
        aEventTblColSet.AddL(col);
        err = iDatabase.AlterTable(KLogNameEventString, aEventTblColSet);
        }
    return err;
    }

#endif

/**
Checks the database structure and alters the tables if that's an old format database.
@return KErrNone, The "alter" operation has completed successfully, system wide or database specific error code otherwise.
@leave  KErrNoMemory, an out of memory condition has occurred;
                      Some other failure codes, not related to the "alter" opertaion.
*/
TInt CLogServDatabaseMarshall::AlterDbIfOldFmtL()
    {
    CDbColSet* tableEventCol = iDatabase.ColSetL(KLogNameEventString);
    CleanupStack::PushL(tableEventCol);
    //Check for old format database which had MaxNumberLength =32
    TInt err = AlterEventTblIfOldFmt(*tableEventCol);
#ifdef SYMBIAN_ENABLE_EVENTLOGGER_DUALSIM	
    //Check if the "SimId" column is present
    if(err == KErrNone)
        {
        err = AlterEventTblIfNoSimIdL(*tableEventCol);
        }
#endif    
    CleanupStack::PopAndDestroy(tableEventCol);
    return err;
    }

/**
Closes and deletes the LogEng database. In _DEBUG builds the "delete file" error will be printed out. 
*/
void CLogServDatabaseMarshall::DbDelete()
    {
    iDatabase.Close();
#ifdef _DEBUG       
	//Do not remove the statement bellow. In _DEBUG builds it forms a single "TInt err2 = iFsSession.Delete(DatabaseName());" statement.
    TInt err2 =
#endif      
    iFsSession.Delete(DatabaseName());
#ifdef _DEBUG
    if((err2 != KErrNone) && (err2 != KErrNotFound))
        {
        RDebug::Print(_L("CLogServDatabaseMarshall::DatabaseOpenL() - Failed to delete file. Error = %d"), err2);
        }
#endif
    }

/**
Attempts to create the LogEng database and tables.
@return   KErrNoNone, The database was created successfully, system wide or database specific error otherwise.
*/
TInt CLogServDatabaseMarshall::DbCreate()
    {
    // Try and create the database
    LOGTEXT("CLogServDatabaseMarshall::DatabaseOpenL() - trying to create database");
    TRAPD(err, DatabaseCreateL(DatabaseName()));
    LOGTEXT2("CLogServDatabaseMarshall::DatabaseOpenL() - creation error was: %d", error);
    return err;
    }

/**
Creates config and string LogEng caches. Finishes the initialization of the event types cache. 
@leave  KErrNoMemory, an out of memory condition has occurred;
                      Note that the function may leave with database specific errors or 
                      other system-wide error codes.
*/
void CLogServDatabaseMarshall::CreateCachesL()
    {
    // Create other cache objects (these require the database to be already opened)
    LOGTEXT("CLogServDatabaseMarshall::DatabaseOpenL() - creating config cache");
    iCacheConfig = CLogServCacheConfig::NewL(*this);
    LOGTEXT("CLogServDatabaseMarshall::DatabaseOpenL() - creating string cache");
    iCacheStrings = CLogServCacheStrings::NewL(*this);
    // Finish the type cache initialization
    LOGTEXT("CLogServDatabaseMarshall::DatabaseOpenL() - initializing type cache");
    iCacheTypes->InitializeL();
    }

void CLogServDatabaseMarshall::DatabaseOpenL()
	{
	// Create the cache objects - objects need to be put into the cache as
	// soon as the database is open.
	LOGTEXT("CLogServDatabaseMarshall::DatabaseOpenL() - creating type cache");
	iCacheTypes = CLogServCacheTypes::NewL(*this);
	TInt err = DoDbOpen();
	// Check we have the expected number of tables
	if(err == KErrNone && !DbTableCntCheckL())
		{
	    err = KErrCorrupt;
		}
    // Check a compaction can be performed. If it can't it indicates a serious problem?
    if(err == KErrNone && (err = iDatabase.Compact()) != KErrNone)
       {
       err = KErrCorrupt;
       }
	if(err == KErrNone)
		{
		err = CLogServDatabaseMarshall::AlterDbIfOldFmtL();
		}
	// If the database failed to open, delete and recreate.
	if(err == KErrNotFound || err == KErrCorrupt || err == KErrArgument || err == KErrEof)
		{
		DbDelete();
		// Try and create the database
		err = DbCreate();
	    if(err != KErrNone)
	        {
	        DbDelete();
	        }
		}
	User::LeaveIfError(err);
	CreateCachesL();
    // At this point, its safe to tell the backup interface what file it has to watch
    LOGTEXT("CLogServDatabaseMarshall::DatabaseOpenL() - registering database filename with backup interface");
    iBackupInterface.BISetDatabaseNameL(DatabaseName());
	LOGTEXT("CLogServDatabaseMarshall::DatabaseOpenL() - end");
	}

void CLogServDatabaseMarshall::DatabaseCreateL(const TDesC& aName)
	{
	User::LeaveIfError(iDatabase.Replace(iFsSession, aName));
	CreateTablesL();
	}

// Note: Number of tables HAS to match KExpectedNumberOfTables
void CLogServDatabaseMarshall::CreateTablesL()
	{
	DTIBeginWithRollBackProtectionLC();

	// Create event table
	TheSql.Format(KLogTableEventString, KLogMaxRemotePartyLength, KLogMaxSubjectLength, KLogMaxNumberLength);
	User::LeaveIfError(iDatabase.Execute(TheSql));
	MakeColumnAutoIncremetingL(KLogNameEventString, KLogFieldIdString);

	// Create event type table
	TheSql.Format(KLogTableTypeString, KLogMaxDescriptionLength);
	User::LeaveIfError(iDatabase.Execute(TheSql));
	MakeColumnAutoIncremetingL(KLogNameTypeString, KLogFieldIdString);

	// Create string list table
	TheSql.Format(KLogTableStringString, KLogMaxSharedStringLength);
	User::LeaveIfError(iDatabase.Execute(TheSql));
	MakeColumnAutoIncremetingL(KLogNameStringString, KLogFieldIdString);

	// Create configuration table
	TheSql.Copy(KLogTableConfigString);
	User::LeaveIfError(iDatabase.Execute(TheSql));

	// Create the index
	CreateIndiciesL();

	// Set the initial configuration
	CreateConfigurationL();

	// Load standard event types
	CreateTypesL();

	DTICommitAndCancelRollbackProtectionL();
	}

void CLogServDatabaseMarshall::CreateTypesL(TBool aReadOnly)
	{
	// Get the array size
	TResourceReader reader;
	iResourceInterface.CreateResourceReaderLC(reader, R_LOG_INITIAL_EVENTS, CLogServResourceInterpreter::ELogWrap);

	// Create them
	DTICacheTypes().CreateStandardTypesL(reader, aReadOnly);

	CleanupStack::PopAndDestroy(); // reader
	}

void CLogServDatabaseMarshall::CreateIndiciesL()
	{
	// Get the array size
	TResourceReader reader;
	iResourceInterface.CreateResourceReaderLC(reader, R_LOG_INDEXES, CLogServResourceInterpreter::ELogWrap);

	const TInt indexes = reader.ReadInt16();

	// Read in the array
	for(TInt c1 = 0; c1 < indexes; c1++)
		{
		const TPtrC name(reader.ReadTPtrC());
		const TPtrC table(reader.ReadTPtrC());

		// Get the number of keys
		const TInt keys = reader.ReadInt16();

		CDbKey* key = CDbKey::NewLC();

		for(TInt c2 = 0; c2 < keys; c2++)
			{
			TPtrC col = reader.ReadTPtrC();
			TUint order = reader.ReadUint16();
			TInt len = reader.ReadInt16();

			// Add the key
			key->AddL(TDbKeyCol(col, len, (TDbKeyCol::TOrder)order));
			}

		// Make key unique if required
		if (reader.ReadInt8())
			key->MakeUnique();

		// Set comparison
		const TDbTextComparison comparison = static_cast<TDbTextComparison>(reader.ReadInt8());
		key->SetComparison(comparison);

		// Create the index
		User::LeaveIfError(iDatabase.CreateIndex(name, table, *key));

		CleanupStack::PopAndDestroy(key);
		}

	CleanupStack::PopAndDestroy(); // reader
	}

void CLogServDatabaseMarshall::CreateConfigurationL()
	{
	// Load the resource/repository file default configuration
	// The precedence is given to the reading from the repository file.
   	TLogConfig config;
	
	CRepository* repository = NULL;
	TRAPD(res, repository = CRepository::NewL(KUidLogengRepository));		
	if (res == KErrNone)
		{
		CleanupStack::PushL(repository);
		ReadRepositoryFileConfigurationL(config, *repository);
		CleanupStack::PopAndDestroy(repository);
		}
	else if (res == KErrCorrupt)
		{
		User::Leave(res);
		}
	else
		{
		ReadResourceFileConfigurationL(config);
		}	
   	// Insert the column
   	TheSql.Format(KLogSqlInsertConfigString, config.iMaxLogSize, config.iMaxRecentLogSize, config.iMaxEventAge);
	User::LeaveIfError(iDatabase.Execute(TheSql));
	}

void CLogServDatabaseMarshall::ReadRepositoryFileConfigurationL(TLogConfig& aConfig, CRepository& repository) const
	{
	TInt maxLogSize;
	TInt maxRecentLogSize;
	TInt maxEventAge;
	//	
	User::LeaveIfError(repository.Get(KMaxLogSizeRepKey, maxLogSize));
	aConfig.iMaxLogSize = static_cast<TLogSize> (maxLogSize);
	User::LeaveIfError(repository.Get(KMaxRecentLogSizeRepKey, maxRecentLogSize));
	aConfig.iMaxRecentLogSize = static_cast<TLogRecentSize> (maxRecentLogSize);
	User::LeaveIfError(repository.Get(KMaxEventAgeRepKey, maxEventAge));
	aConfig.iMaxEventAge = static_cast<TLogAge> (maxEventAge);	
	}

void CLogServDatabaseMarshall::ReadResourceFileConfigurationL(TLogConfig& aConfig) const
	{	
	TResourceReader reader;
	iResourceInterface.CreateResourceReaderLC(reader, R_LOG_INITIAL_CONFIG, CLogServResourceInterpreter::ELogWrap);
	//
	aConfig.iMaxLogSize = static_cast<TLogSize>(reader.ReadUint16());
	aConfig.iMaxRecentLogSize = static_cast<TLogRecentSize>(reader.ReadUint8());
	aConfig.iMaxEventAge = static_cast<TLogAge>(reader.ReadUint32());
	//
	CleanupStack::PopAndDestroy(); // reader	
	}

void CLogServDatabaseMarshall::MakeColumnAutoIncremetingL(const TDesC& aTable, const TDesC& aColumn)
	{
	CDbColSet* newTable = iDatabase.ColSetL(aTable);
	CleanupStack::PushL(newTable);

	const TDbCol* oldCol = newTable->Col(aColumn);
	__ASSERT_DEBUG(oldCol != NULL, Panic(ELogNoSuchColumn));

	TDbCol newCol = *oldCol;
	newCol.iAttributes |= TDbCol::EAutoIncrement;

	newTable->Remove(aColumn);
	newTable->AddL(newCol);

	User::LeaveIfError(iDatabase.DropTable(aTable));
	User::LeaveIfError(iDatabase.CreateTable(aTable, *newTable));
		
	CleanupStack::PopAndDestroy(newTable);
	}
	
