/*
gpiTransfer.c
GameSpy Presence SDK 
Dan "Mr. Pants" Schoenblum

Copyright 1999-2001 GameSpy Industries, Inc

18002 Skypark Circle
Irvine, California 92614
949.798.4200 (Tel)
949.798.4299 (Fax)
devsupport@gamespy.com

***********************************************************************
Please see the GameSpy Presence SDK documentation for more information
**********************************************************************/

//INCLUDES
//////////
#include <stdlib.h>
#ifdef _WIN32
#include <sys/stat.h>
#endif
#include <string.h>
#include "gpi.h"

#define GPI_TRANSFER_VERSION      1
#define GPI_PEER_TIMEOUT_TIME     (1 * 60000)
#define GPI_KEEPALIVE_TIME        (4 * 60000)

//#define GPI_ACKNOWLEDGED_WINDOW   (100000 * 1024)
#define GPI_DATA_SIZE             (8 * 1024)
//#define GPI_CONFIRM_FILES

//FUNCTIONS
///////////
#ifndef NOFILE
static void gpiTransferFree(void * elem)
{
	GPITransfer * transfer = (GPITransfer *)elem;

	if(transfer->message)
		freeclear(transfer->message);

	if(transfer->baseDirectory)
		gsifree(transfer->baseDirectory);

	if(transfer->files)
	{
		ArrayFree(transfer->files);
		transfer->files = NULL;
	}
}

static int gpiTransferCompare(const void * elem1, const void * elem2)
{
	GPITransfer * transfer1 = (GPITransfer *)elem1;
	GPITransfer * transfer2 = (GPITransfer *)elem2;

	return (transfer1->localID - transfer2->localID);
}

static void gpiFreeFile(void * elem)
{
	GPIFile * file = (GPIFile *)elem;
	gsifree(file->path);
	gsifree(file->name);

#ifdef GSI_UNICODE
	gsifree(file->path_W);
	gsifree(file->name_W);
#endif
}

static GPResult
gpiFinishTransferMessage(
  GPConnection * connection,
  GPITransfer * transfer,
  const char * message,
  int len
)
{
	GPResult result = gpiPeerFinishTransferMessage(connection, transfer->peer, message, len);

	transfer->lastSend = current_time();

	return result;
}

GPResult gpiInitTransfers(
  GPConnection * connection
)
{
	GPIConnection * iconnection = (GPIConnection*)*connection;

	iconnection->transfers = ArrayNew(sizeof(GPITransfer), 0, gpiTransferFree);
	if(!iconnection->transfers)
		Error(connection, GP_MEMORY_ERROR, "Out of memory.");

	return GP_NO_ERROR;
}

void gpiCleanupTransfers(
  GPConnection * connection
)
{
	GPIConnection * iconnection = (GPIConnection*)*connection;

	// Free the transfers.
	//////////////////////
	if(iconnection->transfers)
	{
		ArrayFree(iconnection->transfers);
		iconnection->transfers = NULL;
	}
}

static GPResult gpiNewTransfer
(
  GPConnection * connection,
  GPITransfer ** transfer,
  GPProfile profile,
  GPIBool sender
)
{
	GPIConnection * iconnection = (GPIConnection*)*connection;
	GPITransfer transferTemp;

	// Fill in the object.
	//////////////////////
	memset(&transferTemp, 0, sizeof(GPITransfer));
	transferTemp.files = ArrayNew(sizeof(GPIFile), 0, gpiFreeFile);
	if(!transferTemp.files)
		Error(connection, GP_MEMORY_ERROR, "Out of memory.");
	transferTemp.localID = iconnection->nextTransferID++;
	transferTemp.sender = sender;
	transferTemp.profile = profile;
	transferTemp.throttle = -1;
	transferTemp.currentFile = -1;

	// Add it.
	//////////
	ArrayAppend(iconnection->transfers, &transferTemp);

	// Get it.
	//////////
	*transfer = (GPITransfer *)ArrayNth(iconnection->transfers, ArrayLength(iconnection->transfers) - 1);

	return GP_NO_ERROR;
}

GPResult gpiNewSenderTransfer
(
  GPConnection * connection,
  GPITransfer ** transfer,
  GPProfile profile
)
{
	GPITransfer * pTransfer;
	GPIConnection * iconnection = (GPIConnection*)*connection;
	unsigned long now;

	CHECK_RESULT(gpiNewTransfer(connection, transfer, profile, GPITrue));

	now = current_time();

	pTransfer = *transfer;
	pTransfer->state = GPITransferPinging;
	pTransfer->transferID.profileid = iconnection->profileid;
	pTransfer->transferID.count = pTransfer->localID;
	pTransfer->transferID.time = (unsigned int)now;
	pTransfer->lastSend = now;

	return GP_NO_ERROR;
}

static GPResult gpiNewReceiverTransfer
(
  GPConnection * connection,
  GPITransfer ** transfer,
  GPProfile profile,
  GPITransferID * transferID
)
{
#ifdef WIN32
	char buffer[FILENAME_MAX];
#endif
	GPITransfer * pTransfer;

	CHECK_RESULT(gpiNewTransfer(connection, transfer, profile, GPIFalse));

	pTransfer = *transfer;
	pTransfer->state = GPITransferWaiting;
	memcpy(&pTransfer->transferID, transferID, sizeof(GPITransferID));
#ifdef WIN32
	if(GetTempPath(sizeof(buffer), buffer) != 0)
		pTransfer->baseDirectory = goastrdup(buffer);
#endif

	return GP_NO_ERROR;
}

void gpiFreeTransfer
(
  GPConnection * connection,
  GPITransfer * transfer
)
{
	GPIConnection * iconnection = (GPIConnection*)*connection;
	int pos;

	// Find the transfer.
	/////////////////////
	pos = ArraySearch(iconnection->transfers, transfer, gpiTransferCompare, 0, 0);
	assert(pos != NOT_FOUND);
	if(pos == NOT_FOUND)
		return;

	// Remove it.
	/////////////
	ArrayDeleteAt(iconnection->transfers, pos);
}

void gpiCancelTransfer
(
  GPConnection * connection,
  GPITransfer * transfer
)
{
	// Send the cancel message.
	///////////////////////////
	if(transfer->peer)
	{
		// Start the message.
		/////////////////////
		if(gpiPeerStartTransferMessage(connection, transfer->peer, GPI_BM_FILE_TRANSFER_CANCEL, (GPITransferID_st)&transfer->transferID) == GP_NO_ERROR)
		{
			// Finish the message.
			//////////////////////
			gpiFinishTransferMessage(connection, transfer, NULL, 0);
		}
	}
}

void gpiTransferError
(
  GPConnection * connection,
  const GPITransfer * transfer
)
{
	GPTransferCallbackArg * arg;
	GPIConnection * iconnection = (GPIConnection*)*connection;

	// Call the callback.
	/////////////////////
	arg = (GPTransferCallbackArg *)gsimalloc(sizeof(GPTransferCallbackArg));
	if(arg)
	{
		memset(arg, 0, sizeof(GPTransferCallbackArg));
		arg->transfer = transfer->localID;
		arg->type = GP_TRANSFER_ERROR;
		gpiAddCallback(connection, iconnection->callbacks[GPI_TRANSFER_CALLBACK], arg, NULL, GPI_ADD_TRANSFER_CALLBACK);
	}
}

