commands/tail/tail.cpp
author Tom Sutcliffe <thomas.sutcliffe@accenture.com>
Sat, 31 Jul 2010 19:07:57 +0100
changeset 23 092bcc217d9d
parent 0 7f656887cf89
permissions -rw-r--r--
Tidied iocli exports, build macro tweaks. Removed 4 overloads of CCommandBase::RunCommand[L] that are no longer used at all, and changed one more to not be exported as it's only used internally to iocli.dll. fixed builds on platforms that don't support btrace or any form of tracing.

// tail.cpp
// 
// Copyright (c) 2005 - 2010 Accenture. All rights reserved.
// This component and the accompanying materials are made available
// under the terms of the "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:
// Accenture - Initial contribution
//

#include "character_converter.h"
#include "file_reader.h"
#include "tail.h"

const TInt KBlockSize = 512;
const TText KUnicodeParagraphSeparator = 0x2029;
const TText KUnicodeLineSeparator = 0x2028;
const TText KCarriageReturn = 0x000d;
const TText KLineFeed = 0x000a;


//
// CTrailingLineFinder.
//

class CTrailingLineFinder : public CBase, public MFileReaderObserver
	{
public:
	static CTrailingLineFinder* NewL(TInt aBlockSize, CCharacterConverter& aCharacterConverter);
	~CTrailingLineFinder();
	void Find(CFileReader& aFileReader, const TDesC& aFileName, TInt aNumLines, MTrailingLineFinderObserver& aObserver);
	void Cancel();
	const TDesC& operator[](TInt aIndex) const;
private:
	CTrailingLineFinder(CCharacterConverter& aCharacterConverter);
	void ConstructL(TInt aBlockSize);
	void HandleFileDataL(const TDesC8& aData, TReadType aType, TBool& aContinue);
	void FindNextLine(const TDesC& aData, TInt aOffset, TPtrC& aLine, TBool& aComplete);
	void AddLineL(const TDesC& aLine, TBool aComplete);
private:	// From MFileReaderObserver.
	virtual void HandleFileData(const TDesC8& aData, TReadType aType, TBool& aContinue);
	virtual void HandleFileReadError(TInt aError);
private:
	CFileReader* iFileReader;
	CCharacterConverter& iCharacterConverter;
	MTrailingLineFinderObserver* iObserver;
	HBufC8* iBuf;
	TPtr8 iPtr;
	RPointerArray<HBufC> iTrailingLines;
	TInt iCurrentLineIndex;
	TInt iNumLinesToFind;
	TInt iNumCompleteLinesFound;
	TInt iFilePos;
	TBool iPossiblySplitCrLf;
	TBool iLastLineNotComplete;
	};

CTrailingLineFinder* CTrailingLineFinder::NewL(TInt aBlockSize, CCharacterConverter& aCharacterConverter)
	{
	CTrailingLineFinder* self = new(ELeave) CTrailingLineFinder(aCharacterConverter);
	CleanupStack::PushL(self);
	self->ConstructL(aBlockSize);
	CleanupStack::Pop(self);
	return self;
	}

CTrailingLineFinder::~CTrailingLineFinder()
	{
	delete iBuf;
	iTrailingLines.ResetAndDestroy();
	}

void CTrailingLineFinder::Find(CFileReader& aFileReader, const TDesC& aFileName, TInt aNumLines, MTrailingLineFinderObserver& aObserver)
	{
	ASSERT(!iFileReader);
	iTrailingLines.ResetAndDestroy();
	iFileReader = &aFileReader;
	iObserver = &aObserver;
	iNumLinesToFind = aNumLines;
	iNumCompleteLinesFound = 0;
	iCurrentLineIndex = -1;
	iPossiblySplitCrLf = EFalse;
	iFileReader->Read(aFileName, *this);
	}

void CTrailingLineFinder::Cancel()
	{
	if (iFileReader)
		{
		iFileReader->Cancel();
		}
	}

const TDesC& CTrailingLineFinder::operator[](TInt aIndex) const
	{
	return *iTrailingLines[aIndex];
	}

