// Copyright (c) 2006-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:
// USB Mass Storage Application
// 
//

/**
 @file
*/

#include "general.h"
#include "config.h"
#include "activecontrol.h"

#include <usbmsshared.h>
#include <massstorage.h>

#include "usbms.h"

extern CActiveControl* gActiveControl;
extern RTest test;
extern TBool gVerbose;
extern TBool gSkip;
extern TBool gTempTest;


LOCAL_D RFs fs;
LOCAL_D TBuf<0x40> mountList;

LOCAL_D TFixedArray<TBool, KMaxDrives>                   msfsMountedList;  ///< 'true' entry corresponds to the drive with mounted MSFS.FSY
LOCAL_D TFixedArray<CFileSystemDescriptor*, KMaxDrives>  unmountedFsList;  ///< every non-NULL entry corresponds to the unmounted original FS for the drive

LOCAL_D RUsbMassStorage UsbMs;

LOCAL_D CUsbWatch * usbWatch;

static const TUint KNumPropWatch = 4;
LOCAL_D CPropertyWatch * propWatch[KNumPropWatch];

_LIT(KMsFsy, "MSFS.FSY");
_LIT(KMsFs, "MassStorageFileSystem");
_LIT(KOk,"OK");
_LIT(KError,"Error");
_LIT(KBytesTransferredFmt, "%c:%d/%d \n");
_LIT(KErrFmt, "Error: %d\r");
_LIT(KConfigured,"Configured");
_LIT(KNotConfigured,"NOT Configured");


_LIT8(KDefPwd,"123");
TMediaPassword  password(KDefPwd);

//-----------------------------------------------------------------------------

CFileSystemDescriptor::~CFileSystemDescriptor()
    {
    iFsName.Close();
    iPrimaryExtName.Close();
    }

//-----------------------------------------------------------------------------
CFileSystemDescriptor* CFileSystemDescriptor::NewL(const TDesC& aFsName, const TDesC& aPrimaryExtName, TBool aDrvSynch)
    {
    CFileSystemDescriptor* pSelf = new (ELeave) CFileSystemDescriptor;

    CleanupStack::PushL(pSelf);
    
    pSelf->iFsName.CreateMaxL(aFsName.Length());
    pSelf->iFsName.Copy(aFsName);
    
    pSelf->iPrimaryExtName.CreateMaxL(aPrimaryExtName.Length());
    pSelf->iPrimaryExtName.Copy(aPrimaryExtName);

    pSelf->iDriveSynch = aDrvSynch;

    CleanupStack::Pop();

    return pSelf;
    }

//-----------------------------------------------------------------------------
/**
    Dismounts the originally mounted FS and optional primary extension from the drive and stores 
    this information in the FS descriptor

    @return on success returns a pointer to the instantinated FS descriptor
*/
LOCAL_C CFileSystemDescriptor* DoDismountOrginalFS(RFs& aFs, TInt aDrive)
    {
    TInt        nRes;
    TBuf<128>   fsName;
    TBuf<128>   primaryExtName;
    TBool       bDrvSync = EFalse;

    test.Printf(_L("DoDismountOrginalFS drv:%d\n"), aDrive);

    //-- 1. get file system name
    nRes = aFs.FileSystemName(fsName, aDrive);
    if(nRes != KErrNone)
        {//-- probably no file system installed at all
        return NULL;
        }

    //-- 2. find out if the drive sync/async
    TPckgBuf<TBool> drvSyncBuf;
    nRes = aFs.QueryVolumeInfoExt(aDrive, EIsDriveSync, drvSyncBuf);
    if(nRes == KErrNone)
        {
        bDrvSync = drvSyncBuf();
        }

    //-- 3. find out primary extension name if it is present; we will need to add it againt when mounting the FS
    //-- other extensions (non-primary) are not supported yet
    nRes = aFs.ExtensionName(primaryExtName, aDrive, 0);
    if(nRes != KErrNone)
        {   
        primaryExtName.SetLength(0);
        }

    //-- 3.1 check if the drive has non-primary extensions, fail in this case, because this FS can't be mounted back normally
    nRes = aFs.ExtensionName(primaryExtName, aDrive, 1);
    if(nRes == KErrNone)
        {   
        test.Printf(_L("DoDismountOrginalFS Non-primary extensions are not supported!\n"));
        return NULL;
        }

    test.Printf(_L("DoDismountOrginalFS FS:%S, Prim ext:%S, synch:%d\n"), &fsName, &primaryExtName, bDrvSync);

    //-- create FS descriptor and dismount the FS
    CFileSystemDescriptor* pFsDesc = NULL; 
    
    TRAP(nRes, pFsDesc = CFileSystemDescriptor::NewL(fsName, primaryExtName, bDrvSync));
    if(nRes != KErrNone)
        return NULL; //-- OOM ?

    nRes = aFs.DismountFileSystem(fsName, aDrive);
    if(nRes != KErrNone)
        {
        delete pFsDesc;
        pFsDesc = NULL;
        test.Printf(_L("DoDismountOrginalFS Dismounting Err:%d\n"), nRes);
        }
    
    return pFsDesc;
}