GPIFile * gpiAddFileToTransfer
(
  GPITransfer * transfer,
  const char * path,
  const char * name
)
{
	GPIFile file;
	char * str;

	assert(name && name[0]);

	memset(&file, 0, sizeof(GPIFile));

	// Copy the path.
	/////////////////
	if(path)
	{
		file.path = goastrdup(path);
		if(!file.path)
			return NULL;
	}

	// Copy the name.
	/////////////////
	file.name = goastrdup(name);
	if(!file.name)
	{
		gsifree(file.path);
		return NULL;
	}

	// Change all slashes to backslashes.
	/////////////////////////////////////
	while((str = strchr(file.name, '\\')) != NULL)
		*str = '/';

	// Check for a directory.
	/////////////////////////
	if(name[strlen(name) - 1] == '/')
		file.flags = GPI_FILE_DIRECTORY;

	// No size yet.
	///////////////
	file.size = -1;

	// Add it to the list of files.
	///////////////////////////////
	ArrayAppend(transfer->files, &file);

#ifdef GSI_UNICODE
	// Copy the unicode versions
	file.name_W = UTF8ToUCS2StringAlloc(file.name);
	file.path_W = UTF8ToUCS2StringAlloc(file.path);
#endif

	// Return the file.
	///////////////////
	return (GPIFile *)ArrayNth(transfer->files, ArrayLength(transfer->files) - 1);
}

static GPResult gpiSendTransferRequest
(
  GPConnection * connection,
  GPITransfer * transfer
)
{
	char buffer[32];
	GPIFile * file;
	int i;
	int num;

	// Get the number of files.
	///////////////////////////
	num = ArrayLength(transfer->files);

	// Start the message.
	/////////////////////
	CHECK_RESULT(gpiPeerStartTransferMessage(connection, transfer->peer, GPI_BM_FILE_SEND_REQUEST, (GPITransferID_st)&transfer->transferID));

	// Add the rest of the headers.
	///////////////////////////////
	sprintf(buffer, "\\version\\%d\\num\\%d", GPI_TRANSFER_VERSION, num);
	CHECK_RESULT(gpiSendOrBufferString(connection, transfer->peer, buffer));
	for(i = 0 ; i < num ; i++)
	{
		file = (GPIFile *)ArrayNth(transfer->files, i);

		sprintf(buffer, "\\name%d\\", i);
		CHECK_RESULT(gpiSendOrBufferString(connection, transfer->peer, buffer));
		CHECK_RESULT(gpiSendOrBufferString(connection, transfer->peer, file->name));

		sprintf(buffer, "\\size%d\\", i);
		CHECK_RESULT(gpiSendOrBufferString(connection, transfer->peer, buffer));
		CHECK_RESULT(gpiSendOrBufferInt(connection, transfer->peer, file->size));

		sprintf(buffer, "\\mtime%d\\", i);
		CHECK_RESULT(gpiSendOrBufferString(connection, transfer->peer, buffer));
		CHECK_RESULT(gpiSendOrBufferUInt(connection, transfer->peer, file->modTime));
	}

	// Finish the message.
	//////////////////////
	CHECK_RESULT(gpiFinishTransferMessage(connection, transfer, transfer->message, -1));

	return GP_NO_ERROR;
}

static GPITransfer * gpiFindTransferByTransferID
(
  GPConnection * connection,
  GPITransferID * transferID
)
{
	GPIConnection * iconnection = (GPIConnection*)*connection;
	int i;
	int num;
	GPITransfer * transfer;

	num = ArrayLength(iconnection->transfers);
	for(i = 0 ; i < num ; i++)
	{
		transfer = (GPITransfer *)ArrayNth(iconnection->transfers, i);
		if(memcmp(&transfer->transferID, transferID, sizeof(GPITransferID)) == 0)
			return transfer;
	}

	return NULL;
}

GPITransfer * gpiFindTransferByLocalID
(
  GPConnection * connection,
  int localID
)
{
	GPIConnection * iconnection = (GPIConnection*)*connection;
	int i;
	int num;
	GPITransfer * transfer;

	num = ArrayLength(iconnection->transfers);
	for(i = 0 ; i < num ; i++)
	{
		transfer = (GPITransfer *)ArrayNth(iconnection->transfers, i);
		if(transfer->localID == localID)
			return transfer;
	}

	return NULL;
}

int gpiGetTransferLocalIDByIndex
(
  GPConnection * connection,
  int index
)
{
	GPIConnection * iconnection = (GPIConnection*)*connection;
	GPITransfer * transfer;
	int num;

	num = ArrayLength(iconnection->transfers);
	assert(index >= 0);
	assert(index < num);
	if((index < 0) || (index >= num))
		return -1;

	transfer = (GPITransfer *)ArrayNth(iconnection->transfers, index);
	assert(transfer);
	if(!transfer)
		return -1;

	return transfer->localID;
}

void gpiSkipFile
(
  GPConnection * connection,
  GPITransfer * transfer,
  int file,
  int reason
)
{
	char buffer[32];

	if(gpiPeerStartTransferMessage(connection, transfer->peer, GP_FILE_SKIP, (GPITransferID_st)&transfer->transferID) != GP_NO_ERROR)
		return;

	sprintf(buffer, "\\file\\%d\\reason\\%d", file, reason);
	gpiSendOrBufferString(connection, transfer->peer, buffer);
	gpiFinishTransferMessage(connection, transfer, NULL, 0);
}

void gpiSkipCurrentFile
(
  GPConnection * connection,
  GPITransfer * transfer,
  int reason
)
{
	gpiSkipFile(connection, transfer, transfer->currentFile, reason);

	transfer->currentFile++;
}

