//=============================================================================
//
//   File : DccFileTransfer.cpp
//   Creation date : Tue Sep 20 09 2000 15:14:14 by Szymon Stefanek
//
//   This file is part of the KVIrc IRC client distribution
//   Copyright (C) 2000-2010 Szymon Stefanek (pragma at kvirc dot net)
//
//   This program is FREE software. You can redistribute it and/or
//   modify it under the terms of the GNU General Public License
//   as published by the Free Software Foundation; either version 2
//   of the License, or (at your option) any later version.
//
//   This program is distributed in the HOPE that it will be USEFUL,
//   but WITHOUT ANY WARRANTY; without even the implied warranty of
//   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
//   See the GNU General Public License for more details.
//
//   You should have received a copy of the GNU General Public License
//   along with this program. If not, write to the Free Software Foundation,
//   Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
//=============================================================================

#define _KVI_DEBUG_CHECK_RANGE_

#include "DccFileTransfer.h"
#include "DccBroker.h"
#include "DccMarshal.h"
#include "DccWindow.h"

#include "kvi_debug.h"
#include "KviApplication.h"
#include "KviOptions.h"
#include "KviIrcView.h"
#include "KviIconManager.h"
#include "KviLocale.h"
#include "KviError.h"
#include "kvi_out.h"
#include "KviNetUtils.h"
#include "KviConsoleWindow.h"
#include "KviMainWindow.h"
#include "KviMemory.h"
#include "KviThread.h"
#include "KviIrcSocket.h"
#include "KviMediaManager.h"
#include "kvi_socket.h"
#include "KviKvsEventTriggers.h"
#include "KviIrcConnection.h"
#include "KviIrcConnectionUserInfo.h"
#include "KviIrcServerParser.h"
#include "KviKvsScript.h"

#ifdef COMPILE_ON_WINDOWS
// Ugly Windoze compiler...
#include "DccDialog.h"
#endif

#include <QFile>
#include <QPainter>
#include <QDateTime>
#include <qglobal.h>
#include <QSpinBox>
#include <QLayout>
#include <QPushButton>
#include <QEvent>
#include <QCloseEvent>
#include <QTimer>
#include <QtEndian>

#define INSTANT_BANDWIDTH_CHECK_INTERVAL_IN_MSECS 3000
#define INSTANT_BANDWIDTH_CHECK_INTERVAL_IN_SECS 3

// This limit, when multiplied by INSTANT_BANDWIDTH_CHECK_INTERVAL_IN_SECS
// must fit in 31 bits (0x7fffffff)! (because of data size limits)
#define MAX_DCC_BANDWIDTH_LIMIT 0x1fffffff

// FIXME: The events OnDCCConnect etc are in wrong places here...!

extern DccBroker * g_pDccBroker;

extern KVIRC_API KviMediaManager * g_pMediaManager; // KviApplication.cpp

static KviPointerList<DccFileTransfer> * g_pDccFileTransfers = nullptr;
static QPixmap * g_pDccFileTransferIcon = nullptr;

//#warning "The events that have a KviCString data pointer should become real classes, that take care of deleting the data pointer!"
//#warning "Otherwise, when left undispatched we will be leaking memory (event class destroyed but not the data ptr)"

DccRecvThread::DccRecvThread(QObject * par, kvi_socket_t fd, KviDccRecvThreadOptions * opt)
    : DccThread(par, fd)
{
	m_pOpt = opt;
	m_uAverageSpeed = 0;
	m_uInstantSpeed = 0;
	m_uFilePosition = 0;

	m_uTotalReceivedBytes = 0;
	m_uInstantReceivedBytes = 0;
	m_pFile = nullptr;
	m_pTimeInterval = new KviMSecTimeInterval();
	m_uStartTime = 0;
	m_uInstantSpeedInterval = 0;
}

DccRecvThread::~DccRecvThread()
{
	if(m_pOpt)
		delete m_pOpt;
	if(m_pFile)
		delete m_pFile;
	delete m_pTimeInterval;
}

bool DccRecvThread::sendAck(qint64 filePos, bool bUse64BitAck)
{
	quint32 ack32 = htonl(filePos & 0xffffffff);
	quint64 ack64 = qToBigEndian(filePos);

	char * ack = (char *)&ack32;
	int ackSize = 4;

	if(bUse64BitAck)
	{
		ackSize = 8;
		ack = (char *)&ack64;
	}

	int iRet = 0;
#ifdef COMPILE_SSL_SUPPORT
	if(m_pSSL)
		iRet = m_pSSL->write(ack, ackSize);
	else
#endif //COMPILE_SSL_SUPPORT
		iRet = kvi_socket_send(m_fd, (void *)(ack), ackSize);

	if(iRet == ackSize)
		return true; // everything sent

	// When downloading from a fast server using send-ahead via an asymmetric link (such as the
	// common ADSL lines) it may happen that the network output queue gets saturated with ACKs.
	// In this case the network stack will refuse to send our packet and we get here.
	//
	// We should either retry to send the ACK in a while or avoid sending it at all (as with
	// send-ahead acks aren't usually checked per-packet).

	if(iRet == 0)
	{
		// We can live with this: no data has been sent at all
		// Not sending the ack and hoping that the server will not stall is better than
		// killing the connection from our side anyway.
		return true;
	}

	if(iRet < 0)
	{
// Reported error. If it's EAGAIN or EINTR then no data has been sent.
#ifdef COMPILE_SSL_SUPPORT
		if(m_pSSL)
		{
			// dropping ack when no serious ssl error occurred
			switch(m_pSSL->getProtocolError(iRet))
			{
				case KviSSL::ZeroReturn:
				//return false; check eagain
				case KviSSL::Success:
				case KviSSL::WantRead:
				case KviSSL::WantWrite:
					return true;
					break;
				default:
					// Raise unknown SSL ERROR
					postErrorEvent(KviError::SSLError);
					return false;
					break;
			}

			return false;
		}
#endif //COMPILE_SSL_SUPPORT

		int err = kvi_socket_error();
#if defined(COMPILE_ON_WINDOWS) || defined(COMPILE_ON_MINGW)
		if((err != EAGAIN) && (err != EINTR) && (err != WSAEWOULDBLOCK))
#else  //!(defined(COMPILE_ON_WINDOWS) || defined(COMPILE_ON_MINGW))
		if((err != EAGAIN) && (err != EINTR))
#endif //!(defined(COMPILE_ON_WINDOWS) || defined(COMPILE_ON_MINGW))
		{
			// some other kind of error
			postErrorEvent(KviError::AcknowledgeError);
			return false;
		}

		return true; // no data sent: same as iRet == 0 above.
	}

	// Sent something but not everything.
	// How likely is it to get in here ?!
	// Sleep for a short while and try to send the missing part.
	// This will probably throttle the bandwidth usage a bit too.
	msleep(10);

	int iMissingPart = ackSize - iRet;

#ifdef COMPILE_SSL_SUPPORT
	if(m_pSSL)
		iRet = m_pSSL->write(ack + iRet, iMissingPart);
	else
#endif //COMPILE_SSL_SUPPORT
		iRet = kvi_socket_send(m_fd, (void *)(ack + iRet), iMissingPart);

	if(iRet != iMissingPart)
	{
		// Crap.. couldn't send the missing part of the ack :/
		postErrorEvent(KviError::AcknowledgeError);
		return false;
	}
	return true;
}

void DccRecvThread::updateStats()
{
	m_uInstantSpeedInterval += m_pTimeInterval->mark();
	unsigned long uCurTime = m_pTimeInterval->secondsCounter();

	m_pMutex->lock();
	unsigned long uElapsedTime = uCurTime - m_uStartTime;
	if(uElapsedTime < 1)
		uElapsedTime = 1;

	m_uFilePosition = m_pFile->pos();
	m_uAverageSpeed = m_uTotalReceivedBytes / uElapsedTime;

	if(m_uInstantSpeedInterval > INSTANT_BANDWIDTH_CHECK_INTERVAL_IN_MSECS)
	{
		unsigned int uMSecsOfTheNextInterval = 0;
		if(m_uInstantSpeedInterval < (INSTANT_BANDWIDTH_CHECK_INTERVAL_IN_MSECS + (INSTANT_BANDWIDTH_CHECK_INTERVAL_IN_MSECS / 2)))
			uMSecsOfTheNextInterval = m_uInstantSpeedInterval - INSTANT_BANDWIDTH_CHECK_INTERVAL_IN_MSECS;
		m_uInstantSpeed = (m_uInstantReceivedBytes * 1000) / m_uInstantSpeedInterval;
		m_uInstantReceivedBytes = 0;
		m_uInstantSpeedInterval = uMSecsOfTheNextInterval;
	}
	else
	{
		if(uElapsedTime <= INSTANT_BANDWIDTH_CHECK_INTERVAL_IN_SECS)
			m_uInstantSpeed = m_uAverageSpeed;
	}
	m_pMutex->unlock();
}

void DccRecvThread::postMessageEvent(const char * m)
{
	KviThreadDataEvent<KviCString> * e = new KviThreadDataEvent<KviCString>(KVI_DCC_THREAD_EVENT_MESSAGE);
	e->setData(new KviCString(m));
	postEvent(parent(), e);
}

// FIXME: This stuff should be somewhat related to the 1448 bytes TCP basic packet size
//#define KVI_DCC_RECV_BLOCK_SIZE 8192
//#define KVI_DCC_RECV_75PERCENTOF_BLOCK_SIZE 6150
#define KVI_DCC_RECV_BLOCK_SIZE 16384
#define KVI_DCC_RECV_75PERCENTOF_BLOCK_SIZE 12280

