// MiscServer.cpp
// 
// Copyright (c) 2006 - 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 "CloggerServer.h"
#include <fshell/common.mmh>
#include <fshell/ltkutils.h>

#ifdef FSHELL_EZLIB_SUPPORT
#include <EZCompressor.h>
//#include <EZDecompressor.h>
#include <EZlib.h>
#include <EZGzip.h>
#endif

_LIT8(KDiskFull, "\r\n" MINTAGSTART "Log truncated due to disk full\r\n");

void CLogWriter::WriteBuf(TInt aBuf)
	{
	ASSERT(!IsActive() && iEnabled);
		{
		iBuf = aBuf;
		iFile.Write(iServer.GetBuf(aBuf), iStatus);
		SetActive();
		}
	}

TBool CLogWriter::IsBusyWriting()
	{
	return IsActive();
	}

void CLogWriter::RunL()
	{
	if (iStatus == KErrDiskFull)
		{
		//TODO Make this occur before the disk is actually completely empty?
		TInt filePos = 0;
		TInt err = iFile.Seek(ESeekCurrent, filePos);
		if (err == KErrNone && filePos > KDiskFull().Length()) // ensure the file has enough room in it for our message
			{
			// Rewind the log file and overwrite the tail of it with a warning to say the disk is full
			filePos = filePos - KDiskFull().Length();
			err = iFile.Seek(ESeekStart, filePos);
			if (err == KErrNone)
				{
				iFile.Write(KDiskFull, iStatus);
				SetActive();
				return;
				}
			}
		}
	iServer.CompletedWritingBuf(this, iBuf);
	}

void CLogWriter::DoCancel()
	{
	// There's no way to cancel a file write (or any semantic sense to do so) so by doing nothing we get the SyncWaitForCompletion behaviour we wanted
	}

CLogWriter::~CLogWriter()
	{
	// Don't cancel, CloggerServer is supposed to guarantee we won't be deleted when still writing
	//Cancel();
	}

void CLogWriter::CloseWriter()
	{
	delete this;
	}

void CLogWriter::SetEnabled(TBool aEnabled)
	{
	if (!iFile.SubSessionHandle())
		{
		// If the log file is not open, we can't enable ourselves even if the client asks us to
		aEnabled = EFalse;
		}
	iEnabled = aEnabled;
	}

CRDebugWriter::CRDebugWriter(CCloggerServer& aServer)
	: iServer(aServer)
	{}

void CRDebugWriter::WriteBufL(const TDesC8& aBuf)
	{
	TInt maxLen = 256;

	// RDebug::Print only accepts 256 chars on the kernel side, so split the write up
	TInt pos = 0;
	while (pos < aBuf.Length())
		{
		TPtrC8 frag = aBuf.Mid(pos, Min(maxLen, aBuf.Length() - pos));
		//Exec::DebugPrint((TAny*)&frag, 1); // This is quicker than calling RDebugPrint - cuts down on a memory copy
		// ^ Except that you can't do it from non-euser code...
		LtkUtils::RawPrint(frag);
		pos += frag.Length();
		}
	}

void CRDebugWriter::CloseWriter()
	{
	delete this;
	}

///////////////

TInt CSyncWriterWrapper::ThreadFunction(TAny* aSelf)
	{
	CTrapCleanup* cleanup=CTrapCleanup::New();
	if (!cleanup) return KErrNoMemory;
	
	CSyncWriterWrapper* self = static_cast<CSyncWriterWrapper*>(aSelf);
	for (;;)
		{
		User::WaitForRequest(self->iThreadStatus);
		//User::After(1); //DEBUG TO TEST SLOW WRITERS
		if (self->iThreadStatus != KErrNone)
			{
			break;
			}

		const TDesC8& buf = self->iServer.GetBuf(self->iBuf);

		TRAPD(err, self->iWriter.WriteBufL(buf));

		self->iThreadStatus = KRequestPending;
		TRequestStatus* stat = &self->iStatus;
		self->iMainThread.RequestComplete(stat, err);
		}

	delete cleanup;
	return self->iThreadStatus.Int();
	}

void CSyncWriterWrapper::WriteBuf(TInt aBuf)
	{
	ASSERT(!IsActive() && iEnabled);
		{
		iBuf = aBuf;
		iStatus = KRequestPending;
		SetActive();
		TRequestStatus* stat = &iThreadStatus;
		iWorkerThread.RequestComplete(stat, KErrNone);
		}
	}

TBool CSyncWriterWrapper::IsBusyWriting()
	{
	return IsActive();
	}

