/** 
 * @file llxfer_file.cpp
 * @brief implementation of LLXfer_File class for a single xfer (file)
 *
 * $LicenseInfo:firstyear=2001&license=viewergpl$
 * 
 * Copyright (c) 2001-2008, 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"

#if !LL_WINDOWS
#include <errno.h>
#include <unistd.h>
#endif

#include "llxfer_file.h"
#include "lluuid.h"
#include "llerror.h"
#include "llmath.h"
#include "llstring.h"
#include "lldir.h"

// size of chunks read from/written to disk
const U32 LL_MAX_XFER_FILE_BUFFER = 65536;

// local function to copy a file
S32 copy_file(const std::string& from, const std::string& to);

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

LLXfer_File::LLXfer_File (S32 chunk_size)
: LLXfer(chunk_size)
{
	init(LLStringUtil::null, FALSE, chunk_size);
}

LLXfer_File::LLXfer_File (const std::string& local_filename, BOOL delete_local_on_completion, S32 chunk_size)
: LLXfer(chunk_size)
{
	init(local_filename, delete_local_on_completion, chunk_size);
}

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

LLXfer_File::~LLXfer_File ()
{
	cleanup();
}

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

void LLXfer_File::init (const std::string& local_filename, BOOL delete_local_on_completion, S32 chunk_size)
{

	mFp = NULL;
	mLocalFilename.clear();
	mRemoteFilename.clear();
	mRemotePath = LL_PATH_NONE;
	mTempFilename.clear();
	mDeleteLocalOnCompletion = FALSE;
	mDeleteRemoteOnCompletion = FALSE;

	if (!local_filename.empty())
	{
		mLocalFilename =  local_filename.substr(0,LL_MAX_PATH-1);

		// You can only automatically delete .tmp file as a safeguard against nasty messages.
		std::string exten = mLocalFilename.substr(mLocalFilename.length()-4, 4);
		mDeleteLocalOnCompletion = (delete_local_on_completion && exten == ".tmp");
	}
}
	
///////////////////////////////////////////////////////////

void LLXfer_File::cleanup ()
{
	if (mFp)
	{
		fclose(mFp);
		mFp = NULL;
	}

	LLFile::remove(mTempFilename);

	if (mDeleteLocalOnCompletion)
	{
		lldebugs << "Removing file: " << mLocalFilename << llendl;
		LLFile::remove(mLocalFilename);
	}
	else
	{
		lldebugs << "Keeping local file: " << mLocalFilename << llendl;
	}

	LLXfer::cleanup();
}

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

S32 LLXfer_File::initializeRequest(U64 xfer_id,
				   const std::string& local_filename,
				   const std::string& remote_filename,
				   ELLPath remote_path,
				   const LLHost& remote_host,
				   BOOL delete_remote_on_completion,
				   void (*callback)(void**,S32,LLExtStat),
				   void** user_data)
{
 	S32 retval = 0;  // presume success
	
	mID = xfer_id;
	mLocalFilename = local_filename;
	mRemoteFilename = remote_filename;
	mRemotePath = remote_path;
	mRemoteHost = remote_host;
	mDeleteRemoteOnCompletion = delete_remote_on_completion;

	mTempFilename = gDirUtilp->getTempFilename();

	mCallback = callback;
	mCallbackDataHandle = user_data;
	mCallbackResult = LL_ERR_NOERR;

	llinfos << "Requesting xfer from " << remote_host << " for file: " << mLocalFilename << llendl;

	if (mBuffer)
	{
		delete(mBuffer);
		mBuffer = NULL;
	}

	mBuffer = new char[LL_MAX_XFER_FILE_BUFFER];
	mBufferLength = 0;

	mPacketNum = 0;

 	mStatus = e_LL_XFER_PENDING;
	return retval;
}

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

S32 LLXfer_File::startDownload()
{
 	S32 retval = 0;  // presume success
	mFp = LLFile::fopen(mTempFilename,"w+b");		/* Flawfinder : ignore */
	if (mFp)
	{
		fclose(mFp);
		mFp = NULL;

		gMessageSystem->newMessageFast(_PREHASH_RequestXfer);
		gMessageSystem->nextBlockFast(_PREHASH_XferID);
		gMessageSystem->addU64Fast(_PREHASH_ID, mID);
		gMessageSystem->addStringFast(_PREHASH_Filename, mRemoteFilename);
		gMessageSystem->addU8("FilePath", (U8) mRemotePath);
		gMessageSystem->addBOOL("DeleteOnCompletion", mDeleteRemoteOnCompletion);
		gMessageSystem->addBOOL("UseBigPackets", BOOL(mChunkSize == LL_XFER_LARGE_PAYLOAD));
		gMessageSystem->addUUIDFast(_PREHASH_VFileID, LLUUID::null);
		gMessageSystem->addS16Fast(_PREHASH_VFileType, -1);
	
		gMessageSystem->sendReliable(mRemoteHost);		
		mStatus = e_LL_XFER_IN_PROGRESS;
	}
	else
	{
		llwarns << "Couldn't create file to be received!" << llendl;
		retval = -1;
	}

	return (retval);
}

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

S32 LLXfer_File::startSend (U64 xfer_id, const LLHost &remote_host)
{
	S32 retval = LL_ERR_NOERR;  // presume success
	
    mRemoteHost = remote_host;
	mID = xfer_id;
   	mPacketNum = -1;

//	cout << "Sending file: " << mLocalFilename << endl;

	delete [] mBuffer;
	mBuffer = new char[LL_MAX_XFER_FILE_BUFFER];

	mBufferLength = 0;
	mBufferStartOffset = 0;	
	
	mFp = LLFile::fopen(mLocalFilename,"rb");		/* Flawfinder : ignore */
	if (mFp)
	{
		fseek(mFp,0,SEEK_END);
	
		S32 file_size = ftell(mFp);
		if (file_size <= 0)
		{
			return LL_ERR_FILE_EMPTY;
		}
		setXferSize(file_size);

		fseek(mFp,0,SEEK_SET);
	}
	else
	{
		llinfos << "Warning: " << mLocalFilename << " not found." << llendl;
		return (LL_ERR_FILE_NOT_FOUND);
	}

	mStatus = e_LL_XFER_PENDING;

	return (retval);
}

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

