/*
* Copyright (c) 2003-2009 Nokia Corporation and/or its subsidiary(-ies).
* All rights reserved.
* This component and the accompanying materials are made available
* under the terms of the License "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 <e32debug.h>
#include <ecom/ecom.h>

#include "resolver.h"
#include <caf/agentfactory.h>
#include <caf/agentinterface.h>
#include "agentinfo.h"
#include <caf/agent.h>
#include <caf/cafpanic.h>
#include <caf/caferr.h>
#include <caf/patchdata.h>


using namespace ContentAccess;

// Constants for the F32 agent
_LIT(KF32Agent,"F32 CA Agent");


_LIT(KParentDir, "..\\");
_LIT(KPrivateDir, "\\private\\");
const TInt KPrivateDirLength = 9;   // "\\private\\"
const TInt KPrivateDirAndDriveLength = 11;   // "x:\\private\\"
const TInt KPrivateDirOffset = 2; // "x:\\"


EXPORT_C CAgentResolver* CAgentResolver::NewLC(const TBool aDynamicAgentUpdate)
	{
	CAgentResolver* self = new (ELeave) CAgentResolver(aDynamicAgentUpdate);
	CleanupStack::PushL(self);
	self->ConstructL();
	return self;
	}

EXPORT_C CAgentResolver* CAgentResolver::NewL(const TBool aDynamicAgentUpdate)
	{
	CAgentResolver* self = CAgentResolver::NewLC(aDynamicAgentUpdate);
	CleanupStack::Pop(self);
	return self;
	}

CAgentResolver::CAgentResolver(const TBool aDynamicAgentUpdate) : CActive(EPriorityStandard), iDynamicAgentUpdate(aDynamicAgentUpdate)
	{
	}

CAgentResolver::~CAgentResolver()
	{
	// remove ourselves from the ActiveScheduler
	if(IsAdded())
		{
		Deque();
		}
	
	// Unload all the agents
	DestroyListOfAgents();
	
	// Close our ECOM session
	if(iEcomSession)
		{
		if(iDynamicAgentUpdate)
			{
			iEcomSession->CancelNotifyOnChange(iStatus);
			}
		iEcomSession->Close();
		REComSession::FinalClose();
		}

	iSupplierMimeTypes.Close();
	iConsumerMimeTypes.Close();
	iAgentInfos.Close();
	}

void CAgentResolver::ConstructL()
	{
	if(iDynamicAgentUpdate)
		{
		// Add ourselves to the current active scheduler so we can get dynamic 
		// updates when agents are removed or new agents are added
		CActiveScheduler::Add(this);
		}

	iEcomSession = &REComSession::OpenL();
	
	// find all the agents
	BuildListOfAgentsL();

	if(iDynamicAgentUpdate)
		{
		// register for ECOM update notifications in case a new agent appears
		SetActive();
		iEcomSession->NotifyOnChange(iStatus);
		}
	}

void CAgentResolver::BuildListOfAgentsL()
	{
	TInt err = KErrNone;

	// Get all plugins which implement the agent interface
	RImplInfoPtrArray implArray;
	CleanupStack::PushL(TCleanupItem(CleanImplArray, &implArray));
	REComSession::ListImplementationsL(KCAAgentInterfaceUid, implArray);

	for (TInt i = 0; i < implArray.Count(); ++i)
		{
#ifdef __EPOC32__
		// On hardware - to load agents from sources other than ROM the patch 
		// data KCafLoadPostProductionAgents must be set to True (non-zero).
		// Default SymbianOS behavior is to only load agents from ROM
		if ((KCafLoadPostProductionAgents == 0) &&
            !implArray[i]->RomBased())
			{
			// If the agent is not in ROM, don't load it because it might
			// be a security risk.
			continue;
			}
#endif

		// Construct all the agent infos from these implementations
		TRAP(err, AddAgentL(*implArray[i]));

		// If we ran out of memory proagate the leave to the caller
		// otherwise don't let a dodgy agent affect the construction of the other
		// agents
		if(err == KErrNoMemory)
			{
			User::Leave(KErrNoMemory);
			}
		}
	CleanupStack::PopAndDestroy(&implArray);  


	if (!iDefaultAgent)
		{
		// If we didn't find a default agent, we have a big problem so panic
		User::Panic(KCafPanicString, ECafPanicNoF32Agent);
		}
	}

void CAgentResolver::AddAgentL(const CImplementationInformation& aImplInfo)
	{
	// Create a CAgentInfo instance
	CAgentInfo* agentInfo = CAgentInfo::NewLC(aImplInfo);


	if(IsF32Agent(*agentInfo))
		{
		// It's the F32 Agent
		if(iDefaultAgent)
			{
			// If we already have a default agent something is seriously wrong
			User::Panic(KCafPanicString, ECafPanicDuplicateF32Agent);
			}

		// Note that the default agent is NOT stored in the agents list, it is a special case
		iDefaultAgent = agentInfo;
		CleanupStack::Pop(agentInfo);
		}
	else
		{
		// All other agents go in the agent list
		User::LeaveIfError(iAgentInfos.Append(agentInfo));
		CleanupStack::Pop(agentInfo);

		TInt mimeIndex=0;

		// Update our list of all supplier mime types supported by CAF
		for(mimeIndex=0;mimeIndex < agentInfo->SupplierMimeTypes().Count(); mimeIndex++) 
			{
			User::LeaveIfError(iSupplierMimeTypes.Append(*agentInfo->SupplierMimeTypes()[mimeIndex]));
			}

		// Update our list of all consumer mime types supported by CAF
		for(mimeIndex=0;mimeIndex < agentInfo->ConsumerMimeTypes().Count(); mimeIndex++) 
			{
			User::LeaveIfError(iConsumerMimeTypes.Append(*agentInfo->ConsumerMimeTypes()[mimeIndex]));
			}
		}
	}

void CAgentResolver::DestroyListOfAgents()
	{
	iSupplierMimeTypes.Reset();
	iConsumerMimeTypes.Reset();

	// cant forget to delete the default agent
	delete iDefaultAgent;
	iDefaultAgent = NULL;

	// Free memory assocated with the iAgentInfos array 
	iAgentInfos.ResetAndDestroy();
	}

void CAgentResolver::DoCancel()
	{
	// Abort any update notification 
	iEcomSession->CancelNotifyOnChange(iStatus);
	}

void CAgentResolver::RunL()
	{
	// Called by the ECOM framework if a new agent appears

	// remove the existing list of agents and build a new one
	DestroyListOfAgents();
	BuildListOfAgentsL();

	// request notification of any further changes
	iEcomSession->NotifyOnChange(iStatus);
	SetActive();
	}	

TBool CAgentResolver::IsF32Agent(CAgentInfo& aAgentInfo)
	{
	// Check if the agent has no consumer or supplier mime types
	// and that it has the correct name and Uid
	if (aAgentInfo.Agent().ImplementationUid() == KF32AgentImplUid
		&& aAgentInfo.Agent().Name().Compare(KF32Agent()) == 0 
		&& aAgentInfo.ConsumerMimeTypes().Count() == 0 
		&& aAgentInfo.SupplierMimeTypes().Count() == 0)
		{
		return ETrue;
		}
	else
		{
		return EFalse;
		}
	}

CAgentInfo& CAgentResolver::ResolveSupplierMimeL(const TDesC8& aMimeType) const
	{
	// Go through all the agents and return the one which supports the
	// required supplier mime type
	CAgentInfo* retVal=NULL;

	for (TInt i = 0; i < iAgentInfos.Count(); ++i)
		{
		if (iAgentInfos[i]->IsSupportedSupplier(aMimeType))
			{
			retVal = iAgentInfos[i];
			break;
			}
		}
	
	if (!retVal)
		{
		User::Leave(KErrCANoAgent);
		}
	return *retVal;
	}

CAgentInfo& CAgentResolver::ResolveConsumerMime(const TDesC8& aMimeType) const
	{
	// By default, set the return value to be the default agent. If we find
	// anything better, then we change it
	CAgentInfo* retVal = iDefaultAgent;

	for (TInt i = 0; i < iAgentInfos.Count(); ++i)
		{
		if (iAgentInfos[i]->IsSupportedConsumer(aMimeType))
			{
			retVal = iAgentInfos[i];
			break;
			}
		}

	ASSERT(retVal);
	return *retVal;
	}

CAgentInfo& CAgentResolver::ResolveFileL(const TDesC& aURI, TDes& aActualUri, TContentShareMode aShareMode) const
	{
	// Go through all the agents and return the one which supports the file at the given URI	
	TBool thePrivateDir = EFalse;	
	TUid agentUid = ResolveDirectory(aURI, aActualUri, thePrivateDir);
	
	if(agentUid != iDefaultAgent->Agent().ImplementationUid())
		{
		// this file must be living in a private server directory
		// return the agent who owns the directory
		return AgentInfoL(agentUid);
		}
	else
		{
		TInt agentsCount(iAgentInfos.Count());
		CAgentManager* agentManager = NULL;
		for (TInt i = 0; i < agentsCount; ++i)
			{
			TRAPD(result, agentManager = &iAgentInfos[i]->AgentManagerL());
			if(result != KErrNone)
				{
				if(KErrNoMemory == result)
					{
					User::Leave(result);
					}
				else
					{
					continue;	
					}
				}
			if (agentManager->IsRecognizedL(aURI, aShareMode))
				{
				return *iAgentInfos[i];
				}
			}
		}
	return *iDefaultAgent;
	}

CAgentInfo& CAgentResolver::ResolveFileL(RFile& aFile) const
	{
	// Go through all the agents and return the one which supports the file

	TInt agentsCount(iAgentInfos.Count());
	CAgentManager* agentManager = NULL;
	for (TInt i = 0; i < agentsCount; ++i)
		{
		TRAPD(result, agentManager = &iAgentInfos[i]->AgentManagerL());
		if(result != KErrNone)
			{
			if(KErrNoMemory == result)
				{
				User::Leave(result);
				}
			else
				{
				continue;	
				}
			}
		if (agentManager->IsRecognizedL(aFile))
			{
			return *iAgentInfos[i];
			}
		}
	return *iDefaultAgent;
	}

TUid CAgentResolver::ResolveDirectory(const TDesC& aPath, TDes& aActualPath, TBool& aThePrivateDir) const
	{
	TInt i = 0;
	TInt pathLength = 0;
	TBuf <KPrivateDirAndDriveLength> pathLowerCase;
	
	// Assume it's a publicly accessable path
	aThePrivateDir = EFalse;
	
	// Find the length of the path and private directory
	pathLength = aPath.Length();

	// Check that the path is long enough to be within a private directory
	// and does not include "..\\".The  "..\\" sequence could be a security risk
	if(aPath.Find(KParentDir()) == KErrNotFound && pathLength >= KPrivateDirAndDriveLength)
		{
		// Create a lower case copy of the left hand side of the path
		TPtrC lowerCasePtr = aPath.Mid(KPrivateDirOffset, KPrivateDirLength);
		pathLowerCase.Copy(lowerCasePtr);
		pathLowerCase.LowerCase();
	
		// Compare the first directory in the path to \\private\\ 
		if(KPrivateDir() == pathLowerCase)
			{
			// It is a private directory of some sort
			if(pathLength > KPrivateDirAndDriveLength)
				{
				// It must be a server private directory data cage
				TPtrC serverDirectoryPath = aPath.Right(pathLength - KPrivateDirAndDriveLength);
				for(i = 0; i < AgentInfoCount(); i++)
					{
					// See if the part after \\private\\ matches the agent name
					TPtrC privateDirectoryName = AgentInfo(i).PrivateDirectoryName();
					TPtrC agentName = AgentInfo(i).Agent().Name();
					if(privateDirectoryName.Length() && agentName.Length() && agentName == serverDirectoryPath.Left(agentName.Length()))
						{
						// It must be this agent's private directory
						// Convert \\private\\agentName\\... to \\private\\SID\\...
						aActualPath.Copy(aPath.Left(KPrivateDirAndDriveLength));
						aActualPath.Append(privateDirectoryName);
						aActualPath.Append(aPath.Right(pathLength - KPrivateDirAndDriveLength - agentName.Length()));
						return AgentInfo(i).Agent().ImplementationUid();
						}
					}
				}
			else
				{
				// It's just the c:\\private\\ directory
				// Use the default agent, any calls will just fail
				aThePrivateDir = ETrue;
				}
			}
		}
	
	// Not an agent private directory so just return the default agent
	aActualPath.Copy(aPath);
	return iDefaultAgent->Agent().ImplementationUid();
	}

HBufC* CAgentResolver::ConvertAgentFileNameL(const TDesC& aFileName) const
	{
	TInt i = 0;
	TInt fileNameLength = 0;
	TBuf <KPrivateDirAndDriveLength> pathLowerCase;
	
	fileNameLength = aFileName.Length();
	
	// If the path is shorter than the x:\\private\\ it must be a F32 file
	if(fileNameLength  > KPrivateDirAndDriveLength)
		{
		// Create a lower case copy of the left hand side of the path
		TPtrC lowerCasePtr = aFileName.Mid(KPrivateDirOffset, KPrivateDirLength);
		pathLowerCase.Copy(lowerCasePtr);
		pathLowerCase.LowerCase();
	
		// Compare the first directory in the path to \\private\\ 
		if(KPrivateDir() == pathLowerCase)
			{
			// It is a private directory of some sort
			if(fileNameLength > KPrivateDirAndDriveLength)
				{
				// It must be a server private directory data cage
				TPtrC serverDirectoryPath = aFileName.Right(fileNameLength - KPrivateDirAndDriveLength);
				for(i = 0; i < AgentInfoCount(); i++)
					{
					// See if the part after \\private\\ matches the agent name
					TPtrC privateDirectoryName = AgentInfo(i).PrivateDirectoryName();
					TPtrC agentName = AgentInfo(i).Agent().Name();
					if(privateDirectoryName.Length() && agentName.Length() && privateDirectoryName == serverDirectoryPath.Left(privateDirectoryName.Length()))
						{
						// It is this agent's private directory
						// Convert \\private\\SID\\... \\private\\agentName\\... 
						HBufC* buffer = HBufC::NewL(fileNameLength - privateDirectoryName.Length() + agentName.Length());
						TPtr ptr = buffer->Des();
						ptr.Copy(aFileName.Left(KPrivateDirAndDriveLength));
						ptr.Append(agentName);
						ptr.Append(aFileName.Right(fileNameLength - KPrivateDirAndDriveLength - privateDirectoryName.Length()));
						return buffer;
						}
					}
				}
			}
		}
	return aFileName.AllocL();
	}

EXPORT_C TBool CAgentResolver::DoRecognizeL(const TDesC& aName, const TDesC8& aBuffer, TDes8& aFileMimeType, TDes8& aContentMimeType)
	{

	// Given the filename and buffer from apparc, ask the agents in turn if they recognize the file
	// Note this will not call the DefaultAgent (F32) because it won't be able to recognize anything

	TInt agentsCount(iAgentInfos.Count());
	CAgentManager* agentManager = NULL;
	for (TInt i = 0; i < agentsCount; ++i)
		{
		TRAPD(result, agentManager = &iAgentInfos[i]->AgentManagerL());
		if(result != KErrNone)
			{
			if(KErrNoMemory == result)
				{
				User::Leave(result);
				}
			else
				{
				continue;	
				}
			}
		if (agentManager->RecognizeFileL(aName, aBuffer, aFileMimeType, aContentMimeType))
			{
			// force to lower case to ensure that chosen lower case scheme for mime types is maintained
			aFileMimeType.LowerCase();
			aContentMimeType.LowerCase();			
			return ETrue;
			}
		}
	return EFalse;
	}
	

void CAgentResolver::CleanImplArray(TAny* aArray)
	{
	static_cast<RImplInfoPtrArray*>(aArray)->ResetAndDestroy();
	}

EXPORT_C TInt CAgentResolver::PreferredBufferSize()
	{
	TInt size=0;

	if(iDefaultAgent != NULL)
		{
		size = iDefaultAgent->PreferredBufferSize();
		}

	// Find out the maximum buffer requested by any agent
	for (TInt i = 0; i < iAgentInfos.Count(); ++i)
			{
			if(iAgentInfos[i]->PreferredBufferSize() > size)
				{
				size = iAgentInfos[i]->PreferredBufferSize();
				}
			}
	return size;
	}


EXPORT_C const RArray<TPtrC8>& CAgentResolver::ConsumerMimeTypes() const
	{
	return iConsumerMimeTypes;
	}


EXPORT_C const RArray<TPtrC8>& CAgentResolver::SupplierMimeTypes() const
	{
	return iSupplierMimeTypes;
	}


CAgentInfo& CAgentResolver::AgentInfoL(const TDesC& aAgentName) const
	{
	TBool found = EFalse;
	TInt i = 0;
	for(i = 0; i < iAgentInfos.Count(); i++)
		{
		if(iAgentInfos[i]->Agent().Name() == aAgentName)
			{
			found = ETrue;
			break;
			}
		}
	
	if(!found)
		{
		// Can't find the agent so leave
		User::Leave(KErrNotFound);
		}
	
	return *iAgentInfos[i];
	}

CAgentInfo& CAgentResolver::AgentInfoL(const TUid& aUid) const
	{
	TInt i = 0;
	TBool found = EFalse;
	
	// See if it's the F32 agent
	if(aUid == DefaultAgentUid())
		{
		return *iDefaultAgent;
		}
	
	for(i = 0; i < iAgentInfos.Count(); i++)
		{
		if(iAgentInfos[i]->Agent().ImplementationUid() == aUid)
			{
			found = ETrue;
			break;			
			}
		}
	
	if(!found)
		{
		// couldn't find the agent so leave
		User::Leave(KErrNotFound);
		}
	
	return *iAgentInfos[i];
	}

CAgentInfo& CAgentResolver::AgentInfo(TInt aIndex) const
		{
		return *iAgentInfos[aIndex];		
		}

TInt CAgentResolver::AgentInfoCount() const
	{
	return iAgentInfos.Count();	
	}

TUid CAgentResolver::DefaultAgentUid() const
	{
	return iDefaultAgent->Agent().ImplementationUid();
	}
