You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
ktorrent/libktorrent/torrent/queuemanager.cpp

815 lines
17 KiB

/***************************************************************************
* 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 <tqstring.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() : TQObject(),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;
ordering = false;
}
QueueManager::~QueueManager()
{}
void QueueManager::append(kt::TorrentInterface* tc)
{
downloads.append(tc);
downloads.sort();
connect(tc, TQT_SIGNAL(diskSpaceLow(kt::TorrentInterface*, bool)), this, TQT_SLOT(onLowDiskSpace(kt::TorrentInterface*, bool)));
connect(tc, TQT_SIGNAL(torrentStopped(kt::TorrentInterface*)), this, TQT_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::TQM_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)
{
TQPtrList<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)
{
TQPtrList<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)
{
TQString 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;
TQPtrList<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;
TQPtrList<TorrentInterface>::const_iterator i = downloads.begin();
while (i != downloads.end())
{
if (!(*i)->getStats().completed)
++nr;
++i;
}
return nr;
}
int QueueManager::countSeeds()
{
int nr = 0;
TQPtrList<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;
TQPtrList<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;
TQPtrList<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;
}
TQPtrList<kt::TorrentInterface>::iterator QueueManager::begin()
{
return downloads.begin();
}
TQPtrList<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
{
TQPtrList<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)
{
TQPtrList<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() || ordering)
return;
if (paused_state || exiting)
return;
ordering = true;
downloads.sort();
TQPtrList<TorrentInterface>::const_iterator it = downloads.begin();
TQPtrList<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);
}
}
}
ordering = false;
}
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)
{
TQPtrList<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
{
TQPtrList<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;
TQPtrList<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();
TQString 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();
TQString 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() : TQPtrList<kt::TorrentInterface>()
{}
QueuePtrList::~QueuePtrList()
{}
int QueuePtrList::compareItems(TQPtrCollection::Item item1, TQPtrCollection::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"