void CSyncWriterWrapper::RunL()
	{
	iServer.CompletedWritingBuf(this, iBuf);
	}

void CSyncWriterWrapper::DoCancel()
	{
	// There's no way to cancel a sync writer
	}

CSyncWriterWrapper::~CSyncWriterWrapper()
	{
	// Don't cancel, CloggerServer is supposed to guarantee we won't be deleted when still writing
	//Cancel();
	if (iWorkerThread.Handle())
		{
		TRequestStatus* stat = &iThreadStatus;
		iWorkerThread.RequestComplete(stat, KErrCancel);
		TRequestStatus rv;
		iWorkerThread.Rendezvous(rv);
		User::WaitForRequest(rv);
		iWorkerThread.Close();
		}
	iMainThread.Close();
	if (iOwnWriter)
		{
		iWriter.CloseWriter();
		}
	}

void CSyncWriterWrapper::CloseWriter()
	{
	delete this;
	}

void CSyncWriterWrapper::SetEnabled(TBool aEnabled)
	{
	iEnabled = aEnabled;
	}

CSyncWriterWrapper::CSyncWriterWrapper(CCloggerServer& aServer, MSyncWriter& aWriter)
	: CActive(EPriorityHigh), // It's more important to keep writing than to handle clientserver calls
	iServer(aServer), iWriter(aWriter), iBuf(-1), iEnabled(EFalse)
	{
	CActiveScheduler::Add(this);
	}

CSyncWriterWrapper* CSyncWriterWrapper::NewL(CCloggerServer& aServer, MSyncWriter& aWriter, TInt aWriterId)
	{
	CSyncWriterWrapper* self = new(ELeave) CSyncWriterWrapper(aServer, aWriter);
	CleanupStack::PushL(self);
	self->ConstructL(aWriterId);
	CleanupStack::Pop(self);
	self->iOwnWriter = ETrue;
	return self;
	}

void CSyncWriterWrapper::ConstructL(TInt aWriterId)
	{
	User::LeaveIfError(iMainThread.Open(RThread().Id()));
	_LIT(KName, "SyncWriter%i");
	TBuf<16> name;
	name.Format(KName, aWriterId);
	User::LeaveIfError(iWorkerThread.Create(name, &ThreadFunction, 2048, NULL, this));
	if (aWriterId >= 0)
		{
		iWorkerThread.SetPriority(EPriorityMore); // To ensure buffers aren't being filled without us having a chance to empty them
		}
	else
		{
		iWorkerThread.SetPriority(EPriorityMuchLess); // We use the sync writer wrapper around the background compress of rotated log files, and that operation should be low priority
		}
	iWorkerThread.Resume();
	}

//////////

CLogCompressor* CLogCompressor::NewLC(CCloggerServer& aServer)
	{
	CLogCompressor* self = new(ELeave) CLogCompressor(aServer);
	CleanupStack::PushL(self);
	User::LeaveIfError(self->iFs.Connect());
	self->iFs.ShareAuto();
	return self;
	}

CLogCompressor::CLogCompressor(CCloggerServer& aServer)
	: iServer(aServer)
	{
	}

CLogCompressor::~CLogCompressor()
	{
	}

void CLogCompressor::WriteBufL(const TDesC8& /*aBuf*/)
	{
	TDes& filename = iServer.GetFilenameToRotate();

	HBufC* compressedFile = HBufC::NewLC(filename.Length()+3);
	_LIT(KFmt, "%S.gz");
	compressedFile->Des().Format(KFmt, &filename);

	RFile input;

	//_LIT(KInfo,"Compressing file %S to %S\n");
	//console->Printf(KInfo,&inputFile,compressedFile);

	User::LeaveIfError(input.Open(iFs, filename, EFileStream | EFileRead | EFileShareAny));
	CleanupClosePushL(input);

	TRAPD(err, DoGzipL(input, *compressedFile));

	if (err)
		{
		// If the compress failed, delete any fragment (don't care if this fails)
		iFs.Delete(*compressedFile);
		}

	//_LIT(KHoorah,"Hoorah");
	//console->Printf(KHoorah);

	CleanupStack::PopAndDestroy(&input);
	if (err == KErrNone)
		{
		// Don't delete the original unless the compress succeeded!
		err = iFs.Delete(filename);
		}
	if (err == KErrNone)
		{
		// We've sucessfully compressed our file, so update filename to reflect that
		filename = *compressedFile;
		}
	// Don't do anything if the delete fails - the file(s) will be cleaned up when the max number of rotated logs is exceeded
	CleanupStack::PopAndDestroy(compressedFile);
	}

