/** 
 * @file lllfsthread.cpp
 * @brief LLLFSThread base class
 *
 * $LicenseInfo:firstyear=2001&license=viewergpl$
 * 
 * Copyright (c) 2001-2009, Linden Research, Inc.
 * 
 * Second Life Viewer Source Code
 * The source code in this file ("Source Code") is provided by Linden Lab
 * to you under the terms of the GNU General Public License, version 2.0
 * ("GPL"), unless you have obtained a separate licensing agreement
 * ("Other License"), formally executed by you and Linden Lab.  Terms of
 * the GPL can be found in doc/GPL-license.txt in this distribution, or
 * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
 * 
 * There are special exceptions to the terms and conditions of the GPL as
 * it is applied to this Source Code. View the full text of the exception
 * in the file doc/FLOSS-exception.txt in this software distribution, or
 * online at
 * http://secondlifegrid.net/programs/open_source/licensing/flossexception
 * 
 * By copying, modifying or distributing this software, you acknowledge
 * that you have read and understood your obligations described above,
 * and agree to abide by those obligations.
 * 
 * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
 * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
 * COMPLETENESS OR PERFORMANCE.
 * $/LicenseInfo$
 */

#include "linden_common.h"
#include "lllfsthread.h"
#include "llstl.h"
#include "llapr.h"

//============================================================================

/*static*/ LLLFSThread* LLLFSThread::sLocal = NULL;

//============================================================================
// Run on MAIN thread
//static
void LLLFSThread::initClass(bool local_is_threaded)
{
	llassert(sLocal == NULL);
	sLocal = new LLLFSThread(local_is_threaded);
}

//static
S32 LLLFSThread::updateClass(U32 ms_elapsed)
{
	sLocal->update(ms_elapsed);
	return sLocal->getPending();
}

//static
void LLLFSThread::cleanupClass()
{
	sLocal->setQuitting();
	while (sLocal->getPending())
	{
		sLocal->update(0);
	}
	delete sLocal;
	sLocal = 0;
}

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

LLLFSThread::LLLFSThread(bool threaded) :
	LLQueuedThread("LFS", threaded),
	mPriorityCounter(PRIORITY_LOWBITS)
{
	if(!mLocalAPRFilePoolp)
	{
		mLocalAPRFilePoolp = new LLVolatileAPRPool() ;
	}
}

LLLFSThread::~LLLFSThread()
{
	// ~LLQueuedThread() will be called here
}

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

LLLFSThread::handle_t LLLFSThread::read(const std::string& filename,	/* Flawfinder: ignore */ 
										U8* buffer, S32 offset, S32 numbytes,
										Responder* responder, U32 priority)
{
	handle_t handle = generateHandle();

	if (priority == 0) priority = PRIORITY_NORMAL | priorityCounter();
	else if (priority < PRIORITY_LOW) priority |= PRIORITY_LOW; // All reads are at least PRIORITY_LOW

	Request* req = new Request(this, handle, priority,
							   FILE_READ, filename,
							   buffer, offset, numbytes,
							   responder);

	bool res = addRequest(req);
	if (!res)
	{
		llerrs << "LLLFSThread::read called after LLLFSThread::cleanupClass()" << llendl;
	}

	return handle;
}

LLLFSThread::handle_t LLLFSThread::write(const std::string& filename,
										 U8* buffer, S32 offset, S32 numbytes,
										 Responder* responder, U32 priority)
{
	handle_t handle = generateHandle();

	if (priority == 0) priority = PRIORITY_LOW | priorityCounter();
	
	Request* req = new Request(this, handle, priority,
							   FILE_WRITE, filename,
							   buffer, offset, numbytes,
							   responder);

	bool res = addRequest(req);
	if (!res)
	{
		llerrs << "LLLFSThread::read called after LLLFSThread::cleanupClass()" << llendl;
	}
	
	return handle;
}

//============================================================================

LLLFSThread::Request::Request(LLLFSThread* thread,
							  handle_t handle, U32 priority,
							  operation_t op, const std::string& filename,
							  U8* buffer, S32 offset, S32 numbytes,
							  Responder* responder) :
	QueuedRequest(handle, priority, FLAG_AUTO_COMPLETE),
	mThread(thread),
	mOperation(op),
	mFileName(filename),
	mBuffer(buffer),
	mOffset(offset),
	mBytes(numbytes),
	mBytesRead(0),
	mResponder(responder)
{
	if (numbytes <= 0)
	{
		llwarns << "LLLFSThread: Request with numbytes = " << numbytes << llendl;
	}
}

LLLFSThread::Request::~Request()
{
}

// virtual, called from own thread
void LLLFSThread::Request::finishRequest(bool completed)
{
	if (mResponder.notNull())
	{
		mResponder->completed(completed ? mBytesRead : 0);
		mResponder = NULL;
	}
}

void LLLFSThread::Request::deleteRequest()
{
	if (getStatus() == STATUS_QUEUED)
	{
		llerrs << "Attempt to delete a queued LLLFSThread::Request!" << llendl;
	}	
	if (mResponder.notNull())
	{
		mResponder->completed(0);
		mResponder = NULL;
	}
	LLQueuedThread::QueuedRequest::deleteRequest();
}

bool LLLFSThread::Request::processRequest()
{
	bool complete = false;
	if (mOperation ==  FILE_READ)
	{
		llassert(mOffset >= 0);
		LLAPRFile infile ; // auto-closes
		infile.open(mFileName, LL_APR_RB, mThread->getLocalAPRFilePool());
		if (!infile.getFileHandle())
		{
			llwarns << "LLLFS: Unable to read file: " << mFileName << llendl;
			mBytesRead = 0; // fail
			return true;
		}
		S32 off;
		if (mOffset < 0)
			off = infile.seek(APR_END, 0);
		else
			off = infile.seek(APR_SET, mOffset);
		llassert_always(off >= 0);
		mBytesRead = infile.read(mBuffer, mBytes );
		complete = true;
// 		llinfos << "LLLFSThread::READ:" << mFileName << " Bytes: " << mBytesRead << llendl;
	}
	else if (mOperation ==  FILE_WRITE)
	{
		apr_int32_t flags = APR_CREATE|APR_WRITE|APR_BINARY;
		if (mOffset < 0)
			flags |= APR_APPEND;
		LLAPRFile outfile ; // auto-closes
		outfile.open(mFileName, flags, mThread->getLocalAPRFilePool());
		if (!outfile.getFileHandle())
		{
			llwarns << "LLLFS: Unable to write file: " << mFileName << llendl;
			mBytesRead = 0; // fail
			return true;
		}
		if (mOffset >= 0)
		{
			S32 seek = outfile.seek(APR_SET, mOffset);
			if (seek < 0)
			{
				llwarns << "LLLFS: Unable to write file (seek failed): " << mFileName << llendl;
				mBytesRead = 0; // fail
				return true;
			}
		}
		mBytesRead = outfile.write(mBuffer, mBytes );
		complete = true;
// 		llinfos << "LLLFSThread::WRITE:" << mFileName << " Bytes: " << mBytesRead << "/" << mBytes << " Offset:" << mOffset << llendl;
	}
	else
	{
		llerrs << "LLLFSThread::unknown operation: " << (S32)mOperation << llendl;
	}
	return complete;
}

//============================================================================

LLLFSThread::Responder::~Responder()
{
}

//============================================================================