static GPIBool gpiHandleSendRequest
(
  GPConnection * connection,
  GPIPeer * peer,
  GPITransferID * transferID,
  const char * headers,
  const char * buffer,
  int bufferLen
)
{
	GPIConnection * iconnection = (GPIConnection*)*connection;
	GPITransfer * transfer;
	GPIFile * file;
	GPTransferCallbackArg * arg;
	char key[16];
	char intValue[16];
	int version;
	int numFiles;
	char name[FILENAME_MAX];
	int size;
	unsigned int mtime;
	int i;
	size_t len;
	int totalSize = 0;

	// If we don't have a callback, we're not accepting requests.
	/////////////////////////////////////////////////////////////
	if(!iconnection->callbacks[GPI_TRANSFER_CALLBACK].callback)
		return GPIFalse;

	// Check the version.
	/////////////////////
	if(!gpiValueForKey(headers, "\\version\\", intValue, sizeof(intValue)))
		return GPIFalse;
	version = atoi(intValue);
	if(version < 1)
		return GPIFalse;

	// Get the number of files.
	///////////////////////////
	if(!gpiValueForKey(headers, "\\num\\", intValue, sizeof(intValue)))
		return GPIFalse;
	numFiles = atoi(intValue);
	if(numFiles < 1)
		return GPIFalse;

	// Create the transfer object.
	//////////////////////////////
	if(gpiNewReceiverTransfer(connection, &transfer, peer->profile, transferID) != GP_NO_ERROR)
		return GPIFalse;

	// Set the peer.
	////////////////
	transfer->peer = peer;

	// Parse the file list.
	///////////////////////
	for(i = 0 ; i < numFiles ; i++)
	{
		sprintf(key, "\\name%d\\", i);
		if(!gpiValueForKey(headers, key, name, sizeof(name)))
		{
			gpiFreeTransfer(connection, transfer);
			return GPIFalse;
		}
		len = strlen(name);
		if(strstr(name, "//") || strstr(name, "./") || (name[len - 1] == '.') || (name[0] == '/') || (strcspn(name, ":*?\"<>|\n") != len))
		{
			gpiFreeTransfer(connection, transfer);
			return GPIFalse;
		}

		sprintf(key, "\\size%d\\", i);
		if(!gpiValueForKey(headers, key, intValue, sizeof(intValue)))
		{
			gpiFreeTransfer(connection, transfer);
			return GPIFalse;
		}
		size = atoi(intValue);
		if(size < 0)
		{
			gpiFreeTransfer(connection, transfer);
			return GPIFalse;
		}
		totalSize += size;

		sprintf(key, "\\mtime%d\\", i);
		if(!gpiValueForKey(headers, key, intValue, sizeof(intValue)))
		{
			gpiFreeTransfer(connection, transfer);
			return GPIFalse;
		}
		mtime = (unsigned int)strtoul(intValue, NULL, 10);

		file = gpiAddFileToTransfer(transfer, NULL, name);
		if(!file)
		{
			gpiFreeTransfer(connection, transfer);
			return GPIFalse;
		}
		file->size = size;
		file->modTime = mtime;
	}

	// Call the callback.
	/////////////////////
	arg = (GPTransferCallbackArg *)gsimalloc(sizeof(GPTransferCallbackArg));
	if(!arg)
	{
		gpiFreeTransfer(connection, transfer);
		return GPIFalse;
	}
	memset(arg, 0, sizeof(GPTransferCallbackArg));
	arg->transfer = transfer->localID;
	arg->type = GP_TRANSFER_SEND_REQUEST;
	arg->num = numFiles;
#ifndef GSI_UNICODE
	arg->message = goastrdup(buffer);
#else
	arg->message = UTF8ToUCS2StringAlloc(buffer);
#endif
	{
		GPResult aResult = gpiAddCallback(connection, iconnection->callbacks[GPI_TRANSFER_CALLBACK], arg, NULL, GPI_ADD_TRANSFER_CALLBACK);
		if (aResult != GP_NO_ERROR)
			return GPIFalse;
	}

	// Store the total size.
	////////////////////////
	transfer->totalSize = totalSize;

	return GPITrue;
	
	GSI_UNUSED(bufferLen);
}

static GPIBool gpiHandleSendReply
(
  GPConnection * connection,
  GPITransfer * transfer,
  const char * headers,
  const char * buffer,
  int bufferLen
)
{
	GPIConnection * iconnection = (GPIConnection*)*connection;
	GPTransferCallbackArg * arg;
	char intValue[16];
	int version;
	int result;

	if(!transfer->sender)
		return GPIFalse;

	// Check the version.
	////////////////////
	if(!gpiValueForKey(headers, "\\version\\", intValue, sizeof(intValue)))
		return GPIFalse;
	version = atoi(intValue);
	if(version < 1)
		return GPIFalse;

	// Get the result.
	//////////////////
	if(!gpiValueForKey(headers, "\\result\\", intValue, sizeof(intValue)))
		return GPIFalse;
	result = atoi(intValue);

	// Call the callback.
	/////////////////////
	arg = (GPTransferCallbackArg *)gsimalloc(sizeof(GPTransferCallbackArg));
	if(arg)
	{
		memset(arg, 0, sizeof(GPTransferCallbackArg));
		arg->transfer = transfer->localID;
		if(result == GPI_ACCEPTED)
			arg->type = GP_TRANSFER_ACCEPTED;
		else if(result == GPI_REJECTED)
			arg->type = GP_TRANSFER_REJECTED;
		else
			arg->type = GP_TRANSFER_NOT_ACCEPTING;

#ifndef GSI_UNICODE
		arg->message = goastrdup(buffer);
#else
		arg->message = UTF8ToUCS2StringAlloc(buffer);
#endif
		gpiAddCallback(connection, iconnection->callbacks[GPI_TRANSFER_CALLBACK], arg, NULL, GPI_ADD_TRANSFER_CALLBACK);
	}

	// Update transfer state if accepted.
	/////////////////////////////////////
	if(result == GPI_ACCEPTED)
	{
		transfer->state = GPITransferTransferring;
		transfer->currentFile = 0;
	}

	return GPITrue;

	GSI_UNUSED(bufferLen);
}

static GPIBool gpiHandleBegin
(
  GPConnection * connection,
  GPITransfer * transfer,
  const char * headers
)
{
	GPIConnection * iconnection = (GPIConnection*)*connection;
	GPTransferCallbackArg * arg;
	GPIFile * file;
	char intValue[16];
	int fileIndex;
	int size;
	unsigned int mtime;
	char buffer[FILENAME_MAX];
	int count;

	if(transfer->sender)
		return GPIFalse;

	// Get the file.
	////////////////
	if(!gpiValueForKey(headers, "\\file\\", intValue, sizeof(intValue)))
		return GPIFalse;
	fileIndex = atoi(intValue);
	if((fileIndex < 0) || (fileIndex >= ArrayLength(transfer->files)))
		return GPIFalse;
	if(fileIndex != transfer->currentFile)
		return GPIFalse;
	file = (GPIFile *)ArrayNth(transfer->files, fileIndex);

	// Is this a directory?
	///////////////////////
	if(file->flags & GPI_FILE_DIRECTORY)
		return GPIFalse;

	// Get the size.
	////////////////
	if(!gpiValueForKey(headers, "\\size\\", intValue, sizeof(intValue)))
		return GPIFalse;
	size = atoi(intValue);
	if(size < 0)
		return GPIFalse;

	// Update the total size.
	/////////////////////////
	transfer->totalSize -= file->size;
	transfer->totalSize += size;

	// Get the mod time.
	////////////////////
	if(!gpiValueForKey(headers, "\\mtime\\", intValue, sizeof(intValue)))
		return GPIFalse;
	mtime = (unsigned int)strtoul(intValue, NULL, 10);

	// Set file stuff.
	//////////////////
	MD5Init(&file->md5);
	file->modTime = mtime;
	file->size = size;

	// Setup the temp path.
	///////////////////////
	count = 0;
	do
	{
		sprintf(buffer, "%sgpt_%d_%d_%d.gpt", transfer->baseDirectory, transfer->localID, fileIndex, rand());
		file->file = fopen(buffer, "wb");
		count++;
	}
	while(!file->file && (count < 5));

	// Copy off the path.
	/////////////////////
	if(file->file)
	{
		file->path = goastrdup(buffer);
		if(!file->path)
			return GPIFalse;

#ifdef GSI_UNICODE
		file->path_W = UTF8ToUCS2StringAlloc(file->path);
#endif
	}

	// Call the callback.
	/////////////////////
	arg = (GPTransferCallbackArg *)gsimalloc(sizeof(GPTransferCallbackArg));
	if(arg)
	{
		memset(arg, 0, sizeof(GPTransferCallbackArg));
		arg->transfer = transfer->localID;
		arg->index = fileIndex;
		if(file->file)
		{
			arg->type = GP_FILE_BEGIN;
		}
		else
		{
			arg->type = GP_FILE_FAILED;
			arg->num = GP_FILE_WRITE_ERROR;
		}
		gpiAddCallback(connection, iconnection->callbacks[GPI_TRANSFER_CALLBACK], arg, NULL, GPI_ADD_TRANSFER_CALLBACK);
	}

	// Did it fail?
	///////////////
	if(!file->file)
	{
		gpiSkipCurrentFile(connection, transfer, GPI_SKIP_WRITE_ERROR);
		file->flags |= GPI_FILE_FAILED;
		file->reason = GP_FILE_WRITE_ERROR;
	}

	return GPITrue;
}