void CLogCompressor::CloseWriter()
	{
	delete this;
	}

void CLogCompressor::DoGzipL(RFile& aInput, const TDesC& aOutput)
	{
#ifdef FSHELL_EZLIB_SUPPORT
	const TInt bufferSize = 0x8000; // 32 KB
	CEZFileToGZip* com = CEZFileToGZip::NewLC(iFs, aOutput, aInput, bufferSize);
	while (com->DeflateL()){/*do nothing*/}
	CleanupStack::PopAndDestroy(com);
#else
	User::Leave(KErrNotSupported);
#endif
	}

////

CMessageQueueWriter* CMessageQueueWriter::NewL()
	{
	CMessageQueueWriter* self = new(ELeave) CMessageQueueWriter;
	TInt err = self->iQ.CreateGlobal(_L("Clogger.LogMessageQueue"), 4);

	if (err)
		{
		DISOWN(self);
		User::Leave(err);
		}
	return self;
	}

void CMessageQueueWriter::WriteBufL(const TDesC8& aBuf)
	{
	TBuf8<128> buf;
	TPtrC8 lineFrag(aBuf);
	while (lineFrag.Length())
		{
		TInt spaceFree = buf.MaxLength() - buf.Length();
		buf.Append(lineFrag.Left(spaceFree));
		TInt err = iQ.Send(buf);
		if (err == KErrOverflow)
			{
			// Drop a frame
			TBuf8<128> buf2;
			iQ.Receive(buf2);
			// And resend
			err = iQ.Send(buf);
			}

		lineFrag.Set(lineFrag.Mid(Min(spaceFree, lineFrag.Length())));
		buf.Zero();
		}
	}

void CMessageQueueWriter::CloseWriter()
	{
	delete this;
	}

CMessageQueueWriter::CMessageQueueWriter()
	{
	}

CMessageQueueWriter::~CMessageQueueWriter()
	{
	iQ.Close();
	}

CDebugRouterClient* CDebugRouterClient::NewL(CCloggerServer& aServer)
	{
	CDebugRouterClient* self = new(ELeave) CDebugRouterClient(aServer);
	CleanupStack::PushL(self);
	self->ConstructL();
	CleanupStack::Pop(self);
	return self;
	}

void CDebugRouterClient::ConstructL()
	{
	TInt err = RCloggerDebugRouter::LoadDriver();
	if (err != KErrAlreadyExists)
		{
		User::LeaveIfError(err);
		}
	User::LeaveIfError(iDebugRouter.Open());
	}

void CDebugRouterClient::OpenChunkL()
	{
	if (!iSharedChunk.Handle())
		{
		User::LeaveIfError(iDebugRouter.OpenChunk(iSharedChunk));
		iTempBuf.CreateL(2048);
		}
	}

CDebugRouterClient::CDebugRouterClient(CCloggerServer& aServer)
: CActive(CActive::EPriorityStandard + 1), iServer(aServer) // Priority just higher than the server object - it's slightly more important we keep the device driver buffer serviced than we handle normal logging requests
	{
	CActiveScheduler::Add(this);
	}

CDebugRouterClient::~CDebugRouterClient()
	{
	Cancel();
	if (iDebugRouter.Handle())
		{
		iDebugRouter.EnableDebugRouting(RCloggerDebugRouter::EDisable); // I don't really know whether to call this before or after the cancel
		iDebugRouter.Close();
		}
	RCloggerDebugRouter::CloseDriver();
	iSharedChunk.Close();
	iTempBuf.Close();
	}

TPtrC8 Read(TDes8& aTempBuf, TPtrC8& aData, TInt aLength, TPtrC8& aOverflowData)
	{
	if (aLength <= aData.Length())
		{
		// Can read it from this buffer
		TPtrC8 res(aData.Left(aLength));
		aData.Set(aData.Mid(aLength));
		return res;
		}
	else /*if (aLength > aData.Length())*/
		{
		// Descriptor spans wrap point, so need to copy into temp buf
		aTempBuf.Copy(aData.Left(aTempBuf.MaxLength())); // If anyone's crazy enough to write a platsec diagnostic string longer than 2k, it gets truncated
		TInt overflowLen = aLength - aData.Length();
		aData.Set(aOverflowData); // Wrap aData
		aOverflowData.Set(TPtrC8());
		if (overflowLen > aData.Length())
			{
			ASSERT(EFalse); // Shouldn't happen
			// in urel, return everything we've got
			return aData;
			}
		aTempBuf.Append(aData.Left(overflowLen));
		aData.Set(aData.Mid(overflowLen));
		return TPtrC8(aTempBuf);
		}
	}

