You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
kvirc/src/modules/dcc/send.cpp

1899 lines
57 KiB

//=============================================================================
//
// File : send.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-2005 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.
//
//=============================================================================
#include "send.h"
#include "broker.h"
#include "marshal.h"
#include "broker.h"
#include "window.h"
#include "kvi_styled_controls.h"
#ifdef COMPILE_ON_WINDOWS
// Ugly Windoze compiler...
#include "dialogs.h"
#endif
#define _KVI_DEBUG_CHECK_RANGE_
#include "kvi_debug.h"
#include "kvi_app.h"
#include "kvi_options.h"
#include "kvi_ircview.h"
#include "kvi_iconmanager.h"
#include "kvi_locale.h"
#include "kvi_error.h"
#include "kvi_out.h"
#include "kvi_netutils.h"
#include "kvi_console.h"
#include "kvi_frame.h"
#include "kvi_malloc.h"
#include "kvi_memmove.h"
#include "kvi_thread.h"
#include "kvi_ircsocket.h"
#include "kvi_mediatype.h"
#include "kvi_socket.h"
#include "kvi_kvs_eventtriggers.h"
#include "kvi_parameterlist.h"
#include "kvi_ircconnection.h"
#include "kvi_ircconnectionuserinfo.h"
#include "kvi_sparser.h"
#include "kvi_kvs_script.h"
#include <tqevent.h>
#include <tqfile.h>
#include <tqpainter.h>
#include <tqdatetime.h>
#include <tqglobal.h>
#include <tqcheckbox.h>
#include <tqspinbox.h>
#include <tqlayout.h>
#include <tqpushbutton.h>
#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
//#include <unistd.h> //close()
// FIXME: SSL Support here!
// FIXME: The events OnDCCConnect etc are in wrong places here...!
extern KviDccBroker * g_pDccBroker;
extern KVIRC_API KviMediaManager * g_pMediaManager; // kvi_app.cpp
static KviPointerList<KviDccFileTransfer> * g_pDccFileTransfers = 0;
static TQPixmap * g_pDccFileTransferIcon = 0;
//#warning "The events that have a KviStr 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)"
KviDccRecvThread::KviDccRecvThread(TQObject * par,kvi_socket_t fd,KviDccRecvThreadOptions * opt)
: KviDccThread(par,fd)
{
m_pOpt = opt;
m_iAverageSpeed = -1;
m_iInstantSpeed = -1;
m_iFilePosition = 0;
m_iTotalReceivedBytes = 0;
m_iInstantReceivedBytes = 0;
m_pFile = 0;
m_pTimeInterval = new KviMSecTimeInterval();
m_uStartTime = 0;
m_uInstantSpeedInterval = 0;
}
KviDccRecvThread::~KviDccRecvThread()
{
if(m_pOpt)delete m_pOpt;
if(m_pFile)delete m_pFile;
delete m_pTimeInterval;
}
bool KviDccRecvThread::sendAck(int filePos)
{
int size = htonl(filePos);
if(kvi_socket_send(m_fd,(void *)(&size),4) != 4)
{
postErrorEvent(KviError_acknowledgeError);
return false;
}
return true;
}
void KviDccRecvThread::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_iFilePosition = m_pFile->at();
m_iAverageSpeed = m_iTotalReceivedBytes / 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_iInstantSpeed = (m_iInstantReceivedBytes * 1000) / m_uInstantSpeedInterval;
m_iInstantReceivedBytes = 0;
m_uInstantSpeedInterval = uMSecsOfTheNextInterval;
} else {
if(uElapsedTime <= INSTANT_BANDWIDTH_CHECK_INTERVAL_IN_SECS)
m_iInstantSpeed = m_iAverageSpeed;
}
m_pMutex->unlock();
}
void KviDccRecvThread::postMessageEvent(const char * m)
{
KviThreadDataEvent<KviStr> * e = new KviThreadDataEvent<KviStr>(KVI_DCC_THREAD_EVENT_MESSAGE);
e->setData(new KviStr(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 KviDccRecvThread::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 TQFile(TQString::fromUtf8(m_pOpt->szFileName.ptr()));
if(m_pOpt->bResume)
{
if(!m_pFile->open(IO_WriteOnly | IO_Append))
{
postErrorEvent(KviError_cantOpenFileForAppending);
goto exit_dcc;
} // else pFile is already at end
} else {
if(!m_pFile->open(IO_WriteOnly))
{
postErrorEvent(KviError_cantOpenFileForWriting);
goto exit_dcc;
}
}
if(m_pOpt->bSendZeroAck && (!m_pOpt->bNoAcks))
{
if(!sendAck(m_pFile->at()))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 > ((unsigned int)(m_iInstantReceivedBytes)) ? uMaxPossible - m_iInstantReceivedBytes : 0;
if(uToRead > KVI_DCC_RECV_BLOCK_SIZE)uToRead = KVI_DCC_RECV_BLOCK_SIZE;
if(uToRead > 0)
{
int readLen = kvi_socket_recv(m_fd,buffer,uToRead);
if(readLen > 0)
{
// Readed something useful...write back
if((m_pOpt->iTotalFileSize > -1) && ((readLen + (int)m_pFile->at()) > m_pOpt->iTotalFileSize))
{
postMessageEvent(__tr2qs_ctx("WARNING: The peer is sending garbage data past the end of the file","dcc"));
postMessageEvent(__tr2qs_ctx("WARNING: Ignoring data past the declared end of file and closing the connection","dcc"));
readLen = m_pOpt->iTotalFileSize - m_pFile->at();
if(readLen > 0)
{
if(m_pFile->writeBlock(buffer,readLen) != readLen)
postErrorEvent(KviError_fileIOError);
}
break;
} else {
if(m_pFile->writeBlock(buffer,readLen) != readLen)
{
postErrorEvent(KviError_fileIOError);
break;
}
}
// Update stats
m_iTotalReceivedBytes += readLen;
m_iInstantReceivedBytes += readLen;
updateStats();
// Now send the ack
if(m_pOpt->bNoAcks)
{
// No acks...
// Interrupt if the whole file has been received
if(m_pOpt->iTotalFileSize > 0)
{
if(((int)(m_pFile->at())) == m_pOpt->iTotalFileSize)
{
// 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->at()))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...
if(readLen == 0)
{
// readed EOF..
if((((int)(m_pFile->at())) == m_pOpt->iTotalFileSize) || (m_pOpt->iTotalFileSize < 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(((int)(m_pFile->at())) == m_pOpt->iTotalFileSize)
{
// Wait for the peer to close the connection
if(iProbableTerminationTime == 0)
{
iProbableTerminationTime = (int)kvi_unixTime();
m_pFile->flush();
postMessageEvent(__tr2qs_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(__tr2qs_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)
{
debug("LOOP: artificial delay");
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;
}
kvi_socket_close(m_fd);
m_fd = KVI_INVALID_SOCKET;
}
void KviDccRecvThread::initGetInfo()
{
m_pMutex->lock();
}
void KviDccRecvThread::doneGetInfo()
{
m_pMutex->unlock();
}
KviDccSendThread::KviDccSendThread(TQObject * par,kvi_socket_t fd,KviDccSendThreadOptions * opt)
: KviDccThread(par,fd)
{
m_pOpt = opt;
// stats
m_iAverageSpeed = -1;
m_iInstantSpeed = -1;
m_iFilePosition = 0;
m_iTotalSentBytes = 0;
m_pTimeInterval = new KviMSecTimeInterval();
m_uStartTime = 0;
m_uInstantSpeedInterval = 0;
}
KviDccSendThread::~KviDccSendThread()
{
if(m_pOpt)delete m_pOpt;
delete m_pTimeInterval;
}
void KviDccSendThread::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_iAverageSpeed = m_iTotalSentBytes / uElapsedTime;
} else {
// acknowledges : we compute the avg bandwidth based on the acks we receive
m_iAverageSpeed = (m_iAckedBytes - m_pOpt->iStartPosition) / 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_iInstantSpeed = (m_iInstantSentBytes * 1000) / m_uInstantSpeedInterval;
m_uInstantSpeedInterval = uMSecsOfNextPeriodUsed;
m_iInstantSentBytes = 0;
} else {
if(uElapsedTime <= INSTANT_BANDWIDTH_CHECK_INTERVAL_IN_SECS)
m_iInstantSpeed = m_iAverageSpeed;
}
m_pMutex->unlock();
}
void KviDccSendThread::run()
{
m_pTimeInterval->mark();
m_pMutex->lock();
m_uStartTime = m_pTimeInterval->secondsCounter();
m_pMutex->unlock();
m_iTotalSentBytes = 0;
m_iInstantSentBytes = 0;
int iFailedSelects = 0;
char ackbuffer[4];
int iBytesInAckBuffer = 0;
TQ_UINT32 iLastAck = 0;
if(m_pOpt->iPacketSize < 32)m_pOpt->iPacketSize = 32;
char * buffer = (char *)kvi_malloc(m_pOpt->iPacketSize * sizeof(char));
TQFile * pFile = new TQFile(TQString::fromUtf8(m_pOpt->szFileName.ptr()));
if(!pFile->open(IO_ReadOnly))
{
postErrorEvent(KviError_cantOpenFileForReading);
goto exit_dcc;
}
if(pFile->size() < 1)
{
postErrorEvent(KviError_cantSendAZeroSizeFile);
goto exit_dcc;
}
if(m_pOpt->iStartPosition > 0)
{
// seek
if(!(pFile->at(m_pOpt->iStartPosition)))
{
postErrorEvent(KviError_fileIOError);
goto exit_dcc;
}
}
iLastAck = m_pOpt->iStartPosition;
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 = kvi_socket_recv(m_fd,(void *)(ackbuffer + iBytesInAckBuffer),iAckBytesToRead);
if(readLen > 0)
{
iBytesInAckBuffer += readLen;
if(iBytesInAckBuffer == 4)
{
TQ_UINT32 iNewAck = ntohl(*((TQ_UINT32 *)ackbuffer));
if((iNewAck > pFile->at()) || (iNewAck < iLastAck))
{
// the peer is drunk or is trying to fool us
postErrorEvent(KviError_acknowledgeError);
break;
}
iLastAck = iNewAck;
iBytesInAckBuffer = 0;
}
} else {
if(!handleInvalidSocketRead(readLen))break;
}
// update stats
m_pMutex->lock(); // is this really necessary ?
m_iAckedBytes = iLastAck;
m_pMutex->unlock();
if(iLastAck >= 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 = kvi_socket_recv(m_fd,(void *)&iAck,4);
if(readLen == 0)
{
// done...success
updateStats();
KviThreadEvent * e = new KviThreadEvent(KVI_DCC_THREAD_EVENT_SUCCESS);
postEvent(parent(),e);
break;
} else {
if(readLen < 0)
{
if(!handleInvalidSocketRead(readLen))break;
} else {
KviThreadDataEvent<KviStr> * e = new KviThreadDataEvent<KviStr>(KVI_DCC_THREAD_EVENT_MESSAGE);
e->setData(new KviStr(__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 || (iLastAck == pFile->at()))
{
// maximum readable size
int toRead = pFile->size() - pFile->at();
// the max number of bytes we can send in this interval (bandwidth limit)
m_pMutex->lock(); // FIXME: how to remove this lock ?
int iMaxPossible = 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(iMaxPossible < m_iInstantSentBytes)toRead = 0; // already sent too much!
else {
iMaxPossible -= m_iInstantSentBytes;
if(toRead > iMaxPossible)toRead = iMaxPossible;
}
// limit to packet size
if(toRead > m_pOpt->iPacketSize)toRead = m_pOpt->iPacketSize;
int written = 0;
if(toRead > 0)
{
// read data
int readed = pFile->readBlock(buffer,toRead);
if(readed < toRead)
{
postErrorEvent(KviError_fileIOError);
break;
}
// send it out
written = kvi_socket_send(m_fd,buffer,toRead);
if(written < toRead)
{
if(written < 0)
{
// error ?
if(!handleInvalidSocketRead(written))break;
} else {
// seek back to the right position
pFile->at(pFile->at() - (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_iTotalSentBytes += written;
m_iInstantSentBytes += written;
m_iFilePosition = pFile->at();
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:
kvi_free(buffer);
pFile->close();
delete pFile;
pFile = 0;
kvi_socket_close(m_fd);
m_fd = KVI_INVALID_SOCKET;
}
void KviDccSendThread::initGetInfo()
{
m_pMutex->lock();
}
void KviDccSendThread::doneGetInfo()
{
m_pMutex->unlock();
}
KviDccFileTransfer::KviDccFileTransfer(KviDccDescriptor * dcc)
: KviFileTransfer()
{
init(); // ensure we're initialized
g_pDccFileTransfers->append(this);
m_pResumeTimer = 0;
m_pBandwidthDialog = 0;
KviTQString::sprintf(m_szTransferIdString,__tr2qs_ctx("TRANSFER %d","dcc"),id());
m_pDescriptor = dcc;
m_pDescriptor->setTransfer(this);
m_pMarshal = new KviDccMarshal(this);
connect(m_pMarshal,TQT_SIGNAL(error(int)),this,TQT_SLOT(handleMarshalError(int)));
connect(m_pMarshal,TQT_SIGNAL(connected()),this,TQT_SLOT(connected()));
connect(m_pMarshal,TQT_SIGNAL(inProgress()),this,TQT_SLOT(connectionInProgress()));
#ifdef COMPILE_SSL_SUPPORT
connect(m_pMarshal,TQT_SIGNAL(startingSSLHandshake()),this,TQT_SLOT(startingSSLHandshake()));
connect(m_pMarshal,TQT_SIGNAL(sslError(const char *)),this,TQT_SLOT(sslError(const char *)));
#endif
m_szDccType = dcc->bIsTdcc ? (dcc->bRecvFile ? "TRECV" : "TSEND") : (dcc->bRecvFile ? "RECV" : "SEND");
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.toUInt(&bOk) : dcc->szLocalFileSize.toUInt(&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();
}
KviDccFileTransfer::~KviDccFileTransfer()
{
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 KviDccFileTransfer::bandwidthDialogDestroyed()
{
m_pBandwidthDialog = 0;
}
KviWindow * KviDccFileTransfer::eventWindow()
{
KviWindow *w = transferWindow();
if(w)return w;
return m_pDescriptor->console();
}
void KviDccFileTransfer::startConnection()
{
if(!(m_pDescriptor->bActive))
{
// PASSIVE CONNECTION
m_szStatusString = __tr2qs_ctx("Attempting a passive DCC %1 connection","dcc").tqarg(m_szDccType.ptr());
outputAndLog(m_szStatusString);
} else {
// ACTIVE CONNECTION
m_szStatusString = __tr2qs_ctx("Attempting an active DCC %1 connection","dcc").tqarg(m_szDccType.ptr());
outputAndLog(m_szStatusString);
}
if(m_pDescriptor->bResume && m_pDescriptor->bRecvFile)
{
TQString fName;
KviServerParser::encodeCtcpParameter(m_pDescriptor->szFileName.utf8().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.utf8().data(),
m_pDescriptor->szLocalFileSize.utf8().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.utf8().data(),
m_pDescriptor->szLocalFileSize.utf8().data(),0x01);
}
m_szStatusString = __tr2qs_ctx("Sent DCC RESUME request to %1, waiting for ACCEPT","dcc").tqarg(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 TQTimer(this);
connect(m_pResumeTimer,TQT_SIGNAL(timeout()),this,TQT_SLOT(resumeTimedOut()));
m_pResumeTimer->start(KVI_OPTION_UINT(KviOption_uintDccSocketTimeout) * 1000,true);
} else {
listenOrConnect();
}
displayUpdate();
}
void KviDccFileTransfer::listenOrConnect()
{
if(!(m_pDescriptor->bActive))
{
int ret = m_pMarshal->dccListen(m_pDescriptor->szListenIp,m_pDescriptor->szListenPort,m_pDescriptor->bDoTimeout);
if(ret != KviError_success)handleMarshalError(ret);
} else {
int ret = m_pMarshal->dccConnect(m_pDescriptor->szIp.utf8().data(),m_pDescriptor->szPort.utf8().data(),m_pDescriptor->bDoTimeout);
if(ret != KviError_success)handleMarshalError(ret);
}
displayUpdate();
}
void KviDccFileTransfer::resumeTimedOut()
{
if(m_pResumeTimer)
{
delete m_pResumeTimer;
m_pResumeTimer = 0;
}
handleMarshalError(KviError_connectionTimedOut);
}
KviWindow * KviDccFileTransfer::dccMarshalOutputWindow()
{
return transferWindow();
}
const char * KviDccFileTransfer::dccMarshalOutputContextString()
{
return m_szTransferIdString.utf8().data();
}
void KviDccFileTransfer::die()
{
delete this;
}
TQString KviDccFileTransfer::localFileName()
{
return m_pDescriptor->szLocalFileName;
}
void KviDccFileTransfer::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.utf8().data(),m_pDescriptor->szLocalFileName.utf8().data(),m_pDescriptor->szNick.utf8().data(),__tr_ctx("Aborted","dcc"));
KviStr 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(),TQString("Aborted by user"),TQString(tmp.ptr()),m_pDescriptor->idString());
outputAndLog(KVI_OUT_DCCERROR,m_szStatusString);
displayUpdate();
}
void KviDccFileTransfer::fillContextPopup(KviTalPopupMenu * m,int column)
{
m->insertItem(__tr2qs_ctx("Configure Bandwidth...","dcc"),this,TQT_SLOT(configureBandwidth()));
m->insertSeparator();
m->insertItem(__tr2qs_ctx("Resend DCC","dcc"),this,TQT_SLOT(retryDCC()));
m->insertItem(__tr2qs_ctx("Resend TDCC","dcc"),this,TQT_SLOT(retryTDCC()));
m->insertItem(__tr2qs_ctx("Resend RevDCC","dcc"),this,TQT_SLOT(retryRevDCC()));
/* FIX ME credo che il problema sia che se riavvio un trasferimento, a sua volta gia'
avviato, questo non ha irc contex, perche' la finestra "in cui e' nato"e' sta
quella della dcc. Conservarsi l'id della finestra? */
int id = m->insertItem(__tr2qs_ctx("Abort","dcc"),this,TQT_SLOT(abort()));
if(!active())m->setItemEnabled(id,false);
}
void KviDccFileTransfer::configureBandwidth()
{
if(m_pBandwidthDialog)return;
m_pBandwidthDialog = new KviDccFileTransferBandwidthDialog(g_pFrame,this);
connect(m_pBandwidthDialog,TQT_SIGNAL(destroyed()),this,TQT_SLOT(bandwidthDialogDestroyed()));
m_pBandwidthDialog->setModal(true);
m_pBandwidthDialog->show();
}
void KviDccFileTransfer::retryDCC()
{
abort();
TQString szRemoteNick = m_pDescriptor->remoteNick();
TQString szFileName = m_pDescriptor->localFileName();
TQString szId;
szId.setNum(m_pDescriptor->id());
TQString szCommand = "dcc.send -r=$console($dcc.irccontext(" + szId + ")) " + szRemoteNick + " " + "\"" + szFileName + "\"";
KviKvsScript::run(szCommand,g_pActiveWindow);
}
void KviDccFileTransfer::retryTDCC()
{
abort();
TQString szRemoteNick = m_pDescriptor->remoteNick();
TQString szFileName = m_pDescriptor->localFileName();
TQString szId;
szId.setNum(m_pDescriptor->id());
TQString szCommand = "dcc.send -r=$console($dcc.irccontext(" + szId + ")) -t " + szRemoteNick + " " + "\"" + szFileName + "\"";
KviKvsScript::run(szCommand,g_pActiveWindow);
}
void KviDccFileTransfer::retryRevDCC()
{
abort();
TQString szRemoteNick = m_pDescriptor->remoteNick();
TQString szFileName = m_pDescriptor->localFileName();
TQString szId;
szId.setNum(m_pDescriptor->id());
TQString szCommand = "dcc.rsend -z -r=$console($dcc.irccontext(" + szId + ")) " + szRemoteNick + " " + "\"" + szFileName + "\"";
KviKvsScript::run(szCommand,g_pActiveWindow);
}
void KviDccFileTransfer::fillStatusString(TQString &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 KviDccFileTransfer::active()
{
return ((m_eGeneralStatus == Connecting) || (m_eGeneralStatus == Transferring));
}
int KviDccFileTransfer::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 KviDccFileTransfer::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 KviDccFileTransfer::averageSpeed()
{
unsigned int iAvgBandwidth = 0;
if(m_pDescriptor->bRecvFile)
{
if(m_pSlaveRecvThread)
{
m_pSlaveRecvThread->initGetInfo();
iAvgBandwidth = (unsigned int)m_pSlaveRecvThread->averageSpeed();
m_pSlaveRecvThread->doneGetInfo();
}
} else {
if(m_pSlaveSendThread)
{
m_pSlaveSendThread->initGetInfo();
iAvgBandwidth = (unsigned int)m_pSlaveSendThread->averageSpeed();
m_pSlaveSendThread->doneGetInfo();
}
}
return iAvgBandwidth;
}
unsigned int KviDccFileTransfer::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 KviDccFileTransfer::displayPaint(TQPainter * p,int column,int width,int height)
{
TQString txt;
bool bIsTerminated = ((m_eGeneralStatus == Success) || (m_eGeneralStatus == Failure));
switch(column)
{
case COLUMN_TRANSFERTYPE:
{
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(3,3,*g_pDccFileTransferIcon,xoffset,yoffset,48,64);
}
break;
case COLUMN_FILEINFO:
{
TQFontMetrics fm(p->font());
TQString szFrom = __tr2qs_ctx("From: ","dcc");
TQString 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 = 4;
p->setPen(TQt::black);
KviStr szRemote(KviStr::Format,"dcc://%s@%s:%s/%s",m_pDescriptor->szNick.utf8().data(),m_pDescriptor->szIp.utf8().data(),m_pDescriptor->szPort.utf8().data(),
m_pDescriptor->szFileName.utf8().data());
p->drawText(4 + daW1,iY,width - (8 + daW1),height - 8,TQt::AlignTop | TQt::AlignLeft,
m_pDescriptor->bRecvFile ? szRemote.ptr() : m_pDescriptor->szLocalFileName.utf8().data());
iY += iLineSpacing;
p->drawText(4 + daW1,iY,width - (8 + daW1),height - 8,TQt::AlignTop | TQt::AlignLeft,
m_pDescriptor->bRecvFile ? m_pDescriptor->szLocalFileName.utf8().data() : szRemote.ptr());
iY += iLineSpacing;
p->setPen(TQt::darkGray);
p->drawText(4,4,width - 8,height - 8,TQt::AlignTop | TQt::AlignLeft,szFrom);
p->drawText(4,4 + iLineSpacing,width - 8,height - 8,TQt::AlignTop | TQt::AlignLeft,szTo);
p->setPen(TQColor(180,180,200));
iLineSpacing += 2;
p->drawRect(4,height - (iLineSpacing + 4),width - 8,iLineSpacing);
p->fillRect(5,height - (iLineSpacing + 3),width - 10,iLineSpacing - 2,bIsTerminated ? TQColor(210,210,210) : TQColor(190,190,240));
p->setPen(TQt::black);
p->drawText(7,height - (iLineSpacing + 4),width - 14,iLineSpacing,TQt::AlignVCenter | TQt::AlignLeft,m_szStatusString);
}
break;
case COLUMN_PROGRESS:
{
TQFontMetrics fm(p->font());
int iW = width - 8;
int iAvgBandwidth = -1;
int iInstantSpeed = -1;
int iAckedBytes = -1;
int iEta = -1;
unsigned int uTransferred = 0;
if(m_pDescriptor->bRecvFile)
{
if(m_pSlaveRecvThread)
{
m_pSlaveRecvThread->initGetInfo();
iAvgBandwidth = m_pSlaveRecvThread->averageSpeed();
iInstantSpeed = m_pSlaveRecvThread->instantSpeed();
uTransferred = m_pSlaveRecvThread->filePosition();
m_pSlaveRecvThread->doneGetInfo();
}
} else {
if(m_pSlaveSendThread)
{
m_pSlaveSendThread->initGetInfo();
iAvgBandwidth = m_pSlaveSendThread->averageSpeed();
iInstantSpeed = m_pSlaveSendThread->instantSpeed();
uTransferred = m_pSlaveSendThread->filePosition();
iAckedBytes = m_pSlaveSendThread->ackedBytes();
m_pSlaveSendThread->doneGetInfo();
}
}
p->setPen(bIsTerminated ? TQt::lightGray : TQColor(210,210,240));
p->drawRect(4,4,iW,12);
iW -= 2;
if(m_uTotalFileSize > 0)
{
if(iAvgBandwidth > 0)
{
unsigned int uRemaining = m_uTotalFileSize - uTransferred;
iEta = uRemaining / iAvgBandwidth;
}
if(!m_pDescriptor->bNoAcks && (iAckedBytes > 0) && (iAckedBytes < ((int)(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)iAckedBytes) * 100.0) / (double)m_uTotalFileSize;
int iL2 = (int) ((((double)iW) * dPerc2) / 100.0);
int iW2 = iL1 - iL2;
if(iW2 > 0)p->fillRect(5 + iL2,5,iW2,10,bIsTerminated ? TQColor(150,130,110) : TQColor(220,170,100));
p->fillRect(5,5,iL2,10,bIsTerminated ? TQColor(140,110,110) : TQColor(200,100,100));
txt = TQString(__tr2qs_ctx("%1 of %2 (%3%)","dcc")).tqarg(KviTQString::makeSizeReadable(iAckedBytes)).tqarg(KviTQString::makeSizeReadable(m_uTotalFileSize)).tqarg(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(5,5,iL,10,bIsTerminated ? TQColor(140,110,110) : TQColor(200,100,100));
txt = TQString(__tr2qs_ctx("%1 of %2 (%3%)","dcc")).tqarg(KviTQString::makeSizeReadable(uTransferred)).tqarg(KviTQString::makeSizeReadable(m_uTotalFileSize)).tqarg(dPerc,0,'f',2);
}
} else {
txt = TQString(__tr2qs_ctx("%1","dcc")).tqarg(KviTQString::makeSizeReadable(uTransferred));
}
p->setPen(TQt::black);
p->drawText(4,19,width - 8,height - 8,TQt::AlignTop | TQt::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 += " ";
if(iInstantSpeed >= 0)
{
TQString tmpisp;
KviNetUtils::formatNetworkBandwidthString(tmpisp,iInstantSpeed);
txt += tmpisp;
} else {
txt += "? B/s";
}
txt += " [";
} else {
txt = "";
}
txt += __tr2qs_ctx("Avg:","dcc");
txt += " ";
if(iAvgBandwidth >= 0)
{
TQString tmpspd;
KviNetUtils::formatNetworkBandwidthString(tmpspd,iAvgBandwidth);
txt += tmpspd;
} else {
txt += "? B/s";
}
if(!bIsTerminated)
{
txt += "]";
}
int iDaH = height - (iLineSpacing + 4);
p->setPen(TQColor(180,180,200));
p->drawRect(4,iDaH,iLeftHalf,iLineSpacing);
p->fillRect(5,iDaH + 1,iLeftHalf - 2,iLineSpacing - 2,bIsTerminated ? TQColor(210,210,210) : TQColor(190,190,240));
p->setPen(bIsTerminated ? TQt::darkGray : TQt::black);
p->drawText(6,iDaH,iLeftHalf - 4,iLineSpacing,TQt::AlignLeft | TQt::AlignVCenter,txt);
if(bIsTerminated)
{
if((m_tTransferStartTime != 0) && (m_tTransferEndTime != 0))
{
TQString tot = KviTimeUtils::formatTimeInterval(kvi_timeSpan(m_tTransferEndTime,m_tTransferStartTime),KviTimeUtils::NoLeadingEmptyIntervals | KviTimeUtils::NoLeadingZeroes);
txt = "TOT: ";
txt += tot;
} else {
txt = "";
}
} else {
if(iEta >= 0)
{
TQString eta = KviTimeUtils::formatTimeInterval(iEta,KviTimeUtils::NoLeadingEmptyIntervals | KviTimeUtils::NoLeadingZeroes);
txt = "ETA: ";
txt += eta;
} else {
txt = "ETA: ?";
}
}
p->setPen(TQColor(180,180,200));
p->drawRect(width - (4 + iRightHalf),iDaH,iRightHalf,iLineSpacing);
p->fillRect(width - (3 + iRightHalf),iDaH + 1,iRightHalf - 2,iLineSpacing - 2,bIsTerminated ? TQColor(210,210,210) : TQColor(190,190,240));
p->setPen(bIsTerminated ? TQt::darkGray : TQt::black);
p->drawText(width - (2 + iRightHalf),iDaH,iRightHalf - 4,iLineSpacing,TQt::AlignLeft | TQt::AlignVCenter,txt);
}
break;
}
}
int KviDccFileTransfer::displayHeight(int iLineSpacing)
{
int iH = (iLineSpacing * 3) + 10;
return iH >= 70 ? iH : 70;
}
TQString KviDccFileTransfer::tipText()
{
TQString s;
s = TQString("<table><tr><td bgcolor=\"#000000\"><font color=\"#FFFFFF\"><b>DCC %1 (ID %2)</b></font></td></tr>").tqarg(m_szDccType.ptr()).tqarg(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 KviDccFileTransfer::init()
{
if(g_pDccFileTransfers)return;
g_pDccFileTransfers = new KviPointerList<KviDccFileTransfer>;
g_pDccFileTransfers->setAutoDelete(false);
TQPixmap * pix = g_pIconManager->getImage("kvi_dccfiletransfericons.png");
if(pix)g_pDccFileTransferIcon = new TQPixmap(*pix);
else g_pDccFileTransferIcon = new TQPixmap(192,128);
}
void KviDccFileTransfer::done()
{
if(!g_pDccFileTransfers)return;
while(KviDccFileTransfer * t = g_pDccFileTransfers->first())
delete t;
delete g_pDccFileTransfers;
g_pDccFileTransfers = 0;
delete g_pDccFileTransferIcon;
g_pDccFileTransferIcon = 0;
}
unsigned int KviDccFileTransfer::transferCount()
{
if(!g_pDccFileTransfers)return 0;
return g_pDccFileTransfers->count();
}
KviDccFileTransfer * KviDccFileTransfer::nonFailedTransferWithLocalFileName(const TQString &szLocalFileName)
{
if(!g_pDccFileTransfers)return 0;
for(KviDccFileTransfer * t = g_pDccFileTransfers->first();t;t = g_pDccFileTransfers->next())
{
#ifdef COMPILE_ON_WINDOWS
// on windows the file names are case insensitive
if(t->localFileName().lower() == szLocalFileName.lower())
#else
if(t->localFileName() == szLocalFileName)
#endif
{
if(t->m_eGeneralStatus != Failure)
return t;
}
}
return 0;
}
unsigned int KviDccFileTransfer::runningTransfersCount()
{
if(!g_pDccFileTransfers)return 0;
unsigned int cnt = 0;
for(KviDccFileTransfer * t = g_pDccFileTransfers->first();t;t = g_pDccFileTransfers->next())
{
if(t->active())cnt++;
}
return cnt;
}
bool KviDccFileTransfer::handleResumeAccepted(const char * filename,const char * port,const char * szZeroPortTag)
{
if(!g_pDccFileTransfers)return false;
for(KviDccFileTransfer * t = g_pDccFileTransfers->first();t;t = g_pDccFileTransfers->next())
{
if(t->resumeAccepted(filename,port,szZeroPortTag))return true;
}
return false;
}
bool KviDccFileTransfer::handleResumeRequest(const char * filename,const char * port,unsigned int filePos)
{
if(!g_pDccFileTransfers)return false;
for(KviDccFileTransfer * t = g_pDccFileTransfers->first();t;t = g_pDccFileTransfers->next())
{
if(t->doResume(filename,port,filePos))return true;
}
return false;
}
void KviDccFileTransfer::outputAndLog(const TQString &s)
{
KviWindow * out = transferWindow();
addToTransferLog(s);
if(out)out->output(KVI_OUT_DCCMSG,"[%Q]: %Q",&m_szTransferIdString,&s);
}
void KviDccFileTransfer::outputAndLog(int msgtype,const TQString &s)
{
KviWindow * out = transferWindow();
addToTransferLog(s);
if(out)out->output(msgtype,"[%Q]: %Q",&m_szTransferIdString,&s);
}
void KviDccFileTransfer::addToTransferLog(const TQString &s)
{
TQDateTime dt = TQDateTime::currentDateTime();
TQString 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 KviDccFileTransfer::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").tqarg(m_pDescriptor->szIp).tqarg(m_pDescriptor->szPort);
outputAndLog(m_szStatusString);
displayUpdate();
return;
}
// PASSIVE CONNECTION
m_szStatusString = __tr2qs_ctx("Listening on interface %1 port %2","dcc").tqarg(m_pMarshal->localIp()).tqarg(m_pMarshal->localPort());
outputAndLog(m_szStatusString);
if(m_pDescriptor->bSendRequest)
{
TQString 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())
{
TQString 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").tqarg(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"));
}
}
}
}
KviStr 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));
TQString tmp = m_pDescriptor->szFileName;
// just to be sure
KviTQString::cutToLast(tmp,'/');
KviTQString::cutToLast(tmp,'\\');
TQString fName;
// BUG-TO-BUG mIrc compatibility
if(KVI_OPTION_BOOL(KviOption_boolDCCFileTransferReplaceOutgoingSpacesWithUnderscores))
tmp.replace(" ","_");
KviServerParser::encodeCtcpParameter(tmp.utf8().data(),fName);
// Zero port requests want DCC SEND as back-request
KviStr szReq;
if(m_pDescriptor->isZeroPortRequest())
{
szReq = "SEND";
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.utf8().data(),port.ptr(),
m_pDescriptor->szFileSize.utf8().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.utf8().data(),port.ptr(),
&(m_pDescriptor->szLocalFileSize),0x01);
}
outputAndLog(__tr2qs_ctx("Sent DCC %1 request to %2, waiting for remote client to connect...","dcc").tqarg(szReq.ptr()).tqarg(m_pDescriptor->szNick));
} else {
outputAndLog(__tr2qs_ctx("DCC %1 request not sent, awaiting manual connection","dcc").tqarg(m_szDccType.ptr()));
}
KVS_TRIGGER_EVENT_1(KviEvent_OnDCCFileTransferConnectionInProgress,eventWindow(),m_pDescriptor->idString());
displayUpdate();
}
void KviDccFileTransfer::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 KviDccFileTransfer::sslError(const char * msg)
{
#ifdef COMPILE_SSL_SUPPORT
outputAndLog(KVI_OUT_DCCERROR,__tr2qs_ctx("[SSL ERROR]: %1","dcc").tqarg(msg));
#endif
}
bool KviDccFileTransfer::event(TQEvent *e)
{
if(e->type() == KVI_THREAD_EVENT)
{
switch(((KviThreadEvent *)e)->id())
{
case KVI_DCC_THREAD_EVENT_ERROR:
{
int * err = ((KviThreadDataEvent<int> *)e)->getData();
TQString szErrorString = KviError::getDescription(*err);
delete err;
if(m_pDescriptor->bRecvFile)
g_pApp->fileDownloadTerminated(false,m_pDescriptor->szFileName.utf8().data(),m_pDescriptor->szLocalFileName.utf8().data(),m_pDescriptor->szNick.utf8().data(),szErrorString.utf8().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))
{
KviConsole *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))
{
TQString szMsg;
KviTQString::sprintf(szMsg,__tr2qs_ctx(""));
g_pApp->notifierMessage(0,KVI_SMALLICON_DCCMSG,szMsg,30);
}
*/
if(m_pDescriptor->bRecvFile)g_pApp->fileDownloadTerminated(true,m_pDescriptor->szFileName.utf8().data(),m_pDescriptor->szLocalFileName.utf8().data(),m_pDescriptor->szNick.utf8().data());
m_szStatusString = __tr2qs_ctx("Transfer completed","dcc");
outputAndLog(m_szStatusString);
m_eGeneralStatus = Success;
m_tTransferEndTime = kvi_unixTime();
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:
{
KviStr * str = ((KviThreadDataEvent<KviStr> *)e)->getData();
outputAndLog(TQString(__tr_no_xgettext_ctx(str->ptr(),"dcc")));
delete str;
return true;
}
break;
default:
debug("Invalid event type %d received",((KviThreadEvent *)e)->id());
break;
}
}
//#warning "Remove this!"
// if(e->type() == TQEvent::Close)debug("Close event received");
return KviFileTransfer::event(e);
}
void KviDccFileTransfer::handleMarshalError(int err)
{
TQString szErr = KviError::getDescription(err);
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 KviDccFileTransfer::connected()
{
outputAndLog(__tr2qs_ctx("Connected to %1:%2","dcc").tqarg(m_pMarshal->remoteIp()).tqarg(m_pMarshal->remotePort()));
outputAndLog(__tr2qs_ctx("Local end is %1:%2","dcc").tqarg(m_pMarshal->localIp()).tqarg(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.utf8().data();
bool bOk;
o->iTotalFileSize = m_pDescriptor->szFileSize.toInt(&bOk);
if(!bOk)o->iTotalFileSize = -1;
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 KviDccRecvThread(this,m_pMarshal->releaseSocket(),o);
m_pSlaveRecvThread->start();
} else {
KviDccSendThreadOptions * o = new KviDccSendThreadOptions;
o->szFileName = m_pDescriptor->szLocalFileName.utf8().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->iStartPosition = m_pDescriptor->szFileSize.toInt(&bOk);
if(!bOk || (o->iStartPosition < 0))o->iStartPosition = 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 KviDccSendThread(this,m_pMarshal->releaseSocket(),o);
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 KviDccFileTransfer::resumeAccepted(const char *filename,const char *port,const char *szZeroPortTag)
{
if(!(kvi_strEqualCI(filename,m_pDescriptor->szFileName.utf8().data()) || KVI_OPTION_BOOL(KviOption_boolAcceptBrokenFileNameDccResumeRequests)))
return false;
if(!(kvi_strEqualCI(port,m_pDescriptor->szPort.utf8().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").tqarg(m_pDescriptor->szLocalFileSize));
listenOrConnect();
/*
int ret = m_pMarshal->dccConnect(m_pDescriptor->szIp.utf8().data(),
m_pDescriptor->szPort.utf8().data(),m_pDescriptor->bDoTimeout);
if(ret != KviError_success)handleMarshalError(ret);
else {
m_szStatusString = __tr2qs_ctx("Contacting host %1 on port %2","dcc").tqarg(m_pDescriptor->szIp).tqarg(m_pDescriptor->szPort);
outputAndLog(m_szStatusString);
displayUpdate();
}
*/
return true;
}
bool KviDccFileTransfer::doResume(const char * filename,const char * port,unsigned int filePos)
{
if(KviTQString::equalCI(port,m_pMarshal->dccPort()) &&
(!m_pSlaveRecvThread) && (!m_pDescriptor->bRecvFile))
{
if(KviTQString::equalCI(filename,m_pDescriptor->szFileName) || KVI_OPTION_BOOL(KviOption_boolAcceptBrokenFileNameDccResumeRequests))
{
bool bOk;
unsigned int iLocalFileSize = m_pDescriptor->szLocalFileSize.toUInt(&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").tqarg(filePos));
return false;
}
outputAndLog(KVI_OUT_DCCERROR,__tr2qs_ctx("Accepting RESUME request, transfer will begin at position %1","dcc").tqarg(filePos));
m_pDescriptor->szFileSize.setNum(filePos);
KviStr szBuffy;
KviServerParser::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;
}
}
return false;
}
KviDccFileTransferBandwidthDialog::KviDccFileTransferBandwidthDialog(TQWidget * pParent,KviDccFileTransfer * t)
: TQDialog(pParent)
{
TQGridLayout * g = new TQGridLayout(this,3,3,4,4);
m_pTransfer = t;
int iVal = m_pTransfer->bandwidthLimit();
TQString szText = __tr2qs_ctx("Configure bandwidth for DCC transfer %1","dcc").tqarg(t->id());
setCaption(szText);
szText = t->isFileUpload() ? __tr2qs_ctx("Limit upload bandwidth to","dcc") : __tr2qs_ctx("Limit download bandwidth to","dcc");
m_pEnableLimitCheck = new KviStyledCheckBox(szText,this);
g->addWidget(m_pEnableLimitCheck,0,0);
m_pEnableLimitCheck->setChecked((iVal >= 0) && (iVal < MAX_DCC_BANDWIDTH_LIMIT));
m_pLimitBox = new TQSpinBox(0,MAX_DCC_BANDWIDTH_LIMIT-1,1,this);
m_pLimitBox->setEnabled((iVal >= 0) && (iVal < MAX_DCC_BANDWIDTH_LIMIT));
connect(m_pEnableLimitCheck,TQT_SIGNAL(toggled(bool)),m_pLimitBox,TQT_SLOT(setEnabled(bool)));
g->addMultiCellWidget(m_pLimitBox,0,0,1,2);
szText = " ";
szText += __tr2qs_ctx("bytes/sec","dcc");
m_pLimitBox->setSuffix(szText);
m_pLimitBox->setValue(iVal < MAX_DCC_BANDWIDTH_LIMIT ? iVal : 0);
TQPushButton * pb = new TQPushButton(__tr2qs_ctx("OK","dcc"),this);
connect(pb,TQT_SIGNAL(clicked()),this,TQT_SLOT(okClicked()));
pb->setMinimumWidth(80);
g->addWidget(pb,2,2);
pb = new TQPushButton(__tr2qs_ctx("Cancel","dcc"),this);
connect(pb,TQT_SIGNAL(clicked()),this,TQT_SLOT(cancelClicked()));
pb->setMinimumWidth(80);
g->addWidget(pb,2,1);
g->setColStretch(0,1);
g->setRowStretch(1,1);
}
KviDccFileTransferBandwidthDialog::~KviDccFileTransferBandwidthDialog()
{
}
void KviDccFileTransferBandwidthDialog::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 KviDccFileTransferBandwidthDialog::cancelClicked()
{
delete this;
}
void KviDccFileTransferBandwidthDialog::closeEvent(TQCloseEvent * e)
{
e->ignore();
delete this;
}
#include "m_send.moc"