1 /*
2     SPDX-FileCopyrightText: 2005 Joris Guisson <joris.guisson@gmail.com>
3 
4     SPDX-License-Identifier: GPL-2.0-or-later
5 */
6 #include "downloader.h"
7 
8 #include <QFile>
9 #include <QTextStream>
10 #include <klocalizedstring.h>
11 
12 #include "chunkdownload.h"
13 #include "chunkselector.h"
14 #include "version.h"
15 #include "webseed.h"
16 #include <diskio/chunkmanager.h>
17 #include <diskio/piecedata.h>
18 #include <download/piece.h>
19 #include <interfaces/monitorinterface.h>
20 #include <peer/accessmanager.h>
21 #include <peer/badpeerslist.h>
22 #include <peer/chunkcounter.h>
23 #include <peer/peer.h>
24 #include <peer/peerdownloader.h>
25 #include <peer/peermanager.h>
26 #include <torrent/torrent.h>
27 #include <util/array.h>
28 #include <util/error.h>
29 #include <util/file.h>
30 #include <util/functions.h>
31 #include <util/log.h>
32 #include <util/sha1hash.h>
33 
34 namespace bt
35 {
36 bool Downloader::use_webseeds = true;
37 
Downloader(Torrent & tor,PeerManager & pman,ChunkManager & cman)38 Downloader::Downloader(Torrent &tor, PeerManager &pman, ChunkManager &cman)
39     : tor(tor)
40     , pman(pman)
41     , cman(cman)
42     , bytes_downloaded(0)
43     , tmon(nullptr)
44     , chunk_selector(nullptr)
45     , webseed_endgame_mode(false)
46 {
47     webseeds_on = use_webseeds;
48     pman.setPieceHandler(this);
49     chunk_selector = new ChunkSelector();
50     chunk_selector->init(&cman, this, &pman);
51 
52     Uint64 total = tor.getTotalSize();
53     bytes_downloaded = (total - cman.bytesLeft());
54     curr_chunks_downloaded = 0;
55     unnecessary_data = 0;
56 
57     current_chunks.setAutoDelete(true);
58 
59     active_webseed_downloads = 0;
60     const QList<QUrl> &urls = tor.getWebSeeds();
61     for (const QUrl &u : urls) {
62         if (u.scheme() == QLatin1String("http")) {
63             WebSeed *ws = new WebSeed(u, false, tor, cman);
64             webseeds.append(ws);
65             connect(ws, &WebSeed::chunkReady, this, &Downloader::onChunkReady);
66             connect(ws, &WebSeed::chunkDownloadStarted, this, &Downloader::chunkDownloadStarted);
67             connect(ws, &WebSeed::chunkDownloadFinished, this, &Downloader::chunkDownloadFinished);
68         }
69     }
70 
71     if (webseeds.count() > 0) {
72         webseed_range_size = tor.getNumChunks() / webseeds.count();
73         if (webseed_range_size == 0)
74             webseed_range_size = 1;
75 
76         // make sure the range is not to big
77         if (webseed_range_size > tor.getNumChunks() / 10)
78             webseed_range_size = tor.getNumChunks() / 10;
79     } else {
80         webseed_range_size = 1;
81     }
82 }
83 
~Downloader()84 Downloader::~Downloader()
85 {
86     delete chunk_selector;
87     qDeleteAll(webseeds);
88 }
89 
setChunkSelector(ChunkSelectorInterface * csel)90 void Downloader::setChunkSelector(ChunkSelectorInterface *csel)
91 {
92     delete chunk_selector;
93 
94     if (!csel) // check if a custom one was provided, if not create a default one
95         chunk_selector = new ChunkSelector();
96     else
97         chunk_selector = csel;
98 
99     chunk_selector->init(&cman, this, &pman);
100 }
101 
pieceReceived(const Piece & p)102 void Downloader::pieceReceived(const Piece &p)
103 {
104     if (cman.completed())
105         return;
106 
107     ChunkDownload *cd = current_chunks.find(p.getIndex());
108     if (!cd) {
109         unnecessary_data += p.getLength();
110         Out(SYS_DIO | LOG_DEBUG) << "Unnecessary piece, total unnecessary data : " << BytesToString(unnecessary_data) << endl;
111         return;
112     }
113 
114     bool ok = false;
115     if (cd->piece(p, ok)) {
116         if (tmon)
117             tmon->downloadRemoved(cd);
118 
119         if (ok)
120             bytes_downloaded += p.getLength();
121 
122         if (!finished(cd)) {
123             // if the chunk fails don't count the bytes downloaded
124             if (cd->getChunk()->getSize() > bytes_downloaded)
125                 bytes_downloaded = 0;
126             else
127                 bytes_downloaded -= cd->getChunk()->getSize();
128             current_chunks.erase(p.getIndex());
129         } else {
130             current_chunks.erase(p.getIndex());
131             for (WebSeed *ws : qAsConst(webseeds)) {
132                 if (ws->inCurrentRange(p.getIndex()))
133                     ws->chunkDownloaded(p.getIndex());
134             }
135         }
136     } else {
137         if (ok)
138             bytes_downloaded += p.getLength();
139     }
140 
141     if (!ok) {
142         unnecessary_data += p.getLength();
143         Out(SYS_DIO | LOG_DEBUG) << "Unnecessary piece, total unnecessary data : " << BytesToString(unnecessary_data) << endl;
144     }
145 }
146 
endgameMode() const147 bool Downloader::endgameMode() const
148 {
149     return current_chunks.count() >= cman.chunksLeft();
150 }
151 
update()152 void Downloader::update()
153 {
154     if (cman.completed())
155         return;
156 
157     /*
158         Normal update should now handle all modes properly.
159     */
160     normalUpdate();
161 
162     // now see if there aren't any timed out pieces
163     for (PieceDownloader *pd : qAsConst(piece_downloaders)) {
164         pd->checkTimeouts();
165     }
166 
167     if (use_webseeds) {
168         for (WebSeed *ws : qAsConst(webseeds)) {
169             ws->update();
170         }
171     }
172 
173     if (isFinished() && webseeds_on) {
174         for (WebSeed *ws : qAsConst(webseeds)) {
175             ws->cancel();
176         }
177     }
178 }
179 
normalUpdate()180 void Downloader::normalUpdate()
181 {
182     for (CurChunkItr j = current_chunks.begin(); j != current_chunks.end(); ++j) {
183         ChunkDownload *cd = j->second;
184         if (cd->isIdle()) {
185             continue;
186         } else if (cd->isChoked()) {
187             cd->releaseAllPDs();
188         } else if (cd->needsToBeUpdated()) {
189             cd->update();
190         }
191     }
192 
193     for (PieceDownloader *pd : qAsConst(piece_downloaders)) {
194         if (!pd->isChoked()) {
195             while (pd->canDownloadChunk()) {
196                 if (!downloadFrom(pd))
197                     break;
198                 pd->setNearlyDone(false);
199             }
200         }
201     }
202 
203     if (use_webseeds) {
204         for (WebSeed *ws : qAsConst(webseeds)) {
205             if (!ws->busy() && ws->isEnabled() && ws->failedAttempts() < 3) {
206                 downloadFrom(ws);
207             }
208         }
209     } else if (webseeds_on != use_webseeds) {
210         // reset all webseeds, webseeds have been disabled
211         webseeds_on = use_webseeds;
212         for (WebSeed *ws : qAsConst(webseeds)) {
213             if (ws->busy() && ws->isEnabled()) {
214                 ws->cancel();
215             }
216         }
217     }
218 }
219 
selectCD(PieceDownloader * pd,Uint32 n)220 ChunkDownload *Downloader::selectCD(PieceDownloader *pd, Uint32 n)
221 {
222     ChunkDownload *sel = nullptr;
223     Uint32 sel_left = 0xFFFFFFFF;
224 
225     for (CurChunkItr j = current_chunks.begin(); j != current_chunks.end(); ++j) {
226         ChunkDownload *cd = j->second;
227         if (pd->isChoked() || !pd->hasChunk(cd->getChunk()->getIndex()))
228             continue;
229 
230         if (cd->getNumDownloaders() == n) {
231             // lets favor the ones which are nearly finished
232             if (!sel || cd->getTotalPieces() - cd->getPiecesDownloaded() < sel_left) {
233                 sel = cd;
234                 sel_left = sel->getTotalPieces() - sel->getPiecesDownloaded();
235             }
236         }
237     }
238     return sel;
239 }
240 
findDownloadForPD(PieceDownloader * pd)241 bool Downloader::findDownloadForPD(PieceDownloader *pd)
242 {
243     ChunkDownload *sel = nullptr;
244 
245     // See if there are ChunkDownload's which need a PieceDownloader
246     sel = selectCD(pd, 0);
247     if (sel) {
248         return sel->assign(pd);
249     }
250 
251     return false;
252 }
253 
selectWorst(PieceDownloader * pd)254 ChunkDownload *Downloader::selectWorst(PieceDownloader *pd)
255 {
256     ChunkDownload *cdmin = nullptr;
257     for (CurChunkItr j = current_chunks.begin(); j != current_chunks.end(); ++j) {
258         ChunkDownload *cd = j->second;
259         if (!pd->hasChunk(cd->getChunk()->getIndex()) || cd->containsPeer(pd))
260             continue;
261 
262         if (!cdmin)
263             cdmin = cd;
264         else if (cd->getDownloadSpeed() < cdmin->getDownloadSpeed())
265             cdmin = cd;
266         else if (cd->getNumDownloaders() < cdmin->getNumDownloaders())
267             cdmin = cd;
268     }
269 
270     return cdmin;
271 }
272 
downloadFrom(PieceDownloader * pd)273 bool Downloader::downloadFrom(PieceDownloader *pd)
274 {
275     // first see if we can use an existing dowload
276     if (findDownloadForPD(pd))
277         return true;
278 
279     Uint32 chunk = 0;
280     if (chunk_selector->select(pd, chunk)) {
281         Chunk *c = cman.getChunk(chunk);
282         if (current_chunks.contains(chunk)) {
283             return current_chunks.find(chunk)->assign(pd);
284         } else {
285             ChunkDownload *cd = new ChunkDownload(c);
286             current_chunks.insert(chunk, cd);
287             cd->assign(pd);
288             if (tmon)
289                 tmon->downloadStarted(cd);
290             return true;
291         }
292     } else if (pd->getNumGrabbed() == 0) {
293         // If the peer hasn't got a chunk we want,
294         ChunkDownload *cdmin = selectWorst(pd);
295 
296         if (cdmin) {
297             return cdmin->assign(pd);
298         }
299     }
300 
301     return false;
302 }
303 
downloadFrom(WebSeed * ws)304 void Downloader::downloadFrom(WebSeed *ws)
305 {
306     Uint32 first = 0;
307     Uint32 last = 0;
308     webseed_endgame_mode = false;
309     if (chunk_selector->selectRange(first, last, webseed_range_size)) {
310         ws->download(first, last);
311     } else {
312         // go to endgame mode
313         webseed_endgame_mode = true;
314         if (chunk_selector->selectRange(first, last, webseed_range_size))
315             ws->download(first, last);
316     }
317 }
318 
downloading(Uint32 chunk) const319 bool Downloader::downloading(Uint32 chunk) const
320 {
321     return current_chunks.find(chunk) != nullptr;
322 }
323 
canDownloadFromWebSeed(Uint32 chunk) const324 bool Downloader::canDownloadFromWebSeed(Uint32 chunk) const
325 {
326     if (webseed_endgame_mode)
327         return true;
328 
329     for (WebSeed *ws : qAsConst(webseeds)) {
330         if (ws->busy() && ws->inCurrentRange(chunk))
331             return false;
332     }
333 
334     return !downloading(chunk);
335 }
336 
numDownloadersForChunk(Uint32 chunk) const337 Uint32 Downloader::numDownloadersForChunk(Uint32 chunk) const
338 {
339     const ChunkDownload *cd = current_chunks.find(chunk);
340     if (!cd)
341         return 0;
342 
343     return cd->getNumDownloaders();
344 }
345 
addPieceDownloader(PieceDownloader * peer)346 void Downloader::addPieceDownloader(PieceDownloader *peer)
347 {
348     piece_downloaders.append(peer);
349 }
350 
removePieceDownloader(PieceDownloader * peer)351 void Downloader::removePieceDownloader(PieceDownloader *peer)
352 {
353     for (CurChunkItr i = current_chunks.begin(); i != current_chunks.end(); ++i) {
354         ChunkDownload *cd = i->second;
355         cd->killed(peer);
356     }
357     piece_downloaders.removeAll(peer);
358 }
359 
finished(ChunkDownload * cd)360 bool Downloader::finished(ChunkDownload *cd)
361 {
362     Chunk *c = cd->getChunk();
363     // verify the data
364     SHA1Hash h = cd->getHash();
365 
366     if (tor.verifyHash(h, c->getIndex())) {
367         // hash ok so save it
368         try {
369             for (WebSeed *ws : qAsConst(webseeds)) {
370                 // tell all webseeds a chunk is downloaded
371                 if (ws->inCurrentRange(c->getIndex()))
372                     ws->chunkDownloaded(c->getIndex());
373             }
374 
375             cman.chunkDownloaded(c->getIndex());
376             Out(SYS_GEN | LOG_IMPORTANT) << "Chunk " << c->getIndex() << " downloaded " << endl;
377             pman.sendHave(c->getIndex());
378             Q_EMIT chunkDownloaded(c->getIndex());
379         } catch (Error &e) {
380             Out(SYS_DIO | LOG_IMPORTANT) << "Error " << e.toString() << endl;
381             Q_EMIT ioError(e.toString());
382             return false;
383         }
384     } else {
385         Out(SYS_GEN | LOG_IMPORTANT) << "Hash verification error on chunk " << c->getIndex() << endl;
386         Out(SYS_GEN | LOG_IMPORTANT) << "Is        : " << h << endl;
387         Out(SYS_GEN | LOG_IMPORTANT) << "Should be : " << tor.getHash(c->getIndex()) << endl;
388 
389         // reset chunk but only when no webseeder is downloading it
390         if (!webseeds_chunks.find(c->getIndex()))
391             cman.resetChunk(c->getIndex());
392 
393         chunk_selector->reinsert(c->getIndex());
394 
395         PieceDownloader *only = cd->getOnlyDownloader();
396         if (only) {
397             Peer::Ptr p = pman.findPeer(only);
398             if (!p)
399                 return false;
400 
401             QString ip = p->getIPAddresss();
402             Out(SYS_GEN | LOG_NOTICE) << "Peer " << ip << " sent bad data" << endl;
403             AccessManager::instance().banPeer(ip);
404             p->kill();
405         }
406         return false;
407     }
408     return true;
409 }
410 
clearDownloads()411 void Downloader::clearDownloads()
412 {
413     current_chunks.clear();
414     piece_downloaders.clear();
415 
416     for (WebSeed *ws : qAsConst(webseeds))
417         ws->cancel();
418 }
419 
pause()420 void Downloader::pause()
421 {
422     if (tmon) {
423         for (CurChunkItr i = current_chunks.begin(); i != current_chunks.end(); ++i) {
424             ChunkDownload *cd = i->second;
425             tmon->downloadRemoved(cd);
426         }
427     }
428 
429     current_chunks.clear();
430     for (WebSeed *ws : qAsConst(webseeds))
431         ws->reset();
432 }
433 
downloadRate() const434 Uint32 Downloader::downloadRate() const
435 {
436     // sum of the download rate of each peer
437     Uint32 rate = 0;
438     for (PieceDownloader *pd : qAsConst(piece_downloaders))
439         if (pd)
440             rate += pd->getDownloadRate();
441 
442     for (WebSeed *ws : qAsConst(webseeds)) {
443         rate += ws->getDownloadRate();
444     }
445 
446     return rate;
447 }
448 
setMonitor(MonitorInterface * tmo)449 void Downloader::setMonitor(MonitorInterface *tmo)
450 {
451     tmon = tmo;
452     if (!tmon)
453         return;
454 
455     for (CurChunkItr i = current_chunks.begin(); i != current_chunks.end(); ++i) {
456         ChunkDownload *cd = i->second;
457         tmon->downloadStarted(cd);
458     }
459 
460     for (WebSeed *ws : qAsConst(webseeds)) {
461         WebSeedChunkDownload *cd = ws->currentChunkDownload();
462         if (cd)
463             tmon->downloadStarted(cd);
464     }
465 }
466 
saveDownloads(const QString & file)467 void Downloader::saveDownloads(const QString &file)
468 {
469     File fptr;
470     if (!fptr.open(file, "wb"))
471         return;
472 
473     // See bug 219019, don't know why, but it is possible that we get 0 pointers in the map
474     // so get rid of them before we save
475     for (CurChunkItr i = current_chunks.begin(); i != current_chunks.end();) {
476         if (!i->second)
477             i = current_chunks.erase(i);
478         else
479             ++i;
480     }
481 
482     // Save all the current downloads to a file
483     CurrentChunksHeader hdr;
484     hdr.magic = CURRENT_CHUNK_MAGIC;
485     hdr.major = bt::MAJOR;
486     hdr.minor = bt::MINOR;
487     hdr.num_chunks = current_chunks.count();
488     fptr.write(&hdr, sizeof(CurrentChunksHeader));
489 
490     Out(SYS_GEN | LOG_DEBUG) << "Saving " << current_chunks.count() << " chunk downloads" << endl;
491     for (CurChunkItr i = current_chunks.begin(); i != current_chunks.end(); ++i) {
492         ChunkDownload *cd = i->second;
493         cd->save(fptr);
494     }
495 }
496 
loadDownloads(const QString & file)497 void Downloader::loadDownloads(const QString &file)
498 {
499     // don't load stuff if download is finished
500     if (cman.completed())
501         return;
502 
503     // Load all partial downloads
504     File fptr;
505     if (!fptr.open(file, "rb"))
506         return;
507 
508     // recalculate downloaded bytes
509     bytes_downloaded = (tor.getTotalSize() - cman.bytesLeft());
510 
511     CurrentChunksHeader chdr;
512     fptr.read(&chdr, sizeof(CurrentChunksHeader));
513     if (chdr.magic != CURRENT_CHUNK_MAGIC) {
514         Out(SYS_GEN | LOG_DEBUG) << "Warning : current_chunks file corrupted" << endl;
515         return;
516     }
517 
518     Out(SYS_GEN | LOG_DEBUG) << "Loading " << chdr.num_chunks << " active chunk downloads" << endl;
519     for (Uint32 i = 0; i < chdr.num_chunks; i++) {
520         ChunkDownloadHeader hdr;
521         // first read header
522         fptr.read(&hdr, sizeof(ChunkDownloadHeader));
523         Out(SYS_GEN | LOG_DEBUG) << "Loading chunk " << hdr.index << endl;
524         if (hdr.index >= tor.getNumChunks()) {
525             Out(SYS_GEN | LOG_DEBUG) << "Warning : current_chunks file corrupted, invalid index " << hdr.index << endl;
526             return;
527         }
528 
529         Chunk *c = cman.getChunk(hdr.index);
530         if (!c || current_chunks.contains(hdr.index)) {
531             Out(SYS_GEN | LOG_DEBUG) << "Illegal chunk " << hdr.index << endl;
532             return;
533         }
534 
535         ChunkDownload *cd = new ChunkDownload(c);
536         bool ret = false;
537         try {
538             ret = cd->load(fptr, hdr);
539         } catch (...) {
540             ret = false;
541         }
542 
543         if (!ret || c->getStatus() == Chunk::ON_DISK || c->isExcluded()) {
544             delete cd;
545         } else {
546             current_chunks.insert(hdr.index, cd);
547             bytes_downloaded += cd->bytesDownloaded();
548 
549             if (tmon)
550                 tmon->downloadStarted(cd);
551         }
552     }
553 
554     // reset curr_chunks_downloaded to 0
555     curr_chunks_downloaded = 0;
556 }
557 
getDownloadedBytesOfCurrentChunksFile(const QString & file)558 Uint32 Downloader::getDownloadedBytesOfCurrentChunksFile(const QString &file)
559 {
560     // Load all partial downloads
561     File fptr;
562     if (!fptr.open(file, "rb"))
563         return 0;
564 
565     // read the number of chunks
566     CurrentChunksHeader chdr;
567     fptr.read(&chdr, sizeof(CurrentChunksHeader));
568     if (chdr.magic != CURRENT_CHUNK_MAGIC) {
569         Out(SYS_GEN | LOG_DEBUG) << "Warning : current_chunks file corrupted" << endl;
570         return 0;
571     }
572     Uint32 num_bytes = 0;
573 
574     // load all chunks and calculate how much is downloaded
575     for (Uint32 i = 0; i < chdr.num_chunks; i++) {
576         // read the chunkdownload header
577         ChunkDownloadHeader hdr;
578         fptr.read(&hdr, sizeof(ChunkDownloadHeader));
579 
580         Chunk *c = cman.getChunk(hdr.index);
581         if (!c)
582             return num_bytes;
583 
584         ChunkDownload tmp(c);
585         if (!tmp.load(fptr, hdr, false))
586             return num_bytes;
587 
588         num_bytes += tmp.bytesDownloaded();
589     }
590     curr_chunks_downloaded = num_bytes;
591     return num_bytes;
592 }
593 
isFinished() const594 bool Downloader::isFinished() const
595 {
596     return cman.completed();
597 }
598 
onExcluded(Uint32 from,Uint32 to)599 void Downloader::onExcluded(Uint32 from, Uint32 to)
600 {
601     for (Uint32 i = from; i <= to; i++) {
602         ChunkDownload *cd = current_chunks.find(i);
603         if (!cd)
604             continue;
605 
606         cd->cancelAll();
607         cd->releaseAllPDs();
608         if (tmon)
609             tmon->downloadRemoved(cd);
610         current_chunks.erase(i);
611         cman.resetChunk(i); // reset chunk it is not fully downloaded yet
612     }
613 
614     for (WebSeed *ws : qAsConst(webseeds)) {
615         ws->onExcluded(from, to);
616     }
617 }
618 
onIncluded(Uint32 from,Uint32 to)619 void Downloader::onIncluded(Uint32 from, Uint32 to)
620 {
621     chunk_selector->reincluded(from, to);
622 }
623 
corrupted(Uint32 chunk)624 void Downloader::corrupted(Uint32 chunk)
625 {
626     chunk_selector->reinsert(chunk);
627 }
628 
dataChecked(const bt::BitSet & ok_chunks,Uint32 from,Uint32 to)629 void Downloader::dataChecked(const bt::BitSet &ok_chunks, Uint32 from, Uint32 to)
630 {
631     for (Uint32 i = from; i < ok_chunks.getNumBits() && i <= to; i++) {
632         ChunkDownload *cd = current_chunks.find(i);
633         if (ok_chunks.get(i) && cd) {
634             // we have a chunk and we are downloading it so kill it
635             cd->releaseAllPDs();
636             if (tmon)
637                 tmon->downloadRemoved(cd);
638 
639             current_chunks.erase(i);
640         }
641     }
642     chunk_selector->dataChecked(ok_chunks, from, to);
643 }
644 
recalcDownloaded()645 void Downloader::recalcDownloaded()
646 {
647     Uint64 total = tor.getTotalSize();
648     bytes_downloaded = (total - cman.bytesLeft());
649 }
650 
onChunkReady(Chunk * c)651 void Downloader::onChunkReady(Chunk *c)
652 {
653     WebSeed *ws = webseeds_chunks.find(c->getIndex());
654     webseeds_chunks.erase(c->getIndex());
655     PieceData::Ptr piece = c->getPiece(0, c->getSize(), true);
656     if (piece && c->checkHash(tor.getHash(c->getIndex()))) {
657         // hash ok so save it
658         try {
659             bytes_downloaded += c->getSize();
660 
661             for (WebSeed *ws : qAsConst(webseeds)) {
662                 // tell all webseeds a chunk is downloaded
663                 if (ws->inCurrentRange(c->getIndex()))
664                     ws->chunkDownloaded(c->getIndex());
665             }
666 
667             ChunkDownload *cd = current_chunks.find(c->getIndex());
668             if (cd) {
669                 // A ChunkDownload is ongoing for this chunk so kill it, we have the chunk
670                 cd->cancelAll();
671                 if (tmon)
672                     tmon->downloadRemoved(cd);
673                 current_chunks.erase(c->getIndex());
674             }
675 
676             c->savePiece(piece);
677             cman.chunkDownloaded(c->getIndex());
678 
679             Out(SYS_GEN | LOG_IMPORTANT) << "Chunk " << c->getIndex() << " downloaded via webseed ! " << endl;
680             // tell everybody we have the Chunk
681             pman.sendHave(c->getIndex());
682         } catch (Error &e) {
683             Out(SYS_DIO | LOG_IMPORTANT) << "Error " << e.toString() << endl;
684             Q_EMIT ioError(e.toString());
685         }
686     } else {
687         Out(SYS_GEN | LOG_IMPORTANT) << "Hash verification error on chunk " << c->getIndex() << endl;
688         // reset chunk but only when no other peer is downloading it
689         if (!current_chunks.find(c->getIndex()))
690             cman.resetChunk(c->getIndex());
691 
692         chunk_selector->reinsert(c->getIndex());
693         ws->disable(i18n("Disabled because webseed does not match torrent"));
694     }
695 }
696 
chunkDownloadStarted(WebSeedChunkDownload * cd,Uint32 chunk)697 void Downloader::chunkDownloadStarted(WebSeedChunkDownload *cd, Uint32 chunk)
698 {
699     webseeds_chunks.insert(chunk, cd->ws);
700     active_webseed_downloads++;
701     if (tmon)
702         tmon->downloadStarted(cd);
703 }
704 
chunkDownloadFinished(WebSeedChunkDownload * cd,Uint32 chunk)705 void Downloader::chunkDownloadFinished(WebSeedChunkDownload *cd, Uint32 chunk)
706 {
707     webseeds_chunks.erase(chunk);
708     if (active_webseed_downloads > 0)
709         active_webseed_downloads--;
710 
711     if (tmon)
712         tmon->downloadRemoved(cd);
713 }
714 
addWebSeed(const QUrl & url)715 WebSeed *Downloader::addWebSeed(const QUrl &url)
716 {
717     // Check for dupes
718     for (WebSeed *ws : qAsConst(webseeds)) {
719         if (ws->getUrl() == url)
720             return nullptr;
721     }
722 
723     WebSeed *ws = new WebSeed(url, true, tor, cman);
724     webseeds.append(ws);
725     connect(ws, &WebSeed::chunkReady, this, &Downloader::onChunkReady);
726     connect(ws, &WebSeed::chunkDownloadStarted, this, &Downloader::chunkDownloadStarted);
727     connect(ws, &WebSeed::chunkDownloadFinished, this, &Downloader::chunkDownloadFinished);
728     return ws;
729 }
730 
removeWebSeed(const QUrl & url)731 bool Downloader::removeWebSeed(const QUrl &url)
732 {
733     for (WebSeed *ws : qAsConst(webseeds)) {
734         if (ws->getUrl() == url && ws->isUserCreated()) {
735             PtrMap<Uint32, WebSeed>::iterator i = webseeds_chunks.begin();
736             while (i != webseeds_chunks.end()) {
737                 if (i->second == ws)
738                     i = webseeds_chunks.erase(i);
739                 else
740                     ++i;
741             }
742             webseeds.removeAll(ws);
743             delete ws;
744             return true;
745         }
746     }
747     return false;
748 }
749 
removeAllWebSeeds()750 void Downloader::removeAllWebSeeds()
751 {
752     webseeds.clear();
753     webseeds_chunks.clear();
754 }
755 
saveWebSeeds(const QString & file)756 void Downloader::saveWebSeeds(const QString &file)
757 {
758     QFile fptr(file);
759     if (!fptr.open(QIODevice::WriteOnly)) {
760         Out(SYS_GEN | LOG_NOTICE) << "Cannot open " << file << " to save webseeds" << endl;
761         return;
762     }
763 
764     QTextStream out(&fptr);
765     for (WebSeed *ws : qAsConst(webseeds)) {
766         if (ws->isUserCreated())
767             out << ws->getUrl().toDisplayString() << Qt::endl;
768     }
769     out << "====disabled====" << Qt::endl;
770     for (WebSeed *ws : qAsConst(webseeds)) {
771         if (!ws->isEnabled())
772             out << ws->getUrl().toDisplayString() << Qt::endl;
773     }
774 }
775 
loadWebSeeds(const QString & file)776 void Downloader::loadWebSeeds(const QString &file)
777 {
778     QFile fptr(file);
779     if (!fptr.open(QIODevice::ReadOnly)) {
780         Out(SYS_GEN | LOG_NOTICE) << "Cannot open " << file << " to load webseeds" << endl;
781         return;
782     }
783 
784     bool disabled_list_found = false;
785     QTextStream in(&fptr);
786     while (!in.atEnd()) {
787         QString line = in.readLine();
788         if (line == QLatin1String("====disabled====")) {
789             disabled_list_found = true;
790             continue;
791         }
792 
793         QUrl url(line);
794         if (!url.isValid() || url.scheme() != QLatin1String("http"))
795             continue;
796 
797         if (disabled_list_found) {
798             for (WebSeed *ws : qAsConst(webseeds)) {
799                 if (ws->getUrl() == url) {
800                     ws->setEnabled(false);
801                     break;
802                 }
803             }
804         } else {
805             WebSeed *ws = new WebSeed(url, true, tor, cman);
806             webseeds.append(ws);
807             connect(ws, &WebSeed::chunkReady, this, &Downloader::onChunkReady);
808             connect(ws, &WebSeed::chunkDownloadStarted, this, &Downloader::chunkDownloadStarted);
809             connect(ws, &WebSeed::chunkDownloadFinished, this, &Downloader::chunkDownloadFinished);
810         }
811     }
812 }
813 
setGroupIDs(Uint32 up,Uint32 down)814 void Downloader::setGroupIDs(Uint32 up, Uint32 down)
815 {
816     for (WebSeed *ws : qAsConst(webseeds)) {
817         ws->setGroupIDs(up, down);
818     }
819 }
820 
download(Uint32 chunk)821 ChunkDownload *Downloader::download(Uint32 chunk)
822 {
823     return current_chunks.find(chunk);
824 }
825 
download(Uint32 chunk) const826 const bt::ChunkDownload *Downloader::download(Uint32 chunk) const
827 {
828     return current_chunks.find(chunk);
829 }
830 
setUseWebSeeds(bool on)831 void Downloader::setUseWebSeeds(bool on)
832 {
833     use_webseeds = on;
834 }
835 }
836