// Copyright (c) 1998-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 <e32base.h>

#include "MSVIDS.H"

#include "MSVDELET.H"
#include "MSVSERV.H"
#include "MSVPANIC.H"
#include "MSVUTILS.H"
#include "msvindexadapter.h"
#include "msventryfreepool.h"

// static
CMsvDelete* CMsvDelete::NewL(CMsvServer& aServer)
//
//
//
	{
	CMsvDelete* self = new(ELeave) CMsvDelete(aServer);
	CleanupStack::PushL(self);
	self->ConstructL();
	CleanupStack::Pop();
	return self;
	}


CMsvDelete::CMsvDelete(CMsvServer& aServer)
: CActive(EPriorityStandard), iServer(aServer), iLockedIndex(-2)
//
//
//
	{
	}


void CMsvDelete::ConstructL()
//
//
//
	{
	iFileMan = CFileMan::NewL(iServer.FileSession());
	iDescendents = new(ELeave)CMsvEntrySelection;
	CActiveScheduler::Add(this);
	}


CMsvDelete::~CMsvDelete()
//
//
//
	{
	Cancel();
	delete iDescendents;
	delete iFileMan;
	iDeleteArray.Close();
	}

void CMsvDelete::StartL(TMsvId aId, CMsvEntrySelection& aDeletedEntries, CMsvEntrySelection& aMovedEntries, TRequestStatus& aObserverStatus, TBool aPCSyncOverride)
//
//
//
	{
	DoStartL(aId, aDeletedEntries, aMovedEntries, aPCSyncOverride);

	iStatus = KRequestPending;
	TRequestStatus* st=&iStatus;
	User::RequestComplete(st, KErrNone);
	SetActive();
	//
	iObserverStatus = &aObserverStatus;
	*iObserverStatus = KRequestPending;
	}

void CMsvDelete::StartL(TMsvId aId, CMsvEntrySelection& aDeletedEntries, CMsvEntrySelection& aMovedEntries, TBool aPCSyncOverride)
//
//
//
	{	
	DoStartL(aId, aDeletedEntries, aMovedEntries, aPCSyncOverride);

	// the state machine from RunL
	while (iState!=ECompleted)
		{
		switch (iState)
			{
			case ECheck:
				TRAP(iError, CheckEntriesL());
				if (iError)
					iState=ECompleted;
				break;
			case EFiles:
				TRAPD(leave, DeleteFilesL());
				if (leave)
					{
					iError=leave;
					iState=ECompleted;
					}
				break;
			case EIndex:
				DeleteAllIndexEntries();
				iState=ECompleted;
				break;
			case EIndexIndividually:
				DeleteIndividualIndexEntries();
				iState=ECompleted;
				break;
			default:
				__ASSERT_DEBUG(EFalse, PanicServer(EMsvDeleteBadState2));
			}
		}

	// completion code
	iDeletionIndex=0;

	User::LeaveIfError(iError);
	}

void CMsvDelete::DoStartL(TMsvId aId, CMsvEntrySelection& aDeletedEntries, CMsvEntrySelection& aMovedEntries, TBool aPCSyncOverride)
//
//
//
	{
	// Fail now if the index says it's not available
	User::LeaveIfError(iServer.IndexAdapter().ErrorState());
	iId = aId;
	iPCSyncOverride = aPCSyncOverride;

	// Let's not support deletion of entries from non-current drive.
#if (defined SYMBIAN_MSGS_ENHANCED_REMOVABLE_MEDIA_SUPPORT)
	if(GetDriveId(iId) != KCurrentDriveId)
		{
		User::Leave(KErrNotSupported);
		}
#endif

	// get the expanded selection
	iDescendents->Reset();
	iDescendents->AppendL(iId);
	User::LeaveIfError(iServer.IndexAdapter().ExpandSelectionRecursively(*iDescendents));
	// make sure all deleted entries can be stored
	iDeletedEntries = &aDeletedEntries;
	iDeletedEntries->SetReserveL(iDescendents->Count());
	iMovedEntries = &aMovedEntries;
	iMovedEntries->SetReserveL(iDescendents->Count());

	// check if the entry is a service then set up PC override
	TMsvEntry* entry;
	User::LeaveIfError(iServer.IndexAdapter().GetEntry(aId, entry));
	if (entry->iType==KUidMsvServiceEntry)
		iPCSyncOverride=ETrue;

	iState = ECheck;
	}

