summaryrefslogtreecommitdiffstats
path: root/libktorrent/torrent/queuemanager.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'libktorrent/torrent/queuemanager.cpp')
-rw-r--r--libktorrent/torrent/queuemanager.cpp811
1 files changed, 811 insertions, 0 deletions
diff --git a/libktorrent/torrent/queuemanager.cpp b/libktorrent/torrent/queuemanager.cpp
new file mode 100644
index 0000000..4135f8f
--- /dev/null
+++ b/libktorrent/torrent/queuemanager.cpp
@@ -0,0 +1,811 @@
+/***************************************************************************
+ * Copyright (C) 2005 by Joris Guisson *
+ * joris.guisson@gmail.com *
+ * ivasic@gmail.com *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the *
+ * Free Software Foundation, Inc., *
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
+ ***************************************************************************/
+#include "queuemanager.h"
+
+#include <qstring.h>
+#include <kmessagebox.h>
+#include <klocale.h>
+#include <util/log.h>
+#include <util/error.h>
+#include <util/sha1hash.h>
+#include <util/waitjob.h>
+#include <util/fileops.h>
+#include <torrent/globals.h>
+#include <torrent/torrent.h>
+#include <torrent/torrentcontrol.h>
+#include <interfaces/torrentinterface.h>
+#include <interfaces/trackerslist.h>
+#include <settings.h>
+
+
+using namespace kt;
+
+namespace bt
+{
+
+ QueueManager::QueueManager() : QObject(),exiting(false)
+ {
+ downloads.setAutoDelete(true);
+ max_downloads = 0;
+ max_seeds = 0; //for testing. Needs to be added to Settings::
+
+ keep_seeding = true; //test. Will be passed from Core
+ paused_state = false;
+ }
+
+
+ QueueManager::~QueueManager()
+ {}
+
+ void QueueManager::append(kt::TorrentInterface* tc)
+ {
+ downloads.append(tc);
+ downloads.sort();
+
+ connect(tc, SIGNAL(diskSpaceLow(kt::TorrentInterface*, bool)), this, SLOT(onLowDiskSpace(kt::TorrentInterface*, bool)));
+ connect(tc, SIGNAL(torrentStopped(kt::TorrentInterface*)), this, SLOT(torrentStopped(kt::TorrentInterface*)));
+ }
+
+ void QueueManager::remove(kt::TorrentInterface* tc)
+ {
+ paused_torrents.erase(tc);
+
+ int index = downloads.findRef(tc);
+
+ if (index != -1)
+ downloads.remove(index);
+ else
+ Out(SYS_GEN | LOG_IMPORTANT) << "Could not delete removed torrent control." << endl;
+ }
+
+ void QueueManager::clear()
+ {
+ Uint32 nd = downloads.count();
+
+ paused_torrents.clear();
+ downloads.clear();
+
+ // wait for a second to allow all http jobs to send the stopped event
+ if (nd > 0)
+ SynchronousWait(1000);
+ }
+
+ kt::TorrentStartResponse QueueManager::start(kt::TorrentInterface* tc, bool user)
+ {
+ const TorrentStats & s = tc->getStats();
+
+ bool start_tc = user;
+
+ bool check_done = false;
+
+ if (tc->isCheckingData(check_done) && !check_done)
+ return kt::BUSY_WITH_DATA_CHECK;
+
+ if (!user)
+ {
+ if (s.completed)
+ start_tc = (max_seeds == 0 || getNumRunning(false, true) < max_seeds);
+ else
+ start_tc = (max_downloads == 0 || getNumRunning(true) < max_downloads);
+ }
+ else
+ {
+ //User started this torrent so make it user controlled
+ tc->setPriority(0);
+ }
+
+ if (start_tc)
+ {
+
+ if (!s.completed) //no need to check diskspace for seeding torrents
+ {
+ //check diskspace
+ bool shortDiskSpace = !tc->checkDiskSpace(false);
+
+ if (shortDiskSpace)
+ {
+ //we're short!
+
+ switch (Settings::startDownloadsOnLowDiskSpace())
+ {
+
+ case 0: //don't start!
+ tc->setPriority(0);
+ return kt::NOT_ENOUGH_DISKSPACE;
+
+ case 1: //ask user
+ if (KMessageBox::questionYesNo(0, i18n("You don't have enough disk space to download this torrent. Are you sure you want to continue?"), i18n("Insufficient disk space for %1").arg(s.torrent_name)) == KMessageBox::No)
+ {
+ tc->setPriority(0);
+ return kt::USER_CANCELED;
+ }
+ else
+ break;
+
+ case 2: //force start
+ break;
+ }
+ }
+ }
+
+ Out(SYS_GEN | LOG_NOTICE) << "Starting download" << endl;
+
+ float ratio = kt::ShareRatio(s);
+
+ float max_ratio = tc->getMaxShareRatio();
+
+ if (s.completed && max_ratio > 0 && ratio >= max_ratio)
+ {
+ if (KMessageBox::questionYesNo(0, i18n("Torrent \"%1\" has reached its maximum share ratio. Ignore the limit and start seeding anyway?").arg(s.torrent_name), i18n("Maximum share ratio limit reached.")) == KMessageBox::Yes)
+ {
+ tc->setMaxShareRatio(0.00f);
+ startSafely(tc);
+ }
+ else
+ return kt::USER_CANCELED;
+ }
+ else
+ startSafely(tc);
+ }
+ else
+ {
+ return kt::QM_LIMITS_REACHED;
+ }
+
+ return kt::START_OK;
+ }
+
+ void QueueManager::stop(kt::TorrentInterface* tc, bool user)
+ {
+ bool check_done = false;
+
+ if (tc->isCheckingData(check_done) && !check_done)
+ return;
+
+ const TorrentStats & s = tc->getStats();
+
+ if (s.running)
+ {
+ stopSafely(tc, user);
+ }
+
+ if (user) //dequeue it
+ tc->setPriority(0);
+ }
+
+ void QueueManager::startall(int type)
+ {
+ QPtrList<kt::TorrentInterface>::iterator i = downloads.begin();
+
+ while (i != downloads.end())
+ {
+ kt::TorrentInterface* tc = *i;
+
+ if (type >= 3)
+ start(tc, true);
+ else
+ {
+ if ((tc->getStats().completed && type == 2) || (!tc->getStats().completed && type == 1) || (type == 3))
+ start(tc, true);
+ }
+
+ i++;
+ }
+ }
+
+ void QueueManager::stopall(int type)
+ {
+ QPtrList<kt::TorrentInterface>::iterator i = downloads.begin();
+
+ while (i != downloads.end())
+ {
+ kt::TorrentInterface* tc = *i;
+
+ const TorrentStats & s = tc->getStats();
+
+ if (tc->getStats().running)
+ {
+ try
+ {
+ if (type >= 3)
+ stopSafely(tc, true);
+ else if ((s.completed && type == 2) || (!s.completed && type == 1))
+ stopSafely(tc, true);
+ }
+ catch (bt::Error & err)
+ {
+ QString msg =
+ i18n("Error stopping torrent %1 : %2")
+ .arg(s.torrent_name).arg(err.toString());
+ KMessageBox::error(0, msg, i18n("Error"));
+ }
+ }
+ else //if torrent is not running but it is queued we need to make it user controlled
+ if ((s.completed && type == 2) || (!s.completed && type == 1) || (type == 3))
+ tc->setPriority(0);
+
+ i++;
+ }
+ }
+
+ void QueueManager::onExit(WaitJob* wjob)
+ {
+ exiting = true;
+ QPtrList<kt::TorrentInterface>::iterator i = downloads.begin();
+
+ while (i != downloads.end())
+ {
+ kt::TorrentInterface* tc = *i;
+
+ if (tc->getStats().running)
+ {
+ stopSafely(tc, false, wjob);
+ }
+
+ i++;
+ }
+ }
+
+ void QueueManager::startNext()
+ {
+ orderQueue();
+ }
+
+ int QueueManager::countDownloads()
+ {
+ int nr = 0;
+ QPtrList<TorrentInterface>::const_iterator i = downloads.begin();
+
+ while (i != downloads.end())
+ {
+ if (!(*i)->getStats().completed)
+ ++nr;
+
+ ++i;
+ }
+
+ return nr;
+ }
+
+ int QueueManager::countSeeds()
+ {
+ int nr = 0;
+ QPtrList<TorrentInterface>::const_iterator i = downloads.begin();
+
+ while (i != downloads.end())
+ {
+ if ((*i)->getStats().completed)
+ ++nr;
+
+ ++i;
+ }
+
+ return nr;
+ }
+
+ int QueueManager::getNumRunning(bool onlyDownload, bool onlySeed)
+ {
+ int nr = 0;
+ // int test = 1;
+ QPtrList<TorrentInterface>::const_iterator i = downloads.begin();
+
+ while (i != downloads.end())
+ {
+ const TorrentInterface* tc = *i;
+
+ const TorrentStats & s = tc->getStats();
+
+ //Out() << "Torrent " << test++ << s.torrent_name << " priority: " << tc->getPriority() << endl;
+ if (s.running)
+ {
+ if (onlyDownload)
+ {
+ if (!s.completed) nr++;
+ }
+ else
+ {
+ if (onlySeed)
+ {
+ if (s.completed) nr++;
+ }
+ else
+ nr++;
+ }
+ }
+
+ i++;
+ }
+
+ // Out() << endl;
+ return nr;
+ }
+
+ int QueueManager::getNumRunning(bool userControlled, bool onlyDownloads, bool onlySeeds)
+ {
+ int nr = 0;
+ // int test = 1;
+ QPtrList<TorrentInterface>::const_iterator i = downloads.begin();
+
+ while (i != downloads.end())
+ {
+ const TorrentInterface* tc = *i;
+
+ const TorrentStats & s = tc->getStats();
+
+ //Out() << "Torrent " << test++ << s.torrent_name << " priority: " << tc->getPriority() << endl;
+ if (s.running)
+ {
+ if (onlyDownloads)
+ {
+ if (!s.completed && (userControlled && s.user_controlled)) nr++;
+ }
+ else
+ {
+ if (onlySeeds)
+ {
+ if (s.completed && (userControlled && s.user_controlled)) nr++;
+ }
+ else
+ if (userControlled && s.user_controlled) nr++;
+ }
+ }
+
+ i++;
+ }
+
+ // Out() << endl;
+ return nr;
+ }
+
+ QPtrList<kt::TorrentInterface>::iterator QueueManager::begin()
+ {
+ return downloads.begin();
+ }
+
+ QPtrList<kt::TorrentInterface>::iterator QueueManager::end()
+ {
+ return downloads.end();
+ }
+
+ void QueueManager::setMaxDownloads(int m)
+ {
+ max_downloads = m;
+ }
+
+ void QueueManager::setMaxSeeds(int m)
+ {
+ max_seeds = m;
+ }
+
+ void QueueManager::setKeepSeeding(bool ks)
+ {
+ keep_seeding = ks;
+ }
+
+ bool QueueManager::allreadyLoaded(const SHA1Hash & ih) const
+ {
+ QPtrList<kt::TorrentInterface>::const_iterator itr = downloads.begin();
+
+ while (itr != downloads.end())
+ {
+ const TorrentControl* tor = (const TorrentControl*)(*itr);
+
+ if (tor->getTorrent().getInfoHash() == ih)
+ return true;
+
+ itr++;
+ }
+
+ return false;
+ }
+
+ void QueueManager::mergeAnnounceList(const SHA1Hash & ih, const TrackerTier* trk)
+
+ {
+ QPtrList<kt::TorrentInterface>::iterator itr = downloads.begin();
+
+ while (itr != downloads.end())
+ {
+ TorrentControl* tor = (TorrentControl*)(*itr);
+
+ if (tor->getTorrent().getInfoHash() == ih)
+ {
+ TrackersList* ta = tor->getTrackersList();
+ ta->merge(trk);
+ return;
+ }
+
+ itr++;
+ }
+ }
+
+ void QueueManager::orderQueue()
+ {
+ if (!downloads.count())
+ return;
+
+ if (paused_state || exiting)
+ return;
+
+ downloads.sort();
+
+ QPtrList<TorrentInterface>::const_iterator it = downloads.begin();
+ QPtrList<TorrentInterface>::const_iterator its = downloads.end();
+
+
+ if (max_downloads != 0 || max_seeds != 0)
+ {
+ bt::QueuePtrList download_queue;
+ bt::QueuePtrList seed_queue;
+
+ int user_downloading = 0;
+ int user_seeding = 0;
+
+ for (; it != downloads.end(); ++it)
+ {
+ TorrentInterface* tc = *it;
+
+ const TorrentStats & s = tc->getStats();
+
+ if (s.running && s.user_controlled)
+ {
+ if (!s.completed)
+ ++user_downloading;
+ else
+ ++user_seeding;
+ }
+
+ if (!s.user_controlled && !tc->isMovingFiles() && !s.stopped_by_error)
+ {
+ if (s.completed)
+ seed_queue.append(tc);
+ else
+ download_queue.append(tc);
+ }
+ }
+
+ int max_qm_downloads = max_downloads - user_downloading;
+
+ int max_qm_seeds = max_seeds - user_seeding;
+
+ //stop all QM started torrents
+
+ for (Uint32 i = max_qm_downloads; i < download_queue.count() && max_downloads; ++i)
+ {
+ TorrentInterface* tc = download_queue.at(i);
+
+ const TorrentStats & s = tc->getStats();
+
+ if (s.running && !s.user_controlled && !s.completed)
+ {
+ Out(SYS_GEN | LOG_DEBUG) << "QM Stopping: " << s.torrent_name << endl;
+ stop(tc);
+ }
+ }
+
+ //stop all QM started torrents
+ for (Uint32 i = max_qm_seeds; i < seed_queue.count() && max_seeds; ++i)
+ {
+ TorrentInterface* tc = seed_queue.at(i);
+
+ const TorrentStats & s = tc->getStats();
+
+ if (s.running && !s.user_controlled && s.completed)
+ {
+ Out(SYS_GEN | LOG_NOTICE) << "QM Stopping: " << s.torrent_name << endl;
+ stop(tc);
+ }
+ }
+
+ //Now start all needed torrents
+ if (max_downloads == 0)
+ max_qm_downloads = download_queue.count();
+
+ if (max_seeds == 0)
+ max_qm_seeds = seed_queue.count();
+
+ int counter = 0;
+
+ for (Uint32 i = 0; counter < max_qm_downloads && i < download_queue.count(); ++i)
+ {
+ TorrentInterface* tc = download_queue.at(i);
+
+ const TorrentStats & s = tc->getStats();
+
+ if (!s.running && !s.completed && !s.user_controlled)
+ {
+ start(tc, false);
+
+ if (tc->getStats().stopped_by_error)
+ {
+ tc->setPriority(0);
+ continue;
+ }
+ }
+
+ ++counter;
+ }
+
+ counter = 0;
+
+ for (Uint32 i = 0; counter < max_qm_seeds && i < seed_queue.count(); ++i)
+ {
+ TorrentInterface* tc = seed_queue.at(i);
+
+ const TorrentStats & s = tc->getStats();
+
+ if (!s.running && s.completed && !s.user_controlled)
+ {
+ start(tc, false);
+
+ if (tc->getStats().stopped_by_error)
+ {
+ tc->setPriority(0);
+ continue;
+ }
+ }
+
+ ++counter;
+ }
+ }
+ else
+ {
+ //no limits at all
+
+ for (it = downloads.begin(); it != downloads.end(); ++it)
+ {
+ TorrentInterface* tc = *it;
+
+ const TorrentStats & s = tc->getStats();
+
+ if (!s.running && !s.user_controlled && !s.stopped_by_error && !tc->isMovingFiles())
+ {
+ start(tc, false);
+ if (tc->getStats().stopped_by_error)
+ tc->setPriority(0);
+ }
+ }
+ }
+
+ }
+
+ void QueueManager::torrentFinished(kt::TorrentInterface* tc)
+ {
+ //dequeue this tc
+ tc->setPriority(0);
+ //make sure the max_seeds is not reached
+// if(max_seeds !=0 && max_seeds < getNumRunning(false,true))
+// tc->stop(true);
+
+ if (keep_seeding)
+ torrentAdded(tc, false, false);
+ else
+ stop(tc,true);
+
+ orderQueue();
+ }
+
+ void QueueManager::torrentAdded(kt::TorrentInterface* tc, bool user, bool start_torrent)
+ {
+ if (!user)
+ {
+ QPtrList<TorrentInterface>::const_iterator it = downloads.begin();
+
+ while (it != downloads.end())
+ {
+ TorrentInterface* _tc = *it;
+ int p = _tc->getPriority();
+
+ if (p == 0)
+ break;
+ else
+ _tc->setPriority(++p);
+
+ ++it;
+ }
+
+ tc->setPriority(1);
+ }
+ else
+ {
+ tc->setPriority(0);
+ if(start_torrent)
+ start(tc, true);
+ }
+
+ orderQueue();
+ }
+
+ void QueueManager::torrentRemoved(kt::TorrentInterface* tc)
+ {
+ remove(tc);
+
+ orderQueue();
+ }
+
+ void QueueManager::setPausedState(bool pause)
+ {
+ paused_state = pause;
+ if (!pause)
+ {
+ std::set<kt::TorrentInterface*>::iterator it = paused_torrents.begin();
+ while (it != paused_torrents.end())
+ {
+ TorrentInterface* tc = *it;
+ startSafely(tc);
+ it++;
+ }
+
+ paused_torrents.clear();
+ orderQueue();
+ }
+ else
+ {
+ QPtrList<TorrentInterface>::const_iterator it = downloads.begin();
+ for (; it != downloads.end(); it++)
+ {
+ TorrentInterface* tc = *it;
+
+ const TorrentStats & s = tc->getStats();
+
+ if (s.running)
+ {
+ paused_torrents.insert(tc);
+ stopSafely(tc, false);
+ }
+ }
+ }
+ }
+
+ void QueueManager::enqueue(kt::TorrentInterface* tc)
+ {
+ //if a seeding torrent reached its maximum share ratio or maximum seed time don't enqueue it...
+ if (tc->getStats().completed && (tc->overMaxRatio() || tc->overMaxSeedTime()))
+ {
+ Out(SYS_GEN | LOG_IMPORTANT) << "Torrent has reached max share ratio or max seed time and cannot be started automatically." << endl;
+ emit queuingNotPossible(tc);
+ return;
+ }
+
+ torrentAdded(tc, false, false);
+ }
+
+ void QueueManager::dequeue(kt::TorrentInterface* tc)
+ {
+ int tp = tc->getPriority();
+ bool completed = tc->getStats().completed;
+ QPtrList<TorrentInterface>::const_iterator it = downloads.begin();
+
+ while (it != downloads.end())
+ {
+ TorrentInterface* _tc = *it;
+ bool _completed = _tc->getStats().completed;
+
+ if (tc == _tc || (_completed != completed))
+ {
+ ++it;
+ continue;
+ }
+
+ int p = _tc->getPriority();
+
+ if (p < tp)
+ break;
+ else
+ _tc->setPriority(--p);
+
+ ++it;
+ }
+
+ tc->setPriority(0);
+
+ orderQueue();
+ }
+
+ void QueueManager::queue(kt::TorrentInterface* tc)
+ {
+ if (tc->getPriority() == 0)
+ enqueue(tc);
+ else
+ dequeue(tc);
+ }
+
+ void QueueManager::startSafely(kt::TorrentInterface* tc)
+ {
+ try
+ {
+ tc->start();
+ }
+ catch (bt::Error & err)
+ {
+ const TorrentStats & s = tc->getStats();
+
+ QString msg =
+ i18n("Error starting torrent %1 : %2")
+ .arg(s.torrent_name).arg(err.toString());
+
+ KMessageBox::error(0, msg, i18n("Error"));
+ }
+ }
+
+ void QueueManager::stopSafely(kt::TorrentInterface* tc, bool user, WaitJob* wjob)
+ {
+ try
+ {
+ tc->stop(user, wjob);
+ }
+ catch (bt::Error & err)
+ {
+ const TorrentStats & s = tc->getStats();
+
+ QString msg =
+ i18n("Error stopping torrent %1 : %2")
+ .arg(s.torrent_name).arg(err.toString());
+
+ KMessageBox::error(0, msg, i18n("Error"));
+ }
+ }
+
+ void QueueManager::onLowDiskSpace(kt::TorrentInterface* tc, bool toStop)
+ {
+ if(toStop)
+ {
+ stop(tc, false);
+ }
+
+ //then emit the signal to inform trayicon to show passive popup
+ emit lowDiskSpace(tc, toStop);
+ }
+
+ void QueueManager::torrentStopped(kt::TorrentInterface* tc )
+ {
+ orderQueue();
+ }
+
+ /////////////////////////////////////////////////////////////////////////////////////////////
+
+
+ QueuePtrList::QueuePtrList() : QPtrList<kt::TorrentInterface>()
+ {}
+
+ QueuePtrList::~QueuePtrList()
+ {}
+
+ int QueuePtrList::compareItems(QPtrCollection::Item item1, QPtrCollection::Item item2)
+ {
+ kt::TorrentInterface* tc1 = (kt::TorrentInterface*) item1;
+ kt::TorrentInterface* tc2 = (kt::TorrentInterface*) item2;
+
+ if (tc1->getPriority() == tc2->getPriority())
+ return 0;
+
+ if (tc1->getPriority() == 0 && tc2->getPriority() != 0)
+ return 1;
+ else if (tc1->getPriority() != 0 && tc2->getPriority() == 0)
+ return -1;
+
+ return tc1->getPriority() > tc2->getPriority() ? -1 : 1;
+
+ return 0;
+ }
+}
+
+#include "queuemanager.moc"