void DccRecvThread::run()
{
	// take care of sleeping a bit if we can't read stuff
	// so we don't hog the CPU too much...
	int iFailedSelects = 0;
	// take care of sleeping a bit if we get a lot of short reads
	// so we don't hog the CPU too much...
	int iShortReadQuantifier = 0;
	// the algorithm is as follows:
	//    attempt to read KVI_DCC_RECV_BLOCK_SIZE bytes
	//    iShortReadQuantifier += ((KVI_DCC_RECV_75PERCENT_OF_BLOCK_SIZE - realReadedBytes) / 42);
	// thus we gain points if we read less than 75% of the requested size
	// and we loose points otherwise
	// there are nearly 24 points per KB
	//    if(iShortReadQuantifier > 10)
	//       msleep(iShortReadQuantifier);
	// also never sleep more than 500 msecs since it will
	// rise our exit latency too much

	m_pTimeInterval->mark();
	m_pMutex->lock();
	m_uStartTime = m_pTimeInterval->secondsCounter();
	m_pMutex->unlock();

	int iProbableTerminationTime = 0;

	m_pFile = new QFile(QString::fromUtf8(m_pOpt->szFileName.ptr()));

	bool bSend64BitAck = m_pOpt->bSend64BitAck && (m_pOpt->uTotalFileSize >> 32);

	if(m_pOpt->bResume)
	{
		if(!m_pFile->open(QIODevice::WriteOnly | QIODevice::Append))
		{
			postErrorEvent(KviError::CantOpenFileForAppending);
			goto exit_dcc;
		} // else pFile is already at end
	}
	else
	{
		if(!m_pFile->open(QIODevice::WriteOnly))
		{
			postErrorEvent(KviError::CantOpenFileForWriting);
			goto exit_dcc;
		}
	}

	if(m_pOpt->bSendZeroAck && (!m_pOpt->bNoAcks))
	{
		if(!sendAck(m_pFile->pos(), bSend64BitAck))
			goto exit_dcc;
	}

	for(;;)
	{
		// Dequeue events
		while(KviThreadEvent * e = dequeueEvent())
		{
			if(e->id() == KVI_THREAD_EVENT_TERMINATE)
			{
				delete e;
				goto exit_dcc;
			}
			else
			{
				// Other events are senseless to us
				delete e;
			}
		}

		bool bCanRead;
		bool bDummy;

		if(kvi_select(m_fd, &bCanRead, &bDummy, 15000))
		{
			// reset sleep time

			if(bCanRead)
			{
				iFailedSelects = 0;

				// Read a data block
				char buffer[KVI_DCC_RECV_BLOCK_SIZE];

				m_pMutex->lock(); // FIXME: how to remove this lock ?
				unsigned int uMaxPossible = (m_pOpt->uMaxBandwidth < MAX_DCC_BANDWIDTH_LIMIT) ? m_pOpt->uMaxBandwidth * INSTANT_BANDWIDTH_CHECK_INTERVAL_IN_SECS : MAX_DCC_BANDWIDTH_LIMIT * INSTANT_BANDWIDTH_CHECK_INTERVAL_IN_SECS;
				m_pMutex->unlock();
				unsigned int uToRead = uMaxPossible > m_uInstantReceivedBytes ? uMaxPossible - m_uInstantReceivedBytes : 0;
				if(uToRead > KVI_DCC_RECV_BLOCK_SIZE)
					uToRead = KVI_DCC_RECV_BLOCK_SIZE;

				if(uToRead > 0)
				{
					int readLen;
#ifdef COMPILE_SSL_SUPPORT
					if(m_pSSL)
					{
						readLen = m_pSSL->read(buffer, uToRead);
					}
					else
					{
#endif
						readLen = kvi_socket_recv(m_fd, buffer, uToRead);
#ifdef COMPILE_SSL_SUPPORT
					}
#endif

					if(readLen > 0)
					{
						// Readed something useful...write back
						if(((uint)(readLen + m_pFile->pos())) > m_pOpt->uTotalFileSize)
						{
							postMessageEvent(__tr_no_lookup_ctx("WARNING: the peer is sending garbage data past the end of the file", "dcc"));
							postMessageEvent(__tr_no_lookup_ctx("WARNING: ignoring data past the declared end of file and closing the connection", "dcc"));

							readLen = m_pOpt->uTotalFileSize - m_pFile->pos();
							if(readLen > 0)
							{
								if(m_pFile->write(buffer, readLen) != readLen)
									postErrorEvent(KviError::FileIOError);
							}
							break;
						}
						else
						{
							if(m_pFile->write(buffer, readLen) != readLen)
							{
								postErrorEvent(KviError::FileIOError);
								break;
							}
						}

						// Update stats
						m_uTotalReceivedBytes += readLen;
						m_uInstantReceivedBytes += readLen;

						updateStats();
						// Now send the ack
						if(m_pOpt->bNoAcks)
						{
							// No acks...
							// Interrupt if the whole file has been received
							if(m_pOpt->uTotalFileSize > 0)
							{
								if((quint64)m_pFile->pos() == m_pOpt->uTotalFileSize)
								{
									// Received the whole file...die
									KviThreadEvent * e = new KviThreadEvent(KVI_DCC_THREAD_EVENT_SUCCESS);
									postEvent(parent(), e);
									break;
								}
							}
						}
						else
						{
							// Must send the ack... the peer must close the connection

							if(!sendAck(m_pFile->pos(), bSend64BitAck))
								break;
						}

						// now take care of short reads
						iShortReadQuantifier += ((KVI_DCC_RECV_75PERCENTOF_BLOCK_SIZE - readLen) / 42);
						if(iShortReadQuantifier > 10)
						{
							// we're having short reads.. sleep a while
							// but don't allow it to go too high: 0.45 sec is really a lot
							if(iShortReadQuantifier > 500)
								iShortReadQuantifier = 500;
							msleep(iShortReadQuantifier);
						}
						else
						{
							// don't allow it to go too low
							if(iShortReadQuantifier < -500)
								iShortReadQuantifier = -500;
						}
					}
					else
					{
						updateStats();
// Read problem...

#ifdef COMPILE_SSL_SUPPORT
						if(m_pSSL)
						{
							// ssl error....?
							switch(m_pSSL->getProtocolError(readLen))
							{
								case KviSSL::ZeroReturn:
									//check again not necessary a connection closure!
									//if (!handleInvalidSocketRead(readLen)
									// break;
									readLen = 0;
									break;
								case KviSSL::Success:
								case KviSSL::WantRead:
								case KviSSL::WantWrite:
									// hmmm... DO NOT CALL handleInvalidSocketRead
									break;
								case KviSSL::SyscallError:
								{
									int iE = m_pSSL->getLastError(true);
									if(iE != 0)
									{
										raiseSSLError();
										postErrorEvent(KviError::SSLError);
										goto exit_dcc;
									}
								}
								break;
								case KviSSL::SSLError:
								{
									raiseSSLError();
									postErrorEvent(KviError::SSLError);
									goto exit_dcc;
								}
								break;
								default:
									// Raise unknown SSL ERROR
									postErrorEvent(KviError::SSLError);
									goto exit_dcc;
									break;
							}
						}
#endif

						if(readLen == 0)
						{
							// read EOF..
							if(((quint64)m_pFile->pos() == m_pOpt->uTotalFileSize) || (m_pOpt->uTotalFileSize == 0))
							{
								// success if we got the whole file or if we don't know the file size (we trust the peer)
								KviThreadEvent * e = new KviThreadEvent(KVI_DCC_THREAD_EVENT_SUCCESS);
								postEvent(parent(), e);
								break;
							}
						}
#ifdef COMPILE_SSL_SUPPORT
						if(!m_pSSL && !handleInvalidSocketRead(readLen))
							break;
#else
						if(!handleInvalidSocketRead(readLen))
							break;
#endif
					}
				}
				else
				{
					updateStats();

					// reached the bandwidth limit: slow down a bit
					if(m_uInstantSpeedInterval < (INSTANT_BANDWIDTH_CHECK_INTERVAL_IN_MSECS - 100))
						msleep(100);
					else if(m_uInstantSpeedInterval < (INSTANT_BANDWIDTH_CHECK_INTERVAL_IN_MSECS - 20))
						msleep(20);
				}
			}
			else
			{
				// Can't read stuff (can just write)
				updateStats();

				// sleep up to 300 msecs (if data arrives...we want low exit latency here)
				if(iFailedSelects < 100)
					iFailedSelects++;
				updateStats();
				if(iFailedSelects > 3)
					msleep(3 * iFailedSelects);

				if((quint64)m_pFile->pos() == m_pOpt->uTotalFileSize)
				{
					// Wait for the peer to close the connection
					if(iProbableTerminationTime == 0)
					{
						iProbableTerminationTime = (int)kvi_unixTime();
						m_pFile->flush();
						postMessageEvent(__tr_no_lookup_ctx("Data transfer terminated, waiting 30 seconds for the peer to close the connection...", "dcc"));
						// FIXME: Close the file ?
					}
					else
					{
						int iDiff = (((int)kvi_unixTime()) - iProbableTerminationTime);
						if(iDiff > 30)
						{
							// success if we got the whole file or if we don't know the file size (we trust the peer)
							postMessageEvent(__tr_no_lookup_ctx("Data transfer was terminated 30 seconds ago, closing the connection", "dcc"));
							KviThreadEvent * e = new KviThreadEvent(KVI_DCC_THREAD_EVENT_SUCCESS);
							postEvent(parent(), e);
							break;
						}
					}
				}
			}
			// include the artificial delay if needed
			if(m_pOpt->iIdleStepLengthInMSec > 0)
				msleep(m_pOpt->iIdleStepLengthInMSec);
		}
		else
		{
			// sleep up to 200 msecs (if data arrives...we want low exit latency here)
			if(iFailedSelects < 100)
				iFailedSelects++;
			updateStats();
			if(iFailedSelects > 3)
				msleep(2 * iFailedSelects);
		}
	}

exit_dcc:
	if(m_pFile)
	{
		m_pFile->close();
		delete m_pFile;
		m_pFile = nullptr;
	}

#ifdef COMPILE_SSL_SUPPORT
	if(m_pSSL)
	{
		KviSSLMaster::freeSSL(m_pSSL);
		m_pSSL = nullptr;
	}
#endif

	kvi_socket_close(m_fd);
	m_fd = KVI_INVALID_SOCKET;
}

void DccRecvThread::initGetInfo()
{
	m_pMutex->lock();
}

void DccRecvThread::doneGetInfo()
{
	m_pMutex->unlock();
}

DccSendThread::DccSendThread(QObject * par, kvi_socket_t fd, KviDccSendThreadOptions * opt)
    : DccThread(par, fd)
{
	m_pOpt = opt;
	// stats
	m_uAverageSpeed = 0;
	m_uInstantSpeed = 0;
	m_uFilePosition = 0;
	m_uTotalSentBytes = 0;
	m_pTimeInterval = new KviMSecTimeInterval();
	m_uStartTime = 0;
	m_uInstantSpeedInterval = 0;
}

DccSendThread::~DccSendThread()
{
	if(m_pOpt)
		delete m_pOpt;
	delete m_pTimeInterval;
}

void DccSendThread::updateStats()
{
	m_uInstantSpeedInterval += m_pTimeInterval->mark();

	m_pMutex->lock();
	unsigned long uElapsedTime = m_pTimeInterval->secondsCounter() - m_uStartTime;
	if(uElapsedTime < 1)
		uElapsedTime = 1;

	if(m_pOpt->bNoAcks)
	{
		// There are no acks : the avg bandwidth is based on the sent bytes
		m_uAverageSpeed = m_uTotalSentBytes / uElapsedTime;
	}
	else
	{
		// acknowledges : we compute the avg bandwidth based on the acks we receive
		m_uAverageSpeed = (m_uAckedBytes - m_pOpt->uStartPosition) / uElapsedTime;
	}

	if(m_uInstantSpeedInterval >= INSTANT_BANDWIDTH_CHECK_INTERVAL_IN_MSECS)
	{
		// we often overcount the time interval of 10-20 msecs
		// and thus our bandwidth is used less than requested.
		// for this reason we try to account the time in excess
		// to the next period in order to balance the bandwidth usage.
		unsigned long uMSecsOfNextPeriodUsed = 0;
		if(m_uInstantSpeedInterval > INSTANT_BANDWIDTH_CHECK_INTERVAL_IN_MSECS)
		{
			if(m_uInstantSpeedInterval < (INSTANT_BANDWIDTH_CHECK_INTERVAL_IN_MSECS + (INSTANT_BANDWIDTH_CHECK_INTERVAL_IN_MSECS / 2)))
			{
				uMSecsOfNextPeriodUsed = m_uInstantSpeedInterval - INSTANT_BANDWIDTH_CHECK_INTERVAL_IN_MSECS;
				m_uInstantSpeedInterval = INSTANT_BANDWIDTH_CHECK_INTERVAL_IN_MSECS;
			}
			// else we have been delayed for a time comparable to a period
			// and thus we can't recover the bandwidth... let it go as it does...
		}
		m_uInstantSpeed = (m_uInstantSentBytes * 1000) / m_uInstantSpeedInterval;
		m_uInstantSpeedInterval = uMSecsOfNextPeriodUsed;
		m_uInstantSentBytes = 0;
	}
	else
	{
		if(uElapsedTime <= INSTANT_BANDWIDTH_CHECK_INTERVAL_IN_SECS)
			m_uInstantSpeed = m_uAverageSpeed;
	}
	m_pMutex->unlock();
}