static GPIBool gpiHandleEnd
(
  GPConnection * connection,
  GPITransfer * transfer,
  const char * headers
)
{
	GPIConnection * iconnection = (GPIConnection*)*connection;
	GPTransferCallbackArg * arg;
	GPIFile * file;
	GPIBool md5Failed = GPITrue;
	unsigned char rawMD5[16];
	char localMD5[33];
	char remoteMD5[33];
	char intValue[16];
	int fileIndex;

	if(transfer->currentFile == -1)
		return GPIFalse;

	// Check the file index.
	////////////////////////
	if(!gpiValueForKey(headers, "\\file\\", intValue, sizeof(intValue)))
		return GPIFalse;
	fileIndex = atoi(intValue);
	if((fileIndex < 0) || (fileIndex >= ArrayLength(transfer->files)))
		return GPIFalse;
	if(fileIndex != transfer->currentFile)
		return GPITrue;

	// Get the current file.
	////////////////////////
	file = (GPIFile *)ArrayNth(transfer->files, transfer->currentFile);

	// Sender?
	//////////
	if(transfer->sender)
	{
#ifdef GPI_CONFIRM_FILES
		// We should be waiting for confirmation.
		/////////////////////////////////////////
		assert(file->flags & GPI_FILE_CONFIRMING);

		// Call the callback.
		/////////////////////
		arg = (GPTransferCallbackArg *)gsimalloc(sizeof(GPTransferCallbackArg));
		if(arg)
		{
			memset(arg, 0, sizeof(GPTransferCallbackArg));
			arg->transfer = transfer->localID;
			arg->index = transfer->currentFile;
			arg->type = GP_FILE_END;
			gpiAddCallback(connection, iconnection->callbacks[GPI_TRANSFER_CALLBACK], arg, NULL, GPI_ADD_TRANSFER_CALLBACK);
		}

		// Done with the file.
		//////////////////////
		file->flags &= ~GPI_FILE_CONFIRMING;
		file->flags |= GPI_FILE_COMPLETED;
		transfer->currentFile++;
#endif
		return GPITrue;
	}

	// Is this a directory?
	///////////////////////
	if(file->flags & GPI_FILE_DIRECTORY)
	{
		// Directory completed.
		///////////////////////
		file->flags |= GPI_FILE_COMPLETED;
	}
	else
	{
		// Check the file.
		//////////////////
		assert(file->file);
		if(!file->file)
			return GPIFalse;

		// Get the remote md5.
		//////////////////////
		if(!gpiValueForKey(headers, "\\md5\\", remoteMD5, sizeof(remoteMD5)))
			return GPIFalse;

		// Get the local md5.
		/////////////////////
		MD5Final(rawMD5, &file->md5);
		MD5Print(rawMD5, localMD5);

		// Check the md5.
		/////////////////
		md5Failed = (memcmp(localMD5, remoteMD5, 32) != 0) ? GPITrue:GPIFalse;

		// Set the state.
		/////////////////
		if(md5Failed)
		{
			file->flags |= GPI_FILE_FAILED;
			file->reason = GP_FILE_DATA_ERROR;
		}
		else
			file->flags |= GPI_FILE_COMPLETED;

		// Close the file.
		//////////////////
		fclose(file->file);
		file->file = NULL;

		// If the md5 failed, remove the file.
		//////////////////////////////////////
		if(md5Failed)
			remove(file->path);

#ifdef GPI_CONFIRM_FILES
		// Send a confirmation.
		///////////////////////
		if(gpiPeerStartTransferMessage(connection, transfer->peer, GPI_BM_FILE_END, (GPITransferID_st)&transfer->transferID) != GP_NO_ERROR)
			return GPIFalse;
		gpiFinishTransferMessage(connection, transfer, NULL, 0);
#endif
	}

	// Call the callback.
	/////////////////////
	arg = (GPTransferCallbackArg *)gsimalloc(sizeof(GPTransferCallbackArg));
	if(arg)
	{
		memset(arg, 0, sizeof(GPTransferCallbackArg));
		arg->transfer = transfer->localID;
		arg->index = transfer->currentFile;
		if(file->flags & GPI_FILE_DIRECTORY)
		{
			arg->type = GP_FILE_DIRECTORY;
		}
		else if(md5Failed)
		{
			arg->type = GP_FILE_FAILED;
			arg->num = GP_FILE_DATA_ERROR;
		}
		else
		{
			arg->type = GP_FILE_END;
		}
		gpiAddCallback(connection, iconnection->callbacks[GPI_TRANSFER_CALLBACK], arg, NULL, GPI_ADD_TRANSFER_CALLBACK);
	}

	// Next file.
	/////////////
	transfer->currentFile++;

	// Done?
	////////
	if(transfer->currentFile == ArrayLength(transfer->files))
	{
		// The transfer is complete.
		////////////////////////////
		transfer->state = GPITransferComplete;

		// Call the callback.
		/////////////////////
		arg = (GPTransferCallbackArg *)gsimalloc(sizeof(GPTransferCallbackArg));
		if(arg)
		{
			memset(arg, 0, sizeof(GPTransferCallbackArg));
			arg->transfer = transfer->localID;
			arg->type = GP_TRANSFER_DONE;
			gpiAddCallback(connection, iconnection->callbacks[GPI_TRANSFER_CALLBACK], arg, NULL, GPI_ADD_TRANSFER_CALLBACK);
		}
	}

	return GPITrue;
}