void CMsvDelete::DoCancel()
//
//
//
	{
	// need to remove the index entries for any already deleted
	if (iState==EFiles || iState==EIndex || iState==EIndexIndividually)
		iPCSynced ? DeleteIndividualIndexEntries() : DeleteAllIndexEntries();
	Complete(KErrCancel);
	}



void CMsvDelete::RunL()
//
//
//
	{
	TInt error = iStatus.Int();
	if (error==KErrNone)
		TRAP(error, DoRunL());
	if (error)
		Complete(error);
	}


void CMsvDelete::DoRunL()
//
//
//
	{
	switch (iState)
		{
		case EFiles:
			DeleteFilesL();
			break;
		case ECheck:
			CheckEntriesL();
			break;
		case EIndex:
			DeleteAllIndexEntries();
			Complete(iError);
			return;
		case EIndexIndividually:
			DeleteIndividualIndexEntries();
			Complete(iError);
			return;
		default:
			__ASSERT_DEBUG(EFalse, PanicServer(EMsvDeleteBadState));
		}
	iStatus = KRequestPending;
	TRequestStatus* st=&iStatus;
	User::RequestComplete(st, KErrNone);
	SetActive();
	}


void CMsvDelete::CheckEntriesL()
//
//
//
	{
	iLockedIndex = iDescendents->Count();
	while (iLockedIndex)
		{
		TMsvId id = iDescendents->At(iLockedIndex-1);

		// lock the entry
		User::LeaveIfError(iServer.IndexAdapter().IsEntryOrStoreLocked(id));
		iLockedIndex--;

		// check noone is reading the store (reading the store now doesn't
		// keep the file open, therefore we can't rely on checking the file to stop
		// deleting while reading
		TBool reading=EFalse;
		User::LeaveIfError(iServer.IndexAdapter().IsStoreReadingLocked(id,reading));
		if(reading) User::Leave(KErrInUse);


		// get the entry
		TMsvEntry entry;
		User::LeaveIfError(iServer.IndexAdapter().GetEntryNoCache(id, &entry));
		TMsvDelete deleteEntry(entry.Id(), entry.Owner(), entry.PcSyncCount(), entry.iType, entry.Complete());
		iDeleteArray.AppendL(deleteEntry);

		// check if any entries have been synced and not been overridden
		if (!iPCSyncOverride && entry.PcSyncCount())
			iPCSynced=ETrue;

		// cannot delete standard folders
		if (entry.StandardFolder())
			User::Leave(KErrAccessDenied);

		// check the store
		TFileName filename;
		iServer.GetEntryName(id, filename, EFalse);
		TBool open;
		TInt error = iServer.FileSession().IsFileOpen(filename, open);
		if (error!=KErrNone && error!=KErrNotFound && error!=KErrPathNotFound)
			User::Leave(error);
		if (error==KErrNone && open)
			User::Leave(KErrInUse);

		// check any files
		CDir* dir;
		error = iServer.GetFileDirectoryListing(id, filename, dir);
		if (error==KErrNone)
			{
			CleanupStack::PushL(dir);
			User::LeaveIfError(iServer.FileSession().SetSessionPath(filename));
			TInt fCount=dir->Count();
			if (fCount--)
				{
				TBool open;
				TInt error = iServer.FileSession().IsFileOpen((*dir)[fCount].iName, open);
				if (error!=KErrNone && error!=KErrNotFound && error!=KErrPathNotFound)
					User::Leave(error);
				if (error==KErrNone && open)
					User::Leave(KErrInUse);
				if ((*dir)[fCount].IsReadOnly())
					User::LeaveIfError(iServer.FileSession().SetAtt((*dir)[fCount].iName, 0, KEntryAttReadOnly));
				}
			CleanupStack::PopAndDestroy(); // dir
			}
		else if (error!=KErrPathNotFound)
			User::Leave(error);
		}
	iState = EFiles;
	}