void CTrailingLineFinder::HandleFileDataL(const TDesC8& aData, TReadType aType, TBool& aContinue)
	{
	iFilePos += aData.Length();
	aContinue = ETrue;
	const TDesC* convertedData = NULL;

	switch (aType)
		{
		case MFileReaderObserver::EFirst:
			{
			convertedData = &iCharacterConverter.ConvertChunkL(aData, CCharacterConverter::EFirst);
			break;
			}
		case MFileReaderObserver::EMiddle:
			{
			convertedData = &iCharacterConverter.ConvertChunkL(aData, CCharacterConverter::EMiddle);
			break;
			}
		case MFileReaderObserver::ELast:
			{
			convertedData = &iCharacterConverter.ConvertChunkL(aData, CCharacterConverter::ELast);
			break;
			}
		default:
			{
			ASSERT(FALSE);
			}
		}

	if (convertedData->Length() > 0)
		{
		TInt offset = 0;
		FOREVER
			{
			TPtrC linePtr;
			TBool completeLine;
			FindNextLine(*convertedData, offset, linePtr, completeLine);
			AddLineL(linePtr, completeLine);
			if (!completeLine)
				{
				break;
				}
			offset += linePtr.Length();
			}
		}

	if (aType == MFileReaderObserver::ELast)
		{
		iFileReader = NULL;
		iObserver->HandleTrailingLines(iCurrentLineIndex + 1, iFilePos);
		}
	}

void CTrailingLineFinder::AddLineL(const TDesC& aLine, TBool aComplete)
	{
	if (iLastLineNotComplete)
		{
		HBufC*& buf = iTrailingLines[iCurrentLineIndex];
		buf = buf->ReAllocL(buf->Length() + aLine.Length());
		buf->Des() += aLine;
		}
	else
		{
		HBufC* line = aLine.AllocLC();
		if (iNumCompleteLinesFound <= iNumLinesToFind)
			{
			++iCurrentLineIndex;
			User::LeaveIfError(iTrailingLines.Append(line));
			}
		else
			{
			delete iTrailingLines[0];
			iTrailingLines.Remove(0);
			iTrailingLines.Append(line); // Will always succeed due to the above removal.
			}
		CleanupStack::Pop(line);
		}

	iLastLineNotComplete = !aComplete;
	if (aComplete)
		{
		++iNumCompleteLinesFound;
		}
	}

CTrailingLineFinder::CTrailingLineFinder(CCharacterConverter& aCharacterConverter)
	: iCharacterConverter(aCharacterConverter), iPtr(NULL, 0)
	{
	}

void CTrailingLineFinder::ConstructL(TInt aBlockSize)
	{
	iBuf = HBufC8::NewL(aBlockSize);
	iPtr.Set(iBuf->Des());
	}

void CTrailingLineFinder::FindNextLine(const TDesC& aData, TInt aOffset, TPtrC& aLine, TBool& aComplete)
	{
	const TText* first = aData.Ptr() + aOffset;
	const TText* p = first;
	const TText* last = first + aData.Length() - aOffset - 1;
	aComplete = EFalse;
	TBool foundLineBreak = EFalse;
	while ((p <= last) && !foundLineBreak)
		{
		if (iPossiblySplitCrLf)
			{
			iPossiblySplitCrLf = EFalse;
			if (*p == KLineFeed)
				{
				aComplete = ETrue;
				foundLineBreak = ETrue;
				}
			}
		else if ((*p == KUnicodeParagraphSeparator) || (*p == KUnicodeLineSeparator) || (*p == KLineFeed))
			{
			aComplete = ETrue;
			foundLineBreak = ETrue;
			}
		else if (*p == KCarriageReturn)
			{
			if (p == last)
				{
				iPossiblySplitCrLf = ETrue;
				}
			else if (*(p + 1) == KLineFeed)
				{
				aComplete = ETrue;
				foundLineBreak = ETrue;
				++p;
				}
			else
				{
				aComplete = ETrue;
				foundLineBreak = ETrue;
				}
			}
		++p;
		}

	aLine.Set(first, p - first);
	}

void CTrailingLineFinder::HandleFileData(const TDesC8& aData, TReadType aType, TBool& aContinue)
	{
	TRAPD(err, HandleFileDataL(aData, aType, aContinue));
	if (err)
		{
		iObserver->HandleTrailingLineError(err);
		}
	}

void CTrailingLineFinder::HandleFileReadError(TInt aError)
	{
	iObserver->HandleTrailingLineError(aError);
	}


//
// CFileWatcher.
//

class CFileWatcher : public CActive
	{
public:
	static CFileWatcher* NewL(RFs& aFs);
	~CFileWatcher();
	void Start(const TDesC& aFileName, MFileChangeObserver& aObserver);
private:
	CFileWatcher(RFs& aFs);
	void Queue();
private:	// From CActive.
	virtual void DoCancel();
	virtual void RunL();
private:
	RFs& iFs;
	TFileName iFileName;
	MFileChangeObserver* iObserver;
	};


CFileWatcher* CFileWatcher::NewL(RFs& aFs)
	{
	return new(ELeave) CFileWatcher(aFs);
	}

CFileWatcher::~CFileWatcher()
	{
	Cancel();
	}

void CFileWatcher::Start(const TDesC& aFileName, MFileChangeObserver& aObserver)
	{
	ASSERT(!IsActive());
	iFileName = aFileName;
	iObserver = &aObserver;
	Queue();
	}