static GPIBool gpiHandleData
(
  GPConnection * connection,
  GPITransfer * transfer,
  const char * headers,
  const char * buffer,
  int bufferLen
)
{
	GPIConnection * iconnection = (GPIConnection*)*connection;
	GPTransferCallbackArg * arg;
	GPIFile * file;
	GPIBool writeFailed;
	char intValue[16];
	int fileIndex;

	if(transfer->currentFile == -1)
		return GPIFalse;

	// Check the file index.
	////////////////////////
	if(!gpiValueForKey(headers, "\\file\\", intValue, sizeof(intValue)))
		return GPIFalse;
	fileIndex = atoi(intValue);
	if((fileIndex < 0) || (fileIndex >= ArrayLength(transfer->files)))
		return GPIFalse;
	if(fileIndex != transfer->currentFile)
		return GPITrue;

	// Get the current file.
	////////////////////////
	file = (GPIFile *)ArrayNth(transfer->files, transfer->currentFile);

	// Is this a directory?
	///////////////////////
	if(file->flags & GPI_FILE_DIRECTORY)
		return GPIFalse;

#ifdef GPI_ACKNOWLEDGED_WINDOW
	// Sender?
	//////////
	if(transfer->sender)
	{
		char intValue[16];

		// Get the progress.
		////////////////////
		if(!gpiValueForKey(headers, "\\pro\\", intValue, sizeof(intValue)))
			return GPIFalse;
		file->acknowledged = atoi(intValue);

		return GPITrue;
	}
#endif

	// Check the file.
	//////////////////
	assert(file->file);
	if(!file->file)
		return GPIFalse;

	gsDebugFormat(GSIDebugCat_GP, GSIDebugType_File, GSIDebugLevel_RawDump,
		"HNDLDATA(PT): %d\n", bufferLen);

	// Write the data.
	//////////////////
	writeFailed = (GPIBool)(fwrite(buffer, 1, bufferLen, file->file) != (size_t)bufferLen);
	if(writeFailed)
	{
		// Flag the errors.
		///////////////////
		file->flags |= GPI_FILE_FAILED;
		file->reason = GP_FILE_WRITE_ERROR;

		// Remove the file.
		///////////////////
		fclose(file->file);
		file->file = NULL;
		remove(file->path);
	}
	else
	{
		// Update the  MD5.
		///////////////////
		MD5Update(&file->md5, (unsigned char *)buffer, bufferLen);

		// Update the progress.
		///////////////////////
		file->progress += bufferLen;
		transfer->progress += bufferLen;

#ifdef GPI_ACKNOWLEDGED_WINDOW
		// Send an acknowledgment.
		//////////////////////////
		if(gpiPeerStartTransferMessage(connection, transfer->peer, GPI_BM_FILE_DATA, (GPITransferID_st)&transfer->transferID) != GP_NO_ERROR)
			return GPIFalse;
		gpiSendOrBufferString(connection, transfer->peer, "\\pro\\");
		gpiSendOrBufferInt(connection, transfer->peer, file->progress);
		gpiFinishTransferMessage(connection, transfer, NULL, 0);
#endif
	}

	// Call the callback.
	/////////////////////
	arg = (GPTransferCallbackArg *)gsimalloc(sizeof(GPTransferCallbackArg));
	if(arg)
	{
		memset(arg, 0, sizeof(GPTransferCallbackArg));
		arg->transfer = transfer->localID;
		arg->index = transfer->currentFile;
		if(!writeFailed)
		{
			arg->type = GP_FILE_PROGRESS;
			arg->num = file->progress;
		}
		else
		{
			arg->type = GP_FILE_FAILED;
			arg->num = GP_FILE_WRITE_ERROR;
		}
		gpiAddCallback(connection, iconnection->callbacks[GPI_TRANSFER_CALLBACK], arg, NULL, GPI_ADD_TRANSFER_CALLBACK);
	}

	// Did it fail?
	///////////////
	if(writeFailed)
	{
		// Skip the file.
		/////////////////
		gpiSkipCurrentFile(connection, transfer, GPI_SKIP_WRITE_ERROR);
	}

	return GPITrue;
}

static GPIBool gpiHandleSkip
(
  GPConnection * connection,
  GPITransfer * transfer,
  const char * headers
)
{
	GPIConnection * iconnection = (GPIConnection*)*connection;
	GPTransferCallbackArg * arg;
	GPIFile * file;
	char intValue[16];
	int fileIndex;
	int reason;

	// Get the file.
	////////////////
	if(!gpiValueForKey(headers, "\\file\\", intValue, sizeof(intValue)))
		return GPIFalse;
	fileIndex = atoi(intValue);
	if((fileIndex < 0) || (fileIndex >= ArrayLength(transfer->files)))
		return GPIFalse;
	file = (GPIFile *)ArrayNth(transfer->files, fileIndex);

	// Get the reason.
	//////////////////
	if(!gpiValueForKey(headers, "\\reason\\", intValue, sizeof(intValue)))
		return GPIFalse;
	reason = atoi(intValue);

	// Is it not the current file?
	//////////////////////////////
	if(fileIndex != transfer->currentFile)
	{
		// Check if we already finished this file.
		//////////////////////////////////////////
		if(fileIndex < transfer->currentFile)
			return GPIFalse;

		// Mark it for skipping later.
		//////////////////////////////
		if(reason == GPI_SKIP_USER_SKIP)
		{
			file->flags |= GPI_FILE_SKIP;
		}
		else
		{
			file->flags |= GPI_FILE_FAILED;
			if(reason == GPI_SKIP_READ_ERROR)
				file->reason = GP_FILE_READ_ERROR;
			else
				file->reason = GP_FILE_WRITE_ERROR;
		}

		return GPITrue;
	}

	// Delete the file if its already opened.
	/////////////////////////////////////////
	if(!transfer->sender && file->file)
	{
		fclose(file->file);
		file->file = NULL;
		remove(file->path);
	}

	// Next file.
	/////////////
	transfer->currentFile++;

	// Call the callback.
	/////////////////////
	arg = (GPTransferCallbackArg *)gsimalloc(sizeof(GPTransferCallbackArg));
	if(arg)
	{
		memset(arg, 0, sizeof(GPTransferCallbackArg));
		arg->transfer = transfer->localID;
		arg->index = fileIndex;
		if(reason == GPI_SKIP_USER_SKIP)
		{
			arg->type = GP_FILE_SKIP;
		}
		else
		{
			arg->type = GP_FILE_FAILED;
			if(reason == GPI_SKIP_READ_ERROR)
				arg->num = GP_FILE_READ_ERROR;
			else
				arg->num = GP_FILE_WRITE_ERROR;
		}
		gpiAddCallback(connection, iconnection->callbacks[GPI_TRANSFER_CALLBACK], arg, NULL, GPI_ADD_TRANSFER_CALLBACK);
	}

	return GPITrue;
}

static GPIBool gpiHandleTransferThrottle
(
  GPConnection * connection,
  GPITransfer * transfer,
  const char * headers
)
{
	GPIConnection * iconnection = (GPIConnection*)*connection;
	int throttle;
	char intValue[16];
	GPTransferCallbackArg * arg;

	// Get the throttle.
	////////////////////
	if(!gpiValueForKey(headers, "\\rate\\", intValue, sizeof(intValue)))
		return GPIFalse;
	throttle = atoi(intValue);

	// Store the throttle.
	//////////////////////
	transfer->throttle = throttle;

	// If we're the sender, send this back.
	///////////////////////////////////////
	if(transfer->sender)
	{
		if(gpiPeerStartTransferMessage(connection, transfer->peer, GPI_BM_FILE_TRANSFER_THROTTLE, (GPITransferID_st)&transfer->transferID) != GP_NO_ERROR)
			return GPIFalse;
		gpiSendOrBufferString(connection, transfer->peer, "\\rate\\");
		gpiSendOrBufferInt(connection, transfer->peer, throttle);
		gpiFinishTransferMessage(connection, transfer, NULL, 0);
	}

	// Call the callback.
	/////////////////////
	arg = (GPTransferCallbackArg *)gsimalloc(sizeof(GPTransferCallbackArg));
	if(arg)
	{
		memset(arg, 0, sizeof(GPTransferCallbackArg));
		arg->transfer = transfer->localID;
		arg->type = GP_TRANSFER_THROTTLE;
		arg->num = throttle;
		gpiAddCallback(connection, iconnection->callbacks[GPI_TRANSFER_CALLBACK], arg, NULL, GPI_ADD_TRANSFER_CALLBACK);
	}

	return GPITrue;
}

static GPIBool gpiHandleTransferCancel
(
  GPConnection * connection,
  GPITransfer * transfer
)
{
	GPIConnection * iconnection = (GPIConnection*)*connection;
	GPTransferCallbackArg * arg;

//	if(transfer->sender)
//		return GPIFalse;

	// Mark the transfer cancelled.
	///////////////////////////////
	transfer->state = GPITransferCancelled;

	// Call the callback.
	/////////////////////
	arg = (GPTransferCallbackArg *)gsimalloc(sizeof(GPTransferCallbackArg));
	if(arg)
	{
		memset(arg, 0, sizeof(GPTransferCallbackArg));
		arg->transfer = transfer->localID;
		arg->type = GP_TRANSFER_CANCELLED;
		gpiAddCallback(connection, iconnection->callbacks[GPI_TRANSFER_CALLBACK], arg, NULL, GPI_ADD_TRANSFER_CALLBACK);
	}

	return GPITrue;
}

