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