1 /*
2 SPDX-FileCopyrightText: 2005 Joris Guisson <joris.guisson@gmail.com>
3 SPDX-FileCopyrightText: 2005 Ivan Vasic <ivasic@gmail.com>
4
5 SPDX-License-Identifier: GPL-2.0-or-later
6 */
7 #include "torrentcontrol.h"
8
9 #include <QDateTime>
10 #include <QDir>
11 #include <QFile>
12 #include <QTextCodec>
13 #include <QTextStream>
14
15 #include <KLocalizedString>
16
17 #include "choker.h"
18 #include "globals.h"
19 #include "jobqueue.h"
20 #include "peersourcemanager.h"
21 #include "server.h"
22 #include "statsfile.h"
23 #include "timeestimator.h"
24 #include "torrent.h"
25 #include "torrentfile.h"
26 #include "torrentfilestream.h"
27 #include "uploader.h"
28 #include <datachecker/datacheckerjob.h>
29 #include <datachecker/datacheckerthread.h>
30 #include <datachecker/multidatachecker.h>
31 #include <datachecker/singledatachecker.h>
32 #include <dht/dhtbase.h>
33 #include <diskio/cache.h>
34 #include <diskio/chunkmanager.h>
35 #include <diskio/movedatafilesjob.h>
36 #include <diskio/preallocationjob.h>
37 #include <diskio/preallocationthread.h>
38 #include <download/downloader.h>
39 #include <download/webseed.h>
40 #include <interfaces/cachefactory.h>
41 #include <interfaces/chunkselectorinterface.h>
42 #include <interfaces/monitorinterface.h>
43 #include <interfaces/queuemanagerinterface.h>
44 #include <interfaces/trackerslist.h>
45 #include <kio/copyjob.h>
46 #include <net/socketmonitor.h>
47 #include <peer/peer.h>
48 #include <peer/peerdownloader.h>
49 #include <peer/peermanager.h>
50 #include <util/bitset.h>
51 #include <util/error.h>
52 #include <util/fileops.h>
53 #include <util/functions.h>
54 #include <util/log.h>
55 #include <util/waitjob.h>
56
57 namespace bt
58 {
59 bool TorrentControl::completed_datacheck = false;
60 Uint32 TorrentControl::min_diskspace = 100;
61
TorrentControl()62 TorrentControl::TorrentControl()
63 : m_qman(nullptr)
64 , tor(nullptr)
65 , psman(nullptr)
66 , cman(nullptr)
67 , pman(nullptr)
68 , downloader(nullptr)
69 , uploader(nullptr)
70 , choke(nullptr)
71 , tmon(nullptr)
72 , prealloc(false)
73 {
74 job_queue = new JobQueue(this);
75 cache_factory = nullptr;
76 istats.session_bytes_uploaded = 0;
77 old_tordir = QString();
78 stats_file = nullptr;
79 loading_stats = false;
80
81 istats.running_time_dl = istats.running_time_ul = 0;
82 istats.prev_bytes_dl = 0;
83 istats.prev_bytes_ul = 0;
84 istats.io_error = false;
85 istats.priority = 0;
86 istats.custom_output_name = false;
87 istats.diskspace_warning_emitted = false;
88 istats.dht_on = false;
89 updateStats();
90
91 m_eta = new TimeEstimator(this);
92 // by default no torrent limits
93 upload_gid = download_gid = 0;
94 upload_limit = download_limit = 0;
95 assured_upload_speed = assured_download_speed = 0;
96 last_diskspace_check = bt::CurrentTime();
97 }
98
~TorrentControl()99 TorrentControl::~TorrentControl()
100 {
101 if (stats.running) {
102 // block all signals to prevent crash at exit
103 blockSignals(true);
104 stop(nullptr);
105 }
106
107 if (tmon)
108 tmon->destroyed();
109
110 if (downloader)
111 downloader->saveWebSeeds(tordir + "webseeds");
112
113 delete job_queue;
114 delete choke;
115 delete downloader;
116 delete uploader;
117 delete cman;
118 delete pman;
119 delete psman;
120 delete tor;
121 delete m_eta;
122 delete cache_factory;
123 delete stats_file;
124 }
125
update()126 void TorrentControl::update()
127 {
128 UpdateCurrentTime();
129
130 if (istats.io_error) {
131 stop();
132 Q_EMIT stoppedByError(this, stats.error_msg);
133 return;
134 }
135
136 if (stats.paused) {
137 stalled_timer.update();
138 pman->update();
139 updateStatus();
140 updateStats();
141 return;
142 }
143
144 if (prealloc && preallocate())
145 return;
146
147 try {
148 // first update peermanager
149 pman->update();
150 bool comp = stats.completed;
151
152 // then the downloader and uploader
153 uploader->update();
154 downloader->update();
155
156 // helper var, check if needed to move completed files somewhere
157 bool moveCompleted = false;
158 bool checkOnCompletion = false;
159
160 stats.completed = cman->completed();
161 if (stats.completed && !comp) {
162 pman->killSeeders();
163 QDateTime now = QDateTime::currentDateTime();
164 istats.running_time_dl += istats.time_started_dl.secsTo(now);
165 updateStatus();
166 updateStats();
167
168 // download has just been completed
169 // only sent completed to tracker when we have all chunks (so no excluded chunks)
170 if (cman->haveAllChunks())
171 psman->completed();
172 pman->setPartialSeed(!cman->haveAllChunks() && cman->chunksLeft() == 0);
173
174 finished(this);
175
176 // Move completed download to specified directory if needed
177 moveCompleted = !completed_dir.isEmpty();
178
179 // See if we need to do a data check
180 if (completed_datacheck)
181 checkOnCompletion = true;
182 } else if (!stats.completed && comp) {
183 // restart download if necesarry
184 // when user selects that files which were previously excluded,
185 // should now be downloaded
186 if (!psman->isStarted())
187 psman->start();
188 else
189 psman->manualUpdate();
190 istats.time_started_dl = QDateTime::currentDateTime();
191 // Tell QM to redo queue
192 updateQueue();
193 if (stats.superseeding)
194 pman->setSuperSeeding(false, cman->getBitSet());
195 }
196 updateStatus();
197
198 if (wanted_update_timer.getElapsedSinceUpdate() >= 60 * 1000) {
199 // Get a list of the chunks I have...
200 BitSet wanted_chunks = cman->getBitSet();
201 // or don't want...
202 wanted_chunks.orBitSet(cman->getExcludedBitSet());
203 wanted_chunks.orBitSet(cman->getOnlySeedBitSet());
204 // and inverted it get a list of the chunks I want
205 wanted_chunks.invert();
206 pman->setWantedChunks(wanted_chunks);
207 wanted_update_timer.update();
208 }
209
210 // we may need to update the choker
211 if (choker_update_timer.getElapsedSinceUpdate() >= 10000 || pman->chokerNeedsToRun()) {
212 // also get rid of seeders & uninterested when download is finished
213 // no need to keep them around, but also no need to do this
214 // every update, so once every 10 seconds is fine
215 if (stats.completed) {
216 pman->killSeeders();
217 }
218
219 doChoking();
220 choker_update_timer.update();
221 // a good opportunity to make sure we are not keeping to much in memory
222 cman->checkMemoryUsage();
223 }
224
225 // to satisfy people obsessed with their share ratio
226 bool save_stats = m_qman ? m_qman->permitStatsSync(this) : (stats_save_timer.getElapsedSinceUpdate() >= 5 * 60 * 1000);
227
228 if (save_stats) {
229 saveStats();
230 stats_save_timer.update();
231 }
232
233 // Update DownloadCap
234 updateStats();
235
236 if (stats.download_rate > 100) {
237 stalled_timer.update();
238 stats.last_download_activity_time = CurrentTime();
239 }
240
241 if (stats.upload_rate > 100)
242 stats.last_upload_activity_time = CurrentTime();
243
244 // do a manual update if we are stalled for more then 2 minutes
245 // we do not do this for private torrents
246 if (stalled_timer.getElapsedSinceUpdate() > 120000 && !stats.completed && !stats.priv_torrent) {
247 Out(SYS_TRK | LOG_NOTICE) << "Stalled for too long, time to get some fresh blood" << endl;
248 psman->manualUpdate();
249 stalled_timer.update();
250 }
251
252 if (stats.completed && (overMaxRatio() || overMaxSeedTime())) {
253 stats.auto_stopped = true;
254 stop();
255 Q_EMIT seedingAutoStopped(this, overMaxRatio() ? MAX_RATIO_REACHED : MAX_SEED_TIME_REACHED);
256 }
257
258 // Update diskspace if needed (every 1 min)
259 if (!stats.completed && stats.running && bt::CurrentTime() - last_diskspace_check >= 60 * 1000) {
260 checkDiskSpace(true);
261 }
262
263 // Emit the needDataCheck signal if needed
264 if (checkOnCompletion)
265 Q_EMIT needDataCheck(this);
266
267 // Move completed files if needed:
268 if (moveCompleted)
269 moveToCompletedDir();
270 }
271 #ifndef Q_WS_WIN
272 catch (BusError &e) {
273 Out(SYS_DIO | LOG_IMPORTANT) << "Caught SIGBUS " << endl;
274 if (!e.write_operation)
275 onIOError(e.toString());
276 else
277 onIOError(i18n("Error writing to disk, do you have enough diskspace?"));
278 }
279 #endif
280 catch (Error &e) {
281 onIOError(e.toString());
282 }
283 }
284
onIOError(const QString & msg)285 void TorrentControl::onIOError(const QString &msg)
286 {
287 Out(SYS_DIO | LOG_IMPORTANT) << "Error : " << msg << endl;
288 stats.stopped_by_error = true;
289 stats.status = ERROR;
290 stats.error_msg = msg;
291 istats.io_error = true;
292 statusChanged(this);
293 }
294
pause()295 void TorrentControl::pause()
296 {
297 if (!stats.running || stats.paused)
298 return;
299
300 pman->pause();
301
302 try {
303 downloader->saveDownloads(tordir + "current_chunks");
304 } catch (Error &e) {
305 // print out warning in case of failure
306 // it doesn't corrupt the data, so just a couple of lost chunks
307 Out(SYS_GEN | LOG_NOTICE) << "Warning : " << e.toString() << endl;
308 }
309
310 downloader->pause();
311 downloader->saveWebSeeds(tordir + "webseeds");
312 downloader->removeAllWebSeeds();
313 cman->stop();
314 stats.paused = true;
315 updateRunningTimes();
316 saveStats();
317 statusChanged(this);
318
319 Out(SYS_GEN | LOG_NOTICE) << "Paused " << tor->getNameSuggestion() << endl;
320 }
321
unpause()322 void TorrentControl::unpause()
323 {
324 if (!stats.running || !stats.paused || job_queue->runningJobs())
325 return;
326
327 cman->start();
328
329 try {
330 downloader->loadDownloads(tordir + "current_chunks");
331 } catch (Error &e) {
332 // print out warning in case of failure
333 // we can still continue the download
334 Out(SYS_GEN | LOG_NOTICE) << "Warning : " << e.toString() << endl;
335 }
336
337 downloader->loadWebSeeds(tordir + "webseeds");
338 pman->unpause();
339 loadStats();
340 istats.time_started_ul = istats.time_started_dl = QDateTime::currentDateTime();
341 stats.paused = false;
342 statusChanged(this);
343 Out(SYS_GEN | LOG_NOTICE) << "Unpaused " << tor->getNameSuggestion() << endl;
344 }
345
start()346 void TorrentControl::start()
347 {
348 // do not start running torrents or when there is a job running
349 if (stats.running || job_queue->runningJobs())
350 return;
351
352 if (stats.running && stats.paused) {
353 unpause();
354 return;
355 }
356
357 stats.paused = false;
358 stats.stopped_by_error = false;
359 istats.io_error = false;
360 istats.diskspace_warning_emitted = false;
361 try {
362 bool ret = true;
363 aboutToBeStarted(this, ret);
364 if (!ret)
365 return;
366 } catch (Error &err) {
367 // something went wrong when files were recreated, set error and rethrow
368 onIOError(err.toString());
369 return;
370 }
371
372 try {
373 cman->start();
374 } catch (Error &e) {
375 onIOError(e.toString());
376 throw;
377 }
378
379 istats.time_started_ul = istats.time_started_dl = QDateTime::currentDateTime();
380
381 if (prealloc) {
382 if (preallocate())
383 return;
384 }
385
386 continueStart();
387 }
388
continueStart()389 void TorrentControl::continueStart()
390 {
391 // continues start after the prealloc_thread has finished preallocation
392 pman->start(stats.completed && stats.superseeding);
393 pman->loadPeerList(tordir + "peer_list");
394 try {
395 downloader->loadDownloads(tordir + "current_chunks");
396 } catch (Error &e) {
397 // print out warning in case of failure
398 // we can still continue the download
399 Out(SYS_GEN | LOG_NOTICE) << "Warning : " << e.toString() << endl;
400 }
401
402 loadStats();
403 stats.running = true;
404 stats.started = true;
405 stats.queued = false;
406 stats.last_download_activity_time = stats.last_upload_activity_time = CurrentTime();
407 choker_update_timer.update();
408 stats_save_timer.update();
409 wanted_update_timer.update();
410
411 stalled_timer.update();
412 psman->start();
413 stalled_timer.update();
414 pman->setPartialSeed(!cman->haveAllChunks() && cman->chunksLeft() == 0);
415 }
416
updateRunningTimes()417 void TorrentControl::updateRunningTimes()
418 {
419 QDateTime now = QDateTime::currentDateTime();
420 if (!stats.completed)
421 istats.running_time_dl += istats.time_started_dl.secsTo(now);
422 istats.running_time_ul += istats.time_started_ul.secsTo(now);
423 istats.time_started_ul = istats.time_started_dl = now;
424 }
425
stop(WaitJob * wjob)426 void TorrentControl::stop(WaitJob *wjob)
427 {
428 if (!stats.paused)
429 updateRunningTimes();
430
431 // stop preallocation
432 if (job_queue->currentJob() && job_queue->currentJob()->torrentStatus() == ALLOCATING_DISKSPACE)
433 job_queue->currentJob()->kill(false);
434
435 if (stats.running) {
436 psman->stop(wjob);
437
438 if (tmon)
439 tmon->stopped();
440
441 try {
442 downloader->saveDownloads(tordir + "current_chunks");
443 } catch (Error &e) {
444 // print out warning in case of failure
445 // it doesn't corrupt the data, so just a couple of lost chunks
446 Out(SYS_GEN | LOG_NOTICE) << "Warning : " << e.toString() << endl;
447 }
448
449 downloader->clearDownloads();
450 }
451
452 pman->savePeerList(tordir + "peer_list");
453 pman->stop();
454 cman->stop();
455
456 stats.running = false;
457 stats.autostart = wjob != nullptr;
458 stats.queued = false;
459 stats.paused = false;
460 saveStats();
461 updateStatus();
462 updateStats();
463
464 Q_EMIT torrentStopped(this);
465 }
466
setMonitor(MonitorInterface * tmo)467 void TorrentControl::setMonitor(MonitorInterface *tmo)
468 {
469 tmon = tmo;
470 downloader->setMonitor(tmon);
471 if (tmon) {
472 const QList<Peer::Ptr> ppl = pman->getPeers();
473 for (const Peer::Ptr &peer : ppl)
474 tmon->peerAdded(peer.data());
475 }
476
477 tor->setMonitor(tmon);
478 }
479
init(QueueManagerInterface * qman,const QByteArray & data,const QString & tmpdir,const QString & ddir)480 void TorrentControl::init(QueueManagerInterface *qman, const QByteArray &data, const QString &tmpdir, const QString &ddir)
481 {
482 m_qman = qman;
483
484 // first load the torrent file
485 tor = new Torrent();
486 try {
487 tor->load(data, false);
488 } catch (bt::Error &err) {
489 Out(SYS_GEN | LOG_NOTICE) << "Failed to load torrent: " << err.toString() << endl;
490 delete tor;
491 tor = nullptr;
492 throw Error(i18n("An error occurred while loading <b>%1</b>:<br/><b>%2</b>", loadUrl().toString(), err.toString()));
493 }
494
495 tor->setFilePriorityListener(this);
496 initInternal(qman, tmpdir, ddir);
497
498 // copy data into torrent file
499 QString tor_copy = tordir + "torrent";
500 QFile fptr(tor_copy);
501 if (!fptr.open(QIODevice::WriteOnly))
502 throw Error(i18n("Unable to create %1: %2", tor_copy, fptr.errorString()));
503
504 fptr.write(data.data(), data.size());
505 }
506
checkExisting(QueueManagerInterface * qman)507 void TorrentControl::checkExisting(QueueManagerInterface *qman)
508 {
509 // check if we haven't already loaded the torrent
510 // only do this when qman isn't 0
511 if (qman && qman->alreadyLoaded(tor->getInfoHash())) {
512 if (!stats.priv_torrent) {
513 qman->mergeAnnounceList(tor->getInfoHash(), tor->getTrackerList());
514 throw Warning(
515 i18n("You are already downloading the torrent <b>%1</b>. "
516 "The tracker lists from both torrents have been merged.",
517 tor->getNameSuggestion()));
518 } else {
519 throw Warning(i18n("You are already downloading the torrent <b>%1</b>.", tor->getNameSuggestion()));
520 }
521 }
522 }
523
setupDirs(const QString & tmpdir,const QString & ddir)524 void TorrentControl::setupDirs(const QString &tmpdir, const QString &ddir)
525 {
526 tordir = tmpdir;
527
528 if (!tordir.endsWith(DirSeparator()))
529 tordir += DirSeparator();
530
531 outputdir = ddir.trimmed();
532 if (outputdir.length() > 0 && !outputdir.endsWith(DirSeparator()))
533 outputdir += DirSeparator();
534
535 if (!bt::Exists(tordir)) {
536 bt::MakeDir(tordir);
537 }
538 }
539
setupStats()540 void TorrentControl::setupStats()
541 {
542 stats.completed = false;
543 stats.running = false;
544 stats.torrent_name = tor->getNameSuggestion();
545 stats.multi_file_torrent = tor->isMultiFile();
546 stats.total_bytes = tor->getTotalSize();
547 stats.priv_torrent = tor->isPrivate();
548
549 // check the stats file for the custom_output_name variable
550 if (!stats_file)
551 stats_file = new StatsFile(tordir + "stats");
552
553 if (stats_file->hasKey("CUSTOM_OUTPUT_NAME") && stats_file->readULong("CUSTOM_OUTPUT_NAME") == 1) {
554 istats.custom_output_name = true;
555 }
556
557 if (stats.time_added.isNull())
558 stats.time_added = QDateTime::currentDateTime();
559
560 // load outputdir if outputdir is null
561 if (outputdir.isNull() || outputdir.length() == 0)
562 loadOutputDir();
563 }
564
setupData()565 void TorrentControl::setupData()
566 {
567 // create PeerManager and Tracker
568 pman = new PeerManager(*tor);
569 // Out() << "Tracker url " << url << " " << url.protocol() << " " << url.prettyURL() << endl;
570 psman = new PeerSourceManager(this, pman);
571
572 // Create chunkmanager, load the index file if it exists
573 // else create all the necesarry files
574 cman = new ChunkManager(*tor, tordir, outputdir, istats.custom_output_name, cache_factory);
575 if (bt::Exists(tordir + "index"))
576 cman->loadIndexFile();
577
578 connect(cman, &ChunkManager::updateStats, this, &TorrentControl::updateStats);
579 updateStats();
580 stats.completed = cman->completed();
581
582 // create downloader, uploader and choker
583 downloader = new Downloader(*tor, *pman, *cman);
584 downloader->loadWebSeeds(tordir + "webseeds");
585 connect(downloader, &Downloader::ioError, this, &TorrentControl::onIOError);
586 connect(downloader, &Downloader::chunkDownloaded, this, &TorrentControl::downloaded);
587 uploader = new Uploader(*cman, *pman);
588 choke = new Choker(*pman, *cman);
589
590 connect(pman, &PeerManager::newPeer, this, &TorrentControl::onNewPeer);
591 connect(pman, &PeerManager::peerKilled, this, &TorrentControl::onPeerRemoved);
592 connect(cman, &ChunkManager::excluded, downloader, &Downloader::onExcluded);
593 connect(cman, &ChunkManager::included, downloader, &Downloader::onIncluded);
594 connect(cman, &ChunkManager::corrupted, this, &TorrentControl::corrupted);
595 }
596
initInternal(QueueManagerInterface * qman,const QString & tmpdir,const QString & ddir)597 void TorrentControl::initInternal(QueueManagerInterface *qman, const QString &tmpdir, const QString &ddir)
598 {
599 checkExisting(qman);
600 setupDirs(tmpdir, ddir);
601 setupStats();
602 loadEncoding();
603 setupData();
604 updateStatus();
605
606 // to get rid of phantom bytes we need to take into account
607 // the data from downloads already in progress
608 try {
609 Uint64 db = downloader->bytesDownloaded();
610 Uint64 cb = downloader->getDownloadedBytesOfCurrentChunksFile(tordir + "current_chunks");
611 istats.prev_bytes_dl = db + cb;
612
613 // Out() << "Downloaded : " << BytesToString(db) << endl;
614 // Out() << "current_chunks : " << BytesToString(cb) << endl;
615 } catch (Error &e) {
616 // print out warning in case of failure
617 Out(SYS_GEN | LOG_DEBUG) << "Warning : " << e.toString() << endl;
618 istats.prev_bytes_dl = downloader->bytesDownloaded();
619 }
620
621 loadStats();
622 updateStats();
623 saveStats();
624 stats.output_path = cman->getOutputPath();
625 updateStatus();
626 }
627
setDisplayName(const QString & n)628 void TorrentControl::setDisplayName(const QString &n)
629 {
630 display_name = n;
631 saveStats();
632 }
633
announceAllowed()634 bool TorrentControl::announceAllowed()
635 {
636 return psman != nullptr && stats.running;
637 }
638
updateTracker()639 void TorrentControl::updateTracker()
640 {
641 if (announceAllowed()) {
642 psman->manualUpdate();
643 }
644 }
645
scrapeTracker()646 void TorrentControl::scrapeTracker()
647 {
648 psman->scrape();
649 }
650
onNewPeer(Peer * p)651 void TorrentControl::onNewPeer(Peer *p)
652 {
653 if (!stats.superseeding) {
654 // Only send which chunks we have when we are not superseeding
655 if (p->getStats().fast_extensions) {
656 const BitSet &bs = cman->getBitSet();
657 if (bs.allOn())
658 p->sendHaveAll();
659 else if (bs.numOnBits() == 0)
660 p->sendHaveNone();
661 else
662 p->sendBitSet(bs);
663 } else {
664 p->sendBitSet(cman->getBitSet());
665 }
666 }
667
668 if (!stats.completed && !stats.paused)
669 p->sendInterested();
670
671 if (!stats.priv_torrent) {
672 if (p->isDHTSupported())
673 p->sendPort(Globals::instance().getDHT().getPort());
674 else
675 // WORKAROUND so we can contact µTorrent's DHT
676 // They do not properly support the standard and do not turn on
677 // the DHT bit in the handshake, so we just ping each peer by default.
678 p->emitPortPacket();
679 }
680
681 // set group ID's for traffic shaping
682 p->setGroupIDs(upload_gid, download_gid);
683 downloader->addPieceDownloader(p->getPeerDownloader());
684 if (tmon)
685 tmon->peerAdded(p);
686 }
687
onPeerRemoved(Peer * p)688 void TorrentControl::onPeerRemoved(Peer *p)
689 {
690 downloader->removePieceDownloader(p->getPeerDownloader());
691 if (tmon)
692 tmon->peerRemoved(p);
693 }
694
doChoking()695 void TorrentControl::doChoking()
696 {
697 choke->update(stats.completed, stats);
698 }
699
changeTorDir(const QString & new_dir)700 bool TorrentControl::changeTorDir(const QString &new_dir)
701 {
702 int pos = tordir.lastIndexOf(bt::DirSeparator(), -2);
703 if (pos == -1) {
704 Out(SYS_GEN | LOG_DEBUG) << "Could not find torX part in " << tordir << endl;
705 return false;
706 }
707
708 QString ntordir = new_dir + tordir.mid(pos + 1);
709
710 Out(SYS_GEN | LOG_DEBUG) << tordir << " -> " << ntordir << endl;
711 try {
712 bt::Move(tordir, ntordir);
713 old_tordir = tordir;
714 tordir = ntordir;
715 } catch (Error &err) {
716 Out(SYS_GEN | LOG_IMPORTANT) << "Could not move " << tordir << " to " << ntordir << endl;
717 return false;
718 }
719
720 cman->changeDataDir(tordir);
721 return true;
722 }
723
changeOutputDir(const QString & ndir,int flags)724 bool TorrentControl::changeOutputDir(const QString &ndir, int flags)
725 {
726 // check if torrent is running and stop it before moving data
727 QString new_dir = ndir;
728 if (!new_dir.endsWith(bt::DirSeparator()))
729 new_dir += bt::DirSeparator();
730
731 try {
732 QString nd;
733 if (!(flags & bt::TorrentInterface::FULL_PATH)) {
734 if (istats.custom_output_name) {
735 int slash_pos = stats.output_path.lastIndexOf(bt::DirSeparator(), -2);
736 nd = new_dir + stats.output_path.mid(slash_pos + 1);
737 } else {
738 nd = new_dir + tor->getNameSuggestion();
739 }
740 } else {
741 nd = new_dir;
742 }
743
744 if (stats.output_path != nd) {
745 move_data_files_destination_path = nd;
746 Job *j = nullptr;
747 if (flags & bt::TorrentInterface::MOVE_FILES) {
748 if (stats.multi_file_torrent)
749 j = cman->moveDataFiles(nd);
750 else
751 j = cman->moveDataFiles(new_dir);
752 }
753
754 if (j) {
755 j->setTorrent(this);
756 connect(j, &Job::result, this, &TorrentControl::moveDataFilesFinished);
757 job_queue->enqueue(j);
758 return true;
759 } else {
760 moveDataFilesFinished(nullptr);
761 }
762 } else {
763 Out(SYS_GEN | LOG_NOTICE) << "Source is the same as destination, so doing nothing" << endl;
764 }
765 } catch (Error &err) {
766 Out(SYS_GEN | LOG_IMPORTANT) << "Could not move " << stats.output_path << " to " << new_dir << ". Exception: " << endl;
767 return false;
768 }
769
770 return true;
771 }
772
moveDataFilesFinished(KJob * kj)773 void TorrentControl::moveDataFilesFinished(KJob *kj)
774 {
775 Job *job = (Job *)kj;
776 if (job)
777 cman->moveDataFilesFinished(job);
778
779 if (!job || (job && !job->error())) {
780 cman->changeOutputPath(move_data_files_destination_path);
781 outputdir = stats.output_path = move_data_files_destination_path;
782 istats.custom_output_name = true;
783
784 saveStats();
785 Out(SYS_GEN | LOG_NOTICE) << "Data directory changed for torrent "
786 << "'" << stats.torrent_name << "' to: " << move_data_files_destination_path << endl;
787 } else if (job->error()) {
788 Out(SYS_GEN | LOG_IMPORTANT) << "Could not move " << stats.output_path << " to " << move_data_files_destination_path << endl;
789 }
790 }
791
moveTorrentFiles(const QMap<TorrentFileInterface *,QString> & files)792 bool TorrentControl::moveTorrentFiles(const QMap<TorrentFileInterface *, QString> &files)
793 {
794 try {
795 Job *j = cman->moveDataFiles(files);
796 if (j) {
797 connect(j, &Job::result, this, &TorrentControl::moveDataFilesWithMapFinished);
798 job_queue->enqueue(j);
799 }
800
801 } catch (Error &err) {
802 return false;
803 }
804
805 return true;
806 }
807
moveDataFilesWithMapFinished(KJob * j)808 void TorrentControl::moveDataFilesWithMapFinished(KJob *j)
809 {
810 if (!j)
811 return;
812
813 MoveDataFilesJob *job = (MoveDataFilesJob *)j;
814 cman->moveDataFilesFinished(job->fileMap(), job);
815 Out(SYS_GEN | LOG_NOTICE) << "Move of data files completed " << endl;
816 }
817
rollback()818 void TorrentControl::rollback()
819 {
820 try {
821 bt::Move(tordir, old_tordir);
822 tordir = old_tordir;
823 cman->changeDataDir(tordir);
824 } catch (Error &err) {
825 Out(SYS_GEN | LOG_IMPORTANT) << "Could not move " << tordir << " to " << old_tordir << endl;
826 }
827 }
828
updateStatus()829 void TorrentControl::updateStatus()
830 {
831 TorrentStatus old = stats.status;
832 if (stats.stopped_by_error)
833 stats.status = ERROR;
834 else if (job_queue->currentJob() && job_queue->currentJob()->torrentStatus() != INVALID_STATUS)
835 stats.status = job_queue->currentJob()->torrentStatus();
836 else if (stats.queued)
837 stats.status = QUEUED;
838 else if (stats.completed && (overMaxRatio() || overMaxSeedTime()))
839 stats.status = SEEDING_COMPLETE;
840 else if (!stats.running && stats.completed)
841 stats.status = DOWNLOAD_COMPLETE;
842 else if (!stats.started)
843 stats.status = NOT_STARTED;
844 else if (!stats.running)
845 stats.status = STOPPED;
846 else if (stats.running && stats.paused)
847 stats.status = PAUSED;
848 else if (stats.running && stats.completed)
849 stats.status = stats.superseeding ? SUPERSEEDING : SEEDING;
850 else if (stats.running)
851 // protocol messages are also included in speed calculation, so lets not compare with 0
852 stats.status = downloader->downloadRate() > 100 ? DOWNLOADING : STALLED;
853
854 if (old != stats.status)
855 statusChanged(this);
856 }
857
downloadedChunksBitSet() const858 const BitSet &TorrentControl::downloadedChunksBitSet() const
859 {
860 if (cman)
861 return cman->getBitSet();
862 else
863 return BitSet::null;
864 }
865
availableChunksBitSet() const866 const BitSet &TorrentControl::availableChunksBitSet() const
867 {
868 if (!pman)
869 return BitSet::null;
870 else
871 return pman->getAvailableChunksBitSet();
872 }
873
excludedChunksBitSet() const874 const BitSet &TorrentControl::excludedChunksBitSet() const
875 {
876 if (!cman)
877 return BitSet::null;
878 else
879 return cman->getExcludedBitSet();
880 }
881
onlySeedChunksBitSet() const882 const BitSet &TorrentControl::onlySeedChunksBitSet() const
883 {
884 if (!cman)
885 return BitSet::null;
886 else
887 return cman->getOnlySeedBitSet();
888 }
889
saveStats()890 void TorrentControl::saveStats()
891 {
892 if (loading_stats) // don't save while we are loading
893 return;
894
895 if (!stats_file)
896 stats_file = new StatsFile(tordir + "stats");
897
898 stats_file->write("OUTPUTDIR", cman->getDataDir());
899 stats_file->write("COMPLETEDDIR", completed_dir);
900
901 if (cman->getDataDir() != outputdir)
902 outputdir = cman->getDataDir();
903
904 stats_file->write("UPLOADED", QString::number(uploader->bytesUploaded()));
905
906 if (stats.running) {
907 QDateTime now = QDateTime::currentDateTime();
908 if (!stats.completed)
909 stats_file->write("RUNNING_TIME_DL", QString("%1").arg(istats.running_time_dl + istats.time_started_dl.secsTo(now)));
910 else
911 stats_file->write("RUNNING_TIME_DL", QString("%1").arg(istats.running_time_dl));
912 stats_file->write("RUNNING_TIME_UL", QString("%1").arg(istats.running_time_ul + istats.time_started_ul.secsTo(now)));
913 } else {
914 stats_file->write("RUNNING_TIME_DL", QString("%1").arg(istats.running_time_dl));
915 stats_file->write("RUNNING_TIME_UL", QString("%1").arg(istats.running_time_ul));
916 }
917
918 stats_file->write("QM_CAN_START", stats.qm_can_start ? "1" : "0");
919 stats_file->write("PRIORITY", QString("%1").arg(istats.priority));
920 stats_file->write("AUTOSTART", QString("%1").arg(stats.autostart));
921 stats_file->write("IMPORTED", QString("%1").arg(stats.imported_bytes));
922 stats_file->write("CUSTOM_OUTPUT_NAME", istats.custom_output_name ? "1" : "0");
923 stats_file->write("MAX_RATIO", QString("%1").arg(stats.max_share_ratio, 0, 'f', 2));
924 stats_file->write("MAX_SEED_TIME", QString::number(stats.max_seed_time));
925 stats_file->write("RESTART_DISK_PREALLOCATION", prealloc ? "1" : "0");
926 stats_file->write("AUTO_STOPPED", stats.auto_stopped ? "1" : "0");
927
928 if (!stats.priv_torrent) {
929 // save dht and pex
930 stats_file->write("DHT", isFeatureEnabled(DHT_FEATURE) ? "1" : "0");
931 stats_file->write("UT_PEX", isFeatureEnabled(UT_PEX_FEATURE) ? "1" : "0");
932 }
933
934 stats_file->write("UPLOAD_LIMIT", QString::number(upload_limit));
935 stats_file->write("DOWNLOAD_LIMIT", QString::number(download_limit));
936 stats_file->write("ENCODING", QString(tor->getTextCodec()->name()));
937 stats_file->write("ASSURED_UPLOAD_SPEED", QString::number(assured_upload_speed));
938 stats_file->write("ASSURED_DOWNLOAD_SPEED", QString::number(assured_download_speed));
939 if (!user_modified_name.isEmpty())
940 stats_file->write("USER_MODIFIED_NAME", user_modified_name);
941 stats_file->write("DISPLAY_NAME", display_name);
942 stats_file->write("URL", url.toDisplayString());
943
944 stats_file->write("TIME_ADDED", QString("%1").arg(stats.time_added.toTime_t()));
945 stats_file->write("SUPERSEEDING", stats.superseeding ? "1" : "0");
946
947 stats_file->sync();
948 }
949
loadStats()950 void TorrentControl::loadStats()
951 {
952 if (!bt::Exists(tordir + "stats")) {
953 setFeatureEnabled(DHT_FEATURE, true);
954 setFeatureEnabled(UT_PEX_FEATURE, true);
955 return;
956 }
957
958 RecursiveEntryGuard guard(&loading_stats);
959 if (!stats_file)
960 stats_file = new StatsFile(tordir + "stats");
961
962 Uint64 val = stats_file->readUint64("UPLOADED");
963 // stats.session_bytes_uploaded will be calculated based upon prev_bytes_ul
964 // seeing that this will change here, we need to save it
965 istats.session_bytes_uploaded = stats.session_bytes_uploaded;
966 istats.prev_bytes_ul = val;
967 uploader->setBytesUploaded(val);
968
969 istats.running_time_dl = stats_file->readULong("RUNNING_TIME_DL");
970 istats.running_time_ul = stats_file->readULong("RUNNING_TIME_UL");
971 // make sure runtime ul is always equal or largen then running time dl
972 // in case something got corrupted
973 if (istats.running_time_ul < istats.running_time_dl)
974 istats.running_time_ul = istats.running_time_dl;
975
976 outputdir = stats_file->readString("OUTPUTDIR").trimmed();
977 if (stats_file->hasKey("CUSTOM_OUTPUT_NAME") && stats_file->readULong("CUSTOM_OUTPUT_NAME") == 1) {
978 istats.custom_output_name = true;
979 }
980
981 if (stats_file->hasKey("COMPLETEDDIR")) {
982 completed_dir = stats_file->readString("COMPLETEDDIR");
983 if (completed_dir == outputdir)
984 completed_dir = QString();
985 }
986
987 if (stats_file->hasKey("USER_MODIFIED_NAME"))
988 user_modified_name = stats_file->readString("USER_MODIFIED_NAME");
989
990 if (stats_file->hasKey("DISPLAY_NAME"))
991 display_name = stats_file->readString("DISPLAY_NAME");
992
993 istats.priority = stats_file->readInt("PRIORITY");
994 stats.autostart = stats_file->readBoolean("AUTOSTART");
995 stats.imported_bytes = stats_file->readUint64("IMPORTED");
996 stats.max_share_ratio = stats_file->readFloat("MAX_RATIO");
997 stats.max_seed_time = stats_file->readFloat("MAX_SEED_TIME");
998 stats.qm_can_start = stats_file->readBoolean("QM_CAN_START");
999 stats.auto_stopped = stats_file->readBoolean("AUTO_STOPPED");
1000
1001 if (stats_file->hasKey("RESTART_DISK_PREALLOCATION"))
1002 prealloc = stats_file->readString("RESTART_DISK_PREALLOCATION") == "1";
1003
1004 if (!stats.priv_torrent) {
1005 if (stats_file->hasKey("DHT"))
1006 istats.dht_on = stats_file->readBoolean("DHT");
1007 else
1008 istats.dht_on = true;
1009
1010 setFeatureEnabled(DHT_FEATURE, istats.dht_on);
1011 if (stats_file->hasKey("UT_PEX"))
1012 setFeatureEnabled(UT_PEX_FEATURE, stats_file->readBoolean("UT_PEX"));
1013 }
1014
1015 QString codec = stats_file->readString("ENCODING");
1016 if (codec.length() > 0) {
1017 QTextCodec *cod = QTextCodec::codecForName(codec.toLocal8Bit());
1018 if (cod)
1019 changeTextCodec(cod);
1020 }
1021
1022 Uint32 aup = stats_file->readInt("ASSURED_UPLOAD_SPEED");
1023 Uint32 adown = stats_file->readInt("ASSURED_DOWNLOAD_SPEED");
1024 Uint32 up = stats_file->readInt("UPLOAD_LIMIT");
1025 Uint32 down = stats_file->readInt("DOWNLOAD_LIMIT");
1026 setDownloadProps(down, adown);
1027 setUploadProps(up, aup);
1028 pman->setGroupIDs(upload_gid, download_gid);
1029
1030 url = QUrl(stats_file->readString("URL"));
1031
1032 if (stats_file->hasKey("TIME_ADDED"))
1033 stats.time_added.setTime_t(stats_file->readULong("TIME_ADDED"));
1034 else
1035 stats.time_added = QDateTime::currentDateTime();
1036
1037 bool ss = stats_file->hasKey("SUPERSEEDING") && stats_file->readBoolean("SUPERSEEDING");
1038 setSuperSeeding(ss);
1039 }
1040
loadOutputDir()1041 void TorrentControl::loadOutputDir()
1042 {
1043 if (!stats_file)
1044 stats_file = new StatsFile(tordir + "stats");
1045
1046 if (!stats_file->hasKey("OUTPUTDIR"))
1047 return;
1048
1049 outputdir = stats_file->readString("OUTPUTDIR").trimmed();
1050 if (stats_file->hasKey("CUSTOM_OUTPUT_NAME") && stats_file->readULong("CUSTOM_OUTPUT_NAME") == 1) {
1051 istats.custom_output_name = true;
1052 }
1053 }
1054
loadEncoding()1055 void TorrentControl::loadEncoding()
1056 {
1057 if (!stats_file)
1058 stats_file = new StatsFile(tordir + "stats");
1059 if (!stats_file->hasKey("ENCODING"))
1060 return;
1061
1062 QString codec = stats_file->readString("ENCODING");
1063 if (codec.length() > 0) {
1064 QTextCodec *cod = QTextCodec::codecForName(codec.toLocal8Bit());
1065 if (cod)
1066 changeTextCodec(cod);
1067 }
1068 }
1069
readyForPreview() const1070 bool TorrentControl::readyForPreview() const
1071 {
1072 if (tor->isMultiFile() || !tor->isMultimedia())
1073 return false;
1074
1075 Uint32 preview_range = cman->previewChunkRangeSize();
1076 if (preview_range == 0)
1077 return false;
1078
1079 const BitSet &bs = downloadedChunksBitSet();
1080 for (Uint32 i = 0; i < qMin(preview_range, bs.getNumBits()); i++) {
1081 if (!bs.get(i))
1082 return false;
1083 }
1084 return true;
1085 }
1086
isMultimedia() const1087 bool TorrentControl::isMultimedia() const
1088 {
1089 return !tor->isMultiFile() && tor->isMultimedia();
1090 }
1091
updateStats()1092 void TorrentControl::updateStats()
1093 {
1094 stats.num_chunks_downloading = downloader ? downloader->numActiveDownloads() : 0;
1095 stats.num_peers = pman ? pman->getNumConnectedPeers() : 0;
1096 stats.upload_rate = uploader && stats.running ? uploader->uploadRate() : 0;
1097 stats.download_rate = downloader && stats.running ? downloader->downloadRate() : 0;
1098 stats.bytes_left = cman ? cman->bytesLeft() : 0;
1099 stats.bytes_left_to_download = cman ? cman->bytesLeftToDownload() : 0;
1100 stats.bytes_uploaded = uploader ? uploader->bytesUploaded() : 0;
1101 stats.bytes_downloaded = downloader ? downloader->bytesDownloaded() : 0;
1102 stats.total_chunks = tor ? tor->getNumChunks() : 0;
1103 stats.num_chunks_downloaded = cman ? cman->chunksDownloaded() : 0;
1104 stats.num_chunks_excluded = cman ? cman->chunksExcluded() : 0;
1105 stats.chunk_size = tor ? tor->getChunkSize() : 0;
1106 stats.num_chunks_left = cman ? cman->chunksLeft() : 0;
1107 stats.total_bytes_to_download = (tor && cman) ? tor->getTotalSize() - cman->bytesExcluded() : 0;
1108
1109 if (stats.bytes_downloaded >= istats.prev_bytes_dl)
1110 stats.session_bytes_downloaded = stats.bytes_downloaded - istats.prev_bytes_dl;
1111 else
1112 stats.session_bytes_downloaded = 0;
1113
1114 if (stats.bytes_uploaded >= istats.prev_bytes_ul)
1115 stats.session_bytes_uploaded = (stats.bytes_uploaded - istats.prev_bytes_ul) + istats.session_bytes_uploaded;
1116 else
1117 stats.session_bytes_uploaded = istats.session_bytes_uploaded;
1118
1119 getSeederInfo(stats.seeders_total, stats.seeders_connected_to);
1120 getLeecherInfo(stats.leechers_total, stats.leechers_connected_to);
1121 }
1122
trackerScrapeDone()1123 void TorrentControl::trackerScrapeDone()
1124 {
1125 stats.seeders_total = psman->getNumSeeders();
1126 stats.leechers_total = psman->getNumLeechers();
1127 }
1128
getSeederInfo(Uint32 & total,Uint32 & connected_to) const1129 void TorrentControl::getSeederInfo(Uint32 &total, Uint32 &connected_to) const
1130 {
1131 total = 0;
1132 connected_to = 0;
1133 if (!pman || !psman)
1134 return;
1135
1136 connected_to = pman->getNumConnectedSeeders();
1137 total = psman->getNumSeeders();
1138 if (total == 0)
1139 total = connected_to;
1140 }
1141
getLeecherInfo(Uint32 & total,Uint32 & connected_to) const1142 void TorrentControl::getLeecherInfo(Uint32 &total, Uint32 &connected_to) const
1143 {
1144 total = 0;
1145 connected_to = 0;
1146 if (!pman || !psman)
1147 return;
1148
1149 connected_to = pman->getNumConnectedLeechers();
1150 total = psman->getNumLeechers();
1151 if (total == 0)
1152 total = connected_to;
1153 }
1154
getRunningTimeDL() const1155 Uint32 TorrentControl::getRunningTimeDL() const
1156 {
1157 if (!stats.running || stats.completed || stats.paused)
1158 return istats.running_time_dl;
1159 else
1160 return istats.running_time_dl + istats.time_started_dl.secsTo(QDateTime::currentDateTime());
1161 }
1162
getRunningTimeUL() const1163 Uint32 TorrentControl::getRunningTimeUL() const
1164 {
1165 if (!stats.running || stats.paused)
1166 return istats.running_time_ul;
1167 else
1168 return istats.running_time_ul + istats.time_started_ul.secsTo(QDateTime::currentDateTime());
1169 }
1170
getNumFiles() const1171 Uint32 TorrentControl::getNumFiles() const
1172 {
1173 if (tor && tor->isMultiFile())
1174 return tor->getNumFiles();
1175 else
1176 return 0;
1177 }
1178
getTorrentFile(Uint32 index)1179 TorrentFileInterface &TorrentControl::getTorrentFile(Uint32 index)
1180 {
1181 if (tor)
1182 return tor->getFile(index);
1183 else
1184 return TorrentFile::null;
1185 }
1186
getTorrentFile(Uint32 index) const1187 const TorrentFileInterface &TorrentControl::getTorrentFile(Uint32 index) const
1188 {
1189 if (tor)
1190 return tor->getFile(index);
1191 else
1192 return TorrentFile::null;
1193 }
1194
setPriority(int p)1195 void TorrentControl::setPriority(int p)
1196 {
1197 istats.priority = p;
1198 if (!stats_file)
1199 stats_file = new StatsFile(tordir + "stats");
1200
1201 stats_file->write("PRIORITY", QString("%1").arg(istats.priority));
1202 updateStatus();
1203 }
1204
setMaxShareRatio(float ratio)1205 void TorrentControl::setMaxShareRatio(float ratio)
1206 {
1207 if (ratio == 1.00f) {
1208 if (stats.max_share_ratio != ratio)
1209 stats.max_share_ratio = ratio;
1210 } else
1211 stats.max_share_ratio = ratio;
1212
1213 saveStats();
1214 Q_EMIT maxRatioChanged(this);
1215 }
1216
setMaxSeedTime(float hours)1217 void TorrentControl::setMaxSeedTime(float hours)
1218 {
1219 stats.max_seed_time = hours;
1220 saveStats();
1221 }
1222
overMaxRatio()1223 bool TorrentControl::overMaxRatio()
1224 {
1225 return stats.overMaxRatio();
1226 }
1227
overMaxSeedTime()1228 bool TorrentControl::overMaxSeedTime()
1229 {
1230 if (stats.completed && stats.max_seed_time > 0) {
1231 Uint32 dl = getRunningTimeDL();
1232 Uint32 ul = getRunningTimeUL();
1233 if ((ul - dl) / 3600.0f > stats.max_seed_time)
1234 return true;
1235 }
1236
1237 return false;
1238 }
1239
getTrackersList()1240 TrackersList *TorrentControl::getTrackersList()
1241 {
1242 return psman;
1243 }
1244
getTrackersList() const1245 const TrackersList *TorrentControl::getTrackersList() const
1246 {
1247 return psman;
1248 }
1249
startDataCheck(bool auto_import,bt::Uint32 from,bt::Uint32 to)1250 Job *TorrentControl::startDataCheck(bool auto_import, bt::Uint32 from, bt::Uint32 to)
1251 {
1252 Job *j = new DataCheckerJob(auto_import, this, from, to);
1253 job_queue->enqueue(j);
1254 return j;
1255 }
1256
beforeDataCheck()1257 void TorrentControl::beforeDataCheck()
1258 {
1259 stats.status = CHECKING_DATA;
1260 stats.num_corrupted_chunks = 0; // reset the number of corrupted chunks found
1261 statusChanged(this);
1262 }
1263
afterDataCheck(DataCheckerJob * job,const BitSet & result)1264 void TorrentControl::afterDataCheck(DataCheckerJob *job, const BitSet &result)
1265 {
1266 bool completed = stats.completed;
1267 if (job && !job->isStopped()) {
1268 downloader->dataChecked(result, job->firstChunk(), job->lastChunk());
1269 // update chunk manager
1270 cman->dataChecked(result, job->firstChunk(), job->lastChunk());
1271 if (job->isAutoImport()) {
1272 downloader->recalcDownloaded();
1273 stats.imported_bytes = downloader->bytesDownloaded();
1274 stats.completed = cman->completed();
1275 } else {
1276 Uint64 downloaded = stats.bytes_downloaded;
1277 downloader->recalcDownloaded();
1278 updateStats();
1279 if (stats.bytes_downloaded > downloaded)
1280 stats.imported_bytes = stats.bytes_downloaded - downloaded;
1281
1282 stats.completed = cman->completed();
1283 pman->setPartialSeed(!cman->haveAllChunks() && cman->chunksLeft() == 0);
1284 }
1285 }
1286
1287 saveStats();
1288 updateStats();
1289 Out(SYS_GEN | LOG_NOTICE) << "Data check finished" << endl;
1290 updateStatus();
1291 dataCheckFinished();
1292
1293 if (stats.completed != completed) {
1294 // Tell QM to redo queue and emit finished signal
1295 // seeder might have become downloader, so
1296 // queue might need to be redone
1297 // use QTimer because we need to ensure this is run after the JobQueue removes the job
1298 QTimer::singleShot(0, this, SIGNAL(updateQueue()));
1299 if (stats.completed)
1300 QTimer::singleShot(0, this, &TorrentControl::emitFinished);
1301 }
1302 }
1303
emitFinished()1304 void TorrentControl::emitFinished()
1305 {
1306 finished(this);
1307 }
1308
markExistingFilesAsDownloaded()1309 void TorrentControl::markExistingFilesAsDownloaded()
1310 {
1311 cman->markExistingFilesAsDownloaded();
1312 downloader->recalcDownloaded();
1313 stats.imported_bytes = downloader->bytesDownloaded();
1314 if (cman->haveAllChunks())
1315 stats.completed = true;
1316
1317 updateStats();
1318 }
1319
hasExistingFiles() const1320 bool TorrentControl::hasExistingFiles() const
1321 {
1322 return cman->hasExistingFiles();
1323 }
1324
isStorageMounted(QStringList & missing)1325 bool TorrentControl::isStorageMounted(QStringList &missing)
1326 {
1327 return cman->isStorageMounted(missing);
1328 }
1329
hasMissingFiles(QStringList & sl)1330 bool TorrentControl::hasMissingFiles(QStringList &sl)
1331 {
1332 return cman->hasMissingFiles(sl);
1333 }
1334
recreateMissingFiles()1335 void TorrentControl::recreateMissingFiles()
1336 {
1337 try {
1338 cman->recreateMissingFiles();
1339 prealloc = true; // set prealloc to true so files will be truncated again
1340 downloader->dataChecked(cman->getBitSet(), 0, tor->getNumChunks() - 1); // update chunk selector
1341 } catch (Error &err) {
1342 onIOError(err.toString());
1343 throw;
1344 }
1345 }
1346
dndMissingFiles()1347 void TorrentControl::dndMissingFiles()
1348 {
1349 try {
1350 cman->dndMissingFiles();
1351 prealloc = true; // set prealloc to true so files will be truncated again
1352 missingFilesMarkedDND(this);
1353 downloader->dataChecked(cman->getBitSet(), 0, tor->getNumChunks() - 1); // update chunk selector
1354 } catch (Error &err) {
1355 onIOError(err.toString());
1356 throw;
1357 }
1358 }
1359
handleError(const QString & err)1360 void TorrentControl::handleError(const QString &err)
1361 {
1362 onIOError(err);
1363 }
1364
getNumDHTNodes() const1365 Uint32 TorrentControl::getNumDHTNodes() const
1366 {
1367 return tor->getNumDHTNodes();
1368 }
1369
getDHTNode(Uint32 i) const1370 const DHTNode &TorrentControl::getDHTNode(Uint32 i) const
1371 {
1372 return tor->getDHTNode(i);
1373 }
1374
deleteDataFiles()1375 void TorrentControl::deleteDataFiles()
1376 {
1377 if (!cman)
1378 return;
1379
1380 Job *job = cman->deleteDataFiles();
1381 if (job)
1382 job->start(); // don't use queue, this is only done when removing the torrent
1383 }
1384
getInfoHash() const1385 const bt::SHA1Hash &TorrentControl::getInfoHash() const
1386 {
1387 return tor->getInfoHash();
1388 }
1389
addPeerSource(PeerSource * ps)1390 void TorrentControl::addPeerSource(PeerSource *ps)
1391 {
1392 if (psman)
1393 psman->addPeerSource(ps);
1394 }
1395
removePeerSource(PeerSource * ps)1396 void TorrentControl::removePeerSource(PeerSource *ps)
1397 {
1398 if (psman)
1399 psman->removePeerSource(ps);
1400 }
1401
corrupted(Uint32 chunk)1402 void TorrentControl::corrupted(Uint32 chunk)
1403 {
1404 // make sure we will redownload the chunk
1405 downloader->corrupted(chunk);
1406 if (stats.completed)
1407 stats.completed = false;
1408
1409 // emit signal to show a systray message
1410 stats.num_corrupted_chunks++;
1411 corruptedDataFound(this);
1412 }
1413
getETA()1414 int TorrentControl::getETA()
1415 {
1416 return m_eta->estimate();
1417 }
1418
getOwnPeerID() const1419 const bt::PeerID &TorrentControl::getOwnPeerID() const
1420 {
1421 return tor->getPeerID();
1422 }
1423
isFeatureEnabled(TorrentFeature tf)1424 bool TorrentControl::isFeatureEnabled(TorrentFeature tf)
1425 {
1426 switch (tf) {
1427 case DHT_FEATURE:
1428 return psman->dhtStarted();
1429 case UT_PEX_FEATURE:
1430 return pman->isPexEnabled();
1431 default:
1432 return false;
1433 }
1434 }
1435
setFeatureEnabled(TorrentFeature tf,bool on)1436 void TorrentControl::setFeatureEnabled(TorrentFeature tf, bool on)
1437 {
1438 switch (tf) {
1439 case DHT_FEATURE:
1440 if (on) {
1441 if (!stats.priv_torrent) {
1442 psman->addDHT();
1443 istats.dht_on = psman->dhtStarted();
1444 saveStats();
1445 }
1446 } else {
1447 psman->removeDHT();
1448 istats.dht_on = false;
1449 saveStats();
1450 }
1451 break;
1452 case UT_PEX_FEATURE:
1453 if (on) {
1454 if (!stats.priv_torrent && !pman->isPexEnabled()) {
1455 pman->setPexEnabled(true);
1456 }
1457 } else {
1458 pman->setPexEnabled(false);
1459 }
1460 break;
1461 }
1462 }
1463
createFiles()1464 void TorrentControl::createFiles()
1465 {
1466 cman->createFiles(true);
1467 stats.output_path = cman->getOutputPath();
1468 }
1469
checkDiskSpace(bool emit_sig)1470 bool TorrentControl::checkDiskSpace(bool emit_sig)
1471 {
1472 last_diskspace_check = bt::CurrentTime();
1473
1474 // calculate free disk space
1475 Uint64 bytes_free = 0;
1476 if (FreeDiskSpace(getDataDir(), bytes_free)) {
1477 Out(SYS_GEN | LOG_DEBUG) << "FreeBytes " << BytesToString(bytes_free) << endl;
1478 Uint64 bytes_to_download = stats.total_bytes_to_download;
1479 Uint64 downloaded = 0;
1480 try {
1481 downloaded = cman->diskUsage();
1482 Out(SYS_GEN | LOG_DEBUG) << "Downloaded " << BytesToString(downloaded) << endl;
1483 } catch (bt::Error &err) {
1484 Out(SYS_GEN | LOG_DEBUG) << "Error : " << err.toString() << endl;
1485 }
1486 Uint64 remaining = 0;
1487 if (downloaded <= bytes_to_download)
1488 remaining = bytes_to_download - downloaded;
1489
1490 Out(SYS_GEN | LOG_DEBUG) << "Remaining " << BytesToString(remaining) << endl;
1491 if (remaining > bytes_free) {
1492 bool toStop = bytes_free < (Uint64)min_diskspace * 1024 * 1024;
1493
1494 // if we don't need to stop the torrent, only emit the signal once
1495 // so that we do bother the user continuously
1496 if (emit_sig && (toStop || !istats.diskspace_warning_emitted)) {
1497 Q_EMIT diskSpaceLow(this, toStop);
1498 istats.diskspace_warning_emitted = true;
1499 }
1500
1501 if (!stats.running) {
1502 stats.status = NO_SPACE_LEFT;
1503 statusChanged(this);
1504 }
1505
1506 return false;
1507 }
1508 }
1509
1510 return true;
1511 }
1512
setUploadProps(Uint32 limit,Uint32 rate)1513 void TorrentControl::setUploadProps(Uint32 limit, Uint32 rate)
1514 {
1515 net::SocketMonitor &smon = net::SocketMonitor::instance();
1516 if (upload_gid) {
1517 if (!limit && !rate) {
1518 smon.removeGroup(net::SocketMonitor::UPLOAD_GROUP, upload_gid);
1519 upload_gid = 0;
1520 } else {
1521 smon.setGroupLimit(net::SocketMonitor::UPLOAD_GROUP, upload_gid, limit);
1522 smon.setGroupAssuredRate(net::SocketMonitor::UPLOAD_GROUP, upload_gid, rate);
1523 }
1524 } else {
1525 if (limit || rate) {
1526 upload_gid = smon.newGroup(net::SocketMonitor::UPLOAD_GROUP, limit, rate);
1527 }
1528 }
1529
1530 upload_limit = limit;
1531 assured_upload_speed = rate;
1532 }
1533
setDownloadProps(Uint32 limit,Uint32 rate)1534 void TorrentControl::setDownloadProps(Uint32 limit, Uint32 rate)
1535 {
1536 net::SocketMonitor &smon = net::SocketMonitor::instance();
1537 if (download_gid) {
1538 if (!limit && !rate) {
1539 smon.removeGroup(net::SocketMonitor::DOWNLOAD_GROUP, download_gid);
1540 download_gid = 0;
1541 } else {
1542 smon.setGroupLimit(net::SocketMonitor::DOWNLOAD_GROUP, download_gid, limit);
1543 smon.setGroupAssuredRate(net::SocketMonitor::DOWNLOAD_GROUP, download_gid, rate);
1544 }
1545 } else {
1546 if (limit || rate) {
1547 download_gid = smon.newGroup(net::SocketMonitor::DOWNLOAD_GROUP, limit, rate);
1548 }
1549 }
1550
1551 download_limit = limit;
1552 assured_download_speed = rate;
1553 }
1554
setTrafficLimits(Uint32 up,Uint32 down)1555 void TorrentControl::setTrafficLimits(Uint32 up, Uint32 down)
1556 {
1557 setDownloadProps(down, assured_download_speed);
1558 setUploadProps(up, assured_upload_speed);
1559 saveStats();
1560 pman->setGroupIDs(upload_gid, download_gid);
1561 downloader->setGroupIDs(upload_gid, download_gid);
1562 }
1563
getTrafficLimits(Uint32 & up,Uint32 & down)1564 void TorrentControl::getTrafficLimits(Uint32 &up, Uint32 &down)
1565 {
1566 up = upload_limit;
1567 down = download_limit;
1568 }
1569
setAssuredSpeeds(Uint32 up,Uint32 down)1570 void TorrentControl::setAssuredSpeeds(Uint32 up, Uint32 down)
1571 {
1572 setDownloadProps(download_limit, down);
1573 setUploadProps(upload_limit, up);
1574 saveStats();
1575 pman->setGroupIDs(upload_gid, download_gid);
1576 downloader->setGroupIDs(upload_gid, download_gid);
1577 }
1578
getAssuredSpeeds(Uint32 & up,Uint32 & down)1579 void TorrentControl::getAssuredSpeeds(Uint32 &up, Uint32 &down)
1580 {
1581 up = assured_upload_speed;
1582 down = assured_download_speed;
1583 }
1584
getPeerMgr() const1585 const PeerManager *TorrentControl::getPeerMgr() const
1586 {
1587 return pman;
1588 }
1589
preallocFinished(const QString & error,bool completed)1590 void TorrentControl::preallocFinished(const QString &error, bool completed)
1591 {
1592 Out(SYS_GEN | LOG_DEBUG) << "preallocFinished " << error << " " << completed << endl;
1593 if (!error.isEmpty() || !completed) {
1594 // upon error just call onIOError and return
1595 if (!error.isEmpty())
1596 onIOError(error);
1597 prealloc = true; // still need to do preallocation
1598 } else {
1599 // continue the startup of the torrent
1600 prealloc = false;
1601 stats.status = NOT_STARTED;
1602 saveStats();
1603 continueStart();
1604 statusChanged(this);
1605 }
1606 }
1607
getTextCodec() const1608 const QTextCodec *TorrentControl::getTextCodec() const
1609 {
1610 if (!tor)
1611 return nullptr;
1612 else
1613 return tor->getTextCodec();
1614 }
1615
changeTextCodec(QTextCodec * tc)1616 void TorrentControl::changeTextCodec(QTextCodec *tc)
1617 {
1618 if (tor) {
1619 tor->changeTextCodec(tc);
1620 stats.torrent_name = tor->getNameSuggestion();
1621 }
1622 }
1623
setCacheFactory(CacheFactory * cf)1624 void TorrentControl::setCacheFactory(CacheFactory *cf)
1625 {
1626 cache_factory = cf;
1627 }
1628
getNumWebSeeds() const1629 Uint32 TorrentControl::getNumWebSeeds() const
1630 {
1631 return downloader->getNumWebSeeds();
1632 }
1633
getWebSeed(Uint32 i) const1634 const WebSeedInterface *TorrentControl::getWebSeed(Uint32 i) const
1635 {
1636 return downloader->getWebSeed(i);
1637 }
1638
getWebSeed(Uint32 i)1639 WebSeedInterface *TorrentControl::getWebSeed(Uint32 i)
1640 {
1641 return downloader->getWebSeed(i);
1642 }
1643
addWebSeed(const QUrl & url)1644 bool TorrentControl::addWebSeed(const QUrl &url)
1645 {
1646 WebSeed *ws = downloader->addWebSeed(url);
1647 if (ws) {
1648 downloader->saveWebSeeds(tordir + "webseeds");
1649 ws->setGroupIDs(upload_gid, download_gid); // make sure webseed has proper group ID
1650 }
1651 return ws != nullptr;
1652 }
1653
removeWebSeed(const QUrl & url)1654 bool TorrentControl::removeWebSeed(const QUrl &url)
1655 {
1656 bool ret = downloader->removeWebSeed(url);
1657 if (ret)
1658 downloader->saveWebSeeds(tordir + "webseeds");
1659 return ret;
1660 }
1661
setUserModifiedFileName(const QString & n)1662 void TorrentControl::setUserModifiedFileName(const QString &n)
1663 {
1664 TorrentInterface::setUserModifiedFileName(n);
1665 QString path = getDataDir();
1666 if (!path.endsWith(bt::DirSeparator()))
1667 path += bt::DirSeparator();
1668
1669 cman->changeOutputPath(path + n);
1670 outputdir = stats.output_path = path + n;
1671 istats.custom_output_name = true;
1672 }
1673
downloaded(Uint32 chunk)1674 void TorrentControl::downloaded(Uint32 chunk)
1675 {
1676 chunkDownloaded(this, chunk);
1677 }
1678
moveToCompletedDir()1679 void TorrentControl::moveToCompletedDir()
1680 {
1681 QString outdir = completed_dir;
1682 if (!outdir.endsWith(bt::DirSeparator()))
1683 outdir += bt::DirSeparator();
1684
1685 changeOutputDir(outdir, bt::TorrentInterface::MOVE_FILES);
1686 }
1687
setAllowedToStart(bool on)1688 void TorrentControl::setAllowedToStart(bool on)
1689 {
1690 stats.qm_can_start = on;
1691 if (on && stats.stopped_by_error) {
1692 // clear the error so user can restart the torrent
1693 stats.stopped_by_error = false;
1694 }
1695 saveStats();
1696 }
1697
setQueued(bool queued)1698 void TorrentControl::setQueued(bool queued)
1699 {
1700 stats.queued = queued;
1701 updateStatus();
1702 }
1703
getComments() const1704 QString TorrentControl::getComments() const
1705 {
1706 return tor->getComments();
1707 }
1708
allJobsDone()1709 void TorrentControl::allJobsDone()
1710 {
1711 updateStatus();
1712 // update the QM to be sure
1713 updateQueue();
1714 runningJobsDone(this);
1715 }
1716
setChunkSelector(ChunkSelectorInterface * csel)1717 void TorrentControl::setChunkSelector(ChunkSelectorInterface *csel)
1718 {
1719 if (!downloader)
1720 return;
1721
1722 if (csel)
1723 downloader->setChunkSelector(csel);
1724 else
1725 downloader->setChunkSelector(nullptr);
1726 }
1727
networkUp()1728 void TorrentControl::networkUp()
1729 {
1730 psman->manualUpdate();
1731 pman->killStalePeers();
1732 }
1733
preallocate()1734 bool TorrentControl::preallocate()
1735 {
1736 // only start preallocation if we are allowed by the settings
1737 if (Cache::preallocationEnabled() && !cman->haveAllChunks()) {
1738 Out(SYS_GEN | LOG_NOTICE) << "Pre-allocating diskspace" << endl;
1739 stats.running = true;
1740 job_queue->enqueue(new PreallocationJob(cman, this));
1741 updateStatus();
1742 return true;
1743 } else {
1744 prealloc = false;
1745 }
1746
1747 return false;
1748 }
1749
downloadPriorityChanged(TorrentFile * tf,Priority newpriority,Priority oldpriority)1750 void TorrentControl::downloadPriorityChanged(TorrentFile *tf, Priority newpriority, Priority oldpriority)
1751 {
1752 if (cman)
1753 cman->downloadPriorityChanged(tf, newpriority, oldpriority);
1754
1755 // If a file has been reenabled, preallocate it and update stats
1756 if (oldpriority == EXCLUDED) {
1757 prealloc = true;
1758 stats.completed = false;
1759 updateStatus();
1760 updateStats();
1761 // Trigger an update of the queue, so it can be restarted again, if it was auto stopped
1762 updateQueue();
1763 }
1764 }
1765
setSuperSeeding(bool on)1766 void TorrentControl::setSuperSeeding(bool on)
1767 {
1768 if (stats.superseeding == on)
1769 return;
1770
1771 stats.superseeding = on;
1772 if (on) {
1773 if (stats.running && stats.completed)
1774 pman->setSuperSeeding(true, cman->getBitSet());
1775 } else {
1776 pman->setSuperSeeding(false, cman->getBitSet());
1777 }
1778
1779 saveStats();
1780 }
1781
createTorrentFileStream(Uint32 index,bool streaming_mode,QObject * parent)1782 TorrentFileStream::Ptr TorrentControl::createTorrentFileStream(Uint32 index, bool streaming_mode, QObject *parent)
1783 {
1784 if (streaming_mode && !stream.toStrongRef().isNull())
1785 return TorrentFileStream::Ptr(nullptr);
1786
1787 if (stats.multi_file_torrent) {
1788 if (index >= tor->getNumFiles())
1789 return TorrentFileStream::Ptr(nullptr);
1790
1791 TorrentFileStream::Ptr ptr(new TorrentFileStream(this, index, cman, streaming_mode, parent));
1792 if (streaming_mode)
1793 stream = ptr;
1794 return ptr;
1795 } else {
1796 TorrentFileStream::Ptr ptr(new TorrentFileStream(this, cman, streaming_mode, parent));
1797 if (streaming_mode)
1798 stream = ptr;
1799 return ptr;
1800 }
1801 }
1802
1803 }
1804