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.
1771 lines
44 KiB
1771 lines
44 KiB
15 years ago
|
/***************************************************************************
|
||
|
* Copyright (C) 2005 by *
|
||
|
* Joris Guisson <joris.guisson@gmail.com> *
|
||
|
* Ivan Vasic <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 <qdir.h>
|
||
|
#include <qfile.h>
|
||
|
#include <klocale.h>
|
||
|
#include <kmessagebox.h>
|
||
|
#include <kfiledialog.h>
|
||
|
#include <qtextstream.h>
|
||
|
#include <util/log.h>
|
||
|
#include <util/error.h>
|
||
|
#include <util/bitset.h>
|
||
|
#include <util/functions.h>
|
||
|
#include <util/fileops.h>
|
||
|
#include <util/waitjob.h>
|
||
|
#include <interfaces/functions.h>
|
||
|
#include <interfaces/trackerslist.h>
|
||
|
#include <datachecker/singledatachecker.h>
|
||
|
#include <datachecker/multidatachecker.h>
|
||
|
#include <datachecker/datacheckerlistener.h>
|
||
|
#include <datachecker/datacheckerthread.h>
|
||
|
#include <migrate/ccmigrate.h>
|
||
|
#include <migrate/cachemigrate.h>
|
||
|
#include <kademlia/dhtbase.h>
|
||
|
|
||
|
#include "downloader.h"
|
||
|
#include "uploader.h"
|
||
|
#include "peersourcemanager.h"
|
||
|
#include "chunkmanager.h"
|
||
|
#include "torrent.h"
|
||
|
#include "peermanager.h"
|
||
|
|
||
|
#include "torrentfile.h"
|
||
|
#include "torrentcontrol.h"
|
||
|
|
||
|
#include "peer.h"
|
||
|
#include "choker.h"
|
||
|
|
||
|
#include "globals.h"
|
||
|
#include "server.h"
|
||
|
#include "packetwriter.h"
|
||
|
#include "httptracker.h"
|
||
|
#include "udptracker.h"
|
||
|
#include "downloadcap.h"
|
||
|
#include "uploadcap.h"
|
||
|
#include "queuemanager.h"
|
||
|
#include "statsfile.h"
|
||
|
#include "announcelist.h"
|
||
|
#include "preallocationthread.h"
|
||
|
#include "timeestimator.h"
|
||
|
#include "settings.h"
|
||
|
|
||
|
#include <net/socketmonitor.h>
|
||
|
|
||
|
|
||
|
using namespace kt;
|
||
|
|
||
|
namespace bt
|
||
|
{
|
||
|
|
||
|
|
||
|
|
||
|
TorrentControl::TorrentControl()
|
||
|
: tor(0),psman(0),cman(0),pman(0),down(0),up(0),choke(0),tmon(0),prealloc(false)
|
||
|
{
|
||
|
istats.last_announce = 0;
|
||
|
stats.imported_bytes = 0;
|
||
|
stats.trk_bytes_downloaded = 0;
|
||
|
stats.trk_bytes_uploaded = 0;
|
||
|
stats.running = false;
|
||
|
stats.started = false;
|
||
|
stats.stopped_by_error = false;
|
||
|
stats.session_bytes_downloaded = 0;
|
||
|
stats.session_bytes_uploaded = 0;
|
||
|
istats.session_bytes_uploaded = 0;
|
||
|
old_datadir = QString::null;
|
||
|
stats.status = NOT_STARTED;
|
||
|
stats.autostart = true;
|
||
|
stats.user_controlled = false;
|
||
|
stats.priv_torrent = false;
|
||
|
stats.seeders_connected_to = stats.seeders_total = 0;
|
||
|
stats.leechers_connected_to = stats.leechers_total = 0;
|
||
|
istats.running_time_dl = istats.running_time_ul = 0;
|
||
|
istats.prev_bytes_dl = 0;
|
||
|
istats.prev_bytes_ul = 0;
|
||
|
istats.trk_prev_bytes_dl = istats.trk_prev_bytes_ul = 0;
|
||
|
istats.io_error = false;
|
||
|
istats.priority = 0;
|
||
|
stats.max_share_ratio = 0.00f;
|
||
|
istats.custom_output_name = false;
|
||
|
istats.diskspace_warning_emitted = false;
|
||
|
stats.max_seed_time = 0;
|
||
|
updateStats();
|
||
|
prealoc_thread = 0;
|
||
|
dcheck_thread = 0;
|
||
|
istats.dht_on = false;
|
||
|
stats.num_corrupted_chunks = 0;
|
||
|
|
||
|
m_eta = new TimeEstimator(this);
|
||
|
// by default no torrent limits
|
||
|
upload_gid = download_gid = 0;
|
||
|
upload_limit = download_limit = 0;
|
||
|
moving_files = false;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
TorrentControl::~TorrentControl()
|
||
|
{
|
||
|
if (stats.running)
|
||
|
stop(false);
|
||
|
|
||
|
if (tmon)
|
||
|
tmon->destroyed();
|
||
|
delete choke;
|
||
|
delete down;
|
||
|
delete up;
|
||
|
delete cman;
|
||
|
delete pman;
|
||
|
delete psman;
|
||
|
delete tor;
|
||
|
delete m_eta;
|
||
|
}
|
||
|
|
||
|
void TorrentControl::update()
|
||
|
{
|
||
|
UpdateCurrentTime();
|
||
|
if (stats.status == kt::CHECKING_DATA || moving_files)
|
||
|
return;
|
||
|
|
||
|
if (istats.io_error)
|
||
|
{
|
||
|
stop(false);
|
||
|
emit stoppedByError(this, error_msg);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (prealoc_thread)
|
||
|
{
|
||
|
if (prealoc_thread->isDone())
|
||
|
{
|
||
|
// thread done
|
||
|
if (prealoc_thread->errorHappened())
|
||
|
{
|
||
|
// upon error just call onIOError and return
|
||
|
onIOError(prealoc_thread->errorMessage());
|
||
|
delete prealoc_thread;
|
||
|
prealoc_thread = 0;
|
||
|
prealloc = true; // still need to do preallocation
|
||
|
return;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// continue the startup of the torrent
|
||
|
delete prealoc_thread;
|
||
|
prealoc_thread = 0;
|
||
|
prealloc = false;
|
||
|
stats.status = kt::NOT_STARTED;
|
||
|
saveStats();
|
||
|
continueStart();
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
return; // preallocation still going on, so just return
|
||
|
}
|
||
|
|
||
|
|
||
|
try
|
||
|
{
|
||
|
// first update peermanager
|
||
|
pman->update();
|
||
|
bool comp = stats.completed;
|
||
|
|
||
|
//helper var, check if needed to move completed files somewhere
|
||
|
bool moveCompleted = false;
|
||
|
|
||
|
// then the downloader and uploader
|
||
|
up->update(choke->getOptimisticlyUnchokedPeerID());
|
||
|
down->update();
|
||
|
|
||
|
stats.completed = cman->completed();
|
||
|
if (stats.completed && !comp)
|
||
|
{
|
||
|
pman->killSeeders();
|
||
|
QDateTime now = QDateTime::currentDateTime();
|
||
|
istats.running_time_dl += istats.time_started_dl.secsTo(now);
|
||
|
updateStatusMsg();
|
||
|
updateStats();
|
||
|
|
||
|
// download has just been completed
|
||
|
// only sent completed to tracker when we have all chunks (so no excluded chunks)
|
||
|
if (cman->haveAllChunks())
|
||
|
psman->completed();
|
||
|
|
||
|
finished(this);
|
||
|
|
||
|
//Move completed download to specified directory if needed
|
||
|
if(Settings::useCompletedDir())
|
||
|
{
|
||
|
moveCompleted = true;
|
||
|
}
|
||
|
}
|
||
|
else if (!stats.completed && comp)
|
||
|
{
|
||
|
// restart download if necesarry
|
||
|
// when user selects that files which were previously excluded,
|
||
|
// should now be downloaded
|
||
|
if (!psman->isStarted())
|
||
|
psman->start();
|
||
|
else
|
||
|
psman->manualUpdate();
|
||
|
istats.last_announce = bt::GetCurrentTime();
|
||
|
istats.time_started_dl = QDateTime::currentDateTime();
|
||
|
}
|
||
|
updateStatusMsg();
|
||
|
|
||
|
// get rid of dead Peers
|
||
|
Uint32 num_cleared = pman->clearDeadPeers();
|
||
|
|
||
|
// we may need to update the choker
|
||
|
if (choker_update_timer.getElapsedSinceUpdate() >= 10000 || num_cleared > 0)
|
||
|
{
|
||
|
// also get rid of seeders & uninterested when download is finished
|
||
|
// no need to keep them around, but also no need to do this
|
||
|
// every update, so once every 10 seconds is fine
|
||
|
if (stats.completed)
|
||
|
{
|
||
|
pman->killSeeders();
|
||
|
}
|
||
|
|
||
|
doChoking();
|
||
|
choker_update_timer.update();
|
||
|
// a good opportunity to make sure we are not keeping to much in memory
|
||
|
cman->checkMemoryUsage();
|
||
|
}
|
||
|
|
||
|
// to satisfy people obsessed with their share ratio
|
||
|
if (stats_save_timer.getElapsedSinceUpdate() >= 5*60*1000)
|
||
|
{
|
||
|
saveStats();
|
||
|
stats_save_timer.update();
|
||
|
}
|
||
|
|
||
|
// Update DownloadCap
|
||
|
updateStats();
|
||
|
|
||
|
if (stats.download_rate > 0)
|
||
|
stalled_timer.update();
|
||
|
|
||
|
// do a manual update if we are stalled for more then 2 minutes
|
||
|
// we do not do this for private torrents
|
||
|
if (stalled_timer.getElapsedSinceUpdate() > 120000 && !stats.completed &&
|
||
|
!stats.priv_torrent)
|
||
|
{
|
||
|
Out(SYS_TRK|LOG_NOTICE) << "Stalled for too long, time to get some fresh blood" << endl;
|
||
|
psman->manualUpdate();
|
||
|
stalled_timer.update();
|
||
|
}
|
||
|
|
||
|
if(overMaxRatio() || overMaxSeedTime())
|
||
|
{
|
||
|
if(istats.priority!=0) //if it's queued make sure to dequeue it
|
||
|
{
|
||
|
setPriority(0);
|
||
|
stats.user_controlled = true;
|
||
|
}
|
||
|
|
||
|
stop(true);
|
||
|
emit seedingAutoStopped(this, overMaxRatio() ? kt::MAX_RATIO_REACHED : kt::MAX_SEED_TIME_REACHED);
|
||
|
}
|
||
|
|
||
|
//Update diskspace if needed (every 1 min)
|
||
|
if(!stats.completed && stats.running && bt::GetCurrentTime() - last_diskspace_check >= 60 * 1000)
|
||
|
{
|
||
|
checkDiskSpace(true);
|
||
|
}
|
||
|
|
||
|
//Move completed files if needed:
|
||
|
if (moveCompleted)
|
||
|
{
|
||
|
QString outdir = Settings::completedDir();
|
||
|
if(!outdir.endsWith(bt::DirSeparator()))
|
||
|
outdir += bt::DirSeparator();
|
||
|
|
||
|
changeOutputDir(outdir);
|
||
|
}
|
||
|
}
|
||
|
catch (Error & e)
|
||
|
{
|
||
|
onIOError(e.toString());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void TorrentControl::onIOError(const QString & msg)
|
||
|
{
|
||
|
Out(SYS_DIO|LOG_IMPORTANT) << "Error : " << msg << endl;
|
||
|
stats.stopped_by_error = true;
|
||
|
stats.status = ERROR;
|
||
|
error_msg = msg;
|
||
|
istats.io_error = true;
|
||
|
}
|
||
|
|
||
|
void TorrentControl::start()
|
||
|
{
|
||
|
// do not start running torrents
|
||
|
if (stats.running || stats.status == kt::ALLOCATING_DISKSPACE || moving_files)
|
||
|
return;
|
||
|
|
||
|
stats.stopped_by_error = false;
|
||
|
istats.diskspace_warning_emitted = false;
|
||
|
istats.io_error = false;
|
||
|
try
|
||
|
{
|
||
|
bool ret = true;
|
||
|
aboutToBeStarted(this,ret);
|
||
|
if (!ret)
|
||
|
return;
|
||
|
}
|
||
|
catch (Error & err)
|
||
|
{
|
||
|
// something went wrong when files were recreated, set error and rethrow
|
||
|
onIOError(err.toString());
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
try
|
||
|
{
|
||
|
cman->start();
|
||
|
}
|
||
|
catch (Error & e)
|
||
|
{
|
||
|
onIOError(e.toString());
|
||
|
throw;
|
||
|
}
|
||
|
|
||
|
istats.time_started_ul = istats.time_started_dl = QDateTime::currentDateTime();
|
||
|
resetTrackerStats();
|
||
|
|
||
|
if (prealloc)
|
||
|
{
|
||
|
// only start preallocation if we are allowed by the settings
|
||
|
if (Settings::diskPrealloc() && !cman->haveAllChunks())
|
||
|
{
|
||
|
Out(SYS_GEN|LOG_NOTICE) << "Pre-allocating diskspace" << endl;
|
||
|
prealoc_thread = new PreallocationThread(cman);
|
||
|
stats.running = true;
|
||
|
stats.status = kt::ALLOCATING_DISKSPACE;
|
||
|
prealoc_thread->start();
|
||
|
return;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
prealloc = false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
continueStart();
|
||
|
}
|
||
|
|
||
|
void TorrentControl::continueStart()
|
||
|
{
|
||
|
// continues start after the prealoc_thread has finished preallocation
|
||
|
pman->start();
|
||
|
pman->loadPeerList(datadir + "peer_list");
|
||
|
try
|
||
|
{
|
||
|
down->loadDownloads(datadir + "current_chunks");
|
||
|
}
|
||
|
catch (Error & e)
|
||
|
{
|
||
|
// print out warning in case of failure
|
||
|
// we can still continue the download
|
||
|
Out(SYS_GEN|LOG_NOTICE) << "Warning : " << e.toString() << endl;
|
||
|
}
|
||
|
|
||
|
loadStats();
|
||
|
stats.running = true;
|
||
|
stats.started = true;
|
||
|
stats.autostart = true;
|
||
|
choker_update_timer.update();
|
||
|
stats_save_timer.update();
|
||
|
|
||
|
|
||
|
stalled_timer.update();
|
||
|
psman->start();
|
||
|
istats.last_announce = bt::GetCurrentTime();
|
||
|
stalled_timer.update();
|
||
|
}
|
||
|
|
||
|
|
||
|
void TorrentControl::stop(bool user,WaitJob* wjob)
|
||
|
{
|
||
|
QDateTime now = QDateTime::currentDateTime();
|
||
|
if(!stats.completed)
|
||
|
istats.running_time_dl += istats.time_started_dl.secsTo(now);
|
||
|
istats.running_time_ul += istats.time_started_ul.secsTo(now);
|
||
|
istats.time_started_ul = istats.time_started_dl = now;
|
||
|
|
||
|
// stop preallocation thread if necesarry
|
||
|
if (prealoc_thread)
|
||
|
{
|
||
|
prealoc_thread->stop();
|
||
|
prealoc_thread->wait();
|
||
|
|
||
|
if (prealoc_thread->errorHappened() || prealoc_thread->isNotFinished())
|
||
|
{
|
||
|
delete prealoc_thread;
|
||
|
prealoc_thread = 0;
|
||
|
prealloc = true;
|
||
|
saveStats(); // save stats, so that we will start preallocating the next time
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
delete prealoc_thread;
|
||
|
prealoc_thread = 0;
|
||
|
prealloc = false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (stats.running)
|
||
|
{
|
||
|
psman->stop(wjob);
|
||
|
|
||
|
if (tmon)
|
||
|
tmon->stopped();
|
||
|
|
||
|
try
|
||
|
{
|
||
|
down->saveDownloads(datadir + "current_chunks");
|
||
|
}
|
||
|
catch (Error & e)
|
||
|
{
|
||
|
// print out warning in case of failure
|
||
|
// it doesn't corrupt the data, so just a couple of lost chunks
|
||
|
Out(SYS_GEN|LOG_NOTICE) << "Warning : " << e.toString() << endl;
|
||
|
}
|
||
|
|
||
|
down->clearDownloads();
|
||
|
if (user)
|
||
|
{
|
||
|
//make this torrent user controlled
|
||
|
setPriority(0);
|
||
|
stats.autostart = false;
|
||
|
}
|
||
|
}
|
||
|
pman->savePeerList(datadir + "peer_list");
|
||
|
pman->stop();
|
||
|
pman->closeAllConnections();
|
||
|
pman->clearDeadPeers();
|
||
|
cman->stop();
|
||
|
|
||
|
stats.running = false;
|
||
|
saveStats();
|
||
|
updateStatusMsg();
|
||
|
updateStats();
|
||
|
stats.trk_bytes_downloaded = 0;
|
||
|
stats.trk_bytes_uploaded = 0;
|
||
|
|
||
|
emit torrentStopped(this);
|
||
|
}
|
||
|
|
||
|
void TorrentControl::setMonitor(kt::MonitorInterface* tmo)
|
||
|
{
|
||
|
tmon = tmo;
|
||
|
down->setMonitor(tmon);
|
||
|
if (tmon)
|
||
|
{
|
||
|
for (Uint32 i = 0;i < pman->getNumConnectedPeers();i++)
|
||
|
tmon->peerAdded(pman->getPeer(i));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void TorrentControl::init(QueueManager* qman,
|
||
|
const QString & torrent,
|
||
|
const QString & tmpdir,
|
||
|
const QString & ddir,
|
||
|
const QString & default_save_dir)
|
||
|
{
|
||
|
// first load the torrent file
|
||
|
tor = new Torrent();
|
||
|
try
|
||
|
{
|
||
|
tor->load(torrent,false);
|
||
|
}
|
||
|
catch (...)
|
||
|
{
|
||
|
delete tor;
|
||
|
tor = 0;
|
||
|
throw Error(i18n("An error occurred while loading the torrent."
|
||
|
" The torrent is probably corrupt or is not a torrent file.\n%1").arg(torrent));
|
||
|
}
|
||
|
|
||
|
initInternal(qman,tmpdir,ddir,default_save_dir,torrent.startsWith(tmpdir));
|
||
|
|
||
|
// copy torrent in tor dir
|
||
|
QString tor_copy = datadir + "torrent";
|
||
|
if (tor_copy != torrent)
|
||
|
{
|
||
|
bt::CopyFile(torrent,tor_copy);
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
void TorrentControl::init(QueueManager* qman, const QByteArray & data,const QString & tmpdir,
|
||
|
const QString & ddir,const QString & default_save_dir)
|
||
|
{
|
||
|
// first load the torrent file
|
||
|
tor = new Torrent();
|
||
|
try
|
||
|
{
|
||
|
tor->load(data,false);
|
||
|
}
|
||
|
catch (...)
|
||
|
{
|
||
|
delete tor;
|
||
|
tor = 0;
|
||
|
throw Error(i18n("An error occurred while loading the torrent."
|
||
|
" The torrent is probably corrupt or is not a torrent file."));
|
||
|
}
|
||
|
|
||
|
initInternal(qman,tmpdir,ddir,default_save_dir,true);
|
||
|
// copy data into torrent file
|
||
|
QString tor_copy = datadir + "torrent";
|
||
|
QFile fptr(tor_copy);
|
||
|
if (!fptr.open(IO_WriteOnly))
|
||
|
throw Error(i18n("Unable to create %1 : %2")
|
||
|
.arg(tor_copy).arg(fptr.errorString()));
|
||
|
|
||
|
fptr.writeBlock(data.data(),data.size());
|
||
|
}
|
||
|
|
||
|
void TorrentControl::checkExisting(QueueManager* qman)
|
||
|
{
|
||
|
// check if we haven't already loaded the torrent
|
||
|
// only do this when qman isn't 0
|
||
|
if (qman && qman->allreadyLoaded(tor->getInfoHash()))
|
||
|
{
|
||
|
if (!stats.priv_torrent)
|
||
|
{
|
||
|
qman->mergeAnnounceList(tor->getInfoHash(),tor->getTrackerList());
|
||
|
|
||
|
throw Error(i18n("You are already downloading this torrent %1, the list of trackers of both torrents has been merged.").arg(tor->getNameSuggestion()));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
throw Error(i18n("You are already downloading the torrent %1")
|
||
|
.arg(tor->getNameSuggestion()));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void TorrentControl::setupDirs(const QString & tmpdir,const QString & ddir)
|
||
|
{
|
||
|
datadir = tmpdir;
|
||
|
|
||
|
if (!datadir.endsWith(DirSeparator()))
|
||
|
datadir += DirSeparator();
|
||
|
|
||
|
outputdir = ddir.stripWhiteSpace();
|
||
|
if (outputdir.length() > 0 && !outputdir.endsWith(DirSeparator()))
|
||
|
outputdir += DirSeparator();
|
||
|
|
||
|
if (!bt::Exists(datadir))
|
||
|
{
|
||
|
bt::MakeDir(datadir);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void TorrentControl::setupStats()
|
||
|
{
|
||
|
stats.completed = false;
|
||
|
stats.running = false;
|
||
|
stats.torrent_name = tor->getNameSuggestion();
|
||
|
stats.multi_file_torrent = tor->isMultiFile();
|
||
|
stats.total_bytes = tor->getFileLength();
|
||
|
stats.priv_torrent = tor->isPrivate();
|
||
|
|
||
|
// check the stats file for the custom_output_name variable
|
||
|
StatsFile st(datadir + "stats");
|
||
|
if (st.hasKey("CUSTOM_OUTPUT_NAME") && st.readULong("CUSTOM_OUTPUT_NAME") == 1)
|
||
|
{
|
||
|
istats.custom_output_name = true;
|
||
|
}
|
||
|
|
||
|
// load outputdir if outputdir is null
|
||
|
if (outputdir.isNull() || outputdir.length() == 0)
|
||
|
loadOutputDir();
|
||
|
}
|
||
|
|
||
|
void TorrentControl::setupData(const QString & ddir)
|
||
|
{
|
||
|
// create PeerManager and Tracker
|
||
|
pman = new PeerManager(*tor);
|
||
|
//Out() << "Tracker url " << url << " " << url.protocol() << " " << url.prettyURL() << endl;
|
||
|
psman = new PeerSourceManager(this,pman);
|
||
|
connect(psman,SIGNAL(statusChanged( const QString& )),
|
||
|
this,SLOT(trackerStatusChanged( const QString& )));
|
||
|
|
||
|
|
||
|
// Create chunkmanager, load the index file if it exists
|
||
|
// else create all the necesarry files
|
||
|
cman = new ChunkManager(*tor,datadir,outputdir,istats.custom_output_name);
|
||
|
// outputdir is null, see if the cache has figured out what it is
|
||
|
if (outputdir.length() == 0)
|
||
|
outputdir = cman->getDataDir();
|
||
|
|
||
|
// store the outputdir into the output_path variable, so others can access it
|
||
|
|
||
|
connect(cman,SIGNAL(updateStats()),this,SLOT(updateStats()));
|
||
|
if (bt::Exists(datadir + "index"))
|
||
|
cman->loadIndexFile();
|
||
|
|
||
|
stats.completed = cman->completed();
|
||
|
|
||
|
// create downloader,uploader and choker
|
||
|
down = new Downloader(*tor,*pman,*cman);
|
||
|
connect(down,SIGNAL(ioError(const QString& )),
|
||
|
this,SLOT(onIOError(const QString& )));
|
||
|
up = new Uploader(*cman,*pman);
|
||
|
choke = new Choker(*pman,*cman);
|
||
|
|
||
|
|
||
|
connect(pman,SIGNAL(newPeer(Peer* )),this,SLOT(onNewPeer(Peer* )));
|
||
|
connect(pman,SIGNAL(peerKilled(Peer* )),this,SLOT(onPeerRemoved(Peer* )));
|
||
|
connect(cman,SIGNAL(excluded(Uint32, Uint32 )),down,SLOT(onExcluded(Uint32, Uint32 )));
|
||
|
connect(cman,SIGNAL(included( Uint32, Uint32 )),down,SLOT(onIncluded( Uint32, Uint32 )));
|
||
|
connect(cman,SIGNAL(corrupted( Uint32 )),this,SLOT(corrupted( Uint32 )));
|
||
|
}
|
||
|
|
||
|
void TorrentControl::initInternal(QueueManager* qman,
|
||
|
const QString & tmpdir,
|
||
|
const QString & ddir,
|
||
|
const QString & default_save_dir,
|
||
|
bool first_time)
|
||
|
{
|
||
|
checkExisting(qman);
|
||
|
setupDirs(tmpdir,ddir);
|
||
|
setupStats();
|
||
|
|
||
|
if (!first_time)
|
||
|
{
|
||
|
// if we do not need to copy the torrent, it is an existing download and we need to see
|
||
|
// if it is not an old download
|
||
|
try
|
||
|
{
|
||
|
migrateTorrent(default_save_dir);
|
||
|
}
|
||
|
catch (Error & err)
|
||
|
{
|
||
|
|
||
|
throw Error(
|
||
|
i18n("Cannot migrate %1 : %2")
|
||
|
.arg(tor->getNameSuggestion()).arg(err.toString()));
|
||
|
}
|
||
|
}
|
||
|
setupData(ddir);
|
||
|
|
||
|
updateStatusMsg();
|
||
|
|
||
|
// to get rid of phantom bytes we need to take into account
|
||
|
// the data from downloads already in progress
|
||
|
try
|
||
|
{
|
||
|
Uint64 db = down->bytesDownloaded();
|
||
|
Uint64 cb = down->getDownloadedBytesOfCurrentChunksFile(datadir + "current_chunks");
|
||
|
istats.prev_bytes_dl = db + cb;
|
||
|
|
||
|
// Out() << "Downloaded : " << kt::BytesToString(db) << endl;
|
||
|
// Out() << "current_chunks : " << kt::BytesToString(cb) << endl;
|
||
|
}
|
||
|
catch (Error & e)
|
||
|
{
|
||
|
// print out warning in case of failure
|
||
|
Out() << "Warning : " << e.toString() << endl;
|
||
|
istats.prev_bytes_dl = down->bytesDownloaded();
|
||
|
}
|
||
|
|
||
|
loadStats();
|
||
|
updateStats();
|
||
|
saveStats();
|
||
|
stats.output_path = cman->getOutputPath();
|
||
|
/* if (stats.output_path.isNull())
|
||
|
{
|
||
|
cman->createFiles();
|
||
|
stats.output_path = cman->getOutputPath();
|
||
|
}*/
|
||
|
Out() << "OutputPath = " << stats.output_path << endl;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
bool TorrentControl::announceAllowed()
|
||
|
{
|
||
|
if(istats.last_announce == 0)
|
||
|
return true;
|
||
|
|
||
|
if (psman && psman->getNumFailures() == 0)
|
||
|
return bt::GetCurrentTime() - istats.last_announce >= 60 * 1000;
|
||
|
else
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
void TorrentControl::updateTracker()
|
||
|
{
|
||
|
if (stats.running && announceAllowed())
|
||
|
{
|
||
|
psman->manualUpdate();
|
||
|
istats.last_announce = bt::GetCurrentTime();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void TorrentControl::onNewPeer(Peer* p)
|
||
|
{
|
||
|
connect(p,SIGNAL(gotPortPacket( const QString&, Uint16 )),
|
||
|
this,SLOT(onPortPacket( const QString&, Uint16 )));
|
||
|
|
||
|
if (p->getStats().fast_extensions)
|
||
|
{
|
||
|
const BitSet & bs = cman->getBitSet();
|
||
|
if (bs.allOn())
|
||
|
p->getPacketWriter().sendHaveAll();
|
||
|
else if (bs.numOnBits() == 0)
|
||
|
p->getPacketWriter().sendHaveNone();
|
||
|
else
|
||
|
p->getPacketWriter().sendBitSet(bs);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
p->getPacketWriter().sendBitSet(cman->getBitSet());
|
||
|
}
|
||
|
|
||
|
if (!stats.completed)
|
||
|
p->getPacketWriter().sendInterested();
|
||
|
|
||
|
if (!stats.priv_torrent)
|
||
|
{
|
||
|
if (p->isDHTSupported())
|
||
|
p->getPacketWriter().sendPort(Globals::instance().getDHT().getPort());
|
||
|
else
|
||
|
// WORKAROUND so we can contact µTorrent's DHT
|
||
|
// They do not properly support the standard and do not turn on
|
||
|
// the DHT bit in the handshake, so we just ping each peer by default.
|
||
|
p->emitPortPacket();
|
||
|
}
|
||
|
|
||
|
// set group ID's for traffic shaping
|
||
|
p->setGroupIDs(upload_gid,download_gid);
|
||
|
|
||
|
if (tmon)
|
||
|
tmon->peerAdded(p);
|
||
|
}
|
||
|
|
||
|
void TorrentControl::onPeerRemoved(Peer* p)
|
||
|
{
|
||
|
disconnect(p,SIGNAL(gotPortPacket( const QString&, Uint16 )),
|
||
|
this,SLOT(onPortPacket( const QString&, Uint16 )));
|
||
|
if (tmon)
|
||
|
tmon->peerRemoved(p);
|
||
|
}
|
||
|
|
||
|
void TorrentControl::doChoking()
|
||
|
{
|
||
|
choke->update(stats.completed,stats);
|
||
|
}
|
||
|
|
||
|
bool TorrentControl::changeDataDir(const QString & new_dir)
|
||
|
{
|
||
|
int pos = datadir.findRev(bt::DirSeparator(),-2);
|
||
|
if (pos == -1)
|
||
|
{
|
||
|
Out(SYS_GEN|LOG_DEBUG) << "Could not find torX part in " << datadir << endl;
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
QString ndatadir = new_dir + datadir.mid(pos + 1);
|
||
|
|
||
|
Out(SYS_GEN|LOG_DEBUG) << datadir << " -> " << ndatadir << endl;
|
||
|
try
|
||
|
{
|
||
|
bt::Move(datadir,ndatadir);
|
||
|
old_datadir = datadir;
|
||
|
datadir = ndatadir;
|
||
|
}
|
||
|
catch (Error & err)
|
||
|
{
|
||
|
Out(SYS_GEN|LOG_IMPORTANT) << "Could not move " << datadir << " to " << ndatadir << endl;
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
cman->changeDataDir(datadir);
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
bool TorrentControl::changeOutputDir(const QString & new_dir, bool moveFiles)
|
||
|
{
|
||
|
if (moving_files)
|
||
|
return false;
|
||
|
|
||
|
Out(SYS_GEN|LOG_NOTICE) << "Moving data for torrent " << stats.torrent_name << " to " << new_dir << endl;
|
||
|
|
||
|
restart_torrent_after_move_data_files = false;
|
||
|
|
||
|
//check if torrent is running and stop it before moving data
|
||
|
if(stats.running)
|
||
|
{
|
||
|
restart_torrent_after_move_data_files = true;
|
||
|
this->stop(false);
|
||
|
}
|
||
|
|
||
|
moving_files = true;
|
||
|
try
|
||
|
{
|
||
|
QString nd;
|
||
|
if (istats.custom_output_name)
|
||
|
{
|
||
|
int slash_pos = stats.output_path.findRev(bt::DirSeparator(),-2);
|
||
|
nd = new_dir + stats.output_path.mid(slash_pos + 1);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
nd = new_dir + tor->getNameSuggestion();
|
||
|
}
|
||
|
|
||
|
if (stats.output_path != nd)
|
||
|
{
|
||
|
KIO::Job* j = 0;
|
||
|
if(moveFiles)
|
||
|
{
|
||
|
if (stats.multi_file_torrent)
|
||
|
j = cman->moveDataFiles(nd);
|
||
|
else
|
||
|
j = cman->moveDataFiles(new_dir);
|
||
|
}
|
||
|
|
||
|
move_data_files_destination_path = nd;
|
||
|
if (j)
|
||
|
{
|
||
|
connect(j,SIGNAL(result(KIO::Job*)),this,SLOT(moveDataFilesJobDone(KIO::Job*)));
|
||
|
return true;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
moveDataFilesJobDone(0);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Out(SYS_GEN|LOG_NOTICE) << "Source is the same as destination, so doing nothing" << endl;
|
||
|
}
|
||
|
}
|
||
|
catch (Error& err)
|
||
|
{
|
||
|
Out(SYS_GEN|LOG_IMPORTANT) << "Could not move " << stats.output_path << " to " << new_dir << ". Exception: " << err.toString() << endl;
|
||
|
moving_files = false;
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
moving_files = false;
|
||
|
if (restart_torrent_after_move_data_files)
|
||
|
{
|
||
|
this->start();
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
void TorrentControl::moveDataFilesJobDone(KIO::Job* job)
|
||
|
{
|
||
|
if (job)
|
||
|
cman->moveDataFilesCompleted(job);
|
||
|
|
||
|
if (!job || (job && !job->error()))
|
||
|
{
|
||
|
cman->changeOutputPath(move_data_files_destination_path);
|
||
|
outputdir = stats.output_path = move_data_files_destination_path;
|
||
|
istats.custom_output_name = true;
|
||
|
|
||
|
saveStats();
|
||
|
Out(SYS_GEN|LOG_NOTICE) << "Data directory changed for torrent " << "'" << stats.torrent_name << "' to: " << move_data_files_destination_path << endl;
|
||
|
}
|
||
|
else if (job->error())
|
||
|
{
|
||
|
Out(SYS_GEN|LOG_IMPORTANT) << "Could not move " << stats.output_path << " to " << move_data_files_destination_path << endl;
|
||
|
}
|
||
|
|
||
|
moving_files = false;
|
||
|
if (restart_torrent_after_move_data_files)
|
||
|
{
|
||
|
this->start();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void TorrentControl::rollback()
|
||
|
{
|
||
|
try
|
||
|
{
|
||
|
bt::Move(datadir,old_datadir);
|
||
|
datadir = old_datadir;
|
||
|
cman->changeDataDir(datadir);
|
||
|
}
|
||
|
catch (Error & err)
|
||
|
{
|
||
|
Out(SYS_GEN|LOG_IMPORTANT) << "Could not move " << datadir << " to " << old_datadir << endl;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void TorrentControl::updateStatusMsg()
|
||
|
{
|
||
|
if (stats.stopped_by_error)
|
||
|
stats.status = kt::ERROR;
|
||
|
else if (!stats.started)
|
||
|
stats.status = kt::NOT_STARTED;
|
||
|
else if(!stats.running && !stats.user_controlled)
|
||
|
stats.status = kt::QUEUED;
|
||
|
else if (!stats.running && stats.completed && (overMaxRatio() || overMaxSeedTime()))
|
||
|
stats.status = kt::SEEDING_COMPLETE;
|
||
|
else if (!stats.running && stats.completed)
|
||
|
stats.status = kt::DOWNLOAD_COMPLETE;
|
||
|
else if (!stats.running)
|
||
|
stats.status = kt::STOPPED;
|
||
|
else if (stats.running && stats.completed)
|
||
|
stats.status = kt::SEEDING;
|
||
|
else if (stats.running)
|
||
|
// protocol messages are also included in speed calculation, so lets not compare with 0
|
||
|
stats.status = down->downloadRate() > 100 ?
|
||
|
kt::DOWNLOADING : kt::STALLED;
|
||
|
}
|
||
|
|
||
|
const BitSet & TorrentControl::downloadedChunksBitSet() const
|
||
|
{
|
||
|
if (cman)
|
||
|
return cman->getBitSet();
|
||
|
else
|
||
|
return BitSet::null;
|
||
|
}
|
||
|
|
||
|
const BitSet & TorrentControl::availableChunksBitSet() const
|
||
|
{
|
||
|
if (!pman)
|
||
|
return BitSet::null;
|
||
|
else
|
||
|
return pman->getAvailableChunksBitSet();
|
||
|
}
|
||
|
|
||
|
const BitSet & TorrentControl::excludedChunksBitSet() const
|
||
|
{
|
||
|
if (!cman)
|
||
|
return BitSet::null;
|
||
|
else
|
||
|
return cman->getExcludedBitSet();
|
||
|
}
|
||
|
|
||
|
const BitSet & TorrentControl::onlySeedChunksBitSet() const
|
||
|
{
|
||
|
if (!cman)
|
||
|
return BitSet::null;
|
||
|
else
|
||
|
return cman->getOnlySeedBitSet();
|
||
|
}
|
||
|
|
||
|
void TorrentControl::saveStats()
|
||
|
{
|
||
|
StatsFile st(datadir + "stats");
|
||
|
|
||
|
st.write("OUTPUTDIR", cman->getDataDir());
|
||
|
|
||
|
if (cman->getDataDir() != outputdir)
|
||
|
outputdir = cman->getDataDir();
|
||
|
|
||
|
st.write("UPLOADED", QString::number(up->bytesUploaded()));
|
||
|
|
||
|
if (stats.running)
|
||
|
{
|
||
|
QDateTime now = QDateTime::currentDateTime();
|
||
|
st.write("RUNNING_TIME_DL",QString("%1").arg(istats.running_time_dl + istats.time_started_dl.secsTo(now)));
|
||
|
st.write("RUNNING_TIME_UL",QString("%1").arg(istats.running_time_ul + istats.time_started_ul.secsTo(now)));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
st.write("RUNNING_TIME_DL", QString("%1").arg(istats.running_time_dl));
|
||
|
st.write("RUNNING_TIME_UL", QString("%1").arg(istats.running_time_ul));
|
||
|
}
|
||
|
|
||
|
st.write("PRIORITY", QString("%1").arg(istats.priority));
|
||
|
st.write("AUTOSTART", QString("%1").arg(stats.autostart));
|
||
|
st.write("IMPORTED", QString("%1").arg(stats.imported_bytes));
|
||
|
st.write("CUSTOM_OUTPUT_NAME",istats.custom_output_name ? "1" : "0");
|
||
|
st.write("MAX_RATIO", QString("%1").arg(stats.max_share_ratio,0,'f',2));
|
||
|
st.write("MAX_SEED_TIME",QString::number(stats.max_seed_time));
|
||
|
st.write("RESTART_DISK_PREALLOCATION",prealloc ? "1" : "0");
|
||
|
|
||
|
if(!stats.priv_torrent)
|
||
|
{
|
||
|
//save dht and pex
|
||
|
st.write("DHT", isFeatureEnabled(kt::DHT_FEATURE) ? "1" : "0");
|
||
|
st.write("UT_PEX", isFeatureEnabled(kt::UT_PEX_FEATURE) ? "1" : "0");
|
||
|
}
|
||
|
|
||
|
st.write("UPLOAD_LIMIT",QString::number(upload_limit));
|
||
|
st.write("DOWNLOAD_LIMIT",QString::number(download_limit));
|
||
|
|
||
|
st.writeSync();
|
||
|
}
|
||
|
|
||
|
void TorrentControl::loadStats()
|
||
|
{
|
||
|
StatsFile st(datadir + "stats");
|
||
|
|
||
|
Uint64 val = st.readUint64("UPLOADED");
|
||
|
// stats.session_bytes_uploaded will be calculated based upon prev_bytes_ul
|
||
|
// seeing that this will change here, we need to save it
|
||
|
istats.session_bytes_uploaded = stats.session_bytes_uploaded;
|
||
|
istats.prev_bytes_ul = val;
|
||
|
up->setBytesUploaded(val);
|
||
|
|
||
|
this->istats.running_time_dl = st.readULong("RUNNING_TIME_DL");
|
||
|
this->istats.running_time_ul = st.readULong("RUNNING_TIME_UL");
|
||
|
outputdir = st.readString("OUTPUTDIR").stripWhiteSpace();
|
||
|
if (st.hasKey("CUSTOM_OUTPUT_NAME") && st.readULong("CUSTOM_OUTPUT_NAME") == 1)
|
||
|
{
|
||
|
istats.custom_output_name = true;
|
||
|
}
|
||
|
|
||
|
setPriority(st.readInt("PRIORITY"));
|
||
|
stats.user_controlled = istats.priority == 0 ? true : false;
|
||
|
stats.autostart = st.readBoolean("AUTOSTART");
|
||
|
|
||
|
stats.imported_bytes = st.readUint64("IMPORTED");
|
||
|
float rat = st.readFloat("MAX_RATIO");
|
||
|
stats.max_share_ratio = rat;
|
||
|
if (st.hasKey("RESTART_DISK_PREALLOCATION"))
|
||
|
prealloc = st.readString("RESTART_DISK_PREALLOCATION") == "1";
|
||
|
|
||
|
stats.max_seed_time = st.readFloat("MAX_SEED_TIME");
|
||
|
|
||
|
if (!stats.priv_torrent)
|
||
|
{
|
||
|
if(st.hasKey("DHT"))
|
||
|
istats.dht_on = st.readBoolean("DHT");
|
||
|
else
|
||
|
istats.dht_on = true;
|
||
|
|
||
|
setFeatureEnabled(kt::DHT_FEATURE,istats.dht_on);
|
||
|
if (st.hasKey("UT_PEX"))
|
||
|
setFeatureEnabled(kt::UT_PEX_FEATURE,st.readBoolean("UT_PEX"));
|
||
|
}
|
||
|
|
||
|
net::SocketMonitor & smon = net::SocketMonitor::instance();
|
||
|
|
||
|
Uint32 nl = st.readInt("UPLOAD_LIMIT");
|
||
|
if (nl != upload_limit)
|
||
|
{
|
||
|
if (nl > 0)
|
||
|
{
|
||
|
if (upload_gid)
|
||
|
smon.setGroupLimit(net::SocketMonitor::UPLOAD_GROUP,upload_gid,nl);
|
||
|
else
|
||
|
upload_gid = smon.newGroup(net::SocketMonitor::UPLOAD_GROUP,nl);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
smon.removeGroup(net::SocketMonitor::UPLOAD_GROUP,upload_gid);
|
||
|
upload_gid = 0;
|
||
|
}
|
||
|
}
|
||
|
upload_limit = nl;
|
||
|
|
||
|
nl = st.readInt("DOWNLOAD_LIMIT");
|
||
|
if (nl != download_limit)
|
||
|
{
|
||
|
if (nl > 0)
|
||
|
{
|
||
|
if (download_gid)
|
||
|
smon.setGroupLimit(net::SocketMonitor::DOWNLOAD_GROUP,download_gid,nl);
|
||
|
else
|
||
|
download_gid = smon.newGroup(net::SocketMonitor::DOWNLOAD_GROUP,nl);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
smon.removeGroup(net::SocketMonitor::DOWNLOAD_GROUP,download_gid);
|
||
|
download_gid = 0;
|
||
|
}
|
||
|
}
|
||
|
download_limit = nl;
|
||
|
}
|
||
|
|
||
|
void TorrentControl::loadOutputDir()
|
||
|
{
|
||
|
StatsFile st(datadir + "stats");
|
||
|
if (!st.hasKey("OUTPUTDIR"))
|
||
|
return;
|
||
|
|
||
|
outputdir = st.readString("OUTPUTDIR").stripWhiteSpace();
|
||
|
if (st.hasKey("CUSTOM_OUTPUT_NAME") && st.readULong("CUSTOM_OUTPUT_NAME") == 1)
|
||
|
{
|
||
|
istats.custom_output_name = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool TorrentControl::readyForPreview(int start_chunk, int end_chunk)
|
||
|
{
|
||
|
if ( !tor->isMultimedia() && !tor->isMultiFile()) return false;
|
||
|
|
||
|
const BitSet & bs = downloadedChunksBitSet();
|
||
|
for(int i = start_chunk; i<end_chunk; ++i)
|
||
|
{
|
||
|
if ( !bs.get(i) ) return false;
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
Uint32 TorrentControl::getTimeToNextTrackerUpdate() const
|
||
|
{
|
||
|
if (psman)
|
||
|
return psman->getTimeToNextUpdate();
|
||
|
else
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
void TorrentControl::updateStats()
|
||
|
{
|
||
|
stats.num_chunks_downloading = down ? down->numActiveDownloads() : 0;
|
||
|
stats.num_peers = pman ? pman->getNumConnectedPeers() : 0;
|
||
|
stats.upload_rate = up && stats.running ? up->uploadRate() : 0;
|
||
|
stats.download_rate = down && stats.running ? down->downloadRate() : 0;
|
||
|
stats.bytes_left = cman ? cman->bytesLeft() : 0;
|
||
|
stats.bytes_left_to_download = cman ? cman->bytesLeftToDownload() : 0;
|
||
|
stats.bytes_uploaded = up ? up->bytesUploaded() : 0;
|
||
|
stats.bytes_downloaded = down ? down->bytesDownloaded() : 0;
|
||
|
stats.total_chunks = tor ? tor->getNumChunks() : 0;
|
||
|
stats.num_chunks_downloaded = cman ? cman->chunksDownloaded() : 0;
|
||
|
stats.num_chunks_excluded = cman ? cman->chunksExcluded() : 0;
|
||
|
stats.chunk_size = tor ? tor->getChunkSize() : 0;
|
||
|
stats.num_chunks_left = cman ? cman->chunksLeft() : 0;
|
||
|
stats.total_bytes_to_download = (tor && cman) ? tor->getFileLength() - cman->bytesExcluded() : 0;
|
||
|
|
||
|
if (stats.bytes_downloaded >= istats.prev_bytes_dl)
|
||
|
stats.session_bytes_downloaded = stats.bytes_downloaded - istats.prev_bytes_dl;
|
||
|
else
|
||
|
stats.session_bytes_downloaded = 0;
|
||
|
|
||
|
if (stats.bytes_uploaded >= istats.prev_bytes_ul)
|
||
|
stats.session_bytes_uploaded = (stats.bytes_uploaded - istats.prev_bytes_ul) + istats.session_bytes_uploaded;
|
||
|
else
|
||
|
stats.session_bytes_uploaded = istats.session_bytes_uploaded;
|
||
|
/*
|
||
|
Safety check, it is possible that stats.bytes_downloaded gets subtracted in Downloader.
|
||
|
Which can cause stats.bytes_downloaded to be smaller the istats.trk_prev_bytes_dl.
|
||
|
This can screw up your download ratio.
|
||
|
*/
|
||
|
if (stats.bytes_downloaded >= istats.trk_prev_bytes_dl)
|
||
|
stats.trk_bytes_downloaded = stats.bytes_downloaded - istats.trk_prev_bytes_dl;
|
||
|
else
|
||
|
stats.trk_bytes_downloaded = 0;
|
||
|
|
||
|
if (stats.bytes_uploaded >= istats.trk_prev_bytes_ul)
|
||
|
stats.trk_bytes_uploaded = stats.bytes_uploaded - istats.trk_prev_bytes_ul;
|
||
|
else
|
||
|
stats.trk_bytes_uploaded = 0;
|
||
|
|
||
|
getSeederInfo(stats.seeders_total,stats.seeders_connected_to);
|
||
|
getLeecherInfo(stats.leechers_total,stats.leechers_connected_to);
|
||
|
}
|
||
|
|
||
|
void TorrentControl::getSeederInfo(Uint32 & total,Uint32 & connected_to) const
|
||
|
{
|
||
|
total = 0;
|
||
|
connected_to = 0;
|
||
|
if (!pman || !psman)
|
||
|
return;
|
||
|
|
||
|
for (Uint32 i = 0;i < pman->getNumConnectedPeers();i++)
|
||
|
{
|
||
|
if (pman->getPeer(i)->isSeeder())
|
||
|
connected_to++;
|
||
|
}
|
||
|
total = psman->getNumSeeders();
|
||
|
if (total == 0)
|
||
|
total = connected_to;
|
||
|
}
|
||
|
|
||
|
void TorrentControl::getLeecherInfo(Uint32 & total,Uint32 & connected_to) const
|
||
|
{
|
||
|
total = 0;
|
||
|
connected_to = 0;
|
||
|
if (!pman || !psman)
|
||
|
return;
|
||
|
|
||
|
for (Uint32 i = 0;i < pman->getNumConnectedPeers();i++)
|
||
|
{
|
||
|
if (!pman->getPeer(i)->isSeeder())
|
||
|
connected_to++;
|
||
|
}
|
||
|
total = psman->getNumLeechers();
|
||
|
if (total == 0)
|
||
|
total = connected_to;
|
||
|
}
|
||
|
|
||
|
Uint32 TorrentControl::getRunningTimeDL() const
|
||
|
{
|
||
|
if (!stats.running || stats.completed)
|
||
|
return istats.running_time_dl;
|
||
|
else
|
||
|
return istats.running_time_dl + istats.time_started_dl.secsTo(QDateTime::currentDateTime());
|
||
|
}
|
||
|
|
||
|
Uint32 TorrentControl::getRunningTimeUL() const
|
||
|
{
|
||
|
if (!stats.running)
|
||
|
return istats.running_time_ul;
|
||
|
else
|
||
|
return istats.running_time_ul + istats.time_started_ul.secsTo(QDateTime::currentDateTime());
|
||
|
}
|
||
|
|
||
|
Uint32 TorrentControl::getNumFiles() const
|
||
|
{
|
||
|
if (tor && tor->isMultiFile())
|
||
|
return tor->getNumFiles();
|
||
|
else
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
TorrentFileInterface & TorrentControl::getTorrentFile(Uint32 index)
|
||
|
{
|
||
|
if (tor)
|
||
|
return tor->getFile(index);
|
||
|
else
|
||
|
return TorrentFile::null;
|
||
|
}
|
||
|
|
||
|
void TorrentControl::migrateTorrent(const QString & default_save_dir)
|
||
|
{
|
||
|
if (bt::Exists(datadir + "current_chunks") && bt::IsPreMMap(datadir + "current_chunks"))
|
||
|
{
|
||
|
// in case of error copy torX dir to migrate-failed-tor
|
||
|
QString dd = datadir;
|
||
|
int pos = dd.findRev("tor");
|
||
|
if (pos != - 1)
|
||
|
{
|
||
|
dd = dd.replace(pos,3,"migrate-failed-tor");
|
||
|
Out() << "Copying " << datadir << " to " << dd << endl;
|
||
|
bt::CopyDir(datadir,dd,true);
|
||
|
}
|
||
|
|
||
|
bt::MigrateCurrentChunks(*tor,datadir + "current_chunks");
|
||
|
if (outputdir.isNull() && bt::IsCacheMigrateNeeded(*tor,datadir + "cache"))
|
||
|
{
|
||
|
// if the output dir is NULL
|
||
|
if (default_save_dir.isNull())
|
||
|
{
|
||
|
KMessageBox::information(0,
|
||
|
i18n("The torrent %1 was started with a previous version of KTorrent."
|
||
|
" To make sure this torrent still works with this version of KTorrent, "
|
||
|
"we will migrate this torrent. You will be asked for a location to save "
|
||
|
"the torrent to. If you press cancel, we will select your home directory.")
|
||
|
.arg(tor->getNameSuggestion()));
|
||
|
outputdir = KFileDialog::getExistingDirectory(QString::null, 0,i18n("Select Folder to Save To"));
|
||
|
if (outputdir.isNull())
|
||
|
outputdir = QDir::homeDirPath();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
outputdir = default_save_dir;
|
||
|
}
|
||
|
|
||
|
if (!outputdir.endsWith(bt::DirSeparator()))
|
||
|
outputdir += bt::DirSeparator();
|
||
|
|
||
|
bt::MigrateCache(*tor,datadir + "cache",outputdir);
|
||
|
}
|
||
|
|
||
|
// delete backup
|
||
|
if (pos != - 1)
|
||
|
bt::Delete(dd);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void TorrentControl::setPriority(int p)
|
||
|
{
|
||
|
istats.priority = p;
|
||
|
stats.user_controlled = p == 0 ? true : false;
|
||
|
if(p)
|
||
|
stats.status = kt::QUEUED;
|
||
|
else
|
||
|
updateStatusMsg();
|
||
|
|
||
|
saveStats();
|
||
|
}
|
||
|
|
||
|
void TorrentControl::setMaxShareRatio(float ratio)
|
||
|
{
|
||
|
if(ratio == 1.00f)
|
||
|
{
|
||
|
if (stats.max_share_ratio != ratio)
|
||
|
stats.max_share_ratio = ratio;
|
||
|
}
|
||
|
else
|
||
|
stats.max_share_ratio = ratio;
|
||
|
|
||
|
if(stats.completed && !stats.running && !stats.user_controlled && (kt::ShareRatio(stats) >= stats.max_share_ratio))
|
||
|
setPriority(0); //dequeue it
|
||
|
|
||
|
saveStats();
|
||
|
emit maxRatioChanged(this);
|
||
|
}
|
||
|
|
||
|
void TorrentControl::setMaxSeedTime(float hours)
|
||
|
{
|
||
|
stats.max_seed_time = hours;
|
||
|
saveStats();
|
||
|
}
|
||
|
|
||
|
bool TorrentControl::overMaxRatio()
|
||
|
{
|
||
|
if(stats.completed && stats.bytes_uploaded != 0 && stats.bytes_downloaded != 0 && stats.max_share_ratio > 0)
|
||
|
{
|
||
|
if(kt::ShareRatio(stats) >= stats.max_share_ratio)
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
bool TorrentControl::overMaxSeedTime()
|
||
|
{
|
||
|
if(stats.completed && stats.bytes_uploaded != 0 && stats.bytes_downloaded != 0 && stats.max_seed_time > 0)
|
||
|
{
|
||
|
Uint32 dl = getRunningTimeDL();
|
||
|
Uint32 ul = getRunningTimeUL();
|
||
|
if ((ul - dl) / 3600.0f > stats.max_seed_time)
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
|
||
|
QString TorrentControl::statusToString() const
|
||
|
{
|
||
|
switch (stats.status)
|
||
|
{
|
||
|
case kt::NOT_STARTED :
|
||
|
return i18n("Not started");
|
||
|
case kt::DOWNLOAD_COMPLETE :
|
||
|
return i18n("Download completed");
|
||
|
case kt::SEEDING_COMPLETE :
|
||
|
return i18n("Seeding completed");
|
||
|
case kt::SEEDING :
|
||
|
return i18n("Seeding");
|
||
|
case kt::DOWNLOADING:
|
||
|
return i18n("Downloading");
|
||
|
case kt::STALLED:
|
||
|
return i18n("Stalled");
|
||
|
case kt::STOPPED:
|
||
|
return i18n("Stopped");
|
||
|
case kt::ERROR :
|
||
|
return i18n("Error: ") + getShortErrorMessage();
|
||
|
case kt::ALLOCATING_DISKSPACE:
|
||
|
return i18n("Allocating diskspace");
|
||
|
case kt::QUEUED:
|
||
|
return i18n("Queued");
|
||
|
case kt::CHECKING_DATA:
|
||
|
return i18n("Checking data");
|
||
|
case kt::NO_SPACE_LEFT:
|
||
|
return i18n("Stopped. No space left on device.");
|
||
|
}
|
||
|
return QString::null;
|
||
|
}
|
||
|
|
||
|
TrackersList* TorrentControl::getTrackersList()
|
||
|
{
|
||
|
return psman;
|
||
|
}
|
||
|
|
||
|
const TrackersList* TorrentControl::getTrackersList() const
|
||
|
{
|
||
|
return psman;
|
||
|
}
|
||
|
|
||
|
void TorrentControl::onPortPacket(const QString & ip,Uint16 port)
|
||
|
{
|
||
|
if (Globals::instance().getDHT().isRunning() && !stats.priv_torrent)
|
||
|
Globals::instance().getDHT().portRecieved(ip,port);
|
||
|
}
|
||
|
|
||
|
void TorrentControl::startDataCheck(bt::DataCheckerListener* lst,bool auto_import)
|
||
|
{
|
||
|
if (stats.status == kt::ALLOCATING_DISKSPACE)
|
||
|
return;
|
||
|
|
||
|
|
||
|
DataChecker* dc = 0;
|
||
|
stats.status = kt::CHECKING_DATA;
|
||
|
stats.num_corrupted_chunks = 0; // reset the number of corrupted chunks found
|
||
|
if (stats.multi_file_torrent)
|
||
|
dc = new MultiDataChecker();
|
||
|
else
|
||
|
dc = new SingleDataChecker();
|
||
|
|
||
|
dc->setListener(lst);
|
||
|
|
||
|
dcheck_thread = new DataCheckerThread(dc,stats.output_path,*tor,datadir + "dnd" + bt::DirSeparator());
|
||
|
|
||
|
// dc->check(stats.output_path,*tor,datadir + "dnd" + bt::DirSeparator());
|
||
|
dcheck_thread->start();
|
||
|
}
|
||
|
|
||
|
void TorrentControl::afterDataCheck()
|
||
|
{
|
||
|
DataChecker* dc = dcheck_thread->getDataChecker();
|
||
|
DataCheckerListener* lst = dc->getListener();
|
||
|
|
||
|
bool err = !dcheck_thread->getError().isNull();
|
||
|
if (err)
|
||
|
{
|
||
|
// show a queued error message when an error has occurred
|
||
|
KMessageBox::queuedMessageBox(0,KMessageBox::Error,dcheck_thread->getError());
|
||
|
lst->stop();
|
||
|
}
|
||
|
|
||
|
if (lst && !lst->isStopped())
|
||
|
{
|
||
|
down->dataChecked(dc->getDownloaded());
|
||
|
// update chunk manager
|
||
|
cman->dataChecked(dc->getDownloaded());
|
||
|
if (lst->isAutoImport())
|
||
|
{
|
||
|
down->recalcDownloaded();
|
||
|
stats.imported_bytes = down->bytesDownloaded();
|
||
|
if (cman->haveAllChunks())
|
||
|
stats.completed = true;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Uint64 downloaded = stats.bytes_downloaded;
|
||
|
down->recalcDownloaded();
|
||
|
updateStats();
|
||
|
if (stats.bytes_downloaded > downloaded)
|
||
|
stats.imported_bytes = stats.bytes_downloaded - downloaded;
|
||
|
|
||
|
if (cman->haveAllChunks())
|
||
|
stats.completed = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
stats.status = kt::NOT_STARTED;
|
||
|
// update the status
|
||
|
updateStatusMsg();
|
||
|
updateStats();
|
||
|
if (lst)
|
||
|
lst->finished();
|
||
|
delete dcheck_thread;
|
||
|
dcheck_thread = 0;
|
||
|
}
|
||
|
|
||
|
bool TorrentControl::isCheckingData(bool & finished) const
|
||
|
{
|
||
|
if (dcheck_thread)
|
||
|
{
|
||
|
finished = !dcheck_thread->isRunning();
|
||
|
return true;
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
bool TorrentControl::hasExistingFiles() const
|
||
|
{
|
||
|
return cman->hasExistingFiles();
|
||
|
}
|
||
|
|
||
|
bool TorrentControl::hasMissingFiles(QStringList & sl)
|
||
|
{
|
||
|
return cman->hasMissingFiles(sl);
|
||
|
}
|
||
|
|
||
|
void TorrentControl::recreateMissingFiles()
|
||
|
{
|
||
|
try
|
||
|
{
|
||
|
cman->recreateMissingFiles();
|
||
|
prealloc = true; // set prealloc to true so files will be truncated again
|
||
|
down->dataChecked(cman->getBitSet()); // update chunk selector
|
||
|
}
|
||
|
catch (Error & err)
|
||
|
{
|
||
|
onIOError(err.toString());
|
||
|
throw;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void TorrentControl::dndMissingFiles()
|
||
|
{
|
||
|
try
|
||
|
{
|
||
|
cman->dndMissingFiles();
|
||
|
prealloc = true; // set prealloc to true so files will be truncated again
|
||
|
missingFilesMarkedDND(this);
|
||
|
down->dataChecked(cman->getBitSet()); // update chunk selector
|
||
|
}
|
||
|
catch (Error & err)
|
||
|
{
|
||
|
onIOError(err.toString());
|
||
|
throw;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void TorrentControl::handleError(const QString & err)
|
||
|
{
|
||
|
onIOError(err);
|
||
|
}
|
||
|
|
||
|
Uint32 TorrentControl::getNumDHTNodes() const
|
||
|
{
|
||
|
return tor->getNumDHTNodes();
|
||
|
}
|
||
|
|
||
|
const kt::DHTNode & TorrentControl::getDHTNode(Uint32 i) const
|
||
|
{
|
||
|
return tor->getDHTNode(i);
|
||
|
}
|
||
|
|
||
|
void TorrentControl::deleteDataFiles()
|
||
|
{
|
||
|
cman->deleteDataFiles();
|
||
|
}
|
||
|
|
||
|
const bt::SHA1Hash & TorrentControl::getInfoHash() const
|
||
|
{
|
||
|
return tor->getInfoHash();
|
||
|
}
|
||
|
|
||
|
void TorrentControl::resetTrackerStats()
|
||
|
{
|
||
|
istats.trk_prev_bytes_dl = stats.bytes_downloaded,
|
||
|
istats.trk_prev_bytes_ul = stats.bytes_uploaded,
|
||
|
stats.trk_bytes_downloaded = 0;
|
||
|
stats.trk_bytes_uploaded = 0;
|
||
|
}
|
||
|
|
||
|
void TorrentControl::trackerStatusChanged(const QString & ns)
|
||
|
{
|
||
|
stats.trackerstatus = ns;
|
||
|
}
|
||
|
|
||
|
void TorrentControl::addPeerSource(kt::PeerSource* ps)
|
||
|
{
|
||
|
if (psman)
|
||
|
psman->addPeerSource(ps);
|
||
|
}
|
||
|
|
||
|
void TorrentControl::removePeerSource(kt::PeerSource* ps)
|
||
|
{
|
||
|
if (psman)
|
||
|
psman->removePeerSource(ps);
|
||
|
}
|
||
|
|
||
|
void TorrentControl::corrupted(Uint32 chunk)
|
||
|
{
|
||
|
// make sure we will redownload the chunk
|
||
|
down->corrupted(chunk);
|
||
|
if (stats.completed)
|
||
|
stats.completed = false;
|
||
|
|
||
|
// emit signal to show a systray message
|
||
|
stats.num_corrupted_chunks++;
|
||
|
corruptedDataFound(this);
|
||
|
}
|
||
|
|
||
|
Uint32 TorrentControl::getETA()
|
||
|
{
|
||
|
return m_eta->estimate();
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
const bt::PeerID & TorrentControl::getOwnPeerID() const
|
||
|
{
|
||
|
return tor->getPeerID();
|
||
|
}
|
||
|
|
||
|
|
||
|
bool TorrentControl::isFeatureEnabled(TorrentFeature tf)
|
||
|
{
|
||
|
switch (tf)
|
||
|
{
|
||
|
case kt::DHT_FEATURE:
|
||
|
return psman->dhtStarted();
|
||
|
case kt::UT_PEX_FEATURE:
|
||
|
return pman->isPexEnabled();
|
||
|
default:
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void TorrentControl::setFeatureEnabled(TorrentFeature tf,bool on)
|
||
|
{
|
||
|
switch (tf)
|
||
|
{
|
||
|
case kt::DHT_FEATURE:
|
||
|
if (on)
|
||
|
{
|
||
|
if(!stats.priv_torrent)
|
||
|
{
|
||
|
psman->addDHT();
|
||
|
istats.dht_on = psman->dhtStarted();
|
||
|
saveStats();
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
psman->removeDHT();
|
||
|
istats.dht_on = false;
|
||
|
saveStats();
|
||
|
}
|
||
|
break;
|
||
|
case kt::UT_PEX_FEATURE:
|
||
|
if (on)
|
||
|
{
|
||
|
if (!stats.priv_torrent && !pman->isPexEnabled())
|
||
|
{
|
||
|
pman->setPexEnabled(true);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
pman->setPexEnabled(false);
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void TorrentControl::createFiles()
|
||
|
{
|
||
|
cman->createFiles(true);
|
||
|
stats.output_path = cman->getOutputPath();
|
||
|
}
|
||
|
|
||
|
bool TorrentControl::checkDiskSpace(bool emit_sig)
|
||
|
{
|
||
|
last_diskspace_check = bt::GetCurrentTime();
|
||
|
|
||
|
//calculate free disk space
|
||
|
Uint64 bytes_free = 0;
|
||
|
if (FreeDiskSpace(getDataDir(),bytes_free))
|
||
|
{
|
||
|
Uint64 bytes_to_download = stats.total_bytes_to_download;
|
||
|
Uint64 downloaded = 0;
|
||
|
try
|
||
|
{
|
||
|
downloaded = cman->diskUsage();
|
||
|
}
|
||
|
catch (bt::Error & err)
|
||
|
{
|
||
|
Out(SYS_GEN|LOG_DEBUG) << "Error : " << err.toString() << endl;
|
||
|
}
|
||
|
Uint64 remaining = 0;
|
||
|
if (downloaded <= bytes_to_download)
|
||
|
remaining = bytes_to_download - downloaded;
|
||
|
|
||
|
if (remaining > bytes_free)
|
||
|
{
|
||
|
bool toStop = bytes_free < (Uint64) Settings::minDiskSpace() * 1024 * 1024;
|
||
|
|
||
|
// if we don't need to stop the torrent, only emit the signal once
|
||
|
// so that we do bother the user continously
|
||
|
if (emit_sig && (toStop || !istats.diskspace_warning_emitted))
|
||
|
{
|
||
|
emit diskSpaceLow(this, toStop);
|
||
|
istats.diskspace_warning_emitted = true;
|
||
|
}
|
||
|
|
||
|
if (!stats.running)
|
||
|
{
|
||
|
stats.status = NO_SPACE_LEFT;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
void TorrentControl::setTrafficLimits(Uint32 up,Uint32 down)
|
||
|
{
|
||
|
net::SocketMonitor & smon = net::SocketMonitor::instance();
|
||
|
if (up && !upload_gid)
|
||
|
{
|
||
|
// create upload group
|
||
|
upload_gid = smon.newGroup(net::SocketMonitor::UPLOAD_GROUP,up);
|
||
|
upload_limit = up;
|
||
|
}
|
||
|
else if (up && upload_gid)
|
||
|
{
|
||
|
// change existing group limit
|
||
|
smon.setGroupLimit(net::SocketMonitor::UPLOAD_GROUP,upload_gid,up);
|
||
|
upload_limit = up;
|
||
|
}
|
||
|
else if (!up && !upload_gid)
|
||
|
{
|
||
|
upload_limit = up;
|
||
|
}
|
||
|
else // !up && upload_gid
|
||
|
{
|
||
|
// remove existing group
|
||
|
smon.removeGroup(net::SocketMonitor::UPLOAD_GROUP,upload_gid);
|
||
|
upload_gid = upload_limit = 0;
|
||
|
}
|
||
|
|
||
|
if (down && !download_gid)
|
||
|
{
|
||
|
// create download grodown
|
||
|
download_gid = smon.newGroup(net::SocketMonitor::DOWNLOAD_GROUP,down);
|
||
|
download_limit = down;
|
||
|
}
|
||
|
else if (down && download_gid)
|
||
|
{
|
||
|
// change existing grodown limit
|
||
|
smon.setGroupLimit(net::SocketMonitor::DOWNLOAD_GROUP,download_gid,down);
|
||
|
download_limit = down;
|
||
|
}
|
||
|
else if (!down && !download_gid)
|
||
|
{
|
||
|
download_limit = down;
|
||
|
}
|
||
|
else // !down && download_gid
|
||
|
{
|
||
|
// remove existing grodown
|
||
|
smon.removeGroup(net::SocketMonitor::DOWNLOAD_GROUP,download_gid);
|
||
|
download_gid = download_limit = 0;
|
||
|
}
|
||
|
|
||
|
saveStats();
|
||
|
pman->setGroupIDs(upload_gid,download_gid);
|
||
|
}
|
||
|
|
||
|
void TorrentControl::getTrafficLimits(Uint32 & up,Uint32 & down)
|
||
|
{
|
||
|
up = upload_limit;
|
||
|
down = download_limit;
|
||
|
}
|
||
|
|
||
|
const PeerManager * TorrentControl::getPeerMgr() const
|
||
|
{
|
||
|
return pman;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#include "torrentcontrol.moc"
|