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