//============================================================================= // // File : DccFileTransfer.cpp // Creation date : Tue Sep 20 09 2000 15:14:14 by Szymon Stefanek // // This file is part of the KVIrc IRC client distribution // Copyright (C) 2000-2010 Szymon Stefanek (pragma at kvirc dot net) // // This program is FREE software. You can redistribute it and/or // modify it under the terms of the GNU General Public License // as published by the Free Software Foundation; either version 2 // of the License, or (at your option) any later version. // // This program is distributed in the HOPE that it will be USEFUL, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // See the GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, write to the Free Software Foundation, // Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. // //============================================================================= #define _KVI_DEBUG_CHECK_RANGE_ #include "DccFileTransfer.h" #include "DccBroker.h" #include "DccMarshal.h" #include "DccWindow.h" #include "kvi_debug.h" #include "KviApplication.h" #include "KviOptions.h" #include "KviIrcView.h" #include "KviIconManager.h" #include "KviLocale.h" #include "KviError.h" #include "kvi_out.h" #include "KviNetUtils.h" #include "KviConsoleWindow.h" #include "KviMainWindow.h" #include "KviMemory.h" #include "KviThread.h" #include "KviIrcSocket.h" #include "KviMediaManager.h" #include "kvi_socket.h" #include "KviKvsEventTriggers.h" #include "KviIrcConnection.h" #include "KviIrcConnectionUserInfo.h" #include "KviIrcServerParser.h" #include "KviKvsScript.h" #ifdef COMPILE_ON_WINDOWS // Ugly Windoze compiler... #include "DccDialog.h" #endif #include #include #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 // FIXME: The events OnDCCConnect etc are in wrong places here...! extern DccBroker * g_pDccBroker; extern KVIRC_API KviMediaManager * g_pMediaManager; // KviApplication.cpp static KviPointerList * g_pDccFileTransfers = nullptr; static QPixmap * g_pDccFileTransferIcon = nullptr; //#warning "The events that have a KviCString data pointer should become real classes, that take care of deleting the data pointer!" //#warning "Otherwise, when left undispatched we will be leaking memory (event class destroyed but not the data ptr)" DccRecvThread::DccRecvThread(QObject * par, kvi_socket_t fd, KviDccRecvThreadOptions * opt) : DccThread(par, fd) { m_pOpt = opt; m_uAverageSpeed = 0; m_uInstantSpeed = 0; m_uFilePosition = 0; m_uTotalReceivedBytes = 0; m_uInstantReceivedBytes = 0; m_pFile = nullptr; m_pTimeInterval = new KviMSecTimeInterval(); m_uStartTime = 0; m_uInstantSpeedInterval = 0; } DccRecvThread::~DccRecvThread() { if(m_pOpt) delete m_pOpt; if(m_pFile) delete m_pFile; delete m_pTimeInterval; } bool DccRecvThread::sendAck(qint64 filePos, bool bUse64BitAck) { quint32 ack32 = htonl(filePos & 0xffffffff); quint64 ack64 = qToBigEndian(filePos); char * ack = (char *)&ack32; int ackSize = 4; if(bUse64BitAck) { ackSize = 8; ack = (char *)&ack64; } int iRet = 0; #ifdef COMPILE_SSL_SUPPORT if(m_pSSL) iRet = m_pSSL->write(ack, ackSize); else #endif //COMPILE_SSL_SUPPORT iRet = kvi_socket_send(m_fd, (void *)(ack), ackSize); if(iRet == ackSize) return true; // everything sent // When downloading from a fast server using send-ahead via an asymmetric link (such as the // common ADSL lines) it may happen that the network output queue gets saturated with ACKs. // In this case the network stack will refuse to send our packet and we get here. // // We should either retry to send the ACK in a while or avoid sending it at all (as with // send-ahead acks aren't usually checked per-packet). if(iRet == 0) { // We can live with this: no data has been sent at all // Not sending the ack and hoping that the server will not stall is better than // killing the connection from our side anyway. return true; } if(iRet < 0) { // Reported error. If it's EAGAIN or EINTR then no data has been sent. #ifdef COMPILE_SSL_SUPPORT if(m_pSSL) { // dropping ack when no serious ssl error occurred switch(m_pSSL->getProtocolError(iRet)) { case KviSSL::ZeroReturn: //return false; check eagain case KviSSL::Success: case KviSSL::WantRead: case KviSSL::WantWrite: return true; break; default: // Raise unknown SSL ERROR postErrorEvent(KviError::SSLError); return false; break; } return false; } #endif //COMPILE_SSL_SUPPORT int err = kvi_socket_error(); #if defined(COMPILE_ON_WINDOWS) || defined(COMPILE_ON_MINGW) if((err != EAGAIN) && (err != EINTR) && (err != WSAEWOULDBLOCK)) #else //!(defined(COMPILE_ON_WINDOWS) || defined(COMPILE_ON_MINGW)) if((err != EAGAIN) && (err != EINTR)) #endif //!(defined(COMPILE_ON_WINDOWS) || defined(COMPILE_ON_MINGW)) { // some other kind of error postErrorEvent(KviError::AcknowledgeError); return false; } return true; // no data sent: same as iRet == 0 above. } // Sent something but not everything. // How likely is it to get in here ?! // Sleep for a short while and try to send the missing part. // This will probably throttle the bandwidth usage a bit too. msleep(10); int iMissingPart = ackSize - iRet; #ifdef COMPILE_SSL_SUPPORT if(m_pSSL) iRet = m_pSSL->write(ack + iRet, iMissingPart); else #endif //COMPILE_SSL_SUPPORT iRet = kvi_socket_send(m_fd, (void *)(ack + iRet), iMissingPart); if(iRet != iMissingPart) { // Crap.. couldn't send the missing part of the ack :/ postErrorEvent(KviError::AcknowledgeError); return false; } return true; } void DccRecvThread::updateStats() { m_uInstantSpeedInterval += m_pTimeInterval->mark(); unsigned long uCurTime = m_pTimeInterval->secondsCounter(); m_pMutex->lock(); unsigned long uElapsedTime = uCurTime - m_uStartTime; if(uElapsedTime < 1) uElapsedTime = 1; m_uFilePosition = m_pFile->pos(); m_uAverageSpeed = m_uTotalReceivedBytes / uElapsedTime; if(m_uInstantSpeedInterval > INSTANT_BANDWIDTH_CHECK_INTERVAL_IN_MSECS) { unsigned int uMSecsOfTheNextInterval = 0; if(m_uInstantSpeedInterval < (INSTANT_BANDWIDTH_CHECK_INTERVAL_IN_MSECS + (INSTANT_BANDWIDTH_CHECK_INTERVAL_IN_MSECS / 2))) uMSecsOfTheNextInterval = m_uInstantSpeedInterval - INSTANT_BANDWIDTH_CHECK_INTERVAL_IN_MSECS; m_uInstantSpeed = (m_uInstantReceivedBytes * 1000) / m_uInstantSpeedInterval; m_uInstantReceivedBytes = 0; m_uInstantSpeedInterval = uMSecsOfTheNextInterval; } else { if(uElapsedTime <= INSTANT_BANDWIDTH_CHECK_INTERVAL_IN_SECS) m_uInstantSpeed = m_uAverageSpeed; } m_pMutex->unlock(); } void DccRecvThread::postMessageEvent(const char * m) { KviThreadDataEvent * e = new KviThreadDataEvent(KVI_DCC_THREAD_EVENT_MESSAGE); e->setData(new KviCString(m)); postEvent(parent(), e); } // FIXME: This stuff should be somewhat related to the 1448 bytes TCP basic packet size //#define KVI_DCC_RECV_BLOCK_SIZE 8192 //#define KVI_DCC_RECV_75PERCENTOF_BLOCK_SIZE 6150 #define KVI_DCC_RECV_BLOCK_SIZE 16384 #define KVI_DCC_RECV_75PERCENTOF_BLOCK_SIZE 12280 void DccRecvThread::run() { // take care of sleeping a bit if we can't read stuff // so we don't hog the CPU too much... int iFailedSelects = 0; // take care of sleeping a bit if we get a lot of short reads // so we don't hog the CPU too much... int iShortReadQuantifier = 0; // the algorithm is as follows: // attempt to read KVI_DCC_RECV_BLOCK_SIZE bytes // iShortReadQuantifier += ((KVI_DCC_RECV_75PERCENT_OF_BLOCK_SIZE - realReadedBytes) / 42); // thus we gain points if we read less than 75% of the requested size // and we loose points otherwise // there are nearly 24 points per KB // if(iShortReadQuantifier > 10) // msleep(iShortReadQuantifier); // also never sleep more than 500 msecs since it will // rise our exit latency too much m_pTimeInterval->mark(); m_pMutex->lock(); m_uStartTime = m_pTimeInterval->secondsCounter(); m_pMutex->unlock(); int iProbableTerminationTime = 0; m_pFile = new QFile(QString::fromUtf8(m_pOpt->szFileName.ptr())); bool bSend64BitAck = m_pOpt->bSend64BitAck && (m_pOpt->uTotalFileSize >> 32); if(m_pOpt->bResume) { if(!m_pFile->open(QIODevice::WriteOnly | QIODevice::Append)) { postErrorEvent(KviError::CantOpenFileForAppending); goto exit_dcc; } // else pFile is already at end } else { if(!m_pFile->open(QIODevice::WriteOnly)) { postErrorEvent(KviError::CantOpenFileForWriting); goto exit_dcc; } } if(m_pOpt->bSendZeroAck && (!m_pOpt->bNoAcks)) { if(!sendAck(m_pFile->pos(), bSend64BitAck)) goto exit_dcc; } for(;;) { // Dequeue events while(KviThreadEvent * e = dequeueEvent()) { if(e->id() == KVI_THREAD_EVENT_TERMINATE) { delete e; goto exit_dcc; } else { // Other events are senseless to us delete e; } } bool bCanRead; bool bDummy; if(kvi_select(m_fd, &bCanRead, &bDummy, 15000)) { // reset sleep time if(bCanRead) { iFailedSelects = 0; // Read a data block char buffer[KVI_DCC_RECV_BLOCK_SIZE]; m_pMutex->lock(); // FIXME: how to remove this lock ? unsigned int uMaxPossible = (m_pOpt->uMaxBandwidth < MAX_DCC_BANDWIDTH_LIMIT) ? m_pOpt->uMaxBandwidth * INSTANT_BANDWIDTH_CHECK_INTERVAL_IN_SECS : MAX_DCC_BANDWIDTH_LIMIT * INSTANT_BANDWIDTH_CHECK_INTERVAL_IN_SECS; m_pMutex->unlock(); unsigned int uToRead = uMaxPossible > m_uInstantReceivedBytes ? uMaxPossible - m_uInstantReceivedBytes : 0; if(uToRead > KVI_DCC_RECV_BLOCK_SIZE) uToRead = KVI_DCC_RECV_BLOCK_SIZE; if(uToRead > 0) { int readLen; #ifdef COMPILE_SSL_SUPPORT if(m_pSSL) { readLen = m_pSSL->read(buffer, uToRead); } else { #endif readLen = kvi_socket_recv(m_fd, buffer, uToRead); #ifdef COMPILE_SSL_SUPPORT } #endif if(readLen > 0) { // Readed something useful...write back if(((uint)(readLen + m_pFile->pos())) > m_pOpt->uTotalFileSize) { postMessageEvent(__tr_no_lookup_ctx("WARNING: the peer is sending garbage data past the end of the file", "dcc")); postMessageEvent(__tr_no_lookup_ctx("WARNING: ignoring data past the declared end of file and closing the connection", "dcc")); readLen = m_pOpt->uTotalFileSize - m_pFile->pos(); if(readLen > 0) { if(m_pFile->write(buffer, readLen) != readLen) postErrorEvent(KviError::FileIOError); } break; } else { if(m_pFile->write(buffer, readLen) != readLen) { postErrorEvent(KviError::FileIOError); break; } } // Update stats m_uTotalReceivedBytes += readLen; m_uInstantReceivedBytes += readLen; updateStats(); // Now send the ack if(m_pOpt->bNoAcks) { // No acks... // Interrupt if the whole file has been received if(m_pOpt->uTotalFileSize > 0) { if((quint64)m_pFile->pos() == m_pOpt->uTotalFileSize) { // Received the whole file...die KviThreadEvent * e = new KviThreadEvent(KVI_DCC_THREAD_EVENT_SUCCESS); postEvent(parent(), e); break; } } } else { // Must send the ack... the peer must close the connection if(!sendAck(m_pFile->pos(), bSend64BitAck)) break; } // now take care of short reads iShortReadQuantifier += ((KVI_DCC_RECV_75PERCENTOF_BLOCK_SIZE - readLen) / 42); if(iShortReadQuantifier > 10) { // we're having short reads.. sleep a while // but don't allow it to go too high: 0.45 sec is really a lot if(iShortReadQuantifier > 500) iShortReadQuantifier = 500; msleep(iShortReadQuantifier); } else { // don't allow it to go too low if(iShortReadQuantifier < -500) iShortReadQuantifier = -500; } } else { updateStats(); // Read problem... #ifdef COMPILE_SSL_SUPPORT if(m_pSSL) { // ssl error....? switch(m_pSSL->getProtocolError(readLen)) { case KviSSL::ZeroReturn: //check again not necessary a connection closure! //if (!handleInvalidSocketRead(readLen) // break; readLen = 0; break; case KviSSL::Success: case KviSSL::WantRead: case KviSSL::WantWrite: // hmmm... DO NOT CALL handleInvalidSocketRead break; case KviSSL::SyscallError: { int iE = m_pSSL->getLastError(true); if(iE != 0) { raiseSSLError(); postErrorEvent(KviError::SSLError); goto exit_dcc; } } break; case KviSSL::SSLError: { raiseSSLError(); postErrorEvent(KviError::SSLError); goto exit_dcc; } break; default: // Raise unknown SSL ERROR postErrorEvent(KviError::SSLError); goto exit_dcc; break; } } #endif if(readLen == 0) { // read EOF.. if(((quint64)m_pFile->pos() == m_pOpt->uTotalFileSize) || (m_pOpt->uTotalFileSize == 0)) { // success if we got the whole file or if we don't know the file size (we trust the peer) KviThreadEvent * e = new KviThreadEvent(KVI_DCC_THREAD_EVENT_SUCCESS); postEvent(parent(), e); break; } } #ifdef COMPILE_SSL_SUPPORT if(!m_pSSL && !handleInvalidSocketRead(readLen)) break; #else if(!handleInvalidSocketRead(readLen)) break; #endif } } else { updateStats(); // reached the bandwidth limit: slow down a bit if(m_uInstantSpeedInterval < (INSTANT_BANDWIDTH_CHECK_INTERVAL_IN_MSECS - 100)) msleep(100); else if(m_uInstantSpeedInterval < (INSTANT_BANDWIDTH_CHECK_INTERVAL_IN_MSECS - 20)) msleep(20); } } else { // Can't read stuff (can just write) updateStats(); // sleep up to 300 msecs (if data arrives...we want low exit latency here) if(iFailedSelects < 100) iFailedSelects++; updateStats(); if(iFailedSelects > 3) msleep(3 * iFailedSelects); if((quint64)m_pFile->pos() == m_pOpt->uTotalFileSize) { // Wait for the peer to close the connection if(iProbableTerminationTime == 0) { iProbableTerminationTime = (int)kvi_unixTime(); m_pFile->flush(); postMessageEvent(__tr_no_lookup_ctx("Data transfer terminated, waiting 30 seconds for the peer to close the connection...", "dcc")); // FIXME: Close the file ? } else { int iDiff = (((int)kvi_unixTime()) - iProbableTerminationTime); if(iDiff > 30) { // success if we got the whole file or if we don't know the file size (we trust the peer) postMessageEvent(__tr_no_lookup_ctx("Data transfer was terminated 30 seconds ago, closing the connection", "dcc")); KviThreadEvent * e = new KviThreadEvent(KVI_DCC_THREAD_EVENT_SUCCESS); postEvent(parent(), e); break; } } } } // include the artificial delay if needed if(m_pOpt->iIdleStepLengthInMSec > 0) msleep(m_pOpt->iIdleStepLengthInMSec); } else { // sleep up to 200 msecs (if data arrives...we want low exit latency here) if(iFailedSelects < 100) iFailedSelects++; updateStats(); if(iFailedSelects > 3) msleep(2 * iFailedSelects); } } exit_dcc: if(m_pFile) { m_pFile->close(); delete m_pFile; m_pFile = nullptr; } #ifdef COMPILE_SSL_SUPPORT if(m_pSSL) { KviSSLMaster::freeSSL(m_pSSL); m_pSSL = nullptr; } #endif kvi_socket_close(m_fd); m_fd = KVI_INVALID_SOCKET; } void DccRecvThread::initGetInfo() { m_pMutex->lock(); } void DccRecvThread::doneGetInfo() { m_pMutex->unlock(); } DccSendThread::DccSendThread(QObject * par, kvi_socket_t fd, KviDccSendThreadOptions * opt) : DccThread(par, fd) { m_pOpt = opt; // stats m_uAverageSpeed = 0; m_uInstantSpeed = 0; m_uFilePosition = 0; m_uTotalSentBytes = 0; m_pTimeInterval = new KviMSecTimeInterval(); m_uStartTime = 0; m_uInstantSpeedInterval = 0; } DccSendThread::~DccSendThread() { if(m_pOpt) delete m_pOpt; delete m_pTimeInterval; } void DccSendThread::updateStats() { m_uInstantSpeedInterval += m_pTimeInterval->mark(); m_pMutex->lock(); unsigned long uElapsedTime = m_pTimeInterval->secondsCounter() - m_uStartTime; if(uElapsedTime < 1) uElapsedTime = 1; if(m_pOpt->bNoAcks) { // There are no acks : the avg bandwidth is based on the sent bytes m_uAverageSpeed = m_uTotalSentBytes / uElapsedTime; } else { // acknowledges : we compute the avg bandwidth based on the acks we receive m_uAverageSpeed = (m_uAckedBytes - m_pOpt->uStartPosition) / uElapsedTime; } if(m_uInstantSpeedInterval >= INSTANT_BANDWIDTH_CHECK_INTERVAL_IN_MSECS) { // we often overcount the time interval of 10-20 msecs // and thus our bandwidth is used less than requested. // for this reason we try to account the time in excess // to the next period in order to balance the bandwidth usage. unsigned long uMSecsOfNextPeriodUsed = 0; if(m_uInstantSpeedInterval > INSTANT_BANDWIDTH_CHECK_INTERVAL_IN_MSECS) { if(m_uInstantSpeedInterval < (INSTANT_BANDWIDTH_CHECK_INTERVAL_IN_MSECS + (INSTANT_BANDWIDTH_CHECK_INTERVAL_IN_MSECS / 2))) { uMSecsOfNextPeriodUsed = m_uInstantSpeedInterval - INSTANT_BANDWIDTH_CHECK_INTERVAL_IN_MSECS; m_uInstantSpeedInterval = INSTANT_BANDWIDTH_CHECK_INTERVAL_IN_MSECS; } // else we have been delayed for a time comparable to a period // and thus we can't recover the bandwidth... let it go as it does... } m_uInstantSpeed = (m_uInstantSentBytes * 1000) / m_uInstantSpeedInterval; m_uInstantSpeedInterval = uMSecsOfNextPeriodUsed; m_uInstantSentBytes = 0; } else { if(uElapsedTime <= INSTANT_BANDWIDTH_CHECK_INTERVAL_IN_SECS) m_uInstantSpeed = m_uAverageSpeed; } m_pMutex->unlock(); } union _ack_buffer { char cAckBuffer[4]; quint32 i32AckBuffer; }; void DccSendThread::run() { m_pTimeInterval->mark(); m_pMutex->lock(); m_uStartTime = m_pTimeInterval->secondsCounter(); m_pMutex->unlock(); m_uTotalSentBytes = 0; m_uInstantSentBytes = 0; int iFailedSelects = 0; _ack_buffer ackbuffer; int iBytesInAckBuffer = 0; quint32 uLastAck = 0; quint64 uTotLastAck = 0; bool bAckHack = false; quint64 iAckHackRounds = 0; if(m_pOpt->iPacketSize < 32) m_pOpt->iPacketSize = 32; char * buffer = (char *)KviMemory::allocate(m_pOpt->iPacketSize * sizeof(char)); QFile * pFile = new QFile(QString::fromUtf8(m_pOpt->szFileName.ptr())); if(!pFile->open(QIODevice::ReadOnly)) { postErrorEvent(KviError::CantOpenFileForReading); goto exit_dcc; } if(pFile->size() < 1) { postErrorEvent(KviError::CantSendAZeroSizeFile); goto exit_dcc; } if(pFile->size() >= 0xffffffff) { //dcc acks support only files up to 4GiB bAckHack = true; } if(m_pOpt->uStartPosition > 0) { // seek if(!(pFile->seek(m_pOpt->uStartPosition))) { postErrorEvent(KviError::FileIOError); goto exit_dcc; } } uLastAck = m_pOpt->uStartPosition; for(;;) { // Dequeue events while(KviThreadEvent * e = dequeueEvent()) { if(e->id() == KVI_THREAD_EVENT_TERMINATE) { delete e; goto exit_dcc; } else { // Other events are senseless to us delete e; } } bool bCanRead; bool bCanWrite; if(kvi_select(m_fd, &bCanRead, &bCanWrite, 15000)) { // reset the sleep time iFailedSelects = 0; if(bCanRead) { if(!m_pOpt->bNoAcks) { int iAckBytesToRead = 4 - iBytesInAckBuffer; int readLen; #ifdef COMPILE_SSL_SUPPORT if(m_pSSL) { readLen = m_pSSL->read((ackbuffer.cAckBuffer + iBytesInAckBuffer), iAckBytesToRead); } else { #endif readLen = kvi_socket_recv(m_fd, (ackbuffer.cAckBuffer + iBytesInAckBuffer), iAckBytesToRead); #ifdef COMPILE_SSL_SUPPORT } #endif if(readLen > 0) { iBytesInAckBuffer += readLen; if(iBytesInAckBuffer == 4) { quint32 iNewAck = ntohl(ackbuffer.i32AckBuffer); if(iNewAck > pFile->pos()) { // the peer is drunk or is trying to fool us postErrorEvent(KviError::AcknowledgeError); break; } if(iNewAck < uLastAck) { if(bAckHack) { //we reached the 4gb ack limit iAckHackRounds++; } else { // the peer is drunk or is trying to fool us postErrorEvent(KviError::AcknowledgeError); break; } } uLastAck = iNewAck; if(bAckHack) { uTotLastAck = (iAckHackRounds << 32) + iNewAck; } else { uTotLastAck = iNewAck; } iBytesInAckBuffer = 0; } } else { #ifdef COMPILE_SSL_SUPPORT if(m_pSSL) { // ssl error....? switch(m_pSSL->getProtocolError(readLen)) { case KviSSL::ZeroReturn: //if (!handleInvalidSocketRead(readLen) // break; readLen = 0; break; case KviSSL::Success: case KviSSL::WantRead: case KviSSL::WantWrite: // hmmm... break; case KviSSL::SyscallError: { int iE = m_pSSL->getLastError(true); if(iE != 0) { raiseSSLError(); postErrorEvent(KviError::SSLError); goto exit_dcc; } } break; case KviSSL::SSLError: { raiseSSLError(); postErrorEvent(KviError::SSLError); goto exit_dcc; } break; default: // Raise unknown SSL ERROR postErrorEvent(KviError::SSLError); goto exit_dcc; break; } } if(!m_pSSL && !handleInvalidSocketRead(readLen)) break; #else if(!handleInvalidSocketRead(readLen)) break; #endif } // update stats m_pMutex->lock(); // is this really necessary ? m_uAckedBytes = uTotLastAck; m_pMutex->unlock(); if(uLastAck >= (quint64)pFile->size()) { KviThreadEvent * e = new KviThreadEvent(KVI_DCC_THREAD_EVENT_SUCCESS); postEvent(parent(), e); break; } } else { // No acknowledges if(m_pOpt->bIsTdcc) { // We expect the remote end to close the connection when the whole file has been sent if(pFile->atEnd()) { int iAck; int readLen; #ifdef COMPILE_SSL_SUPPORT if(m_pSSL) { readLen = m_pSSL->read((char *)&iAck, 4); } else { #endif readLen = kvi_socket_recv(m_fd, (char *)&iAck, 4); #ifdef COMPILE_SSL_SUPPORT } #endif if(readLen == 0) { // done...success updateStats(); KviThreadEvent * e = new KviThreadEvent(KVI_DCC_THREAD_EVENT_SUCCESS); postEvent(parent(), e); break; } else { if(readLen < 0) { #ifdef COMPILE_SSL_SUPPORT if(m_pSSL) { // ssl error....? switch(m_pSSL->getProtocolError(readLen)) { case KviSSL::ZeroReturn: readLen = 0; break; case KviSSL::Success: case KviSSL::WantRead: case KviSSL::WantWrite: // hmmm... break; case KviSSL::SyscallError: { int iE = m_pSSL->getLastError(true); if(iE != 0) { raiseSSLError(); postErrorEvent(KviError::SSLError); goto exit_dcc; } } break; case KviSSL::SSLError: { raiseSSLError(); postErrorEvent(KviError::SSLError); goto exit_dcc; } break; default: // Raise unknown SSL ERROR postErrorEvent(KviError::SSLError); goto exit_dcc; break; } } if(!m_pSSL && !handleInvalidSocketRead(readLen)) break; #else if(!handleInvalidSocketRead(readLen)) break; #endif } else { KviThreadDataEvent * e = new KviThreadDataEvent(KVI_DCC_THREAD_EVENT_MESSAGE); e->setData(new KviCString(__tr2qs_ctx("WARNING: received data in a DCC TSEND, there should be no acknowledges", "dcc"))); postEvent(parent(), e); } } } } } } if(bCanWrite) { if(!pFile->atEnd()) { if(m_pOpt->bFastSend || m_pOpt->bNoAcks || (uLastAck == (quint64)pFile->pos())) { // maximum readable size qint64 toRead = pFile->size() - pFile->pos(); // the max number of bytes we can send in this interval (bandwidth limit) m_pMutex->lock(); // FIXME: how to remove this lock ? uint uMaxPossible = m_pOpt->uMaxBandwidth < MAX_DCC_BANDWIDTH_LIMIT ? m_pOpt->uMaxBandwidth * INSTANT_BANDWIDTH_CHECK_INTERVAL_IN_SECS : MAX_DCC_BANDWIDTH_LIMIT * INSTANT_BANDWIDTH_CHECK_INTERVAL_IN_SECS; m_pMutex->unlock(); if(uMaxPossible < m_uInstantSentBytes) toRead = 0; // already sent too much! else { uMaxPossible -= m_uInstantSentBytes; if(toRead > uMaxPossible) toRead = uMaxPossible; } // limit to packet size if(toRead > m_pOpt->iPacketSize) toRead = m_pOpt->iPacketSize; int written = 0; if(toRead > 0) { // read data int readed = pFile->read(buffer, toRead); if(readed < toRead) { postErrorEvent(KviError::FileIOError); break; } // send it out #ifdef COMPILE_SSL_SUPPORT if(m_pSSL) { written = m_pSSL->write(buffer, toRead); } else { #endif written = kvi_socket_send(m_fd, buffer, toRead); #ifdef COMPILE_SSL_SUPPORT } #endif if(written < toRead) { if(written < 0) { #ifdef COMPILE_SSL_SUPPORT if(m_pSSL) { // ops...might be an SSL error switch(m_pSSL->getProtocolError(written)) { case KviSSL::Success: case KviSSL::WantWrite: case KviSSL::WantRead: // Async continue... goto handle_system_error; break; case KviSSL::SyscallError: { int iSSLErr = m_pSSL->getLastError(true); if(iSSLErr != 0) { raiseSSLError(); postErrorEvent(KviError::SSLError); goto exit_dcc; } else { goto handle_system_error; } } break; case KviSSL::SSLError: raiseSSLError(); postErrorEvent(KviError::SSLError); goto exit_dcc; break; default: postErrorEvent(KviError::SSLError); goto exit_dcc; break; } } if(!m_pSSL && !handleInvalidSocketRead(written)) break; #else if(!handleInvalidSocketRead(written)) break; #endif handle_system_error: int err = kvi_socket_error(); #if defined(COMPILE_ON_WINDOWS) || defined(COMPILE_ON_MINGW) if((err != EAGAIN) && (err != EINTR) && (err != WSAEWOULDBLOCK)) #else if((err != EAGAIN) && (err != EINTR)) #endif { postErrorEvent(KviError::translateSystemError(err)); goto exit_dcc; } } else { // seek back to the right position pFile->seek(pFile->pos() - (toRead - written)); } } } else { // just nothing to send out in this interval // sleep a while if(m_uInstantSpeedInterval < (INSTANT_BANDWIDTH_CHECK_INTERVAL_IN_MSECS - 100)) { msleep(100); } else if(m_uInstantSpeedInterval < (INSTANT_BANDWIDTH_CHECK_INTERVAL_IN_MSECS - 20)) { msleep(20); } } m_uTotalSentBytes += written; m_uInstantSentBytes += written; m_uFilePosition = pFile->pos(); updateStats(); } } else { if(m_pOpt->bNoAcks && !m_pOpt->bIsTdcc) { // at end of the file in a blind dcc send... // not in a tdcc: we can close the file... updateStats(); KviThreadEvent * e = new KviThreadEvent(KVI_DCC_THREAD_EVENT_SUCCESS); postEvent(parent(), e); break; } else { // upload finished but we're waiting for the last ack // sleep a bit: don't lag the kernie too much while waiting msleep(100); } } } } else { // after 2 failed selects start to sleep if(iFailedSelects > 3) { // sleep up to 200 msecs if(iFailedSelects < 100) iFailedSelects++; msleep(3 * iFailedSelects); } else { iFailedSelects++; } } // include the artificial delay if needed if(m_pOpt->iIdleStepLengthInMSec > 0) { msleep(m_pOpt->iIdleStepLengthInMSec); } } exit_dcc: KviMemory::free(buffer); pFile->close(); delete pFile; pFile = nullptr; #ifdef COMPILE_SSL_SUPPORT if(m_pSSL) { KviSSLMaster::freeSSL(m_pSSL); m_pSSL = nullptr; } #endif kvi_socket_close(m_fd); m_fd = KVI_INVALID_SOCKET; } void DccSendThread::initGetInfo() { m_pMutex->lock(); } void DccSendThread::doneGetInfo() { m_pMutex->unlock(); } DccFileTransfer::DccFileTransfer(DccDescriptor * dcc) : KviFileTransfer() { init(); // ensure we're initialized g_pDccFileTransfers->append(this); m_pResumeTimer = nullptr; m_pBandwidthDialog = nullptr; m_szTransferIdString = QString(__tr2qs_ctx("TRANSFER %1", "dcc")).arg(id()); m_pDescriptor = dcc; m_pDescriptor->setTransfer(this); m_pMarshal = new DccMarshal(this); connect(m_pMarshal, SIGNAL(error(KviError::Code)), this, SLOT(handleMarshalError(KviError::Code))); connect(m_pMarshal, SIGNAL(connected()), this, SLOT(connected())); connect(m_pMarshal, SIGNAL(inProgress()), this, SLOT(connectionInProgress())); #ifdef COMPILE_SSL_SUPPORT connect(m_pMarshal, SIGNAL(startingSSLHandshake()), this, SLOT(startingSSLHandshake())); connect(m_pMarshal, SIGNAL(sslError(const char *)), this, SLOT(sslError(const char *))); #endif m_szDccType = dcc->bRecvFile ? "RECV" : "SEND"; if(dcc->bIsTdcc) m_szDccType.prepend("T"); #ifdef COMPILE_SSL_SUPPORT if(dcc->bIsSSL) m_szDccType.prepend("S"); #endif m_pSlaveRecvThread = nullptr; m_pSlaveSendThread = nullptr; m_tTransferStartTime = 0; m_tTransferEndTime = 0; m_szStatusString = __tr2qs_ctx("Setting up the connection", "dcc"); m_eGeneralStatus = Connecting; bool bOk; m_uTotalFileSize = dcc->bRecvFile ? dcc->szFileSize.toULongLong(&bOk) : dcc->szLocalFileSize.toULongLong(&bOk); if(!bOk) m_uTotalFileSize = 0; if(m_pDescriptor->bRecvFile) m_uMaxBandwidth = KVI_OPTION_BOOL(KviOption_boolLimitDccRecvSpeed) ? KVI_OPTION_UINT(KviOption_uintMaxDccRecvSpeed) : MAX_DCC_BANDWIDTH_LIMIT; else m_uMaxBandwidth = KVI_OPTION_BOOL(KviOption_boolLimitDccSendSpeed) ? KVI_OPTION_UINT(KviOption_uintMaxDccSendSpeed) : MAX_DCC_BANDWIDTH_LIMIT; startConnection(); } DccFileTransfer::~DccFileTransfer() { g_pDccFileTransfers->removeRef(this); if(m_pResumeTimer) delete m_pResumeTimer; if(m_pBandwidthDialog) delete m_pBandwidthDialog; if(m_pSlaveRecvThread) { m_pSlaveRecvThread->terminate(); delete m_pSlaveRecvThread; m_pSlaveRecvThread = nullptr; } if(m_pSlaveSendThread) { m_pSlaveSendThread->terminate(); delete m_pSlaveSendThread; m_pSlaveSendThread = nullptr; } KviThreadManager::killPendingEvents(this); delete m_pDescriptor; delete m_pMarshal; } void DccFileTransfer::bandwidthDialogDestroyed() { m_pBandwidthDialog = nullptr; } KviWindow * DccFileTransfer::eventWindow() { KviWindow * w = transferWindow(); if(w) return w; return m_pDescriptor->console(); } void DccFileTransfer::startConnection() { if(!(m_pDescriptor->bActive)) { // PASSIVE CONNECTION m_szStatusString = __tr2qs_ctx("Attempting a passive DCC %1 connection", "dcc").arg(m_szDccType.ptr()); outputAndLog(m_szStatusString); } else { // ACTIVE CONNECTION m_szStatusString = __tr2qs_ctx("Attempting an active DCC %1 connection", "dcc").arg(m_szDccType.ptr()); outputAndLog(m_szStatusString); } if(m_pDescriptor->bResume && m_pDescriptor->bRecvFile) { QString fName; KviIrcServerParser::encodeCtcpParameter(m_pDescriptor->szFileName.toUtf8().data(), fName); if(m_pDescriptor->isZeroPortRequest()) { m_pDescriptor->console()->connection()->sendFmtData("PRIVMSG %s :%cDCC RESUME %s %s %s %s%c", m_pDescriptor->console()->connection()->encodeText(m_pDescriptor->szNick).data(), 0x01, m_pDescriptor->console()->connection()->encodeText(fName).data(), m_pDescriptor->szPort.toUtf8().data(), m_pDescriptor->szLocalFileSize.toUtf8().data(), m_pDescriptor->zeroPortRequestTag(), 0x01); } else { m_pDescriptor->console()->connection()->sendFmtData("PRIVMSG %s :%cDCC RESUME %s %s %s%c", m_pDescriptor->console()->connection()->encodeText(m_pDescriptor->szNick).data(), 0x01, m_pDescriptor->console()->connection()->encodeText(fName).data(), m_pDescriptor->szPort.toUtf8().data(), m_pDescriptor->szLocalFileSize.toUtf8().data(), 0x01); } m_szStatusString = __tr2qs_ctx("Sent DCC RESUME request to %1, waiting for ACCEPT", "dcc").arg(m_pDescriptor->szNick); outputAndLog(m_szStatusString); // setup the resume timer: we don't want to wait forever if(KVI_OPTION_UINT(KviOption_uintDccSocketTimeout) < 5) KVI_OPTION_UINT(KviOption_uintDccSocketTimeout) = 5; if(m_pResumeTimer) delete m_pResumeTimer; m_pResumeTimer = new QTimer(this); connect(m_pResumeTimer, SIGNAL(timeout()), this, SLOT(resumeTimedOut())); m_pResumeTimer->setInterval(KVI_OPTION_UINT(KviOption_uintDccSocketTimeout) * 1000); m_pResumeTimer->setSingleShot(true); m_pResumeTimer->start(); } else { listenOrConnect(); } displayUpdate(); } void DccFileTransfer::listenOrConnect() { if(!(m_pDescriptor->bActive)) { #ifdef COMPILE_SSL_SUPPORT KviError::Code eError = m_pMarshal->dccListen(m_pDescriptor->szListenIp, m_pDescriptor->szListenPort, m_pDescriptor->bDoTimeout, m_pDescriptor->bIsSSL); #else KviError::Code eError = m_pMarshal->dccListen(m_pDescriptor->szListenIp, m_pDescriptor->szListenPort, m_pDescriptor->bDoTimeout); #endif if(eError != KviError::Success) handleMarshalError(eError); } else { #ifdef COMPILE_SSL_SUPPORT KviError::Code eError = m_pMarshal->dccConnect(m_pDescriptor->szIp.toUtf8().data(), m_pDescriptor->szPort.toUtf8().data(), m_pDescriptor->bDoTimeout, m_pDescriptor->bIsSSL); #else KviError::Code eError = m_pMarshal->dccConnect(m_pDescriptor->szIp.toUtf8().data(), m_pDescriptor->szPort.toUtf8().data(), m_pDescriptor->bDoTimeout); #endif if(eError != KviError::Success) handleMarshalError(eError); } displayUpdate(); } void DccFileTransfer::resumeTimedOut() { if(m_pResumeTimer) { delete m_pResumeTimer; m_pResumeTimer = nullptr; } handleMarshalError(KviError::ConnectionTimedOut); } KviWindow * DccFileTransfer::dccMarshalOutputWindow() { return transferWindow(); } const char * DccFileTransfer::dccMarshalOutputContextString() { return m_szTransferIdString.toUtf8().data(); } QString DccFileTransfer::localFileName() { return m_pDescriptor->szLocalFileName; } void DccFileTransfer::abort() { if(m_pSlaveRecvThread) m_pSlaveRecvThread->terminate(); if(m_pSlaveSendThread) m_pSlaveSendThread->terminate(); if(m_pMarshal) m_pMarshal->abort(); if(m_pDescriptor->bRecvFile) g_pApp->fileDownloadTerminated(false, m_pDescriptor->szFileName.toUtf8().data(), m_pDescriptor->szLocalFileName.toUtf8().data(), m_pDescriptor->szNick.toUtf8().data(), __tr_ctx("Aborted", "dcc")); QString tmp; if(m_pSlaveRecvThread) tmp.setNum(m_pSlaveRecvThread->receivedBytes()); else if(m_pSlaveSendThread) tmp.setNum(m_pSlaveSendThread->sentBytes()); else tmp = '0'; m_eGeneralStatus = Failure; m_tTransferEndTime = kvi_unixTime(); m_szStatusString = __tr2qs_ctx("Transfer failed: ", "dcc"); m_szStatusString += __tr2qs_ctx("Aborted", "dcc"); KVS_TRIGGER_EVENT_3(KviEvent_OnDCCFileTransferFailed, eventWindow(), QString("Aborted by user"), tmp, m_pDescriptor->idString()); outputAndLog(KVI_OUT_DCCERROR, m_szStatusString); displayUpdate(); } void DccFileTransfer::fillContextPopup(QMenu * m) { m->addAction(__tr2qs_ctx("Configure Bandwidth...", "dcc"), this, SLOT(configureBandwidth())); m->addSeparator(); m->addAction(__tr2qs_ctx("Resend DCC", "dcc"), this, SLOT(retryDCC())); m->addAction(__tr2qs_ctx("Resend TDCC", "dcc"), this, SLOT(retryTDCC())); m->addAction(__tr2qs_ctx("Resend RevDCC", "dcc"), this, SLOT(retryRevDCC())); QAction * pAction = m->addAction(__tr2qs_ctx("Abort", "dcc"), this, SLOT(abort())); if(!active()) pAction->setEnabled(false); } void DccFileTransfer::configureBandwidth() { if(m_pBandwidthDialog) return; m_pBandwidthDialog = new DccFileTransferBandwidthDialog(g_pMainWindow, this); connect(m_pBandwidthDialog, SIGNAL(destroyed()), this, SLOT(bandwidthDialogDestroyed())); m_pBandwidthDialog->setModal(true); m_pBandwidthDialog->show(); } void DccFileTransfer::retryDCC() { abort(); QString szRemoteNick = m_pDescriptor->remoteNick(); QString szFileName = m_pDescriptor->localFileName(); QString szId = m_pDescriptor->idString(); KviQString::escapeKvs(&szRemoteNick, KviQString::EscapeSpace); KviQString::escapeKvs(&szFileName, KviQString::EscapeSpace); QString szCommand = "dcc.send -r=$console($dcc.irccontext(" + szId + ")) " + szRemoteNick + " " + szFileName; KviKvsScript::run(szCommand, g_pActiveWindow); } void DccFileTransfer::retryTDCC() { abort(); QString szRemoteNick = m_pDescriptor->remoteNick(); QString szFileName = m_pDescriptor->localFileName(); QString szId = m_pDescriptor->idString(); KviQString::escapeKvs(&szRemoteNick, KviQString::EscapeSpace); KviQString::escapeKvs(&szFileName, KviQString::EscapeSpace); QString szCommand = "dcc.send -r=$console($dcc.irccontext(" + szId + ")) -t " + szRemoteNick + " " + szFileName; KviKvsScript::run(szCommand, g_pActiveWindow); } void DccFileTransfer::retryRevDCC() { abort(); QString szRemoteNick = m_pDescriptor->remoteNick(); QString szFileName = m_pDescriptor->localFileName(); QString szId = m_pDescriptor->idString(); KviQString::escapeKvs(&szRemoteNick, KviQString::EscapeSpace); KviQString::escapeKvs(&szFileName, KviQString::EscapeSpace); QString szCommand = "dcc.rsend -z -r=$console($dcc.irccontext(" + szId + ")) " + szRemoteNick + " " + szFileName; KviKvsScript::run(szCommand, g_pActiveWindow); } void DccFileTransfer::fillStatusString(QString & szBuffer) { switch(m_eGeneralStatus) { case Connecting: szBuffer = "connecting"; break; case Transferring: szBuffer = "transferring"; break; case Failure: szBuffer = "failure"; break; case Success: szBuffer = "success"; break; default: szBuffer = "unknown"; break; } } bool DccFileTransfer::active() { return ((m_eGeneralStatus == Connecting) || (m_eGeneralStatus == Transferring)); } int DccFileTransfer::bandwidthLimit() { int iLimit = m_uMaxBandwidth; // we have the cached value anyway... if(m_pDescriptor->bRecvFile) { if(m_pSlaveRecvThread) { m_pSlaveRecvThread->initGetInfo(); iLimit = (int)m_pSlaveRecvThread->bandwidthLimit(); m_pSlaveRecvThread->doneGetInfo(); if(iLimit < 0) iLimit = MAX_DCC_BANDWIDTH_LIMIT; } } else { if(m_pSlaveSendThread) { m_pSlaveSendThread->initGetInfo(); iLimit = (int)m_pSlaveSendThread->bandwidthLimit(); m_pSlaveSendThread->doneGetInfo(); if(iLimit < 0) iLimit = MAX_DCC_BANDWIDTH_LIMIT; } } return iLimit; } void DccFileTransfer::setBandwidthLimit(int iVal) { if(iVal < 0) iVal = MAX_DCC_BANDWIDTH_LIMIT; if(iVal > MAX_DCC_BANDWIDTH_LIMIT) iVal = MAX_DCC_BANDWIDTH_LIMIT; m_uMaxBandwidth = iVal; if(m_pDescriptor->bRecvFile) { if(m_pSlaveRecvThread) { m_pSlaveRecvThread->initGetInfo(); m_pSlaveRecvThread->setBandwidthLimit(iVal); m_pSlaveRecvThread->doneGetInfo(); } } else { if(m_pSlaveSendThread) { m_pSlaveSendThread->initGetInfo(); m_pSlaveSendThread->setBandwidthLimit(iVal); m_pSlaveSendThread->doneGetInfo(); } } } unsigned int DccFileTransfer::averageSpeed() { unsigned int uAvgBandwidth = 0; if(m_pDescriptor->bRecvFile) { if(m_pSlaveRecvThread) { m_pSlaveRecvThread->initGetInfo(); uAvgBandwidth = m_pSlaveRecvThread->averageSpeed(); m_pSlaveRecvThread->doneGetInfo(); } } else { if(m_pSlaveSendThread) { m_pSlaveSendThread->initGetInfo(); uAvgBandwidth = m_pSlaveSendThread->averageSpeed(); m_pSlaveSendThread->doneGetInfo(); } } return uAvgBandwidth; } unsigned int DccFileTransfer::instantSpeed() { unsigned int uInstBandwidth = 0; if(m_pDescriptor->bRecvFile) { if(m_pSlaveRecvThread) { m_pSlaveRecvThread->initGetInfo(); uInstBandwidth = m_pSlaveRecvThread->instantSpeed(); m_pSlaveRecvThread->doneGetInfo(); } } else { if(m_pSlaveSendThread) { m_pSlaveSendThread->initGetInfo(); uInstBandwidth = m_pSlaveSendThread->instantSpeed(); m_pSlaveSendThread->doneGetInfo(); } } return uInstBandwidth; } unsigned int DccFileTransfer::transferredBytes() { unsigned int uTransferred = 0; if(m_pDescriptor->bRecvFile) { if(m_pSlaveRecvThread) { m_pSlaveRecvThread->initGetInfo(); uTransferred = m_pSlaveRecvThread->filePosition(); m_pSlaveRecvThread->doneGetInfo(); } } else { if(m_pSlaveSendThread) { m_pSlaveSendThread->initGetInfo(); uTransferred = m_pSlaveSendThread->filePosition(); m_pSlaveSendThread->doneGetInfo(); } } return uTransferred; } void DccFileTransfer::displayPaint(QPainter * p, int column, QRect rect) { int width = rect.width(), height = rect.height(); QString txt; bool bIsTerminated = ((m_eGeneralStatus == Success) || (m_eGeneralStatus == Failure)); switch(column) { case COLUMN_TRANSFERTYPE: { if(!g_pDccFileTransferIcon) break; int xoffset = 0; int yoffset = 0; if(m_pDescriptor->bRecvFile) yoffset = 64; switch(m_eGeneralStatus) { case Connecting: xoffset = 0; break; case Transferring: xoffset = 48; break; case Success: xoffset = 96; break; case Failure: xoffset = 144; break; } // 48 is width, 64 is height p->drawPixmap(width / 2 - 48 / 2, rect.top() + height / 2 - 64 / 2, *g_pDccFileTransferIcon, xoffset, yoffset, 48, 64); } break; case COLUMN_FILEINFO: { QFontMetrics fm(p->font()); QString szFrom = __tr2qs_ctx("From: ", "dcc"); QString szTo = __tr2qs_ctx("To: ", "dcc"); int daW1 = fm.horizontalAdvance(szFrom); int daW2 = fm.horizontalAdvance(szTo); if(daW1 < daW2) daW1 = daW2; int iLineSpacing = fm.lineSpacing(); int iY = rect.top() + 4; p->setPen(Qt::black); KviCString szRemote(KviCString::Format, "dcc://%s@%s:%s/%s", m_pDescriptor->szNick.toUtf8().data(), m_pDescriptor->szIp.toUtf8().data(), m_pDescriptor->szPort.toUtf8().data(), m_pDescriptor->szFileName.toUtf8().data()); p->drawText(rect.left() + 4 + daW1, iY, width - (8 + daW1), height - 8, Qt::AlignTop | Qt::AlignLeft, m_pDescriptor->bRecvFile ? szRemote.ptr() : m_pDescriptor->szLocalFileName.toUtf8().data()); iY += iLineSpacing; p->drawText(rect.left() + 4 + daW1, iY, width - (8 + daW1), height - 8, Qt::AlignTop | Qt::AlignLeft, m_pDescriptor->bRecvFile ? m_pDescriptor->szLocalFileName.toUtf8().data() : szRemote.ptr()); iY += iLineSpacing; p->setPen(Qt::darkGray); p->drawText(rect.left() + 4, rect.top() + 4, width - 8, height - 8, Qt::AlignTop | Qt::AlignLeft, szFrom); p->drawText(rect.left() + 4, rect.top() + 4 + iLineSpacing, width - 8, height - 8, Qt::AlignTop | Qt::AlignLeft, szTo); p->setPen(QColor(180, 180, 200)); iLineSpacing += 2; p->drawRect(rect.left() + 4, rect.top() + height - (iLineSpacing + 4), width - 8, iLineSpacing); p->fillRect(rect.left() + 5, rect.top() + height - (iLineSpacing + 3), width - 10, iLineSpacing - 2, bIsTerminated ? QColor(210, 210, 210) : QColor(190, 190, 240)); p->setPen(Qt::black); p->drawText(rect.left() + 7, rect.top() + height - (iLineSpacing + 4), width - 14, iLineSpacing, Qt::AlignVCenter | Qt::AlignLeft, m_szStatusString); } break; case COLUMN_PROGRESS: { QFontMetrics fm(p->font()); int iW = width - 8; uint uAvgBandwidth = 0; uint uInstantSpeed = 0; quint64 uAckedBytes = 0; quint64 uTransferred = 0; int iEta = -1; if(m_pDescriptor->bRecvFile) { if(m_pSlaveRecvThread) { m_pSlaveRecvThread->initGetInfo(); uAvgBandwidth = m_pSlaveRecvThread->averageSpeed(); uInstantSpeed = m_pSlaveRecvThread->instantSpeed(); uTransferred = m_pSlaveRecvThread->filePosition(); m_pSlaveRecvThread->doneGetInfo(); } } else { if(m_pSlaveSendThread) { m_pSlaveSendThread->initGetInfo(); uAvgBandwidth = m_pSlaveSendThread->averageSpeed(); uInstantSpeed = m_pSlaveSendThread->instantSpeed(); uTransferred = m_pSlaveSendThread->filePosition(); uAckedBytes = m_pSlaveSendThread->ackedBytes(); m_pSlaveSendThread->doneGetInfo(); } } p->setPen(bIsTerminated ? Qt::lightGray : QColor(210, 210, 240)); p->drawRect(rect.left() + 4, rect.top() + 4, iW, 12); iW -= 2; if(m_uTotalFileSize > 0) { if(uAvgBandwidth > 0) { quint64 uRemaining = m_uTotalFileSize - uTransferred; iEta = uRemaining / uAvgBandwidth; } if(!m_pDescriptor->bNoAcks && (uAckedBytes > 0) && (uAckedBytes < uTransferred)) { // we are sending a file and are getting acks double dPerc1 = (double)(((double)uTransferred) * 100.0) / (double)m_uTotalFileSize; int iL1 = (int)((((double)iW) * dPerc1) / 100.0); double dPerc2 = (double)(((double)uAckedBytes) * 100.0) / (double)m_uTotalFileSize; int iL2 = (int)((((double)iW) * dPerc2) / 100.0); int iW2 = iL1 - iL2; if(iW2 > 0) p->fillRect(rect.left() + 5 + iL2, rect.top() + 5, iW2, 10, bIsTerminated ? QColor(150, 130, 110) : QColor(220, 170, 100)); p->fillRect(rect.left() + 5, rect.top() + 5, iL2, 10, bIsTerminated ? QColor(140, 110, 110) : QColor(200, 100, 100)); txt = QString(__tr2qs_ctx("%1 of %2 (%3%)", "dcc")).arg(KviQString::makeSizeReadable(uAckedBytes)).arg(KviQString::makeSizeReadable(m_uTotalFileSize)).arg(dPerc2, 0, 'f', 2); } else { // we are receiving a file or not sending acks double dPerc = (double)(((double)uTransferred) * 100.0) / (double)m_uTotalFileSize; int iL = (int)((((double)iW) * dPerc) / 100.0); p->fillRect(rect.left() + 5, rect.top() + 5, iL, 10, bIsTerminated ? QColor(140, 110, 110) : QColor(200, 100, 100)); txt = QString(__tr2qs_ctx("%1 of %2 (%3%)", "dcc")).arg(KviQString::makeSizeReadable(uTransferred)).arg(KviQString::makeSizeReadable(m_uTotalFileSize)).arg(dPerc, 0, 'f', 2); } } else { txt = QString(__tr2qs_ctx("%1", "dcc")).arg(KviQString::makeSizeReadable(uTransferred)); } p->setPen(Qt::black); p->drawText(rect.left() + 4, rect.top() + 19, width - 8, height - 8, Qt::AlignTop | Qt::AlignLeft, txt); int iLeftHalf = (iW - 2) / 2; int iRightHalf = iW - (iLeftHalf + 1); int iLineSpacing = fm.lineSpacing() + 2; if(!bIsTerminated) { txt = __tr2qs_ctx("Speed:", "dcc"); txt += " "; QString tmpisp; KviNetUtils::formatNetworkBandwidthString(tmpisp, uInstantSpeed); txt += tmpisp; txt += " ["; } else { txt = ""; } txt += __tr2qs_ctx("Average:", "dcc"); txt += " "; QString tmpspd; KviNetUtils::formatNetworkBandwidthString(tmpspd, uAvgBandwidth); txt += tmpspd; if(!bIsTerminated) { txt += "]"; } int iDaH = height - (iLineSpacing + 4); p->setPen(QColor(180, 180, 200)); p->drawRect(rect.left() + 4, rect.top() + iDaH, iLeftHalf, iLineSpacing); p->fillRect(rect.left() + 5, rect.top() + iDaH + 1, iLeftHalf - 2, iLineSpacing - 2, bIsTerminated ? QColor(210, 210, 210) : QColor(190, 190, 240)); p->setPen(bIsTerminated ? Qt::darkGray : Qt::black); p->drawText(rect.left() + 6, rect.top() + iDaH, iLeftHalf - 4, iLineSpacing, Qt::AlignLeft | Qt::AlignVCenter, txt); if(bIsTerminated) { if((m_tTransferStartTime != 0) && (m_tTransferEndTime != 0)) { QString tot = KviTimeUtils::formatTimeInterval(kvi_timeSpan(m_tTransferEndTime, m_tTransferStartTime), KviTimeUtils::NoLeadingEmptyIntervals | KviTimeUtils::NoLeadingZeroes); txt = "Total: "; txt += tot; } else { txt = ""; } } else { if(iEta >= 0) { QString eta = KviTimeUtils::formatTimeInterval(iEta, KviTimeUtils::NoLeadingEmptyIntervals | KviTimeUtils::NoLeadingZeroes); txt = "ETA: "; txt += eta; } else { txt = "ETA: ?"; } } p->setPen(QColor(180, 180, 200)); p->drawRect(rect.left() + width - (4 + iRightHalf), rect.top() + iDaH, iRightHalf, iLineSpacing); p->fillRect(rect.left() + width - (3 + iRightHalf), rect.top() + iDaH + 1, iRightHalf - 2, iLineSpacing - 2, bIsTerminated ? QColor(210, 210, 210) : QColor(190, 190, 240)); p->setPen(bIsTerminated ? Qt::darkGray : Qt::black); p->drawText(rect.left() + width - (2 + iRightHalf), rect.top() + iDaH, iRightHalf - 4, iLineSpacing, Qt::AlignLeft | Qt::AlignVCenter, txt); } break; } } int DccFileTransfer::displayHeight(int iLineSpacing) { int iH = (iLineSpacing * 3) + 10; return iH >= 70 ? iH : 70; } QString DccFileTransfer::tipText() { QString s; s = QString(R"()").arg(m_szDccType.ptr()).arg(id()); s += R"("; s += ""; s += "
DCC %1 (ID %2)
)"; s += __tr2qs_ctx("Transfer Log", "dcc"); s += "
"; s += m_szTransferLog; s += "
"; return s; } void DccFileTransfer::init() { if(g_pDccFileTransfers) return; g_pDccFileTransfers = new KviPointerList; g_pDccFileTransfers->setAutoDelete(false); QPixmap * pix = g_pIconManager->getImage("kvi_dccfiletransfericons.png", false); if(pix) g_pDccFileTransferIcon = new QPixmap(*pix); else g_pDccFileTransferIcon = nullptr; } void DccFileTransfer::done() { if(!g_pDccFileTransfers) return; while(DccFileTransfer * t = g_pDccFileTransfers->first()) delete t; delete g_pDccFileTransfers; g_pDccFileTransfers = nullptr; if(g_pDccFileTransferIcon) delete g_pDccFileTransferIcon; g_pDccFileTransferIcon = nullptr; } unsigned int DccFileTransfer::transferCount() { if(!g_pDccFileTransfers) return 0; return g_pDccFileTransfers->count(); } DccFileTransfer * DccFileTransfer::nonFailedTransferWithLocalFileName(const QString & szLocalFileName) { if(!g_pDccFileTransfers) return nullptr; for(DccFileTransfer * t = g_pDccFileTransfers->first(); t; t = g_pDccFileTransfers->next()) { #if defined(COMPILE_ON_WINDOWS) || defined(COMPILE_ON_MINGW) // on windows the file names are case insensitive if(t->localFileName().toLower() == szLocalFileName.toLower()) #else if(t->localFileName() == szLocalFileName) #endif { if(t->m_eGeneralStatus != Failure) return t; } } return nullptr; } unsigned int DccFileTransfer::runningTransfersCount() { if(!g_pDccFileTransfers) return 0; unsigned int cnt = 0; for(DccFileTransfer * t = g_pDccFileTransfers->first(); t; t = g_pDccFileTransfers->next()) { if(t->active()) cnt++; } return cnt; } bool DccFileTransfer::handleResumeAccepted(const char * filename, const char * port, const char * szZeroPortTag) { if(!g_pDccFileTransfers) return false; for(DccFileTransfer * t = g_pDccFileTransfers->first(); t; t = g_pDccFileTransfers->next()) { if(t->resumeAccepted(filename, port, szZeroPortTag)) return true; } return false; } bool DccFileTransfer::handleResumeRequest(const char * filename, const char * port, quint64 filePos) { if(!g_pDccFileTransfers) return false; for(DccFileTransfer * t = g_pDccFileTransfers->first(); t; t = g_pDccFileTransfers->next()) { if(t->doResume(filename, port, filePos)) return true; } return false; } void DccFileTransfer::outputAndLog(const QString & s) { KviWindow * out = transferWindow(); addToTransferLog(s); if(out) out->output(KVI_OUT_DCCMSG, "[%Q]: %Q", &m_szTransferIdString, &s); } void DccFileTransfer::outputAndLog(int msgtype, const QString & s) { KviWindow * out = transferWindow(); addToTransferLog(s); if(out) out->output(msgtype, "[%Q]: %Q", &m_szTransferIdString, &s); } void DccFileTransfer::addToTransferLog(const QString & s) { QDateTime dt = QDateTime::currentDateTime(); QString ts; ts = QString::asprintf("[%4d.%2d.%2d %2d:%2d:%2d] ", dt.date().year(), dt.date().month(), dt.date().day(), dt.time().hour(), dt.time().minute(), dt.time().second()); m_szTransferLog += ts + s; m_szTransferLog += "
"; } void DccFileTransfer::connectionInProgress() { if(m_pDescriptor->bActive) { // ACTIVE CONNECTION // if((kvi_strEqualCS(m_szDccType.ptr(), "RECV")) || (kvi_strEqualCS(m_szDccType.ptr(),"TRECV"))) // { // // FIXME: that's not true!... we're NOT connected here // if(TRIGGER_EVENT_5PARAM_RETVALUE(KviEvent_OnDCCGetConnected,this,m_pDescriptor->szPort.ptr(),m_pDescriptor->szFileName.ptr(),m_pDescriptor->szNick.ptr(),m_pDescriptor->szUser.ptr(),m_pDescriptor->szHost.ptr())); // } else { // if(TRIGGER_EVENT_5PARAM_RETVALUE(KviEvent_OnDCCSendConnected,this,m_pDescriptor->szPort.ptr(),m_pDescriptor->szFileName.ptr(),m_pDescriptor->szNick.ptr(),m_pDescriptor->szUser.ptr(),m_pDescriptor->szHost.ptr())); // } // m_szStatusString = __tr2qs_ctx("Contacting host %1 on port %2", "dcc").arg(m_pDescriptor->szIp, m_pDescriptor->szPort); outputAndLog(m_szStatusString); displayUpdate(); return; } // PASSIVE CONNECTION m_szStatusString = __tr2qs_ctx("Listening on interface %1 port %2", "dcc").arg(m_pMarshal->localIp(), m_pMarshal->localPort()); outputAndLog(m_szStatusString); if(m_pDescriptor->bSendRequest) { QString ip; if(!m_pDescriptor->szFakeIp.isEmpty()) { ip = m_pDescriptor->szFakeIp; } else { ip = m_pDescriptor->szListenIp; if(KVI_OPTION_BOOL(KviOption_boolDccGuessIpFromServerWhenLocalIsUnroutable)) { if(!KviNetUtils::isRoutableIpString(ip)) { // try to get the IP that the IRC server can see if(m_pDescriptor->console()) { QString tmp = m_pDescriptor->console()->connection() ? m_pDescriptor->console()->connection()->userInfo()->hostIp() : ""; if(!tmp.isEmpty()) { ip = tmp; outputAndLog(__tr2qs_ctx("The local IP address is private, determining from IRC server: %1", "dcc").arg(ip)); } else { outputAndLog(__tr2qs_ctx("The local IP address is private, but unable to determine it from the IRC server", "dcc")); } } else { outputAndLog(__tr2qs_ctx("The local IP address is private, but have no IRC server to determine it from", "dcc")); } } } } KviCString port = !m_pDescriptor->szFakePort.isEmpty() ? m_pDescriptor->szFakePort : m_pMarshal->localPort(); //#warning "OPTION FOR SENDING 127.0.0.1 and so on (not an unsigned nuumber)" struct in_addr a; if(KviNetUtils::stringIpToBinaryIp(ip, &a)) ip.setNum(htonl(a.s_addr)); QString tmp = m_pDescriptor->szFileName; // just to be sure KviQString::cutToLast(tmp, '/'); KviQString::cutToLast(tmp, '\\'); QString fName; // BUG-TO-BUG mIrc compatibility if(KVI_OPTION_BOOL(KviOption_boolDCCFileTransferReplaceOutgoingSpacesWithUnderscores)) tmp.replace(" ", "_"); KviIrcServerParser::encodeCtcpParameter(tmp.toUtf8().data(), fName); // Zero port requests want DCC SEND as back-request KviCString szReq; if(m_pDescriptor->isZeroPortRequest()) { szReq = "SEND"; if(m_pDescriptor->bIsTdcc) szReq.prepend("T"); #ifdef COMPILE_SSL_SUPPORT if(m_pDescriptor->bIsSSL) szReq.prepend("S"); #endif m_pDescriptor->console()->connection()->sendFmtData("PRIVMSG %s :%cDCC %s %s %s %s %s %s%c", m_pDescriptor->console()->connection()->encodeText(m_pDescriptor->szNick).data(), 0x01, m_pDescriptor->console()->connection()->encodeText(szReq.ptr()).data(), m_pDescriptor->console()->connection()->encodeText(fName).data(), ip.toUtf8().data(), port.ptr(), m_pDescriptor->szFileSize.toUtf8().data(), m_pDescriptor->zeroPortRequestTag(), 0x01); } else { szReq = m_szDccType; m_pDescriptor->console()->connection()->sendFmtData("PRIVMSG %s :%cDCC %s %s %s %s %Q%c", m_pDescriptor->console()->connection()->encodeText(m_pDescriptor->szNick).data(), 0x01, m_pDescriptor->console()->connection()->encodeText(szReq.ptr()).data(), m_pDescriptor->console()->connection()->encodeText(fName).data(), ip.toUtf8().data(), port.ptr(), &(m_pDescriptor->szLocalFileSize), 0x01); } outputAndLog(__tr2qs_ctx("Sent DCC %1 request to %2, waiting for remote client to connect...", "dcc").arg(szReq.ptr(), m_pDescriptor->szNick)); } else { outputAndLog(__tr2qs_ctx("DCC %1 request not sent, awaiting manual connection", "dcc").arg(m_szDccType.ptr())); } KVS_TRIGGER_EVENT_1(KviEvent_OnDCCFileTransferConnectionInProgress, eventWindow(), m_pDescriptor->idString()); displayUpdate(); } void DccFileTransfer::startingSSLHandshake() { #ifdef COMPILE_SSL_SUPPORT outputAndLog(KVI_OUT_SSL, __tr2qs_ctx("Low-level transport connection established", "dcc")); outputAndLog(KVI_OUT_SSL, __tr2qs_ctx("Starting Secure Socket Layer handshake", "dcc")); #endif } void DccFileTransfer::sslError(const char * msg) { #ifdef COMPILE_SSL_SUPPORT outputAndLog(KVI_OUT_DCCERROR, __tr2qs_ctx("[SSL ERROR]: %1", "dcc").arg(msg)); #endif } bool DccFileTransfer::event(QEvent * e) { if(e->type() == KVI_THREAD_EVENT) { switch(((KviThreadEvent *)e)->id()) { case KVI_DCC_THREAD_EVENT_ERROR: { KviError::Code * pError = ((KviThreadDataEvent *)e)->getData(); QString szErrorString = KviError::getDescription(*pError); delete pError; if(m_pDescriptor->bRecvFile) g_pApp->fileDownloadTerminated(false, m_pDescriptor->szFileName.toUtf8().data(), m_pDescriptor->szLocalFileName.toUtf8().data(), m_pDescriptor->szNick.toUtf8().data(), szErrorString.toUtf8().data()); m_szStatusString = __tr2qs_ctx("Transfer failed: ", "dcc"); m_szStatusString += szErrorString; m_eGeneralStatus = Failure; m_tTransferEndTime = kvi_unixTime(); KVS_TRIGGER_EVENT_3(KviEvent_OnDCCFileTransferFailed, eventWindow(), szErrorString, (kvs_int_t)(m_pSlaveRecvThread ? m_pSlaveRecvThread->receivedBytes() : m_pSlaveSendThread->sentBytes()), m_pDescriptor->idString()); outputAndLog(KVI_OUT_DCCERROR, m_szStatusString); displayUpdate(); return true; } break; case KVI_DCC_THREAD_EVENT_SUCCESS: { // FIXME: for >= 3.2.0 change this text to // File Upload/Download terminated, or something like this if(KVI_OPTION_BOOL(KviOption_boolNotifyDccSendSuccessInConsole)) { KviConsoleWindow * c; if(!g_pApp->windowExists(m_pDescriptor->console())) c = g_pApp->activeConsole(); else c = m_pDescriptor->console(); c->output(KVI_OUT_DCCMSG, __tr2qs_ctx("DCC %s transfer with %Q@%Q:%Q completed: \r![!dbl]%Q\r%Q\r", "dcc"), m_pDescriptor->bIsTdcc ? (m_pDescriptor->bRecvFile ? "TRECV" : "TSEND") : (m_pDescriptor->bRecvFile ? "RECV" : "SEND"), &(m_pDescriptor->szNick), &(m_pDescriptor->szIp), &(m_pDescriptor->szPort), &(KVI_OPTION_STRING(KviOption_stringUrlFileCommand)), &(m_pDescriptor->szLocalFileName)); } if(m_pDescriptor->bRecvFile) g_pApp->fileDownloadTerminated(true, m_pDescriptor->szFileName.toUtf8().data(), m_pDescriptor->szLocalFileName.toUtf8().data(), m_pDescriptor->szNick.toUtf8().data()); m_szStatusString = __tr2qs_ctx("Transfer completed", "dcc"); outputAndLog(m_szStatusString); m_eGeneralStatus = Success; m_tTransferEndTime = kvi_unixTime(); if(m_pResumeTimer) delete m_pResumeTimer; KVS_TRIGGER_EVENT_2(KviEvent_OnDCCFileTransferSuccess, eventWindow(), (kvs_int_t)(m_pSlaveRecvThread ? m_pSlaveRecvThread->receivedBytes() : m_pSlaveSendThread->sentBytes()), m_pDescriptor->idString()); displayUpdate(); if(KVI_OPTION_BOOL(KviOption_boolAutoCloseDccSendOnSuccess)) die(); return true; } break; case KVI_DCC_THREAD_EVENT_MESSAGE: { KviCString * str = ((KviThreadDataEvent *)e)->getData(); outputAndLog(QString(__tr_no_xgettext_ctx(str->ptr(), "dcc"))); delete str; return true; } break; default: qDebug("Invalid event type %d received", ((KviThreadEvent *)e)->id()); break; } } return KviFileTransfer::event(e); } void DccFileTransfer::handleMarshalError(KviError::Code eError) { QString szErr = KviError::getDescription(eError); m_eGeneralStatus = Failure; m_szStatusString = __tr2qs_ctx("Transfer failed: ", "dcc"); m_szStatusString += szErr; outputAndLog(m_szStatusString); KVS_TRIGGER_EVENT_3(KviEvent_OnDCCFileTransferFailed, eventWindow(), szErr, (kvs_int_t)0, m_pDescriptor->idString()); displayUpdate(); } void DccFileTransfer::connected() { outputAndLog(__tr2qs_ctx("Connected to %1:%2", "dcc").arg(m_pMarshal->remoteIp(), m_pMarshal->remotePort())); outputAndLog(__tr2qs_ctx("Local end is %1:%2", "dcc").arg(m_pMarshal->localIp(), m_pMarshal->localPort())); m_tTransferStartTime = kvi_unixTime(); if(!(m_pDescriptor->bActive)) { m_pDescriptor->szIp = m_pMarshal->remoteIp(); m_pDescriptor->szPort = m_pMarshal->remotePort(); m_pDescriptor->szHost = m_pMarshal->remoteIp(); } if(m_pDescriptor->bRecvFile) { KviDccRecvThreadOptions * o = new KviDccRecvThreadOptions; o->szFileName = m_pDescriptor->szLocalFileName.toUtf8().data(); bool bOk; o->uTotalFileSize = m_pDescriptor->szFileSize.toULongLong(&bOk); if(!bOk) o->uTotalFileSize = 0; o->bResume = m_pDescriptor->bResume; o->iIdleStepLengthInMSec = KVI_OPTION_BOOL(KviOption_boolDccSendForceIdleStep) ? KVI_OPTION_UINT(KviOption_uintDccSendIdleStepInMSec) : 0; o->bIsTdcc = m_pDescriptor->bIsTdcc; o->bSendZeroAck = KVI_OPTION_BOOL(KviOption_boolSendZeroAckInDccRecv); o->bSend64BitAck = KVI_OPTION_BOOL(KviOption_boolSend64BitAckInDccRecv); o->bNoAcks = m_pDescriptor->bNoAcks; o->uMaxBandwidth = m_uMaxBandwidth; m_pSlaveRecvThread = new DccRecvThread(this, m_pMarshal->releaseSocket(), o); #ifdef COMPILE_SSL_SUPPORT KviSSL * s = m_pMarshal->releaseSSL(); if(s) { m_pSlaveRecvThread->setSSL(s); } #endif m_pSlaveRecvThread->start(); } else { KviDccSendThreadOptions * o = new KviDccSendThreadOptions; o->szFileName = m_pDescriptor->szLocalFileName.toUtf8().data(); o->bFastSend = KVI_OPTION_BOOL(KviOption_boolUseFastDccSend); o->iIdleStepLengthInMSec = KVI_OPTION_BOOL(KviOption_boolDccSendForceIdleStep) ? KVI_OPTION_UINT(KviOption_uintDccSendIdleStepInMSec) : 0; bool bOk; o->bIsTdcc = m_pDescriptor->bIsTdcc; o->uStartPosition = m_pDescriptor->szFileSize.toULongLong(&bOk); if(!bOk) o->uStartPosition = 0; o->iPacketSize = KVI_OPTION_UINT(KviOption_uintDccSendPacketSize); if(o->iPacketSize < 32) o->iPacketSize = 32; o->uMaxBandwidth = m_uMaxBandwidth; o->bNoAcks = m_pDescriptor->bNoAcks; m_pSlaveSendThread = new DccSendThread(this, m_pMarshal->releaseSocket(), o); #ifdef COMPILE_SSL_SUPPORT KviSSL * s = m_pMarshal->releaseSSL(); if(s) { m_pSlaveSendThread->setSSL(s); } #endif m_pSlaveSendThread->start(); } m_eGeneralStatus = Transferring; m_szStatusString = __tr2qs_ctx("Transferring data", "dcc"); KVS_TRIGGER_EVENT_1(KviEvent_OnDCCFileTransferBegin, eventWindow(), m_pDescriptor->idString()); outputAndLog(m_szStatusString); displayUpdate(); } bool DccFileTransfer::resumeAccepted(const char * filename, const char * port, const char * szZeroPortTag) { if(!(kvi_strEqualCI(filename, m_pDescriptor->szFileName.toUtf8().data()) || KVI_OPTION_BOOL(KviOption_boolAcceptBrokenFileNameDccResumeRequests))) return false; if(!(kvi_strEqualCI(port, m_pDescriptor->szPort.toUtf8().data()) && (!m_pSlaveRecvThread) && m_pDescriptor->bResume && m_pDescriptor->bRecvFile && m_pResumeTimer)) return false; if(kvi_strEqualCI(port, "0")) { if(!kvi_strEqualCI(szZeroPortTag, m_pDescriptor->zeroPortRequestTag())) return false; } delete m_pResumeTimer; m_pResumeTimer = nullptr; outputAndLog(__tr2qs_ctx("RESUME accepted, transfer will begin at position %1", "dcc").arg(m_pDescriptor->szLocalFileSize)); listenOrConnect(); return true; } bool DccFileTransfer::doResume(const char * filename, const char * port, quint64 filePos) { if(m_pSlaveRecvThread) return false; // we're already receiving stuff... if(m_pSlaveSendThread) return false; // we're already sending stuff... if(m_pDescriptor->bRecvFile) return false; // we're receiving... can't resume anything bool bFileNameMatches = KviQString::equalCI(filename, m_pDescriptor->szFileName); bool bPortMatches = KviQString::equalCI(port, m_pMarshal->dccPort()); if(!bPortMatches) { // port doesn't match if(!bFileNameMatches) return false; // neither filename nor port match if(!KVI_OPTION_BOOL(KviOption_boolAcceptMismatchedPortDccResumeRequests)) return false; // hmm.. try to accept a mismatched port request if(_OUTPUT_VERBOSE) outputAndLog(KVI_OUT_DCCMSG, __tr2qs_ctx("Processing RESUME request with mismatched port (%1)", "dcc").arg(port)); } // port matches if(!bFileNameMatches) { if(!KVI_OPTION_BOOL(KviOption_boolAcceptBrokenFileNameDccResumeRequests)) { if(_OUTPUT_VERBOSE) outputAndLog( KVI_OUT_DCCMSG, __tr2qs_ctx("Invalid RESUME request: invalid file name (got '%1' but should be '%2')", "dcc") .arg(filename) .arg(m_pDescriptor->szFileName)); return false; // bad file name } if(_OUTPUT_VERBOSE) outputAndLog(KVI_OUT_DCCMSG, __tr2qs_ctx("Processing RESUME request with broken filename (%1)", "dcc").arg(filename)); } bool bOk; quint64 iLocalFileSize = m_pDescriptor->szLocalFileSize.toULongLong(&bOk); if(!bOk) { // ops...internal error outputAndLog(KVI_OUT_DCCERROR, __tr2qs_ctx("Internal error in RESUME request", "dcc")); return false; } if(iLocalFileSize <= filePos) { outputAndLog(KVI_OUT_DCCERROR, __tr2qs_ctx("Invalid RESUME request: position %1 is larger than file size", "dcc").arg(filePos)); return false; } outputAndLog(KVI_OUT_DCCERROR, __tr2qs_ctx("Accepting RESUME request, transfer will begin at position %1", "dcc").arg(filePos)); m_pDescriptor->szFileSize.setNum(filePos); KviCString szBuffy; KviIrcServerParser::encodeCtcpParameter(filename, szBuffy); m_pDescriptor->console()->connection()->sendFmtData("PRIVMSG %s :%cDCC ACCEPT %s %s %s%c", m_pDescriptor->console()->connection()->encodeText(m_pDescriptor->szNick).data(), 0x01, m_pDescriptor->console()->connection()->encodeText(szBuffy.ptr()).data(), port, m_pDescriptor->console()->connection()->encodeText(QString::number(filePos)).data(), 0x01); return true; } DccThread * DccFileTransfer::getSlaveThread() { if(m_pDescriptor->bRecvFile) { return m_pSlaveRecvThread; } else { return m_pSlaveSendThread; } } DccFileTransferBandwidthDialog::DccFileTransferBandwidthDialog(QWidget * pParent, DccFileTransfer * t) : QDialog(pParent) { QGridLayout * g = new QGridLayout(this); m_pTransfer = t; int iVal = m_pTransfer->bandwidthLimit(); QString szText = __tr2qs_ctx("Configure Bandwidth for DCC Transfer %1", "dcc").arg(t->id()); setWindowTitle(szText); szText = t->isFileUpload() ? __tr2qs_ctx("Limit upload bandwidth to:", "dcc") : __tr2qs_ctx("Limit download bandwidth to:", "dcc"); m_pEnableLimitCheck = new QCheckBox(szText, this); g->addWidget(m_pEnableLimitCheck, 0, 0); m_pEnableLimitCheck->setChecked((iVal >= 0) && (iVal < MAX_DCC_BANDWIDTH_LIMIT)); m_pLimitBox = new QSpinBox(this); m_pLimitBox->setMinimum(0); m_pLimitBox->setMaximum(MAX_DCC_BANDWIDTH_LIMIT - 1); m_pLimitBox->setSingleStep(1); m_pLimitBox->setEnabled((iVal >= 0) && (iVal < MAX_DCC_BANDWIDTH_LIMIT)); connect(m_pEnableLimitCheck, SIGNAL(toggled(bool)), m_pLimitBox, SLOT(setEnabled(bool))); g->addWidget(m_pLimitBox, 0, 1, 1, 2); szText = " "; szText += __tr2qs_ctx("bytes/sec", "dcc"); m_pLimitBox->setSuffix(szText); m_pLimitBox->setValue(iVal < MAX_DCC_BANDWIDTH_LIMIT ? iVal : 0); QPushButton * pb = new QPushButton(__tr2qs_ctx("OK", "dcc"), this); connect(pb, SIGNAL(clicked()), this, SLOT(okClicked())); pb->setMinimumWidth(80); g->addWidget(pb, 2, 2); pb = new QPushButton(__tr2qs_ctx("Cancel", "dcc"), this); connect(pb, SIGNAL(clicked()), this, SLOT(cancelClicked())); pb->setMinimumWidth(80); g->addWidget(pb, 2, 1); g->setColumnStretch(0, 1); g->setRowStretch(1, 1); } DccFileTransferBandwidthDialog::~DccFileTransferBandwidthDialog() = default; void DccFileTransferBandwidthDialog::okClicked() { int iVal = MAX_DCC_BANDWIDTH_LIMIT; if(m_pEnableLimitCheck->isChecked()) { iVal = m_pLimitBox->value(); if(iVal < 0) iVal = MAX_DCC_BANDWIDTH_LIMIT; if(iVal > MAX_DCC_BANDWIDTH_LIMIT) iVal = MAX_DCC_BANDWIDTH_LIMIT; } m_pTransfer->setBandwidthLimit(iVal); delete this; } void DccFileTransferBandwidthDialog::cancelClicked() { delete this; } void DccFileTransferBandwidthDialog::closeEvent(QCloseEvent * e) { e->ignore(); delete this; }