void CMsvDelete::DeleteFilesL()
//
//
//
	{
	TFileName filename;
	TMsvId id = iDescendents->At(iDeletionIndex++);

	// delete the binary files
	CDir* dir=NULL;
	TBool partiallyDeleted=EFalse;
	TInt error = iServer.GetFileDirectoryListing(id, filename, dir);
	if (error==KErrNone)
		{
		User::LeaveIfError(iServer.FileSession().SetSessionPath(filename));
		// remove any files
		TInt fCount=dir->Count();
		while (fCount--)
			{
			error = iServer.FileSession().Delete((*dir)[fCount].iName);
			if (error==KErrNone)
				partiallyDeleted=ETrue;
			else if (error!=KErrNotFound && error!=KErrPathNotFound)
				goto failed;
			}

		// remove the directory
		error = iServer.FileSession().RmDir(filename);
		if (error==KErrNone)
			partiallyDeleted=ETrue;
		else if (error!=KErrNotFound && error!=KErrPathNotFound)
			goto failed;
		}
	else if (error!=KErrPathNotFound && error!=KErrNotFound)
		goto failed;

	// delete the store
	iServer.GetEntryName(id, filename, EFalse); // error ignore as entry exists
	error = iServer.FileSession().Delete(filename);
	// Try to delete the (single digit) parent folder if it is empty. E.g.: "...\00001001_S\9\"
	iServer.FileSession().RmDir(filename); // ignore error
	if (error==KErrNone)
		partiallyDeleted=ETrue;
	else if (error!=KErrNotFound && error!=KErrPathNotFound)
		goto failed;


	filename.Append(KMsvUtilsNewExtension);  // try and make sure that a temporary store file
	iServer.FileSession().Delete(filename);  // hasn't been left behind by CMsvCachedStore
	                                         // but ignore the error as we can't do anything about it

	// for the last entry, check if it a service and move to next state
	if (iDeletionIndex==iDescendents->Count())
		{
		// get the entry and check whether it is a service
		TBool serviceType =EFalse;
		TInt deleteArrayCount = iDeleteArray.Count();
		for(TInt i =0; i < deleteArrayCount; ++i)
			{
			if(iDeleteArray[i].iEntryId == id)
				{
				if(iDeleteArray[i].iType == KUidMsvServiceEntry)
					{
					serviceType = ETrue;
					}
				break;	
				}
			}	
		if(serviceType)
			{
#if (defined SYMBIAN_MSGS_ENHANCED_REMOVABLE_MEDIA_SUPPORT)
			iServer.Context().MessageFolder(GetDriveId(id), filename);
			MsvUtils::ConstructEntryName(UnmaskTMsvId(id), id, filename, MsvUtils::EPath);
#else
			filename = iServer.Context().MessageFolder();
			MsvUtils::ConstructEntryName(id, id, filename, MsvUtils::EPath);
#endif
			error = iFileMan->RmDir(filename);
			if (error && error!=KErrPathNotFound && error!=KErrNotFound)
				goto failed;
			}
		// we have completed all the deletion, move onto next state
		iState = iPCSynced ? EIndexIndividually : EIndex;
		}

	// deletion was successful
	iDeletedEntries->AppendL(id); // will not leave, space has been reserved
	iMovedEntries->AppendL(id);   // will not leave, space has been reserved
	delete dir;
	return;

failed:
	if (partiallyDeleted)
		{
		TMsvEntry* entry=NULL;
		TBool complete =EFalse;
		for(TInt i =0; i< iDeleteArray.Count(); ++i)
			{
			if(iDeleteArray[i].iEntryId == id)
				{
				if(iDeleteArray[i].iIsComplete)
					{
					iServer.IndexAdapter().GetEntry(id, entry);
					if(entry)
						{
						complete = ETrue;
						}
					}
				break;	
				}
			}
			
		if(complete)
			{
			// mark as imcomplete
			TMsvEntry icmpEntry=*entry;
			icmpEntry.SetComplete(EFalse);
			iServer.IndexAdapter().LockEntry(id);
			iServer.IndexAdapter().ChangeEntry(icmpEntry, KMsvServerId, EFalse); // ignore error
			iServer.IndexAdapter().ReleaseEntry(id);
			}
		}

	// we have failed, move onto next state
	iState = iPCSynced ? EIndexIndividually : EIndex;
	iError=error;
	delete dir;
	}



