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