smartinstaller/adm/src/ADMPackageInfo.cpp
author Santosh V Patil <santosh.v.patil@nokia.com>
Wed, 30 Jun 2010 11:01:26 +0530
branchADM
changeset 48 364021cecc90
permissions -rw-r--r--
SmartInstaller contribution based on the Nokia Qt SDK 1.0 release

/*
* Copyright (c) 2009-2010 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: 
*     CPackageInfo implementation
*
*
*/


#include <e32base.h>
#include <f32file.h>
#include <sysutil.h>        // SysUtil::DiskSpaceBelowCriticalLevel()

#include "ADMXmlParser.h"
#include "ADMPackageInfo.h"
#include "ADMDownloadHandler.h"
#include "ADMAppUi.h"

#include "macros.h"

//TODO: Right place?!
// The extension of the changes file
_LIT(KChangesExt,"_changes.xml");

CPackageInfo* CPackageInfo::NewLC()
	{
	CPackageInfo* object = new ( ELeave ) CPackageInfo();
	CleanupStack::PushL( object );
	object->ConstructL();
	return object;
	}

CPackageInfo* CPackageInfo::NewL()
	{
	CPackageInfo* object = CPackageInfo::NewLC();
	CleanupStack::Pop();
	return object;
	}

void CPackageInfo::ConstructL()
	{
	}

CPackageInfo::CPackageInfo() :
	iPackageStatus(EPackageStatusUnknown),
	iEdgeDlinkHdr(_FOFF(Edge, iEdgeDlink)),
	iEdgeDlinkIter(iEdgeDlinkHdr)
	{
	}

CPackageInfo::~CPackageInfo()
	{
	Edge* currentitem;

	iEdgeDlinkIter.SetToFirst();

	// Clear the memory allocated for all Edges.
	while((currentitem = iEdgeDlinkIter++) != NULL)
		{
		currentitem->iEdgeDlink.Deque();
		delete currentitem;
		};

	DELETE_IF_NONNULL( iPackageName );
	DELETE_IF_NONNULL( iVendor );
	DELETE_IF_NONNULL( iDateOfSubmission );
	DELETE_IF_NONNULL( iDateOfModification );
	DELETE_IF_NONNULL( iUrl );
	DELETE_IF_NONNULL( iDownloadUrl );
	DELETE_IF_NONNULL( iDepFileName );
	DELETE_IF_NONNULL( iChangesFileName );
	DELETE_IF_NONNULL( iSisPackageName );
	}

void CPackageInfo::AddEdgeL(CPackageInfo* aDest)
	{
	User::LeaveIfNull(aDest);

	Edge* newEdge = Edge::NewL(this, aDest);
	if (iEdgeDlinkHdr.IsEmpty())
		{
		// This is the first Edge in the list
		iEdgeDlinkHdr.AddFirst(*newEdge);
		iEdgeDlinkIter.SetToFirst();
		}
	else
		{
		// Append Edge to the list
		iEdgeDlinkHdr.AddLast(*newEdge);
		iEdgeDlinkIter.SetToLast();
		}
	}

void CPackageInfo::VisitL(CDepTree *aDepTree)
	{
	// TODO: Proper error handling
	User::LeaveIfNull(aDepTree);

	Edge *e;
	CPackageInfo *temp;

	if (iVisited == BEING_VISITED)
		{
		// Cycle is detected
		aDepTree->iCyclePresent = ETrue;
		return;
		}
	if (iVisited == DONE_VISITED)
		{
		// Node already visited added to the fetch list
		return;
		}

	iVisited = BEING_VISITED;

	// Visit all the Edges first
	while(iEdgeDlinkIter!= NULL)
		{
		e = iEdgeDlinkIter--;
		temp = e->GetVertexDst();
		temp->VisitL(aDepTree);
		}
	iVisited = DONE_VISITED;

	aDepTree->AddFetchPackageInfo(this);
	}

void CPackageInfo::SetDepFileNameL(const TDesC& aName)
	{
	__ASSERT_ALWAYS( aName.Length() > 0, User::Leave( KErrArgument) );

	DELETE_IF_NONNULL( iDepFileName );
	iDepFileName = aName.AllocL();
	}

