loggingservices/eventlogger/LogServ/src/LogServView.cpp
changeset 0 08ec8eefde2f
child 55 44f437012c90
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/loggingservices/eventlogger/LogServ/src/LogServView.cpp	Fri Jan 22 11:06:30 2010 +0200
@@ -0,0 +1,861 @@
+// 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 "LogServView.h"
+#include "logpackage.h"
+#include "logservpanic.h"
+#include "LogServBackupInterface.h"
+#include "LogServViewChangeManager.h"
+#include "LogServDatabaseChangeInterface.h"
+#include "LogServCacheTypes.h"
+#include "LogServSqlStrings.h"
+#include "LOGFILTQ.H"
+
+// Constants
+const TInt KLogViewContentsGranuality = 20;
+const TInt KLogViewLockStatusEventGranularity = 3;
+
+TDbColNo CLogServViewBase::iIdColNo = 0;
+TDbColNo CLogServViewBase::iTypeColNo = 0;
+TDbColNo CLogServViewBase::iFlagColNo[] = {0, 0, 0, 0};
+
+TDbColNo CLogServViewRecent::iIdColNo = 0;
+TDbColNo CLogServViewRecent::iRecentColNo = 0;
+TDbColNo CLogServViewRecent::iDuplicateColNo = 0;
+
+//diagnostic message for the platform security
+const char* KIgnoreDiagnostic = "This diagnostic message does not indicate an error, please ignore it";
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// -----> CLogServViewBase (source)
+/////////////////////////////////////////////////////////////////////////////////////////
+CLogServViewBase::CLogServViewBase(MLogServDatabaseTransactionInterface& aDatabase, 
+                                   MLogServBackupInterface& aBackupInterface, 
+                                   CLogPackage& aPackage, TLogViewType aType, TLogViewId aViewId, 
+                                   const RMessage2& aMessage) :
+    iDatabase(aDatabase), 
+    iPackage(aPackage),
+    iBackupInterface(aBackupInterface), 
+    iType(aType), 
+    iViewId(aViewId), 
+    iMessage (aMessage)
+	{
+	}
+
+CLogServViewBase::~CLogServViewBase()
+	{
+	iDatabase.DTIChangeInterface().DCIRequestChangeNotificationsCancel(*this);
+	iBackupInterface.BIObserverRemove(*this);
+	//
+	iViewContents.Close();
+	//	
+	iStandardTypeSecurityCache.Close();
+	//
+	delete iLockChangeObserver;
+	delete iSql;
+	delete iChangeManager;
+	}
+
+
+void CLogServViewBase::ConstructL()
+	{
+	// Handles changes for this view
+	iChangeManager = CLogServViewChangeManager::NewL(iDatabase.DTIChangeInterface());
+
+	// Register for change events
+	iDatabase.DTIChangeInterface().DCIRequestChangeNotificationsL(*this);
+
+	// Register for backup events
+	iBackupInterface.BIObserverAddL(*this, MLogServBackupInterface::EObjectView);
+
+	// Observes when the view is locked/unlocked due to a backup
+	iLockChangeObserver = CLogServViewLockObserver::NewL(iBackupInterface);
+
+	const RArray<TUid>& arrTUids = iDatabase.DTIUidsOfStandardTypes();
+	TInt count = arrTUids.Count();
+	iStandardTypeSecurityCache.ReserveL(count);
+	for(TInt i=0; i < count; i++)
+		{
+		SStandardTypeSecurity securitySetting;
+		securitySetting.eventType = arrTUids[i];
+		securitySetting.readAccess = iDatabase.DTIIsAllowed(EReadOp, iMessage, arrTUids[i], KIgnoreDiagnostic);
+		securitySetting.writeAccess = iDatabase.DTIIsAllowed(EWriteOp, iMessage, arrTUids[i], KIgnoreDiagnostic);
+		TInt err = iStandardTypeSecurityCache.Append(securitySetting);	
+        __ASSERT_ALWAYS(err == KErrNone, Panic(ELogArrayReserved));
+		}
+	}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void CLogServViewBase::DCOHandleChangeEventsL(const CLogChangeDefinition& aChanges)
+	{
+	// Just return if the view isn't setup
+	if	(!iSql)
+		return;
+
+	TRAPD(error, DoHandleChangeEventsL(aChanges));
+	if	(error != KErrNone)
+		{
+		iViewContents.Close();
+	    iViewContentsReady = EFalse;
+		iRebuildViewContents = ETrue;
+		}
+	}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void CLogServViewBase::BOHandleEventL(TLogServBackupEvent aEvent)
+	{
+	switch(aEvent)
+		{
+	case EBackupStarting:
+		iViewContents.Close();
+	    iViewContentsReady = EFalse;
+		break;
+	case EBackupEnded:
+		RebuildViewL();
+		break;
+		}
+	}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////////////
+
+#pragma BullseyeCoverage off
+
+/**
+Remove an entry from the view.
+By default you can't remove an event from a view.
+*/
+void CLogServViewBase::RemoveL(const RMessage2& /*aMessage*/)
+	{
+	User::Leave(KErrNotSupported);
+	}
+
+/**
+By default you can't clear duplicates from a view
+*/
+void CLogServViewBase::ClearDuplicatesL(const RMessage2& /*aMessage*/)
+	{
+	User::Leave(KErrNotSupported);
+	}
+
+#pragma BullseyeCoverage on
+
+/**
+Set the flags of all the entries in the view.
+*/
+void CLogServViewBase::SetFlagsL(const RMessage2& aMessage)
+	{
+	if	(ViewIsReady())
+		{
+		// Flags from client
+		const TLogFlags flags = static_cast<TLogFlags>(aMessage.Int2());
+		RLogDbView view;
+		view.PrepareLC(iDatabase.DTIDatabase(), *iSql);
+		InitializeColumnsL(view);
+		if(view.FirstL())
+			{
+            iDatabase.DTIBeginWithRollBackProtectionLC();
+			// Iterate through the events
+			do
+				{
+				// Get current event id
+				view.GetL();
+				const TLogId id = view.ColInt32(CLogServViewBase::iIdColNo);
+				TUint8 eventTypeIndex = view.ColUint8(CLogServViewBase::iTypeColNo);
+				if(IsAllowed(EWriteOp, eventTypeIndex))
+					{
+					// Make the change
+					view.UpdateL();
+		            TInt bit = KLogFlagsCount;
+		            while(bit--)
+		                {
+                        const TBool flagIsSet = flags & (0x1 << bit) ? 1 : 0;
+		                view.SetColL(CLogServViewBase::iFlagColNo[bit], flagIsSet);
+		                }
+					view.PutL();
+					iDatabase.DTIChangeInterface().DCISubmitChangedEventContextL(ELogChangeTypeEventChanged, id);
+					}
+				}
+			while(view.NextL());
+			iDatabase.DTICommitAndCancelRollbackProtectionL();
+			}
+		CleanupStack::PopAndDestroy(&view);
+		}
+	else
+		::PanicClientL(aMessage, ELogViewRecentViewNotYetReadyForFlagSetting);
+	}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+The client has requested change notifications
+*/
+void CLogServViewBase::RequestChangeNotifications(const RMessage2& aMessage)
+	{
+	iChangeManager->RequestChangeNotifications(aMessage);
+	}
+
+/**
+The client has cancelled a previous change notification request
+*/
+void CLogServViewBase::RequestChangeNotificationsCancel()
+	{
+	iChangeManager->RequestChangeNotificationsCancel();
+	}
+
+/**
+The client has requested lock status change notifications
+*/
+void CLogServViewBase::RequestLockStatusChanges(const RMessage2& aMessage)
+	{
+	iLockChangeObserver->RequestLockStatusChanges(aMessage);
+	}
+
+/**
+The client has cancelled a previous change notification request
+*/
+void CLogServViewBase::RequestLockStatusChangesCancel()
+	{
+	iLockChangeObserver->RequestLockStatusChangesCancel();
+	}
+
+/**
+The client has requested the current list of changes
+*/
+void CLogServViewBase::RequestChangesL(const RMessage2& aMessage)
+	{
+	if	(!iSql)
+		::PanicClientL(aMessage, ELogViewNotSetupForChangesFetch);
+	else
+		iChangeManager->DeliverChangesL(aMessage);
+	}
+
+/**
+Set up the server-side view based upon the client-side filter. This
+method essentially fetches a client-side filter and then dynamically
+executes the associated SELECT statement on the database to build up
+a view. This view is then cached in terms of the entry ids (iViewContents).
+*/
+void CLogServViewBase::SetupL(const RMessage2& aMessage, TLogFilterConstructionType aFilterType)
+	{
+	// Get the query string
+	const TPtrC pSQL(GetQueryStringL(aMessage, aFilterType));
+	HBufC* buf = pSQL.AllocLC();
+	// Setup the contents
+	PrepareViewContentsL(pSQL);
+	// Tidy up
+	CleanupStack::Pop(buf);
+	delete iSql;
+	iSql = buf;
+	}
+
+/**
+Returns the number of entries in this view
+*/
+TInt CLogServViewBase::Count() const
+	{
+	return const_cast<CLogServViewBase*>(this)->RebuildViewContentsIfNecessary() ? 0 : iViewContents.Count(); 
+	}
+
+/**
+Get the log id at the specified index
+*/
+TLogId CLogServViewBase::At(TInt aIndex) const
+	{
+	return (TUint)aIndex < iViewContents.Count() ? iViewContents[aIndex] : KLogNullId;   
+	}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void CLogServViewBase::DestroyList(TAny *aPtr)
+	{
+	CLogFilterList* filter = reinterpret_cast<CLogFilterList*>(aPtr);
+	filter->ResetAndDestroy();
+	delete filter;
+	}
+
+void CLogServViewBase::InitializeColumnsL(RDbRowSet& aRowSet)
+    {
+    if(CLogServViewBase::iIdColNo == 0)
+        {
+        CDbColSet* colset = aRowSet.ColSetL();
+        CLogServViewBase::iIdColNo = colset->ColNo(KLogFieldIdString);
+        CLogServViewBase::iTypeColNo = colset->ColNo(KLogFieldEventTypeString);
+        for(TInt i=0;i<KLogFlagsCount;++i)
+            {
+            TDbColName colname;
+            colname.Format(KLogFieldEventFlagString, i + 1);
+            CLogServViewBase::iFlagColNo[i] = colset->ColNo(colname);
+            __ASSERT_DEBUG(CLogServViewBase::iFlagColNo[i] > 0, User::Invariant());
+            }
+        delete colset;
+        }
+    __ASSERT_DEBUG(CLogServViewBase::iIdColNo > 0 && CLogServViewBase::iTypeColNo > 0, User::Invariant());  
+    }
+
+/////////////////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void CLogServViewBase::ResetViewContentsL(RDbRowSet& aRowSet)
+	{
+	// Get the view contents
+	RArray<TLogId> viewContents(KLogViewContentsGranuality);
+	CleanupClosePushL(viewContents);
+	viewContents.ReserveL(aRowSet.CountL());
+	if(aRowSet.FirstL())
+		{
+		do
+			{
+			// Get the id at the current position
+			aRowSet.GetL();
+			const TLogId id = aRowSet.ColInt32(CLogServViewBase::iIdColNo);
+			TUint8 eventTypeIndex = aRowSet.ColUint8(CLogServViewBase::iTypeColNo);
+			if(IsAllowed(EReadOp, eventTypeIndex))
+			    {
+				TInt err = viewContents.Append(id);
+		        __ASSERT_ALWAYS(err == KErrNone, Panic(ELogArrayReserved));
+			    }
+			}
+		while(aRowSet.NextL());
+		}
+	// Tidy up - don't leave from below here
+    CleanupStack::Pop(&viewContents);
+	iViewContents.Close();
+	iViewContents = viewContents;
+    iViewContentsReady = ETrue;
+	}
+
+void CLogServViewBase::PrepareViewContentsL(const TDesC& aSQL)
+	{
+	// Generate the view
+    RLogDbView view;
+    view.PrepareLC(iDatabase.DTIDatabase(), aSQL);
+    InitializeColumnsL(view);
+	// Reset the view
+	ResetViewContentsL(view);
+	CleanupStack::PopAndDestroy(&view);
+	}
+
+void CLogServViewBase::RebuildViewL()
+	{
+	if	(iSql && iSql->Length())
+		PrepareViewContentsL(*iSql);
+	}
+
+//
+//  The last change notification indication was not handled correctly.
+//  Attempt to re-initialise the view
+//
+TInt CLogServViewBase::RebuildViewContentsIfNecessary()
+	{
+	TInt error = KErrNone;
+	//
+	if	(iRebuildViewContents && iSql && iSql->Length())
+		{
+		TRAP(error, 
+
+			RebuildViewL();
+
+			// The view is okay now
+			iRebuildViewContents = EFalse;
+			);
+		}
+	return error;
+	}
+
+void CLogServViewBase::DoHandleChangeEventsL(const CLogChangeDefinition& aChanges)
+	{
+    if(!ViewIsReady())
+        {
+        return;
+        }
+	
+	// Generate the view
+    RLogDbView view;
+    view.PrepareLC(iDatabase.DTIDatabase(), *iSql);
+    InitializeColumnsL(view);
+	_LIT(KLogIdQuery, "Id = %d");
+	TBuf<15> find;
+	TInt changeIndex;
+
+	// Prepare for a change transaction
+	iChangeManager->ChangeTransactionPrepare();
+
+	const TInt count = aChanges.Count();
+	for(TInt i=0; i<count; i++)
+		{
+		// Fetch the change details
+		TLogId logId = KLogNullId;
+		TLogDatabaseChangeType type = aChanges.At(i, logId);
+
+		// Mark the insertion position as 'not found'
+		changeIndex = KErrNotFound;
+
+		// Format the find query
+		find.Format(KLogIdQuery, logId);
+
+		// Handle the various change descriptions
+		switch(type)
+			{
+		case ELogChangeTypeEventAdded:
+			{
+			__ASSERT_DEBUG(iViewContents.Find(logId) == KErrNotFound, Panic(ELogEventAlreadyInView));
+
+			// See if the event is in the view
+			if(view.FirstL())
+				{
+				const TDbQuery dbQuery(find);
+				changeIndex = view.FindL(RDbRowSet::EForwards, dbQuery);
+				if	(changeIndex >= 0)
+					{
+					view.GetL();
+					TUint8 eventTypeIndex = view.ColUint8(CLogServViewBase::iTypeColNo);
+					if (IsAllowed(EReadOp, eventTypeIndex))
+						User::LeaveIfError(iViewContents.Insert(logId, changeIndex));
+					
+					iChangeManager->ChangeTransactionSubmitL(logId, ELogChangeTypeEventAdded, changeIndex);
+					}
+				}
+			}
+			break;
+		case ELogChangeTypeEventChanged:
+		case ELogChangeTypeEventChangedHidden:
+			{
+			// See if the event is in the view
+			if	(view.FirstL() && (changeIndex = view.FindL(RDbRowSet::EForwards, TDbQuery(find))) >= KErrNone)
+				{
+				// If the event was already in the view then it has changed otherwise it's been added
+				const TInt findIndex = iViewContents.Find(logId);
+				if	(findIndex >= KErrNone)
+					{
+					// If the item that changed also caused its position within the view to be altered
+					// then we need to simulate a delete, followed by an addition
+					if	(findIndex != changeIndex)
+						{
+						iChangeManager->ChangeTransactionSubmitL(logId, ELogChangeTypeEventDeleted, findIndex);
+						iChangeManager->ChangeTransactionSubmitL(logId, ELogChangeTypeEventAdded, changeIndex);
+						}
+					// Only tell the view if the CLogEvent has changed
+					else if (type != ELogChangeTypeEventChangedHidden)
+						{
+						iChangeManager->ChangeTransactionSubmitL(logId, ELogChangeTypeEventChanged, changeIndex);
+						}
+					}
+				else
+					{
+					User::LeaveIfError(iViewContents.Insert(logId, changeIndex));
+					iChangeManager->ChangeTransactionSubmitL(logId, ELogChangeTypeEventAdded, changeIndex);
+
+					// Update the type to indicate that this change action
+					// really resulted in an addition
+					type = ELogChangeTypeEventAdded;
+					}
+				}
+			else
+				{
+				changeIndex = iViewContents.Find(logId);
+
+				// If it used to be in the view then it's been removed because it's 
+				// not in there anymore
+				if	(changeIndex >= KErrNone)
+					{
+					iViewContents.Remove(changeIndex);
+					iChangeManager->ChangeTransactionSubmitL(logId, ELogChangeTypeEventDeleted, changeIndex);
+
+					// Update the type to indicate that this change action
+					// really resulted in a deletion
+					type = ELogChangeTypeEventDeleted;
+					}
+				}
+			}
+			break;
+		case ELogChangeTypeEventDeleted:
+			{
+			changeIndex = iViewContents.Find(logId);
+
+			// If it used to be in the view then tell it about the deletion
+			if	(changeIndex >= KErrNone)
+				{
+				iViewContents.Remove(changeIndex);
+				iChangeManager->ChangeTransactionSubmitL(logId, ELogChangeTypeEventDeleted, changeIndex);
+				}
+			}
+			break;
+		case ELogChangeTypeLogCleared:
+			{
+			RebuildViewL();
+			iChangeManager->ChangeTransactionSubmitL(logId, ELogChangeTypeLogCleared, 0);
+			}
+			break;
+		default:
+			__ASSERT_DEBUG(EFalse, Panic(ELogUnrecognizedChangeType));
+			break;
+			}
+		}
+
+	CleanupStack::PopAndDestroy(&view);
+
+	// Commit the transaction. Will notify client if necessary
+	iChangeManager->ChangeTransactionCommitL();
+	}
+
+TBool CLogServViewBase::IsAllowed(TEventOp aEventOp, TUint8 aEventTypeIndex)
+	{
+	TBool result = ETrue;
+
+	const TLogServCacheTypeEntry& entry = iDatabase.DTICacheTypes().FindById(aEventTypeIndex);
+	TUid eventTypeUid = entry.iEventType->Uid();
+	TInt count = iStandardTypeSecurityCache.Count();
+		
+	for(TInt i=0;i<count;++i)
+		{
+		if (eventTypeUid == iStandardTypeSecurityCache[i].eventType)
+			{
+			result = (aEventOp == EWriteOp) ? iStandardTypeSecurityCache[i].writeAccess : iStandardTypeSecurityCache[i].readAccess;
+			break;
+			}
+		}
+	
+	return result;
+	}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// -----> CLogServViewLockObserver (source)
+/////////////////////////////////////////////////////////////////////////////////////////
+
+CLogServViewLockObserver::CLogServViewLockObserver(MLogServBackupInterface& aBackupInterface)
+:	iBackupInterface(aBackupInterface), iLockEvents(KLogViewLockStatusEventGranularity)
+	{
+	}
+
+CLogServViewLockObserver::~CLogServViewLockObserver()
+	{
+	iBackupInterface.BIObserverRemove(*this);
+	iLockEvents.Close();
+	}
+
+void CLogServViewLockObserver::ConstructL()
+	{
+	// Register for backup events
+	iBackupInterface.BIObserverAddL(*this, MLogServBackupInterface::EObjectViewLock);
+	}
+
+CLogServViewLockObserver* CLogServViewLockObserver::NewL(MLogServBackupInterface& aBackupInterface)
+	{
+	CLogServViewLockObserver* self = new(ELeave) CLogServViewLockObserver(aBackupInterface);
+	CleanupStack::PushL(self);
+	self->ConstructL();
+	CleanupStack::Pop(self);
+	return self;
+	}
+
+void CLogServViewLockObserver::BOHandleEventL(TLogServBackupEvent aEvent)
+	{
+	// Map event
+	TLogViewLockStatus status = ELogViewWindowOpen;
+	if	(aEvent == MLogServBackupObserver::EBackupStarting)
+		status = ELogViewWindowLocked;
+
+	// Cache or complete immediately
+	if	(!HaveLockStatusChangePointer())
+		User::LeaveIfError(iLockEvents.Append(status));
+	else
+		CompleteLockStatusChangeMessage(status);
+	}
+
+/**
+The client has requested lock status change notifications
+*/
+void CLogServViewLockObserver::RequestLockStatusChanges(const RMessage2& aMessage)
+	{
+	if	(!HaveLockStatusChangePointer())
+		{
+		iLockStatusChangeMessage = aMessage;
+		
+		// Already have one cached event
+		if	(iLockEvents.Count())
+			{
+			CompleteLockStatusChangeMessage(iLockEvents[0]);
+			iLockEvents.Remove(0);
+			}
+		}
+	else
+		PanicClient(aMessage, ELogViewLockStatusChangeRequestAlreadyIssued);
+	}
+
+/**
+The client has cancelled a previous change notification request
+*/
+void CLogServViewLockObserver::RequestLockStatusChangesCancel()
+	{
+	if	(HaveLockStatusChangePointer())
+		CompleteLockStatusChangeMessage(KErrCancel);
+	}
+
+void  CLogServViewLockObserver::CompleteLockStatusChangeMessage(TInt aCompletionCode)
+	{
+	__ASSERT_ALWAYS(HaveLockStatusChangePointer(), Panic(ELogViewNoLockStatusChangeMessage));
+	iLockStatusChangeMessage.Complete(aCompletionCode);
+	}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// -----> CLogServViewEvent (source)
+/////////////////////////////////////////////////////////////////////////////////////////
+CLogServViewEvent::CLogServViewEvent(MLogServDatabaseTransactionInterface& aDatabase, MLogServBackupInterface& aBackupInterface, CLogPackage& aPackage, TLogViewId aViewId, const RMessage2& aMessage)
+:	CLogServViewBase(aDatabase, aBackupInterface, aPackage, ELogViewTypeEvent, aViewId, aMessage)
+	{
+	}
+
+CLogServViewEvent* CLogServViewEvent::NewL(MLogServDatabaseTransactionInterface& aDatabase, MLogServBackupInterface& aBackupInterface, CLogPackage& aPackage, TLogViewId aViewId, const RMessage2& aMessage)
+	{
+	CLogServViewEvent* self = new(ELeave) CLogServViewEvent(aDatabase, aBackupInterface, aPackage, aViewId, aMessage);
+	CleanupStack::PushL(self);
+	self->ConstructL();
+	CleanupStack::Pop(self);
+	return self;
+	}
+
+/**
+Setup the view
+Fetch the client-side SQL query string which this view uses to obtain a data-set.
+*/
+TPtrC CLogServViewEvent::GetQueryStringL(const RMessage2& aMessage, TLogFilterConstructionType aFilterType) 
+	{
+	// Read stuff from the client
+	iPackage.ResizeL(aMessage.GetDesLengthL(2));
+	aMessage.ReadL(2, iPackage.Ptr());
+
+	// Decode the parameters we've read from the client
+	CLogFilterList* filter = new(ELeave)CLogFilterList;
+	CleanupStack::PushL(TCleanupItem(DestroyList, filter));
+	iPackage.GetLogFilterListL(*filter);
+	RLogDynBuf expr;
+    TLogFilterExprBuilder exprBuilder(iDatabase);
+    exprBuilder.BuildExprLC(expr, *filter, KLogWhere, aFilterType);
+    // Generate the query string that will be used
+    TheSql.Format(KLogSqlEventViewString, &KLogViewSelectColList, &expr.DesC());
+    CleanupStack::PopAndDestroy(2, filter);
+	return TheSql;
+	}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// -----> CLogServViewRecent (source)
+/////////////////////////////////////////////////////////////////////////////////////////
+CLogServViewRecent::CLogServViewRecent(MLogServDatabaseTransactionInterface& aDatabase, MLogServBackupInterface& aBackupInterface, CLogPackage& aPackage, TLogViewId aViewId, const RMessage2& aMessage)
+:	CLogServViewBase(aDatabase, aBackupInterface, aPackage, ELogViewTypeRecent, aViewId, aMessage)
+	{
+	}
+
+CLogServViewRecent* CLogServViewRecent::NewL(MLogServDatabaseTransactionInterface& aDatabase, MLogServBackupInterface& aBackupInterface, CLogPackage& aPackage, TLogViewId aViewId, const RMessage2& aMessage)
+	{
+	CLogServViewRecent* self = new(ELeave) CLogServViewRecent(aDatabase, aBackupInterface, aPackage, aViewId, aMessage);
+	CleanupStack::PushL(self);
+	self->ConstructL();
+	CleanupStack::Pop(self);
+	return self;
+	}
+
+/**
+Setup the view
+Fetch the client-side SQL query string which this view uses to obtain a data-set.
+*/
+TPtrC CLogServViewRecent::GetQueryStringL(const RMessage2& aMessage, TLogFilterConstructionType aFilterType)
+	{
+	// Read stuff from the client
+	iPackage.ResizeL(aMessage.GetDesLengthL(2));
+	aMessage.ReadL(2, iPackage.Ptr());
+
+	// Decode the parameters we've read from the client
+	CLogFilterList* filter = new(ELeave)CLogFilterList;
+	CleanupStack::PushL(TCleanupItem(DestroyList, filter));
+	iPackage.GetLogFilterListL(*filter);
+	RLogDynBuf expr;
+	TLogFilterExprBuilder exprBuilder(iDatabase);
+	exprBuilder.BuildExprLC(expr, *filter, KLogAnd, aFilterType);
+    iRecentList = static_cast<TLogRecentList>(aMessage.Int3());
+    // Generate the view that will be used
+    if (iRecentList == KLogNullRecentList)
+        {
+        TheSql.Format(KLogSqlAllRecentViewString, &KLogViewSelectColList, &expr.DesC());
+        }
+    else
+        {
+        TheSql.Format(KLogSqlRecentViewString, &KLogViewSelectColList, iRecentList, &expr.DesC());
+        }
+    CleanupStack::PopAndDestroy(2, filter);
+	return TheSql;
+	}
+
+/**
+Remove an entry from the view.
+Note the client can call this even if the view isn't valid.
+*/
+void CLogServViewRecent::RemoveL(const RMessage2& aMessage)
+	{
+	if(! aMessage.HasCapability(ECapabilityWriteDeviceData))
+		User::Leave(KErrPermissionDenied);
+		
+	const TLogId id = static_cast<TLogId>(aMessage.Int2());
+    TBuf<20> num;
+    num.AppendNum(id);
+
+	iDatabase.DTIBeginWithRollBackProtectionLC();
+	
+    TheSql.Copy(KLogSqlRemoveDuplicateEvents);
+    TheSql.Append(KIdEqStr);
+    TheSql.Append(num);
+    TheSql.Append(KLogOr);
+    TheSql.Append(KDuplicateEqStr);
+    TheSql.Append(num);
+    User::LeaveIfError(iDatabase.DTIExecuteSql(TheSql));
+
+	// This is a "hidden" change. It may affect the contents of a view, but the actual event hasn't changed
+	iDatabase.DTIChangeInterface().DCISubmitChangedEventContextL(ELogChangeTypeEventChangedHidden, id);
+	iDatabase.DTICommitAndCancelRollbackProtectionL();
+	}
+
+/**
+Clear all duplicate events associated with this recent list.
+*/
+void CLogServViewRecent::ClearDuplicatesL(const RMessage2& aMessage)
+	{
+	if(! aMessage.HasCapability(ECapabilityWriteDeviceData))
+		User::Leave(KErrPermissionDenied);
+	
+	if	(iRecentList > 0)
+		{
+		// Get list of duplicates
+		TheSql.Format(KLogSqlSelectAllDuplicatesString, iRecentList);
+        RLogDbView view;
+        view.PrepareLC(iDatabase.DTIDatabase(), TheSql);
+        InitializeColumns2L(view);
+		if(view.FirstL())
+			{
+            iDatabase.DTIBeginWithRollBackProtectionLC();
+			// Iterate through the events
+			do
+				{
+				// Get current event id
+				view.GetL();
+				const TLogId id = view.ColInt32(CLogServViewRecent::iIdColNo);
+				// Make the change
+				view.UpdateL();
+				view.SetColNullL(CLogServViewRecent::iRecentColNo);
+				view.SetColNullL(CLogServViewRecent::iDuplicateColNo);
+				view.PutL();
+				// This is a "hidden" change. It may affect the contents of a view, but the actual event hasn't changed
+				iDatabase.DTIChangeInterface().DCISubmitChangedEventContextL(ELogChangeTypeEventChangedHidden, id);
+				}
+			while(view.NextL());
+			iDatabase.DTICommitAndCancelRollbackProtectionL();
+			}
+		CleanupStack::PopAndDestroy(&view);
+		}
+	else
+		::PanicClientL(aMessage, ELogInvalidRecentView);
+	}
+
+void CLogServViewRecent::InitializeColumns2L(RDbRowSet& aRowSet)
+    {
+    if(CLogServViewRecent::iIdColNo == 0)
+        {
+        CDbColSet* colset = aRowSet.ColSetL();
+        CLogServViewRecent::iIdColNo = colset->ColNo(KLogFieldIdString);
+        CLogServViewRecent::iRecentColNo = colset->ColNo(KLogFieldEventRecentString);
+        CLogServViewRecent::iDuplicateColNo = colset->ColNo(KLogFieldEventDuplicateString);
+        delete colset;
+        }
+    __ASSERT_DEBUG(CLogServViewRecent::iIdColNo > 0 && 
+            CLogServViewRecent::iRecentColNo > 0 && 
+            CLogServViewRecent::iDuplicateColNo > 0, User::Invariant());  
+        
+    }
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// -----> CLogServViewDuplicate (source)
+/////////////////////////////////////////////////////////////////////////////////////////
+CLogServViewDuplicate::CLogServViewDuplicate(MLogServDatabaseTransactionInterface& aDatabase, MLogServBackupInterface& aBackupInterface, CLogPackage& aPackage, TLogViewId aViewId, const RMessage2& aMessage)
+:	CLogServViewBase(aDatabase, aBackupInterface, aPackage, ELogViewTypeDuplicate, aViewId, aMessage)
+//
+//	Duplicate view
+//
+	{
+	}
+
+CLogServViewDuplicate* CLogServViewDuplicate::NewL(MLogServDatabaseTransactionInterface& aDatabase, MLogServBackupInterface& aBackupInterface, CLogPackage& aPackage, TLogViewId aViewId, const RMessage2& aMessage)
+	{
+	CLogServViewDuplicate* self = new(ELeave) CLogServViewDuplicate(aDatabase, aBackupInterface, aPackage, aViewId, aMessage);
+	CleanupStack::PushL(self);
+	self->ConstructL();
+	CleanupStack::Pop(self);
+	return self;
+	}
+
+/**
+Setup the view
+Fetch the client-side SQL query string which this view uses to obtain a data-set.
+*/
+TPtrC CLogServViewDuplicate::GetQueryStringL(const RMessage2& aMessage, TLogFilterConstructionType aFilterType)
+	{
+	// Read stuff from the client
+	iPackage.ResizeL(aMessage.GetDesLengthL(2));
+	aMessage.ReadL(2, iPackage.Ptr());
+
+	// Decode the parameters we've read from the client
+	CLogFilterList* filter = new(ELeave)CLogFilterList;
+	CleanupStack::PushL(TCleanupItem(DestroyList, filter));
+	iPackage.GetLogFilterListL(*filter);
+	RLogDynBuf expr;
+	TLogFilterExprBuilder exprBuilder(iDatabase);
+	exprBuilder.BuildExprLC(expr, *filter, KLogAnd, aFilterType);
+	iSourceId = (TInt)aMessage.Ptr3();
+    // Generate the view that will be used
+    TheSql.Format(KLogSqlDuplicateViewString2, &KLogViewSelectColList, iSourceId, &expr.DesC());
+    CleanupStack::PopAndDestroy(2, filter);
+	return TheSql;
+	}
+
+/**
+Remove an entry from the view.
+*/
+void CLogServViewDuplicate::RemoveL(const RMessage2& aMessage)
+	{
+	if(! aMessage.HasCapability(ECapabilityWriteDeviceData))
+		User::Leave(KErrPermissionDenied);
+
+	const TLogId id = static_cast<TLogId>(aMessage.Int2());
+
+	iDatabase.DTIBeginWithRollBackProtectionLC();
+
+	// Do the actual work
+	TheSql.Format(KLogSqlRemoveDuplicateString, id, iSourceId);
+	const TInt error = iDatabase.DTIExecuteSql(TheSql);
+	User::LeaveIfError(error);
+	//
+	// This is a "hidden" change. It may affect the contents of a view, but the actual event hasn't changed
+	iDatabase.DTIChangeInterface().DCISubmitChangedEventContextL(ELogChangeTypeEventChangedHidden, id);
+	iDatabase.DTICommitAndCancelRollbackProtectionL();
+	}