union _ack_buffer {
	char cAckBuffer[4];
	quint32 i32AckBuffer;
};

void DccSendThread::run()
{
	m_pTimeInterval->mark();
	m_pMutex->lock();
	m_uStartTime = m_pTimeInterval->secondsCounter();
	m_pMutex->unlock();

	m_uTotalSentBytes = 0;
	m_uInstantSentBytes = 0;
	int iFailedSelects = 0;
	_ack_buffer ackbuffer;
	int iBytesInAckBuffer = 0;
	quint32 uLastAck = 0;
	quint64 uTotLastAck = 0;
	bool bAckHack = false;
	quint64 iAckHackRounds = 0;

	if(m_pOpt->iPacketSize < 32)
		m_pOpt->iPacketSize = 32;
	char * buffer = (char *)KviMemory::allocate(m_pOpt->iPacketSize * sizeof(char));

	QFile * pFile = new QFile(QString::fromUtf8(m_pOpt->szFileName.ptr()));

	if(!pFile->open(QIODevice::ReadOnly))
	{
		postErrorEvent(KviError::CantOpenFileForReading);
		goto exit_dcc;
	}

	if(pFile->size() < 1)
	{
		postErrorEvent(KviError::CantSendAZeroSizeFile);
		goto exit_dcc;
	}

	if(pFile->size() >= 0xffffffff)
	{
		//dcc acks support only files up to 4GiB
		bAckHack = true;
	}

	if(m_pOpt->uStartPosition > 0)
	{
		// seek
		if(!(pFile->seek(m_pOpt->uStartPosition)))
		{
			postErrorEvent(KviError::FileIOError);
			goto exit_dcc;
		}
	}

	uLastAck = m_pOpt->uStartPosition;

	for(;;)
	{
		// Dequeue events
		while(KviThreadEvent * e = dequeueEvent())
		{
			if(e->id() == KVI_THREAD_EVENT_TERMINATE)
			{
				delete e;
				goto exit_dcc;
			}
			else
			{
				// Other events are senseless to us
				delete e;
			}
		}

		bool bCanRead;
		bool bCanWrite;

		if(kvi_select(m_fd, &bCanRead, &bCanWrite, 15000))
		{
			// reset the sleep time
			iFailedSelects = 0;
			if(bCanRead)
			{
				if(!m_pOpt->bNoAcks)
				{
					int iAckBytesToRead = 4 - iBytesInAckBuffer;

					int readLen;
#ifdef COMPILE_SSL_SUPPORT
					if(m_pSSL)
					{
						readLen = m_pSSL->read((ackbuffer.cAckBuffer + iBytesInAckBuffer), iAckBytesToRead);
					}
					else
					{
#endif
						readLen = kvi_socket_recv(m_fd, (ackbuffer.cAckBuffer + iBytesInAckBuffer), iAckBytesToRead);
#ifdef COMPILE_SSL_SUPPORT
					}
#endif

					if(readLen > 0)
					{
						iBytesInAckBuffer += readLen;
						if(iBytesInAckBuffer == 4)
						{
							quint32 iNewAck = ntohl(ackbuffer.i32AckBuffer);
							if(iNewAck > pFile->pos())
							{
								// the peer is drunk or is trying to fool us
								postErrorEvent(KviError::AcknowledgeError);
								break;
							}
							if(iNewAck < uLastAck)
							{
								if(bAckHack)
								{
									//we reached the 4gb ack limit
									iAckHackRounds++;
								}
								else
								{
									// the peer is drunk or is trying to fool us
									postErrorEvent(KviError::AcknowledgeError);
									break;
								}
							}
							uLastAck = iNewAck;
							if(bAckHack)
							{
								uTotLastAck = (iAckHackRounds << 32) + iNewAck;
							}
							else
							{

								uTotLastAck = iNewAck;
							}
							iBytesInAckBuffer = 0;
						}
					}
					else
					{
#ifdef COMPILE_SSL_SUPPORT
						if(m_pSSL)
						{
							// ssl error....?
							switch(m_pSSL->getProtocolError(readLen))
							{

								case KviSSL::ZeroReturn:
									//if (!handleInvalidSocketRead(readLen)
									// break;
									readLen = 0;
									break;
								case KviSSL::Success:
								case KviSSL::WantRead:
								case KviSSL::WantWrite:
									// hmmm...
									break;
								case KviSSL::SyscallError:
								{
									int iE = m_pSSL->getLastError(true);
									if(iE != 0)
									{
										raiseSSLError();
										postErrorEvent(KviError::SSLError);
										goto exit_dcc;
									}
								}
								break;
								case KviSSL::SSLError:
								{
									raiseSSLError();
									postErrorEvent(KviError::SSLError);
									goto exit_dcc;
								}
								break;
								default:
									// Raise unknown SSL ERROR
									postErrorEvent(KviError::SSLError);
									goto exit_dcc;
									break;
							}
						}

						if(!m_pSSL && !handleInvalidSocketRead(readLen))
							break;
#else
						if(!handleInvalidSocketRead(readLen))
							break;
#endif
					}

					// update stats
					m_pMutex->lock(); // is this really necessary ?
					m_uAckedBytes = uTotLastAck;
					m_pMutex->unlock();

					if(uLastAck >= (quint64)pFile->size())
					{
						KviThreadEvent * e = new KviThreadEvent(KVI_DCC_THREAD_EVENT_SUCCESS);
						postEvent(parent(), e);
						break;
					}
				}
				else
				{
					// No acknowledges
					if(m_pOpt->bIsTdcc)
					{
						// We expect the remote end to close the connection when the whole file has been sent
						if(pFile->atEnd())
						{
							int iAck;
							int readLen;
#ifdef COMPILE_SSL_SUPPORT
							if(m_pSSL)
							{
								readLen = m_pSSL->read((char *)&iAck, 4);
							}
							else
							{
#endif
								readLen = kvi_socket_recv(m_fd, (char *)&iAck, 4);
#ifdef COMPILE_SSL_SUPPORT
							}
#endif
							if(readLen == 0)
							{
								// done...success
								updateStats();
								KviThreadEvent * e = new KviThreadEvent(KVI_DCC_THREAD_EVENT_SUCCESS);
								postEvent(parent(), e);
								break;
							}
							else
							{
								if(readLen < 0)
								{
#ifdef COMPILE_SSL_SUPPORT
									if(m_pSSL)
									{
										// ssl error....?
										switch(m_pSSL->getProtocolError(readLen))
										{

											case KviSSL::ZeroReturn:
												readLen = 0;
												break;
											case KviSSL::Success:
											case KviSSL::WantRead:
											case KviSSL::WantWrite:
												// hmmm...
												break;
											case KviSSL::SyscallError:
											{
												int iE = m_pSSL->getLastError(true);
												if(iE != 0)
												{
													raiseSSLError();
													postErrorEvent(KviError::SSLError);
													goto exit_dcc;
												}
											}
											break;
											case KviSSL::SSLError:
											{
												raiseSSLError();
												postErrorEvent(KviError::SSLError);
												goto exit_dcc;
											}
											break;
											default:
												// Raise unknown SSL ERROR
												postErrorEvent(KviError::SSLError);
												goto exit_dcc;
												break;
										}
									}

									if(!m_pSSL && !handleInvalidSocketRead(readLen))
										break;
#else
									if(!handleInvalidSocketRead(readLen))
										break;
#endif
								}
								else
								{
									KviThreadDataEvent<KviCString> * e = new KviThreadDataEvent<KviCString>(KVI_DCC_THREAD_EVENT_MESSAGE);
									e->setData(new KviCString(__tr2qs_ctx("WARNING: received data in a DCC TSEND, there should be no acknowledges", "dcc")));
									postEvent(parent(), e);
								}
							}
						}
					}
				}
			}
			if(bCanWrite)
			{
				if(!pFile->atEnd())
				{
					if(m_pOpt->bFastSend || m_pOpt->bNoAcks || (uLastAck == (quint64)pFile->pos()))
					{
						// maximum readable size
						qint64 toRead = pFile->size() - pFile->pos();
						// the max number of bytes we can send in this interval (bandwidth limit)
						m_pMutex->lock(); // FIXME: how to remove this lock ?
						uint uMaxPossible = m_pOpt->uMaxBandwidth < MAX_DCC_BANDWIDTH_LIMIT ? m_pOpt->uMaxBandwidth * INSTANT_BANDWIDTH_CHECK_INTERVAL_IN_SECS : MAX_DCC_BANDWIDTH_LIMIT * INSTANT_BANDWIDTH_CHECK_INTERVAL_IN_SECS;
						m_pMutex->unlock();
						if(uMaxPossible < m_uInstantSentBytes)
							toRead = 0; // already sent too much!
						else
						{
							uMaxPossible -= m_uInstantSentBytes;
							if(toRead > uMaxPossible)
								toRead = uMaxPossible;
						}
						// limit to packet size
						if(toRead > m_pOpt->iPacketSize)
							toRead = m_pOpt->iPacketSize;

						int written = 0;
						if(toRead > 0)
						{
							// read data
							int readed = pFile->read(buffer, toRead);
							if(readed < toRead)
							{
								postErrorEvent(KviError::FileIOError);
								break;
							}
// send it out

#ifdef COMPILE_SSL_SUPPORT
							if(m_pSSL)
							{
								written = m_pSSL->write(buffer, toRead);
							}
							else
							{
#endif
								written = kvi_socket_send(m_fd, buffer, toRead);
#ifdef COMPILE_SSL_SUPPORT
							}
#endif

							if(written < toRead)
							{
								if(written < 0)
								{
#ifdef COMPILE_SSL_SUPPORT
									if(m_pSSL)
									{
										// ops...might be an SSL error
										switch(m_pSSL->getProtocolError(written))
										{
											case KviSSL::Success:
											case KviSSL::WantWrite:
											case KviSSL::WantRead:
												// Async continue...
												goto handle_system_error;
												break;
											case KviSSL::SyscallError:
												{
													int iSSLErr = m_pSSL->getLastError(true);
													if(iSSLErr != 0)
													{
														raiseSSLError();
														postErrorEvent(KviError::SSLError);
														goto exit_dcc;
													}
													else
													{
														goto handle_system_error;
													}
												}
												break;
											case KviSSL::SSLError:
												raiseSSLError();
												postErrorEvent(KviError::SSLError);
												goto exit_dcc;
												break;
											default:
												postErrorEvent(KviError::SSLError);
												goto exit_dcc;
												break;
										}
									}

									if(!m_pSSL && !handleInvalidSocketRead(written))
										break;

#else
									if(!handleInvalidSocketRead(written))
										break;
#endif

								handle_system_error:
									int err = kvi_socket_error();
#if defined(COMPILE_ON_WINDOWS) || defined(COMPILE_ON_MINGW)
									if((err != EAGAIN) && (err != EINTR) && (err != WSAEWOULDBLOCK))
#else
									if((err != EAGAIN) && (err != EINTR))
#endif
									{
										postErrorEvent(KviError::translateSystemError(err));
										goto exit_dcc;
									}
								}
								else
								{
									// seek back to the right position
									pFile->seek(pFile->pos() - (toRead - written));
								}
							}
						}
						else
						{
							// just nothing to send out in this interval
							// sleep a while
							if(m_uInstantSpeedInterval < (INSTANT_BANDWIDTH_CHECK_INTERVAL_IN_MSECS - 100))
							{
								msleep(100);
							}
							else if(m_uInstantSpeedInterval < (INSTANT_BANDWIDTH_CHECK_INTERVAL_IN_MSECS - 20))
							{
								msleep(20);
							}
						}

						m_uTotalSentBytes += written;
						m_uInstantSentBytes += written;
						m_uFilePosition = pFile->pos();
						updateStats();
					}
				}
				else
				{
					if(m_pOpt->bNoAcks && !m_pOpt->bIsTdcc)
					{
						// at end of the file in a blind dcc send...
						// not in a tdcc: we can close the file...
						updateStats();
						KviThreadEvent * e = new KviThreadEvent(KVI_DCC_THREAD_EVENT_SUCCESS);
						postEvent(parent(), e);
						break;
					}
					else
					{
						// upload finished but we're waiting for the last ack
						// sleep a bit: don't lag the kernie too much while waiting
						msleep(100);
					}
				}
			}
		}
		else
		{
			// after 2 failed selects start to sleep
			if(iFailedSelects > 3)
			{
				// sleep up to 200 msecs
				if(iFailedSelects < 100)
					iFailedSelects++;
				msleep(3 * iFailedSelects);
			}
			else
			{
				iFailedSelects++;
			}
		}

		// include the artificial delay if needed
		if(m_pOpt->iIdleStepLengthInMSec > 0)
		{
			msleep(m_pOpt->iIdleStepLengthInMSec);
		}
	}

exit_dcc:
	KviMemory::free(buffer);
	pFile->close();
	delete pFile;
	pFile = nullptr;

#ifdef COMPILE_SSL_SUPPORT
	if(m_pSSL)
	{
		KviSSLMaster::freeSSL(m_pSSL);
		m_pSSL = nullptr;
	}
#endif
	kvi_socket_close(m_fd);
	m_fd = KVI_INVALID_SOCKET;
}