S32 LLXfer_File::getMaxBufferSize ()
{
	return(LL_MAX_XFER_FILE_BUFFER);
}

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

S32 LLXfer_File::suck(S32 start_position)
{
	S32 retval = 0;

	if (mFp)
	{
		// grab a buffer from the right place in the file
		fseek (mFp,start_position,SEEK_SET);
		
		mBufferLength = (U32)fread(mBuffer,1,LL_MAX_XFER_FILE_BUFFER,mFp);
		mBufferStartOffset = start_position;
			
		if (feof(mFp))
		{
			mBufferContainsEOF = TRUE;
		}
		else
		{
			mBufferContainsEOF = FALSE;
		}		
	}
	else
	{
		retval = -1;
	}

	return (retval);
}

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

S32 LLXfer_File::flush()
{
	S32 retval = 0;
	if (mBufferLength)
	{
		if (mFp)
		{
			llerrs << "Overwriting open file pointer!" << llendl;
		}
		mFp = LLFile::fopen(mTempFilename,"a+b");		/* Flawfinder : ignore */

		if (mFp)
		{
			if (fwrite(mBuffer,1,mBufferLength,mFp) != mBufferLength)
			{
				llwarns << "Short write" << llendl;
			}
			
//			llinfos << "******* wrote " << mBufferLength << " bytes of file xfer" << llendl;
			fclose(mFp);
			mFp = NULL;
			
			mBufferLength = 0;
		}
		else
		{
			llwarns << "LLXfer_File::flush() unable to open " << mTempFilename << " for writing!" << llendl;
			retval = LL_ERR_CANNOT_OPEN_FILE;
		}
	}
	return (retval);
}

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

S32 LLXfer_File::processEOF()
{
	S32 retval = 0;
	mStatus = e_LL_XFER_COMPLETE;

	S32 flushval = flush();

	// If we have no other errors, our error becomes the error generated by
	// flush.
	if (!mCallbackResult)
	{
		mCallbackResult = flushval;
	}

	LLFile::remove(mLocalFilename);

	if (!mCallbackResult)
	{
		if (LLFile::rename(mTempFilename,mLocalFilename))
		{
#if !LL_WINDOWS
			S32 error_number = errno;
			llinfos << "Rename failure (" << error_number << ") - "
					<< mTempFilename << " to " << mLocalFilename << llendl;
			if(EXDEV == error_number)
			{
				if(copy_file(mTempFilename, mLocalFilename) == 0)
				{
					llinfos << "Rename across mounts; copying+unlinking the file instead." << llendl;
					unlink(mTempFilename.c_str());
				}
				else
				{
					llwarns << "Copy failure - " << mTempFilename << " to "
							<< mLocalFilename << llendl;
				}
			}
			else
			{
				//LLFILE* fp = LLFile::fopen(mTempFilename, "r");
				//llwarns << "File " << mTempFilename << " does "
				//		<< (!fp ? "not" : "" ) << " exit." << llendl;
				//if(fp) fclose(fp);
				//fp = LLFile::fopen(mLocalFilename, "r");
				//llwarns << "File " << mLocalFilename << " does "
				//		<< (!fp ? "not" : "" ) << " exit." << llendl;
				//if(fp) fclose(fp);
				llwarns << "Rename fatally failed, can only handle EXDEV ("
						<< EXDEV << ")" << llendl;
			}
#else
			llwarns << "Rename failure - " << mTempFilename << " to "
					<< mLocalFilename << llendl;
#endif
		}
	}

	if (mFp)
	{
		fclose(mFp);
		mFp = NULL;
	}

	retval = LLXfer::processEOF();

	return(retval);
}

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

BOOL LLXfer_File::matchesLocalFilename(const std::string& filename) 
{
	return (filename == mLocalFilename);
}

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

BOOL LLXfer_File::matchesRemoteFilename(const std::string& filename, ELLPath remote_path) 
{
	return ((filename == mRemoteFilename) && (remote_path == mRemotePath));
}


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

std::string LLXfer_File::getFileName() 
{
	return mLocalFilename;
}

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

// hacky - doesn't matter what this is
// as long as it's different from the other classes
U32 LLXfer_File::getXferTypeTag()
{
	return LLXfer::XFER_FILE;
}

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

#if !LL_WINDOWS

// This is really close to, but not quite a general purpose copy
// function. It does not really spam enough information, but is useful
// for this cpp file, because this should never be called in a
// production environment.
S32 copy_file(const std::string& from, const std::string& to)
{
	S32 rv = 0;
	LLFILE* in = LLFile::fopen(from, "rb");	/*Flawfinder: ignore*/
	LLFILE* out = LLFile::fopen(to, "wb");	/*Flawfinder: ignore*/
	if(in && out)
	{
		S32 read = 0;
		const S32 COPY_BUFFER_SIZE = 16384;
		U8 buffer[COPY_BUFFER_SIZE];
		while(((read = fread(buffer, 1, sizeof(buffer), in)) > 0)
			  && (fwrite(buffer, 1, read, out) == (U32)read));		/* Flawfinder : ignore */
		if(ferror(in) || ferror(out)) rv = -2;
	}
	else
	{
		rv = -1;
	}
	if(in) fclose(in);
	if(out) fclose(out);
	return rv;
}
#endif