CFileWatcher::CFileWatcher(RFs& aFs)
	: CActive(CActive::EPriorityStandard), iFs(aFs)
	{
	CActiveScheduler::Add(this);
	}

void CFileWatcher::Queue()
	{
	ASSERT(!IsActive());
	iFs.NotifyChange(ENotifyWrite, iStatus, iFileName);
	SetActive();
	}

void CFileWatcher::DoCancel()
	{
	iFs.NotifyChangeCancel(iStatus);
	}

void CFileWatcher::RunL()
	{
	Queue();
	iObserver->HandleFileChange(iFileName);
	}


//
// CCmdTail.
//

CCommandBase* CCmdTail::NewLC()
	{
	CCmdTail* self = new(ELeave) CCmdTail();
	CleanupStack::PushL(self);
	self->ConstructL();
	return self;
	}

CCmdTail::~CCmdTail()
	{
	delete iCharacterConverter;
	delete iTrailingLineFinder;
	delete iFileWatcher;
	delete iFileReader;
	}

CCmdTail::CCmdTail()
	: CCommandBase(EManualComplete), iNumLines(10)
	{
	}

void CCmdTail::ConstructL()
	{
	BaseConstructL();
	}

void CCmdTail::WriteChunkToConsoleL(const TDesC8& aData, TReadType aType)
	{
	switch (aType)
		{
		case MFileReaderObserver::EFirst:
			{
			Write(iCharacterConverter->ConvertChunkL(aData, CCharacterConverter::EFirst));
			break;
			}
		case MFileReaderObserver::EMiddle: // Treat "middle" and "last" to same as with tailing it's not possible to know if "last" is really last.
		case MFileReaderObserver::ELast:
			{
			Write(iCharacterConverter->ConvertChunkL(aData, CCharacterConverter::EMiddle));
			break;
			}
		default:
			{
			ASSERT(FALSE);
			}
		}
	}

const TDesC& CCmdTail::Name() const
	{
	_LIT(KName, "tail");
	return KName;
	}

void CCmdTail::DoRunL()
	{
	iCharacterConverter = CCharacterConverter::NewL(KBlockSize, FsL());
	iTrailingLineFinder = CTrailingLineFinder::NewL(KBlockSize, *iCharacterConverter);
	iFileWatcher = CFileWatcher::NewL(Fs());
	iFileReader = CFileReader::NewL(KBlockSize, Fs(), ETrue);
	iTrailingLineFinder->Find(*iFileReader, iFileName, iNumLines, *this);
	}

void CCmdTail::OptionsL(RCommandOptionList& aOptions)
	{
	_LIT(KOptFollow, "follow");
	_LIT(KOptNumLines, "lines");
	aOptions.AppendBoolL(iFollow, KOptFollow);
	aOptions.AppendIntL(iNumLines, KOptNumLines);
	}

void CCmdTail::ArgumentsL(RCommandArgumentList& aArguments)
	{
	_LIT(KArgFileName, "file_name");
	aArguments.AppendFileNameL(iFileName, KArgFileName);
	}

void CCmdTail::HandleTrailingLines(TInt aNumLinesFound, TInt aEndFilePos)
	{
	if (iFollow)
		{
		iFilePos = aEndFilePos;
		iFileWatcher->Start(iFileName, *this);
		}
	for (TInt i = 0; i < aNumLinesFound; ++i)
		{
		Write((*iTrailingLineFinder)[i]);
		}
	if (!iFollow)
		{
		Complete();
		}
	}

void CCmdTail::HandleTrailingLineError(TInt aError)
	{
	PrintError(aError, _L("Problem finding trailing %d lines in %S: %d"), iNumLines, &iFileName, aError);
	Complete(aError);
	}

void CCmdTail::HandleFileChange(const TDesC& /*aFileName*/)
	{
	if (!iFileReader->IsActive())
		{
		iFileReader->Read(iFileName, iFilePos, *this);
		}
	}

void CCmdTail::HandleFileData(const TDesC8& aData, TReadType aType, TBool& aContinue)
	{
	aContinue = ETrue;
	iFilePos += aData.Length();
	TRAPD(err, WriteChunkToConsoleL(aData, aType));
	if (err)
		{
		aContinue = EFalse;
		PrintError(err, _L("Problem writing chunk of %S to console: %d"), &iFileName, err);
		}
	}

void CCmdTail::HandleFileReadError(TInt aError)
	{
	PrintWarning(_L("Problem reading %S: %d"), &iFileName, aError);
	}


#ifdef EXE_BUILD
EXE_BOILER_PLATE(CCmdTail)
#endif