static GPIBool gpiHandleTransferKeepalive
(
  GPConnection * connection,
  GPITransfer * transfer
)
{
	GSI_UNUSED(connection);
	GSI_UNUSED(transfer);

	// Ignore keep-alive.
	/////////////////////
	return GPITrue;
}

static GPResult gpiSendFileEnd
(
  GPConnection * connection,
  GPITransfer * transfer,
  GPIFile * file
)
{
	CHECK_RESULT(gpiPeerStartTransferMessage(connection, transfer->peer, GPI_BM_FILE_END, (GPITransferID_st)&transfer->transferID));

	// Add the file index.
	//////////////////////
	gpiSendOrBufferStringLen(connection, transfer->peer, "\\file\\", 6);
	gpiSendOrBufferInt(connection, transfer->peer, transfer->currentFile);

	// Only add the MD5 for files.
	//////////////////////////////
	if(!(file->flags & GPI_FILE_DIRECTORY))
	{
		unsigned char md5Raw[16];
		char md5[33];

		// Get the MD5.
		///////////////
		MD5Final(md5Raw, &file->md5);
		MD5Print(md5Raw, md5);

		// Add it.
		//////////
		gpiSendOrBufferString(connection, transfer->peer, "\\md5\\");
		gpiSendOrBufferString(connection, transfer->peer, md5);
	}

	gpiFinishTransferMessage(connection, transfer, NULL, 0);

	return GP_NO_ERROR;
}

static GPResult gpiSendFileBegin
(
  GPConnection * connection,
  GPITransfer * transfer,
  GPIFile * file
)
{
	char buffer[64];

	// Get the file info.
	/////////////////////
	if(!gpiGetTransferFileInfo(file->file, &file->size, &file->modTime))
		Error(connection, GP_PARAMETER_ERROR, "Can't get info on file.");

	CHECK_RESULT(gpiPeerStartTransferMessage(connection, transfer->peer, GPI_BM_FILE_BEGIN, (GPITransferID_st)&transfer->transferID));
	sprintf(buffer, "\\file\\%d\\size\\%d\\mtime\\%u", transfer->currentFile, file->size, (unsigned int)file->modTime);
	gpiSendOrBufferString(connection, transfer->peer, buffer);
	gpiFinishTransferMessage(connection, transfer, NULL, 0);

	return GP_NO_ERROR;
}

static GPResult gpiSendFileData
(
  GPConnection * connection,
  GPITransfer * transfer,
  unsigned char * data,
  size_t len
)
{
	CHECK_RESULT(gpiPeerStartTransferMessage(connection, transfer->peer, GPI_BM_FILE_DATA, (GPITransferID_st)&transfer->transferID));

	// Add the file index.
	//////////////////////
	gpiSendOrBufferStringLen(connection, transfer->peer, "\\file\\", 6);
	gpiSendOrBufferInt(connection, transfer->peer, transfer->currentFile);

	gpiFinishTransferMessage(connection, transfer, (char *)data, len);

	return GP_NO_ERROR;
}

GPResult gpiProcessCurrentFile
(
  GPConnection * connection,
  GPITransfer * transfer
)
{
	GPIConnection * iconnection = (GPIConnection*)*connection;
	GPIFile * file;
	GPTransferCallbackArg * arg;
	size_t num;
	int i;
	int total;

	assert(transfer->currentFile >= 0);
	assert(transfer->currentFile < ArrayLength(transfer->files));

	// Get the current file.
	////////////////////////
	file = (GPIFile *)ArrayNth(transfer->files, transfer->currentFile);

	assert(!(file->flags & GPI_FILE_FAILED));

#ifdef GPI_CONFIRM_FILES
	// If it's being confirmed, just wait.
	//////////////////////////////////////
	if(file->flags & GPI_FILE_CONFIRMING)
		return GP_NO_ERROR;
#endif

	// Check if its been marked for skipping.
	/////////////////////////////////////////
	if(file->flags & GPI_FILE_SKIP)
	{
		// Skip it.
		///////////
		gpiSkipCurrentFile(connection, transfer, GPI_SKIP_USER_SKIP);

		// Call the callback.
		/////////////////////
		arg = (GPTransferCallbackArg *)gsimalloc(sizeof(GPTransferCallbackArg));
		if(arg)
		{
			memset(arg, 0, sizeof(GPTransferCallbackArg));
			arg->transfer = transfer->localID;
			arg->index = transfer->currentFile;
			arg->type = GP_FILE_SKIP;
			gpiAddCallback(connection, iconnection->callbacks[GPI_TRANSFER_CALLBACK], arg, NULL, GPI_ADD_TRANSFER_CALLBACK);
		}
	}
	else
	{
		// Is this a directory?
		///////////////////////
		if(file->flags & GPI_FILE_DIRECTORY)
		{
			// Call the callback.
			/////////////////////
			arg = (GPTransferCallbackArg *)gsimalloc(sizeof(GPTransferCallbackArg));
			if(arg)
			{
				memset(arg, 0, sizeof(GPTransferCallbackArg));
				arg->transfer = transfer->localID;
				arg->index = transfer->currentFile;
				arg->type = GP_FILE_DIRECTORY;
				gpiAddCallback(connection, iconnection->callbacks[GPI_TRANSFER_CALLBACK], arg, NULL, GPI_ADD_TRANSFER_CALLBACK);
			}

			// Send the end.
			////////////////
			gpiSendFileEnd(connection, transfer, file);
			file->flags |= GPI_FILE_COMPLETED;
			transfer->currentFile++;
		}
		else
		{
			static char buffer[GPI_DATA_SIZE];

			// Open the file if we need to.
			///////////////////////////////
			if(!file->file)
			{
				// Open it.
				///////////
				file->file = fopen(file->path, "rb");
				if(file->file)
				{
					// Send the begin.
					//////////////////
					CHECK_RESULT(gpiSendFileBegin(connection, transfer, file));

					// Init the md5.
					////////////////
					MD5Init(&file->md5);

					// Call the callback.
					/////////////////////
					arg = (GPTransferCallbackArg *)gsimalloc(sizeof(GPTransferCallbackArg));
					if(arg)
					{
						memset(arg, 0, sizeof(GPTransferCallbackArg));
						arg->transfer = transfer->localID;
						arg->index = transfer->currentFile;
						arg->type = GP_FILE_BEGIN;
						gpiAddCallback(connection, iconnection->callbacks[GPI_TRANSFER_CALLBACK], arg, NULL, GPI_ADD_TRANSFER_CALLBACK);
					}
				}
				else
				{
					// Call the callback.
					/////////////////////
					arg = (GPTransferCallbackArg *)gsimalloc(sizeof(GPTransferCallbackArg));
					if(arg)
					{
						memset(arg, 0, sizeof(GPTransferCallbackArg));
						arg->transfer = transfer->localID;
						arg->index = transfer->currentFile;
						arg->type = GP_FILE_FAILED;
						arg->num = GP_FILE_READ_ERROR;
						gpiAddCallback(connection, iconnection->callbacks[GPI_TRANSFER_CALLBACK], arg, NULL, GPI_ADD_TRANSFER_CALLBACK);
					}

					// Failed to open.
					//////////////////
					gpiSkipCurrentFile(connection, transfer, GPI_SKIP_READ_ERROR);
					file->flags |= GPI_FILE_FAILED;
					file->reason = GP_FILE_READ_ERROR;

					return GP_NO_ERROR;
				}
			}

			// TODO: THROTTLING

			// Send until done, and while messages are actually being sent.
			///////////////////////////////////////////////////////////////
			total = 0;
			for(i = 0 ; (file->progress < file->size) && !transfer->peer->outputBuffer.len /*&& (i < 20)*/ ; i++)
			{
#ifdef GPI_ACKNOWLEDGED_WINDOW
				// Don't get too far ahead.
				///////////////////////////
				if((file->acknowledged + GPI_ACKNOWLEDGED_WINDOW) < file->progress)
					break;
#endif

				// Read data.
				/////////////
				num = fread(buffer, 1, sizeof(buffer), file->file);
				if(num)
				{
					// Update the md5.
					//////////////////
					MD5Update(&file->md5, (unsigned char*)buffer, num);

					// Send the data.
					/////////////////
					CHECK_RESULT(gpiSendFileData(connection, transfer, (unsigned char*)buffer, num));

					// Update progress.
					///////////////////
					transfer->progress += num;
					file->progress += num;
					total += num;

					// Call the callback.
					/////////////////////
					arg = (GPTransferCallbackArg *)gsimalloc(sizeof(GPTransferCallbackArg));
					if(arg)
					{
						memset(arg, 0, sizeof(GPTransferCallbackArg));
						arg->transfer = transfer->localID;
						arg->index = transfer->currentFile;
						arg->type = GP_FILE_PROGRESS;
						arg->num = file->progress;
						gpiAddCallback(connection, iconnection->callbacks[GPI_TRANSFER_CALLBACK], arg, NULL, GPI_ADD_TRANSFER_CALLBACK);
					}
				}

				// Did we not get to the end?
				/////////////////////////////
				if((num < sizeof(buffer)) && (file->progress != file->size))
				{
					// Failed reading.
					//////////////////
					gpiSkipCurrentFile(connection, transfer, GPI_SKIP_READ_ERROR);
					file->flags |= GPI_FILE_FAILED;
					file->reason = GP_FILE_READ_ERROR;

					return GP_NO_ERROR;
				}
			}

			if(total)
			{
				gsDebugFormat(GSIDebugCat_GP, GSIDebugType_File, GSIDebugLevel_RawDump,
					"SENTTOTL(PT): %d\n", total);
			}

			// Did we finish the file?
			//////////////////////////
			if(file->progress == file->size)
			{
				// Close the file.
				//////////////////
				fclose(file->file);
				file->file = NULL;

				// Send the end.
				////////////////
				gpiSendFileEnd(connection, transfer, file);

#ifdef GPI_CONFIRM_FILES
				// Wait for the confirmation.
				/////////////////////////////
				file->flags |= GPI_FILE_CONFIRMING;
#else
				// Call the callback.
				/////////////////////
				arg = (GPTransferCallbackArg *)gsimalloc(sizeof(GPTransferCallbackArg));
				if(arg)
				{
					memset(arg, 0, sizeof(GPTransferCallbackArg));
					arg->transfer = transfer->localID;
					arg->index = transfer->currentFile;
					arg->type = GP_FILE_END;
					gpiAddCallback(connection, iconnection->callbacks[GPI_TRANSFER_CALLBACK], arg, NULL, GPI_ADD_TRANSFER_CALLBACK);
				}

				// Done with the file.
				//////////////////////
				file->flags |= GPI_FILE_COMPLETED;
				transfer->currentFile++;
#endif
			}
		}
	}

	return GP_NO_ERROR;
}