//TODO : Clean possible?
void CPackageInfo::SetChangesFileNameL()
	{
	DELETE_IF_NONNULL( iChangesFileName );

#ifdef FEATURE_CHANGES_USE_UID
	//By default, use Packageuid as the Changes filename.
	//set the length as ( PackageUIDLength (8) + Changes_ext length )
	iChangesFileName = HBufC::NewL(8 + KChangesExt().Length());
	TPtr nameBufPtr = iChangesFileName->Des();
	nameBufPtr.Num(GetPackageUid(), EHex);
	nameBufPtr.Append(KChangesExt);
#else
	//Use Packagename as the Changes filename.
	// If package name is NULL, then Package UID is used as Changes file name.
	if( GetPackageName() != NULL )
		{
		iChangesFileName = HBufC::NewL( GetPackageName()->Length() +
										KChangesExt().Length() );
		TPtr nameBufPtr = iChangesFileName->Des();
		nameBufPtr.Copy(GetPackageName()->Des());

		const TInt nameLength = GetPackageName()->LocateReverse('.');

		if (nameLength != KErrNotFound)
			{
			//Remove the extension.
			nameBufPtr.Delete( nameLength, GetPackageName()->Length() - nameLength);
			}
		nameBufPtr.Append(KChangesExt);
		}
	else
		{
		iChangesFileName = HBufC::NewL(8 + KChangesExt().Length());
		TPtr nameBufPtr = iChangesFileName->Des();
		nameBufPtr.Num(GetPackageUid(), EHex);
		nameBufPtr.Append(KChangesExt);
		}
#endif
	}

void CPackageInfo::SetSisFileNameL(const TDesC& aName)
	{
	__ASSERT_ALWAYS( aName.Length() > 0, User::Leave( KErrArgument) );

	DELETE_IF_NONNULL( iSisPackageName );
	iSisPackageName = aName.AllocL();
	}

// -----------------------------------------------------------------------------
// Creates download URL for the package. The constructed URL is stored in
// member variable iDownloadUrl. The URL is constructed only once, the
// subsequent calls to this return iDownloadUrl.
//
//     base_url/package/version/dep_file
// i.e.
//     http://server.somewhere.net/root/qt/4.6.1/qt_dep.xml
// -----------------------------------------------------------------------------
//
HBufC8* CPackageInfo::GetDownloadUrlL()
	{
#ifdef FEATURE_CHANGES_DOWNLOAD_URL
	if (iDownloadUrl)
		{
		return iDownloadUrl;
		}

	// Url should never be null as specifying
	// the Url in changes file is mandatory
	User::LeaveIfNull(iUrl);

	TInt len = iUrl->Des().Length();
#ifdef FEATURE_CHANGES_USE_UID
	len += 8; // UID length as HEX string // iPackageName->Length();
#else
	len += iPackageName->Length();
#endif

	// TVersion: iMajor(8bit), iMinor(8bit), iBuild(16bit) = 255 255 65535
	// delimiters: /qt/4.6.1/ => //../ = (len=5)
	len += (3+3+5 + 5);

	HBufC8* url = HBufC8::NewL(len);
	TPtr8 urlPtr = url->Des();
	// Construct the download path:
	//     base_url/package/version/dep_file
	// i.e.
	//     http://server.somewhere.net/root/qt/4.6.1/qt_dep.xml
	urlPtr.Copy(*iUrl);

	// Append forward slash if that's missing
	if (urlPtr.Right(1)[0] != '/')
		{
		urlPtr.Append('/');
		}

#ifdef FEATURE_CHANGES_USE_UID
	urlPtr.AppendNum(iPackageUid, EHex);
#else
	urlPtr.Append(iPackageName->Des());
#endif
	urlPtr.Append('/');
	urlPtr.AppendNum(iVersion.iMajor);
	urlPtr.Append('.');
	urlPtr.AppendNum(iVersion.iMinor);
	urlPtr.Append('.');
	urlPtr.AppendNum(iVersion.iBuild);
	urlPtr.Append('/');

	iDownloadUrl = url;

	return url;
#else
	// Url should never be null as specifying
	// the Url in changes file is mandatory
	User::LeaveIfNull(iUrl);
	return iUrl;
#endif // FEATURE_CHANGES_DOWNLOAD_URL
	}