//-----------------------------------------------------------------------------
/**
    Tries to restore the original FS on the drive using the FS descriptor provided
    @return standard error code.
*/
LOCAL_C TInt DoRestoreFS(RFs& aFs, TInt aDrive, CFileSystemDescriptor* apFsDesc)
    {
    TInt nRes;

    test.Printf(_L("DoRestoreFS drv:%d\n"), aDrive);

    //-- 1. check that there is no FS installed
    TBuf<128>   fsName;
    nRes = aFs.FileSystemName(fsName, aDrive);
    if(nRes == KErrNone)
        {//-- there is a file system already installed
		test.Printf(_L("DoRestoreFS This drive already has FS intalled:%S \n"), &fsName);
        return KErrAlreadyExists;
        }
 
    TPtrC ptrN  (apFsDesc->FsName());
    TPtrC ptrExt(apFsDesc->PrimaryExtName());
    test.Printf(_L("DoRestoreFS Mounting FS:%S, Prim ext:%S, synch:%d\n"), &ptrN, &ptrExt, apFsDesc->DriveIsSynch());

    if(ptrExt.Length() >0)
        {//-- there is a primary extension to be mounted
        nRes = aFs.AddExtension(ptrExt);
        if(nRes != KErrNone && nRes != KErrAlreadyExists)
            {
            return nRes;
            }

        nRes = aFs.MountFileSystem(ptrN, ptrExt, aDrive, apFsDesc->DriveIsSynch());
        }
    else
        {
        nRes = aFs.MountFileSystem(ptrN, aDrive, apFsDesc->DriveIsSynch());
        }

    if(nRes != KErrNone)
        {
        test.Printf(_L("DoRestoreFS Mount failed! code:%d\n"),nRes);    
        }

    return nRes;
    }


//-----------------------------------------------------------------------------
/**
    Dismount the original FS from the drive and mount MsFS instead
*/
LOCAL_C void MountMsFs(TInt driveNumber)
	{
	test.Printf(_L("MountMsFs driveNumber=%d\n"), driveNumber); 

    //-- 1. try dismounting the original FS
    CFileSystemDescriptor* fsDesc = DoDismountOrginalFS(fs, driveNumber);
    unmountedFsList[driveNumber] = fsDesc;

    if(fsDesc)
        {
        TPtrC ptrN(fsDesc->FsName());
        test.Printf(_L("drv:%d FS:%S Dismounted OK\n"),driveNumber, &ptrN);
        }
    else
        {
        test.Printf(_L("drv:%d Dismount FS Failed!\n"),driveNumber);
        }

    //-- 2. try to mount the "MSFS"
    TInt error;
    error = fs.MountFileSystem(KMsFs, driveNumber);
	test.Printf(_L("MSFS Mount:   %S (%d)\n"), (error?&KError:&KOk), error);
	if (!error)
		msfsMountedList[driveNumber] = ETrue;

	}

