File indexing completed on 2024-11-24 04:31:17

0001 /*
0002     SPDX-FileCopyrightText: 2005 Joris Guisson <joris.guisson@gmail.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 #include "downloader.h"
0007 
0008 #include <QFile>
0009 #include <QTextStream>
0010 #include <klocalizedstring.h>
0011 
0012 #include "chunkdownload.h"
0013 #include "chunkselector.h"
0014 #include "version.h"
0015 #include "webseed.h"
0016 #include <diskio/chunkmanager.h>
0017 #include <diskio/piecedata.h>
0018 #include <download/piece.h>
0019 #include <interfaces/monitorinterface.h>
0020 #include <peer/accessmanager.h>
0021 #include <peer/badpeerslist.h>
0022 #include <peer/chunkcounter.h>
0023 #include <peer/peer.h>
0024 #include <peer/peerdownloader.h>
0025 #include <peer/peermanager.h>
0026 #include <torrent/torrent.h>
0027 #include <util/array.h>
0028 #include <util/error.h>
0029 #include <util/file.h>
0030 #include <util/functions.h>
0031 #include <util/log.h>
0032 #include <util/sha1hash.h>
0033 
0034 namespace bt
0035 {
0036 bool Downloader::use_webseeds = true;
0037 
0038 Downloader::Downloader(Torrent &tor, PeerManager &pman, ChunkManager &cman)
0039     : tor(tor)
0040     , pman(pman)
0041     , cman(cman)
0042     , bytes_downloaded(0)
0043     , tmon(nullptr)
0044     , chunk_selector(nullptr)
0045     , webseed_endgame_mode(false)
0046 {
0047     webseeds_on = use_webseeds;
0048     pman.setPieceHandler(this);
0049     chunk_selector = new ChunkSelector();
0050     chunk_selector->init(&cman, this, &pman);
0051 
0052     Uint64 total = tor.getTotalSize();
0053     bytes_downloaded = (total - cman.bytesLeft());
0054     curr_chunks_downloaded = 0;
0055     unnecessary_data = 0;
0056 
0057     current_chunks.setAutoDelete(true);
0058 
0059     active_webseed_downloads = 0;
0060     const QList<QUrl> &urls = tor.getWebSeeds();
0061     for (const QUrl &u : urls) {
0062         if (u.scheme() == QLatin1String("http")) {
0063             WebSeed *ws = new WebSeed(u, false, tor, cman);
0064             webseeds.append(ws);
0065             connect(ws, &WebSeed::chunkReady, this, &Downloader::onChunkReady);
0066             connect(ws, &WebSeed::chunkDownloadStarted, this, &Downloader::chunkDownloadStarted);
0067             connect(ws, &WebSeed::chunkDownloadFinished, this, &Downloader::chunkDownloadFinished);
0068         }
0069     }
0070 
0071     if (webseeds.count() > 0) {
0072         webseed_range_size = tor.getNumChunks() / webseeds.count();
0073         if (webseed_range_size == 0)
0074             webseed_range_size = 1;
0075 
0076         // make sure the range is not to big
0077         if (webseed_range_size > tor.getNumChunks() / 10)
0078             webseed_range_size = tor.getNumChunks() / 10;
0079     } else {
0080         webseed_range_size = 1;
0081     }
0082 }
0083 
0084 Downloader::~Downloader()
0085 {
0086     delete chunk_selector;
0087     qDeleteAll(webseeds);
0088 }
0089 
0090 void Downloader::setChunkSelector(ChunkSelectorInterface *csel)
0091 {
0092     delete chunk_selector;
0093 
0094     if (!csel) // check if a custom one was provided, if not create a default one
0095         chunk_selector = new ChunkSelector();
0096     else
0097         chunk_selector = csel;
0098 
0099     chunk_selector->init(&cman, this, &pman);
0100 }
0101 
0102 void Downloader::pieceReceived(const Piece &p)
0103 {
0104     if (cman.completed())
0105         return;
0106 
0107     ChunkDownload *cd = current_chunks.find(p.getIndex());
0108     if (!cd) {
0109         unnecessary_data += p.getLength();
0110         Out(SYS_DIO | LOG_DEBUG) << "Unnecessary piece, total unnecessary data : " << BytesToString(unnecessary_data) << endl;
0111         return;
0112     }
0113 
0114     bool ok = false;
0115     if (cd->piece(p, ok)) {
0116         if (tmon)
0117             tmon->downloadRemoved(cd);
0118 
0119         if (ok)
0120             bytes_downloaded += p.getLength();
0121 
0122         if (!finished(cd)) {
0123             // if the chunk fails don't count the bytes downloaded
0124             if (cd->getChunk()->getSize() > bytes_downloaded)
0125                 bytes_downloaded = 0;
0126             else
0127                 bytes_downloaded -= cd->getChunk()->getSize();
0128             current_chunks.erase(p.getIndex());
0129         } else {
0130             current_chunks.erase(p.getIndex());
0131             for (WebSeed *ws : std::as_const(webseeds)) {
0132                 if (ws->inCurrentRange(p.getIndex()))
0133                     ws->chunkDownloaded(p.getIndex());
0134             }
0135         }
0136     } else {
0137         if (ok)
0138             bytes_downloaded += p.getLength();
0139     }
0140 
0141     if (!ok) {
0142         unnecessary_data += p.getLength();
0143         Out(SYS_DIO | LOG_DEBUG) << "Unnecessary piece, total unnecessary data : " << BytesToString(unnecessary_data) << endl;
0144     }
0145 }
0146 
0147 bool Downloader::endgameMode() const
0148 {
0149     return current_chunks.count() >= cman.chunksLeft();
0150 }
0151 
0152 void Downloader::update()
0153 {
0154     if (cman.completed())
0155         return;
0156 
0157     /*
0158         Normal update should now handle all modes properly.
0159     */
0160     normalUpdate();
0161 
0162     // now see if there aren't any timed out pieces
0163     for (PieceDownloader *pd : std::as_const(piece_downloaders)) {
0164         pd->checkTimeouts();
0165     }
0166 
0167     if (use_webseeds) {
0168         for (WebSeed *ws : std::as_const(webseeds)) {
0169             ws->update();
0170         }
0171     }
0172 
0173     if (isFinished() && webseeds_on) {
0174         for (WebSeed *ws : std::as_const(webseeds)) {
0175             ws->cancel();
0176         }
0177     }
0178 }
0179 
0180 void Downloader::normalUpdate()
0181 {
0182     for (CurChunkItr j = current_chunks.begin(); j != current_chunks.end(); ++j) {
0183         ChunkDownload *cd = j->second;
0184         if (cd->isIdle()) {
0185             continue;
0186         } else if (cd->isChoked()) {
0187             cd->releaseAllPDs();
0188         } else if (cd->needsToBeUpdated()) {
0189             cd->update();
0190         }
0191     }
0192 
0193     for (PieceDownloader *pd : std::as_const(piece_downloaders)) {
0194         if (!pd->isChoked()) {
0195             while (pd->canDownloadChunk()) {
0196                 if (!downloadFrom(pd))
0197                     break;
0198                 pd->setNearlyDone(false);
0199             }
0200         }
0201     }
0202 
0203     if (use_webseeds) {
0204         for (WebSeed *ws : std::as_const(webseeds)) {
0205             if (!ws->busy() && ws->isEnabled() && ws->failedAttempts() < 3) {
0206                 downloadFrom(ws);
0207             }
0208         }
0209     } else if (webseeds_on != use_webseeds) {
0210         // reset all webseeds, webseeds have been disabled
0211         webseeds_on = use_webseeds;
0212         for (WebSeed *ws : std::as_const(webseeds)) {
0213             if (ws->busy() && ws->isEnabled()) {
0214                 ws->cancel();
0215             }
0216         }
0217     }
0218 }
0219 
0220 ChunkDownload *Downloader::selectCD(PieceDownloader *pd, Uint32 n)
0221 {
0222     ChunkDownload *sel = nullptr;
0223     Uint32 sel_left = 0xFFFFFFFF;
0224 
0225     for (CurChunkItr j = current_chunks.begin(); j != current_chunks.end(); ++j) {
0226         ChunkDownload *cd = j->second;
0227         if (pd->isChoked() || !pd->hasChunk(cd->getChunk()->getIndex()))
0228             continue;
0229 
0230         if (cd->getNumDownloaders() == n) {
0231             // lets favor the ones which are nearly finished
0232             if (!sel || cd->getTotalPieces() - cd->getPiecesDownloaded() < sel_left) {
0233                 sel = cd;
0234                 sel_left = sel->getTotalPieces() - sel->getPiecesDownloaded();
0235             }
0236         }
0237     }
0238     return sel;
0239 }
0240 
0241 bool Downloader::findDownloadForPD(PieceDownloader *pd)
0242 {
0243     ChunkDownload *sel = nullptr;
0244 
0245     // See if there are ChunkDownload's which need a PieceDownloader
0246     sel = selectCD(pd, 0);
0247     if (sel) {
0248         return sel->assign(pd);
0249     }
0250 
0251     return false;
0252 }
0253 
0254 ChunkDownload *Downloader::selectWorst(PieceDownloader *pd)
0255 {
0256     ChunkDownload *cdmin = nullptr;
0257     for (CurChunkItr j = current_chunks.begin(); j != current_chunks.end(); ++j) {
0258         ChunkDownload *cd = j->second;
0259         if (!pd->hasChunk(cd->getChunk()->getIndex()) || cd->containsPeer(pd))
0260             continue;
0261 
0262         if (!cdmin)
0263             cdmin = cd;
0264         else if (cd->getDownloadSpeed() < cdmin->getDownloadSpeed())
0265             cdmin = cd;
0266         else if (cd->getNumDownloaders() < cdmin->getNumDownloaders())
0267             cdmin = cd;
0268     }
0269 
0270     return cdmin;
0271 }
0272 
0273 bool Downloader::downloadFrom(PieceDownloader *pd)
0274 {
0275     // first see if we can use an existing dowload
0276     if (findDownloadForPD(pd))
0277         return true;
0278 
0279     Uint32 chunk = 0;
0280     if (chunk_selector->select(pd, chunk)) {
0281         Chunk *c = cman.getChunk(chunk);
0282         if (current_chunks.contains(chunk)) {
0283             return current_chunks.find(chunk)->assign(pd);
0284         } else {
0285             ChunkDownload *cd = new ChunkDownload(c);
0286             current_chunks.insert(chunk, cd);
0287             cd->assign(pd);
0288             if (tmon)
0289                 tmon->downloadStarted(cd);
0290             return true;
0291         }
0292     } else if (pd->getNumGrabbed() == 0) {
0293         // If the peer hasn't got a chunk we want,
0294         ChunkDownload *cdmin = selectWorst(pd);
0295 
0296         if (cdmin) {
0297             return cdmin->assign(pd);
0298         }
0299     }
0300 
0301     return false;
0302 }
0303 
0304 void Downloader::downloadFrom(WebSeed *ws)
0305 {
0306     Uint32 first = 0;
0307     Uint32 last = 0;
0308     webseed_endgame_mode = false;
0309     if (chunk_selector->selectRange(first, last, webseed_range_size)) {
0310         ws->download(first, last);
0311     } else {
0312         // go to endgame mode
0313         webseed_endgame_mode = true;
0314         if (chunk_selector->selectRange(first, last, webseed_range_size))
0315             ws->download(first, last);
0316     }
0317 }
0318 
0319 bool Downloader::downloading(Uint32 chunk) const
0320 {
0321     return current_chunks.find(chunk) != nullptr;
0322 }
0323 
0324 bool Downloader::canDownloadFromWebSeed(Uint32 chunk) const
0325 {
0326     if (webseed_endgame_mode)
0327         return true;
0328 
0329     for (WebSeed *ws : std::as_const(webseeds)) {
0330         if (ws->busy() && ws->inCurrentRange(chunk))
0331             return false;
0332     }
0333 
0334     return !downloading(chunk);
0335 }
0336 
0337 Uint32 Downloader::numDownloadersForChunk(Uint32 chunk) const
0338 {
0339     const ChunkDownload *cd = current_chunks.find(chunk);
0340     if (!cd)
0341         return 0;
0342 
0343     return cd->getNumDownloaders();
0344 }
0345 
0346 void Downloader::addPieceDownloader(PieceDownloader *peer)
0347 {
0348     piece_downloaders.append(peer);
0349 }
0350 
0351 void Downloader::removePieceDownloader(PieceDownloader *peer)
0352 {
0353     for (CurChunkItr i = current_chunks.begin(); i != current_chunks.end(); ++i) {
0354         ChunkDownload *cd = i->second;
0355         cd->killed(peer);
0356     }
0357     piece_downloaders.removeAll(peer);
0358 }
0359 
0360 bool Downloader::finished(ChunkDownload *cd)
0361 {
0362     Chunk *c = cd->getChunk();
0363     // verify the data
0364     SHA1Hash h = cd->getHash();
0365 
0366     if (tor.verifyHash(h, c->getIndex())) {
0367         // hash ok so save it
0368         try {
0369             for (WebSeed *ws : std::as_const(webseeds)) {
0370                 // tell all webseeds a chunk is downloaded
0371                 if (ws->inCurrentRange(c->getIndex()))
0372                     ws->chunkDownloaded(c->getIndex());
0373             }
0374 
0375             cman.chunkDownloaded(c->getIndex());
0376             Out(SYS_GEN | LOG_IMPORTANT) << "Chunk " << c->getIndex() << " downloaded " << endl;
0377             pman.sendHave(c->getIndex());
0378             Q_EMIT chunkDownloaded(c->getIndex());
0379         } catch (Error &e) {
0380             Out(SYS_DIO | LOG_IMPORTANT) << "Error " << e.toString() << endl;
0381             Q_EMIT ioError(e.toString());
0382             return false;
0383         }
0384     } else {
0385         Out(SYS_GEN | LOG_IMPORTANT) << "Hash verification error on chunk " << c->getIndex() << endl;
0386         Out(SYS_GEN | LOG_IMPORTANT) << "Is        : " << h << endl;
0387         Out(SYS_GEN | LOG_IMPORTANT) << "Should be : " << tor.getHash(c->getIndex()) << endl;
0388 
0389         // reset chunk but only when no webseeder is downloading it
0390         if (!webseeds_chunks.find(c->getIndex()))
0391             cman.resetChunk(c->getIndex());
0392 
0393         chunk_selector->reinsert(c->getIndex());
0394 
0395         PieceDownloader *only = cd->getOnlyDownloader();
0396         if (only) {
0397             Peer::Ptr p = pman.findPeer(only);
0398             if (!p)
0399                 return false;
0400 
0401             QString ip = p->getIPAddresss();
0402             Out(SYS_GEN | LOG_NOTICE) << "Peer " << ip << " sent bad data" << endl;
0403             AccessManager::instance().banPeer(ip);
0404             p->kill();
0405         }
0406         return false;
0407     }
0408     return true;
0409 }
0410 
0411 void Downloader::clearDownloads()
0412 {
0413     current_chunks.clear();
0414     piece_downloaders.clear();
0415 
0416     for (WebSeed *ws : std::as_const(webseeds))
0417         ws->cancel();
0418 }
0419 
0420 void Downloader::pause()
0421 {
0422     if (tmon) {
0423         for (CurChunkItr i = current_chunks.begin(); i != current_chunks.end(); ++i) {
0424             ChunkDownload *cd = i->second;
0425             tmon->downloadRemoved(cd);
0426         }
0427     }
0428 
0429     current_chunks.clear();
0430     for (WebSeed *ws : std::as_const(webseeds))
0431         ws->reset();
0432 }
0433 
0434 Uint32 Downloader::downloadRate() const
0435 {
0436     // sum of the download rate of each peer
0437     Uint32 rate = 0;
0438     for (PieceDownloader *pd : std::as_const(piece_downloaders))
0439         if (pd)
0440             rate += pd->getDownloadRate();
0441 
0442     for (WebSeed *ws : std::as_const(webseeds)) {
0443         rate += ws->getDownloadRate();
0444     }
0445 
0446     return rate;
0447 }
0448 
0449 void Downloader::setMonitor(MonitorInterface *tmo)
0450 {
0451     tmon = tmo;
0452     if (!tmon)
0453         return;
0454 
0455     for (CurChunkItr i = current_chunks.begin(); i != current_chunks.end(); ++i) {
0456         ChunkDownload *cd = i->second;
0457         tmon->downloadStarted(cd);
0458     }
0459 
0460     for (WebSeed *ws : std::as_const(webseeds)) {
0461         WebSeedChunkDownload *cd = ws->currentChunkDownload();
0462         if (cd)
0463             tmon->downloadStarted(cd);
0464     }
0465 }
0466 
0467 void Downloader::saveDownloads(const QString &file)
0468 {
0469     File fptr;
0470     if (!fptr.open(file, "wb"))
0471         return;
0472 
0473     // See bug 219019, don't know why, but it is possible that we get 0 pointers in the map
0474     // so get rid of them before we save
0475     for (CurChunkItr i = current_chunks.begin(); i != current_chunks.end();) {
0476         if (!i->second)
0477             i = current_chunks.erase(i);
0478         else
0479             ++i;
0480     }
0481 
0482     // Save all the current downloads to a file
0483     CurrentChunksHeader hdr;
0484     hdr.magic = CURRENT_CHUNK_MAGIC;
0485     hdr.major = bt::MAJOR;
0486     hdr.minor = bt::MINOR;
0487     hdr.num_chunks = current_chunks.count();
0488     fptr.write(&hdr, sizeof(CurrentChunksHeader));
0489 
0490     Out(SYS_GEN | LOG_DEBUG) << "Saving " << current_chunks.count() << " chunk downloads" << endl;
0491     for (CurChunkItr i = current_chunks.begin(); i != current_chunks.end(); ++i) {
0492         ChunkDownload *cd = i->second;
0493         cd->save(fptr);
0494     }
0495 }
0496 
0497 void Downloader::loadDownloads(const QString &file)
0498 {
0499     // don't load stuff if download is finished
0500     if (cman.completed())
0501         return;
0502 
0503     // Load all partial downloads
0504     File fptr;
0505     if (!fptr.open(file, "rb"))
0506         return;
0507 
0508     // recalculate downloaded bytes
0509     bytes_downloaded = (tor.getTotalSize() - cman.bytesLeft());
0510 
0511     CurrentChunksHeader chdr;
0512     fptr.read(&chdr, sizeof(CurrentChunksHeader));
0513     if (chdr.magic != CURRENT_CHUNK_MAGIC) {
0514         Out(SYS_GEN | LOG_DEBUG) << "Warning : current_chunks file corrupted" << endl;
0515         return;
0516     }
0517 
0518     Out(SYS_GEN | LOG_DEBUG) << "Loading " << chdr.num_chunks << " active chunk downloads" << endl;
0519     for (Uint32 i = 0; i < chdr.num_chunks; i++) {
0520         ChunkDownloadHeader hdr;
0521         // first read header
0522         fptr.read(&hdr, sizeof(ChunkDownloadHeader));
0523         Out(SYS_GEN | LOG_DEBUG) << "Loading chunk " << hdr.index << endl;
0524         if (hdr.index >= tor.getNumChunks()) {
0525             Out(SYS_GEN | LOG_DEBUG) << "Warning : current_chunks file corrupted, invalid index " << hdr.index << endl;
0526             return;
0527         }
0528 
0529         Chunk *c = cman.getChunk(hdr.index);
0530         if (!c || current_chunks.contains(hdr.index)) {
0531             Out(SYS_GEN | LOG_DEBUG) << "Illegal chunk " << hdr.index << endl;
0532             return;
0533         }
0534 
0535         ChunkDownload *cd = new ChunkDownload(c);
0536         bool ret = false;
0537         try {
0538             ret = cd->load(fptr, hdr);
0539         } catch (...) {
0540             ret = false;
0541         }
0542 
0543         if (!ret || c->getStatus() == Chunk::ON_DISK || c->isExcluded()) {
0544             delete cd;
0545         } else {
0546             current_chunks.insert(hdr.index, cd);
0547             bytes_downloaded += cd->bytesDownloaded();
0548 
0549             if (tmon)
0550                 tmon->downloadStarted(cd);
0551         }
0552     }
0553 
0554     // reset curr_chunks_downloaded to 0
0555     curr_chunks_downloaded = 0;
0556 }
0557 
0558 Uint32 Downloader::getDownloadedBytesOfCurrentChunksFile(const QString &file)
0559 {
0560     // Load all partial downloads
0561     File fptr;
0562     if (!fptr.open(file, "rb"))
0563         return 0;
0564 
0565     // read the number of chunks
0566     CurrentChunksHeader chdr;
0567     fptr.read(&chdr, sizeof(CurrentChunksHeader));
0568     if (chdr.magic != CURRENT_CHUNK_MAGIC) {
0569         Out(SYS_GEN | LOG_DEBUG) << "Warning : current_chunks file corrupted" << endl;
0570         return 0;
0571     }
0572     Uint32 num_bytes = 0;
0573 
0574     // load all chunks and calculate how much is downloaded
0575     for (Uint32 i = 0; i < chdr.num_chunks; i++) {
0576         // read the chunkdownload header
0577         ChunkDownloadHeader hdr;
0578         fptr.read(&hdr, sizeof(ChunkDownloadHeader));
0579 
0580         Chunk *c = cman.getChunk(hdr.index);
0581         if (!c)
0582             return num_bytes;
0583 
0584         ChunkDownload tmp(c);
0585         if (!tmp.load(fptr, hdr, false))
0586             return num_bytes;
0587 
0588         num_bytes += tmp.bytesDownloaded();
0589     }
0590     curr_chunks_downloaded = num_bytes;
0591     return num_bytes;
0592 }
0593 
0594 bool Downloader::isFinished() const
0595 {
0596     return cman.completed();
0597 }
0598 
0599 void Downloader::onExcluded(Uint32 from, Uint32 to)
0600 {
0601     for (Uint32 i = from; i <= to; i++) {
0602         ChunkDownload *cd = current_chunks.find(i);
0603         if (!cd)
0604             continue;
0605 
0606         cd->cancelAll();
0607         cd->releaseAllPDs();
0608         if (tmon)
0609             tmon->downloadRemoved(cd);
0610         current_chunks.erase(i);
0611         cman.resetChunk(i); // reset chunk it is not fully downloaded yet
0612     }
0613 
0614     for (WebSeed *ws : std::as_const(webseeds)) {
0615         ws->onExcluded(from, to);
0616     }
0617 }
0618 
0619 void Downloader::onIncluded(Uint32 from, Uint32 to)
0620 {
0621     chunk_selector->reincluded(from, to);
0622 }
0623 
0624 void Downloader::corrupted(Uint32 chunk)
0625 {
0626     chunk_selector->reinsert(chunk);
0627 }
0628 
0629 void Downloader::dataChecked(const bt::BitSet &ok_chunks, Uint32 from, Uint32 to)
0630 {
0631     for (Uint32 i = from; i < ok_chunks.getNumBits() && i <= to; i++) {
0632         ChunkDownload *cd = current_chunks.find(i);
0633         if (ok_chunks.get(i) && cd) {
0634             // we have a chunk and we are downloading it so kill it
0635             cd->releaseAllPDs();
0636             if (tmon)
0637                 tmon->downloadRemoved(cd);
0638 
0639             current_chunks.erase(i);
0640         }
0641     }
0642     chunk_selector->dataChecked(ok_chunks, from, to);
0643 }
0644 
0645 void Downloader::recalcDownloaded()
0646 {
0647     Uint64 total = tor.getTotalSize();
0648     bytes_downloaded = (total - cman.bytesLeft());
0649 }
0650 
0651 void Downloader::onChunkReady(Chunk *c)
0652 {
0653     WebSeed *ws = webseeds_chunks.find(c->getIndex());
0654     webseeds_chunks.erase(c->getIndex());
0655     PieceData::Ptr piece = c->getPiece(0, c->getSize(), true);
0656     if (piece && c->checkHash(tor.getHash(c->getIndex()))) {
0657         // hash ok so save it
0658         try {
0659             bytes_downloaded += c->getSize();
0660 
0661             for (WebSeed *ws : std::as_const(webseeds)) {
0662                 // tell all webseeds a chunk is downloaded
0663                 if (ws->inCurrentRange(c->getIndex()))
0664                     ws->chunkDownloaded(c->getIndex());
0665             }
0666 
0667             ChunkDownload *cd = current_chunks.find(c->getIndex());
0668             if (cd) {
0669                 // A ChunkDownload is ongoing for this chunk so kill it, we have the chunk
0670                 cd->cancelAll();
0671                 if (tmon)
0672                     tmon->downloadRemoved(cd);
0673                 current_chunks.erase(c->getIndex());
0674             }
0675 
0676             c->savePiece(piece);
0677             cman.chunkDownloaded(c->getIndex());
0678 
0679             Out(SYS_GEN | LOG_IMPORTANT) << "Chunk " << c->getIndex() << " downloaded via webseed ! " << endl;
0680             // tell everybody we have the Chunk
0681             pman.sendHave(c->getIndex());
0682         } catch (Error &e) {
0683             Out(SYS_DIO | LOG_IMPORTANT) << "Error " << e.toString() << endl;
0684             Q_EMIT ioError(e.toString());
0685         }
0686     } else {
0687         Out(SYS_GEN | LOG_IMPORTANT) << "Hash verification error on chunk " << c->getIndex() << endl;
0688         // reset chunk but only when no other peer is downloading it
0689         if (!current_chunks.find(c->getIndex()))
0690             cman.resetChunk(c->getIndex());
0691 
0692         chunk_selector->reinsert(c->getIndex());
0693         ws->disable(i18n("Disabled because webseed does not match torrent"));
0694     }
0695 }
0696 
0697 void Downloader::chunkDownloadStarted(WebSeedChunkDownload *cd, Uint32 chunk)
0698 {
0699     webseeds_chunks.insert(chunk, cd->ws);
0700     active_webseed_downloads++;
0701     if (tmon)
0702         tmon->downloadStarted(cd);
0703 }
0704 
0705 void Downloader::chunkDownloadFinished(WebSeedChunkDownload *cd, Uint32 chunk)
0706 {
0707     webseeds_chunks.erase(chunk);
0708     if (active_webseed_downloads > 0)
0709         active_webseed_downloads--;
0710 
0711     if (tmon)
0712         tmon->downloadRemoved(cd);
0713 }
0714 
0715 WebSeed *Downloader::addWebSeed(const QUrl &url)
0716 {
0717     // Check for dupes
0718     for (WebSeed *ws : std::as_const(webseeds)) {
0719         if (ws->getUrl() == url)
0720             return nullptr;
0721     }
0722 
0723     WebSeed *ws = new WebSeed(url, true, tor, cman);
0724     webseeds.append(ws);
0725     connect(ws, &WebSeed::chunkReady, this, &Downloader::onChunkReady);
0726     connect(ws, &WebSeed::chunkDownloadStarted, this, &Downloader::chunkDownloadStarted);
0727     connect(ws, &WebSeed::chunkDownloadFinished, this, &Downloader::chunkDownloadFinished);
0728     return ws;
0729 }
0730 
0731 bool Downloader::removeWebSeed(const QUrl &url)
0732 {
0733     for (WebSeed *ws : std::as_const(webseeds)) {
0734         if (ws->getUrl() == url && ws->isUserCreated()) {
0735             PtrMap<Uint32, WebSeed>::iterator i = webseeds_chunks.begin();
0736             while (i != webseeds_chunks.end()) {
0737                 if (i->second == ws)
0738                     i = webseeds_chunks.erase(i);
0739                 else
0740                     ++i;
0741             }
0742             webseeds.removeAll(ws);
0743             delete ws;
0744             return true;
0745         }
0746     }
0747     return false;
0748 }
0749 
0750 void Downloader::removeAllWebSeeds()
0751 {
0752     webseeds.clear();
0753     webseeds_chunks.clear();
0754 }
0755 
0756 void Downloader::saveWebSeeds(const QString &file)
0757 {
0758     QFile fptr(file);
0759     if (!fptr.open(QIODevice::WriteOnly)) {
0760         Out(SYS_GEN | LOG_NOTICE) << "Cannot open " << file << " to save webseeds" << endl;
0761         return;
0762     }
0763 
0764     QTextStream out(&fptr);
0765     for (WebSeed *ws : std::as_const(webseeds)) {
0766         if (ws->isUserCreated())
0767             out << ws->getUrl().toDisplayString() << Qt::endl;
0768     }
0769     out << "====disabled====" << Qt::endl;
0770     for (WebSeed *ws : std::as_const(webseeds)) {
0771         if (!ws->isEnabled())
0772             out << ws->getUrl().toDisplayString() << Qt::endl;
0773     }
0774 }
0775 
0776 void Downloader::loadWebSeeds(const QString &file)
0777 {
0778     QFile fptr(file);
0779     if (!fptr.open(QIODevice::ReadOnly)) {
0780         Out(SYS_GEN | LOG_NOTICE) << "Cannot open " << file << " to load webseeds" << endl;
0781         return;
0782     }
0783 
0784     bool disabled_list_found = false;
0785     QTextStream in(&fptr);
0786     while (!in.atEnd()) {
0787         QString line = in.readLine();
0788         if (line == QLatin1String("====disabled====")) {
0789             disabled_list_found = true;
0790             continue;
0791         }
0792 
0793         QUrl url(line);
0794         if (!url.isValid() || url.scheme() != QLatin1String("http"))
0795             continue;
0796 
0797         if (disabled_list_found) {
0798             for (WebSeed *ws : std::as_const(webseeds)) {
0799                 if (ws->getUrl() == url) {
0800                     ws->setEnabled(false);
0801                     break;
0802                 }
0803             }
0804         } else {
0805             WebSeed *ws = new WebSeed(url, true, tor, cman);
0806             webseeds.append(ws);
0807             connect(ws, &WebSeed::chunkReady, this, &Downloader::onChunkReady);
0808             connect(ws, &WebSeed::chunkDownloadStarted, this, &Downloader::chunkDownloadStarted);
0809             connect(ws, &WebSeed::chunkDownloadFinished, this, &Downloader::chunkDownloadFinished);
0810         }
0811     }
0812 }
0813 
0814 void Downloader::setGroupIDs(Uint32 up, Uint32 down)
0815 {
0816     for (WebSeed *ws : std::as_const(webseeds)) {
0817         ws->setGroupIDs(up, down);
0818     }
0819 }
0820 
0821 ChunkDownload *Downloader::download(Uint32 chunk)
0822 {
0823     return current_chunks.find(chunk);
0824 }
0825 
0826 const bt::ChunkDownload *Downloader::download(Uint32 chunk) const
0827 {
0828     return current_chunks.find(chunk);
0829 }
0830 
0831 void Downloader::setUseWebSeeds(bool on)
0832 {
0833     use_webseeds = on;
0834 }
0835 }
0836 
0837 #include "moc_downloader.cpp"