#ifdef USE_LOGFILE
CDepTree* CDepTree::NewLC(RFileLogger& aLogger, const TDesC& aDownloadPath)
	{
	CDepTree* object = new ( ELeave ) CDepTree(aLogger);
#else
CDepTree* CDepTree::NewLC(const TDesC& aDownloadPath)
	{
	CDepTree* object = new ( ELeave ) CDepTree();
#endif
	CleanupStack::PushL( object );
	object->ConstructL(aDownloadPath);
	return object;
	}

#ifdef USE_LOGFILE
CDepTree* CDepTree::NewL(RFileLogger& aLogger, const TDesC& aDownloadPath)
	{
	CDepTree* object = CDepTree::NewLC(aLogger, aDownloadPath);
#else
CDepTree* CDepTree::NewL(const TDesC& aDownloadPath)
	{
	CDepTree* object = CDepTree::NewLC(aDownloadPath);
#endif
	CleanupStack::Pop();
	return object;
	}

#ifdef USE_LOGFILE
CDepTree::CDepTree(RFileLogger& aLogger) :
	iLog(aLogger),
#else
CDepTree::CDepTree() :
#endif
	iCurrentPackage(-1),
	iSortedPackage(-1),
	iCyclePresent(EFalse),
	iPackageDlinkHdr(_FOFF(CPackageInfo,iPackageDlink)),
	iPackageDlinkFetchIter(iPackageDlinkHdr)
	{
	iFetchList.Reset();
	iSortedList.Reset();
	}

void CDepTree::ConstructL(const TDesC& aDownloadPath)
	{
	iDownloadPath = aDownloadPath.AllocL();
	}

CDepTree::~CDepTree()
	{
	DELETE_IF_NONNULL( iDownloadPath );

	CPackageInfo* currentPackage;
	iPackageDlinkFetchIter.SetToFirst();

	TDblQueIter<CPackageInfo> PackageIter(iPackageDlinkHdr);
	PackageIter.SetToFirst();

	// Clear memory allocated for all packages
	while((currentPackage = PackageIter++) != NULL)
		{
		currentPackage->iPackageDlink.Deque();
		delete currentPackage;
		};

	iFetchList.Close();
	iSortedList.Close();
	}

const TLinearOrder<CPackageInfo> CPackageInfo::KSortOrderByDrivePriority(CPackageInfo::CompareByDrivePriority);

TInt CPackageInfo::CompareByDrivePriority( const CPackageInfo& aPackageOne, const CPackageInfo& aPackageTwo )
{
	if ( (aPackageOne.GetDrivePriority()) > (aPackageTwo.GetDrivePriority()) )
		{
		return -1;
		}
	else
		{
		return 1;
		}
}

TBool CDepTree::ConstructFetchListL()
	{
	if(iPackageDlinkHdr.IsEmpty())
		{
		return EFalse;
		}

	CPackageInfo *rootNode = iPackageDlinkHdr.First();
	rootNode->VisitL(this);
	return ETrue;
	}

TBool CDepTree::SetDriveInfo()
	{
#ifdef FEATURE_INSTALL_DRIVE_SELECTION
	TInt driveIndex = 0;
	CPackageInfo* currentNode = NULL;
	TBool retStatus = ETrue; // Default to successful check
	RArray<TChar> driveLetters(EDriveZ-EDriveA+1);
	RArray<TInt64> driveSpaces(EDriveZ-EDriveA+1);
	RFs fs;

	if (fs.Connect() != KErrNone)
		{
		return EFalse;
		}

	CleanupClosePushL(fs);
	CleanupClosePushL(driveLetters);
	CleanupClosePushL(driveSpaces);

	// Get the drive letters and spaces
	TRAPD(err, GetDriveListL(fs, driveLetters, driveSpaces) );
	if (err != KErrNone)
		{
		return EFalse;
		}

	// The packages are sorted based on priority
	for (TInt index = 0; (index < iSortedList.Count()) && (retStatus != EFalse); index++ )
		{
		//LOG2( "Index is %d", index );

		currentNode = iSortedList[index];

		const TChar mandatoryDriveLetter = currentNode->GetMandatoryInstallDrive();

		if ( mandatoryDriveLetter )
			{
			// Reset the driveIndex for drive selection of each package.
			for ( driveIndex = 0; driveIndex < driveSpaces.Count(); driveIndex++ )
				{
				// Check whether the current drive is the mandatory drive.
				if ( driveLetters[driveIndex] == mandatoryDriveLetter )
					{
					// Check whether there is enough space in mandatory drive.
					if ( currentNode->GetInstallSize() < driveSpaces[driveIndex] )
						{
						LOG3( "PkgUid 0x%08X installation forced to %c:", currentNode->GetPackageUid(), (char)driveLetters[driveIndex] );
						currentNode->SetInstallDrive(driveLetters[driveIndex]);
						driveSpaces[driveIndex] -= currentNode->GetInstallSize();
						}
					else
						{
						retStatus = EFalse;
						}
					break;
					}
				}
			}// if ( mandatoryDriveLetter )
		else
			{
			// driveIndex is reset -> there could be a smaller package which fits in previous drive.
			for ( driveIndex = 0; driveIndex < driveSpaces.Count(); driveIndex++ )
				{
				if (currentNode->GetInstallSize() < driveSpaces[driveIndex])
					{
					currentNode->SetInstallDrive(driveLetters[driveIndex]);
					driveSpaces[driveIndex] -= currentNode->GetInstallSize();
					break;
					}
				}
			}// else of if ( mandatoryDriveLetter )

		if ( driveSpaces.Count() <= driveIndex )
			{
			//Invalid mandatory drive specified.
			retStatus = EFalse;
			}
		}// for loop of packages

	// All the dependent sis files are assigned the drives where they need to be installed.
	// Now go thru the drives to figure out which one can accommodate the download.

	// This is done as a final act so that the memory is not blocked unnecessarily by download,
	// which otherwise could be used to install a dependency sis.

	// Reset the driveIndex -> start from system drive.
	for ( driveIndex = 0; ( driveIndex < driveSpaces.Count() ) && ( retStatus != EFalse ); driveIndex++ )
		{
/*
		TInt drive;
		RFs::CharToDrive(driveLetters[driveIndex], drive);
		if (!SysUtil::DiskSpaceBelowCriticalLevelL(&fs, iMaxDownloadSize, drive))
			{
*/
		if ( driveSpaces[driveIndex] > iMaxDownloadSize )
			{
			driveSpaces[driveIndex] -= iMaxDownloadSize;
			iDownloadDrive = driveLetters[driveIndex];
			//Drive specified in download path is overriden.
			iDownloadPath->Des()[0] = iDownloadDrive;
			LOG4( "Download drive %c: free %ld (%.02f MB)", (char)iDownloadDrive, driveSpaces[driveIndex], (TReal)driveSpaces[driveIndex]/1048576.0 );
			break;
			}
		}

	// Fail, if no drive has space to max download size.
	if ( driveSpaces.Count() <= driveIndex )
		{
		// Fail: no memory.
		retStatus = EFalse;
		}

	CleanupStack::PopAndDestroy(3, &fs);

	return retStatus;

#else
	TInt64 driveSpace = iMaxDownloadSize;
	CPackageInfo* currentNode = NULL;
	const TInt installDrive = RFs::GetSystemDrive();
	TChar instDriveLetter;
	TInt err;
	TBool retStatus = ETrue; // Default to successful check
	RFs fs;

	err = fs.Connect();
	if (err != KErrNone)
		return EFalse;

	CleanupClosePushL(fs);

	RFs::DriveToChar(installDrive, instDriveLetter);

	// The install size of all dependent packages to be installed is added to the required driveSpace.
	for (TInt index = 0; index < iSortedList.Count(); index++ )
		{
		currentNode = iSortedList[index];
		driveSpace += currentNode->GetInstallSize();
		// Set the installation drive for the package
		currentNode->SetInstallDrive(instDriveLetter);
		}

	// check whether there is enough space in C to download and install all dependencies.
	TRAP( err, retStatus = !SysUtil::DiskSpaceBelowCriticalLevelL( &fs, driveSpace, installDrive ) );

	// If free space check fails, indicate not enough space
	if (err != KErrNone)
		retStatus = EFalse;

	CleanupStack::PopAndDestroy(&fs);

	return retStatus;
#endif
	}

void CDepTree::GetDriveListL(RFs& aRFs, RArray<TChar>& aDriveLetters, RArray<TInt64>& aDriveSpaces)
	{
	// This is the LFSS free space threshold
	const TInt freeSpaceAdjustment = 1024 * 384;

	// get information about drives
	TDriveList driveList;

	// List all drives in the system
	User::LeaveIfError( aRFs.DriveList(driveList) );

	TVolumeInfo volInfo;
	TInt64 volSpace = 0;
	TChar systemDriveLetter;
	const TInt systemDrive = RFs::GetSystemDrive();

	// Not sure whether the first DriveLetter in the while loop corresponds to SystemDrive.
	// Hence setting it explicitly as the first entry in Drive Array
	RFs::DriveToChar(systemDrive, systemDriveLetter);
	if (aRFs.Volume(volInfo, systemDrive) == KErrNone)
		{
		LOG4( "SysDrive %c: Free %ld (%.02f MB)", (char)systemDriveLetter, volInfo.iFree, (TReal)volInfo.iFree/1048576.0 );
		volSpace = volInfo.iFree - freeSpaceAdjustment;  // bytes
		if (volSpace < 0)
			{
			volSpace = 0;
			}
		}

	User::LeaveIfError(aDriveLetters.Append(systemDriveLetter));
	User::LeaveIfError(aDriveSpaces.Append(volSpace));

	// Check all drives
	for (TInt driveNumber = EDriveA; driveNumber <= EDriveZ; driveNumber++)
		{

		if (!driveList[driveNumber])
			{
			// Not a recognised drive
			continue;
			}

		if (aRFs.Volume(volInfo, driveNumber) != KErrNone)
			{
			// The volume is not usable (e.g. no media card inserted)
			continue;
			}

		if (driveNumber == systemDrive)
			{
			//System Drive, already added to the list
			continue;
			}

		if ( (volInfo.iDrive.iType==EMediaNotPresent) ||
			 (volInfo.iDrive.iType==EMediaRom) ||
			 (volInfo.iDrive.iType==EMediaRemote) ||
			 (volInfo.iDrive.iType==EMediaRam) ||
			 (volInfo.iDrive.iType==EMediaUnknown) )
			{
			// Exclude drives not suitable for installation
			continue;
			}

		// Do not list read only and substituted drives as an option to install to
		if (volInfo.iDrive.iDriveAtt & KDriveAttRom ||
			volInfo.iDrive.iDriveAtt & KDriveAttSubsted)
			{
			continue;
			}

		const TInt64 volSpace = volInfo.iFree - freeSpaceAdjustment;  // bytes
		if (volSpace < 0)
			{
			// Volume space below 0 => skip drive
			continue;
			}

		TChar drvLetter;
		User::LeaveIfError(RFs::DriveToChar(driveNumber, drvLetter));
		User::LeaveIfError(aDriveLetters.Append(drvLetter));
		User::LeaveIfError(aDriveSpaces.Append(volSpace));
		LOG4( "Drive %c: Free %ld (%.02f MB)", (char)drvLetter, volSpace, (TReal)volSpace/1048576.0 );
		} // for
	}

TBool CDepTree::IsDepTreeEmpty() const
	{
	return (iPackageDlinkHdr.IsEmpty());
	}

TBool CDepTree::IsCyclePresent() const
	{
	return iCyclePresent;
	}

void CDepTree::AddPackageInfo(CPackageInfo *aPackageInfo)
	{
	if(iPackageDlinkHdr.IsEmpty())
		{
		// This is the first node in the list
		iPackageDlinkHdr.AddFirst(*aPackageInfo);
		iPackageDlinkFetchIter.SetToFirst();
		}
	else
		{
		// Append node to the list
		iPackageDlinkHdr.AddLast(*aPackageInfo);
		}
	}

TPtrC CDepTree::GetDownloadPath() const
	{
	return iDownloadPath->Des();
	}

void CDepTree::AddFetchPackageInfo(CPackageInfo *aPackageInfo)
	{
	iFetchList.Append(aPackageInfo);
	//Add the download/install sizes only if the package is not already installed.
	if(aPackageInfo->iPackageStatus != EPackageInstalled)
		{
		iSortedList.InsertInOrder(aPackageInfo,CPackageInfo::KSortOrderByDrivePriority);
		iTotalDownloadSize += aPackageInfo->iDownloadSize;

		if(aPackageInfo->iDownloadSize > iMaxDownloadSize)
			{
			iMaxDownloadSize = aPackageInfo->iDownloadSize;
			}
		}
	}

CPackageInfo* CDepTree::LocatePackageInDepTree(const TUint32& aDepPackageUid)
	{
	CPackageInfo* currentItem;
	TDblQueIter<CPackageInfo> PackageIter(iPackageDlinkHdr);
	PackageIter.SetToFirst();

	while((currentItem = PackageIter++) != NULL)
		{
		if(currentItem->iPackageUid == aDepPackageUid)
			{
			return currentItem;
			}
		};
	return NULL;
	}

CPackageInfo* CDepTree::GetNextNode()
	{
	CPackageInfo* nextNode;

	if(iPackageDlinkFetchIter++ != NULL)
		{
		// This is because the first node is the root package and
		// tree is already created from it.
		nextNode = iPackageDlinkFetchIter;
		}
	else
		{
		// This case should never arise, as the api
		// should not get called.
		nextNode = NULL;
		}
	return nextNode;
	}

CPackageInfo* CDepTree::GetFetchNode( TInt nodeLocation)
	{
	CPackageInfo* node = NULL;

	if (iFetchList.Count() > nodeLocation )
		{
		node = iFetchList[nodeLocation];
		}
	return node;
	}


CPackageInfo* CDepTree::GetNextFetchNode()
	{
	CPackageInfo* nextNode = NULL;

	if (iFetchList.Count() > (iCurrentPackage+1) )
		{
		nextNode = iFetchList[++iCurrentPackage];
		}
	return nextNode;
	}

//TODO: Remove this. this is only for testing purpose.
//otherwise, there is never a need to get the sorted nodes separately.
CPackageInfo* CDepTree::GetNextSortedNode()
	{
	CPackageInfo* nextNode = NULL;

	if (iSortedList.Count() > (iSortedPackage+1) )
		{
		nextNode = iSortedList[++iSortedPackage];
		}
	return nextNode;
	}

CPackageInfo* CDepTree::GetPreviousFetchNode()
	{
	CPackageInfo* previousNode = NULL;
	if (iCurrentPackage > 0)
		{
		previousNode = iFetchList[--iCurrentPackage];
		}
	 return previousNode;
	}

void CDepTree::RemoveDownloadedFiles(RFs& aRfs)
	{
	CPackageInfo* currentItem;
	TDblQueIter<CPackageInfo> PackageIter(iPackageDlinkHdr);
	PackageIter.SetToFirst();

	// Remove all the downloaded dep and changes file
	while((currentItem = PackageIter) != NULL)
		{
		if ( currentItem->iDepFileName )
			{
			DeleteFile(aRfs, *(currentItem->iDepFileName), *iDownloadPath);
			}
		if ( currentItem->iChangesFileName )
			{
			DeleteFile(aRfs, *(currentItem->iChangesFileName), *iDownloadPath);
			}
		if ( currentItem->iSisPackageName )
			{
			DeleteFile(aRfs, *(currentItem->iSisPackageName), *iDownloadPath);
			}
		PackageIter++;
		}
	}

void CDepTree::DeleteFile(RFs& aRfs, const TDesC& aFileName, const TDesC& aFilePath )
	{
	TFileName filename;

	filename.Copy(aFilePath);
	filename.Append(aFileName);
	aRfs.Delete(filename);
	}

CPackageInfo* CDepTree::GetRootNode()
	{
	if (iPackageDlinkHdr.IsEmpty())
		{
		return NULL;
		}
	else
		{
		return iPackageDlinkHdr.First();
		}
	}

Edge* Edge::NewLC(CPackageInfo *aVtx1, CPackageInfo *aVtx2)
	{
	Edge* object = new ( ELeave ) Edge(aVtx1, aVtx2);
	CleanupStack::PushL( object );
	object->ConstructL();
	return object;
	}

Edge* Edge::NewL(CPackageInfo *aVtx1, CPackageInfo *aVtx2)
	{
	Edge* object = Edge::NewLC(aVtx1, aVtx2);
	CleanupStack::Pop();
	return object;
	}

void Edge::ConstructL()
	{
	}

Edge::Edge(CPackageInfo *aVtx1, CPackageInfo *aVtx2)
	{
	iVertexOrg = aVtx1;
	iVertexDst = aVtx2;
	}

CPackageInfo* Edge::GetVertexOrg() const
	{
	return iVertexOrg;
	}

CPackageInfo* Edge::GetVertexDst() const
	{
	return iVertexDst;
	}