/***************************************************************************
 *   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 <tqdir.h>
#include <tqfile.h>
#include <tdelocale.h>
#include <tdemessagebox.h>
#include <tdefiledialog.h>
#include <tqtextstream.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 = TQString();
		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();
				TQDateTime now = TQDateTime::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 = TQDateTime::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)
			{
				TQString outdir = Settings::completedDir();
				if(!outdir.endsWith(bt::DirSeparator()))
					outdir += bt::DirSeparator();
				
				changeOutputDir(outdir);
			}
		}
		catch (Error & e)
		{
			onIOError(e.toString());
		}
	}

	void TorrentControl::onIOError(const TQString & 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 = TQDateTime::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)
	{
		TQDateTime now = TQDateTime::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 TQString & torrent,
							  const TQString & tmpdir,
							  const TQString & ddir,
							  const TQString & 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
		TQString tor_copy = datadir + "torrent";
		if (tor_copy != torrent)
		{
			bt::CopyFile(torrent,tor_copy);
		}
		
	}
	
	
	void TorrentControl::init(QueueManager* qman, const TQByteArray & data,const TQString & tmpdir,
							  const TQString & ddir,const TQString & 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
		TQString tor_copy = datadir + "torrent";
		TQFile 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 TQString & tmpdir,const TQString & 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 TQString & 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,TQ_SIGNAL(statusChanged( const TQString& )),
				this,TQ_SLOT(trackerStatusChanged( const TQString& )));


		// 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,TQ_SIGNAL(updateStats()),this,TQ_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,TQ_SIGNAL(ioError(const TQString& )),
				this,TQ_SLOT(onIOError(const TQString& )));
		up = new Uploader(*cman,*pman);
		choke = new Choker(*pman,*cman);


		connect(pman,TQ_SIGNAL(newPeer(Peer* )),this,TQ_SLOT(onNewPeer(Peer* )));
		connect(pman,TQ_SIGNAL(peerKilled(Peer* )),this,TQ_SLOT(onPeerRemoved(Peer* )));
		connect(cman,TQ_SIGNAL(excluded(Uint32, Uint32 )),down,TQ_SLOT(onExcluded(Uint32, Uint32 )));
		connect(cman,TQ_SIGNAL(included( Uint32, Uint32 )),down,TQ_SLOT(onIncluded( Uint32, Uint32 )));
		connect(cman,TQ_SIGNAL(corrupted( Uint32 )),this,TQ_SLOT(corrupted( Uint32 )));
	}
	
	void TorrentControl::initInternal(QueueManager* qman,
									  const TQString & tmpdir,
									  const TQString & ddir,
									  const TQString & 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,TQ_SIGNAL(gotPortPacket( const TQString&, Uint16 )),
				this,TQ_SLOT(onPortPacket( const TQString&, 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,TQ_SIGNAL(gotPortPacket( const TQString&, Uint16 )),
				this,TQ_SLOT(onPortPacket( const TQString&, Uint16 )));
		if (tmon)
			tmon->peerRemoved(p);
	}

	void TorrentControl::doChoking()
	{
		choke->update(stats.completed,stats);
	}

	bool TorrentControl::changeDataDir(const TQString & 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;
		}
		
		TQString 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 TQString & 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
		{
			TQString 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)
			{
				TDEIO::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,TQ_SIGNAL(result(TDEIO::Job*)),this,TQ_SLOT(moveDataFilesJobDone(TDEIO::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(TDEIO::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", TQString::number(up->bytesUploaded()));
		
		if (stats.running)
		{
			TQDateTime now = TQDateTime::currentDateTime();
			st.write("RUNNING_TIME_DL",TQString("%1").arg(istats.running_time_dl + istats.time_started_dl.secsTo(now)));
			st.write("RUNNING_TIME_UL",TQString("%1").arg(istats.running_time_ul + istats.time_started_ul.secsTo(now)));
		}
		else
		{
			st.write("RUNNING_TIME_DL", TQString("%1").arg(istats.running_time_dl));
			st.write("RUNNING_TIME_UL", TQString("%1").arg(istats.running_time_ul));
		}
		
		st.write("PRIORITY", TQString("%1").arg(istats.priority));
		st.write("AUTOSTART", TQString("%1").arg(stats.autostart));
		st.write("IMPORTED", TQString("%1").arg(stats.imported_bytes));
		st.write("CUSTOM_OUTPUT_NAME",istats.custom_output_name ? "1" : "0");
		st.write("MAX_RATIO", TQString("%1").arg(stats.max_share_ratio,0,'f',2));
		st.write("MAX_SEED_TIME",TQString::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",TQString::number(upload_limit));
		st.write("DOWNLOAD_LIMIT",TQString::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(TQDateTime::currentDateTime());
	}

	Uint32 TorrentControl::getRunningTimeUL() const
	{
		if (!stats.running)
			return istats.running_time_ul;
		else
			return istats.running_time_ul + istats.time_started_ul.secsTo(TQDateTime::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 TQString & 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
			TQString 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(TQString(), 0,i18n("Select Folder to Save To"));
					if (outputdir.isNull())
						outputdir = TQDir::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;
	}

	
	TQString 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 TQString();
	}

	TrackersList* TorrentControl::getTrackersList()
	{
		return psman;
	}
	
	const TrackersList* TorrentControl::getTrackersList() const 
	{
		return psman;
	}

	void TorrentControl::onPortPacket(const TQString & 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(TQStringList & 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 TQString & 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 TQString & 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"