void DccSendThread::initGetInfo()
{
	m_pMutex->lock();
}

void DccSendThread::doneGetInfo()
{
	m_pMutex->unlock();
}

DccFileTransfer::DccFileTransfer(DccDescriptor * dcc)
    : KviFileTransfer()
{
	init(); // ensure we're initialized
	g_pDccFileTransfers->append(this);

	m_pResumeTimer = nullptr;
	m_pBandwidthDialog = nullptr;

	m_szTransferIdString = QString(__tr2qs_ctx("TRANSFER %1", "dcc")).arg(id());

	m_pDescriptor = dcc;
	m_pDescriptor->setTransfer(this);

	m_pMarshal = new DccMarshal(this);

	connect(m_pMarshal, SIGNAL(error(KviError::Code)), this, SLOT(handleMarshalError(KviError::Code)));
	connect(m_pMarshal, SIGNAL(connected()), this, SLOT(connected()));
	connect(m_pMarshal, SIGNAL(inProgress()), this, SLOT(connectionInProgress()));
#ifdef COMPILE_SSL_SUPPORT
	connect(m_pMarshal, SIGNAL(startingSSLHandshake()), this, SLOT(startingSSLHandshake()));
	connect(m_pMarshal, SIGNAL(sslError(const char *)), this, SLOT(sslError(const char *)));
#endif

	m_szDccType = dcc->bRecvFile ? "RECV" : "SEND";
	if(dcc->bIsTdcc)
		m_szDccType.prepend("T");
#ifdef COMPILE_SSL_SUPPORT
	if(dcc->bIsSSL)
		m_szDccType.prepend("S");
#endif
	m_pSlaveRecvThread = nullptr;
	m_pSlaveSendThread = nullptr;

	m_tTransferStartTime = 0;
	m_tTransferEndTime = 0;

	m_szStatusString = __tr2qs_ctx("Setting up the connection", "dcc");
	m_eGeneralStatus = Connecting;

	bool bOk;
	m_uTotalFileSize = dcc->bRecvFile ? dcc->szFileSize.toULongLong(&bOk) : dcc->szLocalFileSize.toULongLong(&bOk);
	if(!bOk)
		m_uTotalFileSize = 0;

	if(m_pDescriptor->bRecvFile)
		m_uMaxBandwidth = KVI_OPTION_BOOL(KviOption_boolLimitDccRecvSpeed) ? KVI_OPTION_UINT(KviOption_uintMaxDccRecvSpeed) : MAX_DCC_BANDWIDTH_LIMIT;
	else
		m_uMaxBandwidth = KVI_OPTION_BOOL(KviOption_boolLimitDccSendSpeed) ? KVI_OPTION_UINT(KviOption_uintMaxDccSendSpeed) : MAX_DCC_BANDWIDTH_LIMIT;

	startConnection();
}

DccFileTransfer::~DccFileTransfer()
{
	g_pDccFileTransfers->removeRef(this);

	if(m_pResumeTimer)
		delete m_pResumeTimer;
	if(m_pBandwidthDialog)
		delete m_pBandwidthDialog;

	if(m_pSlaveRecvThread)
	{
		m_pSlaveRecvThread->terminate();
		delete m_pSlaveRecvThread;
		m_pSlaveRecvThread = nullptr;
	}

	if(m_pSlaveSendThread)
	{
		m_pSlaveSendThread->terminate();
		delete m_pSlaveSendThread;
		m_pSlaveSendThread = nullptr;
	}

	KviThreadManager::killPendingEvents(this);

	delete m_pDescriptor;
	delete m_pMarshal;
}

void DccFileTransfer::bandwidthDialogDestroyed()
{
	m_pBandwidthDialog = nullptr;
}

KviWindow * DccFileTransfer::eventWindow()
{
	KviWindow * w = transferWindow();
	if(w)
		return w;
	return m_pDescriptor->console();
}

void DccFileTransfer::startConnection()
{
	if(!(m_pDescriptor->bActive))
	{
		// PASSIVE CONNECTION
		m_szStatusString = __tr2qs_ctx("Attempting a passive DCC %1 connection", "dcc").arg(m_szDccType.ptr());
		outputAndLog(m_szStatusString);
	}
	else
	{
		// ACTIVE CONNECTION
		m_szStatusString = __tr2qs_ctx("Attempting an active DCC %1 connection", "dcc").arg(m_szDccType.ptr());
		outputAndLog(m_szStatusString);
	}

	if(m_pDescriptor->bResume && m_pDescriptor->bRecvFile)
	{
		QString fName;
		KviIrcServerParser::encodeCtcpParameter(m_pDescriptor->szFileName.toUtf8().data(), fName);
		if(m_pDescriptor->isZeroPortRequest())
		{
			m_pDescriptor->console()->connection()->sendFmtData("PRIVMSG %s :%cDCC RESUME %s %s %s %s%c",
			    m_pDescriptor->console()->connection()->encodeText(m_pDescriptor->szNick).data(),
			    0x01,
			    m_pDescriptor->console()->connection()->encodeText(fName).data(),
			    m_pDescriptor->szPort.toUtf8().data(),
			    m_pDescriptor->szLocalFileSize.toUtf8().data(),
			    m_pDescriptor->zeroPortRequestTag(), 0x01);
		}
		else
		{
			m_pDescriptor->console()->connection()->sendFmtData("PRIVMSG %s :%cDCC RESUME %s %s %s%c",
			    m_pDescriptor->console()->connection()->encodeText(m_pDescriptor->szNick).data(),
			    0x01,
			    m_pDescriptor->console()->connection()->encodeText(fName).data(),
			    m_pDescriptor->szPort.toUtf8().data(),
			    m_pDescriptor->szLocalFileSize.toUtf8().data(), 0x01);
		}
		m_szStatusString = __tr2qs_ctx("Sent DCC RESUME request to %1, waiting for ACCEPT", "dcc").arg(m_pDescriptor->szNick);
		outputAndLog(m_szStatusString);

		// setup the resume timer: we don't want to wait forever

		if(KVI_OPTION_UINT(KviOption_uintDccSocketTimeout) < 5)
			KVI_OPTION_UINT(KviOption_uintDccSocketTimeout) = 5;

		if(m_pResumeTimer)
			delete m_pResumeTimer;
		m_pResumeTimer = new QTimer(this);
		connect(m_pResumeTimer, SIGNAL(timeout()), this, SLOT(resumeTimedOut()));
		m_pResumeTimer->setInterval(KVI_OPTION_UINT(KviOption_uintDccSocketTimeout) * 1000);
		m_pResumeTimer->setSingleShot(true);
		m_pResumeTimer->start();
	}
	else
	{
		listenOrConnect();
	}

	displayUpdate();
}

void DccFileTransfer::listenOrConnect()
{
	if(!(m_pDescriptor->bActive))
	{
#ifdef COMPILE_SSL_SUPPORT
		KviError::Code eError = m_pMarshal->dccListen(m_pDescriptor->szListenIp, m_pDescriptor->szListenPort, m_pDescriptor->bDoTimeout, m_pDescriptor->bIsSSL);
#else
		KviError::Code eError = m_pMarshal->dccListen(m_pDescriptor->szListenIp, m_pDescriptor->szListenPort, m_pDescriptor->bDoTimeout);
#endif
		if(eError != KviError::Success)
			handleMarshalError(eError);
	}
	else
	{
#ifdef COMPILE_SSL_SUPPORT
		KviError::Code eError = m_pMarshal->dccConnect(m_pDescriptor->szIp.toUtf8().data(), m_pDescriptor->szPort.toUtf8().data(), m_pDescriptor->bDoTimeout, m_pDescriptor->bIsSSL);
#else
		KviError::Code eError = m_pMarshal->dccConnect(m_pDescriptor->szIp.toUtf8().data(), m_pDescriptor->szPort.toUtf8().data(), m_pDescriptor->bDoTimeout);
#endif
		if(eError != KviError::Success)
			handleMarshalError(eError);
	}

	displayUpdate();
}

void DccFileTransfer::resumeTimedOut()
{
	if(m_pResumeTimer)
	{
		delete m_pResumeTimer;
		m_pResumeTimer = nullptr;
	}
	handleMarshalError(KviError::ConnectionTimedOut);
}

KviWindow * DccFileTransfer::dccMarshalOutputWindow()
{
	return transferWindow();
}

const char * DccFileTransfer::dccMarshalOutputContextString()
{
	return m_szTransferIdString.toUtf8().data();
}

QString DccFileTransfer::localFileName()
{
	return m_pDescriptor->szLocalFileName;
}

void DccFileTransfer::abort()
{
	if(m_pSlaveRecvThread)
		m_pSlaveRecvThread->terminate();
	if(m_pSlaveSendThread)
		m_pSlaveSendThread->terminate();
	if(m_pMarshal)
		m_pMarshal->abort();

	if(m_pDescriptor->bRecvFile)
		g_pApp->fileDownloadTerminated(false, m_pDescriptor->szFileName.toUtf8().data(), m_pDescriptor->szLocalFileName.toUtf8().data(), m_pDescriptor->szNick.toUtf8().data(), __tr_ctx("Aborted", "dcc"));

	QString tmp;

	if(m_pSlaveRecvThread)
		tmp.setNum(m_pSlaveRecvThread->receivedBytes());
	else if(m_pSlaveSendThread)
		tmp.setNum(m_pSlaveSendThread->sentBytes());
	else
		tmp = '0';

	m_eGeneralStatus = Failure;
	m_tTransferEndTime = kvi_unixTime();
	m_szStatusString = __tr2qs_ctx("Transfer failed: ", "dcc");
	m_szStatusString += __tr2qs_ctx("Aborted", "dcc");

	KVS_TRIGGER_EVENT_3(KviEvent_OnDCCFileTransferFailed, eventWindow(), QString("Aborted by user"), tmp, m_pDescriptor->idString());

	outputAndLog(KVI_OUT_DCCERROR, m_szStatusString);
	displayUpdate();
}