//-----------------------------------------------------------------------------
/**
    Dismount MsFS and mount the original FS 
*/
LOCAL_C TInt RestoreMount(TInt driveNumber)
	{
	TInt err = KErrNone;

    //-- 1. try dismounting the "MSFS"
	if (msfsMountedList[driveNumber])
		{
		err = fs.DismountFileSystem(KMsFs, driveNumber);
		test.Printf(_L("MSFS Dismount:%S (%d)\n"), (err?&KError:&KOk), err);
		if (err)
			return err;

		msfsMountedList[driveNumber] = EFalse;
        }

    //-- 2. try to mount the original FS back
    CFileSystemDescriptor* fsDesc = unmountedFsList[driveNumber];
    if(fsDesc)
        {
        err = DoRestoreFS(fs, driveNumber, fsDesc);

        TPtrC ptrN(fsDesc->FsName());
        test.Printf(_L("%S Mount:    %S (%d)\n"), &ptrN, (err?&KError:&KOk), err);
        
        delete fsDesc;
        unmountedFsList[driveNumber] = NULL;
        }

	return err;
	}

//////////////////////////////////////////////////////////////////////////////
//
// CPropertyWatch
// An active object that tracks changes to the KUsbMsDriveState properties
//
//////////////////////////////////////////////////////////////////////////////

CPropertyWatch* CPropertyWatch::NewLC(TUsbMsDriveState_Subkey aSubkey, PropertyHandlers::THandler aHandler)
	{
	CPropertyWatch* me=new(ELeave) CPropertyWatch(aHandler);
	CleanupStack::PushL (me);
	me->ConstructL(aSubkey);
	CleanupStack::Pop();
	return me;
	}

CPropertyWatch::CPropertyWatch(PropertyHandlers::THandler aHandler)
	: CActive(0), iHandler(aHandler)
	{}

void CPropertyWatch::ConstructL(TUsbMsDriveState_Subkey aSubkey)
	{
	User::LeaveIfError(iProperty.Attach(KUsbMsDriveState_Category, aSubkey));
	CActiveScheduler::Add(this);
	// initial subscription and process current property value
	RunL();
	}

CPropertyWatch::~CPropertyWatch()
	{
	Cancel();
	iProperty.Close();
	}

void CPropertyWatch::DoCancel()
	{
	iProperty.Cancel();
	}

void CPropertyWatch::RunL()
	{
	// resubscribe before processing new value to prevent missing updates
	iProperty.Subscribe(iStatus);
	SetActive();

	iHandler(iProperty);
	}

//////////////////////////////////////////////////////////////////////////////
//
// CUsbWatch
//
//////////////////////////////////////////////////////////////////////////////

CUsbWatch* CUsbWatch::NewLC(RUsb& aUsb)
	{
	CUsbWatch* me=new(ELeave) CUsbWatch(aUsb);
	CleanupStack::PushL (me);
	me->ConstructL();
	CleanupStack::Pop();
	return me;
	}

CUsbWatch::CUsbWatch(RUsb& aUsb)
	: 
	CActive(0), 
	iUsb(aUsb),
	iUsbDeviceState(EUsbcDeviceStateUndefined),
	iWasConfigured(EFalse)
	{}

void CUsbWatch::ConstructL()
	{
	CActiveScheduler::Add(this);
	RunL();
	}

CUsbWatch::~CUsbWatch()
	{
	Cancel();
	iUsb.AlternateDeviceStatusNotifyCancel();
	}

void CUsbWatch::DoCancel()
	{
	iUsb.AlternateDeviceStatusNotifyCancel();
	}