void CDebugRouterClient::RunL()
	{
	TUint chunkSize = iSharedChunk.Size();
	const TUint KDataStartOffset = sizeof(SDebugChunkHeader);
	SDebugChunkHeader* chunkHeader = (SDebugChunkHeader*)iSharedChunk.Base();
	TUint start = chunkHeader->iStartOffset;
	TUint end = chunkHeader->iEndOffset;
	TUint overflows = chunkHeader->iOverflows;

	TBool wrap = (start > end);
	TUint endLen = wrap ? chunkSize - start : end - start;
	TUint startLen = wrap ? end - KDataStartOffset : 0;

	TPtrC8 endData(iSharedChunk.Base() + start, endLen);
	TPtrC8 startData;
	if (wrap) startData.Set(iSharedChunk.Base() + KDataStartOffset, startLen);
	TPtrC8 data(endData);

	while (data.Length())
		{
		TPtrC8 header = Read(iTempBuf, data, sizeof(SCloggerTraceInfo), startData);
		if (header.Length() < (TInt)sizeof(SCloggerTraceInfo))
			{
			ASSERT(EFalse); // for udeb
			break; // Something's broken
			}
		SCloggerTraceInfo info;
		Mem::Copy(&info, header.Ptr(), sizeof(SCloggerTraceInfo));
		ASSERT(info.iTraceType == 'K' || info.iTraceType == 'U' || info.iTraceType == 'P');
		TPtrC8 msg = Read(iTempBuf, data, info.iLength, startData);
		iServer.LogKernMessage(info.iTraceType, info.iTickCount, info.iThreadId, msg);
		}
	if (overflows)
		{
		_LIT8(KErr, "RDebug::Print buffer overflowed, %u calls not logged");
		iServer.LogError(KErr, overflows);
		}
	// Zero the memory so it's easier to read in the crashlog
	memclr(iSharedChunk.Base() + start, endLen);
	if (startLen) memclr(iSharedChunk.Base() + KDataStartOffset, startLen);
	
	StartRouting(-1); // Magic number to indicate no need to call EnableDebugRouting again
	}

void CDebugRouterClient::DoCancel()
	{
	iDebugRouter.CancelReceive();
	//iDebugRouter.EnableDebugRouting(EFalse);
	// We only call Cancel() outside of our destructor when doing a FlushBuffers, in that case we'd like to make use of the device drivers buffering if possible, and not tell it to stop completely
	}

void CDebugRouterClient::StartRouting(TBool aConsumeLogs)
	{
	if (aConsumeLogs != -1)
		{
		RCloggerDebugRouter::TEnableOption enable = aConsumeLogs ? RCloggerDebugRouter::EEnableRoutingAndConsume : RCloggerDebugRouter::EEnableRouting;
		iDebugRouter.EnableDebugRouting(enable);
		}
	if (!IsActive())
		{
		iDebugRouter.ReceiveData(iStatus);
		SetActive();
		}
	}

void CDebugRouterClient::StopRouting()
	{
	Cancel();
	iDebugRouter.EnableDebugRouting(RCloggerDebugRouter::EDisable);
	}

TInt CDebugRouterClient::RegisterCrashDumpAreas(const TDesC8& aCrashDumpAreas)
	{
	return iDebugRouter.RegisterCrashDumpAreas(aCrashDumpAreas);
	}

TInt CDebugRouterClient::CreateKernChunkForClient(RThread* aClient, TInt aMaxSize, TInt aCommittedSize, RChunk& aOurChunk)
	{
	SCreateChunkParams params;
	params.iHandleOfOtherThread = aClient ? aClient->Handle() : 0;
	params.iMaxSize = aMaxSize;
	params.iCommittedSize = aCommittedSize;
	params.iChunkHandle = 0; // Not strictly necessary to set this
	params.iOtherThreadChunkHandle = 0; // Not strictly necessary to set this
	
	TInt err = iDebugRouter.CreateChunk(params);
	if (err == KErrNone)
		{
		aOurChunk.SetHandle(params.iChunkHandle);
		return aClient ? params.iOtherThreadChunkHandle : KErrNone;
		}
	return err;
	// Return either an error, or the handle for the other thread, or KErrNone if there was no error and we didn't specify another thread
	}

TInt CDebugRouterClient::AdjustChunk(RChunk& aChunk, TInt aNewSize)
	{
	return iDebugRouter.AdjustChunk(aChunk, aNewSize);
	}
