commands/tail/tail.cpp
changeset 0 7f656887cf89
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/commands/tail/tail.cpp	Wed Jun 23 15:52:26 2010 +0100
@@ -0,0 +1,458 @@
+// 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
+