void CMsvDelete::DeleteAllIndexEntries()
//
//
//
	{
	iMovedEntries->Reset();

	if (iDeletedEntries->Count()==0)
		return;

	// delete the entries
	TInt error = KErrNone;
#if (defined SYMBIAN_MESSAGESTORE_HEADER_BODY_USING_SQLDB)
	TMsvEntry* entry = NULL;
	TInt deleteIndex = 0;
	while(deleteIndex < iDeletedEntries->Count())
		{
		iServer.IndexAdapter().GetEntry(iDeletedEntries->At(deleteIndex), entry);
		if(entry==NULL)			
			{
			iDeletedEntries->Delete(deleteIndex);
			continue;
			}
		
		// If there's a DB store, delete any header entries associated with the entry.
		TRAP(error, iServer.MessageDBAdapter().DeleteHeaderEntryL(entry->iMtm, entry->iId));
		if(KErrNotFound == error) // Ignore entry not found case
			error = KErrNone;
	
		if(error)
			iDeletedEntries->Delete(deleteIndex);
		else
			++deleteIndex;
		}
#endif
	if(!error) // Looks awkward inside the macro above..
		error = iServer.IndexAdapter().DeleteSelectionUsingTransaction(*iDeletedEntries);

	if (error && iError==KErrNone)
		iError=error;
	}


void CMsvDelete::DeleteIndividualIndexEntries()
//
//
//
	{
	if (iDeletedEntries->Count()==0)
		return;

	TInt moveIndex=0;
	TInt deleteIndex=0;
	iServer.IndexAdapter().BeginTransaction();
	
	while(deleteIndex < iDeletedEntries->Count())
		{
		// get the entry
		TMsvEntry* entry=NULL;
 	    iServer.IndexAdapter().GetEntry(iDeletedEntries->At(deleteIndex), entry);
		if(entry==NULL)			
			{
			iDeletedEntries->Delete(deleteIndex);
			iMovedEntries->Delete(moveIndex);
			continue;
			}

		TInt error = KErrNone;
		if(!entry->Owner())
			{
			// either delete the entry, or move it to the deleted folder
			if(entry->PcSyncCount()==0)
				{ // Deleting the entry..
#if (defined SYMBIAN_MESSAGESTORE_HEADER_BODY_USING_SQLDB)
				// If there's a DB store, delete any header entries associated with the entry.
				TRAP(error, iServer.MessageDBAdapter().DeleteHeaderEntryL(entry->iMtm, entry->iId));
				if(KErrNotFound == error) // Ignore entry not found case
					error = KErrNone;
#endif
				if(!error) // Looks awkward inside the macro above..
					error = iServer.IndexAdapter().ForceDeleteEntry(entry->Id());
				
				if (error)
					iDeletedEntries->Delete(deleteIndex);
				else
					deleteIndex++;
				iMovedEntries->Delete(moveIndex);
				}
			else
				{ // Moving the entry..
				TMsvEntry deletedEntry(*entry);
				// this should be removed and CMsvDelete made a friend of TMsvEntry
				deletedEntry.iDetails.Set(TPtrC());
				deletedEntry.iDescription.Set(TPtrC());
				deletedEntry.SetDeleted(ETrue);
				deletedEntry.SetVisible(EFalse);
				deletedEntry.SetParent(KMsvDeletedEntryFolderEntryId);

				TMsvId backUpEntryId = entry->Id();
				iServer.IndexAdapter().LockEntry(backUpEntryId);
				error = iServer.IndexAdapter().ChangeEntry(deletedEntry, KMsvServerId, EFalse);
				iServer.IndexAdapter().ReleaseEntry(backUpEntryId);
				if (error)
					iMovedEntries->Delete(moveIndex);
				else
					moveIndex++;
				iDeletedEntries->Delete(deleteIndex);
				}
			}
		else
			error = KErrAccessDenied;

		if (error && iError==KErrNone)
			iError=error;
		}
	
	iServer.IndexAdapter().CommitTransaction();
	}



void CMsvDelete::Complete(TInt aError)
//
//
//
	{
	User::RequestComplete(iObserverStatus, aError);
	iState=ECompleted;
	iDeletionIndex=0;
	}
