1 /*
2     SPDX-FileCopyrightText: 2009 Joris Guisson <joris.guisson@gmail.com>
3 
4     SPDX-License-Identifier: GPL-2.0-or-later
5 */
6 #include "trackermanager.h"
7 #include <QFile>
8 #include <QTextStream>
9 #include <klocalizedstring.h>
10 #include <peer/peermanager.h>
11 #include <torrent/torrent.h>
12 #include <torrent/torrentcontrol.h>
13 #include <tracker/httptracker.h>
14 #include <tracker/tracker.h>
15 #include <tracker/udptracker.h>
16 #include <util/log.h>
17 
18 namespace bt
19 {
TrackerManager(bt::TorrentControl * tor,PeerManager * pman)20 TrackerManager::TrackerManager(bt::TorrentControl *tor, PeerManager *pman)
21     : tor(tor)
22     , pman(pman)
23     , curr(nullptr)
24     , started(false)
25 {
26     trackers.setAutoDelete(true);
27     no_save_custom_trackers = false;
28 
29     const TrackerTier *t = tor->getTorrent().getTrackerList();
30     int tier = 1;
31     while (t) {
32         // add all standard trackers
33         const QList<QUrl> &tr = t->urls;
34         QList<QUrl>::const_iterator i = tr.begin();
35         while (i != tr.end()) {
36             addTracker(*i, false, tier);
37             ++i;
38         }
39 
40         tier++;
41         t = t->next;
42     }
43 
44     // load custom trackers
45     loadCustomURLs();
46     // Load status of each tracker
47     loadTrackerStatus();
48 
49     if (tor->getStats().priv_torrent)
50         switchTracker(selectTracker());
51 }
52 
~TrackerManager()53 TrackerManager::~TrackerManager()
54 {
55     saveCustomURLs();
56     saveTrackerStatus();
57 }
58 
getCurrentTracker() const59 TrackerInterface *TrackerManager::getCurrentTracker() const
60 {
61     return curr;
62 }
63 
noTrackersReachable() const64 bool TrackerManager::noTrackersReachable() const
65 {
66     if (tor->getStats().priv_torrent) {
67         return curr ? curr->trackerStatus() == TRACKER_ERROR : false;
68     } else {
69         int enabled = 0;
70 
71         // If all trackers have an ERROR status, and there is at least one
72         // enabled, we must return true;
73         for (PtrMap<QUrl, Tracker>::const_iterator i = trackers.begin(); i != trackers.end(); ++i) {
74             if (i->second->isEnabled()) {
75                 if (i->second->trackerStatus() != TRACKER_ERROR)
76                     return false;
77                 enabled++;
78             }
79         }
80 
81         return enabled > 0;
82     }
83 }
84 
addTrackerStatusToInfo(const Tracker * tr,TrackersStatusInfo & info)85 void addTrackerStatusToInfo(const Tracker *tr, TrackersStatusInfo &info)
86 {
87     if (tr) {
88         info.trackers_count++;
89 
90         if (tr->trackerStatus() == TRACKER_ERROR) {
91             info.errors++;
92             if (tr->timeOut())
93                 info.timeout_errors++;
94         }
95         if (tr->hasWarning())
96             info.warnings++;
97     }
98 }
99 
getTrackersStatusInfo() const100 TrackersStatusInfo TrackerManager::getTrackersStatusInfo() const
101 {
102     TrackersStatusInfo tsi;
103     tsi.trackers_count = tsi.errors = tsi.timeout_errors = tsi.warnings = 0;
104 
105     if (tor->getStats().priv_torrent) {
106         addTrackerStatusToInfo(curr, tsi);
107         return tsi;
108     }
109 
110     for (PtrMap<QUrl, Tracker>::const_iterator i = trackers.begin(); i != trackers.end(); ++i)
111         if (i->second->isEnabled())
112             addTrackerStatusToInfo(i->second, tsi);
113     return tsi;
114 }
115 
setCurrentTracker(bt::TrackerInterface * t)116 void TrackerManager::setCurrentTracker(bt::TrackerInterface *t)
117 {
118     if (!tor->getStats().priv_torrent)
119         return;
120 
121     Tracker *trk = (Tracker *)t;
122     if (!trk)
123         return;
124 
125     if (curr != trk) {
126         if (curr)
127             curr->stop();
128         switchTracker(trk);
129         trk->start();
130     }
131 }
132 
setCurrentTracker(const QUrl & url)133 void TrackerManager::setCurrentTracker(const QUrl &url)
134 {
135     Tracker *trk = trackers.find(url);
136     if (trk)
137         setCurrentTracker(trk);
138 }
139 
getTrackers()140 QList<TrackerInterface *> TrackerManager::getTrackers()
141 {
142     QList<TrackerInterface *> ret;
143     for (PtrMap<QUrl, Tracker>::const_iterator i = trackers.begin(); i != trackers.end(); ++i) {
144         ret.append(i->second);
145     }
146 
147     return ret;
148 }
149 
addTracker(const QUrl & url,bool custom,int tier)150 TrackerInterface *TrackerManager::addTracker(const QUrl &url, bool custom, int tier)
151 {
152     if (trackers.contains(url))
153         return nullptr;
154 
155     Tracker *trk = nullptr;
156     if (url.scheme() == QLatin1String("udp"))
157         trk = new UDPTracker(url, this, tor->getTorrent().getPeerID(), tier);
158     else if (url.scheme() == QLatin1String("http") || url.scheme() == QLatin1String("https"))
159         trk = new HTTPTracker(url, this, tor->getTorrent().getPeerID(), tier);
160     else
161         return nullptr;
162 
163     addTracker(trk);
164     if (custom) {
165         custom_trackers.append(url);
166         if (!no_save_custom_trackers) {
167             saveCustomURLs();
168             saveTrackerStatus();
169         }
170     }
171 
172     return trk;
173 }
174 
removeTracker(bt::TrackerInterface * t)175 bool TrackerManager::removeTracker(bt::TrackerInterface *t)
176 {
177     return removeTracker(t->trackerURL());
178 }
179 
removeTracker(const QUrl & url)180 bool TrackerManager::removeTracker(const QUrl &url)
181 {
182     if (!custom_trackers.contains(url))
183         return false;
184 
185     custom_trackers.removeAll(url);
186     Tracker *trk = trackers.find(url);
187     if (trk && curr == trk && tor->getStats().priv_torrent) {
188         // do a timed delete on the tracker, so the stop signal
189         // has plenty of time to reach it
190         trk->stop();
191         trk->timedDelete(10 * 1000);
192         trackers.setAutoDelete(false);
193         trackers.erase(url);
194         trackers.setAutoDelete(true);
195 
196         if (trackers.count() > 0) {
197             switchTracker(selectTracker());
198             if (curr)
199                 curr->start();
200         }
201     } else {
202         // just delete if not the current one
203         trackers.erase(url);
204     }
205     saveCustomURLs();
206     return true;
207 }
208 
canRemoveTracker(bt::TrackerInterface * t)209 bool TrackerManager::canRemoveTracker(bt::TrackerInterface *t)
210 {
211     return custom_trackers.contains(t->trackerURL());
212 }
213 
restoreDefault()214 void TrackerManager::restoreDefault()
215 {
216     QList<QUrl>::iterator i = custom_trackers.begin();
217     while (i != custom_trackers.end()) {
218         Tracker *t = trackers.find(*i);
219         if (t) {
220             if (t->isStarted())
221                 t->stop();
222 
223             if (curr == t && tor->getStats().priv_torrent) {
224                 curr = nullptr;
225                 trackers.erase(*i);
226             } else {
227                 trackers.erase(*i);
228             }
229         }
230         ++i;
231     }
232 
233     custom_trackers.clear();
234     saveCustomURLs();
235     if (tor->getStats().priv_torrent && curr == nullptr)
236         switchTracker(selectTracker());
237 }
238 
addTracker(Tracker * trk)239 void TrackerManager::addTracker(Tracker *trk)
240 {
241     trackers.insert(trk->trackerURL(), trk);
242     connect(trk, &Tracker::peersReady, pman, &PeerManager::peerSourceReady);
243     connect(trk, &Tracker::scrapeDone, tor, &TorrentControl::trackerScrapeDone);
244     connect(trk, &Tracker::requestOK, this, &TrackerManager::onTrackerOK);
245     connect(trk, &Tracker::requestFailed, this, &TrackerManager::onTrackerError);
246 }
247 
start()248 void TrackerManager::start()
249 {
250     if (started)
251         return;
252 
253     if (tor->getStats().priv_torrent) {
254         if (!curr) {
255             if (trackers.count() > 0) {
256                 switchTracker(selectTracker());
257                 if (curr)
258                     curr->start();
259             }
260         } else {
261             curr->start();
262         }
263     } else {
264         for (PtrMap<QUrl, Tracker>::const_iterator i = trackers.begin(); i != trackers.end(); ++i) {
265             if (i->second->isEnabled())
266                 i->second->start();
267         }
268     }
269 
270     started = true;
271 }
272 
stop(bt::WaitJob * wjob)273 void TrackerManager::stop(bt::WaitJob *wjob)
274 {
275     if (!started)
276         return;
277 
278     started = false;
279     if (tor->getStats().priv_torrent) {
280         if (curr)
281             curr->stop(wjob);
282 
283         for (PtrMap<QUrl, Tracker>::const_iterator i = trackers.begin(); i != trackers.end(); ++i) {
284             i->second->reset();
285         }
286     } else {
287         for (PtrMap<QUrl, Tracker>::const_iterator i = trackers.begin(); i != trackers.end(); ++i) {
288             i->second->stop(wjob);
289             i->second->reset();
290         }
291     }
292 }
293 
completed()294 void TrackerManager::completed()
295 {
296     if (tor->getStats().priv_torrent) {
297         if (curr)
298             curr->completed();
299     } else {
300         for (PtrMap<QUrl, Tracker>::const_iterator i = trackers.begin(); i != trackers.end(); ++i) {
301             i->second->completed();
302         }
303     }
304 }
305 
scrape()306 void TrackerManager::scrape()
307 {
308     for (PtrMap<QUrl, Tracker>::const_iterator i = trackers.begin(); i != trackers.end(); ++i) {
309         i->second->scrape();
310     }
311 }
312 
manualUpdate()313 void TrackerManager::manualUpdate()
314 {
315     if (tor->getStats().priv_torrent) {
316         if (curr) {
317             curr->manualUpdate();
318         }
319     } else {
320         for (PtrMap<QUrl, Tracker>::const_iterator i = trackers.begin(); i != trackers.end(); ++i) {
321             if (i->second->isEnabled())
322                 i->second->manualUpdate();
323         }
324     }
325 }
326 
saveCustomURLs()327 void TrackerManager::saveCustomURLs()
328 {
329     QString trackers_file = tor->getTorDir() + QLatin1String("trackers");
330     QFile file(trackers_file);
331     if (!file.open(QIODevice::WriteOnly))
332         return;
333 
334     QTextStream stream(&file);
335     for (const QUrl &url : qAsConst(custom_trackers))
336         stream << url.toDisplayString() << Qt::endl;
337 }
338 
loadCustomURLs()339 void TrackerManager::loadCustomURLs()
340 {
341     QString trackers_file = tor->getTorDir() + QLatin1String("trackers");
342     QFile file(trackers_file);
343     if (!file.open(QIODevice::ReadOnly))
344         return;
345 
346     no_save_custom_trackers = true;
347     QTextStream stream(&file);
348     while (!stream.atEnd()) {
349         addTracker(QUrl(stream.readLine()), true);
350     }
351     no_save_custom_trackers = false;
352 }
353 
saveTrackerStatus()354 void TrackerManager::saveTrackerStatus()
355 {
356     QString status_file = tor->getTorDir() + QLatin1String("tracker_status");
357     QFile file(status_file);
358     if (!file.open(QIODevice::WriteOnly))
359         return;
360 
361     QTextStream stream(&file);
362     PtrMap<QUrl, Tracker>::iterator i = trackers.begin();
363     while (i != trackers.end()) {
364         QUrl url = i->first;
365         Tracker *trk = i->second;
366 
367         stream << (trk->isEnabled() ? "1:" : "0:") << url.toDisplayString() << Qt::endl;
368         ++i;
369     }
370 }
371 
loadTrackerStatus()372 void TrackerManager::loadTrackerStatus()
373 {
374     QString status_file = tor->getTorDir() + QLatin1String("tracker_status");
375     QFile file(status_file);
376     if (!file.open(QIODevice::ReadOnly))
377         return;
378 
379     QTextStream stream(&file);
380     while (!stream.atEnd()) {
381         QString line = stream.readLine();
382         if (line.size() < 2)
383             continue;
384 
385         if (line[0] == '0') {
386             Tracker *trk = trackers.find(QUrl(line.mid(2))); // url starts at the second char
387             if (trk)
388                 trk->setEnabled(false);
389         }
390     }
391 }
392 
selectTracker()393 Tracker *TrackerManager::selectTracker()
394 {
395     Tracker *n = nullptr;
396     PtrMap<QUrl, Tracker>::iterator i = trackers.begin();
397     while (i != trackers.end()) {
398         Tracker *t = i->second;
399         if (t->isEnabled()) {
400             if (!n)
401                 n = t;
402             else if (t->failureCount() < n->failureCount())
403                 n = t;
404             else if (t->failureCount() == n->failureCount())
405                 n = t->getTier() < n->getTier() ? t : n;
406         }
407         ++i;
408     }
409 
410     if (n) {
411         Out(SYS_TRK | LOG_DEBUG) << "Selected tracker " << n->trackerURL().toString() << " (tier = " << n->getTier() << ")" << endl;
412     }
413 
414     return n;
415 }
416 
onTrackerError(const QString & err)417 void TrackerManager::onTrackerError(const QString &err)
418 {
419     Q_UNUSED(err);
420     if (!started)
421         return;
422 
423     if (!tor->getStats().priv_torrent) {
424         Tracker *trk = (Tracker *)sender();
425         trk->handleFailure();
426     } else {
427         Tracker *trk = (Tracker *)sender();
428         if (trk == curr) {
429             // select an other tracker
430             trk = selectTracker();
431             if (trk == curr) { // if we can't find another handle the failure
432                 trk->handleFailure();
433             } else {
434                 curr->stop();
435                 switchTracker(trk);
436                 if (curr->failureCount() > 0)
437                     curr->handleFailure();
438                 else
439                     curr->start();
440             }
441         } else
442             trk->handleFailure();
443     }
444 }
445 
onTrackerOK()446 void TrackerManager::onTrackerOK()
447 {
448     Tracker *tracker = (Tracker *)sender();
449     if (tracker->isStarted())
450         tracker->scrape();
451 }
452 
updateCurrentManually()453 void TrackerManager::updateCurrentManually()
454 {
455     if (!curr)
456         return;
457 
458     curr->manualUpdate();
459 }
460 
switchTracker(Tracker * trk)461 void TrackerManager::switchTracker(Tracker *trk)
462 {
463     if (curr == trk)
464         return;
465 
466     curr = trk;
467     if (curr)
468         Out(SYS_TRK | LOG_NOTICE) << "Switching to tracker " << trk->trackerURL() << endl;
469 }
470 
getNumSeeders() const471 Uint32 TrackerManager::getNumSeeders() const
472 {
473     if (tor->getStats().priv_torrent) {
474         return curr && curr->getNumSeeders() > 0 ? curr->getNumSeeders() : 0;
475     }
476 
477     int r = 0;
478     for (PtrMap<QUrl, Tracker>::const_iterator i = trackers.begin(); i != trackers.end(); ++i) {
479         int v = i->second->getNumSeeders();
480         if (v > r)
481             r = v;
482     }
483 
484     return r;
485 }
486 
getNumLeechers() const487 Uint32 TrackerManager::getNumLeechers() const
488 {
489     if (tor->getStats().priv_torrent)
490         return curr && curr->getNumLeechers() > 0 ? curr->getNumLeechers() : 0;
491 
492     int r = 0;
493     for (PtrMap<QUrl, Tracker>::const_iterator i = trackers.begin(); i != trackers.end(); ++i) {
494         int v = i->second->getNumLeechers();
495         if (v > r)
496             r = v;
497     }
498 
499     return r;
500 }
501 
setTrackerEnabled(const QUrl & url,bool enabled)502 void TrackerManager::setTrackerEnabled(const QUrl &url, bool enabled)
503 {
504     Tracker *trk = trackers.find(url);
505     if (!trk)
506         return;
507 
508     trk->setEnabled(enabled);
509     if (!enabled) {
510         trk->stop();
511         if (curr == trk) { // if the current tracker is disabled, switch to another one
512             switchTracker(selectTracker());
513             if (curr)
514                 curr->start();
515         }
516     } else {
517         // start tracker if necessary
518         if (!tor->getStats().priv_torrent && started)
519             trk->start();
520     }
521 
522     saveTrackerStatus();
523 }
524 
bytesDownloaded() const525 Uint64 TrackerManager::bytesDownloaded() const
526 {
527     const TorrentStats &s = tor->getStats();
528     if (s.imported_bytes > s.bytes_downloaded)
529         return 0;
530     else
531         return s.bytes_downloaded - s.imported_bytes;
532 }
533 
bytesUploaded() const534 Uint64 TrackerManager::bytesUploaded() const
535 {
536     return tor->getStats().bytes_uploaded;
537 }
538 
bytesLeft() const539 Uint64 TrackerManager::bytesLeft() const
540 {
541     return tor->getStats().bytes_left;
542 }
543 
infoHash() const544 const bt::SHA1Hash &TrackerManager::infoHash() const
545 {
546     return tor->getInfoHash();
547 }
548 
isPartialSeed() const549 bool TrackerManager::isPartialSeed() const
550 {
551     return pman->isPartialSeed();
552 }
553 
554 }
555