//============================================================================= // // 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 #include #include #include #include #include #include #include #include #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 //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 * 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 * e = new KviThreadDataEvent(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) { tqDebug("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 * e = new KviThreadDataEvent(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").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) { 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").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 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")).arg(KviTQString::makeSizeReadable(iAckedBytes)).arg(KviTQString::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(5,5,iL,10,bIsTerminated ? TQColor(140,110,110) : TQColor(200,100,100)); txt = TQString(__tr2qs_ctx("%1 of %2 (%3%)","dcc")).arg(KviTQString::makeSizeReadable(uTransferred)).arg(KviTQString::makeSizeReadable(m_uTotalFileSize)).arg(dPerc,0,'f',2); } } else { txt = TQString(__tr2qs_ctx("%1","dcc")).arg(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("").arg(m_szDccType.ptr()).arg(id()); s += ""; s += ""; s += "
DCC %1 (ID %2)
"; s += __tr2qs_ctx("Transfer Log","dcc"); s += "
"; s += m_szTransferLog; s += "
"; return s; } void KviDccFileTransfer::init() { if(g_pDccFileTransfers)return; g_pDccFileTransfers = new KviPointerList; 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 += "
"; } 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").arg(m_pDescriptor->szIp).arg(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()).arg(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").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")); } } } } 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").arg(szReq.ptr()).arg(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 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").arg(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 *)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 *)e)->getData(); outputAndLog(TQString(__tr_no_xgettext_ctx(str->ptr(),"dcc"))); delete str; return true; } break; default: tqDebug("Invalid event type %d received",((KviThreadEvent *)e)->id()); break; } } //#warning "Remove this!" // if(e->type() == TQEvent::Close)tqDebug("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").arg(m_pMarshal->remoteIp()).arg(m_pMarshal->remotePort())); outputAndLog(__tr2qs_ctx("Local end is %1:%2","dcc").arg(m_pMarshal->localIp()).arg(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").arg(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").arg(m_pDescriptor->szIp).arg(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").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); 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").arg(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"