aboutsummaryrefslogtreecommitdiffstats
path: root/src/modules/dcc/DccFileTransfer.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/modules/dcc/DccFileTransfer.cpp')
-rw-r--r--src/modules/dcc/DccFileTransfer.cpp2248
1 files changed, 2248 insertions, 0 deletions
diff --git a/src/modules/dcc/DccFileTransfer.cpp b/src/modules/dcc/DccFileTransfer.cpp
new file mode 100644
index 000000000..4fd0ed4a2
--- /dev/null
+++ b/src/modules/dcc/DccFileTransfer.cpp
@@ -0,0 +1,2248 @@
+//=============================================================================
+//
+// 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 opinion) 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 "DccBroker.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 "KviMemory.h"
+#include "KviThread.h"
+#include "KviIrcSocket.h"
+#include "KviMediaManager.h"
+#include "kvi_socket.h"
+#include "KviKvsEventTriggers.h"
+#include "KviParameterList.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>
+
+#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 = 0;
+static QPixmap * g_pDccFileTransferIcon = 0;
+
+//#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 = 0;
+ 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(int filePos)
+{
+ quint32 size = htonl(filePos & 0xffffffff);
+ int iRet=0;
+#ifdef COMPILE_SSL_SUPPORT
+ if(m_pSSL)
+ {
+ iRet = m_pSSL->write((char*)(&size),4);
+ } else {
+#endif
+ iRet = kvi_socket_send(m_fd,(void *)(&size),4);
+#ifdef COMPILE_SSL_SUPPORT
+ }
+#endif
+
+ if(iRet != 4)
+ {
+ 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
+
+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()));
+
+ 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()))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()))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:
+ readLen = 0;
+ break;
+ 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;
+ }
+ }
+#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;
+ }
+ }
+ if(!handleInvalidSocketRead(readLen))break;
+ }
+ } 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 = 0;
+ }
+
+#ifdef COMPILE_SSL_SUPPORT
+ if(m_pSSL)
+ {
+ KviSSLMaster::freeSSL(m_pSSL);
+ m_pSSL = 0;
+ }
+#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;
+ int 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*4u*1024u*1024u*1024u) + iNewAck;
+ } else {
+
+ uTotLastAck = iNewAck;
+ }
+ iBytesInAckBuffer = 0;
+ }
+ } else {
+#ifdef COMPILE_SSL_SUPPORT
+ if(m_pSSL)
+ {
+ // ssl error....?
+ switch(m_pSSL->getProtocolError(readLen))
+ {
+ case KviSSL::ZeroReturn:
+ readLen = 0;
+ break;
+ 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;
+ }
+ }
+#endif
+
+ if(!handleInvalidSocketRead(readLen))break;
+ }
+
+ // 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::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;
+ }
+ }
+#endif
+
+ if(!handleInvalidSocketRead(readLen))break;
+ } 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::WantWrite:
+ case KviSSL::WantRead:
+ // Async continue...
+ goto handle_system_error;
+ break;
+ case KviSSL::SyscallError:
+ if(written == 0)
+ {
+ raiseSSLError();
+ postErrorEvent(KviError::RemoteEndClosedConnection);
+ goto exit_dcc;
+ } else {
+ 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;
+ }
+ }
+#endif
+
+ // error ?
+ if(!handleInvalidSocketRead(written))break;
+
+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 = 0;
+
+#ifdef COMPILE_SSL_SUPPORT
+ if(m_pSSL)
+ {
+ KviSSLMaster::freeSSL(m_pSSL);
+ m_pSSL = 0;
+ }
+#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 = 0;
+ m_pBandwidthDialog = 0;
+
+ KviQString::sprintf(m_szTransferIdString,__tr2qs_ctx("TRANSFER %d","dcc"),id());
+
+ m_pDescriptor = dcc;
+ m_pDescriptor->setTransfer(this);
+
+ m_pMarshal = new DccMarshal(this);
+
+ connect(m_pMarshal,SIGNAL(error(int)),this,SLOT(handleMarshalError(int)));
+ 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 = 0;
+ m_pSlaveSendThread = 0;
+
+ 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 = 0;
+ }
+
+ if(m_pSlaveSendThread)
+ {
+ m_pSlaveSendThread->terminate();
+ delete m_pSlaveSendThread;
+ m_pSlaveSendThread = 0;
+ }
+
+ KviThreadManager::killPendingEvents(this);
+
+ delete m_pDescriptor;
+ delete m_pMarshal;
+}
+
+void DccFileTransfer::bandwidthDialogDestroyed()
+{
+ m_pBandwidthDialog = 0;
+}
+
+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 = 0;
+ }
+ 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(KviTalPopupMenu * m)
+{
+ m->insertItem(__tr2qs_ctx("Configure Bandwidth...","dcc"),this,SLOT(configureBandwidth()));
+ m->insertSeparator();
+ m->insertItem(__tr2qs_ctx("Resend DCC","dcc"),this,SLOT(retryDCC()));
+ m->insertItem(__tr2qs_ctx("Resend TDCC","dcc"),this,SLOT(retryTDCC()));
+ m->insertItem(__tr2qs_ctx("Resend RevDCC","dcc"),this,SLOT(retryRevDCC()));
+ int id = m->insertItem(__tr2qs_ctx("Abort","dcc"),this,SLOT(abort()));
+ if(!active())m->setItemEnabled(id,false);
+}
+
+void DccFileTransfer::configureBandwidth()
+{
+ if(m_pBandwidthDialog)return;
+ m_pBandwidthDialog = new DccFileTransferBandwidthDialog(g_pFrame,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::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;
+ }
+ p->drawPixmap(rect.left() + 3,rect.top() + 3,*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.width(szFrom);
+ int daW2 = fm.width(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), 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), 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("Spd:","dcc");
+ txt += " ";
+
+ QString tmpisp;
+ KviNetUtils::formatNetworkBandwidthString(tmpisp,uInstantSpeed);
+ txt += tmpisp;
+ txt += " [";
+ } else {
+ txt = "";
+ }
+
+ txt += __tr2qs_ctx("Avg:","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 = "TOT: ";
+ 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("<table><tr><td bgcolor=\"#000000\"><font color=\"#FFFFFF\"><b>DCC %1 (ID %2)</b></font></td></tr>").arg(m_szDccType.ptr()).arg(id());
+
+ s += "<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 = 0;
+}
+
+void DccFileTransfer::done()
+{
+ if(!g_pDccFileTransfers)return;
+ while(DccFileTransfer * t = g_pDccFileTransfers->first())
+ delete t;
+ delete g_pDccFileTransfers;
+ g_pDccFileTransfers = 0;
+ if(g_pDccFileTransferIcon)delete g_pDccFileTransferIcon;
+ g_pDccFileTransferIcon = 0;
+}
+
+unsigned int DccFileTransfer::transferCount()
+{
+ if(!g_pDccFileTransfers)return 0;
+ return g_pDccFileTransfers->count();
+}
+
+DccFileTransfer * DccFileTransfer::nonFailedTransferWithLocalFileName(const QString &szLocalFileName)
+{
+ if(!g_pDccFileTransfers)return 0;
+ 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 0;
+}
+
+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.sprintf("[%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]play $0\r%s\r","dcc"),
+ m_pDescriptor->bIsTdcc ? (m_pDescriptor->bRecvFile ? "TRECV" : "TSEND") : (m_pDescriptor->bRecvFile ? "RECV" : "SEND"),
+ &(m_pDescriptor->szNick),&(m_pDescriptor->szIp),&(m_pDescriptor->szPort),
+ &(m_pDescriptor->szLocalFileName));
+ }
+ /*
+ // Also add an optional message to the notifier, unless it is an AVATAR download!
+ if(KVI_OPTION_BOOL(KviOption_boolNotifiDccDownloadSuccessInNotifier))
+ {
+ QString szMsg;
+ KviQString::sprintf(szMsg,__tr2qs_ctx(""));
+ g_pApp->notifierMessage(0,KVI_SMALLICON_DCCMSG,szMsg,KVI_OPTION_UINT(KviOption_uintNotifierAutoHideTime));
+ }
+ */
+ 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;
+ }
+ }
+//#warning "Remove this!"
+// if(e->type() == QEvent::Close)qDebug("Close event received");
+ 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->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 = 0;
+
+ outputAndLog(__tr2qs_ctx("RESUME accepted, transfer will begin at position %1","dcc").arg(m_pDescriptor->szLocalFileSize));
+
+ listenOrConnect();
+
+ /*
+ int ret = m_pMarshal->dccConnect(m_pDescriptor->szIp.toUtf8().data(),
+ m_pDescriptor->szPort.toUtf8().data(),m_pDescriptor->bDoTimeout);
+
+ if(ret != KviError::Success)handleMarshalError(ret);
+ else {
+ m_szStatusString = __tr2qs_ctx("Contacting host %1 on port %2","dcc").arg(m_pDescriptor->szIp, m_pDescriptor->szPort);
+ outputAndLog(m_szStatusString);
+ displayUpdate();
+ }
+ */
+
+ 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)
+ {
+ // bad file name
+ if(!bPortMatches)
+ return false; // neither filename nor port match
+
+ // port matches (this is very likely to be the right transfer)
+
+ 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 %u%c",
+ m_pDescriptor->console()->connection()->encodeText(m_pDescriptor->szNick).data(),
+ 0x01,
+ m_pDescriptor->console()->connection()->encodeText(szBuffy.ptr()).data(),
+ port,filePos,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()
+{
+}
+
+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;
+}
+
+
+#ifndef COMPILE_USE_STANDALONE_MOC_SOURCES
+#include "m_DccFileTransfer.moc"
+#endif //!COMPILE_USE_STANDALONE_MOC_SOURCES