void DccFileTransfer::fillContextPopup(QMenu * m)
{
	m->addAction(__tr2qs_ctx("Configure Bandwidth...", "dcc"), this, SLOT(configureBandwidth()));
	m->addSeparator();
	m->addAction(__tr2qs_ctx("Resend DCC", "dcc"), this, SLOT(retryDCC()));
	m->addAction(__tr2qs_ctx("Resend TDCC", "dcc"), this, SLOT(retryTDCC()));
	m->addAction(__tr2qs_ctx("Resend RevDCC", "dcc"), this, SLOT(retryRevDCC()));
	QAction * pAction = m->addAction(__tr2qs_ctx("Abort", "dcc"), this, SLOT(abort()));
	if(!active())
		pAction->setEnabled(false);
}

void DccFileTransfer::configureBandwidth()
{
	if(m_pBandwidthDialog)
		return;
	m_pBandwidthDialog = new DccFileTransferBandwidthDialog(g_pMainWindow, this);
	connect(m_pBandwidthDialog, SIGNAL(destroyed()), this, SLOT(bandwidthDialogDestroyed()));
	m_pBandwidthDialog->setModal(true);
	m_pBandwidthDialog->show();
}

void DccFileTransfer::retryDCC()
{
	abort();
	QString szRemoteNick = m_pDescriptor->remoteNick();
	QString szFileName = m_pDescriptor->localFileName();
	QString szId = m_pDescriptor->idString();
	KviQString::escapeKvs(&szRemoteNick, KviQString::EscapeSpace);
	KviQString::escapeKvs(&szFileName, KviQString::EscapeSpace);

	QString szCommand = "dcc.send -r=$console($dcc.irccontext(" + szId + ")) " + szRemoteNick + " " + szFileName;
	KviKvsScript::run(szCommand, g_pActiveWindow);
}

void DccFileTransfer::retryTDCC()
{
	abort();
	QString szRemoteNick = m_pDescriptor->remoteNick();
	QString szFileName = m_pDescriptor->localFileName();
	QString szId = m_pDescriptor->idString();
	KviQString::escapeKvs(&szRemoteNick, KviQString::EscapeSpace);
	KviQString::escapeKvs(&szFileName, KviQString::EscapeSpace);

	QString szCommand = "dcc.send -r=$console($dcc.irccontext(" + szId + ")) -t " + szRemoteNick + " " + szFileName;
	KviKvsScript::run(szCommand, g_pActiveWindow);
}

void DccFileTransfer::retryRevDCC()
{
	abort();
	QString szRemoteNick = m_pDescriptor->remoteNick();
	QString szFileName = m_pDescriptor->localFileName();
	QString szId = m_pDescriptor->idString();
	KviQString::escapeKvs(&szRemoteNick, KviQString::EscapeSpace);
	KviQString::escapeKvs(&szFileName, KviQString::EscapeSpace);

	QString szCommand = "dcc.rsend -z -r=$console($dcc.irccontext(" + szId + ")) " + szRemoteNick + " " + szFileName;
	KviKvsScript::run(szCommand, g_pActiveWindow);
}

void DccFileTransfer::fillStatusString(QString & szBuffer)
{
	switch(m_eGeneralStatus)
	{
		case Connecting:
			szBuffer = "connecting";
			break;
		case Transferring:
			szBuffer = "transferring";
			break;
		case Failure:
			szBuffer = "failure";
			break;
		case Success:
			szBuffer = "success";
			break;
		default:
			szBuffer = "unknown";
			break;
	}
}

bool DccFileTransfer::active()
{
	return ((m_eGeneralStatus == Connecting) || (m_eGeneralStatus == Transferring));
}

int DccFileTransfer::bandwidthLimit()
{
	int iLimit = m_uMaxBandwidth; // we have the cached value anyway...
	if(m_pDescriptor->bRecvFile)
	{
		if(m_pSlaveRecvThread)
		{
			m_pSlaveRecvThread->initGetInfo();
			iLimit = (int)m_pSlaveRecvThread->bandwidthLimit();
			m_pSlaveRecvThread->doneGetInfo();
			if(iLimit < 0)
				iLimit = MAX_DCC_BANDWIDTH_LIMIT;
		}
	}
	else
	{
		if(m_pSlaveSendThread)
		{
			m_pSlaveSendThread->initGetInfo();
			iLimit = (int)m_pSlaveSendThread->bandwidthLimit();
			m_pSlaveSendThread->doneGetInfo();
			if(iLimit < 0)
				iLimit = MAX_DCC_BANDWIDTH_LIMIT;
		}
	}
	return iLimit;
}

void DccFileTransfer::setBandwidthLimit(int iVal)
{
	if(iVal < 0)
		iVal = MAX_DCC_BANDWIDTH_LIMIT;
	if(iVal > MAX_DCC_BANDWIDTH_LIMIT)
		iVal = MAX_DCC_BANDWIDTH_LIMIT;
	m_uMaxBandwidth = iVal;
	if(m_pDescriptor->bRecvFile)
	{
		if(m_pSlaveRecvThread)
		{
			m_pSlaveRecvThread->initGetInfo();
			m_pSlaveRecvThread->setBandwidthLimit(iVal);
			m_pSlaveRecvThread->doneGetInfo();
		}
	}
	else
	{
		if(m_pSlaveSendThread)
		{
			m_pSlaveSendThread->initGetInfo();
			m_pSlaveSendThread->setBandwidthLimit(iVal);
			m_pSlaveSendThread->doneGetInfo();
		}
	}
}

unsigned int DccFileTransfer::averageSpeed()
{
	unsigned int uAvgBandwidth = 0;
	if(m_pDescriptor->bRecvFile)
	{
		if(m_pSlaveRecvThread)
		{
			m_pSlaveRecvThread->initGetInfo();
			uAvgBandwidth = m_pSlaveRecvThread->averageSpeed();
			m_pSlaveRecvThread->doneGetInfo();
		}
	}
	else
	{
		if(m_pSlaveSendThread)
		{
			m_pSlaveSendThread->initGetInfo();
			uAvgBandwidth = m_pSlaveSendThread->averageSpeed();
			m_pSlaveSendThread->doneGetInfo();
		}
	}
	return uAvgBandwidth;
}

unsigned int DccFileTransfer::instantSpeed()
{
	unsigned int uInstBandwidth = 0;
	if(m_pDescriptor->bRecvFile)
	{
		if(m_pSlaveRecvThread)
		{
			m_pSlaveRecvThread->initGetInfo();
			uInstBandwidth = m_pSlaveRecvThread->instantSpeed();
			m_pSlaveRecvThread->doneGetInfo();
		}
	}
	else
	{
		if(m_pSlaveSendThread)
		{
			m_pSlaveSendThread->initGetInfo();
			uInstBandwidth = m_pSlaveSendThread->instantSpeed();
			m_pSlaveSendThread->doneGetInfo();
		}
	}
	return uInstBandwidth;
}

unsigned int DccFileTransfer::transferredBytes()
{
	unsigned int uTransferred = 0;
	if(m_pDescriptor->bRecvFile)
	{
		if(m_pSlaveRecvThread)
		{
			m_pSlaveRecvThread->initGetInfo();
			uTransferred = m_pSlaveRecvThread->filePosition();
			m_pSlaveRecvThread->doneGetInfo();
		}
	}
	else
	{
		if(m_pSlaveSendThread)
		{
			m_pSlaveSendThread->initGetInfo();
			uTransferred = m_pSlaveSendThread->filePosition();
			m_pSlaveSendThread->doneGetInfo();
		}
	}
	return uTransferred;
}