GPResult gpiProcessTransfer
(
  GPConnection * connection,
  GPITransfer * transfer
)
{
	GPIConnection * iconnection = (GPIConnection*)*connection;
	int currentFile;
	int len;
	GPTransferCallbackArg * arg;
	unsigned long now;

	// We only process sending transfers.
	/////////////////////////////////////
	if(!transfer->sender)
		return GP_NO_ERROR;

	// Is the transfer finished?
	////////////////////////////
	if(transfer->state >= GPITransferComplete)
		return GP_NO_ERROR;

	// Get the time.
	////////////////
	now = current_time();

	// Check for no peer connection established.
	////////////////////////////////////////////
	if(!transfer->peer)
	{
		// If its been too long, the person probably isn't really online.
		/////////////////////////////////////////////////////////////////
		if((now - transfer->lastSend) > GPI_PEER_TIMEOUT_TIME)
		{
			GPTransferCallbackArg * arg;

			// We couldn't connect.
			///////////////////////
			transfer->state = GPITransferNoConnection;

			// Call the callback.
			/////////////////////
			arg = (GPTransferCallbackArg *)gsimalloc(sizeof(GPTransferCallbackArg));
			if(arg)
			{
				memset(arg, 0, sizeof(GPTransferCallbackArg));
				arg->transfer = transfer->localID;
				arg->type = GP_TRANSFER_NO_CONNECTION;
				gpiAddCallback(connection, iconnection->callbacks[GPI_TRANSFER_CALLBACK], arg, NULL, GPI_ADD_TRANSFER_CALLBACK);
			}

			return GP_NO_ERROR;
		}
	}
	else
	{
		// Check for inactivity.
		////////////////////////
		if((now - transfer->lastSend) > GPI_KEEPALIVE_TIME)
		{
			// Send a keepalive.
			////////////////////
			CHECK_RESULT(gpiPeerStartTransferMessage(connection, transfer->peer, GPI_BM_FILE_TRANSFER_KEEPALIVE, (GPITransferID_st)&transfer->transferID));
			gpiFinishTransferMessage(connection, transfer, NULL, 0);
		}
	}

	// If we're paused, there's nothing else to do.
	///////////////////////////////////////////////
	if(transfer->throttle == 0)
		return GP_NO_ERROR;

	// Don't send files if we're not transfering yet.
	//////////////////////////////////////////////////
	if(transfer->state < GPITransferTransferring)
		return GP_NO_ERROR;

	// Don't send files if we have regular messages pending.
	////////////////////////////////////////////////////////
	if(ArrayLength(transfer->peer->messages))
		return GP_NO_ERROR;

	// Process the current file.
	////////////////////////////
	len = ArrayLength(transfer->files);
	while(transfer->currentFile < len)
	{
		currentFile = transfer->currentFile;
		CHECK_RESULT(gpiProcessCurrentFile(connection, transfer));
		if(currentFile == transfer->currentFile)
			break;
	}

	// Did we finish?
	/////////////////
	if(transfer->currentFile == len)
	{
		// Call the callback.
		/////////////////////
		arg = (GPTransferCallbackArg *)gsimalloc(sizeof(GPTransferCallbackArg));
		if(arg)
		{
			memset(arg, 0, sizeof(GPTransferCallbackArg));
			arg->transfer = transfer->localID;
			arg->type = GP_TRANSFER_DONE;
			gpiAddCallback(connection, iconnection->callbacks[GPI_TRANSFER_CALLBACK], arg, NULL, GPI_ADD_TRANSFER_CALLBACK);
		}

		// Mark it as complete.
		///////////////////////
		transfer->state = GPITransferComplete;
	}

	return GP_NO_ERROR;
}

GPResult gpiProcessTransfers
(
  GPConnection * connection
)
{
	GPIConnection * iconnection = (GPIConnection*)*connection;
	int len;
	int i;
	GPITransfer * transfer;

	// Go through each transfer.
	////////////////////////////
	len = ArrayLength(iconnection->transfers);
	for(i = 0 ; i < len ; i++)
	{
		// Get the transfer.
		////////////////////
		transfer = (GPITransfer *)ArrayNth(iconnection->transfers, i);

		// Process it.
		//////////////
		gpiProcessTransfer(connection, transfer);
	}

	return GP_NO_ERROR;
}

