/** * Get the fastest free peer, may remove dead peers. */ PM::IPeer* ChunkDownloader::getTheFastestFreePeer() { QMutexLocker locker(&this->mutex); PM::IPeer* current = nullptr; bool isTheNmberOfPeersHasChanged = false; for (QMutableListIterator<PM::IPeer*> i(this->peers); i.hasNext();) { PM::IPeer* peer = i.next(); if (!peer->isAvailable()) { i.remove(); this->linkedPeers.rmLink(peer); isTheNmberOfPeersHasChanged = true; } else if (this->occupiedPeersDownloadingChunk.isPeerFree(peer) && (!current || peer->getSpeed() > current->getSpeed())) current = peer; } if (isTheNmberOfPeersHasChanged) emit numberOfPeersChanged(); return current; }
void ChunkDownloader::run() { int deltaRead = 0; QElapsedTimer timer; timer.start(); this->lastTransferStatus = QUEUED; try { QSharedPointer<FM::IDataWriter> writer = this->chunk->getDataWriter(); static const int SOCKET_TIMEOUT = SETTINGS.get<quint32>("socket_timeout"); static const int TIME_PERIOD_CHOOSE_ANOTHER_PEER = 1000.0 * SETTINGS.get<double>("time_recheck_chunk_factor") * SETTINGS.get<quint32>("chunk_size") / SETTINGS.get<quint32>("lan_speed"); static const int BUFFER_SIZE = SETTINGS.get<quint32>("buffer_size_writing"); char buffer[BUFFER_SIZE]; const int initialKnownBytes = this->chunk->getKnownBytes(); int bytesToRead = this->chunkSize - initialKnownBytes; int bytesToWrite = 0; int bytesWritten = 0; forever { this->mutex.lock(); if (!this->downloading) { L_DEBU(QString("Downloading aborted, chunk : %1%2").arg(this->chunk->toStringLog()).arg(this->chunk->isComplete() ? "" : " Not complete!")); this->closeTheSocket = true; // Because some garbage from the remote uploader will continue to come in this socket. this->mutex.unlock(); break; } this->mutex.unlock(); int bytesRead = this->socket->read(buffer + bytesToWrite, bytesToRead < BUFFER_SIZE - bytesToWrite ? bytesToRead : BUFFER_SIZE - bytesToWrite); bytesToRead -= bytesRead; if (bytesRead == 0) { if (!this->socket->waitForReadyRead(SOCKET_TIMEOUT)) { L_WARN(QString("Connection dropped, error = %1, bytesAvailable = %2").arg(socket->errorString()).arg(socket->bytesAvailable())); this->closeTheSocket = true; this->lastTransferStatus = TRANSFER_ERROR; break; } continue; } else if (bytesRead == -1) { L_WARN(QString("Socket : cannot receive data : %1").arg(this->chunk->toStringLog())); this->closeTheSocket = true; this->lastTransferStatus = TRANSFER_ERROR; break; } deltaRead += bytesRead; bytesToWrite += bytesRead; if (timer.elapsed() > TIME_PERIOD_CHOOSE_ANOTHER_PEER) { this->currentDownloadingPeer->setSpeed(deltaRead / timer.elapsed() * 1000); L_DEBU(QString("Check for a better peer for the chunk: %1, current peer: %2 . . .").arg(this->chunk->toStringLog()).arg(this->currentDownloadingPeer->toStringLog())); timer.start(); deltaRead = 0; // If a another peer exists and its speed is greater than our by a factor 'switch_to_another_peer_factor' // then we will try to switch to this peer. PM::IPeer* peer = this->getTheFastestFreePeer(); if ( peer && peer != this->currentDownloadingPeer && peer->getSpeed() / SETTINGS.get<double>("switch_to_another_peer_factor") > this->currentDownloadingPeer->getSpeed() ) { L_DEBU(QString("Switch to a better peer: %1").arg(peer->toStringLog())); this->closeTheSocket = true; // We ask to close the socket to avoid to get garbage data. break; } } // If the buffer is full or there is no more byte to read. if (bytesToWrite == BUFFER_SIZE || bytesToRead == 0) { writer->write(buffer, bytesToWrite); bytesWritten += bytesToWrite; bytesToWrite = 0; } this->transferRateCalculator.addData(bytesRead); if (initialKnownBytes + bytesWritten >= this->chunkSize) break; } } catch(FM::FileResetException) { L_DEBU("FileResetException"); this->closeTheSocket = true; this->lastTransferStatus = FILE_NON_EXISTENT; } catch(FM::ChunkDataUnknownException) { L_DEBU("ChunkDataUnknownException"); this->closeTheSocket = true; this->lastTransferStatus = UNABLE_TO_OPEN_THE_FILE; } catch(FM::UnableToOpenFileInWriteModeException) { L_DEBU("UnableToOpenFileInWriteModeException"); this->closeTheSocket = true; this->lastTransferStatus = UNABLE_TO_OPEN_THE_FILE; } catch(FM::IOErrorException&) { L_DEBU("IOErrorException"); this->closeTheSocket = true; this->lastTransferStatus = FILE_IO_ERROR; } catch (FM::ChunkDeletedException&) { L_DEBU("ChunkDeletedException"); this->closeTheSocket = true; this->lastTransferStatus = FILE_NON_EXISTENT; } catch (FM::TryToWriteBeyondTheEndOfChunkException&) { L_DEBU("TryToWriteBeyondTheEndOfChunkException"); this->closeTheSocket = true; this->lastTransferStatus = GOT_TOO_MUCH_DATA; } catch (FM::hashMissmatchException) { static const quint32 BLOCK_DURATION = SETTINGS.get<quint32>("block_duration_corrupted_data"); L_USER(QString(tr("Corrupted data received for the file \"%1\" from peer %2. Peer blocked for %3 ms")).arg(this->chunk->getFilePath()).arg(this->currentDownloadingPeer->getNick()).arg(BLOCK_DURATION)); /*: A reason why the user has been blocked */ this->currentDownloadingPeer->block(BLOCK_DURATION, tr("Has sent corrupted data")); this->closeTheSocket = true; this->lastTransferStatus = HASH_MISSMATCH; } if (timer.elapsed() > MINIMUM_DELTA_TIME_TO_COMPUTE_SPEED) this->currentDownloadingPeer->setSpeed(deltaRead / timer.elapsed() * 1000); this->socket->setReadBufferSize(0); this->socket->moveToThread(this->mainThread); }