void DccFileTransfer::displayPaint(QPainter * p, int column, QRect rect)
{
	int width = rect.width(), height = rect.height();
	QString txt;
	bool bIsTerminated = ((m_eGeneralStatus == Success) || (m_eGeneralStatus == Failure));

	switch(column)
	{
		case COLUMN_TRANSFERTYPE:
		{
			if(!g_pDccFileTransferIcon)
				break;
			int xoffset = 0;
			int yoffset = 0;
			if(m_pDescriptor->bRecvFile)
				yoffset = 64;
			switch(m_eGeneralStatus)
			{
				case Connecting:
					xoffset = 0;
					break;
				case Transferring:
					xoffset = 48;
					break;
				case Success:
					xoffset = 96;
					break;
				case Failure:
					xoffset = 144;
					break;
			}
			// 48 is width, 64 is height
			p->drawPixmap(width / 2 - 48 / 2, rect.top() + height / 2 - 64 / 2, *g_pDccFileTransferIcon, xoffset, yoffset, 48, 64);
		}
		break;
		case COLUMN_FILEINFO:
		{

			QFontMetrics fm(p->font());

			QString szFrom = __tr2qs_ctx("From: ", "dcc");
			QString szTo = __tr2qs_ctx("To: ", "dcc");

			int daW1 = fm.horizontalAdvance(szFrom);
			int daW2 = fm.horizontalAdvance(szTo);
			if(daW1 < daW2)
				daW1 = daW2;
			int iLineSpacing = fm.lineSpacing();

			int iY = rect.top() + 4;

			p->setPen(Qt::black);

			KviCString szRemote(KviCString::Format, "dcc://%s@%s:%s/%s", m_pDescriptor->szNick.toUtf8().data(), m_pDescriptor->szIp.toUtf8().data(), m_pDescriptor->szPort.toUtf8().data(),
			    m_pDescriptor->szFileName.toUtf8().data());

			p->drawText(rect.left() + 4 + daW1, iY, width - (8 + daW1), height - 8, Qt::AlignTop | Qt::AlignLeft,
			    m_pDescriptor->bRecvFile ? szRemote.ptr() : m_pDescriptor->szLocalFileName.toUtf8().data());
			iY += iLineSpacing;

			p->drawText(rect.left() + 4 + daW1, iY, width - (8 + daW1), height - 8, Qt::AlignTop | Qt::AlignLeft,
			    m_pDescriptor->bRecvFile ? m_pDescriptor->szLocalFileName.toUtf8().data() : szRemote.ptr());
			iY += iLineSpacing;

			p->setPen(Qt::darkGray);

			p->drawText(rect.left() + 4, rect.top() + 4, width - 8, height - 8, Qt::AlignTop | Qt::AlignLeft, szFrom);
			p->drawText(rect.left() + 4, rect.top() + 4 + iLineSpacing, width - 8, height - 8, Qt::AlignTop | Qt::AlignLeft, szTo);

			p->setPen(QColor(180, 180, 200));

			iLineSpacing += 2;

			p->drawRect(rect.left() + 4, rect.top() + height - (iLineSpacing + 4), width - 8, iLineSpacing);
			p->fillRect(rect.left() + 5, rect.top() + height - (iLineSpacing + 3), width - 10, iLineSpacing - 2, bIsTerminated ? QColor(210, 210, 210) : QColor(190, 190, 240));

			p->setPen(Qt::black);

			p->drawText(rect.left() + 7, rect.top() + height - (iLineSpacing + 4), width - 14, iLineSpacing, Qt::AlignVCenter | Qt::AlignLeft, m_szStatusString);
		}
		break;
		case COLUMN_PROGRESS:
		{

			QFontMetrics fm(p->font());

			int iW = width - 8;
			uint uAvgBandwidth = 0;
			uint uInstantSpeed = 0;
			quint64 uAckedBytes = 0;
			quint64 uTransferred = 0;
			int iEta = -1;

			if(m_pDescriptor->bRecvFile)
			{
				if(m_pSlaveRecvThread)
				{
					m_pSlaveRecvThread->initGetInfo();
					uAvgBandwidth = m_pSlaveRecvThread->averageSpeed();
					uInstantSpeed = m_pSlaveRecvThread->instantSpeed();
					uTransferred = m_pSlaveRecvThread->filePosition();
					m_pSlaveRecvThread->doneGetInfo();
				}
			}
			else
			{
				if(m_pSlaveSendThread)
				{
					m_pSlaveSendThread->initGetInfo();
					uAvgBandwidth = m_pSlaveSendThread->averageSpeed();
					uInstantSpeed = m_pSlaveSendThread->instantSpeed();
					uTransferred = m_pSlaveSendThread->filePosition();
					uAckedBytes = m_pSlaveSendThread->ackedBytes();
					m_pSlaveSendThread->doneGetInfo();
				}
			}

			p->setPen(bIsTerminated ? Qt::lightGray : QColor(210, 210, 240));
			p->drawRect(rect.left() + 4, rect.top() + 4, iW, 12);

			iW -= 2;

			if(m_uTotalFileSize > 0)
			{
				if(uAvgBandwidth > 0)
				{
					quint64 uRemaining = m_uTotalFileSize - uTransferred;
					iEta = uRemaining / uAvgBandwidth;
				}

				if(!m_pDescriptor->bNoAcks && (uAckedBytes > 0) && (uAckedBytes < uTransferred))
				{
					// we are sending a file and are getting acks

					double dPerc1 = (double)(((double)uTransferred) * 100.0) / (double)m_uTotalFileSize;
					int iL1 = (int)((((double)iW) * dPerc1) / 100.0);
					double dPerc2 = (double)(((double)uAckedBytes) * 100.0) / (double)m_uTotalFileSize;
					int iL2 = (int)((((double)iW) * dPerc2) / 100.0);
					int iW2 = iL1 - iL2;
					if(iW2 > 0)
						p->fillRect(rect.left() + 5 + iL2, rect.top() + 5, iW2, 10, bIsTerminated ? QColor(150, 130, 110) : QColor(220, 170, 100));
					p->fillRect(rect.left() + 5, rect.top() + 5, iL2, 10, bIsTerminated ? QColor(140, 110, 110) : QColor(200, 100, 100));

					txt = QString(__tr2qs_ctx("%1 of %2 (%3%)", "dcc")).arg(KviQString::makeSizeReadable(uAckedBytes)).arg(KviQString::makeSizeReadable(m_uTotalFileSize)).arg(dPerc2, 0, 'f', 2);
				}
				else
				{
					// we are receiving a file or not sending acks
					double dPerc = (double)(((double)uTransferred) * 100.0) / (double)m_uTotalFileSize;
					int iL = (int)((((double)iW) * dPerc) / 100.0);
					p->fillRect(rect.left() + 5, rect.top() + 5, iL, 10, bIsTerminated ? QColor(140, 110, 110) : QColor(200, 100, 100));

					txt = QString(__tr2qs_ctx("%1 of %2 (%3%)", "dcc")).arg(KviQString::makeSizeReadable(uTransferred)).arg(KviQString::makeSizeReadable(m_uTotalFileSize)).arg(dPerc, 0, 'f', 2);
				}
			}
			else
			{
				txt = QString(__tr2qs_ctx("%1", "dcc")).arg(KviQString::makeSizeReadable(uTransferred));
			}

			p->setPen(Qt::black);

			p->drawText(rect.left() + 4, rect.top() + 19, width - 8, height - 8, Qt::AlignTop | Qt::AlignLeft, txt);

			int iLeftHalf = (iW - 2) / 2;
			int iRightHalf = iW - (iLeftHalf + 1);
			int iLineSpacing = fm.lineSpacing() + 2;

			if(!bIsTerminated)
			{
				txt = __tr2qs_ctx("Speed:", "dcc");
				txt += " ";

				QString tmpisp;
				KviNetUtils::formatNetworkBandwidthString(tmpisp, uInstantSpeed);
				txt += tmpisp;
				txt += " [";
			}
			else
			{
				txt = "";
			}

			txt += __tr2qs_ctx("Average:", "dcc");
			txt += " ";
			QString tmpspd;
			KviNetUtils::formatNetworkBandwidthString(tmpspd, uAvgBandwidth);
			txt += tmpspd;

			if(!bIsTerminated)
			{
				txt += "]";
			}

			int iDaH = height - (iLineSpacing + 4);

			p->setPen(QColor(180, 180, 200));
			p->drawRect(rect.left() + 4, rect.top() + iDaH, iLeftHalf, iLineSpacing);
			p->fillRect(rect.left() + 5, rect.top() + iDaH + 1, iLeftHalf - 2, iLineSpacing - 2, bIsTerminated ? QColor(210, 210, 210) : QColor(190, 190, 240));
			p->setPen(bIsTerminated ? Qt::darkGray : Qt::black);
			p->drawText(rect.left() + 6, rect.top() + iDaH, iLeftHalf - 4, iLineSpacing, Qt::AlignLeft | Qt::AlignVCenter, txt);

			if(bIsTerminated)
			{
				if((m_tTransferStartTime != 0) && (m_tTransferEndTime != 0))
				{
					QString tot = KviTimeUtils::formatTimeInterval(kvi_timeSpan(m_tTransferEndTime, m_tTransferStartTime), KviTimeUtils::NoLeadingEmptyIntervals | KviTimeUtils::NoLeadingZeroes);
					txt = "Total: ";
					txt += tot;
				}
				else
				{
					txt = "";
				}
			}
			else
			{
				if(iEta >= 0)
				{
					QString eta = KviTimeUtils::formatTimeInterval(iEta, KviTimeUtils::NoLeadingEmptyIntervals | KviTimeUtils::NoLeadingZeroes);
					txt = "ETA: ";
					txt += eta;
				}
				else
				{
					txt = "ETA: ?";
				}
			}

			p->setPen(QColor(180, 180, 200));
			p->drawRect(rect.left() + width - (4 + iRightHalf), rect.top() + iDaH, iRightHalf, iLineSpacing);
			p->fillRect(rect.left() + width - (3 + iRightHalf), rect.top() + iDaH + 1, iRightHalf - 2, iLineSpacing - 2, bIsTerminated ? QColor(210, 210, 210) : QColor(190, 190, 240));
			p->setPen(bIsTerminated ? Qt::darkGray : Qt::black);
			p->drawText(rect.left() + width - (2 + iRightHalf), rect.top() + iDaH, iRightHalf - 4, iLineSpacing, Qt::AlignLeft | Qt::AlignVCenter, txt);
		}
		break;
	}
}

int DccFileTransfer::displayHeight(int iLineSpacing)
{
	int iH = (iLineSpacing * 3) + 10;
	return iH >= 70 ? iH : 70;
}

