File indexing completed on 2025-02-16 04:37:32
0001 /* 0002 SPDX-FileCopyrightText: 2005 Joris Guisson <joris.guisson@gmail.com> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "chunkdownload.h" 0008 0009 #include <KLocalizedString> 0010 0011 #include <algorithm> 0012 0013 #include <diskio/chunk.h> 0014 #include <diskio/piecedata.h> 0015 #include <download/piece.h> 0016 #include <interfaces/piecedownloader.h> 0017 #include <util/array.h> 0018 #include <util/error.h> 0019 #include <util/file.h> 0020 #include <util/log.h> 0021 0022 #include "downloader.h" 0023 0024 namespace bt 0025 { 0026 DownloadStatus::DownloadStatus() 0027 : timeouts(0) 0028 { 0029 } 0030 0031 DownloadStatus::~DownloadStatus() 0032 { 0033 } 0034 0035 void DownloadStatus::add(Uint32 p) 0036 { 0037 status.insert(p); 0038 } 0039 0040 void DownloadStatus::remove(Uint32 p) 0041 { 0042 status.remove(p); 0043 } 0044 0045 bool DownloadStatus::contains(Uint32 p) 0046 { 0047 return status.contains(p); 0048 } 0049 0050 void DownloadStatus::clear() 0051 { 0052 status.clear(); 0053 } 0054 0055 //////////////////////////////////////////////////// 0056 0057 ChunkDownload::ChunkDownload(Chunk *chunk) 0058 : chunk(chunk) 0059 { 0060 num = num_downloaded = 0; 0061 num = chunk->getSize() / MAX_PIECE_LEN; 0062 0063 if (chunk->getSize() % MAX_PIECE_LEN != 0) { 0064 last_size = chunk->getSize() % MAX_PIECE_LEN; 0065 num++; 0066 } else { 0067 last_size = MAX_PIECE_LEN; 0068 } 0069 0070 pieces = BitSet(num); 0071 pieces.clear(); 0072 piece_data = new PieceData::Ptr[num]; // array of pointers to the piece data 0073 0074 dstatus.setAutoDelete(true); 0075 0076 num_pieces_in_hash = 0; 0077 hash_gen.start(); 0078 } 0079 0080 ChunkDownload::~ChunkDownload() 0081 { 0082 delete[] piece_data; 0083 } 0084 0085 bool ChunkDownload::piece(const Piece &p, bool &ok) 0086 { 0087 ok = false; 0088 timer.update(); 0089 0090 Uint32 pp = p.getOffset() / MAX_PIECE_LEN; 0091 Uint32 len = pp == num - 1 ? last_size : MAX_PIECE_LEN; 0092 if (pp >= num || pieces.get(pp) || p.getLength() != len) 0093 return false; 0094 0095 if (DownloadStatus *ds = dstatus.find(p.getPieceDownloader())) 0096 ds->remove(pp); 0097 0098 PieceData::Ptr buf = chunk->getPiece(p.getOffset(), p.getLength(), false); 0099 if (buf && buf->write(p.getData(), p.getLength()) == p.getLength()) { 0100 piece_data[pp] = buf; 0101 ok = true; 0102 pieces.set(pp, true); 0103 piece_providers.insert(p.getPieceDownloader()); 0104 num_downloaded++; 0105 if (pdown.count() > 1) { 0106 endgameCancel(p); 0107 } 0108 0109 updateHash(); 0110 0111 if (num_downloaded >= num) { 0112 // finalize hash 0113 hash_gen.end(); 0114 releaseAllPDs(); 0115 return true; 0116 } 0117 } 0118 0119 sendRequests(); 0120 return false; 0121 } 0122 0123 void ChunkDownload::releaseAllPDs() 0124 { 0125 for (PieceDownloader *pd : std::as_const(pdown)) { 0126 pd->release(); 0127 sendCancels(pd); 0128 disconnect(pd, &PieceDownloader::timedout, this, &ChunkDownload::onTimeout); 0129 disconnect(pd, &PieceDownloader::rejected, this, &ChunkDownload::onRejected); 0130 } 0131 dstatus.clear(); 0132 pdown.clear(); 0133 } 0134 0135 bool ChunkDownload::assign(PieceDownloader *pd) 0136 { 0137 if (!pd || pdown.contains(pd)) 0138 return false; 0139 0140 pd->grab(); 0141 pdown.append(pd); 0142 dstatus.insert(pd, new DownloadStatus()); 0143 connect(pd, &PieceDownloader::timedout, this, &ChunkDownload::onTimeout); 0144 connect(pd, &PieceDownloader::rejected, this, &ChunkDownload::onRejected); 0145 sendRequests(); 0146 return true; 0147 } 0148 0149 void ChunkDownload::release(PieceDownloader *pd) 0150 { 0151 if (!pdown.contains(pd)) 0152 return; 0153 0154 pd->release(); 0155 sendCancels(pd); 0156 disconnect(pd, &PieceDownloader::timedout, this, &ChunkDownload::onTimeout); 0157 disconnect(pd, &PieceDownloader::rejected, this, &ChunkDownload::onRejected); 0158 dstatus.erase(pd); 0159 pdown.removeAll(pd); 0160 } 0161 0162 void ChunkDownload::notDownloaded(const Request &r, bool reject) 0163 { 0164 // find the peer 0165 DownloadStatus *ds = dstatus.find(r.getPieceDownloader()); 0166 if (ds) { 0167 // Out(SYS_DIO|LOG_DEBUG) << "ds != 0" << endl; 0168 Uint32 p = r.getOffset() / MAX_PIECE_LEN; 0169 ds->remove(p); 0170 0171 PieceDownloader *pd = r.getPieceDownloader(); 0172 if (reject) { 0173 // reject, so release the PieceDownloader 0174 pd->release(); 0175 sendCancels(pd); 0176 killed(pd); 0177 } else { 0178 pd->cancel(r); // cancel request 0179 ds->timeout(); 0180 // if we have more then one PieceDownloader and there are timeouts, release it 0181 if (ds->numTimeouts() > 0 && pdown.count() > 0) { 0182 pd->release(); 0183 sendCancels(pd); 0184 killed(pd); 0185 } 0186 } 0187 } 0188 0189 sendRequests(); 0190 } 0191 0192 void ChunkDownload::onRejected(const Request &r) 0193 { 0194 if (chunk->getIndex() == r.getIndex()) { 0195 notDownloaded(r, true); 0196 } 0197 } 0198 0199 void ChunkDownload::onTimeout(const Request &r) 0200 { 0201 // see if we are dealing with a piece of ours 0202 if (chunk->getIndex() == r.getIndex()) { 0203 Out(SYS_CON | LOG_DEBUG) 0204 << QString("Request timed out %1 %2 %3 %4").arg(r.getIndex()).arg(r.getOffset()).arg(r.getLength()).arg(r.getPieceDownloader()->getName()) << endl; 0205 0206 notDownloaded(r, false); 0207 } 0208 } 0209 0210 Uint32 ChunkDownload::bestPiece(PieceDownloader *pd) 0211 { 0212 Uint32 best = num; 0213 Uint32 best_count = 0; 0214 // select the piece which is being downloaded the least 0215 for (Uint32 i = 0; i < num; i++) { 0216 if (pieces.get(i)) 0217 continue; 0218 0219 DownloadStatus *ds = dstatus.find(pd); 0220 if (ds && ds->contains(i)) 0221 continue; 0222 0223 Uint32 times_downloading = 0; 0224 PtrMap<PieceDownloader *, DownloadStatus>::iterator j = dstatus.begin(); 0225 while (j != dstatus.end()) { 0226 if (j->first != pd && j->second->contains(i)) 0227 times_downloading++; 0228 ++j; 0229 } 0230 0231 // nobody is downloading this piece, so return it 0232 if (times_downloading == 0) 0233 return i; 0234 0235 // check if the piece is better then the current best 0236 if (best == num || best_count > times_downloading) { 0237 best_count = times_downloading; 0238 best = i; 0239 } 0240 } 0241 0242 return best; 0243 } 0244 0245 void ChunkDownload::sendRequests() 0246 { 0247 timer.update(); 0248 QList<PieceDownloader *> tmp = pdown; 0249 0250 while (tmp.count() > 0) { 0251 for (QList<PieceDownloader *>::iterator i = tmp.begin(); i != tmp.end();) { 0252 PieceDownloader *pd = *i; 0253 if (!pd->isChoked() && pd->canAddRequest() && sendRequest(pd)) 0254 ++i; 0255 else 0256 i = tmp.erase(i); 0257 } 0258 } 0259 } 0260 0261 bool ChunkDownload::sendRequest(PieceDownloader *pd) 0262 { 0263 DownloadStatus *ds = dstatus.find(pd); 0264 if (!ds || pd->isChoked()) 0265 return false; 0266 0267 // get the best piece to download 0268 Uint32 bp = bestPiece(pd); 0269 if (bp >= num) 0270 return false; 0271 0272 pd->download(Request(chunk->getIndex(), bp * MAX_PIECE_LEN, bp + 1 < num ? MAX_PIECE_LEN : last_size, pd)); 0273 ds->add(bp); 0274 0275 Uint32 left = num - num_downloaded; 0276 if (left < 2 && left > 0) 0277 pd->setNearlyDone(true); 0278 0279 return true; 0280 } 0281 0282 void ChunkDownload::update() 0283 { 0284 // go over all PD's and do requets again 0285 sendRequests(); 0286 } 0287 0288 void ChunkDownload::sendCancels(PieceDownloader *pd) 0289 { 0290 DownloadStatus *ds = dstatus.find(pd); 0291 if (!ds) 0292 return; 0293 0294 DownloadStatus::iterator itr = ds->begin(); 0295 while (itr != ds->end()) { 0296 Uint32 i = *itr; 0297 pd->cancel(Request(chunk->getIndex(), i * MAX_PIECE_LEN, i + 1 < num ? MAX_PIECE_LEN : last_size, nullptr)); 0298 ++itr; 0299 } 0300 ds->clear(); 0301 timer.update(); 0302 } 0303 0304 void ChunkDownload::endgameCancel(const Piece &p) 0305 { 0306 auto i = pdown.constBegin(); 0307 while (i != pdown.constEnd()) { 0308 PieceDownloader *pd = *i; 0309 DownloadStatus *ds = dstatus.find(pd); 0310 Uint32 pp = p.getOffset() / MAX_PIECE_LEN; 0311 if (ds && ds->contains(pp)) { 0312 pd->cancel(Request(p)); 0313 ds->remove(pp); 0314 } 0315 i++; 0316 } 0317 } 0318 0319 void ChunkDownload::killed(PieceDownloader *pd) 0320 { 0321 if (!pdown.contains(pd)) 0322 return; 0323 0324 dstatus.erase(pd); 0325 pdown.removeAll(pd); 0326 disconnect(pd, &PieceDownloader::timedout, this, &ChunkDownload::onTimeout); 0327 disconnect(pd, &PieceDownloader::rejected, this, &ChunkDownload::onRejected); 0328 } 0329 0330 Uint32 ChunkDownload::getChunkIndex() const 0331 { 0332 return chunk->getIndex(); 0333 } 0334 0335 QString ChunkDownload::getPieceDownloaderName() const 0336 { 0337 if (pdown.count() == 0) { 0338 return QString(); 0339 } else if (pdown.count() == 1) { 0340 return pdown.first()->getName(); 0341 } else { 0342 return i18np("1 peer", "%1 peers", pdown.count()); 0343 } 0344 } 0345 0346 Uint32 ChunkDownload::getDownloadSpeed() const 0347 { 0348 Uint32 r = 0; 0349 for (PieceDownloader *pd : std::as_const(pdown)) 0350 r += pd->getDownloadRate(); 0351 0352 return r; 0353 } 0354 0355 void ChunkDownload::save(File &file) 0356 { 0357 ChunkDownloadHeader hdr; 0358 hdr.index = chunk->getIndex(); 0359 hdr.num_bits = pieces.getNumBits(); 0360 hdr.buffered = true; // unused 0361 // save the chunk header 0362 file.write(&hdr, sizeof(ChunkDownloadHeader)); 0363 // save the bitset 0364 file.write(pieces.getData(), pieces.getNumBytes()); 0365 0366 // save how many PieceHeader structs are to be written 0367 Uint32 num_pieces_to_follow = 0; 0368 for (Uint32 i = 0; i < hdr.num_bits; i++) 0369 if (piece_data[i] && piece_data[i]->ok()) 0370 num_pieces_to_follow++; 0371 0372 file.write(&num_pieces_to_follow, sizeof(Uint32)); 0373 0374 // save all buffered pieces 0375 for (Uint32 i = 0; i < hdr.num_bits; i++) { 0376 if (!piece_data[i] || !piece_data[i]->ok()) 0377 continue; 0378 0379 PieceData::Ptr pd = piece_data[i]; 0380 PieceHeader phdr; 0381 phdr.piece = i; 0382 phdr.size = pd->length(); 0383 phdr.mapped = pd->mapped() ? 1 : 0; 0384 file.write(&phdr, sizeof(PieceHeader)); 0385 if (!pd->mapped()) { // buffered pieces need to be saved 0386 pd->writeToFile(file, pd->length()); 0387 } 0388 } 0389 } 0390 0391 bool ChunkDownload::load(File &file, ChunkDownloadHeader &hdr, bool update_hash) 0392 { 0393 // read pieces 0394 if (hdr.num_bits != num) 0395 return false; 0396 0397 pieces = BitSet(hdr.num_bits); 0398 file.read(pieces.getData(), pieces.getNumBytes()); 0399 pieces.updateNumOnBits(); 0400 0401 num_downloaded = pieces.numOnBits(); 0402 Uint32 num_pieces_to_follow = 0; 0403 if (file.read(&num_pieces_to_follow, sizeof(Uint32)) != sizeof(Uint32) || num_pieces_to_follow > num) 0404 return false; 0405 0406 for (Uint32 i = 0; i < num_pieces_to_follow; i++) { 0407 PieceHeader phdr; 0408 if (file.read(&phdr, sizeof(PieceHeader)) != sizeof(PieceHeader)) 0409 return false; 0410 0411 if (phdr.piece >= num) 0412 return false; 0413 0414 PieceData::Ptr p = chunk->getPiece(phdr.piece * MAX_PIECE_LEN, phdr.size, false); 0415 if (!p) 0416 return false; 0417 0418 if (!phdr.mapped) { 0419 if (p->readFromFile(file, p->length()) != p->length()) { 0420 return false; 0421 } 0422 } 0423 piece_data[phdr.piece] = p; 0424 } 0425 0426 // initialize hash 0427 if (update_hash) { 0428 num_pieces_in_hash = 0; 0429 updateHash(); 0430 } 0431 0432 // add a 0 downloader, so that pieces downloaded 0433 // in a previous session cannot get a peer banned in this session 0434 if (num_downloaded) 0435 piece_providers.insert(nullptr); 0436 0437 return true; 0438 } 0439 0440 Uint32 ChunkDownload::bytesDownloaded() const 0441 { 0442 Uint32 num_bytes = 0; 0443 for (Uint32 i = 0; i < num; i++) { 0444 if (pieces.get(i)) { 0445 num_bytes += i == num - 1 ? last_size : MAX_PIECE_LEN; 0446 } 0447 } 0448 return num_bytes; 0449 } 0450 0451 void ChunkDownload::cancelAll() 0452 { 0453 auto i = pdown.constBegin(); 0454 while (i != pdown.constEnd()) { 0455 sendCancels(*i); 0456 i++; 0457 } 0458 } 0459 0460 PieceDownloader *ChunkDownload::getOnlyDownloader() 0461 { 0462 if (piece_providers.size() == 1) { 0463 return *piece_providers.begin(); 0464 } else { 0465 return nullptr; 0466 } 0467 } 0468 0469 void ChunkDownload::getStats(Stats &s) 0470 { 0471 s.chunk_index = chunk->getIndex(); 0472 s.current_peer_id = getPieceDownloaderName(); 0473 s.download_speed = getDownloadSpeed(); 0474 s.num_downloaders = getNumDownloaders(); 0475 s.pieces_downloaded = num_downloaded; 0476 s.total_pieces = num; 0477 } 0478 0479 bool ChunkDownload::isChoked() const 0480 { 0481 QList<PieceDownloader *>::const_iterator i = pdown.begin(); 0482 while (i != pdown.end()) { 0483 const PieceDownloader *pd = *i; 0484 // if there is one which isn't choked 0485 if (!pd->isChoked()) 0486 return false; 0487 ++i; 0488 } 0489 return true; 0490 } 0491 0492 void ChunkDownload::updateHash() 0493 { 0494 // update the hash until where we can 0495 Uint32 nn = num_pieces_in_hash; 0496 while (nn < num && pieces.get(nn)) 0497 nn++; 0498 0499 for (Uint32 i = num_pieces_in_hash; i < nn; i++) { 0500 PieceData::Ptr piece = piece_data[i]; 0501 Uint32 len = i == num - 1 ? last_size : MAX_PIECE_LEN; 0502 if (!piece) 0503 piece = chunk->getPiece(i * MAX_PIECE_LEN, len, true); 0504 0505 if (piece && piece->ok()) { 0506 piece->updateHash(hash_gen); 0507 chunk->savePiece(piece); 0508 } 0509 } 0510 num_pieces_in_hash = nn; 0511 } 0512 0513 }