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