QString DccFileTransfer::tipText()
{
	QString s;

	s = QString(R"(<table><tr><td bgcolor="#000000"><font color="#FFFFFF"><b>DCC %1 (ID %2)</b></font></td></tr>)").arg(m_szDccType.ptr()).arg(id());

	s += R"(<tr><td bgcolor="#404040"><font color="#FFFFFF">)";
	s += __tr2qs_ctx("Transfer Log", "dcc");
	s += "</font></td></tr>";
	s += "<tr><td bgcolor=\"#C0C0C0\">";
	s += m_szTransferLog;
	s += "</td></tr>";
	s += "<table>";

	return s;
}

void DccFileTransfer::init()
{
	if(g_pDccFileTransfers)
		return;
	g_pDccFileTransfers = new KviPointerList<DccFileTransfer>;
	g_pDccFileTransfers->setAutoDelete(false);

	QPixmap * pix = g_pIconManager->getImage("kvi_dccfiletransfericons.png", false);
	if(pix)
		g_pDccFileTransferIcon = new QPixmap(*pix);
	else
		g_pDccFileTransferIcon = nullptr;
}

void DccFileTransfer::done()
{
	if(!g_pDccFileTransfers)
		return;
	while(DccFileTransfer * t = g_pDccFileTransfers->first())
		delete t;
	delete g_pDccFileTransfers;
	g_pDccFileTransfers = nullptr;
	if(g_pDccFileTransferIcon)
		delete g_pDccFileTransferIcon;
	g_pDccFileTransferIcon = nullptr;
}

unsigned int DccFileTransfer::transferCount()
{
	if(!g_pDccFileTransfers)
		return 0;
	return g_pDccFileTransfers->count();
}

DccFileTransfer * DccFileTransfer::nonFailedTransferWithLocalFileName(const QString & szLocalFileName)
{
	if(!g_pDccFileTransfers)
		return nullptr;
	for(DccFileTransfer * t = g_pDccFileTransfers->first(); t; t = g_pDccFileTransfers->next())
	{
#if defined(COMPILE_ON_WINDOWS) || defined(COMPILE_ON_MINGW)
		// on windows the file names are case insensitive
		if(t->localFileName().toLower() == szLocalFileName.toLower())
#else
		if(t->localFileName() == szLocalFileName)
#endif
		{
			if(t->m_eGeneralStatus != Failure)
				return t;
		}
	}
	return nullptr;
}

unsigned int DccFileTransfer::runningTransfersCount()
{
	if(!g_pDccFileTransfers)
		return 0;
	unsigned int cnt = 0;
	for(DccFileTransfer * t = g_pDccFileTransfers->first(); t; t = g_pDccFileTransfers->next())
	{
		if(t->active())
			cnt++;
	}
	return cnt;
}

bool DccFileTransfer::handleResumeAccepted(const char * filename, const char * port, const char * szZeroPortTag)
{
	if(!g_pDccFileTransfers)
		return false;

	for(DccFileTransfer * t = g_pDccFileTransfers->first(); t; t = g_pDccFileTransfers->next())
	{
		if(t->resumeAccepted(filename, port, szZeroPortTag))
			return true;
	}

	return false;
}

bool DccFileTransfer::handleResumeRequest(const char * filename, const char * port, quint64 filePos)
{
	if(!g_pDccFileTransfers)
		return false;

	for(DccFileTransfer * t = g_pDccFileTransfers->first(); t; t = g_pDccFileTransfers->next())
	{
		if(t->doResume(filename, port, filePos))
			return true;
	}

	return false;
}

void DccFileTransfer::outputAndLog(const QString & s)
{
	KviWindow * out = transferWindow();
	addToTransferLog(s);
	if(out)
		out->output(KVI_OUT_DCCMSG, "[%Q]: %Q", &m_szTransferIdString, &s);
}

void DccFileTransfer::outputAndLog(int msgtype, const QString & s)
{
	KviWindow * out = transferWindow();
	addToTransferLog(s);
	if(out)
		out->output(msgtype, "[%Q]: %Q", &m_szTransferIdString, &s);
}

void DccFileTransfer::addToTransferLog(const QString & s)
{
	QDateTime dt = QDateTime::currentDateTime();
	QString ts;
	ts = QString::asprintf("[%4d.%2d.%2d %2d:%2d:%2d] ", dt.date().year(), dt.date().month(), dt.date().day(), dt.time().hour(), dt.time().minute(), dt.time().second());
	m_szTransferLog += ts + s;
	m_szTransferLog += "<br>";
}

void DccFileTransfer::connectionInProgress()
{
	if(m_pDescriptor->bActive)
	{
		// ACTIVE CONNECTION
		//		if((kvi_strEqualCS(m_szDccType.ptr(), "RECV")) || (kvi_strEqualCS(m_szDccType.ptr(),"TRECV")))
		//		{
		//			// FIXME: that's not true!... we're NOT connected here
		//			if(TRIGGER_EVENT_5PARAM_RETVALUE(KviEvent_OnDCCGetConnected,this,m_pDescriptor->szPort.ptr(),m_pDescriptor->szFileName.ptr(),m_pDescriptor->szNick.ptr(),m_pDescriptor->szUser.ptr(),m_pDescriptor->szHost.ptr()));
		//		} else {
		//			if(TRIGGER_EVENT_5PARAM_RETVALUE(KviEvent_OnDCCSendConnected,this,m_pDescriptor->szPort.ptr(),m_pDescriptor->szFileName.ptr(),m_pDescriptor->szNick.ptr(),m_pDescriptor->szUser.ptr(),m_pDescriptor->szHost.ptr()));
		//		}
		//
		m_szStatusString = __tr2qs_ctx("Contacting host %1 on port %2", "dcc").arg(m_pDescriptor->szIp, m_pDescriptor->szPort);
		outputAndLog(m_szStatusString);
		displayUpdate();
		return;
	}

	// PASSIVE CONNECTION
	m_szStatusString = __tr2qs_ctx("Listening on interface %1 port %2", "dcc").arg(m_pMarshal->localIp(), m_pMarshal->localPort());
	outputAndLog(m_szStatusString);

	if(m_pDescriptor->bSendRequest)
	{
		QString ip;
		if(!m_pDescriptor->szFakeIp.isEmpty())
		{
			ip = m_pDescriptor->szFakeIp;
		}
		else
		{
			ip = m_pDescriptor->szListenIp;

			if(KVI_OPTION_BOOL(KviOption_boolDccGuessIpFromServerWhenLocalIsUnroutable))
			{
				if(!KviNetUtils::isRoutableIpString(ip))
				{
					// try to get the IP that the IRC server can see
					if(m_pDescriptor->console())
					{
						QString tmp = m_pDescriptor->console()->connection() ? m_pDescriptor->console()->connection()->userInfo()->hostIp() : "";
						if(!tmp.isEmpty())
						{
							ip = tmp;
							outputAndLog(__tr2qs_ctx("The local IP address is private, determining from IRC server: %1", "dcc").arg(ip));
						}
						else
						{
							outputAndLog(__tr2qs_ctx("The local IP address is private, but unable to determine it from the IRC server", "dcc"));
						}
					}
					else
					{
						outputAndLog(__tr2qs_ctx("The local IP address is private, but have no IRC server to determine it from", "dcc"));
					}
				}
			}
		}

		KviCString port = !m_pDescriptor->szFakePort.isEmpty() ? m_pDescriptor->szFakePort : m_pMarshal->localPort();
		//#warning "OPTION FOR SENDING 127.0.0.1 and so on (not an unsigned nuumber)"
		struct in_addr a;
		if(KviNetUtils::stringIpToBinaryIp(ip, &a))
			ip.setNum(htonl(a.s_addr));

		QString tmp = m_pDescriptor->szFileName;
		// just to be sure
		KviQString::cutToLast(tmp, '/');
		KviQString::cutToLast(tmp, '\\');

		QString fName;

		// BUG-TO-BUG mIrc compatibility
		if(KVI_OPTION_BOOL(KviOption_boolDCCFileTransferReplaceOutgoingSpacesWithUnderscores))
			tmp.replace(" ", "_");

		KviIrcServerParser::encodeCtcpParameter(tmp.toUtf8().data(), fName);
		// Zero port requests want DCC SEND as back-request
		KviCString szReq;

		if(m_pDescriptor->isZeroPortRequest())
		{
			szReq = "SEND";
			if(m_pDescriptor->bIsTdcc)
				szReq.prepend("T");
#ifdef COMPILE_SSL_SUPPORT
			if(m_pDescriptor->bIsSSL)
				szReq.prepend("S");
#endif
			m_pDescriptor->console()->connection()->sendFmtData("PRIVMSG %s :%cDCC %s %s %s %s %s %s%c",
			    m_pDescriptor->console()->connection()->encodeText(m_pDescriptor->szNick).data(),
			    0x01,
			    m_pDescriptor->console()->connection()->encodeText(szReq.ptr()).data(),
			    m_pDescriptor->console()->connection()->encodeText(fName).data(),
			    ip.toUtf8().data(), port.ptr(),
			    m_pDescriptor->szFileSize.toUtf8().data(), m_pDescriptor->zeroPortRequestTag(), 0x01);
		}
		else
		{
			szReq = m_szDccType;
			m_pDescriptor->console()->connection()->sendFmtData("PRIVMSG %s :%cDCC %s %s %s %s %Q%c",
			    m_pDescriptor->console()->connection()->encodeText(m_pDescriptor->szNick).data(),
			    0x01,
			    m_pDescriptor->console()->connection()->encodeText(szReq.ptr()).data(),
			    m_pDescriptor->console()->connection()->encodeText(fName).data(),
			    ip.toUtf8().data(), port.ptr(),
			    &(m_pDescriptor->szLocalFileSize), 0x01);
		}
		outputAndLog(__tr2qs_ctx("Sent DCC %1 request to %2, waiting for remote client to connect...", "dcc").arg(szReq.ptr(), m_pDescriptor->szNick));
	}
	else
	{
		outputAndLog(__tr2qs_ctx("DCC %1 request not sent, awaiting manual connection", "dcc").arg(m_szDccType.ptr()));
	}

	KVS_TRIGGER_EVENT_1(KviEvent_OnDCCFileTransferConnectionInProgress, eventWindow(), m_pDescriptor->idString());

	displayUpdate();
}

void DccFileTransfer::startingSSLHandshake()
{
#ifdef COMPILE_SSL_SUPPORT
	outputAndLog(KVI_OUT_SSL, __tr2qs_ctx("Low-level transport connection established", "dcc"));
	outputAndLog(KVI_OUT_SSL, __tr2qs_ctx("Starting Secure Socket Layer handshake", "dcc"));
#endif
}

void DccFileTransfer::sslError(const char * msg)
{
#ifdef COMPILE_SSL_SUPPORT
	outputAndLog(KVI_OUT_DCCERROR, __tr2qs_ctx("[SSL ERROR]: %1", "dcc").arg(msg));
#endif
}

bool DccFileTransfer::event(QEvent * e)
{
	if(e->type() == KVI_THREAD_EVENT)
	{
		switch(((KviThreadEvent *)e)->id())
		{
			case KVI_DCC_THREAD_EVENT_ERROR:
			{
				KviError::Code * pError = ((KviThreadDataEvent<KviError::Code> *)e)->getData();
				QString szErrorString = KviError::getDescription(*pError);
				delete pError;
				if(m_pDescriptor->bRecvFile)
					g_pApp->fileDownloadTerminated(false, m_pDescriptor->szFileName.toUtf8().data(), m_pDescriptor->szLocalFileName.toUtf8().data(), m_pDescriptor->szNick.toUtf8().data(), szErrorString.toUtf8().data());

				m_szStatusString = __tr2qs_ctx("Transfer failed: ", "dcc");
				m_szStatusString += szErrorString;
				m_eGeneralStatus = Failure;
				m_tTransferEndTime = kvi_unixTime();

				KVS_TRIGGER_EVENT_3(KviEvent_OnDCCFileTransferFailed,
				    eventWindow(),
				    szErrorString,
				    (kvs_int_t)(m_pSlaveRecvThread ? m_pSlaveRecvThread->receivedBytes() : m_pSlaveSendThread->sentBytes()),
				    m_pDescriptor->idString());

				outputAndLog(KVI_OUT_DCCERROR, m_szStatusString);
				displayUpdate();
				return true;
			}
			break;
			case KVI_DCC_THREAD_EVENT_SUCCESS:
			{
				// FIXME: for >= 3.2.0 change this text to
				// File Upload/Download terminated, or something like this
				if(KVI_OPTION_BOOL(KviOption_boolNotifyDccSendSuccessInConsole))
				{
					KviConsoleWindow * c;
					if(!g_pApp->windowExists(m_pDescriptor->console()))
						c = g_pApp->activeConsole();
					else
						c = m_pDescriptor->console();
					c->output(KVI_OUT_DCCMSG, __tr2qs_ctx("DCC %s transfer with %Q@%Q:%Q completed: \r![!dbl]%Q\r%Q\r", "dcc"),
					    m_pDescriptor->bIsTdcc ? (m_pDescriptor->bRecvFile ? "TRECV" : "TSEND") : (m_pDescriptor->bRecvFile ? "RECV" : "SEND"),
					    &(m_pDescriptor->szNick), &(m_pDescriptor->szIp), &(m_pDescriptor->szPort),
					    &(KVI_OPTION_STRING(KviOption_stringUrlFileCommand)),
					    &(m_pDescriptor->szLocalFileName));
				}

				if(m_pDescriptor->bRecvFile)
					g_pApp->fileDownloadTerminated(true, m_pDescriptor->szFileName.toUtf8().data(), m_pDescriptor->szLocalFileName.toUtf8().data(), m_pDescriptor->szNick.toUtf8().data());
				m_szStatusString = __tr2qs_ctx("Transfer completed", "dcc");
				outputAndLog(m_szStatusString);
				m_eGeneralStatus = Success;
				m_tTransferEndTime = kvi_unixTime();
				if(m_pResumeTimer)
					delete m_pResumeTimer;

				KVS_TRIGGER_EVENT_2(KviEvent_OnDCCFileTransferSuccess,
				    eventWindow(),
				    (kvs_int_t)(m_pSlaveRecvThread ? m_pSlaveRecvThread->receivedBytes() : m_pSlaveSendThread->sentBytes()),
				    m_pDescriptor->idString());

				displayUpdate();

				if(KVI_OPTION_BOOL(KviOption_boolAutoCloseDccSendOnSuccess))
					die();
				return true;
			}
			break;
			case KVI_DCC_THREAD_EVENT_MESSAGE:
			{
				KviCString * str = ((KviThreadDataEvent<KviCString> *)e)->getData();
				outputAndLog(QString(__tr_no_xgettext_ctx(str->ptr(), "dcc")));
				delete str;
				return true;
			}
			break;
			default:
				qDebug("Invalid event type %d received", ((KviThreadEvent *)e)->id());
				break;
		}
	}
	return KviFileTransfer::event(e);
}

void DccFileTransfer::handleMarshalError(KviError::Code eError)
{
	QString szErr = KviError::getDescription(eError);
	m_eGeneralStatus = Failure;
	m_szStatusString = __tr2qs_ctx("Transfer failed: ", "dcc");
	m_szStatusString += szErr;
	outputAndLog(m_szStatusString);
	KVS_TRIGGER_EVENT_3(KviEvent_OnDCCFileTransferFailed, eventWindow(), szErr, (kvs_int_t)0, m_pDescriptor->idString());
	displayUpdate();
}

void DccFileTransfer::connected()
{
	outputAndLog(__tr2qs_ctx("Connected to %1:%2", "dcc").arg(m_pMarshal->remoteIp(), m_pMarshal->remotePort()));
	outputAndLog(__tr2qs_ctx("Local end is %1:%2", "dcc").arg(m_pMarshal->localIp(), m_pMarshal->localPort()));

	m_tTransferStartTime = kvi_unixTime();

	if(!(m_pDescriptor->bActive))
	{
		m_pDescriptor->szIp = m_pMarshal->remoteIp();
		m_pDescriptor->szPort = m_pMarshal->remotePort();
		m_pDescriptor->szHost = m_pMarshal->remoteIp();
	}

	if(m_pDescriptor->bRecvFile)
	{
		KviDccRecvThreadOptions * o = new KviDccRecvThreadOptions;
		o->szFileName = m_pDescriptor->szLocalFileName.toUtf8().data();
		bool bOk;
		o->uTotalFileSize = m_pDescriptor->szFileSize.toULongLong(&bOk);
		if(!bOk)
			o->uTotalFileSize = 0;
		o->bResume = m_pDescriptor->bResume;
		o->iIdleStepLengthInMSec = KVI_OPTION_BOOL(KviOption_boolDccSendForceIdleStep) ? KVI_OPTION_UINT(KviOption_uintDccSendIdleStepInMSec) : 0;
		o->bIsTdcc = m_pDescriptor->bIsTdcc;
		o->bSendZeroAck = KVI_OPTION_BOOL(KviOption_boolSendZeroAckInDccRecv);
		o->bSend64BitAck = KVI_OPTION_BOOL(KviOption_boolSend64BitAckInDccRecv);
		o->bNoAcks = m_pDescriptor->bNoAcks;
		o->uMaxBandwidth = m_uMaxBandwidth;
		m_pSlaveRecvThread = new DccRecvThread(this, m_pMarshal->releaseSocket(), o);

#ifdef COMPILE_SSL_SUPPORT
		KviSSL * s = m_pMarshal->releaseSSL();
		if(s)
		{
			m_pSlaveRecvThread->setSSL(s);
		}
#endif
		m_pSlaveRecvThread->start();
	}
	else
	{
		KviDccSendThreadOptions * o = new KviDccSendThreadOptions;
		o->szFileName = m_pDescriptor->szLocalFileName.toUtf8().data();
		o->bFastSend = KVI_OPTION_BOOL(KviOption_boolUseFastDccSend);
		o->iIdleStepLengthInMSec = KVI_OPTION_BOOL(KviOption_boolDccSendForceIdleStep) ? KVI_OPTION_UINT(KviOption_uintDccSendIdleStepInMSec) : 0;
		bool bOk;
		o->bIsTdcc = m_pDescriptor->bIsTdcc;
		o->uStartPosition = m_pDescriptor->szFileSize.toULongLong(&bOk);
		if(!bOk)
			o->uStartPosition = 0;
		o->iPacketSize = KVI_OPTION_UINT(KviOption_uintDccSendPacketSize);
		if(o->iPacketSize < 32)
			o->iPacketSize = 32;
		o->uMaxBandwidth = m_uMaxBandwidth;
		o->bNoAcks = m_pDescriptor->bNoAcks;
		m_pSlaveSendThread = new DccSendThread(this, m_pMarshal->releaseSocket(), o);
#ifdef COMPILE_SSL_SUPPORT
		KviSSL * s = m_pMarshal->releaseSSL();
		if(s)
		{
			m_pSlaveSendThread->setSSL(s);
		}
#endif
		m_pSlaveSendThread->start();
	}

	m_eGeneralStatus = Transferring;
	m_szStatusString = __tr2qs_ctx("Transferring data", "dcc");

	KVS_TRIGGER_EVENT_1(KviEvent_OnDCCFileTransferBegin, eventWindow(), m_pDescriptor->idString());

	outputAndLog(m_szStatusString);
	displayUpdate();
}

bool DccFileTransfer::resumeAccepted(const char * filename, const char * port, const char * szZeroPortTag)
{
	if(!(kvi_strEqualCI(filename, m_pDescriptor->szFileName.toUtf8().data()) || KVI_OPTION_BOOL(KviOption_boolAcceptBrokenFileNameDccResumeRequests)))
		return false;

	if(!(kvi_strEqualCI(port, m_pDescriptor->szPort.toUtf8().data()) && (!m_pSlaveRecvThread) && m_pDescriptor->bResume && m_pDescriptor->bRecvFile && m_pResumeTimer))
		return false;

	if(kvi_strEqualCI(port, "0"))
	{
		if(!kvi_strEqualCI(szZeroPortTag, m_pDescriptor->zeroPortRequestTag()))
			return false;
	}

	delete m_pResumeTimer;
	m_pResumeTimer = nullptr;

	outputAndLog(__tr2qs_ctx("RESUME accepted, transfer will begin at position %1", "dcc").arg(m_pDescriptor->szLocalFileSize));

	listenOrConnect();

	return true;
}

bool DccFileTransfer::doResume(const char * filename, const char * port, quint64 filePos)
{
	if(m_pSlaveRecvThread)
		return false; // we're already receiving stuff...
	if(m_pSlaveSendThread)
		return false; // we're already sending stuff...

	if(m_pDescriptor->bRecvFile)
		return false; // we're receiving... can't resume anything

	bool bFileNameMatches = KviQString::equalCI(filename, m_pDescriptor->szFileName);
	bool bPortMatches = KviQString::equalCI(port, m_pMarshal->dccPort());

	if(!bPortMatches)
	{
		// port doesn't match
		if(!bFileNameMatches)
			return false; // neither filename nor port match

		if(!KVI_OPTION_BOOL(KviOption_boolAcceptMismatchedPortDccResumeRequests))
			return false;

		// hmm.. try to accept a mismatched port request

		if(_OUTPUT_VERBOSE)
			outputAndLog(KVI_OUT_DCCMSG, __tr2qs_ctx("Processing RESUME request with mismatched port (%1)", "dcc").arg(port));
	}

	// port matches

	if(!bFileNameMatches)
	{
		if(!KVI_OPTION_BOOL(KviOption_boolAcceptBrokenFileNameDccResumeRequests))
		{
			if(_OUTPUT_VERBOSE)
				outputAndLog(
				    KVI_OUT_DCCMSG,
				    __tr2qs_ctx("Invalid RESUME request: invalid file name (got '%1' but should be '%2')", "dcc")
				        .arg(filename)
				        .arg(m_pDescriptor->szFileName));
			return false; // bad file name
		}

		if(_OUTPUT_VERBOSE)
			outputAndLog(KVI_OUT_DCCMSG, __tr2qs_ctx("Processing RESUME request with broken filename (%1)", "dcc").arg(filename));
	}

	bool bOk;
	quint64 iLocalFileSize = m_pDescriptor->szLocalFileSize.toULongLong(&bOk);

	if(!bOk)
	{
		// ops...internal error
		outputAndLog(KVI_OUT_DCCERROR, __tr2qs_ctx("Internal error in RESUME request", "dcc"));
		return false;
	}

	if(iLocalFileSize <= filePos)
	{
		outputAndLog(KVI_OUT_DCCERROR, __tr2qs_ctx("Invalid RESUME request: position %1 is larger than file size", "dcc").arg(filePos));
		return false;
	}

	outputAndLog(KVI_OUT_DCCERROR, __tr2qs_ctx("Accepting RESUME request, transfer will begin at position %1", "dcc").arg(filePos));

	m_pDescriptor->szFileSize.setNum(filePos);

	KviCString szBuffy;
	KviIrcServerParser::encodeCtcpParameter(filename, szBuffy);

	m_pDescriptor->console()->connection()->sendFmtData("PRIVMSG %s :%cDCC ACCEPT %s %s %s%c",
	    m_pDescriptor->console()->connection()->encodeText(m_pDescriptor->szNick).data(),
	    0x01,
	    m_pDescriptor->console()->connection()->encodeText(szBuffy.ptr()).data(),
	    port,
	    m_pDescriptor->console()->connection()->encodeText(QString::number(filePos)).data(),
	    0x01);

	return true;
}

DccThread * DccFileTransfer::getSlaveThread()
{
	if(m_pDescriptor->bRecvFile)
	{
		return m_pSlaveRecvThread;
	}
	else
	{
		return m_pSlaveSendThread;
	}
}

DccFileTransferBandwidthDialog::DccFileTransferBandwidthDialog(QWidget * pParent, DccFileTransfer * t)
    : QDialog(pParent)
{
	QGridLayout * g = new QGridLayout(this);

	m_pTransfer = t;
	int iVal = m_pTransfer->bandwidthLimit();

	QString szText = __tr2qs_ctx("Configure Bandwidth for DCC Transfer %1", "dcc").arg(t->id());
	setWindowTitle(szText);

	szText = t->isFileUpload() ? __tr2qs_ctx("Limit upload bandwidth to:", "dcc") : __tr2qs_ctx("Limit download bandwidth to:", "dcc");

	m_pEnableLimitCheck = new QCheckBox(szText, this);
	g->addWidget(m_pEnableLimitCheck, 0, 0);

	m_pEnableLimitCheck->setChecked((iVal >= 0) && (iVal < MAX_DCC_BANDWIDTH_LIMIT));

	m_pLimitBox = new QSpinBox(this);
	m_pLimitBox->setMinimum(0);
	m_pLimitBox->setMaximum(MAX_DCC_BANDWIDTH_LIMIT - 1);
	m_pLimitBox->setSingleStep(1);

	m_pLimitBox->setEnabled((iVal >= 0) && (iVal < MAX_DCC_BANDWIDTH_LIMIT));
	connect(m_pEnableLimitCheck, SIGNAL(toggled(bool)), m_pLimitBox, SLOT(setEnabled(bool)));
	g->addWidget(m_pLimitBox, 0, 1, 1, 2);

	szText = " ";
	szText += __tr2qs_ctx("bytes/sec", "dcc");
	m_pLimitBox->setSuffix(szText);
	m_pLimitBox->setValue(iVal < MAX_DCC_BANDWIDTH_LIMIT ? iVal : 0);

	QPushButton * pb = new QPushButton(__tr2qs_ctx("OK", "dcc"), this);
	connect(pb, SIGNAL(clicked()), this, SLOT(okClicked()));
	pb->setMinimumWidth(80);
	g->addWidget(pb, 2, 2);

	pb = new QPushButton(__tr2qs_ctx("Cancel", "dcc"), this);
	connect(pb, SIGNAL(clicked()), this, SLOT(cancelClicked()));
	pb->setMinimumWidth(80);
	g->addWidget(pb, 2, 1);

	g->setColumnStretch(0, 1);
	g->setRowStretch(1, 1);
}

DccFileTransferBandwidthDialog::~DccFileTransferBandwidthDialog()
    = default;

void DccFileTransferBandwidthDialog::okClicked()
{
	int iVal = MAX_DCC_BANDWIDTH_LIMIT;
	if(m_pEnableLimitCheck->isChecked())
	{
		iVal = m_pLimitBox->value();
		if(iVal < 0)
			iVal = MAX_DCC_BANDWIDTH_LIMIT;
		if(iVal > MAX_DCC_BANDWIDTH_LIMIT)
			iVal = MAX_DCC_BANDWIDTH_LIMIT;
	}
	m_pTransfer->setBandwidthLimit(iVal);
	delete this;
}

void DccFileTransferBandwidthDialog::cancelClicked()
{
	delete this;
}

void DccFileTransferBandwidthDialog::closeEvent(QCloseEvent * e)
{
	e->ignore();
	delete this;
}