static TBool IsDriveConnected(TInt driveStatusIndex)
	{
	TInt driveStatus = PropertyHandlers::allDrivesStatus[2*driveStatusIndex+1];
	return driveStatus >= EUsbMsDriveState_Connected ? ETrue : EFalse;
	}

static TChar DriveNumberToLetter(TInt driveNumber)
	{
	TChar driveLetter = '?';
	fs.DriveToChar(driveNumber, driveLetter);
	return driveLetter;
	}
	
static TBool IsDriveInMountList(TUint driveLetter)
	{
	TUint16 driveLetter16 = static_cast<TUint16>(driveLetter);
	return(!mountList.Length() || KErrNotFound != mountList.Find(&driveLetter16, 1));
	}

void CUsbWatch::RunL()
	{
	gActiveControl->SetMSFinished(EFalse);
	if (gVerbose)
		{
		switch (iUsbDeviceState)
			{
			case EUsbcDeviceStateUndefined : 					// 0
				test.Printf(_L(">> CUSBWatch:Undefined %S\n"), iWasConfigured ? &KConfigured : &KNotConfigured);
				break;
			
			case EUsbcDeviceStateAttached :						// 1
				test.Printf(_L(">> CUSBWatch:Attached %S\n"), iWasConfigured ? &KConfigured : &KNotConfigured);
				break;
			
			case EUsbcDeviceStatePowered :						// 2
				test.Printf(_L(">> CUSBWatch:Powered %S\n"), iWasConfigured ? &KConfigured : &KNotConfigured);
				break;
	
			case EUsbcDeviceStateDefault :						// 3
				test.Printf(_L(">> CUSBWatch:Default %S\n"), iWasConfigured ? &KConfigured : &KNotConfigured);
				break;
			
			case EUsbcDeviceStateAddress :						// 4
				test.Printf(_L(">> CUSBWatch:Address %S\n"), iWasConfigured ? &KConfigured : &KNotConfigured);
				break;
			
			case EUsbcDeviceStateConfigured :					// 5
				test.Printf(_L(">> CUSBWatch:Configured %S\n"), iWasConfigured ? &KConfigured : &KNotConfigured);
				break;
			
			case EUsbcDeviceStateSuspended : 					// 6
				test.Printf(_L(">> CUSBWatch:Suspended %S\n"), iWasConfigured ? &KConfigured : &KNotConfigured);
				break;
			
			default :
				test.Printf(_L(">> CUSBWatch:UNKNOWN %S\n"), iWasConfigured ? &KConfigured : &KNotConfigured);
				break;
		
			}
		}
	iUsb.AlternateDeviceStatusNotify(iStatus, iUsbDeviceState);
	SetActive();

	// If the cable is disconnected, unmount all the connected drives.
	if(iWasConfigured && iUsbDeviceState == EUsbcDeviceStateUndefined)
		{
		for(TInt i=0; i<PropertyHandlers::allDrivesStatus.Length()/2; i++)
			{
			if(IsDriveConnected(i))
				{
				RDebug::Print(_L("CUsbWatch calling RestoreMount"));
				RestoreMount(PropertyHandlers::allDrivesStatus[2*i]);
				}
			}

		iWasConfigured = EFalse;
		}

	// If cable is connected, mount all drives in the auto-mount list.
	// This is done for performance, since if this is not done here,
	// mounting will happen later after each drive enters the 
	// Connecting state.
	if(iUsbDeviceState == EUsbcDeviceStateConfigured)
		{
		for(TInt i=0; i<PropertyHandlers::allDrivesStatus.Length()/2; i++)
			{
			TInt driveNumber = PropertyHandlers::allDrivesStatus[2*i];
			if(!IsDriveConnected(i) && IsDriveInMountList(DriveNumberToLetter(driveNumber)))
				{
				RDebug::Print(_L("CUsbWatch calling MountMsFs"));
				MountMsFs(driveNumber);
				}
			}

		iWasConfigured = ETrue;
		}
	}

