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 }