GPIBool gpiGetTransferFileInfo
(
  FILE * file,
  int * size,
  gsi_time * modTime
)
{
#ifdef _WIN32
	struct _stat stats;

	if(_fstat(_fileno(file), &stats) != 0)
		return GPIFalse;

	*size = (int)stats.st_size;
	*modTime = stats.st_mtime;
#else
	if(fseek(file, 0, SEEK_END) != 0)
		return GPIFalse;

	*size = (int)ftell(file);
	if(*size == -1)
		return GPIFalse;

	*modTime = 0;

	fseek(file, 0, SEEK_SET);
#endif

	return GPITrue;
}

void gpiTransferPeerDestroyed
(
  GPConnection * connection,
  GPIPeer * peer
)
{
	GPIConnection * iconnection = (GPIConnection*)*connection;
	GPTransferCallbackArg * arg;
	GPITransfer * transfer;
	int i;
	int len;

	// Search for transfers that use this peer.
	///////////////////////////////////////////
	len = ArrayLength(iconnection->transfers);
	for(i = 0 ; i < len ; i++)
	{
		transfer = (GPITransfer *)ArrayNth(iconnection->transfers, i);

		if (transfer->peer == peer)
		{
			// Call the callback.
			/////////////////////
			arg = (GPTransferCallbackArg *)gsimalloc(sizeof(GPTransferCallbackArg));
			if(arg)
			{
				memset(arg, 0, sizeof(GPTransferCallbackArg));
				arg->transfer = transfer->localID;
				arg->type = GP_TRANSFER_LOST_CONNECTION;
				gpiAddCallback(connection, iconnection->callbacks[GPI_TRANSFER_CALLBACK], arg, NULL, GPI_ADD_TRANSFER_CALLBACK);
			}
		
			// So long tranfer.
			///////////////////
			transfer->state = GPITransferNoConnection;
		}
	}

}

void gpiTransfersHandlePong
(
  GPConnection * connection,
  GPProfile profile,
  GPIPeer * peer
)
{
	GPIConnection * iconnection = (GPIConnection*)*connection;
	GPITransfer * transfer;
	int i;
	int len;

	// Go through all the transfers.
	////////////////////////////////
	len = ArrayLength(iconnection->transfers);
	for(i = 0 ; i < len ; i++)
	{
		// Get this transfer.
		/////////////////////
		transfer = (GPITransfer *)ArrayNth(iconnection->transfers, i);
		assert(transfer);

		// Is it waiting on a pong from this profile?
		/////////////////////////////////////////////
		if((transfer->state == GPITransferPinging) && (transfer->profile == profile))
		{
			// Did we not get a connection?
			///////////////////////////////
			if(!peer)
			{
				GPTransferCallbackArg * arg;

				// We couldn't connect.
				///////////////////////
				transfer->state = GPITransferNoConnection;

				// Call the callback.
				/////////////////////
				arg = (GPTransferCallbackArg *)gsimalloc(sizeof(GPTransferCallbackArg));
				if(arg)
				{
					memset(arg, 0, sizeof(GPTransferCallbackArg));
					arg->transfer = transfer->localID;
					arg->type = GP_TRANSFER_NO_CONNECTION;
					gpiAddCallback(connection, iconnection->callbacks[GPI_TRANSFER_CALLBACK], arg, NULL, GPI_ADD_TRANSFER_CALLBACK);
				}
			}
			else
			{
				// Store the peer we're connected on.
				/////////////////////////////////////
				transfer->peer = peer;

				// We're connected, so send our request.
				////////////////////////////////////////
				gpiSendTransferRequest(connection, transfer);

				// Waiting for a response.
				//////////////////////////
				transfer->state = GPITransferWaiting;
			}
		}
	}
}
#endif

GPResult gpiSendTransferReply
(
  GPConnection * connection,
  const GPITransferID * transferID,
  GPIPeer * peer,
  int result,
  const char * msg
)
{
	char buffer[32];

	if(!msg)
		msg = "";

	// Start the message.
	/////////////////////
	CHECK_RESULT(gpiPeerStartTransferMessage(connection, peer, GPI_BM_FILE_SEND_REPLY, transferID));

	// Add the rest of the headers.
	///////////////////////////////
	sprintf(buffer, "\\version\\%d\\result\\%d", GPI_TRANSFER_VERSION, result);
	CHECK_RESULT(gpiSendOrBufferString(connection, peer, buffer));

	// Finish the message.
	//////////////////////
	CHECK_RESULT(gpiPeerFinishTransferMessage(connection, peer, msg, -1));

	return GP_NO_ERROR;
}

void gpiHandleTransferMessage
(
  GPConnection * connection,
  GPIPeer * peer,
  int type,
  const char * headers,
  const char * buffer,
  int len
)
{
	char value[64];
	GPITransferID transferID;
#ifndef NOFILE
	GPITransfer * transfer;
	GPIBool success;
#endif

	// Get the transfer ID.
	///////////////////////
	if(!gpiValueForKey(headers, "\\xfer\\", value, sizeof(value)))
		return;
	if(sscanf(value, "%d %u %u", &transferID.profileid, &transferID.count, &transferID.time) != 3)
		return;

#ifdef NOFILE
	gpiSendTransferReply(connection, &transferID, peer, GPI_NOT_ACCEPTING, NULL);
#else

	// Send request messages don't yet have a transfer object.
	//////////////////////////////////////////////////////////
	if(type == GPI_BM_FILE_SEND_REQUEST)
	{
		// Check for not accepting connections.
		///////////////////////////////////////
		if(!gpiHandleSendRequest(connection, peer, &transferID, headers, buffer, len))
			gpiSendTransferReply(connection, &transferID, peer, GPI_NOT_ACCEPTING, NULL);

		return;
	}

	// Find the transfer based on the ID.
	/////////////////////////////////////
	transfer = gpiFindTransferByTransferID(connection, &transferID);
	if(!transfer || (transfer->peer != peer))
		return;

	// Handle it based on the type.
	///////////////////////////////
	switch(type)
	{
		case GPI_BM_FILE_SEND_REPLY:
			success = gpiHandleSendReply(connection, transfer, headers, buffer, len);
			break;
		case GPI_BM_FILE_BEGIN:
			success = gpiHandleBegin(connection, transfer, headers);
			break;
		case GPI_BM_FILE_END:
			success = gpiHandleEnd(connection, transfer, headers);
			break;
		case GPI_BM_FILE_DATA:
			success = gpiHandleData(connection, transfer, headers, buffer, len);
			break;
		case GPI_BM_FILE_SKIP:
			success = gpiHandleSkip(connection, transfer, headers);
			break;
		case GPI_BM_FILE_TRANSFER_THROTTLE:
			success = gpiHandleTransferThrottle(connection, transfer, headers);
			break;
		case GPI_BM_FILE_TRANSFER_CANCEL:
			success = gpiHandleTransferCancel(connection, transfer);
			break;
		case GPI_BM_FILE_TRANSFER_KEEPALIVE:
			success = gpiHandleTransferKeepalive(connection, transfer);
			break;
		default:
			success = GPITrue;
	}

	// Check if there was a transfer error.
	///////////////////////////////////////
	if(!success)
		gpiTransferError(connection, transfer);
#endif

	GSI_UNUSED(type);
	GSI_UNUSED(buffer);
	GSI_UNUSED(len);
}