//////////////////////////////////////////////////////////////////////////////
//
// PropertyHandlers
//
//////////////////////////////////////////////////////////////////////////////

TBuf8<16> PropertyHandlers::allDrivesStatus;
TUsbMsBytesTransferred PropertyHandlers::iKBytesRead;
TUsbMsBytesTransferred PropertyHandlers::iKBytesWritten;
TInt PropertyHandlers::iMediaError;

void PropertyHandlers::Read(RProperty& aProperty)
	{
	Transferred(aProperty, iKBytesRead);
	}

void PropertyHandlers::Written(RProperty& aProperty)
	{
	Transferred(aProperty, iKBytesWritten);
	}

void PropertyHandlers::Transferred(RProperty& aProperty, TUsbMsBytesTransferred& aReadOrWritten)
	{
	TInt err = aProperty.Get(aReadOrWritten);
	if(err == KErrNone)
		{
		for(TInt i = 0; i < allDrivesStatus.Length()/2; i++)
			{
			if (gVerbose)
				{
				test.Printf(KBytesTransferredFmt, 
						(char)DriveNumberToLetter(allDrivesStatus[2*i]), iKBytesRead[i], iKBytesWritten[i]);
				}
			}
		}
	else
		{
		test.Printf(KErrFmt, err);
		}
	}
	
void PropertyHandlers::DriveStatus(RProperty& aProperty)
	{
	if (gVerbose)
		{
		test.Printf(_L(">> PropertyHandlers::DriveStatus"));
		}
	TInt err = aProperty.Get(allDrivesStatus);
	if(err == KErrNone)
		{
		if (gVerbose)
			{
			test.Printf(_L(" Status:  "));
			}
		for(TInt i = 0; i < allDrivesStatus.Length()/2; i++)
			{
			TInt driveNumber = allDrivesStatus[2*i];
			TInt driveStatus = allDrivesStatus[2*i+1];
			TChar driveLetter = DriveNumberToLetter(driveNumber);

			if (gVerbose)
				{
				switch(driveStatus)
					{
					case EUsbMsDriveState_Disconnected:
						{
						test.Printf(_L("%c:%d:Disconnected\n"), (char)driveLetter, driveStatus);
						break;
						}
					case EUsbMsDriveState_Connecting:
						{
						test.Printf(_L("%c:%d:Connecting\n"), (char)driveLetter, driveStatus);
						break;
						}
					case EUsbMsDriveState_Connected:
						{
						test.Printf(_L("%c:%d:Connected\n"), (char)driveLetter, driveStatus);
						break;
						}
					case EUsbMsDriveState_Disconnecting:
						{
						test.Printf(_L("%c:%d:Disconnecting\n"), (char)driveLetter, driveStatus);
						break;
						}
					case EUsbMsDriveState_Active:
						{
						test.Printf(_L("%c:%d:Active\n"), (char)driveLetter, driveStatus);
						break;
						}
					case EUsbMsDriveState_Locked:
						{
						test.Printf(_L("%c:%d:Locked\n"), (char)driveLetter, driveStatus);
						break;
						}
					case EUsbMsDriveState_MediaNotPresent:
						{
						test.Printf(_L("%c:%d:Not Present\n"), (char)driveLetter, driveStatus);
						break;
						}
					case EUsbMsDriveState_Removed:
						{
						test.Printf(_L("%c:%d:Removed\n"), (char)driveLetter, driveStatus);
						break;
						}
					case EUsbMsDriveState_Error:
						{
						test.Printf(_L("%c:%d:Error\n"), (char)driveLetter, driveStatus);
						break;
						}
					default :
						{
						test.Printf(_L("%c:%d:Unknown\n"), (char)driveLetter, driveStatus);
						break;
						}
					}
				}

			if (driveStatus == EUsbMsDriveState_Connected)
				{
				gActiveControl->SetMSFinished(EFalse);				
				}
			if (driveStatus == EUsbMsDriveState_Disconnected)
				{
				gActiveControl->SetMSFinished(ETrue);				
				}
			if(IsDriveInMountList(driveLetter))
				{
				if (driveStatus == EUsbMsDriveState_Connecting)
					{
					MountMsFs(driveNumber);
					}
				else if (driveStatus == EUsbMsDriveState_Disconnecting)
					{
					RestoreMount(driveNumber);
					}
				else
					{
					//RDebug::Print(_L("PropertyHandlers::DriveStatus: nothing to do"));
					}
				}
			else
				{
				//RDebug::Print(_L("PropertyHandlers::DriveStatus: %c: is not in mountList\n"), driveLetter);
				}
			}
		}
	else
		{
		test.Printf(KErrFmt, err);
		}

	}

