1 /*
2 SPDX-FileCopyrightText: 2005 Joris Guisson <joris.guisson@gmail.com>
3 SPDX-License-Identifier: GPL-2.0-or-later
4 */
5
6 #include "queuemanager.h"
7
8 #include <QNetworkConfigurationManager>
9
10 #include <KLocalizedString>
11 #include <KMessageBox>
12
13 #include <algorithm>
14 #include <climits>
15 #include <interfaces/torrentinterface.h>
16 #include <interfaces/trackerslist.h>
17 #include <settings.h>
18 #include <torrent/globals.h>
19 #include <torrent/jobqueue.h>
20 #include <torrent/torrent.h>
21 #include <torrent/torrentcontrol.h>
22 #include <util/error.h>
23 #include <util/fileops.h>
24 #include <util/functions.h>
25 #include <util/log.h>
26 #include <util/sha1hash.h>
27 #include <util/waitjob.h>
28
29 using namespace bt;
30
31 namespace kt
32 {
QueueManager()33 QueueManager::QueueManager()
34 : QObject()
35 {
36 max_downloads = 0;
37 max_seeds = 0; // for testing. Needs to be added to Settings::
38
39 keep_seeding = true; // test. Will be passed from Core
40 suspended_state = false;
41 exiting = false;
42 ordering = false;
43
44 last_stats_sync_permitted = 0;
45
46 QNetworkConfigurationManager *networkConfigurationManager = new QNetworkConfigurationManager(this);
47 connect(networkConfigurationManager, &QNetworkConfigurationManager::onlineStateChanged, this, &QueueManager::onOnlineStateChanged);
48 }
49
~QueueManager()50 QueueManager::~QueueManager()
51 {
52 qDeleteAll(downloads);
53 }
54
append(bt::TorrentInterface * tc)55 void QueueManager::append(bt::TorrentInterface *tc)
56 {
57 downloads.append(tc);
58 connect(tc, &TorrentInterface::diskSpaceLow, this, &QueueManager::onLowDiskSpace);
59 connect(tc, &TorrentInterface::torrentStopped, this, &QueueManager::torrentStopped);
60 connect(tc, &TorrentInterface::updateQueue, this, &QueueManager::orderQueue);
61 }
62
remove(bt::TorrentInterface * tc)63 void QueueManager::remove(bt::TorrentInterface *tc)
64 {
65 suspended_torrents.erase(tc);
66 int index = downloads.indexOf(tc);
67 if (index != -1)
68 downloads.takeAt(index)->deleteLater();
69 }
70
clear()71 void QueueManager::clear()
72 {
73 exiting = true;
74 suspended_torrents.clear();
75 qDeleteAll(downloads);
76 downloads.clear();
77 }
78
startInternal(bt::TorrentInterface * tc)79 TorrentStartResponse QueueManager::startInternal(bt::TorrentInterface *tc)
80 {
81 const TorrentStats &s = tc->getStats();
82
83 if (!s.completed && !checkDiskSpace(tc, false))
84 return bt::NOT_ENOUGH_DISKSPACE;
85 else if (s.completed && !checkLimits(tc, false))
86 return bt::MAX_SHARE_RATIO_REACHED;
87
88 Out(SYS_GEN | LOG_NOTICE) << "Starting download " << s.torrent_name << endl;
89 startSafely(tc);
90 return START_OK;
91 }
92
checkLimits(TorrentInterface * tc,bool interactive)93 bool QueueManager::checkLimits(TorrentInterface *tc, bool interactive)
94 {
95 QString msg;
96 const TorrentStats &s = tc->getStats();
97 bool max_ratio_reached = tc->overMaxRatio();
98 bool max_seed_time_reached = tc->overMaxSeedTime();
99
100 if (max_ratio_reached && max_seed_time_reached)
101 msg = i18n("The torrent \"%1\" has reached its maximum share ratio and its maximum seed time. Ignore the limit and start seeding anyway?",
102 s.torrent_name);
103 else if (max_ratio_reached && !max_seed_time_reached)
104 msg = i18n("The torrent \"%1\" has reached its maximum share ratio. Ignore the limit and start seeding anyway?", s.torrent_name);
105 else if (max_seed_time_reached && !max_ratio_reached)
106 msg = i18n("The torrent \"%1\" has reached its maximum seed time. Ignore the limit and start seeding anyway?", s.torrent_name);
107 else
108 return true;
109
110 if (interactive && KMessageBox::questionYesNo(nullptr, msg, i18n("Limits reached.")) == KMessageBox::Yes) {
111 if (max_ratio_reached)
112 tc->setMaxShareRatio(0.00f);
113 if (max_seed_time_reached)
114 tc->setMaxSeedTime(0.0f);
115 return true;
116 }
117
118 return false;
119 }
120
checkDiskSpace(TorrentInterface * tc,bool interactive)121 bool QueueManager::checkDiskSpace(TorrentInterface *tc, bool interactive)
122 {
123 if (tc->checkDiskSpace(false))
124 return true;
125
126 // we're short!
127 switch (Settings::startDownloadsOnLowDiskSpace()) {
128 case 0: // don't start!
129 return false;
130 case 1: { // ask user
131 const TorrentStats &s = tc->getStats();
132 QString msg = i18n(
133 "You don't have enough disk space to download this torrent. "
134 "Are you sure you want to continue?");
135
136 QString caption = i18n("Insufficient disk space for %1", s.torrent_name);
137 if (!interactive || KMessageBox::questionYesNo(nullptr, msg, caption) == KMessageBox::No)
138 return false;
139 else
140 break;
141 }
142 case 2: // force start
143 break;
144 }
145
146 return true;
147 }
148
start(bt::TorrentInterface * tc)149 TorrentStartResponse QueueManager::start(bt::TorrentInterface *tc)
150 {
151 if (tc->getJobQueue()->runningJobs()) {
152 tc->setAllowedToStart(true);
153 return BUSY_WITH_JOB;
154 }
155
156 const TorrentStats &s = tc->getStats();
157 if (!s.completed && !checkDiskSpace(tc, true)) {
158 return bt::NOT_ENOUGH_DISKSPACE;
159 } else if (s.completed && !checkLimits(tc, true)) {
160 return bt::MAX_SHARE_RATIO_REACHED;
161 }
162
163 if (!enabled()) {
164 return startInternal(tc);
165 } else {
166 tc->setAllowedToStart(true);
167 orderQueue();
168 return START_OK;
169 }
170 }
171
stop(bt::TorrentInterface * tc)172 void QueueManager::stop(bt::TorrentInterface *tc)
173 {
174 if (tc->getJobQueue()->runningJobs())
175 return;
176
177 const TorrentStats &s = tc->getStats();
178 if (enabled())
179 tc->setAllowedToStart(false);
180
181 if (s.running)
182 stopSafely(tc);
183 else
184 tc->setQueued(false);
185 }
186
stop(QList<bt::TorrentInterface * > & todo)187 void QueueManager::stop(QList<bt::TorrentInterface *> &todo)
188 {
189 ordering = true;
190 for (bt::TorrentInterface *tc : qAsConst(todo)) {
191 stop(tc);
192 }
193 ordering = false;
194 if (enabled())
195 orderQueue();
196 }
197
checkDiskSpace(QList<bt::TorrentInterface * > & todo)198 void QueueManager::checkDiskSpace(QList<bt::TorrentInterface *> &todo)
199 {
200 // first see if we need to ask the user to start torrents when diskspace is low
201 if (Settings::startDownloadsOnLowDiskSpace() == 2) {
202 QStringList names;
203 QList<bt::TorrentInterface *> tmp;
204 for (bt::TorrentInterface *tc : qAsConst(todo)) {
205 const TorrentStats &s = tc->getStats();
206 if (!s.completed && !tc->checkDiskSpace(false)) {
207 names.append(s.torrent_name);
208 tmp.append(tc);
209 }
210 }
211
212 if (tmp.count() > 0) {
213 if (KMessageBox::questionYesNoList(nullptr, i18n("Not enough disk space for the following torrents. Do you want to start them anyway?"), names)
214 == KMessageBox::No) {
215 for (bt::TorrentInterface *tc : qAsConst(tmp))
216 todo.removeAll(tc);
217 }
218 }
219 }
220 // if the policy is to not start, remove torrents from todo list if diskspace is low
221 else if (Settings::startDownloadsOnLowDiskSpace() == 0) {
222 QList<bt::TorrentInterface *>::iterator i = todo.begin();
223 while (i != todo.end()) {
224 bt::TorrentInterface *tc = *i;
225 const TorrentStats &s = tc->getStats();
226 if (!s.completed && !tc->checkDiskSpace(false))
227 i = todo.erase(i);
228 else
229 i++;
230 }
231 }
232 }
233
checkMaxSeedTime(QList<bt::TorrentInterface * > & todo)234 void QueueManager::checkMaxSeedTime(QList<bt::TorrentInterface *> &todo)
235 {
236 QStringList names;
237 QList<bt::TorrentInterface *> tmp;
238 for (bt::TorrentInterface *tc : qAsConst(todo)) {
239 const TorrentStats &s = tc->getStats();
240 if (s.completed && tc->overMaxSeedTime()) {
241 names.append(s.torrent_name);
242 tmp.append(tc);
243 }
244 }
245
246 if (tmp.count() > 0) {
247 if (KMessageBox::questionYesNoList(nullptr,
248 i18n("The following torrents have reached their maximum seed time. Do you want to start them anyway?"),
249 names)
250 == KMessageBox::No) {
251 for (bt::TorrentInterface *tc : qAsConst(tmp))
252 todo.removeAll(tc);
253 } else {
254 for (bt::TorrentInterface *tc : qAsConst(tmp))
255 tc->setMaxSeedTime(0.0f);
256 }
257 }
258 }
259
checkMaxRatio(QList<bt::TorrentInterface * > & todo)260 void QueueManager::checkMaxRatio(QList<bt::TorrentInterface *> &todo)
261 {
262 QStringList names;
263 QList<bt::TorrentInterface *> tmp;
264 for (bt::TorrentInterface *tc : qAsConst(todo)) {
265 const TorrentStats &s = tc->getStats();
266 if (s.completed && tc->overMaxRatio()) {
267 names.append(s.torrent_name);
268 tmp.append(tc);
269 }
270 }
271
272 if (tmp.count() > 0) {
273 if (KMessageBox::questionYesNoList(nullptr,
274 i18n("The following torrents have reached their maximum share ratio. Do you want to start them anyway?"),
275 names)
276 == KMessageBox::No) {
277 for (bt::TorrentInterface *tc : qAsConst(tmp))
278 todo.removeAll(tc);
279 } else {
280 for (bt::TorrentInterface *tc : qAsConst(tmp))
281 tc->setMaxShareRatio(0.0f);
282 }
283 }
284 }
285
start(QList<bt::TorrentInterface * > & todo)286 void QueueManager::start(QList<bt::TorrentInterface *> &todo)
287 {
288 if (todo.count() == 0)
289 return;
290
291 // check diskspace stuff
292 checkDiskSpace(todo);
293 if (todo.count() == 0)
294 return;
295
296 checkMaxSeedTime(todo);
297 if (todo.count() == 0)
298 return;
299
300 checkMaxRatio(todo);
301 if (todo.count() == 0)
302 return;
303
304 // start what is left
305 for (bt::TorrentInterface *tc : qAsConst(todo)) {
306 const TorrentStats &s = tc->getStats();
307 if (s.running)
308 continue;
309
310 if (tc->getJobQueue()->runningJobs())
311 continue;
312
313 if (enabled())
314 tc->setAllowedToStart(true);
315 else
316 startSafely(tc);
317 }
318
319 if (enabled())
320 orderQueue();
321 }
322
startAll()323 void QueueManager::startAll()
324 {
325 if (enabled()) {
326 for (bt::TorrentInterface *tc : qAsConst(downloads))
327 tc->setAllowedToStart(true);
328
329 orderQueue();
330 } else {
331 // first get the list of torrents which need to be started
332 QList<bt::TorrentInterface *> todo;
333 for (bt::TorrentInterface *tc : qAsConst(downloads)) {
334 const TorrentStats &s = tc->getStats();
335 if (s.running)
336 continue;
337
338 if (tc->getJobQueue()->runningJobs())
339 continue;
340
341 todo.append(tc);
342 }
343
344 start(todo);
345 }
346 }
347
stopAll()348 void QueueManager::stopAll()
349 {
350 stop(downloads);
351 }
352
startAutoStartTorrents()353 void QueueManager::startAutoStartTorrents()
354 {
355 if (enabled() || suspended_state)
356 return;
357
358 // first get the list of torrents which need to be started
359 QList<bt::TorrentInterface *> todo;
360 for (bt::TorrentInterface *tc : qAsConst(downloads)) {
361 const TorrentStats &s = tc->getStats();
362 if (s.running || tc->getJobQueue()->runningJobs() || !s.autostart)
363 continue;
364
365 todo.append(tc);
366 }
367
368 start(todo);
369 }
370
onExit(WaitJob * wjob)371 void QueueManager::onExit(WaitJob *wjob)
372 {
373 exiting = true;
374 QList<bt::TorrentInterface *>::iterator i = downloads.begin();
375 while (i != downloads.end()) {
376 bt::TorrentInterface *tc = *i;
377 if (tc->getStats().running) {
378 stopSafely(tc, wjob);
379 }
380 i++;
381 }
382 }
383
startNext()384 void QueueManager::startNext()
385 {
386 orderQueue();
387 }
388
countDownloads()389 int QueueManager::countDownloads()
390 {
391 return getNumRunning(DOWNLOADS);
392 }
393
countSeeds()394 int QueueManager::countSeeds()
395 {
396 return getNumRunning(SEEDS);
397 }
398
getNumRunning(Flags flags)399 int QueueManager::getNumRunning(Flags flags)
400 {
401 int nr = 0;
402 QList<TorrentInterface *>::const_iterator i = downloads.constBegin();
403 while (i != downloads.constEnd()) {
404 const TorrentInterface *tc = *i;
405 const TorrentStats &s = tc->getStats();
406
407 if (s.running) {
408 if (flags == ALL || (flags == DOWNLOADS && !s.completed) || (flags == SEEDS && s.completed))
409 nr++;
410 }
411 i++;
412 }
413 return nr;
414 }
415
getTorrent(Uint32 idx) const416 const bt::TorrentInterface *QueueManager::getTorrent(Uint32 idx) const
417 {
418 if (idx >= (Uint32)downloads.count())
419 return nullptr;
420 else
421 return downloads[idx];
422 }
423
getTorrent(bt::Uint32 idx)424 bt::TorrentInterface *QueueManager::getTorrent(bt::Uint32 idx)
425 {
426 if (idx >= (Uint32)downloads.count())
427 return nullptr;
428 else
429 return downloads[idx];
430 }
431
begin()432 QList<bt::TorrentInterface *>::iterator QueueManager::begin()
433 {
434 return downloads.begin();
435 }
436
end()437 QList<bt::TorrentInterface *>::iterator QueueManager::end()
438 {
439 return downloads.end();
440 }
441
begin() const442 QList<bt::TorrentInterface *>::const_iterator QueueManager::begin() const
443 {
444 return downloads.cbegin();
445 }
446
end() const447 QList<bt::TorrentInterface *>::const_iterator QueueManager::end() const
448 {
449 return downloads.cend();
450 }
451
setMaxDownloads(int m)452 void QueueManager::setMaxDownloads(int m)
453 {
454 max_downloads = m;
455 }
456
onLowDiskSpace(bt::TorrentInterface * tc,bool toStop)457 void QueueManager::onLowDiskSpace(bt::TorrentInterface *tc, bool toStop)
458 {
459 if (toStop) {
460 stopSafely(tc);
461 if (enabled()) {
462 tc->setAllowedToStart(false);
463 orderQueue();
464 }
465 }
466
467 // then emit the signal to inform trayicon to show passive popup
468 Q_EMIT lowDiskSpace(tc, toStop);
469 }
470
setMaxSeeds(int m)471 void QueueManager::setMaxSeeds(int m)
472 {
473 max_seeds = m;
474 }
475
setKeepSeeding(bool ks)476 void QueueManager::setKeepSeeding(bool ks)
477 {
478 keep_seeding = ks;
479 }
480
alreadyLoaded(const bt::SHA1Hash & ih) const481 bool QueueManager::alreadyLoaded(const bt::SHA1Hash &ih) const
482 {
483 for (const bt::TorrentInterface *tor : qAsConst(downloads)) {
484 if (tor->getInfoHash() == ih)
485 return true;
486 }
487 return false;
488 }
489
mergeAnnounceList(const bt::SHA1Hash & ih,const TrackerTier * trk)490 void QueueManager::mergeAnnounceList(const bt::SHA1Hash &ih, const TrackerTier *trk)
491 {
492 for (bt::TorrentInterface *tor : qAsConst(downloads)) {
493 if (tor->getInfoHash() == ih) {
494 TrackersList *ta = tor->getTrackersList();
495 const int cnt = ta->getTrackers().count();
496 ta->merge(trk);
497 if (cnt < ta->getTrackers().count()) {
498 // new trackers were added
499 // do "Manual Announce" for this torrent
500 if (tor->getStats().running) {
501 tor->updateTracker();
502 }
503 }
504 return;
505 }
506 }
507 }
508
permitStatsSync(TorrentControl * tc)509 bool QueueManager::permitStatsSync(TorrentControl *tc)
510 {
511 // we want to assure that minimum time interval delay is happen
512 // before next TorrentControl dumps its State to the file
513
514 // if you have more than 500 running torrents it's feasible to
515 // increase the period to avoid too small interval value
516 const TimeStamp max_period = (50 * 60 * 1000) * (downloads.size() / 500 + 1);
517
518 if (tc->getStatsSyncElapsedTime() >= max_period) {
519 const bt::TimeStamp now = Now();
520 const bt::TimeStamp interval = max_period / downloads.size();
521 if (now - last_stats_sync_permitted > interval) {
522 last_stats_sync_permitted = now;
523 return true;
524 }
525 }
526 return false;
527 }
528
orderQueue()529 void QueueManager::orderQueue()
530 {
531 if (ordering || !downloads.count() || exiting)
532 return;
533
534 Q_EMIT orderingQueue();
535
536 downloads.sort(); // sort downloads, even when suspended so that the QM widget is updated
537 if (Settings::manuallyControlTorrents() || suspended_state) {
538 Q_EMIT queueOrdered();
539 return;
540 }
541
542 RecursiveEntryGuard guard(&ordering); // make sure that recursive entering of this function is not possible
543
544 QueuePtrList download_queue;
545 QueuePtrList seed_queue;
546
547 for (TorrentInterface *tc : qAsConst(downloads)) {
548 const TorrentStats &s = tc->getStats();
549 if (s.running || (tc->isAllowedToStart() && !s.stopped_by_error && !tc->getJobQueue()->runningJobs())) {
550 if (s.completed) {
551 if (s.running || (!tc->overMaxRatio() && !tc->overMaxSeedTime()))
552 seed_queue.append(tc);
553 } else
554 download_queue.append(tc);
555 }
556 }
557
558 int num_running = 0;
559 for (bt::TorrentInterface *tc : qAsConst(download_queue)) {
560 const TorrentStats &s = tc->getStats();
561
562 if (num_running < max_downloads || max_downloads == 0) {
563 if (!s.running) {
564 Out(SYS_GEN | LOG_DEBUG) << "QM Starting: " << s.torrent_name << endl;
565 if (startInternal(tc) == bt::START_OK)
566 num_running++;
567 } else
568 num_running++;
569 } else {
570 if (s.running) {
571 Out(SYS_GEN | LOG_DEBUG) << "QM Stopping: " << s.torrent_name << endl;
572 stopSafely(tc);
573 }
574 tc->setQueued(true);
575 }
576 }
577
578 num_running = 0;
579 for (bt::TorrentInterface *tc : qAsConst(seed_queue)) {
580 const TorrentStats &s = tc->getStats();
581 if (num_running < max_seeds || max_seeds == 0) {
582 if (!s.running) {
583 Out(SYS_GEN | LOG_DEBUG) << "QM Starting: " << s.torrent_name << endl;
584 if (startInternal(tc) == bt::START_OK)
585 num_running++;
586 } else
587 num_running++;
588 } else {
589 if (s.running) {
590 Out(SYS_GEN | LOG_DEBUG) << "QM Stopping: " << s.torrent_name << endl;
591 stopSafely(tc);
592 }
593 tc->setQueued(true);
594 }
595 }
596
597 Q_EMIT queueOrdered();
598 }
599
torrentFinished(bt::TorrentInterface * tc)600 void QueueManager::torrentFinished(bt::TorrentInterface *tc)
601 {
602 if (!keep_seeding) {
603 if (enabled())
604 tc->setAllowedToStart(false);
605
606 stopSafely(tc);
607 }
608
609 orderQueue();
610 }
611
torrentAdded(bt::TorrentInterface * tc,bool start_torrent)612 void QueueManager::torrentAdded(bt::TorrentInterface *tc, bool start_torrent)
613 {
614 if (enabled()) {
615 // new torrents have the lowest priority
616 // so everybody else gets a higher priority
617 for (TorrentInterface *otc : qAsConst(downloads)) {
618 int p = otc->getPriority();
619 otc->setPriority(p + 1);
620 }
621 tc->setAllowedToStart(start_torrent);
622 tc->setPriority(0);
623 rearrangeQueue();
624 orderQueue();
625 } else {
626 if (start_torrent)
627 start(tc);
628 }
629 }
630
torrentRemoved(bt::TorrentInterface * tc)631 void QueueManager::torrentRemoved(bt::TorrentInterface *tc)
632 {
633 remove(tc);
634 rearrangeQueue();
635 orderQueue();
636 }
637
torrentsRemoved(QList<bt::TorrentInterface * > & tors)638 void QueueManager::torrentsRemoved(QList<bt::TorrentInterface *> &tors)
639 {
640 for (bt::TorrentInterface *tc : qAsConst(tors))
641 remove(tc);
642 rearrangeQueue();
643 orderQueue();
644 }
645
setSuspendedState(bool suspend)646 void QueueManager::setSuspendedState(bool suspend)
647 {
648 if (suspended_state == suspend)
649 return;
650
651 suspended_state = suspend;
652 if (!suspend) {
653 UpdateCurrentTime();
654 std::set<bt::TorrentInterface *>::iterator it = suspended_torrents.begin();
655 while (it != suspended_torrents.end()) {
656 TorrentInterface *tc = *it;
657 startSafely(tc);
658 it++;
659 }
660
661 suspended_torrents.clear();
662 orderQueue();
663 } else {
664 for (TorrentInterface *tc : qAsConst(downloads)) {
665 const TorrentStats &s = tc->getStats();
666 if (s.running) {
667 suspended_torrents.insert(tc);
668 stopSafely(tc);
669 }
670 }
671 }
672 Q_EMIT suspendStateChanged(suspended_state);
673 }
674
rearrangeQueue()675 void QueueManager::rearrangeQueue()
676 {
677 downloads.sort();
678 reindexQueue();
679 }
680
startSafely(bt::TorrentInterface * tc)681 void QueueManager::startSafely(bt::TorrentInterface *tc)
682 {
683 try {
684 tc->start();
685 } catch (bt::Error &err) {
686 const TorrentStats &s = tc->getStats();
687 QString msg = i18n("Error starting torrent %1: %2", s.torrent_name, err.toString());
688 KMessageBox::error(nullptr, msg, i18n("Error"));
689 }
690 }
691
stopSafely(bt::TorrentInterface * tc,WaitJob * wjob)692 void QueueManager::stopSafely(bt::TorrentInterface *tc, WaitJob *wjob)
693 {
694 try {
695 tc->stop(wjob);
696 } catch (bt::Error &err) {
697 const TorrentStats &s = tc->getStats();
698 QString msg = i18n("Error stopping torrent %1: %2", s.torrent_name, err.toString());
699 KMessageBox::error(nullptr, msg, i18n("Error"));
700 }
701 }
702
torrentStopped(bt::TorrentInterface *)703 void QueueManager::torrentStopped(bt::TorrentInterface *)
704 {
705 orderQueue();
706 }
707
IsStalled(bt::TorrentInterface * tc,bt::TimeStamp now,bt::Uint32 min_stall_time)708 static bool IsStalled(bt::TorrentInterface *tc, bt::TimeStamp now, bt::Uint32 min_stall_time)
709 {
710 bt::Int64 stalled_time = 0;
711 if (tc->getStats().completed)
712 stalled_time = (now - tc->getStats().last_upload_activity_time) / 1000;
713 else
714 stalled_time = (now - tc->getStats().last_download_activity_time) / 1000;
715
716 return stalled_time > min_stall_time * 60 && tc->getStats().running;
717 }
718
checkStalledTorrents(bt::TimeStamp now,bt::Uint32 min_stall_time)719 void QueueManager::checkStalledTorrents(bt::TimeStamp now, bt::Uint32 min_stall_time)
720 {
721 if (!enabled())
722 return;
723
724 QueuePtrList newlist;
725 QueuePtrList stalled;
726 bool can_decrease = false;
727
728 // find all stalled ones
729 for (bt::TorrentInterface *tc : qAsConst(downloads)) {
730 if (IsStalled(tc, now, min_stall_time)) {
731 stalled.append(tc);
732 } else {
733 // decreasing makes only sense if there are QM torrents after the stalled ones
734 can_decrease = stalled.count() > 0;
735 newlist.append(tc);
736 }
737 }
738
739 if (stalled.count() == 0 || stalled.count() == downloads.count() || !can_decrease)
740 return;
741
742 for (bt::TorrentInterface *tc : qAsConst(stalled))
743 Out(SYS_GEN | LOG_NOTICE) << "The torrent " << tc->getStats().torrent_name << " has stalled longer than " << min_stall_time
744 << " minutes, decreasing its priority" << endl;
745
746 downloads.clear();
747 downloads += newlist;
748 downloads += stalled;
749 // redo priorities and then order the queue
750 int prio = downloads.count();
751 for (bt::TorrentInterface *tc : qAsConst(downloads)) {
752 tc->setPriority(prio--);
753 }
754 orderQueue();
755 }
756
onOnlineStateChanged(bool isOnline)757 void QueueManager::onOnlineStateChanged(bool isOnline)
758 {
759 if (isOnline) {
760 Out(SYS_GEN | LOG_IMPORTANT) << "Network is up" << endl;
761 // if the network has gone down, longer then 2 minutes
762 // all the connections are probably stale, so tell all
763 // running torrents, that they need to reannounce and kill stale peers
764 if (network_down_time.isValid() && network_down_time.secsTo(QDateTime::currentDateTime()) > 120) {
765 for (bt::TorrentInterface *tc : qAsConst(downloads)) {
766 if (tc->getStats().running)
767 tc->networkUp();
768 }
769 }
770
771 network_down_time = QDateTime();
772 } else {
773 Out(SYS_GEN | LOG_IMPORTANT) << "Network is down" << endl;
774 network_down_time = QDateTime::currentDateTime();
775 }
776 }
777
reindexQueue()778 void QueueManager::reindexQueue()
779 {
780 int prio = downloads.count();
781 // make sure everybody has an unique priority
782 for (bt::TorrentInterface *tc : qAsConst(downloads)) {
783 tc->setPriority(prio--);
784 }
785 }
786
loadState(KSharedConfigPtr cfg)787 void QueueManager::loadState(KSharedConfigPtr cfg)
788 {
789 KConfigGroup g = cfg->group("QueueManager");
790 suspended_state = g.readEntry("suspended", false);
791
792 if (suspended_state) {
793 QStringList info_hash_list = g.readEntry("suspended_torrents", QStringList());
794 for (bt::TorrentInterface *t : qAsConst(downloads)) {
795 if (info_hash_list.contains(t->getInfoHash().toString()))
796 suspended_torrents.insert(t);
797 }
798 }
799 }
800
saveState(KSharedConfigPtr cfg)801 void QueueManager::saveState(KSharedConfigPtr cfg)
802 {
803 KConfigGroup g = cfg->group("QueueManager");
804 g.writeEntry("suspended", suspended_state);
805
806 if (suspended_state) {
807 QStringList info_hash_list;
808 for (bt::TorrentInterface *t : qAsConst(suspended_torrents)) {
809 info_hash_list << t->getInfoHash().toString();
810 }
811 g.writeEntry("suspended_torrents", info_hash_list);
812 }
813 }
814
checkFileConflicts(TorrentInterface * tc,QStringList & conflicting) const815 bool QueueManager::checkFileConflicts(TorrentInterface *tc, QStringList &conflicting) const
816 {
817 conflicting.clear();
818
819 // First get a set off all files of tc
820 QSet<QString> files;
821 if (tc->getStats().multi_file_torrent) {
822 for (bt::Uint32 i = 0; i < tc->getNumFiles(); i++)
823 files.insert(tc->getTorrentFile(i).getPathOnDisk());
824 } else
825 files.insert(tc->getStats().output_path);
826
827 for (bt::TorrentInterface *t : qAsConst(downloads)) {
828 if (t == tc)
829 continue;
830
831 if (t->getStats().multi_file_torrent) {
832 for (bt::Uint32 i = 0; i < t->getNumFiles(); i++) {
833 if (files.contains(t->getTorrentFile(i).getPathOnDisk())) {
834 conflicting.append(t->getDisplayName());
835 break;
836 }
837 }
838 } else {
839 if (files.contains(t->getStats().output_path))
840 conflicting.append(t->getDisplayName());
841 }
842 }
843
844 return !conflicting.isEmpty();
845 }
846
847 /////////////////////////////////////////////////////////////////////////////////////////////
848
QueuePtrList()849 QueuePtrList::QueuePtrList()
850 : QList<bt::TorrentInterface *>()
851 {
852 }
853
~QueuePtrList()854 QueuePtrList::~QueuePtrList()
855 {
856 }
857
sort()858 void QueuePtrList::sort()
859 {
860 std::sort(begin(), end(), QueuePtrList::biggerThan);
861 }
862
biggerThan(bt::TorrentInterface * tc1,bt::TorrentInterface * tc2)863 bool QueuePtrList::biggerThan(bt::TorrentInterface *tc1, bt::TorrentInterface *tc2)
864 {
865 return tc1->getPriority() > tc2->getPriority();
866 }
867
868 }
869