void PropertyHandlers::MediaError(RProperty& aProperty)
	{
	TInt r = aProperty.Get(iMediaError);
	if(r != KErrNone)
		{
		return;
		}

	test.Printf(_L("Media Error %x\n"), iMediaError);
	if (iMediaError > 0)
		{
		gActiveControl->SetMSFinished(ETrue);
		}
	}


void StartMassStorage(RDEVCLIENT* aPort)
	{
    TInt r = KErrUnknown;

	test.Start (_L("Start Mass Storage"));

	fs.Connect();

	// Add MS file system
	test.Next (_L("Add MS File System"));
	r = fs.AddFileSystem(KMsFsy);
	test(r == KErrNone || r == KErrAlreadyExists);

#ifdef USB_SC
	aPort->FinalizeInterface();
#endif


	test.Next (_L("Create active objects\n"));
	propWatch[0] = CPropertyWatch::NewLC(EUsbMsDriveState_KBytesRead, PropertyHandlers::Read);
	propWatch[1] = CPropertyWatch::NewLC(EUsbMsDriveState_KBytesWritten, PropertyHandlers::Written);
	propWatch[2] = CPropertyWatch::NewLC(EUsbMsDriveState_DriveStatus, PropertyHandlers::DriveStatus);
	propWatch[3] = CPropertyWatch::NewLC(EUsbMsDriveState_MediaError, PropertyHandlers::MediaError);
	usbWatch = CUsbWatch::NewLC(*aPort);

	TBuf<8>  t_vendorId(_L("vendor"));
	TBuf<16> t_productId(_L("product"));
	TBuf<4>  t_productRev(_L("1.00"));

	TMassStorageConfig msConfig;
	msConfig.iVendorId.Copy(t_vendorId);
	msConfig.iProductId.Copy(t_productId);
	msConfig.iProductRev.Copy(t_productRev);

 	test.Next(_L("Connect to Mass Storage"));
	r = UsbMs.Connect();
	test_KErrNone (r);

	test.Next(_L("Start Mass Storage"));
	r = UsbMs.Start(msConfig);
	test_KErrNone (r);

	test.End();
	}
	
void StopMassStorage(RDEVCLIENT* aPort)
	{
    TInt r = KErrUnknown;
	
	test.Start (_L("Stop Mass Storage"));

	r = UsbMs.Stop();
	test_KErrNone (r);
	UsbMs.Close();

	for (TInt driveNumber = 0; driveNumber < KMaxDrives; driveNumber++)
		{
		if (msfsMountedList[driveNumber])
			{
			r = fs.DismountFileSystem(KMsFs, driveNumber);
			test_KErrNone (r);

			msfsMountedList[driveNumber] = EFalse;
			}
		}

	r = fs.RemoveFileSystem(KMsFs);
	test_KErrNone (r);

	fs.Close();
	
	delete usbWatch;
	for (TUint i =0; i < KNumPropWatch; i++)
		{
		delete propWatch[i];
		}
	
	aPort->Close();
		
	test.End();
	}

