diff options
Diffstat (limited to 'libktorrent')
293 files changed, 45035 insertions, 0 deletions
diff --git a/libktorrent/Makefile.am b/libktorrent/Makefile.am new file mode 100644 index 0000000..cea2eaa --- /dev/null +++ b/libktorrent/Makefile.am @@ -0,0 +1,22 @@ +INCLUDES = -I$(srcdir)/. $(all_includes) +SUBDIRS = util torrent kademlia interfaces migrate mse datachecker net +METASOURCES = AUTO + +lib_LTLIBRARIES = libktorrent.la +libktorrent_la_LDFLAGS = ${KDE_RPATH} $(all_libraries) -release $(VERSION) + +kde_kcfg_DATA = ktorrent.kcfg + +# make sure settings.h is built before anything else +BUILT_SOURCES=settings.h + +noinst_HEADERS = expandablewidget.h functions.h ktversion.h labelview.h \ + pluginmanager.h pluginmanagerprefpage.h +libktorrent_la_SOURCES = expandablewidget.cpp functions.cpp labelview.cpp \ + labelviewitembase.ui pluginmanager.cpp pluginmanagerprefpage.cpp pluginmanagerwidget.ui \ + settings.kcfgc +libktorrent_la_LIBADD = ../libktorrent/net/libnet.la \ + ../libktorrent/datachecker/libdatachecker.la ../libktorrent/mse/libmse.la ../libktorrent/migrate/libmigrate.la \ + ../libktorrent/util/libutil.la ../libktorrent/torrent/libtorrent.la \ + ../libktorrent/kademlia/libkademlia.la ../libktorrent/interfaces/libinterfaces.la $(LIB_KPARTS) +KDE_CXXFLAGS = $(USE_EXCEPTIONS) $(USE_RTTI) diff --git a/libktorrent/datachecker/Makefile.am b/libktorrent/datachecker/Makefile.am new file mode 100644 index 0000000..b5e9ee0 --- /dev/null +++ b/libktorrent/datachecker/Makefile.am @@ -0,0 +1,8 @@ +INCLUDES = -I$(srcdir)/.. -I$(srcdir)/. $(all_includes) +METASOURCES = AUTO +libdatachecker_la_LDFLAGS = $(all_libraries) +noinst_LTLIBRARIES = libdatachecker.la +libdatachecker_la_SOURCES = datachecker.cpp multidatachecker.cpp \ + singledatachecker.cpp datacheckerlistener.cpp datacheckerthread.cpp +noinst_HEADERS = datacheckerlistener.h datacheckerthread.h +KDE_CXXFLAGS = $(USE_EXCEPTIONS) $(USE_RTTI) diff --git a/libktorrent/datachecker/datachecker.cpp b/libktorrent/datachecker/datachecker.cpp new file mode 100644 index 0000000..04bd08e --- /dev/null +++ b/libktorrent/datachecker/datachecker.cpp @@ -0,0 +1,34 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 "datachecker.h" + +namespace bt { + + DataChecker::DataChecker() : listener(0) + { + } + + + DataChecker::~DataChecker() + { + } + + +} diff --git a/libktorrent/datachecker/datachecker.h b/libktorrent/datachecker/datachecker.h new file mode 100644 index 0000000..e181925 --- /dev/null +++ b/libktorrent/datachecker/datachecker.h @@ -0,0 +1,78 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef BTDATACHECKER_H +#define BTDATACHECKER_H + +#include <util/bitset.h> +#include "datacheckerlistener.h" + +class QString; + + +namespace bt +{ + class Torrent; + + + /** + * @author Joris Guisson + * + * Checks which data is downloaded, given a torrent and a file or directory containing + * files of the torrent. + */ + class DataChecker + { + public: + DataChecker(); + virtual ~DataChecker(); + + /// Set the listener + void setListener(DataCheckerListener* l) {listener = l;} + + /** + * Check to see which chunks have been downloaded of a torrent, and which chunks fail. + * The corresponding bitsets should be filled with this information. + * If anything goes wrong and Error should be thrown. + * @param path path to the file or dir (this needs to end with the name suggestion of the torrent) + * @param tor The torrent + * @param dnddir DND dir, optional argument if we know this + */ + virtual void check(const QString & path,const Torrent & tor,const QString & dnddir) = 0; + + /** + * Get the BitSet representing all the downloaded chunks. + */ + const BitSet & getDownloaded() const {return downloaded;} + + /** + * Get the BitSet representing all the failed chunks. + */ + const BitSet & getFailed() const {return failed;} + + /// Get the listener + DataCheckerListener* getListener() {return listener;} + protected: + BitSet failed,downloaded; + DataCheckerListener* listener; + }; + +} + +#endif diff --git a/libktorrent/datachecker/datacheckerlistener.cpp b/libktorrent/datachecker/datacheckerlistener.cpp new file mode 100644 index 0000000..a4a2201 --- /dev/null +++ b/libktorrent/datachecker/datacheckerlistener.cpp @@ -0,0 +1,33 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 "datacheckerlistener.h" + +namespace bt +{ + + DataCheckerListener::DataCheckerListener(bool auto_import) : stopped(false),auto_import(auto_import) + {} + + + DataCheckerListener::~DataCheckerListener() + {} + + +} diff --git a/libktorrent/datachecker/datacheckerlistener.h b/libktorrent/datachecker/datacheckerlistener.h new file mode 100644 index 0000000..a770bab --- /dev/null +++ b/libktorrent/datachecker/datacheckerlistener.h @@ -0,0 +1,80 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef BTDATACHECKERLISTENER_H +#define BTDATACHECKERLISTENER_H + +#include <util/constants.h> + +namespace bt +{ + + /** + @author Joris Guisson <joris.guisson@gmail.com> + */ + class DataCheckerListener + { + public: + DataCheckerListener(bool auto_import); + virtual ~DataCheckerListener(); + + /** + * Called when a chunk has been proccessed. + * @param num The number processed + * @param total The total number of pieces to process + */ + virtual void progress(Uint32 num,Uint32 total) = 0; + + /** + * Called when a failed or dowloaded chunk is found. + * @param num_failed The number of failed chunks + * @param num_downloaded Number of downloaded chunks + */ + virtual void status(Uint32 num_failed,Uint32 num_downloaded) = 0; + + /** + * Data check has been finished. + */ + virtual void finished() = 0; + + /** + * Test if we need to stop. + */ + bool needToStop() const {return stopped;} + + /// Check if the check has been stopped + bool isStopped() const {return stopped;} + + /// Is this an auto_import + bool isAutoImport() const {return auto_import;} + + /** + * Stop the data check. + */ + void stop() {stopped = true;} + private: + bool stopped; + + protected: + bool auto_import; + }; + +} + +#endif diff --git a/libktorrent/datachecker/datacheckerthread.cpp b/libktorrent/datachecker/datacheckerthread.cpp new file mode 100644 index 0000000..12a58d7 --- /dev/null +++ b/libktorrent/datachecker/datacheckerthread.cpp @@ -0,0 +1,58 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <util/log.h> +#include <util/error.h> +#include <torrent/torrent.h> +#include "datachecker.h" +#include "datacheckerthread.h" + +namespace bt +{ + + DataCheckerThread::DataCheckerThread(DataChecker* dc, + const QString & path, + const Torrent & tor, + const QString & dnddir) + : dc(dc),path(path),tor(tor),dnddir(dnddir) + { + running = true; + } + + + DataCheckerThread::~DataCheckerThread() + { + delete dc; + } + + void DataCheckerThread::run() + { + try + { + dc->check(path,tor,dnddir); + } + catch (bt::Error & e) + { + error = e.toString(); + Out(SYS_GEN|LOG_DEBUG) << error << endl; + } + running = false; + } + +} diff --git a/libktorrent/datachecker/datacheckerthread.h b/libktorrent/datachecker/datacheckerthread.h new file mode 100644 index 0000000..749e3e8 --- /dev/null +++ b/libktorrent/datachecker/datacheckerthread.h @@ -0,0 +1,61 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef BTDATACHECKERTHREAD_H +#define BTDATACHECKERTHREAD_H + +#include <qthread.h> + +namespace bt +{ + class Torrent; + class DataChecker; + + /** + @author Joris Guisson <joris.guisson@gmail.com> + + Thread which runs the data check. + */ + class DataCheckerThread : public QThread + { + DataChecker* dc; + QString path; + const Torrent & tor; + QString dnddir; + bool running; + QString error; + public: + DataCheckerThread(DataChecker* dc,const QString & path,const Torrent & tor,const QString & dnddir); + virtual ~DataCheckerThread(); + + virtual void run(); + + /// Get the data checker + DataChecker* getDataChecker() {return dc;} + + /// Are we still running + bool isRunning() const {return running;} + + /// Get the error (if any occured) + QString getError() const {return error;} + }; + +} + +#endif diff --git a/libktorrent/datachecker/multidatachecker.cpp b/libktorrent/datachecker/multidatachecker.cpp new file mode 100644 index 0000000..3c26721 --- /dev/null +++ b/libktorrent/datachecker/multidatachecker.cpp @@ -0,0 +1,201 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson & Maggioni Marcello * + * joris.guisson@gmail.com * + * marcello.maggioni@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 <klocale.h> +#include <kapplication.h> +#include <util/log.h> +#include <util/file.h> +#include <util/fileops.h> +#include <util/error.h> +#include <util/array.h> +#include <util/functions.h> +#include <torrent/dndfile.h> +#include <torrent/globals.h> +#include <torrent/torrent.h> +#include <torrent/torrentfile.h> +#include "multidatachecker.h" + +namespace bt +{ + + MultiDataChecker::MultiDataChecker(): DataChecker(),buf(0) + {} + + + MultiDataChecker::~MultiDataChecker() + { + delete [] buf; + } + + void MultiDataChecker::check(const QString& path, const Torrent& tor,const QString & dnddir) + { + Uint32 num_chunks = tor.getNumChunks(); + // initialize the bitsets + downloaded = BitSet(num_chunks); + failed = BitSet(num_chunks); + + cache = path; + if (!cache.endsWith(bt::DirSeparator())) + cache += bt::DirSeparator(); + + dnd_dir = dnddir; + if (!dnddir.endsWith(bt::DirSeparator())) + dnd_dir += bt::DirSeparator(); + + Uint64 chunk_size = tor.getChunkSize(); + Uint32 cur_chunk = 0; + TimeStamp last_update_time = bt::GetCurrentTime(); + + buf = new Uint8[chunk_size]; + + for (cur_chunk = 0;cur_chunk < num_chunks;cur_chunk++) + { + Uint32 cs = (cur_chunk == num_chunks - 1) ? tor.getFileLength() % chunk_size : chunk_size; + if (cs == 0) + cs = chunk_size; + if (!loadChunk(cur_chunk,cs,tor)) + { + downloaded.set(cur_chunk,false); + failed.set(cur_chunk,true); + continue; + } + + bool ok = (SHA1Hash::generate(buf,cs) == tor.getHash(cur_chunk)); + downloaded.set(cur_chunk,ok); + failed.set(cur_chunk,!ok); + + if (listener) + { + listener->status(failed.numOnBits(),downloaded.numOnBits()); + listener->progress(cur_chunk,num_chunks); + if (listener->needToStop()) + return; + } + + TimeStamp now = bt::GetCurrentTime(); + if (now - last_update_time > 1000) + { + Out() << "Checked " << cur_chunk << " chunks" << endl; + // KApplication::kApplication()->processEvents(); + last_update_time = now; + } + } + } + + static Uint32 ReadFullChunk(Uint32 chunk,Uint32 cs, + const TorrentFile & tf, + const Torrent & tor, + Uint8* buf, + const QString & cache) + { + File fptr; + if (!fptr.open(cache + tf.getPath(), "rb")) + { + Out() << QString("Warning : Cannot open %1 : %2").arg(cache + + tf.getPath()).arg(fptr.errorString()) << endl; + return 0; + } + + Uint64 off = tf.fileOffset(chunk,tor.getChunkSize()); + fptr.seek(File::BEGIN,off); + return fptr.read(buf,cs); + } + + bool MultiDataChecker::loadChunk(Uint32 ci,Uint32 cs,const Torrent & tor) + { + QValueList<Uint32> tflist; + tor.calcChunkPos(ci,tflist); + + // one file is simple + if (tflist.count() == 1) + { + const TorrentFile & f = tor.getFile(tflist.first()); + if (!f.doNotDownload()) + { + ReadFullChunk(ci,cs,f,tor,buf,cache); + return true; + } + return false; + } + + Uint64 read = 0; // number of bytes read + for (Uint32 i = 0;i < tflist.count();i++) + { + const TorrentFile & f = tor.getFile(tflist[i]); + + // first calculate offset into file + // only the first file can have an offset + // the following files will start at the beginning + Uint64 off = 0; + if (i == 0) + off = f.fileOffset(ci,tor.getChunkSize()); + + Uint32 to_read = 0; + // then the amount of data we can read from this file + if (i == 0) + to_read = f.getLastChunkSize(); + else if (i == tflist.count() - 1) + to_read = cs - read; + else + to_read = f.getSize(); + + // read part of data + if (f.doNotDownload()) + { + if (!dnd_dir.isNull() && bt::Exists(dnd_dir + f.getPath() + ".dnd")) + { + Uint32 ret = 0; + DNDFile dfd(dnd_dir + f.getPath() + ".dnd"); + if (i == 0) + ret = dfd.readLastChunk(buf,read,cs); + else if (i == tflist.count() - 1) + ret = dfd.readFirstChunk(buf,read,cs); + else + ret = dfd.readFirstChunk(buf,read,cs); + + if (ret > 0 && ret != to_read) + Out() << "Warning : MultiDataChecker::load ret != to_read (dnd)" << endl; + } + } + else + { + if (!bt::Exists(cache + f.getPath()) || bt::FileSize(cache + f.getPath()) < off) + return false; + + File fptr; + if (!fptr.open(cache + f.getPath(), "rb")) + { + Out() << QString("Warning : Cannot open %1 : %2").arg(cache + + f.getPath()).arg(fptr.errorString()) << endl; + return false; + } + else + { + fptr.seek(File::BEGIN,off); + if (fptr.read(buf+read,to_read) != to_read) + Out() << "Warning : MultiDataChecker::load ret != to_read" << endl; + } + } + read += to_read; + } + return true; + } +} diff --git a/libktorrent/datachecker/multidatachecker.h b/libktorrent/datachecker/multidatachecker.h new file mode 100644 index 0000000..d095e99 --- /dev/null +++ b/libktorrent/datachecker/multidatachecker.h @@ -0,0 +1,49 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef BTMULTIDATACHECKER_H +#define BTMULTIDATACHECKER_H + +#include "datachecker.h" + +namespace bt +{ + + /** + @author Joris Guisson + */ + class MultiDataChecker : public DataChecker + { + public: + MultiDataChecker(); + virtual ~MultiDataChecker(); + + virtual void check(const QString& path, const Torrent& tor,const QString & dnddir); + private: + bool loadChunk(Uint32 ci,Uint32 cs,const Torrent & to); + + private: + QString cache; + QString dnd_dir; + Uint8* buf; + }; + +} + +#endif diff --git a/libktorrent/datachecker/singledatachecker.cpp b/libktorrent/datachecker/singledatachecker.cpp new file mode 100644 index 0000000..0579338 --- /dev/null +++ b/libktorrent/datachecker/singledatachecker.cpp @@ -0,0 +1,103 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <klocale.h> +#include <kapplication.h> +#include <util/log.h> +#include <util/file.h> +#include <util/error.h> +#include <util/array.h> +#include <util/functions.h> +#include <torrent/globals.h> +#include <torrent/torrent.h> +#include "singledatachecker.h" + +namespace bt +{ + + SingleDataChecker::SingleDataChecker(): DataChecker() + {} + + + SingleDataChecker::~SingleDataChecker() + {} + + + void SingleDataChecker::check(const QString& path, const Torrent& tor,const QString &) + { + // open the file + Uint32 num_chunks = tor.getNumChunks(); + Uint32 chunk_size = tor.getChunkSize(); + File fptr; + if (!fptr.open(path,"rb")) + { + throw Error(i18n("Cannot open file : %1 : %2") + .arg(path).arg( fptr.errorString())); + } + + // initialize the bitsets + downloaded = BitSet(num_chunks); + failed = BitSet(num_chunks); + + TimeStamp last_update_time = bt::GetCurrentTime(); + + // loop over all chunks + Array<Uint8> buf(chunk_size); + for (Uint32 i = 0;i < num_chunks;i++) + { + if (listener) + { + listener->progress(i,num_chunks); + if (listener->needToStop()) // if we need to stop just return + return; + } + + TimeStamp now = bt::GetCurrentTime(); + if (now - last_update_time > 1000) + { + Out(SYS_DIO|LOG_DEBUG) << "Checked " << i << " chunks" << endl; + last_update_time = now; + } + + if (!fptr.eof()) + { + // read the chunk + Uint32 size = i == num_chunks - 1 && tor.getFileLength() % tor.getChunkSize() > 0 ? + tor.getFileLength() % tor.getChunkSize() : (Uint32)tor.getChunkSize(); + + fptr.seek(File::BEGIN,(Int64)i*tor.getChunkSize()); + fptr.read(buf,size); + // generate and test hash + SHA1Hash h = SHA1Hash::generate(buf,size); + bool ok = (h == tor.getHash(i)); + downloaded.set(i,ok); + failed.set(i,!ok); + } + else + { + // at end of file so set to default values for a failed chunk + downloaded.set(i,false); + failed.set(i,true); + } + if (listener) + listener->status(failed.numOnBits(),downloaded.numOnBits()); + } + } + +} diff --git a/libktorrent/datachecker/singledatachecker.h b/libktorrent/datachecker/singledatachecker.h new file mode 100644 index 0000000..20107b3 --- /dev/null +++ b/libktorrent/datachecker/singledatachecker.h @@ -0,0 +1,44 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef BTSINGLEDATACHECKER_H +#define BTSINGLEDATACHECKER_H + +#include "datachecker.h" + +namespace bt +{ + + /** + * @author Joris Guisson + * + * Data checker for single file torrents. + */ + class SingleDataChecker : public DataChecker + { + public: + SingleDataChecker(); + virtual ~SingleDataChecker(); + + virtual void check(const QString& path, const Torrent& tor,const QString & dnddir); + }; + +} + +#endif diff --git a/libktorrent/expandablewidget.cpp b/libktorrent/expandablewidget.cpp new file mode 100644 index 0000000..cdac376 --- /dev/null +++ b/libktorrent/expandablewidget.cpp @@ -0,0 +1,179 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <qlayout.h> +#include <qsplitter.h> +#include "expandablewidget.h" + +namespace kt +{ + + ExpandableWidget::ExpandableWidget(QWidget* child,QWidget *parent, const char *name) + : QWidget(parent, name) + { + top_layout = new QHBoxLayout(this); + child->reparent(this,QPoint(),true); + // make top of stack + begin = new StackElement; + begin->w = child; + top_layout->add(child); + } + + + ExpandableWidget::~ExpandableWidget() + { + if (begin) + { + // delete begin->w; + delete begin; + } + } + + void ExpandableWidget::expand(QWidget* w,Position pos) + { + // create new element + StackElement* se = new StackElement; + se->w = w; + se->pos = pos; + se->next = begin; + + // remove old top from layout + top_layout->remove(begin->w); + + // create new toplevel splitter + Qt::Orientation orientation = (pos == RIGHT || pos == LEFT) ? Qt::Horizontal : Qt::Vertical; + QSplitter* s = new QSplitter(orientation,this);; + se->s = s; + + // reparent w and the bottom widget to s + w->reparent(s,QPoint(),false); + if (begin->s) + begin->s->reparent(s,QPoint(),false); + else + begin->w->reparent(s,QPoint(),false); + + // add w and the bottom widget to s + if (pos == RIGHT || pos == ABOVE) + { + s->moveToFirst(w); + s->setResizeMode(w,QSplitter::KeepSize); + s->moveToLast(begin->s ? begin->s : begin->w); + } + else + { + s->moveToFirst(begin->s ? begin->s : begin->w); + s->moveToLast(w); + s->setResizeMode(w,QSplitter::KeepSize); + } + // make se new top of stack + begin = se; + + // add toplevel splitter to layout + top_layout->add(s); + + // show s + s->show(); + } + + void ExpandableWidget::remove(QWidget* w) + { + // find the correct stackelement + StackElement* se = begin; + StackElement* prev = 0; // element before se + while (se->w != w && se->next) + { + prev = se; + se = se->next; + } + + // do not remove last + if (!se->next) + return; + + if (!prev) + { + // we need to remove the first + top_layout->remove(se->s); + // reparent current top to 0 + se->w->reparent(0,QPoint(),false); + se->s->reparent(0,QPoint(),false); + // set new top + begin = se->next; + + + if (begin->s) + { + begin->s->reparent(this,QPoint(),false); + top_layout->add(begin->s); + begin->s->show(); + } + else + { + begin->w->reparent(this,QPoint(),false); + top_layout->add(begin->w); + begin->w->show(); + } + + se->next = 0; + // delete splitter and se + delete se->s; + delete se; + } + else + { + StackElement* next = se->next; + // isolate the node + se->next = 0; + prev->next = next; + + // reparent se to 0 + se->s->reparent(0,QPoint(),false); + se->w->reparent(0,QPoint(),false); + + // reparent se->next to prev + if (next->s) + next->s->reparent(prev->s,QPoint(),false); + else + next->w->reparent(prev->s,QPoint(),false); + + // update prev's splitter + if (prev->pos == RIGHT || prev->pos == ABOVE) + { + prev->s->moveToFirst(prev->w); + prev->s->setResizeMode(prev->w,QSplitter::KeepSize); + prev->s->moveToLast(next->s ? next->s : next->w); + prev->s->setResizeMode(next->s ? next->s : next->w,QSplitter::KeepSize); + } + else + { + prev->s->moveToFirst(next->s ? next->s : next->w); + prev->s->setResizeMode(next->s ? next->s : next->w,QSplitter::KeepSize); + prev->s->moveToLast(prev->w); + prev->s->setResizeMode(prev->w,QSplitter::KeepSize); + } + + // delete se and splitter + delete se->s; + delete se; + prev->next->w->show(); + prev->s->show(); + } + } +} +#include "expandablewidget.moc" diff --git a/libktorrent/expandablewidget.h b/libktorrent/expandablewidget.h new file mode 100644 index 0000000..823ce5f --- /dev/null +++ b/libktorrent/expandablewidget.h @@ -0,0 +1,89 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef KTEXPANDABLEWIDGET_H +#define KTEXPANDABLEWIDGET_H + +#include <qwidget.h> +#include <qptrlist.h> +#include <interfaces/guiinterface.h> + +class QSplitter; +class QHBoxLayout; + +namespace kt +{ + + + + /** + * @author Joris Guisson + * @brief Widget which can be expanded with more widgets + * + * This is a sort of container widget, which at the minimum has + * one child widget. It allows to add more widgets separating the new widget + * and everything which was previously in the container by a separator. + */ + class ExpandableWidget : public QWidget + { + Q_OBJECT + public: + /** + * Constructor, the first child must be provided. + * @param child The first child + * @param parent The parent + * @param name The name + */ + ExpandableWidget(QWidget* child,QWidget *parent = 0, const char *name = 0); + virtual ~ExpandableWidget(); + + + + /** + * Expand the widget. This will ensure the proper parent child relations. + * @param w The widget + * @param pos It's position relative to the current widget + */ + void expand(QWidget* w,Position pos); + + /** + * Remove a widget. This will ensure the proper parent child relations. + * The widget w will become parentless. Note the first child will never be removed. + * @param w The widget + */ + void remove(QWidget* w); + private: + struct StackElement + { + QWidget* w; + QSplitter* s; + Position pos; + StackElement* next; + + StackElement() : w(0),s(0),pos(LEFT),next(0) {} + ~StackElement() {delete next;} + }; + + StackElement* begin; + QHBoxLayout* top_layout; + }; + +} + +#endif diff --git a/libktorrent/functions.cpp b/libktorrent/functions.cpp new file mode 100644 index 0000000..3bc4f88 --- /dev/null +++ b/libktorrent/functions.cpp @@ -0,0 +1,40 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <qdatetime.h> +#include <klocale.h> +#include <kglobal.h> +#include <kstandarddirs.h> +#include <util/functions.h> +#include "functions.h" + +using namespace bt; + +namespace kt +{ + QString DataDir() + { + QString str = KGlobal::dirs()->saveLocation("data","ktorrent"); + if (!str.endsWith(bt::DirSeparator())) + return str + bt::DirSeparator(); + else + return str; + } + +} diff --git a/libktorrent/functions.h b/libktorrent/functions.h new file mode 100644 index 0000000..02f7870 --- /dev/null +++ b/libktorrent/functions.h @@ -0,0 +1,35 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef FUNCTIONS_H +#define FUNCTIONS_H + +#include <qstring.h> +#include <util/constants.h> + +namespace kt +{ + /** + * Will return the data directory + * @return ~/.kde/share/apps/ktorrent/ + */ + QString DataDir(); +} + +#endif diff --git a/libktorrent/interfaces/Makefile.am b/libktorrent/interfaces/Makefile.am new file mode 100644 index 0000000..dca1a4a --- /dev/null +++ b/libktorrent/interfaces/Makefile.am @@ -0,0 +1,15 @@ +INCLUDES = -I$(srcdir)/.. $(all_includes) +METASOURCES = AUTO +libinterfaces_la_LDFLAGS = $(all_libraries) +noinst_LTLIBRARIES = libinterfaces.la +noinst_HEADERS = plugin.h coreinterface.h guiinterface.h torrentinterface.h \ + monitorinterface.h chunkdownloadinterface.h peerinterface.h torrentfileinterface.h \ + filetreeitem.h filetreediritem.h logmonitorinterface.h ipblockinginterface.h \ + trackerslist.h peersource.h exitoperation.h +libinterfaces_la_SOURCES = plugin.cpp coreinterface.cpp guiinterface.cpp \ + prefpageinterface.cpp torrentinterface.cpp monitorinterface.cpp chunkdownloadinterface.cpp \ + peerinterface.cpp torrentfileinterface.cpp filetreeitem.cpp filetreediritem.cpp \ + functions.cpp logmonitorinterface.cpp ipblockinginterface.cpp trackerslist.cpp \ + peersource.cpp exitoperation.cpp + +KDE_CXXFLAGS = $(USE_EXCEPTIONS) $(USE_RTTI) diff --git a/libktorrent/interfaces/chunkdownloadinterface.cpp b/libktorrent/interfaces/chunkdownloadinterface.cpp new file mode 100644 index 0000000..d991605 --- /dev/null +++ b/libktorrent/interfaces/chunkdownloadinterface.cpp @@ -0,0 +1,33 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 "chunkdownloadinterface.h" + +namespace kt +{ + + ChunkDownloadInterface::ChunkDownloadInterface() + {} + + + ChunkDownloadInterface::~ChunkDownloadInterface() + {} + + +} diff --git a/libktorrent/interfaces/chunkdownloadinterface.h b/libktorrent/interfaces/chunkdownloadinterface.h new file mode 100644 index 0000000..161a534 --- /dev/null +++ b/libktorrent/interfaces/chunkdownloadinterface.h @@ -0,0 +1,62 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef KTCHUNKDOWNLOADINTERFACE_H +#define KTCHUNKDOWNLOADINTERFACE_H + +#include <qstring.h> +#include <util/constants.h> + +namespace kt +{ + + /** + * @author Joris Guisson + * @brief Interface for a ChunkDownload + * + * This class provides the interface for a ChunkDownload object. + */ + class ChunkDownloadInterface + { + public: + ChunkDownloadInterface(); + virtual ~ChunkDownloadInterface(); + + struct Stats + { + /// The PeerID of the current downloader + QString current_peer_id; + /// The current download speed + bt::Uint32 download_speed; + /// The index of the chunk + bt::Uint32 chunk_index; + /// The number of pieces of the chunk which have been downloaded + bt::Uint32 pieces_downloaded; + /// The total number of pieces of the chunk + bt::Uint32 total_pieces; + /// The number of assigned downloaders + bt::Uint32 num_downloaders; + }; + + virtual void getStats(Stats & s) = 0; + }; + +} + +#endif diff --git a/libktorrent/interfaces/coreinterface.cpp b/libktorrent/interfaces/coreinterface.cpp new file mode 100644 index 0000000..cb350d8 --- /dev/null +++ b/libktorrent/interfaces/coreinterface.cpp @@ -0,0 +1,33 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 "coreinterface.h" + +namespace kt +{ + + CoreInterface::CoreInterface() + {} + + + CoreInterface::~CoreInterface() + {} +} + +#include "coreinterface.moc" diff --git a/libktorrent/interfaces/coreinterface.h b/libktorrent/interfaces/coreinterface.h new file mode 100644 index 0000000..613ba8e --- /dev/null +++ b/libktorrent/interfaces/coreinterface.h @@ -0,0 +1,258 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef KTCOREINTERFACE_H +#define KTCOREINTERFACE_H + +#include <kurl.h> +#include <qobject.h> +#include <util/constants.h> +#include <torrent/queuemanager.h> + +///Stats struct +struct CurrentStats +{ + bt::Uint32 download_speed; + bt::Uint32 upload_speed; + bt::Uint64 bytes_downloaded; + bt::Uint64 bytes_uploaded; +}; + +namespace bt +{ + class QueueManager; +} +namespace kt +{ + class TorrentInterface; + + /** + * @author Joris Guisson + * @brief Interface for plugins to communicate with the application's core + * + * This interface provides the plugin with the functionality to modify + * the applications core, the core is responsible for managing all + * TorrentControl objects. + */ + class CoreInterface : public QObject + { + Q_OBJECT + public: + CoreInterface(); + virtual ~CoreInterface(); + + /** + * Set the maximum number of simultanious downloads. + * @param max The max num (0 == no limit) + */ + virtual void setMaxDownloads(int max) = 0; + + virtual void setMaxSeeds(int max) = 0; + + virtual void setMaxDownloadSpeed(int v) = 0; + virtual void setMaxUploadSpeed(int v) = 0; + + /** + * Set wether or not we should keep seeding after + * a download has finished. + * @param ks Keep seeding yes or no + */ + virtual void setKeepSeeding(bool ks) = 0; + + /** + * Change the data dir. This involves copying + * all data from the old dir to the new. + * This can offcourse go horribly wrong, therefore + * if it doesn't succeed it returns false + * and leaves everything where it supposed to be. + * @param new_dir The new directory + */ + virtual bool changeDataDir(const QString & new_dir) = 0; + + /** + * Start all, takes into account the maximum number of downloads. + * @param type - Weather to start downloads, seeds or both. 1=Downloads, 2=Seeds, 3=All + */ + virtual void startAll(int type) = 0; + + /** + * Stop all torrents. + * @param type - Weather to start downloads, seeds or both. 1=Downloads, 2=Seeds, 3=All + */ + virtual void stopAll(int type) = 0; + + /** + * Start a torrent, takes into account the maximum number of downloads. + * @param tc The TorrentControl + */ + virtual void start(TorrentInterface* tc) = 0; + + /** + * Stop a torrent, may start another download if it hasn't been started. + * @param tc The TorrentControl + * @param user true if user stopped the torrent, false otherwise + */ + virtual void stop(TorrentInterface* tc, bool user = false) = 0; + + /** + * Enqueue/Dequeue function. Places a torrent in queue. + * If the torrent is already in queue this will remove it from queue. + * @param tc TorrentControl pointer. + */ + virtual void queue(kt::TorrentInterface* tc) = 0; + + virtual bt::QueueManager* getQueueManager() = 0; + + virtual CurrentStats getStats() = 0; + + /** + * Switch the port when no torrents are running. + * @param port The new port + * @return true if we can, false if there are torrents running + */ + virtual bool changePort(bt::Uint16 port) = 0; + + /// Get the number of torrents running (including seeding torrents). + virtual bt::Uint32 getNumTorrentsRunning() const = 0; + + /// Get the number of torrents not running. + virtual bt::Uint32 getNumTorrentsNotRunning() const = 0; + + /** + * Load a torrent file. Pops up an error dialog + * if something goes wrong. + * @param file The torrent file + * @param savedir Dir to save the data + * @param silently Wether or not to do this silently + */ + virtual bool load(const QString & file,const QString & savedir,bool silently) = 0; + + /** + * Load a torrent file. Pops up an error dialog + * if something goes wrong. Will ask the user for a save location, or use + * the default. + * @param url The torrent file + */ + virtual void load(const KURL& url) = 0; + + /** + * Load a torrent file. Pops up an error dialog + * if something goes wrong. Will ask the user for a save location, or use + * the default. This will not popup a file selection dialog for multi file torrents. + * @param url The torrent file + */ + virtual void loadSilently(const KURL& url) = 0; + + /** + * Remove a download.This will delete all temp + * data from this TorrentControl And delete the + * TorrentControl itself. It can also potentially + * start a new download (when one is waiting to be downloaded). + * @param tc The torrent + * @param data_to Wether or not to delete the file data to + */ + virtual void remove(TorrentInterface* tc,bool data_to) = 0; + + /** + * Inserts IP range to be blocked into IPBlocklist + * @param ip QString reference to single IP or IP range. For example: + * single - 127.0.0.5 + * range - 127.0.*.* + **/ + virtual void addBlockedIP(QString& ip) = 0; + + /** + * Removes IP range from IPBlocklist + * @param ip QString reference to single IP or IP range. For example: + * single - 127.0.0.5 + * range - 127.0.*.* + **/ + virtual void removeBlockedIP(QString& ip) = 0; + + /** + * Find the next free torX dir. + * @return Path to the dir (including the torX part) + */ + virtual QString findNewTorrentDir() const = 0; + + /** + * Load an existing torrent, which has already a properly set up torX dir. + * @param tor_dir The torX dir + */ + virtual void loadExistingTorrent(const QString & tor_dir) = 0; + + /** + * Returns maximum allowed download speed. + */ + virtual int getMaxDownloadSpeed() = 0; + + /** + * Returns maximum allowed upload speed. + */ + virtual int getMaxUploadSpeed() = 0; + + /** + * Sets global paused state for all torrents (QueueManager) and stopps all torrents. + * No torrents will be automatically started/stopped. + */ + virtual void setPausedState(bool pause) = 0; + + /// Get the global share ratio limit + virtual float getGlobalMaxShareRatio() const = 0; + + signals: + /** + * Seeing that when load returns the loading process may not have finished yet, + * and some code expects this. We emit this signal to notify that code of it. + * @param url The url which has been loaded + * @param success Wether or not it succeeded + * @param canceled Wether or not it was canceled by the user + */ + void loadingFinished(const KURL & url,bool success,bool canceled); + + /** + * A TorrentInterface was added + * @param tc + */ + void torrentAdded(kt::TorrentInterface* tc); + + + /** + * A TorrentInterface was removed + * @param tc + */ + void torrentRemoved(kt::TorrentInterface* tc); + + /** + * A TorrentInterface has finished downloading. + * @param tc + */ + void finished(kt::TorrentInterface* tc); + + /** + * Torrent download is stopped by error + * @param tc TorrentInterface + * @param msg Error message + */ + void torrentStoppedByError(kt::TorrentInterface* tc, QString msg); + }; + +} + +#endif diff --git a/libktorrent/interfaces/exitoperation.cpp b/libktorrent/interfaces/exitoperation.cpp new file mode 100644 index 0000000..8eedb7a --- /dev/null +++ b/libktorrent/interfaces/exitoperation.cpp @@ -0,0 +1,47 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 "exitoperation.h" + +namespace kt +{ + + ExitOperation::ExitOperation() + {} + + + ExitOperation::~ExitOperation() + {} + + ExitJobOperation::ExitJobOperation(KIO::Job* j) + { + connect(j,SIGNAL(result(KIO::Job*)),this,SLOT(onResult( KIO::Job* ))); + } + + ExitJobOperation::~ExitJobOperation() + { + } + + void ExitJobOperation::onResult(KIO::Job* ) + { + operationFinished(this); + } + +} +#include "exitoperation.moc" diff --git a/libktorrent/interfaces/exitoperation.h b/libktorrent/interfaces/exitoperation.h new file mode 100644 index 0000000..edaa2fa --- /dev/null +++ b/libktorrent/interfaces/exitoperation.h @@ -0,0 +1,67 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef KTEXITOPERATION_H +#define KTEXITOPERATION_H + +#include <qobject.h> +#include <kio/job.h> + +namespace kt +{ + + /** + * @author Joris Guisson <joris.guisson@gmail.com> + * + * Object to derive from for operations which need to be performed at exit. + * The operation should emit the operationFinished signal when they are done. + * + * ExitOperation's can be used in combination with a WaitJob, to wait for a certain amount of time + * to give serveral ExitOperation's the time time to finish up. + */ + class ExitOperation : public QObject + { + Q_OBJECT + public: + ExitOperation(); + virtual ~ExitOperation(); + + /// wether or not we can do a deleteLater on the job after it has finished. + virtual bool deleteAllowed() const {return true;} + signals: + void operationFinished(kt::ExitOperation* opt); + }; + + /** + * Exit operation which waits for a KIO::Job + */ + class ExitJobOperation : public ExitOperation + { + Q_OBJECT + public: + ExitJobOperation(KIO::Job* j); + virtual ~ExitJobOperation(); + + virtual bool deleteAllowed() const {return false;} + private slots: + virtual void onResult(KIO::Job* j); + }; +} + +#endif diff --git a/libktorrent/interfaces/filetreediritem.cpp b/libktorrent/interfaces/filetreediritem.cpp new file mode 100644 index 0000000..b294015 --- /dev/null +++ b/libktorrent/interfaces/filetreediritem.cpp @@ -0,0 +1,295 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <kglobal.h> +#include <klocale.h> +#include <kiconloader.h> +#include <kmimetype.h> +#include <util/functions.h> +#include <interfaces/functions.h> +#include <torrent/globals.h> +#include "filetreediritem.h" +#include "filetreeitem.h" +#include "torrentfileinterface.h" +#include <torrent/torrentfile.h> + +using namespace bt; + +namespace kt +{ + + FileTreeDirItem::FileTreeDirItem(KListView* klv,const QString & name,FileTreeRootListener* rl) + : QCheckListItem(klv,QString::null,QCheckListItem::CheckBox),name(name),root_listener(rl) + { + parent = 0; + size = 0; + setPixmap(0,KGlobal::iconLoader()->loadIcon("folder",KIcon::Small)); + setText(0,name); + setText(1,BytesToString(size)); + setText(2,i18n("Yes")); + manual_change = true; + setOn(true); + manual_change = false; + } + + FileTreeDirItem::FileTreeDirItem(FileTreeDirItem* parent,const QString & name) + : QCheckListItem(parent,QString::null,QCheckListItem::CheckBox), + name(name),parent(parent) + { + size = 0; + setPixmap(0,KGlobal::iconLoader()->loadIcon("folder",KIcon::Small)); + setText(0,name); + setText(1,BytesToString(size)); + setText(2,i18n("Yes")); + manual_change = true; + setOn(true); + manual_change = false; + } + + FileTreeDirItem::~FileTreeDirItem() + { + } + + void FileTreeDirItem::insert(const QString & path,kt::TorrentFileInterface & file) + { + size += file.getSize(); + setText(1,BytesToString(size)); + int p = path.find(bt::DirSeparator()); + if (p == -1) + { + children.insert(path,newFileTreeItem(path,file)); + } + else + { + QString subdir = path.left(p); + FileTreeDirItem* sd = subdirs.find(subdir); + if (!sd) + { + sd = newFileTreeDirItem(subdir); + subdirs.insert(subdir,sd); + } + + sd->insert(path.mid(p+1),file); + } + } + + void FileTreeDirItem::setAllChecked(bool on,bool keep_data) + { + if (!manual_change) + { + manual_change = true; + setOn(on); + manual_change = false; + } + // first set all the child items + bt::PtrMap<QString,FileTreeItem>::iterator i = children.begin(); + while (i != children.end()) + { + i->second->setChecked(on,keep_data); + i++; + } + + // then recursivly move on to subdirs + bt::PtrMap<QString,FileTreeDirItem>::iterator j = subdirs.begin(); + while (j != subdirs.end()) + { + j->second->setAllChecked(on,keep_data); + j++; + } + } + + + void FileTreeDirItem::invertChecked() + { + // first set all the child items + bt::PtrMap<QString,FileTreeItem>::iterator i = children.begin(); + while (i != children.end()) + { + FileTreeItem* item = i->second; + item->setChecked(!item->isOn()); + i++; + } + + // then recursivly move on to subdirs + bt::PtrMap<QString,FileTreeDirItem>::iterator j = subdirs.begin(); + while (j != subdirs.end()) + { + j->second->invertChecked(); + j++; + } + } + + void FileTreeDirItem::stateChange(bool on) + { + if (!manual_change) + { + if (on) + { + setAllChecked(true); + } + else + { + switch (confirmationDialog()) + { + case KEEP_DATA: + setAllChecked(false,true); + break; + case THROW_AWAY_DATA: + setAllChecked(false,false); + break; + case CANCELED: + default: + manual_change = true; + setOn(true); + manual_change = false; + return; + } + } + if (parent) + parent->childStateChange(); + } + setText(2,on ? i18n("Yes") : i18n("No")); + } + + Uint64 FileTreeDirItem::bytesToDownload() const + { + Uint64 tot = 0; + // first check all the child items + bt::PtrMap<QString,FileTreeItem>::const_iterator i = children.begin(); + while (i != children.end()) + { + const FileTreeItem* item = i->second; + tot += item->bytesToDownload(); + i++; + } + + // then recursivly move on to subdirs + bt::PtrMap<QString,FileTreeDirItem>::const_iterator j = subdirs.begin(); + while (j != subdirs.end()) + { + tot += j->second->bytesToDownload(); + j++; + } + return tot; + } + + bool FileTreeDirItem::allChildrenOn() + { + // first check all the child items + bt::PtrMap<QString,FileTreeItem>::iterator i = children.begin(); + while (i != children.end()) + { + FileTreeItem* item = i->second; + if (!item->isOn()) + return false; + i++; + } + + // then recursivly move on to subdirs + bt::PtrMap<QString,FileTreeDirItem>::iterator j = subdirs.begin(); + while (j != subdirs.end()) + { + if (!j->second->allChildrenOn()) + return false; + j++; + } + return true; + } + + void FileTreeDirItem::childStateChange() + { + // only set this dir on if all children are on + manual_change = true; + setOn(allChildrenOn()); + manual_change = false; + + if (parent) + parent->childStateChange(); + else if (root_listener) + root_listener->treeItemChanged(); + + } + + int FileTreeDirItem::compare(QListViewItem* i, int col, bool ascending) const + { + if (col == 1) + { + FileTreeDirItem* other = dynamic_cast<FileTreeDirItem*>(i); + if (!other) + return 0; + else + return (int)(size - other->size); + } + else + { + //return QCheckListItem::compare(i, col, ascending); + // case insensitive comparison + return QString::compare(text(col).lower(),i->text(col).lower()); + } + } + + TorrentFileInterface & FileTreeDirItem::findTorrentFile(QListViewItem* item) + { + // first check all the child items + TorrentFileInterface & nullfile = (TorrentFileInterface &)TorrentFile::null; + bt::PtrMap<QString,FileTreeItem>::iterator i = children.begin(); + while (i != children.end()) + { + FileTreeItem* file = i->second; + if (file == (FileTreeItem*)item) + return file->getTorrentFile(); + i++; + } + + // then recursivly move on to subdirs + bt::PtrMap<QString,FileTreeDirItem>::iterator j = subdirs.begin(); + while (j != subdirs.end()) + { + TorrentFileInterface & thefile = j->second->findTorrentFile(item); + if(!thefile.isNull()) + return thefile; + j++; + } + return nullfile; + } + + FileTreeItem* FileTreeDirItem::newFileTreeItem(const QString & name,TorrentFileInterface & file) + { + return new FileTreeItem(this,name,file); + } + + FileTreeDirItem* FileTreeDirItem::newFileTreeDirItem(const QString & subdir) + { + return new FileTreeDirItem(this,subdir); + } + + bt::ConfirmationResult FileTreeDirItem::confirmationDialog() + { + return bt::THROW_AWAY_DATA; + } + + QString FileTreeDirItem::getPath() const + { + if (!parent) + return bt::DirSeparator(); + else + return parent->getPath() + name + bt::DirSeparator(); + } +} + diff --git a/libktorrent/interfaces/filetreediritem.h b/libktorrent/interfaces/filetreediritem.h new file mode 100644 index 0000000..00650f2 --- /dev/null +++ b/libktorrent/interfaces/filetreediritem.h @@ -0,0 +1,140 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef KTFILETREEDIRITEM_H +#define KTFILETREEDIRITEM_H + +#include <klistview.h> +#include <util/constants.h> +#include <util/ptrmap.h> + +namespace kt +{ + using namespace bt; + + class FileTreeItem; + class TorrentFileInterface; + class TorrentInterface; + + class FileTreeRootListener + { + public: + /// An item in the file tree has changed his state + virtual void treeItemChanged() = 0; + }; + + /** + * @author Joris Guisson + * + * Directory item the file tree showing the files in a multifile torrent + */ + class FileTreeDirItem : public QCheckListItem + { + protected: + QString name; + Uint64 size; + bt::PtrMap<QString,FileTreeItem> children; + bt::PtrMap<QString,FileTreeDirItem> subdirs; + FileTreeDirItem* parent; + bool manual_change; + FileTreeRootListener* root_listener; + public: + FileTreeDirItem(KListView* klv,const QString & name,FileTreeRootListener* rl = 0); + FileTreeDirItem(FileTreeDirItem* parent,const QString & name); + virtual ~FileTreeDirItem(); + + /// Get the path of the directory (if this is the root directory / will be returned) + QString getPath() const; + + /** + * Recursively insert a TorrentFileInterface. + * @param path Path of file + * @param file File itself + */ + void insert(const QString & path,kt::TorrentFileInterface & file); + + /** + * Recursivly walk the tree to find the TorrentFile which + * is shown by a QListViewItem (which should be an FileTreeItem). + * If item can't be found or item is an FileTreeDirItem, a reference to + * TorrentFile::null will be returned. In which case the isNull() function + * of TorrentFile will return true + * @param item Pointer to the QListViewItem + * @return A reference to the TorrentFile + */ + kt::TorrentFileInterface & findTorrentFile(QListViewItem* item); + + /** + * Set all items checked or not. + * @param on true everything checked, false everything not checked + * @param keep_data In case of unchecking keep the data or not + */ + void setAllChecked(bool on,bool keep_data = false); + + /** + * Invert all items, checked items become unchecked and unchecked become checked. + */ + void invertChecked(); + + /** + * Called by the child to notify the parent it's state has changed. + */ + void childStateChange(); + + FileTreeDirItem* getParent() {return parent;} + + /// Recusively get the total number of bytes to download + Uint64 bytesToDownload() const; + + protected: + /** + * Can be overrided by subclasses, so they can use their own + * custom FileTreeItem's. Will be called in insert. + * @param name Name of the file + * @param file The TorrentFileInterface + * @return A newly created FileTreeItem + */ + virtual FileTreeItem* newFileTreeItem(const QString & name, + TorrentFileInterface & file); + + + /** + * Can be overrided by subclasses, so they can use their own + * custom FileTreeDirItem's. Will be called in insert. + * @param subdir The name of the subdir + * @return A newly created FileTreeDirItem + */ + virtual FileTreeDirItem* newFileTreeDirItem(const QString & subdir); + + + /** + * Subclasses should override this if they want to show a confirmation dialog. + * @return What to do (i.e. keep the data, get rid of it or do nothing + */ + virtual bt::ConfirmationResult confirmationDialog(); + + private: + virtual void stateChange(bool on); + virtual int compare(QListViewItem* i, int col, bool ascending) const; + bool allChildrenOn(); + }; + +} + +#endif diff --git a/libktorrent/interfaces/filetreeitem.cpp b/libktorrent/interfaces/filetreeitem.cpp new file mode 100644 index 0000000..32f265c --- /dev/null +++ b/libktorrent/interfaces/filetreeitem.cpp @@ -0,0 +1,182 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <kglobal.h> +#include <klocale.h> +#include <kiconloader.h> +#include <kmimetype.h> +#include <interfaces/functions.h> +#include <torrent/globals.h> +#include "filetreeitem.h" +#include "filetreediritem.h" +#include "torrentfileinterface.h" + +using namespace bt; + +namespace kt +{ + + FileTreeItem::FileTreeItem(FileTreeDirItem* item,const QString & name,kt::TorrentFileInterface & file) + : QCheckListItem(item,QString::null,QCheckListItem::CheckBox),name(name),file(file) + { + parent = item; + manual_change = false; + init(); + } + + FileTreeItem::~FileTreeItem() + { + } + + void FileTreeItem::setChecked(bool on,bool keep_data) + { + manual_change = true; + setOn(on); + manual_change = false; + + if (!on) + { + if (keep_data) + file.setPriority(ONLY_SEED_PRIORITY); + else + file.setDoNotDownload(true); + } + else + { + if (file.getPriority() == ONLY_SEED_PRIORITY) + file.setPriority(NORMAL_PRIORITY); + else + file.setDoNotDownload(false); + } + + updatePriorityText(); + parent->childStateChange(); + } + + void FileTreeItem::updatePriorityText() + { + switch(file.getPriority()) + { + case FIRST_PRIORITY: + setText(2,i18n("Yes, First")); + break; + case LAST_PRIORITY: + setText(2,i18n("Yes, Last")); + break; + case EXCLUDED: + case ONLY_SEED_PRIORITY: + setText(2,i18n("No")); + break; + case PREVIEW_PRIORITY: + break; + default: + setText(2,i18n("Yes")); + break; + } + } + + void FileTreeItem::init() + { + manual_change = true; + if (file.doNotDownload() || file.getPriority() == ONLY_SEED_PRIORITY) + setOn(false); + else + setOn(true); + manual_change = false; + setText(0,name); + setText(1,BytesToString(file.getSize())); + updatePriorityText(); + setPixmap(0,KMimeType::findByPath(name)->pixmap(KIcon::Small)); + } + + void FileTreeItem::stateChange(bool on) + { + if (manual_change) + { + updatePriorityText(); + return; + } + + if (!on) + { + switch (confirmationDialog()) + { + case KEEP_DATA: + file.setPriority(ONLY_SEED_PRIORITY); + break; + case THROW_AWAY_DATA: + file.setDoNotDownload(true); + break; + case CANCELED: + default: + manual_change = true; + setOn(true); + manual_change = false; + return; + } + } + else + { + if (file.getPriority() == ONLY_SEED_PRIORITY) + file.setPriority(NORMAL_PRIORITY); + else + file.setDoNotDownload(false); + + } + + updatePriorityText(); + parent->childStateChange(); + } + + int FileTreeItem::compare(QListViewItem* i, int col, bool ascending) const + { + if (col == 1) + { + FileTreeItem* other = dynamic_cast<FileTreeItem*>(i); + if (!other) + return 0; + else + return (int)(file.getSize() - other->file.getSize()); + } + else + { + // lets sort case insensitive + return QString::compare(text(col).lower(),i->text(col).lower()); + // QCheckListItem::compare(i, col, ascending); + } + } + + + ConfirmationResult FileTreeItem::confirmationDialog() + { + if (file.isPreExistingFile()) + return KEEP_DATA; + else + return THROW_AWAY_DATA; + } + + Uint64 FileTreeItem::bytesToDownload() const + { + if (file.doNotDownload()) + return 0; + else + return file.getSize(); + } + +} diff --git a/libktorrent/interfaces/filetreeitem.h b/libktorrent/interfaces/filetreeitem.h new file mode 100644 index 0000000..6f9f1b1 --- /dev/null +++ b/libktorrent/interfaces/filetreeitem.h @@ -0,0 +1,91 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef KTFILETREEITEM_H +#define KTFILETREEITEM_H + +#include <klistview.h> +#include <util/constants.h> + +using namespace bt; + +namespace kt +{ + class TorrentFileInterface; + class TorrentInterface; + class FileTreeDirItem; + + + + /** + * @author Joris Guisson + * + * File item part of a tree which shows the files in a multifile torrent. + * This is derived from QCheckListItem, if the user checks or unchecks the box, + * wether or not to download a file will be changed. + */ + class FileTreeItem : public QCheckListItem + { + protected: + QString name; + TorrentFileInterface & file; + FileTreeDirItem* parent; + bool manual_change; + public: + /** + * Constructor, set the parent, name and file + * @param item Parent item + * @param name Name of file + * @param file THe TorrentFileInterface + * @return + */ + FileTreeItem(FileTreeDirItem* item,const QString & name,TorrentFileInterface & file); + virtual ~FileTreeItem(); + + /// Get a reference to the TorrentFileInterface + TorrentFileInterface & getTorrentFile() {return file;} + + /** + * Set the box checked or not. + * @param on Checked or not + * @param keep_data In case of unchecking keep the data or not + */ + void setChecked(bool on,bool keep_data = false); + + /// Get the number of bytes to download in this file + Uint64 bytesToDownload() const; + + + private: + void init(); + virtual void stateChange(bool on); + void updatePriorityText(); + + protected: + virtual int compare(QListViewItem* i, int col, bool ascending) const; + + /** + * Subclasses should override this if they want to show a confirmation dialog. + * @return What to do (i.e. keep the data, get rid of it or do nothing + */ + virtual bt::ConfirmationResult confirmationDialog(); + }; +} + +#endif diff --git a/libktorrent/interfaces/functions.cpp b/libktorrent/interfaces/functions.cpp new file mode 100644 index 0000000..2c6286f --- /dev/null +++ b/libktorrent/interfaces/functions.cpp @@ -0,0 +1,62 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <qdatetime.h> +#include <klocale.h> +#include <kglobal.h> +#include "functions.h" + +using namespace bt; + +namespace kt +{ + + + QString BytesToString(Uint64 bytes,int precision) + { + KLocale* loc = KGlobal::locale(); + if (bytes >= 1024 * 1024 * 1024) + return i18n("%1 GB").arg(loc->formatNumber(bytes / TO_GIG,precision < 0 ? 2 : precision)); + else if (bytes >= 1024*1024) + return i18n("%1 MB").arg(loc->formatNumber(bytes / TO_MEG,precision < 0 ? 1 : precision)); + else if (bytes >= 1024) + return i18n("%1 KB").arg(loc->formatNumber(bytes / TO_KB,precision < 0 ? 1 : precision)); + else + return i18n("%1 B").arg(bytes); + } + + QString KBytesPerSecToString(double speed,int precision) + { + KLocale* loc = KGlobal::locale(); + return i18n("%1 KB/s").arg(loc->formatNumber(speed,precision)); + } + + QString DurationToString(Uint32 nsecs) + { + KLocale* loc = KGlobal::locale(); + QTime t; + int ndays = nsecs / 86400; + t = t.addSecs(nsecs % 86400); + QString s = loc->formatTime(t,true,true); + if (ndays > 0) + s = i18n("1 day ","%n days ",ndays) + s; + + return s; + } +} diff --git a/libktorrent/interfaces/functions.h b/libktorrent/interfaces/functions.h new file mode 100644 index 0000000..1bf7178 --- /dev/null +++ b/libktorrent/interfaces/functions.h @@ -0,0 +1,47 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef FUNCTIONS_H +#define FUNCTIONS_H + +#include <qstring.h> +#include <util/constants.h> + +namespace kt +{ + const double TO_KB = 1024.0; + const double TO_MEG = (1024.0 * 1024.0); + const double TO_GIG = (1024.0 * 1024.0 * 1024.0); + + QString BytesToString(bt::Uint64 bytes,int precision = -1); + QString KBytesPerSecToString(double speed,int precision = 1); + QString DurationToString(bt::Uint32 nsecs); + + template<class T> int CompareVal(T a,T b) + { + if (a < b) + return -1; + else if (a > b) + return 1; + else + return 0; + } +} + +#endif diff --git a/libktorrent/interfaces/guiinterface.cpp b/libktorrent/interfaces/guiinterface.cpp new file mode 100644 index 0000000..8a87d90 --- /dev/null +++ b/libktorrent/interfaces/guiinterface.cpp @@ -0,0 +1,54 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 "guiinterface.h" +#include "torrentinterface.h" + +namespace kt +{ + + GUIInterface::GUIInterface() + {} + + + GUIInterface::~GUIInterface() + {} + + + void GUIInterface::notifyViewListeners(TorrentInterface* tc) + { + QPtrList<ViewListener>::iterator i = listeners.begin(); + while (i != listeners.end()) + { + ViewListener* vl = *i; + vl->currentTorrentChanged(tc); + i++; + } + } + + void GUIInterface::addViewListener(ViewListener* vl) + { + listeners.append(vl); + } + + void GUIInterface::removeViewListener(ViewListener* vl) + { + listeners.remove(vl); + } +} diff --git a/libktorrent/interfaces/guiinterface.h b/libktorrent/interfaces/guiinterface.h new file mode 100644 index 0000000..a263bb6 --- /dev/null +++ b/libktorrent/interfaces/guiinterface.h @@ -0,0 +1,218 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef KTGUIINTERFACE_H +#define KTGUIINTERFACE_H + +#include <qptrlist.h> + +class QWidget; +class QIconSet; +class QString; +class KToolBar; +class KProgress; + +namespace kt +{ + class PrefPageInterface; + class Plugin; + class TorrentInterface; + class GUIInterface; + + enum Position + { + LEFT, ///< New widgets will be added to the left of the old + RIGHT, ///< New widgets will be added to the right of the old + ABOVE, ///< New widgets will be added above the old + BELOW ///< New widgets will be added below the old + }; + + /** + * Small interface for classes who want to know when + * current torrent in the gui changes. + */ + class ViewListener + { + public: + ViewListener() {} + virtual ~ViewListener() {} + + virtual void currentTorrentChanged(TorrentInterface* tc) = 0; + }; + + /** + * Plugins wanting to add closeable tabs, should implement this interface. + * That way they can be notified of close requests. + * Not providing this interface in addTabPage means the tab cannot be closed. + */ + class CloseTabListener + { + public: + /// By default all tabs can be closed, but this can be overridden + virtual bool closeAllowed(QWidget* ) {return true;} + + /// THe close button was pressed for this tab, please remove it from the GUI + virtual void tabCloseRequest(kt::GUIInterface* gui,QWidget* tab) = 0; + }; + + /** + * @author Joris Guisson + * @brief Interface to modify the GUI + * + * This interface allows plugins and others to modify the GUI. + */ + class GUIInterface + { + QPtrList<ViewListener> listeners; + public: + GUIInterface(); + virtual ~GUIInterface(); + + + /// Add a view listener. + void addViewListener(ViewListener* vl); + + /// Remove a view listener + void removeViewListener(ViewListener* vl); + + /// Add a progress bar tot the status bar, if one is already present this will fail and return 0 + virtual KProgress* addProgressBarToStatusBar() = 0; + + /// Remove the progress bar from the status bar + virtual void removeProgressBarFromStatusBar(KProgress* p) = 0; + + /** + * Add a new tab page to the GUI + * @param page The widget + * @param icon Icon for the tab + * @param caption Text on the tab + * @param ctl For closeable tabs this pointer should be set + */ + virtual void addTabPage(QWidget* page,const QIconSet & icon, + const QString & caption,CloseTabListener* ctl = 0) = 0; + + /** + * Remove a tab page, does nothing if the page + * isn't added. Does not delete the widget. + * @param page The page + */ + virtual void removeTabPage(QWidget* page) = 0; + + /** + * Add a page to the preference dialog. + * @param page The page + */ + virtual void addPrefPage(PrefPageInterface* page) = 0; + + + /** + * Remove a page from the preference dialog. + * @param page The page + */ + virtual void removePrefPage(PrefPageInterface* page) = 0; + + /** + * Change the statusbar message. + * @param msg The new message + */ + virtual void changeStatusbar(const QString& msg) = 0; + + /** + * Merge the GUI of a plugin. + * @param p The Plugin + */ + virtual void mergePluginGui(Plugin* p) = 0; + + /** + * Remove the GUI of a plugin. + * @param p The Plugin + */ + virtual void removePluginGui(Plugin* p) = 0; + + /** + * Embed a widget in the view in the mainview. + * The view and the new widget will be separated by a separator. + * @param w The widget + * @param pos How the widget will be positioned against the already present widgets + */ + virtual void addWidgetInView(QWidget* w,Position pos) = 0; + + /** + * Remove a widget added with addWidgetInView. + * The widget will be reparented to 0. + * @param w The widget + */ + virtual void removeWidgetFromView(QWidget* w) = 0; + + /** + * Add a widget below the view. + * @param w The widget + * @param icon Name of icon to use + * @param caption The caption to use + */ + virtual void addWidgetBelowView(QWidget* w,const QString & icon,const QString & caption) = 0; + + /** + * Remove a widget, which was added below the view. + * @param w The widget + */ + virtual void removeWidgetBelowView(QWidget* w) = 0; + + enum ToolDock + { + DOCK_LEFT, + DOCK_RIGHT, + DOCK_BOTTOM + }; + + /** + * Add a tool widget. + * @param w The widget + * @param icon Name of icon to use + * @param caption The caption to use + * @param dock Where to dock the widget + */ + virtual void addToolWidget(QWidget* w,const QString & icon,const QString & caption,ToolDock dock) = 0; + + /** + * Remove a tool widget. + * @param w The widget + */ + virtual void removeToolWidget(QWidget* w) = 0; + + /// Get the current torrent. + virtual const TorrentInterface* getCurrentTorrent() const = 0; + + /// Add a toolbar + virtual KToolBar* addToolBar(const char* name) = 0; + + /// Remove a toolbar + virtual void removeToolBar(KToolBar* tb) = 0; + + protected: + /** + * Notifies all view listeners of the change in the current downloading TorrentInterface + * @param tc Pointer to current TorrentInterface + */ + void notifyViewListeners(TorrentInterface* tc); + }; + +} + +#endif diff --git a/libktorrent/interfaces/ipblockinginterface.cpp b/libktorrent/interfaces/ipblockinginterface.cpp new file mode 100644 index 0000000..e92e24c --- /dev/null +++ b/libktorrent/interfaces/ipblockinginterface.cpp @@ -0,0 +1,33 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@gmail.com * + * ivasic@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ +#include "ipblockinginterface.h" + +namespace kt +{ + IPBlockingInterface::IPBlockingInterface() + {} + + + IPBlockingInterface::~IPBlockingInterface() + {} +} + + diff --git a/libktorrent/interfaces/ipblockinginterface.h b/libktorrent/interfaces/ipblockinginterface.h new file mode 100644 index 0000000..4054af9 --- /dev/null +++ b/libktorrent/interfaces/ipblockinginterface.h @@ -0,0 +1,48 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@gmail.com * + * ivasic@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef IPBLOCKINGINTERFACE_H +#define IPBLOCKINGINTERFACE_H + +class QString; + +namespace kt +{ + /** + * @author Ivan Vasic + * @brief Interface for IPBlocklist to communicate with IPBlockingPlugin + */ + class IPBlockingInterface + { + public: + IPBlockingInterface(); + virtual ~IPBlockingInterface(); + + /** + * This function checks if IP is listed in antip2p filter list. + * @return TRUE if IP should be blocked. FALSE otherwise + * @arg ip String representation of IP address. + */ + virtual bool isBlockedIP(const QString& ip) = 0; + + }; +} +#endif diff --git a/libktorrent/interfaces/logmonitorinterface.cpp b/libktorrent/interfaces/logmonitorinterface.cpp new file mode 100644 index 0000000..df77a53 --- /dev/null +++ b/libktorrent/interfaces/logmonitorinterface.cpp @@ -0,0 +1,33 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 "logmonitorinterface.h" + +namespace kt +{ + + LogMonitorInterface::LogMonitorInterface() + {} + + + LogMonitorInterface::~LogMonitorInterface() + {} + + +} diff --git a/libktorrent/interfaces/logmonitorinterface.h b/libktorrent/interfaces/logmonitorinterface.h new file mode 100644 index 0000000..4fccb0e --- /dev/null +++ b/libktorrent/interfaces/logmonitorinterface.h @@ -0,0 +1,50 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef KTLOGMONITORINTERFACE_H +#define KTLOGMONITORINTERFACE_H + +class QString; + +namespace kt +{ + + /** + * @author Joris Guisson + * @brief Interface for classes who which to receive which log messages are printed + * + * This class is an interface for all classes which want to know, + * what is written to the log. + */ + class LogMonitorInterface + { + public: + LogMonitorInterface(); + virtual ~LogMonitorInterface(); + + /** + * A line was written to the log file. + * @param line The line + */ + virtual void message(const QString & line, unsigned int arg) = 0; + }; + +} + +#endif diff --git a/libktorrent/interfaces/monitorinterface.cpp b/libktorrent/interfaces/monitorinterface.cpp new file mode 100644 index 0000000..d3d0c52 --- /dev/null +++ b/libktorrent/interfaces/monitorinterface.cpp @@ -0,0 +1,33 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 "monitorinterface.h" + +namespace kt +{ + + MonitorInterface::MonitorInterface() + {} + + + MonitorInterface::~MonitorInterface() + {} + + +} diff --git a/libktorrent/interfaces/monitorinterface.h b/libktorrent/interfaces/monitorinterface.h new file mode 100644 index 0000000..a199800 --- /dev/null +++ b/libktorrent/interfaces/monitorinterface.h @@ -0,0 +1,79 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef KTMONITORINTERFACE_H +#define KTMONITORINTERFACE_H + + +namespace kt +{ + class ChunkDownloadInterface; + class PeerInterface; + + /** + * @author Joris Guisson + * @brief Interface for classes who want to monitor a TorrentInterface + * + * Classes who want to keep track of all peers currently connected for a given + * torrent and all chunks who are currently downloading can implement this interface. + */ + class MonitorInterface + { + public: + MonitorInterface(); + virtual ~MonitorInterface(); + + /** + * A peer has been added. + * @param peer The peer + */ + virtual void peerAdded(kt::PeerInterface* peer) = 0; + + /** + * A peer has been removed. + * @param peer The peer + */ + virtual void peerRemoved(kt::PeerInterface* peer) = 0; + + /** + * The download of a chunk has been started. + * @param cd The ChunkDownload + */ + virtual void downloadStarted(kt::ChunkDownloadInterface* cd) = 0; + + /** + * The download of a chunk has been stopped. + * @param cd The ChunkDownload + */ + virtual void downloadRemoved(kt::ChunkDownloadInterface* cd) = 0; + + /** + * The download has been stopped. + */ + virtual void stopped() = 0; + + /** + * The download has been deleted. + */ + virtual void destroyed() = 0; + }; + +} + +#endif diff --git a/libktorrent/interfaces/peerinterface.cpp b/libktorrent/interfaces/peerinterface.cpp new file mode 100644 index 0000000..a4ab246 --- /dev/null +++ b/libktorrent/interfaces/peerinterface.cpp @@ -0,0 +1,33 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 "peerinterface.h" + +namespace kt +{ + + PeerInterface::PeerInterface() + {} + + + PeerInterface::~PeerInterface() + {} + + +} diff --git a/libktorrent/interfaces/peerinterface.h b/libktorrent/interfaces/peerinterface.h new file mode 100644 index 0000000..f77d0f8 --- /dev/null +++ b/libktorrent/interfaces/peerinterface.h @@ -0,0 +1,91 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef KTPEERINTERFACE_H +#define KTPEERINTERFACE_H + +#include <qstring.h> +#include <util/constants.h> +namespace kt +{ + + /** + * @author Joris Guisson + * @brief Interface for a Peer + * + * This is the interface for a Peer, it allows other classes to + * get statistics about a Peer. + */ + class PeerInterface + { + public: + PeerInterface(); + virtual ~PeerInterface(); + + struct Stats + { + /// IP address of peer (dotted notation) + QString ip_address; + /// The client (Azureus, BitComet, ...) + QString client; + /// Download rate (bytes/s) + bt::Uint32 download_rate; + /// Upload rate (bytes/s) + bt::Uint32 upload_rate; + /// Choked or not + bool choked; + /// Snubbed or not (i.e. we haven't received a piece for a minute) + bool snubbed; + /// Percentage of file which the peer has + float perc_of_file; + /// Does this peer support DHT + bool dht_support; + /// Amount of data uploaded + bt::Uint64 bytes_uploaded; + /// Amount of data downloaded + bt::Uint64 bytes_downloaded; + /// Advanced choke algorithm score + double aca_score; + /// The evil flag is on when the peer has not choked us, + /// but has snubbed us and requests have timedout + bool evil; + /// Flag to indicate if this peer has an upload slot + bool has_upload_slot; + /// Wether or not this connection is encrypted + bool encrypted; + /// Number of upload requests queued + bt::Uint32 num_up_requests; + /// Number of outstanding download requests queued + bt::Uint32 num_down_requests; + /// Supports the fast extensions + bool fast_extensions; + /// Is this a peer on the local network + bool local; + /// Wether or not the peer supports the extension protocol + bool extension_protocol; + }; + + virtual const Stats & getStats() const = 0; + + virtual void kill() = 0; + }; + +} + +#endif diff --git a/libktorrent/interfaces/peersource.cpp b/libktorrent/interfaces/peersource.cpp new file mode 100644 index 0000000..18368b1 --- /dev/null +++ b/libktorrent/interfaces/peersource.cpp @@ -0,0 +1,64 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 "peersource.h" + +namespace kt +{ + + PeerSource::PeerSource() + {} + + + PeerSource::~PeerSource() + {} + + void PeerSource::completed() + {} + + void PeerSource::manualUpdate() + {} + + void PeerSource::aboutToBeDestroyed() + {} + + void PeerSource::addPeer(const QString & ip,bt::Uint16 port,bool local) + { + PotentialPeer pp; + pp.ip = ip; + pp.port = port; + pp.local = local; + peers.append(pp); + } + + bool PeerSource::takePotentialPeer(PotentialPeer & pp) + { + if (peers.count() > 0) + { + pp = peers.front(); + peers.pop_front(); + return true; + } + return false; + } + + + +} +#include "peersource.moc" diff --git a/libktorrent/interfaces/peersource.h b/libktorrent/interfaces/peersource.h new file mode 100644 index 0000000..9c2b589 --- /dev/null +++ b/libktorrent/interfaces/peersource.h @@ -0,0 +1,118 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef KTPEERSOURCE_H +#define KTPEERSOURCE_H + +#include <qobject.h> +#include <qvaluelist.h> +#include <util/constants.h> + +namespace bt +{ + class WaitJob; +} + +namespace kt +{ + struct PotentialPeer + { + QString ip; + bt::Uint16 port; + bool local; + + PotentialPeer() : port(0),local(false) {} + }; + + /** + * @author Joris Guisson <joris.guisson@gmail.com> + * + * This class is the base class for all classes who which to provide potential peers + * for torrents. PeerSources should work independently and should emit a signal when they + * have peers ready. + */ + class PeerSource : public QObject + { + Q_OBJECT + public: + PeerSource(); + virtual ~PeerSource(); + + + + /** + * Take the first PotentialPeer from the list. The item + * is removed from the list. + * @param pp PotentialPeer struct to fill + * @return true If there was one available, false if not + */ + bool takePotentialPeer(PotentialPeer & pp); + + /** + * Add a peer to the list of peers. + * @param ip The ip + * @param port The port + * @param local Wether or not the peer is on the local network + */ + void addPeer(const QString & ip,bt::Uint16 port,bool local = false); + + public slots: + /** + * Start gathering peers. + */ + virtual void start() = 0; + + /** + * Stop gathering peers. + */ + virtual void stop(bt::WaitJob* wjob = 0) = 0; + + /** + * The torrent has finished downloading. + * This is optional and should be used by HTTP and UDP tracker sources + * to notify the tracker. + */ + virtual void completed(); + + /** + * PeerSources wanting to implement a manual update, should implement this. + */ + virtual void manualUpdate(); + + /** + * The source is about to be destroyed. Subclasses can override this + * to clean up some things. + */ + virtual void aboutToBeDestroyed(); + signals: + /** + * This signal should be emitted when a new batch of peers is ready. + * @param ps The PeerSource + */ + void peersReady(kt::PeerSource* ps); + + + private: + /// List to keep the potential peers in. + QValueList<PotentialPeer> peers; + }; + +} + +#endif diff --git a/libktorrent/interfaces/plugin.cpp b/libktorrent/interfaces/plugin.cpp new file mode 100644 index 0000000..6354985 --- /dev/null +++ b/libktorrent/interfaces/plugin.cpp @@ -0,0 +1,49 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 "plugin.h" + +namespace kt +{ + + Plugin::Plugin(QObject *parent, const char* qt_name,const QStringList & /*args*/, + const QString & name,const QString & gui_name,const QString & author, + const QString & email,const QString & description, + const QString & icon) + : KParts::Plugin(parent,qt_name), + name(name),author(author),email(email),description(description),icon(icon),gui_name(gui_name) + { + core = 0; + gui = 0; + loaded = false; + } + + + Plugin::~Plugin() + {} + + void Plugin::guiUpdate() + { + } + + void Plugin::shutdown(bt::WaitJob* ) + { + } +} +#include "plugin.moc" diff --git a/libktorrent/interfaces/plugin.h b/libktorrent/interfaces/plugin.h new file mode 100644 index 0000000..ac43fbc --- /dev/null +++ b/libktorrent/interfaces/plugin.h @@ -0,0 +1,152 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef KTPLUGIN_H +#define KTPLUGIN_H + +#include <ktversion.h> +#include <kparts/plugin.h> + +namespace bt +{ + class WaitJob; +} + +namespace kt +{ + class CoreInterface; + class GUIInterface; + + /** + * @author Joris Guisson + * @brief Base class for all plugins + * + * This is the base class for all plugins. Plugins should implement + * the load and unload methods, any changes made in load must be undone in + * unload. + * + * It's also absolutely forbidden to do any complex initialization in the constructor + * (setting an int to 0 is ok, creating widgets isn't). + * Only the name, author and description may be set in the constructor. + */ + class Plugin : public KParts::Plugin + { + Q_OBJECT + public: + /** + * Constructor, set the name of the plugin, the name and e-mail of the author and + * a short description of the plugin. + * @param name Name of plugin + * @param gui_name Name to display in GUI (i18n version of name) + * @param author Author of plugin + * @param mail E-mail address of author + * @param description What does the plugin do + * @param icon Name of the plugin's icon + */ + Plugin(QObject *parent,const char* qt_name,const QStringList & args, + const QString & name,const QString & gui_name,const QString & author, + const QString & email,const QString & description, + const QString & icon); + virtual ~Plugin(); + + /** + * This gets called, when the plugin gets loaded by KTorrent. + * Any changes made here must be later made undone, when unload is + * called. + * Upon error a bt::Error should be thrown. And the plugin should remain + * in an uninitialized state. The Error contains an error message, which will + * get show to the user. + */ + virtual void load() = 0; + + /** + * Gets called when the plugin gets unloaded. + * Should undo anything load did. + */ + virtual void unload() = 0; + + /** + * For plugins who need to update something, the same time as the + * GUI updates. + */ + virtual void guiUpdate(); + + /** + * This should be implemented by plugins who need finish of some stuff which might take some time. + * These operations must be finished or killed by a timeout before we can proceed with unloading the plugin. + * @param job The WaitJob which monitors the plugin + */ + virtual void shutdown(bt::WaitJob* job); + + const QString & getName() const {return name;} + const QString & getAuthor() const {return author;} + const QString & getEMailAddress() const {return email;} + const QString & getDescription() const {return description;} + const QString & getIcon() const {return icon;} + const QString & getGuiName() const {return gui_name;} + + /// Get a pointer to the CoreInterface + CoreInterface* getCore() {return core;} + + /// Get a const pointer to the CoreInterface + const CoreInterface* getCore() const {return core;} + + /** + * Set the core, used by PluginManager to set the pointer + * to the core. + * @param c Pointer to the core + */ + void setCore(CoreInterface* c) {core = c;} + + /// Get a pointer to the CoreInterface + GUIInterface* getGUI() {return gui;} + + /// Get a const pointer to the CoreInterface + const GUIInterface* getGUI() const {return gui;} + + /** + * Set the core, used by PluginManager to set the pointer + * to the core. + * @param c Pointer to the core + */ + void setGUI(GUIInterface* c) {gui = c;} + + /// See if the plugin is loaded + bool isLoaded() const {return loaded;} + + /// Check wether the plugin matches the version of KT + virtual bool versionCheck(const QString & version) const = 0; + + private: + QString name; + QString author; + QString email; + QString description; + QString icon; + QString gui_name; + CoreInterface* core; + GUIInterface* gui; + bool loaded; + + friend class PluginManager; + }; + +} + +#endif diff --git a/libktorrent/interfaces/prefpageinterface.cpp b/libktorrent/interfaces/prefpageinterface.cpp new file mode 100644 index 0000000..b905f07 --- /dev/null +++ b/libktorrent/interfaces/prefpageinterface.cpp @@ -0,0 +1,32 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 "prefpageinterface.h" +namespace kt +{ + PrefPageInterface::PrefPageInterface(const QString & name,const QString & header, + const QPixmap & pix) + : pixmap(pix),itemName(name),header(header) + {} + + + PrefPageInterface::~PrefPageInterface() + {} +} + diff --git a/libktorrent/interfaces/prefpageinterface.h b/libktorrent/interfaces/prefpageinterface.h new file mode 100644 index 0000000..7d4d6dc --- /dev/null +++ b/libktorrent/interfaces/prefpageinterface.h @@ -0,0 +1,84 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef PREFPAGEINTERFACE_H +#define PREFPAGEINTERFACE_H + +#include <qpixmap.h> + +class QWidget; + +namespace kt +{ + /** + * @author Ivan Vasic + * @brief Interface to add configuration dialog page. + * + * This interface allows plugins and others to add their own pages in Configuration dialog + */ + class PrefPageInterface + { + public: + /** + * Constructor, set the name, header and pixmap + * @param name + * @param header + * @param pix + */ + PrefPageInterface(const QString & name,const QString & header,const QPixmap & pix); + virtual ~PrefPageInterface(); + + const QString& getItemName() { return itemName; } + const QString& getHeader() { return header; } + const QPixmap& getPixmap() { return pixmap; } + + /** + * Apply the changes that have been made in the + * pref page. If the settings the user gave isn't valid false should be returned. + * This will prevent the dialog from closing. + * @return true if the data validates, false otherwise + */ + virtual bool apply() = 0; + + /** + * Create the actual widget. + * @param parent The parent of the widget + */ + virtual void createWidget(QWidget* parent)=0; + + /** + * Update all data on the widget, gets called before + * the preference dialog gets shown. + */ + virtual void updateData() = 0; + + /// Delete the widget, gets called when the page gets removed. + virtual void deleteWidget() = 0; + + private: + ///Used in IconList mode. You should prefer a pixmap with size 32x32 pixels + QPixmap pixmap; + ///String used in the list or as tab item name. + QString itemName; + ///Header text use in the list modes. Ignored in Tabbed mode. If empty, the item text is used instead. + QString header; + }; +} +#endif + diff --git a/libktorrent/interfaces/torrentfileinterface.cpp b/libktorrent/interfaces/torrentfileinterface.cpp new file mode 100644 index 0000000..4cca138 --- /dev/null +++ b/libktorrent/interfaces/torrentfileinterface.cpp @@ -0,0 +1,44 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 "torrentfileinterface.h" + +namespace kt +{ + + TorrentFileInterface::TorrentFileInterface(const QString & path,Uint64 size) + : path(path),size(size),first_chunk(0),last_chunk(0),num_chunks_downloaded(0), + priority(NORMAL_PRIORITY),m_emitDlStatusChanged(true),preview(false) + { + preexisting = false; + } + + + TorrentFileInterface::~TorrentFileInterface() + {} + + float TorrentFileInterface::getDownloadPercentage() const + { + Uint32 num = last_chunk - first_chunk + 1; + return 100.0f * (float)num_chunks_downloaded / num; + } +} + +#include "torrentfileinterface.moc" + diff --git a/libktorrent/interfaces/torrentfileinterface.h b/libktorrent/interfaces/torrentfileinterface.h new file mode 100644 index 0000000..430534c --- /dev/null +++ b/libktorrent/interfaces/torrentfileinterface.h @@ -0,0 +1,131 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef KTTORRENTFILEINTERFACE_H +#define KTTORRENTFILEINTERFACE_H + +#include <qobject.h> +#include <qstring.h> +#include <util/constants.h> + +namespace kt +{ + using bt::Uint32; + using bt::Uint64; + using bt::Priority; + using bt::PREVIEW_PRIORITY; + using bt::FIRST_PRIORITY; + using bt::NORMAL_PRIORITY; + using bt::LAST_PRIORITY; + using bt::EXCLUDED; + + /** + * @author Joris Guisson + * @brief Interface for a file in a multifile torrent + * + * This class is the interface for a file in a multifile torrent. + */ + class TorrentFileInterface : public QObject + { + Q_OBJECT + public: + /** + * Constructor, set the path and size. + * @param path The path + * @param size The size + */ + TorrentFileInterface(const QString & path,Uint64 size); + virtual ~TorrentFileInterface(); + + /// Get the path of the file + QString getPath() const {return path;} + + /// Get the size of the file + Uint64 getSize() const {return size;} + + /// Get the index of the first chunk in which this file lies + Uint32 getFirstChunk() const {return first_chunk;} + + /// Get the last chunk of the file + Uint32 getLastChunk() const {return last_chunk;} + + /// See if the TorrentFile is null. + bool isNull() const {return path.isNull();} + + /// Set wether we have to not download this file + virtual void setDoNotDownload(bool dnd) = 0; + + /// Wether or not we have to not download this file + virtual bool doNotDownload() const = 0; + + /// Checks if this file is multimedial + virtual bool isMultimedia() const = 0; + + /// Gets the current priority of the torrent + virtual Priority getPriority() const {return priority;} + + /// Sets the priority of the torrent + virtual void setPriority(Priority newpriority = NORMAL_PRIORITY) = 0; + + /// Wheather to emit signal when dl status changes or not. + virtual void setEmitDownloadStatusChanged(bool show) = 0; + + /// Emits signal dlStatusChanged. Use it only with FileSelectDialog! + virtual void emitDownloadStatusChanged() = 0; + + /// Did this file exist before the torrent was loaded by KT + bool isPreExistingFile() const {return preexisting;} + + /// Set wether this file is preexisting + void setPreExisting(bool pe) {preexisting = pe;} + + /// Get the % of the file which is downloaded + float getDownloadPercentage() const; + + /// See if preview is available + bool isPreviewAvailable() const {return preview;} + + signals: + /** + * Emitted when the download percentage has been changed. + * @param p The new percentage + */ + void downloadPercentageChanged(float p); + + /** + * Emitted when the preview becomes available or not. + * @param available + */ + void previewAvailable(bool available); + + protected: + QString path; + Uint64 size; + Uint32 first_chunk; + Uint32 last_chunk; + Uint32 num_chunks_downloaded; + Priority priority; + bool preexisting; + bool m_emitDlStatusChanged; + bool preview; + }; + +} + +#endif diff --git a/libktorrent/interfaces/torrentinterface.cpp b/libktorrent/interfaces/torrentinterface.cpp new file mode 100644 index 0000000..763bd55 --- /dev/null +++ b/libktorrent/interfaces/torrentinterface.cpp @@ -0,0 +1,43 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 "torrentinterface.h" + +namespace kt +{ + + float ShareRatio(const TorrentStats & stats) + { + if (stats.bytes_downloaded == 0) + return 0.0f; + else + return (float) stats.bytes_uploaded / (stats.bytes_downloaded /*+ stats.imported_bytes*/); + } + + + TorrentInterface::TorrentInterface() + {} + + + TorrentInterface::~TorrentInterface() + {} + +} + +#include "torrentinterface.moc" diff --git a/libktorrent/interfaces/torrentinterface.h b/libktorrent/interfaces/torrentinterface.h new file mode 100644 index 0000000..95d5766 --- /dev/null +++ b/libktorrent/interfaces/torrentinterface.h @@ -0,0 +1,509 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef KTTORRENTINTERFACE_H +#define KTTORRENTINTERFACE_H + +#include <qobject.h> +#include <util/constants.h> +#include <interfaces/trackerslist.h> + +#include <kurl.h> + +namespace bt +{ + class BitSet; + class DataCheckerListener; + class SHA1Hash; + class WaitJob; + class PeerID; +} + +namespace kt +{ + using bt::Uint32; + using bt::Uint64; + + class MonitorInterface; + class TorrentFileInterface; + class PeerSource; + + enum TorrentStatus + { + NOT_STARTED, + SEEDING_COMPLETE, + DOWNLOAD_COMPLETE, + SEEDING, + DOWNLOADING, + STALLED, + STOPPED, + ALLOCATING_DISKSPACE, + ERROR, + QUEUED, + CHECKING_DATA, + NO_SPACE_LEFT + }; + + enum TorrentStartResponse + { + START_OK, + USER_CANCELED, + NOT_ENOUGH_DISKSPACE, + MAX_SHARE_RATIO_REACHED, + BUSY_WITH_DATA_CHECK, + QM_LIMITS_REACHED // Max seeds or downloads reached + }; + + enum AutoStopReason + { + MAX_RATIO_REACHED, + MAX_SEED_TIME_REACHED + }; + + struct TorrentStats + { + /// The number of bytes imported (igore these for average speed) + Uint64 imported_bytes; + /// Total number of bytes downloaded. + Uint64 bytes_downloaded; + /// Total number of bytes uploaded. + Uint64 bytes_uploaded; + /// The number of bytes left (gets sent to the tracker) + Uint64 bytes_left; + /// The number of bytes left to download (bytes_left - excluded bytes) + Uint64 bytes_left_to_download; + /// total number of bytes in torrent + Uint64 total_bytes; + /// The total number of bytes which need to be downloaded + Uint64 total_bytes_to_download; + /// The download rate in bytes per sec + Uint32 download_rate; + /// The upload rate in bytes per sec + Uint32 upload_rate; + /// The number of peers we are connected to + Uint32 num_peers; + /// The number of chunks we are currently downloading + Uint32 num_chunks_downloading; + /// The total number of chunks + Uint32 total_chunks; + /// The number of chunks which have been downloaded + Uint32 num_chunks_downloaded; + /// Get the number of chunks which have been excluded + Uint32 num_chunks_excluded; + /// Get the number of chunks left + Uint32 num_chunks_left; + /// Size of each chunk + Uint32 chunk_size; + /// Total seeders in swarm + Uint32 seeders_total; + /// Num seeders connected to + Uint32 seeders_connected_to; + /// Total leechers in swarm + Uint32 leechers_total; + /// Num leechers connected to + Uint32 leechers_connected_to; + /// Status of the download + TorrentStatus status; + /// The status of the tracker + QString trackerstatus; + /// The number of bytes downloaded in this session + Uint64 session_bytes_downloaded; + /// The number of bytes uploaded in this session + Uint64 session_bytes_uploaded; + /// The number of bytes downloaded since the last started event, this gets sent to the tracker + Uint64 trk_bytes_downloaded; + /// The number of bytes upload since the last started event, this gets sent to the tracker + Uint64 trk_bytes_uploaded; + /// Name of the torrent + QString torrent_name; + /// Path of the dir or file where the data will get saved + QString output_path; + /// See if we are running + bool running; + /// See if the torrent has been started + bool started; + /// See if we are allowed to startup this torrent automatically. + bool autostart; + /// See if we have a multi file torrent + bool multi_file_torrent; + /// See if the torrent is stopped by error + bool stopped_by_error; + /// See if the download is completed + bool completed; + /// See if this torrent is controlled by user + bool user_controlled; + /// Maximum share ratio + float max_share_ratio; + /// Maximum seed time + float max_seed_time; + /// Private torrent (i.e. no use of DHT) + bool priv_torrent; + /// Number of corrupted chunks found since the last check + Uint32 num_corrupted_chunks; + }; + + + struct DHTNode + { + QString ip; + bt::Uint16 port; + }; + + enum TorrentFeature + { + DHT_FEATURE, + UT_PEX_FEATURE // µTorrent peer exchange + }; + + + /** + * @author Joris Guisson + * @brief Interface for an object which controls one torrent + * + * This class is the interface for an object which controls the + * up- and download of one torrent. + */ + class TorrentInterface : public QObject + { + Q_OBJECT + public: + TorrentInterface(); + virtual ~TorrentInterface(); + + + /** + * Update the object, should be called periodically. + */ + virtual void update() = 0; + + /** + * Start the download of the torrent. + */ + virtual void start() = 0; + + /** + * Stop the download, closes all connections. + * @param user wether or not the user did this explicitly + * @param wjob WaitJob, used when KT is shutting down, + * so that we can wait for all stopped events to reach the tracker + */ + virtual void stop(bool user,bt::WaitJob* wjob = 0) = 0; + + /** + * Update the tracker, this should normally handled internally. + * We leave it public so that the user can do a manual announce. + */ + virtual void updateTracker() = 0; + + /// Get the torrent's statistics + const TorrentStats & getStats() const {return stats;} + + /** + * Checks if torrent is multimedial and chunks needed for preview are downloaded + * @param start_chunk The index of starting chunk to check + * @param end_chunk The index of the last chunk to check + * In case of single torrent file defaults can be used (0,1) + **/ + virtual bool readyForPreview(int start_chunk = 0, int end_chunk = 1) = 0; + + /** + * Get the torX directory of this torrent. Temporary stuff like the index + * file get stored there. + */ + virtual QString getTorDir() const = 0; + + /// Get the data directory of this torrent + virtual QString getDataDir() const = 0; + + /// Get a short error message + virtual QString getShortErrorMessage() const = 0; + + /** + * Get the download running time of this torrent in seconds + * @return Uint32 - time in seconds + */ + virtual Uint32 getRunningTimeDL() const = 0; + + /** + * Get the upload running time of this torrent in seconds + * @return Uint32 - time in seconds + */ + virtual Uint32 getRunningTimeUL() const = 0; + + /** + * Change to a new data dir. If this fails + * we will fall back on the old directory. + * @param new_dir The new directory + * @return true upon succes + */ + virtual bool changeDataDir(const QString & new_dir) = 0; + + /** + * Change torrents output directory. If this fails we will fall back on the old directory. + * @param new_dir The new directory + * @param moveFiles Wheather to actually move the files or just change the directory without moving them. + * @return true upon success. + */ + virtual bool changeOutputDir(const QString& new_dir, bool moveFiles = true) = 0; + + /** + * Roll back the previous changeDataDir call. + * Does nothing if there was no previous changeDataDir call. + */ + virtual void rollback() = 0; + + /** + * Get a BitSet of the status of all Chunks + */ + virtual const bt::BitSet & downloadedChunksBitSet() const = 0; + + /** + * Get a BitSet of the availability of all Chunks + */ + virtual const bt::BitSet & availableChunksBitSet() const = 0; + + /** + * Get a BitSet of the excluded Chunks + */ + virtual const bt::BitSet & excludedChunksBitSet() const = 0; + + /** + * Get a bitset of only seed chunks + */ + virtual const bt::BitSet & onlySeedChunksBitSet() const = 0; + + /// Set the monitor + virtual void setMonitor(MonitorInterface* tmo) = 0; + + /// Get the time to the next tracker update in seconds. + virtual Uint32 getTimeToNextTrackerUpdate() const = 0; + + /// Get the number of files in a multifile torrent (0 if we do not have a multifile torrent) + virtual Uint32 getNumFiles() const = 0; + + /** + * Get the index'th file of a multifile torrent + * @param index The index + * @return The TorrentFileInterface (isNull() will be true in case of error) + */ + virtual TorrentFileInterface & getTorrentFile(Uint32 index) = 0; + + ///Get a pointer to TrackersList object + virtual TrackersList* getTrackersList() = 0; + + ///Get a pointer to TrackersList object + virtual const TrackersList* getTrackersList() const = 0; + + ///Get the torrent queue number. Zero if not in queue + virtual int getPriority() const = 0; + + ///Set the torrent queue number. + virtual void setPriority(int p) = 0; + + /// Set the max share ratio + virtual void setMaxShareRatio(float ratio) = 0; + + /// Get the max share ratio + virtual float getMaxShareRatio() const = 0; + + /// Set the max seed time in hours (0 is no limit) + virtual void setMaxSeedTime(float hours) = 0; + + /// Get the max seed time + virtual float getMaxSeedTime() const = 0; + + /// Make a string of the current status + virtual QString statusToString() const = 0; + + ///Is manual announce allowed? + virtual bool announceAllowed() = 0; + + + /** + * Returns estimated time left for finishing download. Returned value is in seconds. + * Uses TimeEstimator class to calculate this value. + */ + virtual Uint32 getETA() = 0; + + /** + * Verify the correctness of all data. + * @param lst The listener + * @param auto_import Wether or not this is an initial import + */ + virtual void startDataCheck(bt::DataCheckerListener* lst,bool auto_import) = 0; + + /** + * Data check has been finished, this should be called. + */ + virtual void afterDataCheck() = 0; + + /** + * Are we doing a data check on this torrent. + * @param finished This will be set to true if the data check is finished + */ + virtual bool isCheckingData(bool & finished) const = 0; + + /** + * Test all files and see if they are not missing. + * If so put them in a list + */ + virtual bool hasMissingFiles(QStringList & sl) = 0; + + /** + * Recreate missing files. + */ + virtual void recreateMissingFiles() = 0; + + /** + * Mark missing files as do not download. + */ + virtual void dndMissingFiles() = 0; + + + /// Get the number of initial DHT nodes + virtual Uint32 getNumDHTNodes() const = 0; + + /// Get a DHT node + virtual const DHTNode & getDHTNode(Uint32 i) const = 0; + + /** Delete the data files of the torrent, + * they will be lost permanently + */ + virtual void deleteDataFiles() = 0; + + ///Checks if a seeding torrent has reached its maximum share ratio + virtual bool overMaxRatio() = 0; + + /// Checks if a seeding torrent has reached it's max seed time + virtual bool overMaxSeedTime() = 0; + + /// Handle an error + virtual void handleError(const QString & err) = 0; + + /// Get the info_hash. + virtual const bt::SHA1Hash & getInfoHash() const = 0; + + /** + * Add a new PeerSource + * @param ps + */ + virtual void addPeerSource(PeerSource* ps) = 0; + + /** + * Remove a nPeerSource + * @param ps + */ + virtual void removePeerSource(PeerSource* ps) = 0; + + /// Is a feature enabled + virtual bool isFeatureEnabled(TorrentFeature tf) = 0; + + /// Disable or enable a feature + virtual void setFeatureEnabled(TorrentFeature tf,bool on) = 0; + + /// Get our PeerID + virtual const bt::PeerID & getOwnPeerID() const = 0; + + /// Set the traffic limits for this torrent + virtual void setTrafficLimits(Uint32 up,Uint32 down) = 0; + + /// Get the traffic limits + virtual void getTrafficLimits(Uint32 & up,Uint32 & down) = 0; + + /// Check if there is enough diskspace available for this torrent + virtual bool checkDiskSpace(bool emit_sig = true) = 0; + + /// Are we in the process of moving files + virtual bool isMovingFiles() const = 0; + signals: + /** + * Emited when we have finished downloading. + * @param me The object who emitted the signal + */ + void finished(kt::TorrentInterface* me); + + /** + * Emited when a Torrent download is stopped by error + * @param me The object who emitted the signal + * @param msg Error message + */ + void stoppedByError(kt::TorrentInterface* me, QString msg); + + /** + * Emited when maximum share ratio for this torrent is changed + * @param me The object which emitted the signal. + */ + void maxRatioChanged(kt::TorrentInterface* me); + + /** + * Emited then torrent is stopped from seeding by KTorrent. + * Happens when torrent has reached maximum share ratio and maybe we'll add something more... + * @param me The object which emitted the signal. + */ + void seedingAutoStopped(kt::TorrentInterface* me,kt::AutoStopReason reason); + + /** + * Emitted just before the torrent is started, this should be used to do some + * checks on the files in the cache. + * @param me The torrent which emitted the signal + * @param ret The return value + */ + void aboutToBeStarted(kt::TorrentInterface* me,bool & ret); + + /** + * Emitted when missing files have been marked as dnd. + * The intention of this signal is to update the GUI. + * @param me The torrent which emitted the signal + */ + void missingFilesMarkedDND(kt::TorrentInterface* me); + + /** + * A corrupted chunk has been found during upload. + * @param me The torrent which emitted the signal + */ + void corruptedDataFound(kt::TorrentInterface* me); + + /** + * Disk is running out of space. + * @param me The torrent which emitted the signal + * @param toStop should this torrent be stopped or not + */ + void diskSpaceLow(kt::TorrentInterface* me, bool toStop); + + /** + * Torrent has been stopped + * @param me The torrent which emitted the signal + */ + void torrentStopped(kt::TorrentInterface* me); + + protected: + TorrentStats stats; + }; + + + /** + * Calculates the share ratio of a torrent. + * @param stats The stats of the torrent + * @return The share ratio + */ + float ShareRatio(const TorrentStats & stats); + +} + +#endif diff --git a/libktorrent/interfaces/trackerslist.cpp b/libktorrent/interfaces/trackerslist.cpp new file mode 100644 index 0000000..c119625 --- /dev/null +++ b/libktorrent/interfaces/trackerslist.cpp @@ -0,0 +1,51 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <torrent/torrent.h> +#include "trackerslist.h" + +namespace kt +{ + + TrackersList::TrackersList() + { + } + + + TrackersList::~TrackersList() + { + } + + void TrackersList::merge(const bt::TrackerTier* first) + { + int tier = 1; + while (first) + { + KURL::List::const_iterator i = first->urls.begin(); + while (i != first->urls.end()) + { + addTracker(*i,true,tier); + i++; + } + tier++; + first = first->next; + } + } + +} diff --git a/libktorrent/interfaces/trackerslist.h b/libktorrent/interfaces/trackerslist.h new file mode 100644 index 0000000..55dc05e --- /dev/null +++ b/libktorrent/interfaces/trackerslist.h @@ -0,0 +1,89 @@ +/*************************************************************************** + * Copyright (C) 2005 by 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. * + ***************************************************************************/ +#ifndef KTTRACKERSLIST_H +#define KTTRACKERSLIST_H + +#include <kurl.h> + +namespace bt +{ + struct TrackerTier; +} + +namespace kt +{ + /** + * @author Ivan Vasić <ivasic@gmail.com> + * + * This interface is used to provide access to AnnounceList object which holds a list of available trackers for a torrent. + */ + class TrackersList + { + public: + TrackersList(); + virtual ~TrackersList(); + + /** + * Get the current tracker URL. + */ + virtual KURL getTrackerURL() const = 0; + + /** + * Gets a list of available trackers. + */ + virtual KURL::List getTrackerURLs() = 0; + + /** + * Adds a tracker URL to the list. + * @param url The URL + * @param custom Is it a custom tracker + * @param tier Which tier (or priority) the tracker has, tier 1 are + * the main trackers, tier 2 are backups ... + */ + virtual void addTracker(KURL url, bool custom = true,int tier = 1) = 0; + + /** + * Removes the tracker from the list. + * @param url - Tracker url. + */ + virtual bool removeTracker(KURL url) = 0; + + /** + * Sets the current tracker and does the announce. + * @param url - Tracker url. + */ + virtual void setTracker(KURL url) = 0; + + /** + * Restores the default tracker and does the announce. + */ + virtual void restoreDefault() = 0; + + /** + * Merge an other tracker list. + * @param first The first TrackerTier + */ + void merge(const bt::TrackerTier* first); + + }; + +} + +#endif diff --git a/libktorrent/kademlia/Makefile.am b/libktorrent/kademlia/Makefile.am new file mode 100644 index 0000000..1b567b2 --- /dev/null +++ b/libktorrent/kademlia/Makefile.am @@ -0,0 +1,12 @@ +INCLUDES = -I$(srcdir)/.. $(all_includes) +METASOURCES = AUTO +libkademlia_la_LDFLAGS = $(all_libraries) +noinst_LTLIBRARIES = libkademlia.la +noinst_HEADERS = key.h node.h kbucket.h rpccall.h rpcserver.h database.h dht.h \ + rpcmsg.h kclosestnodessearch.h nodelookup.h task.h pack.h \ + taskmanager.h announcetask.h dhttrackerbackend.h dhtbase.h +libkademlia_la_SOURCES = key.cpp node.cpp kbucket.cpp rpccall.cpp rpcserver.cpp \ + database.cpp dht.cpp rpcmsg.cpp kclosestnodessearch.cpp nodelookup.cpp task.cpp \ + pack.cpp taskmanager.cpp announcetask.cpp \ + dhttrackerbackend.cpp dhtbase.cpp +KDE_CXXFLAGS = $(USE_EXCEPTIONS) $(USE_RTTI) diff --git a/libktorrent/kademlia/announcetask.cpp b/libktorrent/kademlia/announcetask.cpp new file mode 100644 index 0000000..b7350a2 --- /dev/null +++ b/libktorrent/kademlia/announcetask.cpp @@ -0,0 +1,154 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <util/log.h> +#include <torrent/globals.h> +#include "announcetask.h" +#include "node.h" +#include "pack.h" + +using namespace bt; + +namespace dht +{ + + AnnounceTask::AnnounceTask(Database* db,RPCServer* rpc, Node* node,const dht::Key & info_hash,bt::Uint16 port) + : Task(rpc, node),info_hash(info_hash),port(port),db(db) + {} + + + AnnounceTask::~AnnounceTask() + {} + + + void AnnounceTask::callFinished(RPCCall* c, MsgBase* rsp) + { + // Out() << "AnnounceTask::callFinished" << endl; + // if we do not have a get peers response, return + // announce_peer's response are just empty anyway + if (c->getMsgMethod() != dht::GET_PEERS) + return; + + // it is either a GetPeersNodesRsp or a GetPeersValuesRsp + GetPeersRsp* gpr = dynamic_cast<GetPeersRsp*>(rsp); + if (!gpr) + return; + + if (gpr->containsNodes()) + { + const QByteArray & n = gpr->getData(); + Uint32 nval = n.size() / 26; + for (Uint32 i = 0;i < nval;i++) + { + // add node to todo list + KBucketEntry e = UnpackBucketEntry(n,i*26); + if (!todo.contains(e) && !visited.contains(e) && + todo.count() < 100) + { + todo.append(e); + } + } + } + else + { + // store the items in the database + const DBItemList & items = gpr->getItemList(); + for (DBItemList::const_iterator i = items.begin();i != items.end();i++) + { + db->store(info_hash,*i); + // also add the items to the returned_items list + returned_items.append(*i); + } + + // add the peer who responded to the answered list, so we can do an announce + KBucketEntry e(rsp->getOrigin(),rsp->getID()); + if (!answered.contains(KBucketEntryAndToken(e,gpr->getToken())) && !answered_visited.contains(e)) + { + answered.append(KBucketEntryAndToken(e,gpr->getToken())); + } + + emitDataReady(); + } + } + + void AnnounceTask::callTimeout(RPCCall* ) + { + //Out() << "AnnounceTask::callTimeout " << endl; + } + + void AnnounceTask::update() + { +/* Out() << "AnnounceTask::update " << endl; + Out() << "todo " << todo.count() << " ; answered " << answered.count() << endl; + Out() << "visited " << visited.count() << " ; answered_visited " << answered_visited.count() << endl; + */ + while (!answered.empty() && canDoRequest()) + { + KBucketEntryAndToken & e = answered.first(); + if (!answered_visited.contains(e)) + { + AnnounceReq* anr = new AnnounceReq(node->getOurID(),info_hash,port,e.getToken()); + anr->setOrigin(e.getAddress()); + rpcCall(anr); + answered_visited.append(e); + } + answered.pop_front(); + } + + // go over the todo list and send get_peers requests + // until we have nothing left + while (!todo.empty() && canDoRequest()) + { + KBucketEntry e = todo.first(); + // onLy send a findNode if we haven't allrready visited the node + if (!visited.contains(e)) + { + // send a findNode to the node + GetPeersReq* gpr = new GetPeersReq(node->getOurID(),info_hash); + gpr->setOrigin(e.getAddress()); + rpcCall(gpr); + visited.append(e); + } + // remove the entry from the todo list + todo.pop_front(); + } + + if (todo.empty() && answered.empty() && getNumOutstandingRequests() == 0 && !isFinished()) + { + Out(SYS_DHT|LOG_NOTICE) << "DHT: AnnounceTask done" << endl; + done(); + } + else if (answered_visited.count() >= dht::K) + { + // if K announces have occurred stop + Out(SYS_DHT|LOG_NOTICE) << "DHT: AnnounceTask done" << endl; + done(); + } + } + + bool AnnounceTask::takeItem(DBItem & item) + { + if (returned_items.empty()) + return false; + + item = returned_items.first(); + returned_items.pop_front(); + return true; + } +} diff --git a/libktorrent/kademlia/announcetask.h b/libktorrent/kademlia/announcetask.h new file mode 100644 index 0000000..d6bfa7c --- /dev/null +++ b/libktorrent/kademlia/announcetask.h @@ -0,0 +1,74 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef DHTANNOUNCETASK_H +#define DHTANNOUNCETASK_H + +#include <task.h> +#include "kbucket.h" + +namespace dht +{ + class Database; + + class KBucketEntryAndToken : public KBucketEntry + { + Key token; + public: + KBucketEntryAndToken() {} + KBucketEntryAndToken(const KBucketEntry & e,const Key & token) + : KBucketEntry(e),token(token) {} + virtual ~KBucketEntryAndToken() {} + + const Key & getToken() const {return token;} + }; + + /** + @author Joris Guisson <joris.guisson@gmail.com> + */ + class AnnounceTask : public Task + { + public: + AnnounceTask(Database* db,RPCServer* rpc, Node* node,const dht::Key & info_hash,bt::Uint16 port); + virtual ~AnnounceTask(); + + virtual void callFinished(RPCCall* c, MsgBase* rsp); + virtual void callTimeout(RPCCall* c); + virtual void update(); + + /** + * Take one item from the returned values. + * Returns false if there is no item to take. + * @param item The item + * @return false if no item to take, true else + */ + bool takeItem(DBItem & item); + private: + dht::Key info_hash; + bt::Uint16 port; + QValueList<KBucketEntryAndToken> answered; // nodes which have answered with values + QValueList<KBucketEntry> answered_visited; // nodes which have answered with values which have been visited + Database* db; + DBItemList returned_items; + + }; + +} + +#endif diff --git a/libktorrent/kademlia/database.cpp b/libktorrent/kademlia/database.cpp new file mode 100644 index 0000000..447975f --- /dev/null +++ b/libktorrent/kademlia/database.cpp @@ -0,0 +1,186 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <util/functions.h> +#include <util/log.h> +#include <torrent/globals.h> +#include "database.h" + +using namespace bt; + +namespace dht +{ + DBItem::DBItem() + { + memset(item,0,9); + time_stamp = bt::GetCurrentTime(); + } + + DBItem::DBItem(const bt::Uint8* ip_port) + { + memcpy(item,ip_port,6); + time_stamp = bt::GetCurrentTime(); + } + + DBItem::DBItem(const DBItem & it) + { + memcpy(item,it.item,6); + time_stamp = it.time_stamp; + } + + DBItem::~DBItem() + {} + + bool DBItem::expired(bt::TimeStamp now) const + { + return (now - time_stamp >= MAX_ITEM_AGE); + } + + DBItem & DBItem::operator = (const DBItem & it) + { + memcpy(item,it.item,6); + time_stamp = it.time_stamp; + return *this; + } + + /////////////////////////////////////////////// + + Database::Database() + { + items.setAutoDelete(true); + } + + + Database::~Database() + {} + + void Database::store(const dht::Key & key,const DBItem & dbi) + { + DBItemList* dbl = items.find(key); + if (!dbl) + { + dbl = new DBItemList(); + items.insert(key,dbl); + } + dbl->append(dbi); + } + + void Database::sample(const dht::Key & key,DBItemList & tdbl,bt::Uint32 max_entries) + { + DBItemList* dbl = items.find(key); + if (!dbl) + return; + + if (dbl->count() < max_entries) + { + DBItemList::iterator i = dbl->begin(); + while (i != dbl->end()) + { + tdbl.append(*i); + i++; + } + } + else + { + Uint32 num_added = 0; + DBItemList::iterator i = dbl->begin(); + while (i != dbl->end() && num_added < max_entries) + { + tdbl.append(*i); + num_added++; + i++; + } + } + } + + void Database::expire(bt::TimeStamp now) + { + bt::PtrMap<dht::Key,DBItemList>::iterator itr = items.begin(); + while (itr != items.end()) + { + DBItemList* dbl = itr->second; + // newer keys are inserted at the back + // so we can stop when we hit the first key which is not expired + while (dbl->count() > 0 && dbl->first().expired(now)) + { + dbl->pop_front(); + } + itr++; + } + } + + dht::Key Database::genToken(Uint32 ip,Uint16 port) + { + Uint8 tdata[14]; + TimeStamp now = bt::GetCurrentTime(); + // generate a hash of the ip port and the current time + // should prevent anybody from crapping things up + bt::WriteUint32(tdata,0,ip); + bt::WriteUint16(tdata,4,port); + bt::WriteUint64(tdata,6,now); + + dht::Key token = SHA1Hash::generate(tdata,14); + // keep track of the token, tokens will expire after a while + tokens.insert(token,now); + return token; + } + + bool Database::checkToken(const dht::Key & token,Uint32 ip,Uint16 port) + { + // the token must be in the map + if (!tokens.contains(token)) + { + Out(SYS_DHT|LOG_DEBUG) << "Unknown token" << endl; + return false; + } + + // in the map so now get the timestamp and regenerate the token + // using the IP and port of the sender + TimeStamp ts = tokens[token]; + Uint8 tdata[14]; + bt::WriteUint32(tdata,0,ip); + bt::WriteUint16(tdata,4,port); + bt::WriteUint64(tdata,6,ts); + dht::Key ct = SHA1Hash::generate(tdata,14); + // compare the generated token to the one received + if (token != ct) // not good, this peer didn't went through the proper channels + { + Out(SYS_DHT|LOG_DEBUG) << "Invalid token" << endl; + return false; + } + // expire the token + tokens.erase(token); + return true; + } + + bool Database::contains(const dht::Key & key) const + { + return items.find(key) != 0; + } + + void Database::insert(const dht::Key & key) + { + DBItemList* dbl = items.find(key); + if (!dbl) + { + dbl = new DBItemList(); + items.insert(key,dbl); + } + } +} diff --git a/libktorrent/kademlia/database.h b/libktorrent/kademlia/database.h new file mode 100644 index 0000000..94e6b3f --- /dev/null +++ b/libktorrent/kademlia/database.h @@ -0,0 +1,129 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef DHTDATABASE_H +#define DHTDATABASE_H + +#include <qmap.h> +#include <qvaluelist.h> +#include <util/ptrmap.h> +#include <util/constants.h> +#include <util/array.h> +#include "key.h" + + +namespace dht +{ + /// Each item may only exist for 30 minutes + const bt::Uint32 MAX_ITEM_AGE = 30 * 60 * 1000; + + /** + * @author Joris Guisson + * + * Item in the database, will keep track of an IP and port combination. + * As well as the time it was inserted. + */ + class DBItem + { + bt::Uint8 item[6]; + bt::TimeStamp time_stamp; + public: + DBItem(); + DBItem(const bt::Uint8* ip_port); + DBItem(const DBItem & item); + virtual ~DBItem(); + + /// See if the item is expired + bool expired(bt::TimeStamp now) const; + + /// Get the data of an item + const bt::Uint8* getData() const {return item;} + + DBItem & operator = (const DBItem & item); + }; + + typedef QValueList<DBItem> DBItemList; + + /** + * @author Joris Guisson + * + * Class where all the key value paires get stored. + */ + class Database + { + bt::PtrMap<dht::Key,DBItemList> items; + QMap<dht::Key,bt::TimeStamp> tokens; + public: + Database(); + virtual ~Database(); + + /** + * Store an entry in the database + * @param key The key + * @param dbi The DBItem to store + */ + void store(const dht::Key & key,const DBItem & dbi); + + /** + * Get max_entries items from the database, which have + * the same key, items are taken randomly from the list. + * If the key is not present no items will be returned, if + * there are fewer then max_entries items for the key, all + * entries will be returned + * @param key The key to search for + * @param dbl The list to store the items in + * @param max_entries The maximum number entries + */ + void sample(const dht::Key & key,DBItemList & dbl,bt::Uint32 max_entries); + + /** + * Expire all items older then 30 minutes + * @param now The time it is now + * (we pass this along so we only have to calculate it once) + */ + void expire(bt::TimeStamp now); + + /** + * Generate a write token, which will give peers write access to + * the DB. + * @param ip The IP of the peer + * @param port The port of the peer + * @return A Key + */ + dht::Key genToken(bt::Uint32 ip,bt::Uint16 port); + + /** + * Check if a received token is OK. + * @param token The token received + * @param ip The ip of the sender + * @param port The port of the sender + * @return true if the token was given to this peer, false other wise + */ + bool checkToken(const dht::Key & token,bt::Uint32 ip,bt::Uint16 port); + + /// Test wether or not the DB contains a key + bool contains(const dht::Key & key) const; + + /// Insert an empty item (only if it isn't already in the DB) + void insert(const dht::Key & key); + }; + +} + +#endif diff --git a/libktorrent/kademlia/dht.cpp b/libktorrent/kademlia/dht.cpp new file mode 100644 index 0000000..1d00ab8 --- /dev/null +++ b/libktorrent/kademlia/dht.cpp @@ -0,0 +1,378 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <qmap.h> +#include <kresolver.h> +#include <util/log.h> +#include <util/array.h> +#include <util/functions.h> +#include <torrent/bnode.h> +#include <torrent/globals.h> +#include <ksocketaddress.h> +#include "announcetask.h" +#include "dht.h" +#include "node.h" +#include "rpcserver.h" +#include "rpcmsg.h" +#include "kclosestnodessearch.h" +#include "database.h" +#include "taskmanager.h" +#include "nodelookup.h" + + +using namespace bt; +using namespace KNetwork; + +namespace dht +{ + + + + DHT::DHT() : node(0),srv(0),db(0),tman(0) + { + connect(&update_timer,SIGNAL(timeout()),this,SLOT(update())); + } + + + DHT::~DHT() + { + if (running) + stop(); + } + + void DHT::start(const QString & table,const QString & key_file,bt::Uint16 port) + { + if (running) + return; + + if (port == 0) + port = 6881; + + table_file = table; + this->port = port; + Out(SYS_DHT|LOG_NOTICE) << "DHT: Starting on port " << port << endl; + srv = new RPCServer(this,port); + node = new Node(srv,key_file); + db = new Database(); + tman = new TaskManager(); + expire_timer.update(); + running = true; + srv->start(); + node->loadTable(table); + update_timer.start(1000); + started(); + } + + + void DHT::stop() + { + if (!running) + return; + + update_timer.stop(); + Out(SYS_DHT|LOG_NOTICE) << "DHT: Stopping " << endl; + srv->stop(); + node->saveTable(table_file); + running = false; + stopped(); + delete tman; tman = 0; + delete db; db = 0; + delete node; node = 0; + delete srv; srv = 0; + } + + void DHT::ping(PingReq* r) + { + if (!running) + return; + + // ignore requests we get from ourself + if (r->getID() == node->getOurID()) + return; + + Out(SYS_DHT|LOG_NOTICE) << "DHT: Sending ping response" << endl; + PingRsp rsp(r->getMTID(),node->getOurID()); + rsp.setOrigin(r->getOrigin()); + srv->sendMsg(&rsp); + node->recieved(this,r); + } + + + + void DHT::findNode(FindNodeReq* r) + { + if (!running) + return; + + // ignore requests we get from ourself + if (r->getID() == node->getOurID()) + return; + + Out(SYS_DHT|LOG_DEBUG) << "DHT: got findNode request" << endl; + node->recieved(this,r); + // find the K closest nodes and pack them + KClosestNodesSearch kns(r->getTarget(),K); + + node->findKClosestNodes(kns); + + Uint32 rs = kns.requiredSpace(); + // create the data + QByteArray nodes(rs); + // pack the found nodes in a byte array + if (rs > 0) + kns.pack(nodes); + + FindNodeRsp fnr(r->getMTID(),node->getOurID(),nodes); + fnr.setOrigin(r->getOrigin()); + srv->sendMsg(&fnr); + } + + + void DHT::announce(AnnounceReq* r) + { + if (!running) + return; + + // ignore requests we get from ourself + if (r->getID() == node->getOurID()) + return; + + Out(SYS_DHT|LOG_DEBUG) << "DHT: got announce request" << endl; + node->recieved(this,r); + // first check if the token is OK + dht::Key token = r->getToken(); + if (!db->checkToken(token,r->getOrigin().ipAddress().IPv4Addr(),r->getOrigin().port())) + return; + + // everything OK, so store the value + Uint8 tdata[6]; + bt::WriteUint32(tdata,0,r->getOrigin().ipAddress().IPv4Addr()); + bt::WriteUint16(tdata,4,r->getPort()); + db->store(r->getInfoHash(),DBItem(tdata)); + // send a proper response to indicate everything is OK + AnnounceRsp rsp(r->getMTID(),node->getOurID()); + rsp.setOrigin(r->getOrigin()); + srv->sendMsg(&rsp); + } + + + + void DHT::getPeers(GetPeersReq* r) + { + if (!running) + return; + + // ignore requests we get from ourself + if (r->getID() == node->getOurID()) + return; + + Out(SYS_DHT|LOG_DEBUG) << "DHT: got getPeers request" << endl; + node->recieved(this,r); + DBItemList dbl; + db->sample(r->getInfoHash(),dbl,50); + + // generate a token + dht::Key token = db->genToken(r->getOrigin().ipAddress().IPv4Addr(),r->getOrigin().port()); + + if (dbl.count() == 0) + { + // if data is null do the same as when we have a findNode request + // find the K closest nodes and pack them + KClosestNodesSearch kns(r->getInfoHash(),K); + node->findKClosestNodes(kns); + Uint32 rs = kns.requiredSpace(); + // create the data + QByteArray nodes(rs); + // pack the found nodes in a byte array + if (rs > 0) + kns.pack(nodes); + + GetPeersRsp fnr(r->getMTID(),node->getOurID(),nodes,token); + fnr.setOrigin(r->getOrigin()); + srv->sendMsg(&fnr); + } + else + { + // send a get peers response + GetPeersRsp fvr(r->getMTID(),node->getOurID(),dbl,token); + fvr.setOrigin(r->getOrigin()); + srv->sendMsg(&fvr); + } + } + + void DHT::response(MsgBase* r) + { + if (!running) + return; + + node->recieved(this,r); + } + + void DHT::error(ErrMsg* ) + {} + + + void DHT::portRecieved(const QString & ip,bt::Uint16 port) + { + if (!running) + return; + + Out(SYS_DHT|LOG_DEBUG) << "Sending ping request to " << ip << ":" << port << endl; + PingReq* r = new PingReq(node->getOurID()); + r->setOrigin(KInetSocketAddress(ip,port)); + srv->doCall(r); + } + + bool DHT::canStartTask() const + { + // we can start a task if we have less then 7 runnning and + // there are at least 16 RPC slots available + if (tman->getNumTasks() >= 7) + return false; + else if (256 - srv->getNumActiveRPCCalls() <= 16) + return false; + + return true; + } + + AnnounceTask* DHT::announce(const bt::SHA1Hash & info_hash,bt::Uint16 port) + { + if (!running) + return 0; + + KClosestNodesSearch kns(info_hash,K); + node->findKClosestNodes(kns); + if (kns.getNumEntries() > 0) + { + Out(SYS_DHT|LOG_NOTICE) << "DHT: Doing announce " << endl; + AnnounceTask* at = new AnnounceTask(db,srv,node,info_hash,port); + at->start(kns,!canStartTask()); + tman->addTask(at); + if (!db->contains(info_hash)) + db->insert(info_hash); + return at; + } + + return 0; + } + + NodeLookup* DHT::refreshBucket(const dht::Key & id,KBucket & bucket) + { + if (!running) + return 0; + + KClosestNodesSearch kns(id,K); + bucket.findKClosestNodes(kns); + bucket.updateRefreshTimer(); + if (kns.getNumEntries() > 0) + { + Out(SYS_DHT|LOG_DEBUG) << "DHT: refreshing bucket " << endl; + NodeLookup* nl = new NodeLookup(id,srv,node); + nl->start(kns,!canStartTask()); + tman->addTask(nl); + return nl; + } + + return 0; + } + + NodeLookup* DHT::findNode(const dht::Key & id) + { + if (!running) + return 0; + + KClosestNodesSearch kns(id,K); + node->findKClosestNodes(kns); + if (kns.getNumEntries() > 0) + { + Out(SYS_DHT|LOG_DEBUG) << "DHT: finding node " << endl; + NodeLookup* at = new NodeLookup(id,srv,node); + at->start(kns,!canStartTask()); + tman->addTask(at); + return at; + } + + return 0; + } + + void DHT::update() + { + if (!running) + return; + + if (expire_timer.getElapsedSinceUpdate() > 5*60*1000) + { + db->expire(bt::GetCurrentTime()); + expire_timer.update(); + } + + node->refreshBuckets(this); + tman->removeFinishedTasks(this); + stats.num_tasks = tman->getNumTasks() + tman->getNumQueuedTasks(); + stats.num_peers = node->getNumEntriesInRoutingTable(); + } + + void DHT::timeout(const MsgBase* r) + { + node->onTimeout(r); + } + + void DHT::addDHTNode(const QString & host,Uint16 hport) + { + if (!running) + return; + + KResolverResults res = KResolver::resolve(host,QString::number(hport)); + if (res.count() > 0) + { + srv->ping(node->getOurID(),res.front().address()); + } + } + + QMap<QString, int> DHT::getClosestGoodNodes(int maxNodes) + { + QMap<QString, int> map; + + if(!node) + return map; + + int max = 0; + KClosestNodesSearch kns(node->getOurID(), maxNodes*2); + node->findKClosestNodes(kns); + + KClosestNodesSearch::Itr it; + for(it = kns.begin(); it != kns.end(); ++it) + { + KBucketEntry e = it->second; + + if(!e.isGood()) + continue; + + KInetSocketAddress a = e.getAddress(); + + map.insert(a.ipAddress().toString(), a.port()); + if(++max >= maxNodes) + break; + } + + return map; + } +} + +#include "dht.moc" diff --git a/libktorrent/kademlia/dht.h b/libktorrent/kademlia/dht.h new file mode 100644 index 0000000..8642836 --- /dev/null +++ b/libktorrent/kademlia/dht.h @@ -0,0 +1,136 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef DHTDHT_H +#define DHTDHT_H + +#include <qtimer.h> +#include <qstring.h> +#include <qmap.h> +#include <util/constants.h> +#include <util/timer.h> +#include "key.h" +#include "dhtbase.h" + +namespace bt +{ + class SHA1Hash; +} + +namespace KNetwork +{ + class KInetSocketAddress; +} + +namespace dht +{ + class Node; + class RPCServer; + class PingReq; + class FindNodeReq; + class FindValueReq; + class StoreValueReq; + class GetPeersReq; + class MsgBase; + class ErrMsg; + class MsgBase; + class AnnounceReq; + class Database; + class TaskManager; + class Task; + class AnnounceTask; + class NodeLookup; + class KBucket; + + /** + @author Joris Guisson <joris.guisson@gmail.com> + */ + class DHT : public DHTBase + { + Q_OBJECT + public: + DHT(); + virtual ~DHT(); + + void ping(PingReq* r); + void findNode(FindNodeReq* r); + void response(MsgBase* r); + void getPeers(GetPeersReq* r); + void announce(AnnounceReq* r); + void error(ErrMsg* r); + void timeout(const MsgBase* r); + + /** + * A Peer has received a PORT message, and uses this function to alert the DHT of it. + * @param ip The IP of the peer + * @param port The port in the PORT message + */ + void portRecieved(const QString & ip,bt::Uint16 port); + + /** + * Do an announce on the DHT network + * @param info_hash The info_hash + * @param port The port + * @return The task which handles this + */ + AnnounceTask* announce(const bt::SHA1Hash & info_hash,bt::Uint16 port); + + /** + * Refresh a bucket using a find node task. + * @param id The id + * @param bucket The bucket to refresh + */ + NodeLookup* refreshBucket(const dht::Key & id,KBucket & bucket); + + /** + * Do a NodeLookup. + * @param id The id of the key to search + */ + NodeLookup* findNode(const dht::Key & id); + + /// See if it is possible to start a task + bool canStartTask() const; + + void start(const QString & table,const QString & key_file,bt::Uint16 port); + void stop(); + void addDHTNode(const QString & host,bt::Uint16 hport); + + /** + * Returns maxNodes number of <IP address, port> nodes + * that are closest to ourselves and are good. + * @param maxNodes maximum nr of nodes in QMap to return. + */ + QMap<QString, int> getClosestGoodNodes(int maxNodes); + + private slots: + void update(); + + private: + Node* node; + RPCServer* srv; + Database* db; + TaskManager* tman; + bt::Timer expire_timer; + QString table_file; + QTimer update_timer; + }; + +} + +#endif diff --git a/libktorrent/kademlia/dhtbase.cpp b/libktorrent/kademlia/dhtbase.cpp new file mode 100644 index 0000000..b0ff582 --- /dev/null +++ b/libktorrent/kademlia/dhtbase.cpp @@ -0,0 +1,37 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 "dhtbase.h" + +namespace dht +{ + + DHTBase::DHTBase() : running(false),port(0) + { + stats.num_peers = 0; + stats.num_tasks = 0; + } + + + DHTBase::~DHTBase() + {} +} + +#include "dhtbase.moc" + diff --git a/libktorrent/kademlia/dhtbase.h b/libktorrent/kademlia/dhtbase.h new file mode 100644 index 0000000..dfa880a --- /dev/null +++ b/libktorrent/kademlia/dhtbase.h @@ -0,0 +1,129 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef DHTDHTBASE_H +#define DHTDHTBASE_H + +#include <qobject.h> +#include <util/constants.h> + +class QString; + +namespace bt +{ + class SHA1Hash; +} + +namespace dht +{ + class AnnounceTask; + + struct Stats + { + /// number of peers in the routing table + bt::Uint32 num_peers; + /// Number of running tasks + bt::Uint32 num_tasks; + }; + + /** + * @author Joris Guisson <joris.guisson@gmail.com> + * + * Interface for DHT class, this is to keep other things separate from the inner workings + * of the DHT. + */ + class DHTBase : public QObject + { + Q_OBJECT + public: + DHTBase(); + virtual ~DHTBase(); + + + /** + * Start the DHT + * @param table File where the save table is located + * @param key_file The file where the key is stored + * @param port The port to use + */ + virtual void start(const QString & table,const QString & key_file,bt::Uint16 port) = 0; + + /** + * Stop the DHT + */ + virtual void stop() = 0; + + /** + * Update the DHT + */ + virtual void update() = 0; + + /** + * A Peer has received a PORT message, and uses this function to alert the DHT of it. + * @param ip The IP of the peer + * @param port The port in the PORT message + */ + virtual void portRecieved(const QString & ip,bt::Uint16 port) = 0; + + /** + * Do an announce on the DHT network + * @param info_hash The info_hash + * @param port The port + * @return The task which handles this + */ + virtual AnnounceTask* announce(const bt::SHA1Hash & info_hash,bt::Uint16 port) = 0; + + /** + * See if the DHT is running. + */ + bool isRunning() const {return running;} + + /// Get the DHT port + bt::Uint16 getPort() const {return port;} + + /// Get statistics about the DHT + const dht::Stats & getStats() const {return stats;} + + /** + * Add a DHT node. This node shall be pinged immediately. + * @param host The hostname or ip + * @param hport The port of the host + */ + virtual void addDHTNode(const QString & host,bt::Uint16 hport) = 0; + + /** + * Returns maxNodes number of <IP address, port> nodes + * that are closest to ourselves and are good. + * @param maxNodes maximum nr of nodes in QMap to return. + */ + virtual QMap<QString, int> getClosestGoodNodes(int maxNodes) = 0; + + signals: + void started(); + void stopped(); + + protected: + bool running; + bt::Uint16 port; + dht::Stats stats; + }; + +} + +#endif diff --git a/libktorrent/kademlia/dhttrackerbackend.cpp b/libktorrent/kademlia/dhttrackerbackend.cpp new file mode 100644 index 0000000..c90e6f7 --- /dev/null +++ b/libktorrent/kademlia/dhttrackerbackend.cpp @@ -0,0 +1,154 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <kurl.h> +#include <qhostaddress.h> +#include <util/log.h> +#include <util/functions.h> +#include <torrent/globals.h> +#include <torrent/server.h> +#include <torrent/peermanager.h> +#include <interfaces/torrentinterface.h> +#include "dhttrackerbackend.h" +#include "dht.h" +#include "announcetask.h" + +using namespace bt; + +namespace dht +{ + + DHTTrackerBackend::DHTTrackerBackend(DHTBase & dh_table,kt::TorrentInterface* tor) + : dh_table(dh_table),curr_task(0),tor(tor) + { + connect(&timer,SIGNAL(timeout()),this,SLOT(onTimeout())); + connect(&dh_table,SIGNAL(started()),this,SLOT(manualUpdate())); + connect(&dh_table,SIGNAL(stopped()),this,SLOT(dhtStopped())); + started = false; + } + + + DHTTrackerBackend::~DHTTrackerBackend() + { + if (curr_task) + curr_task->kill(); + } + + void DHTTrackerBackend::start() + { + started = true; + if (dh_table.isRunning()) + doRequest(); + } + + void DHTTrackerBackend::dhtStopped() + { + stop(0); + curr_task = 0; + } + + void DHTTrackerBackend::stop(bt::WaitJob*) + { + started = false; + if (curr_task) + { + curr_task->kill(); + timer.stop(); + } + } + + void DHTTrackerBackend::manualUpdate() + { + if (dh_table.isRunning() && started) + doRequest(); + } + + + bool DHTTrackerBackend::doRequest() + { + if (!dh_table.isRunning()) + return false; + + if (curr_task) + return true; + + const SHA1Hash & info_hash = tor->getInfoHash(); + Uint16 port = bt::Globals::instance().getServer().getPortInUse(); + curr_task = dh_table.announce(info_hash,port); + if (curr_task) + { + for (Uint32 i = 0;i < tor->getNumDHTNodes();i++) + { + const kt::DHTNode & n = tor->getDHTNode(i); + curr_task->addDHTNode(n.ip,n.port); + } + connect(curr_task,SIGNAL(dataReady( Task* )),this,SLOT(onDataReady( Task* ))); + connect(curr_task,SIGNAL(finished( Task* )),this,SLOT(onFinished( Task* ))); + + return true; + } + + return false; + } + + void DHTTrackerBackend::onFinished(Task* t) + { + if (curr_task == t) + { + onDataReady(curr_task); + curr_task = 0; + // do another announce in 5 minutes or so + timer.start(5 * 60 * 1000,true); + } + } + + void DHTTrackerBackend::onDataReady(Task* t) + { + if (curr_task == t) + { + Uint32 cnt = 0; + DBItem item; + while (curr_task->takeItem(item)) + { + Uint16 port = bt::ReadUint16(item.getData(),4); + QString ip = QHostAddress(ReadUint32(item.getData(),0)).toString(); + + addPeer(ip,port); + cnt++; + } + + if (cnt) + { + Out(SYS_DHT|LOG_NOTICE) << + QString("DHT: Got %1 potential peers for torrent %2") + .arg(cnt).arg(tor->getStats().torrent_name) << endl; + peersReady(this); + } + } + } + + void DHTTrackerBackend::onTimeout() + { + if (dh_table.isRunning() && started) + doRequest(); + } + +} + +#include "dhttrackerbackend.moc" diff --git a/libktorrent/kademlia/dhttrackerbackend.h b/libktorrent/kademlia/dhttrackerbackend.h new file mode 100644 index 0000000..355aab9 --- /dev/null +++ b/libktorrent/kademlia/dhttrackerbackend.h @@ -0,0 +1,75 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef DHTDHTTRACKERBACKEND_H +#define DHTDHTTRACKERBACKEND_H + +#include <qtimer.h> +#include <interfaces/peersource.h> +#include "task.h" + +namespace kt +{ + class TorrentInterface; +} + +namespace bt +{ + class WaitJob; +} + + +namespace dht +{ + class DHTBase; + class AnnounceTask; + + + /** + @author Joris Guisson <joris.guisson@gmail.com> + */ + class DHTTrackerBackend : public kt::PeerSource + { + Q_OBJECT + public: + DHTTrackerBackend(DHTBase & dh_table,kt::TorrentInterface* tor); + virtual ~DHTTrackerBackend(); + + virtual void start(); + virtual void stop(bt::WaitJob* wjob = 0); + virtual void manualUpdate(); + + private slots: + void onTimeout(); + bool doRequest(); + void onDataReady(Task* t); + void onFinished(Task* t); + void dhtStopped(); + + private: + DHTBase & dh_table; + AnnounceTask* curr_task; + kt::TorrentInterface* tor; + QTimer timer; + bool started; + }; + +} + +#endif diff --git a/libktorrent/kademlia/kbucket.cpp b/libktorrent/kademlia/kbucket.cpp new file mode 100644 index 0000000..fb60d1b --- /dev/null +++ b/libktorrent/kademlia/kbucket.cpp @@ -0,0 +1,355 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <ksocketaddress.h> +#include <util/file.h> +#include <util/log.h> +#include <util/functions.h> +#include <netinet/in.h> +#include "kbucket.h" +#include "kclosestnodessearch.h" +#include "rpcserver.h" +#include "node.h" + +using namespace KNetwork; +using namespace bt; + +namespace dht +{ + KBucketEntry::KBucketEntry() + { + last_responded = bt::GetCurrentTime(); + failed_queries = 0; + questionable_pings = 0; + } + + KBucketEntry::KBucketEntry(const KInetSocketAddress & addr,const Key & id) + : addr(addr),node_id(id) + { + last_responded = bt::GetCurrentTime(); + failed_queries = 0; + questionable_pings = 0; + } + + KBucketEntry::KBucketEntry(const KBucketEntry & other) + : addr(other.addr),node_id(other.node_id), + last_responded(other.last_responded),failed_queries(other.failed_queries),questionable_pings(other.questionable_pings) + {} + + + KBucketEntry::~KBucketEntry() + {} + + KBucketEntry & KBucketEntry::operator = (const KBucketEntry & other) + { + addr = other.addr; + node_id = other.node_id; + last_responded = other.last_responded; + failed_queries = other.failed_queries; + questionable_pings = other.questionable_pings; + return *this; + } + + bool KBucketEntry::operator == (const KBucketEntry & entry) const + { + return addr == entry.addr && node_id == entry.node_id; + } + + bool KBucketEntry::isGood() const + { + if (bt::GetCurrentTime() - last_responded > 15 * 60 * 1000) + return false; + else + return true; + } + + bool KBucketEntry::isQuestionable() const + { + if (bt::GetCurrentTime() - last_responded > 15 * 60 * 1000) + return true; + else + return false; + } + + + bool KBucketEntry::isBad() const + { + if (isGood()) + return false; + + return failed_queries > 2 || questionable_pings > 2; + } + + void KBucketEntry::hasResponded() + { + last_responded = bt::GetCurrentTime(); + failed_queries = 0; // reset failed queries + questionable_pings = 0; + } + + + ////////////////////////////////////////////////////////// + + KBucket::KBucket(Uint32 idx,RPCServer* srv,Node* node) + : idx(idx),srv(srv),node(node) + { + last_modified = bt::GetCurrentTime(); + refresh_task = 0; + } + + + KBucket::~KBucket() + {} + + void KBucket::insert(const KBucketEntry & entry) + { + QValueList<KBucketEntry>::iterator i = entries.find(entry); + + // If in the list, move it to the end + if (i != entries.end()) + { + KBucketEntry & e = *i; + e.hasResponded(); + last_modified = bt::GetCurrentTime(); + entries.remove(i); + entries.append(entry); + return; + } + + // insert if not already in the list and we still have room + if (i == entries.end() && entries.count() < dht::K) + { + entries.append(entry); + last_modified = bt::GetCurrentTime(); + } + else if (!replaceBadEntry(entry)) + { + // ping questionable nodes when replacing a bad one fails + pingQuestionable(entry); + } + } + + void KBucket::onResponse(RPCCall* c,MsgBase* rsp) + { + last_modified = bt::GetCurrentTime(); + + if (!pending_entries_busy_pinging.contains(c)) + return; + + KBucketEntry entry = pending_entries_busy_pinging[c]; + pending_entries_busy_pinging.erase(c); // call is done so erase it + + // we have a response so try to find the next bad or questionable node + // if we do not have room see if we can get rid of some bad peers + if (!replaceBadEntry(entry)) // if no bad peers ping a questionable one + pingQuestionable(entry); + + } + + + + void KBucket::onTimeout(RPCCall* c) + { + if (!pending_entries_busy_pinging.contains(c)) + return; + + KBucketEntry entry = pending_entries_busy_pinging[c]; + + // replace the entry which timed out + QValueList<KBucketEntry>::iterator i; + for (i = entries.begin();i != entries.end();i++) + { + KBucketEntry & e = *i; + if (e.getAddress() == c->getRequest()->getOrigin()) + { + last_modified = bt::GetCurrentTime(); + entries.remove(i); + entries.append(entry); + break; + } + } + pending_entries_busy_pinging.erase(c); // call is done so erase it + // see if we can do another pending entry + if (pending_entries_busy_pinging.count() < 2 && pending_entries.count() > 0) + { + KBucketEntry pe = pending_entries.front(); + pending_entries.pop_front(); + if (!replaceBadEntry(pe)) // if no bad peers ping a questionable one + pingQuestionable(pe); + } + } + + void KBucket::pingQuestionable(const KBucketEntry & replacement_entry) + { + if (pending_entries_busy_pinging.count() >= 2) + { + pending_entries.append(replacement_entry); // lets not have to many pending_entries calls going on + return; + } + + QValueList<KBucketEntry>::iterator i; + // we haven't found any bad ones so try the questionable ones + for (i = entries.begin();i != entries.end();i++) + { + KBucketEntry & e = *i; + if (e.isQuestionable()) + { + Out(SYS_DHT|LOG_DEBUG) << "Pinging questionable node : " << e.getAddress().toString() << endl; + PingReq* p = new PingReq(node->getOurID()); + p->setDestination(e.getAddress()); + RPCCall* c = srv->doCall(p); + if (c) + { + e.onPingQuestionable(); + c->addListener(this); + // add the pending entry + pending_entries_busy_pinging.insert(c,replacement_entry); + return; + } + } + } + } + + bool KBucket::replaceBadEntry(const KBucketEntry & entry) + { + QValueList<KBucketEntry>::iterator i; + for (i = entries.begin();i != entries.end();i++) + { + KBucketEntry & e = *i; + if (e.isBad()) + { + // bad one get rid of it + last_modified = bt::GetCurrentTime(); + entries.remove(i); + entries.append(entry); + return true; + } + } + return false; + } + + bool KBucket::contains(const KBucketEntry & entry) const + { + return entries.contains(entry); + } + + void KBucket::findKClosestNodes(KClosestNodesSearch & kns) + { + QValueList<KBucketEntry>::iterator i = entries.begin(); + while (i != entries.end()) + { + kns.tryInsert(*i); + i++; + } + } + + bool KBucket::onTimeout(const KInetSocketAddress & addr) + { + QValueList<KBucketEntry>::iterator i; + + for (i = entries.begin();i != entries.end();i++) + { + KBucketEntry & e = *i; + if (e.getAddress() == addr) + { + e.requestTimeout(); + return true; + } + } + return false; + } + + bool KBucket::needsToBeRefreshed() const + { + bt::TimeStamp now = bt::GetCurrentTime(); + if (last_modified > now) + { + last_modified = now; + return false; + } + + return !refresh_task && entries.count() > 0 && (now - last_modified > BUCKET_REFRESH_INTERVAL); + } + + void KBucket::updateRefreshTimer() + { + last_modified = bt::GetCurrentTime(); + } + + + + void KBucket::save(bt::File & fptr) + { + BucketHeader hdr; + hdr.magic = BUCKET_MAGIC_NUMBER; + hdr.index = idx; + hdr.num_entries = entries.count(); + + fptr.write(&hdr,sizeof(BucketHeader)); + QValueList<KBucketEntry>::iterator i; + for (i = entries.begin();i != entries.end();i++) + { + KBucketEntry & e = *i; + const KIpAddress & ip = e.getAddress().ipAddress(); + Uint8 tmp[26]; + bt::WriteUint32(tmp,0,ip.IPv4Addr()); + bt::WriteUint16(tmp,4,e.getAddress().port()); + memcpy(tmp+6,e.getID().getData(),20); + fptr.write(tmp,26); + } + } + + void KBucket::load(bt::File & fptr,const BucketHeader & hdr) + { + if (hdr.num_entries > K) + return; + + for (Uint32 i = 0;i < hdr.num_entries;i++) + { + Uint8 tmp[26]; + if (fptr.read(tmp,26) != 26) + return; + + entries.append(KBucketEntry( + KInetSocketAddress( + KIpAddress(bt::ReadUint32(tmp,0)), + bt::ReadUint16(tmp,4)), + dht::Key(tmp+6))); + } + } + + void KBucket::onFinished(Task* t) + { + if (t == refresh_task) + refresh_task = 0; + } + + void KBucket::setRefreshTask(Task* t) + { + refresh_task = t; + if (refresh_task) + { + connect(refresh_task,SIGNAL(finished( Task* )), + this,SLOT(onFinished( Task* ))); + } + } + +} + +#include "kbucket.moc" diff --git a/libktorrent/kademlia/kbucket.h b/libktorrent/kademlia/kbucket.h new file mode 100644 index 0000000..139ce10 --- /dev/null +++ b/libktorrent/kademlia/kbucket.h @@ -0,0 +1,212 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef DHTKBUCKET_H +#define DHTKBUCKET_H + +#include <qvaluelist.h> +#include <util/constants.h> +#include <ksocketaddress.h> +#include "key.h" +#include "rpccall.h" +#include "task.h" + +using bt::Uint32; +using bt::Uint16; +using bt::Uint8; +using KNetwork::KInetSocketAddress; + +namespace bt +{ + class File; +} + +namespace dht +{ + class RPCServer; + class KClosestNodesSearch; + class Node; + class Task; + + const Uint32 K = 8; + const Uint32 BUCKET_MAGIC_NUMBER = 0xB0C4B0C4; + const Uint32 BUCKET_REFRESH_INTERVAL = 15 * 60 * 1000; +// const Uint32 BUCKET_REFRESH_INTERVAL = 120 * 1000; + + struct BucketHeader + { + Uint32 magic; + Uint32 index; + Uint32 num_entries; + }; + + /** + * @author Joris Guisson + * + * Entry in a KBucket, it basically contains an ip_address of a node, + * the udp port of the node and a node_id. + */ + class KBucketEntry + { + KInetSocketAddress addr; + Key node_id; + bt::TimeStamp last_responded; + Uint32 failed_queries; + Uint32 questionable_pings; + public: + /** + * Constructor, sets everything to 0. + * @return + */ + KBucketEntry(); + + /** + * Constructor, set the ip, port and key + * @param addr socket address + * @param id ID of node + */ + KBucketEntry(const KInetSocketAddress & addr,const Key & id); + + /** + * Copy constructor. + * @param other KBucketEntry to copy + * @return + */ + KBucketEntry(const KBucketEntry & other); + + /// Destructor + virtual ~KBucketEntry(); + + /** + * Assignment operator. + * @param other Node to copy + * @return this KBucketEntry + */ + KBucketEntry & operator = (const KBucketEntry & other); + + /// Equality operator + bool operator == (const KBucketEntry & entry) const; + + /// Get the socket address of the node + const KInetSocketAddress & getAddress() const {return addr;} + + /// Get it's ID + const Key & getID() const {return node_id;} + + /// Is this node a good node + bool isGood() const; + + /// Is this node questionable (haven't heard from it in the last 15 minutes) + bool isQuestionable() const; + + /// Is it a bad node. (Hasn't responded to a query + bool isBad() const; + + /// Signal the entry that the peer has responded + void hasResponded(); + + /// A request timed out + void requestTimeout() {failed_queries++;} + + /// The entry has been pinged because it is questionable + void onPingQuestionable() {questionable_pings++;} + + /// The null entry + static KBucketEntry null; + }; + + + /** + * @author Joris Guisson + * + * A KBucket is just a list of KBucketEntry objects. + * The list is sorted by time last seen : + * The first element is the least recently seen, the last + * the most recently seen. + */ + class KBucket : public RPCCallListener + { + Q_OBJECT + + Uint32 idx; + QValueList<KBucketEntry> entries,pending_entries; + RPCServer* srv; + Node* node; + QMap<RPCCall*,KBucketEntry> pending_entries_busy_pinging; + mutable bt::TimeStamp last_modified; + Task* refresh_task; + public: + KBucket(Uint32 idx,RPCServer* srv,Node* node); + virtual ~KBucket(); + + /** + * Inserts an entry into the bucket. + * @param entry The entry to insert + */ + void insert(const KBucketEntry & entry); + + /// Get the least recently seen node + const KBucketEntry & leastRecentlySeen() const {return entries[0];} + + /// Get the number of entries + Uint32 getNumEntries() const {return entries.count();} + + /// See if this bucket contains an entry + bool contains(const KBucketEntry & entry) const; + + /** + * Find the K closest entries to a key and store them in the KClosestNodesSearch + * object. + * @param kns The object to storre the search results + */ + void findKClosestNodes(KClosestNodesSearch & kns); + + /** + * A peer failed to respond + * @param addr Address of the peer + */ + bool onTimeout(const KInetSocketAddress & addr); + + /// Check if the bucket needs to be refreshed + bool needsToBeRefreshed() const; + + /// save the bucket to a file + void save(bt::File & fptr); + + /// Load the bucket from a file + void load(bt::File & fptr,const BucketHeader & hdr); + + /// Update the refresh timer of the bucket + void updateRefreshTimer(); + + /// Set the refresh task + void setRefreshTask(Task* t); + + private: + virtual void onResponse(RPCCall* c,MsgBase* rsp); + virtual void onTimeout(RPCCall* c); + void pingQuestionable(const KBucketEntry & replacement_entry); + bool replaceBadEntry(const KBucketEntry & entry); + + private slots: + void onFinished(Task* t); + }; +} + +#endif diff --git a/libktorrent/kademlia/kclosestnodessearch.cpp b/libktorrent/kademlia/kclosestnodessearch.cpp new file mode 100644 index 0000000..4a97c7f --- /dev/null +++ b/libktorrent/kademlia/kclosestnodessearch.cpp @@ -0,0 +1,84 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <util/functions.h> +#include "kclosestnodessearch.h" +#include "pack.h" + +using namespace bt; +using namespace KNetwork; + +namespace dht +{ + typedef std::map<dht::Key,KBucketEntry>::iterator KNSitr; + + KClosestNodesSearch::KClosestNodesSearch(const dht::Key & key,Uint32 max_entries) + : key(key),max_entries(max_entries) + {} + + + KClosestNodesSearch::~KClosestNodesSearch() + {} + + + void KClosestNodesSearch::tryInsert(const KBucketEntry & e) + { + // calculate distance between key and e + dht::Key d = dht::Key::distance(key,e.getID()); + + if (emap.size() < max_entries) + { + // room in the map so just insert + emap.insert(std::make_pair(d,e)); + } + else + { + // now find the max distance + // seeing that the last element of the map has also + // the biggest distance to key (std::map is sorted on the distance) + // we just take the last + const dht::Key & max = emap.rbegin()->first; + if (d < max) + { + // insert if d is smaller then max + emap.insert(std::make_pair(d,e)); + // erase the old max value + emap.erase(max); + } + } + + } + + void KClosestNodesSearch::pack(QByteArray & ba) + { + // make sure we do not writ to much + Uint32 max_items = ba.size() / 26; + Uint32 j = 0; + + KNSitr i = emap.begin(); + while (i != emap.end() && j < max_items) + { + PackBucketEntry(i->second,ba,j*26); + i++; + j++; + i++; + } + } + +} diff --git a/libktorrent/kademlia/kclosestnodessearch.h b/libktorrent/kademlia/kclosestnodessearch.h new file mode 100644 index 0000000..e006a25 --- /dev/null +++ b/libktorrent/kademlia/kclosestnodessearch.h @@ -0,0 +1,90 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef DHTKCLOSESTNODESSEARCH_H +#define DHTKCLOSESTNODESSEARCH_H + +#include <map> +#include "key.h" +#include "kbucket.h" + +namespace dht +{ + + /** + * @author Joris Guisson <joris.guisson@gmail.com> + * + * Class used to store the search results during a K closests nodes search + * Note: we use a std::map because of lack of functionality in QMap + */ + class KClosestNodesSearch + { + dht::Key key; + std::map<dht::Key,KBucketEntry> emap; + Uint32 max_entries; + public: + /** + * Constructor sets the key to compare with + * @param key The key to compare with + * @param max_entries The maximum number of entries can be in the map + * @return + */ + KClosestNodesSearch(const dht::Key & key,Uint32 max_entries); + virtual ~KClosestNodesSearch(); + + typedef std::map<dht::Key,KBucketEntry>::iterator Itr; + typedef std::map<dht::Key,KBucketEntry>::const_iterator CItr; + + Itr begin() {return emap.begin();} + Itr end() {return emap.end();} + + CItr begin() const {return emap.begin();} + CItr end() const {return emap.end();} + + /// Get the target key of the search3 + const dht::Key & getSearchTarget() const {return key;} + + /// Get the number of entries. + bt::Uint32 getNumEntries() const {return emap.size();} + + /** + * Try to insert an entry. + * @param e The entry + */ + void tryInsert(const KBucketEntry & e); + + /** + * Gets the required space in bytes to pack the nodes. + * This should be used to determin the size of the buffer + * passed to pack. + * @return 26 * number of entries + */ + Uint32 requiredSpace() const {return emap.size()* 26;} + + /** + * Pack the search results in a buffer, the buffer should have + * enough space to store requiredSpace() bytes. + * @param ba The buffer + */ + void pack(QByteArray & ba); + }; + +} + +#endif diff --git a/libktorrent/kademlia/key.cpp b/libktorrent/kademlia/key.cpp new file mode 100644 index 0000000..6e62ff6 --- /dev/null +++ b/libktorrent/kademlia/key.cpp @@ -0,0 +1,110 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <time.h> +#include <stdlib.h> +#include <qcstring.h> +#include <util/constants.h> +#include "key.h" + +using namespace bt; + +namespace dht +{ + + Key::Key() + {} + + Key::Key(const bt::SHA1Hash & k) : bt::SHA1Hash(k) + { + } + + Key::Key(const Uint8* d) : bt::SHA1Hash(d) + { + } + + Key::Key(const QByteArray & ba) + { + for (Uint32 i = 0;i < 20 && i < ba.size();i++) + hash[i] = ba[i]; + } + + Key::~Key() + {} + + bool Key::operator == (const Key & other) const + { + return bt::SHA1Hash::operator ==(other); + } + + bool Key::operator != (const Key & other) const + { + return !operator == (other); + } + + bool Key::operator < (const Key & other) const + { + for (int i = 0;i < 20;i++) + { + if (hash[i] < other.hash[i]) + return true; + else if (hash[i] > other.hash[i]) + return false; + } + return false; + } + + bool Key::operator <= (const Key & other) const + { + return operator < (other) || operator == (other); + } + + bool Key::operator > (const Key & other) const + { + for (int i = 0;i < 20;i++) + { + if (hash[i] < other.hash[i]) + return false; + else if (hash[i] > other.hash[i]) + return true; + } + return false; + } + + bool Key::operator >= (const Key & other) const + { + return operator > (other) || operator == (other); + } + + Key Key::distance(const Key & a,const Key & b) + { + return a ^ b; + } + + Key Key::random() + { + srand(time(0)); + Key k; + for (int i = 0;i < 20;i++) + { + k.hash[i] = (Uint8)rand() % 0xFF; + } + return k; + } +} diff --git a/libktorrent/kademlia/key.h b/libktorrent/kademlia/key.h new file mode 100644 index 0000000..e818dc1 --- /dev/null +++ b/libktorrent/kademlia/key.h @@ -0,0 +1,129 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef DHTKEY_H +#define DHTKEY_H + +#include <qcstring.h> +#include <util/sha1hash.h> + + + +namespace dht +{ + + /** + * @author Joris Guisson + * @brief Key in the distributed hash table + * + * Key's in the distributed hash table are just SHA-1 hashes. + * Key provides all necesarry operators to be used as a value. + */ + class Key : public bt::SHA1Hash + { + public: + /** + * Constructor, sets key to 0. + */ + Key(); + + /** + * Copy constructor. Seeing that Key doesn't add any data + * we just pass a SHA1Hash, Key's are automatically covered by this + * @param k Hash to copy + */ + Key(const bt::SHA1Hash & k); + + /** + * Make a key out of a bytearray + * @param ba The QByteArray + */ + Key(const QByteArray & ba); + + /** + * Make a key out of a 20 byte array. + * @param d The array + */ + Key(const bt::Uint8* d); + + /// Destructor. + virtual ~Key(); + + /** + * Create a random key. + * @return A random Key + */ + static Key random(); + + /** + * Equality operator. + * @param other The key to compare + * @return true if this key is equal to other + */ + bool operator == (const Key & other) const; + + /** + * Inequality operator. + * @param other The key to compare + * @return true if this key is not equal to other + */ + bool operator != (const Key & other) const; + + /** + * Smaller then operator. + * @param other The key to compare + * @return rue if this key is smaller then other + */ + bool operator < (const Key & other) const; + + + /** + * Smaller then or equal operator. + * @param other The key to compare + * @return rue if this key is smaller then or equal to other + */ + bool operator <= (const Key & other) const; + + + /** + * Greater then operator. + * @param other The key to compare + * @return rue if this key is greater then other + */ + bool operator > (const Key & other) const; + + /** + * Greater then or equal operator. + * @param other The key to compare + * @return rue if this key is greater then or equal to other + */ + bool operator >= (const Key & other) const; + + /** + * The distance of two keys is the keys xor together. + * @param a The first key + * @param b The second key + * @return a xor b + */ + static Key distance(const Key & a,const Key & b); + }; + +} + +#endif diff --git a/libktorrent/kademlia/node.cpp b/libktorrent/kademlia/node.cpp new file mode 100644 index 0000000..96c39a4 --- /dev/null +++ b/libktorrent/kademlia/node.cpp @@ -0,0 +1,287 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <util/log.h> +#include <util/file.h> +#include <util/fileops.h> +#include <util/functions.h> +#include <torrent/globals.h> +#include "node.h" +#include "rpcmsg.h" +#include "key.h" +#include "rpccall.h" +#include "rpcserver.h" +#include "kclosestnodessearch.h" +#include "dht.h" +#include "nodelookup.h" + +using namespace bt; +using namespace KNetwork; + +namespace dht +{ + static void SaveKey(const dht::Key & key,const QString & key_file) + { + bt::File fptr; + if (!fptr.open(key_file,"wb")) + { + Out(SYS_DHT|LOG_IMPORTANT) << "DHT: Cannot open file " << key_file << " : " << fptr.errorString() << endl; + return; + } + + fptr.write(key.getData(),20); + fptr.close(); + } + + static dht::Key LoadKey(const QString & key_file,bool & new_key) + { + bt::File fptr; + if (!fptr.open(key_file,"rb")) + { + Out(SYS_DHT|LOG_IMPORTANT) << "DHT: Cannot open file " << key_file << " : " << fptr.errorString() << endl; + dht::Key r = dht::Key::random(); + SaveKey(r,key_file); + new_key = true; + return r; + } + + Uint8 data[20]; + if (fptr.read(data,20) != 20) + { + dht::Key r = dht::Key::random(); + SaveKey(r,key_file); + new_key = true; + return r; + } + + new_key = false; + return dht::Key(data); + } + + Node::Node(RPCServer* srv,const QString & key_file) : srv(srv) + { + num_receives = 0; + num_entries = 0; + delete_table = false; + our_id = LoadKey(key_file,delete_table); + for (int i = 0;i < 160;i++) + bucket[i] = 0; + } + + + Node::~Node() + { + for (int i = 0;i < 160;i++) + { + KBucket* b = bucket[i]; + if (b) + delete b; + } + } + + Uint8 Node::findBucket(const dht::Key & id) + { + // XOR our id and the sender's ID + dht::Key d = dht::Key::distance(id,our_id); + // now use the first on bit to determin which bucket it should go in + + Uint8 bit_on = 0xFF; + for (Uint32 i = 0;i < 20;i++) + { + // get the byte + Uint8 b = *(d.getData() + i); + // no bit on in this byte so continue + if (b == 0x00) + continue; + + for (Uint8 j = 0;j < 8;j++) + { + if (b & (0x80 >> j)) + { + // we have found the bit + bit_on = (19 - i)*8 + (7 - j); + return bit_on; + } + } + } + return bit_on; + } + + void Node::recieved(DHT* dh_table,const MsgBase* msg) + { + Uint8 bit_on = findBucket(msg->getID()); + + // return if bit_on is not good + if (bit_on >= 160) + return; + + // make the bucket if it doesn't exist + if (!bucket[bit_on]) + bucket[bit_on] = new KBucket(bit_on,srv,this); + + // insert it into the bucket + KBucket* kb = bucket[bit_on]; + kb->insert(KBucketEntry(msg->getOrigin(),msg->getID())); + num_receives++; + if (num_receives == 3) + { + // do a node lookup upon our own id + // when we insert the first entry in the table + dh_table->findNode(our_id); + } + + num_entries = 0; + for (Uint32 i = 0;i < 160;i++) + if (bucket[i]) + num_entries += bucket[i]->getNumEntries(); + } + + void Node::findKClosestNodes(KClosestNodesSearch & kns) + { + // go over all buckets until + for (Uint32 i = 0;i < 160;i++) + { + if (bucket[i]) + { + bucket[i]->findKClosestNodes(kns); + } + } + } + + void Node::onTimeout(const MsgBase* msg) + { + for (Uint32 i = 0;i < 160;i++) + { + if (bucket[i] && bucket[i]->onTimeout(msg->getDestination())) + { + return; + } + } + } + + /// Generate a random key which lies in a certain bucket + Key RandomKeyInBucket(Uint32 b,const Key & our_id) + { + // first generate a random one + Key r = dht::Key::random(); + Uint8* data = (Uint8*)r.getData(); + + // before we hit bit b, everything needs to be equal to our_id + Uint8 nb = b / 8; + for (Uint8 i = 0;i < nb;i++) + data[i] = *(our_id.getData() + i); + + + // copy all bits of ob, until we hit the bit which needs to be different + Uint8 ob = *(our_id.getData() + nb); + for (Uint8 j = 0;j < b % 8;j++) + { + if ((0x80 >> j) & ob) + data[nb] |= (0x80 >> j); + else + data[nb] &= ~(0x80 >> j); + } + + // if the bit b is on turn it off else turn it on + if ((0x80 >> (b % 8)) & ob) + data[nb] &= ~(0x80 >> (b % 8)); + else + data[nb] |= (0x80 >> (b % 8)); + + return Key(data); + } + + void Node::refreshBuckets(DHT* dh_table) + { + for (Uint32 i = 0;i < 160;i++) + { + KBucket* b = bucket[i]; + if (b && b->needsToBeRefreshed()) + { + // the key needs to be the refreshed + NodeLookup* nl = dh_table->refreshBucket(RandomKeyInBucket(i,our_id),*b); + if (nl) + b->setRefreshTask(nl); + } + } + } + + + void Node::saveTable(const QString & file) + { + bt::File fptr; + if (!fptr.open(file,"wb")) + { + Out(SYS_DHT|LOG_IMPORTANT) << "DHT: Cannot open file " << file << " : " << fptr.errorString() << endl; + return; + } + + for (Uint32 i = 0;i < 160;i++) + { + KBucket* b = bucket[i]; + if (b) + { + b->save(fptr); + } + } + } + + void Node::loadTable(const QString & file) + { + if (delete_table) + { + delete_table = false; + bt::Delete(file,true); + Out(SYS_DHT|LOG_IMPORTANT) << "DHT: new key, so removing table" << endl; + return; + } + + bt::File fptr; + if (!fptr.open(file,"rb")) + { + Out(SYS_DHT|LOG_IMPORTANT) << "DHT: Cannot open file " << file << " : " << fptr.errorString() << endl; + return; + } + + num_entries = 0; + while (!fptr.eof()) + { + BucketHeader hdr; + if (fptr.read(&hdr,sizeof(BucketHeader)) != sizeof(BucketHeader)) + return; + + if (hdr.magic != dht::BUCKET_MAGIC_NUMBER || hdr.num_entries > dht::K || hdr.index > 160) + return; + + if (hdr.num_entries == 0) + continue; + + Out(SYS_DHT|LOG_NOTICE) << "DHT: Loading bucket " << hdr.index << endl; + if (bucket[hdr.index]) + delete bucket[hdr.index]; + + bucket[hdr.index] = new KBucket(hdr.index,srv,this); + bucket[hdr.index]->load(fptr,hdr); + num_entries += bucket[hdr.index]->getNumEntries(); + } + } +} + +#include "node.moc" diff --git a/libktorrent/kademlia/node.h b/libktorrent/kademlia/node.h new file mode 100644 index 0000000..56f41f1 --- /dev/null +++ b/libktorrent/kademlia/node.h @@ -0,0 +1,103 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef DHTNODE_H +#define DHTNODE_H + +#include <qobject.h> +#include "key.h" +#include "kbucket.h" + + +using bt::Uint8; + +namespace dht +{ + class DHT; + class MsgBase; + class RPCServer; + class KClosestNodesSearch; + + /** + * @author Joris Guisson + * + * A Node represents us in the kademlia network. It contains + * our id and 160 KBucket's. + * A KBucketEntry is in node i, when the difference between our id and + * the KBucketEntry's id is between 2 to the power i and 2 to the power i+1. + */ + class Node : public QObject + { + Q_OBJECT + public: + Node(RPCServer* srv,const QString & key_file); + virtual ~Node(); + + /** + * An RPC message was received, the node must now update + * the right bucket. + * @param dh_table The DHT + * @param msg The message + * @param srv The RPCServer to send a ping if necessary + */ + void recieved(DHT* dh_table,const MsgBase* msg); + + /// Get our own ID + const dht::Key & getOurID() const {return our_id;} + + /** + * Find the K closest entries to a key and store them in the KClosestNodesSearch + * object. + * @param kns The object to storre the search results + */ + void findKClosestNodes(KClosestNodesSearch & kns); + + /** + * Increase the failed queries count of the bucket entry we sent the message to + */ + void onTimeout(const MsgBase* msg); + + /// Check if a buckets needs to be refreshed, and refresh if necesarry + void refreshBuckets(DHT* dh_table); + + /// Save the routing table to a file + void saveTable(const QString & file); + + /// Load the routing table from a file + void loadTable(const QString & file); + + /// Get the number of entries in the routing table + Uint32 getNumEntriesInRoutingTable() const {return num_entries;} + private: + Uint8 findBucket(const dht::Key & id); + + + + private: + dht::Key our_id; + KBucket* bucket[160]; + RPCServer* srv; + Uint32 num_receives; + Uint32 num_entries; + bool delete_table; + }; + +} + +#endif diff --git a/libktorrent/kademlia/nodelookup.cpp b/libktorrent/kademlia/nodelookup.cpp new file mode 100644 index 0000000..9fa616c --- /dev/null +++ b/libktorrent/kademlia/nodelookup.cpp @@ -0,0 +1,98 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <util/log.h> +#include <torrent/globals.h> +#include "nodelookup.h" +#include "rpcmsg.h" +#include "node.h" +#include "pack.h" + +using namespace bt; + +namespace dht +{ + + NodeLookup::NodeLookup(const dht::Key & key,RPCServer* rpc,Node* node) + : Task(rpc,node),node_id(key),num_nodes_rsp(0) + { + } + + + NodeLookup::~NodeLookup() + {} + + + void NodeLookup::callFinished(RPCCall* ,MsgBase* rsp) + { + // Out() << "NodeLookup::callFinished" << endl; + if (isFinished()) + return; + + // check the response and see if it is a good one + if (rsp->getMethod() == dht::FIND_NODE && rsp->getType() == dht::RSP_MSG) + { + FindNodeRsp* fnr = (FindNodeRsp*)rsp; + const QByteArray & nodes = fnr->getNodes(); + Uint32 nnodes = nodes.size() / 26; + for (Uint32 j = 0;j < nnodes;j++) + { + // unpack an entry and add it to the todo list + KBucketEntry e = UnpackBucketEntry(nodes,j*26); + // lets not talk to ourself + if (e.getID() != node->getOurID() && !todo.contains(e) && !visited.contains(e)) + todo.append(e); + } + num_nodes_rsp++; + } + } + + void NodeLookup::callTimeout(RPCCall*) + { + // Out() << "NodeLookup::callTimeout" << endl; + } + + void NodeLookup::update() + { + // Out() << "NodeLookup::update" << endl; + // Out() << "todo = " << todo.count() << " ; visited = " << visited.count() << endl; + // go over the todo list and send find node calls + // until we have nothing left + while (!todo.empty() && canDoRequest()) + { + KBucketEntry e = todo.first(); + // only send a findNode if we haven't allrready visited the node + if (!visited.contains(e)) + { + // send a findNode to the node + FindNodeReq* fnr = new FindNodeReq(node->getOurID(),node_id); + fnr->setOrigin(e.getAddress()); + rpcCall(fnr); + visited.append(e); + } + // remove the entry from the todo list + todo.pop_front(); + } + + if (todo.empty() && getNumOutstandingRequests() == 0 && !isFinished()) + done(); + else if (num_nodes_rsp > 50) + done(); // quit after 50 nodes responses + } +} diff --git a/libktorrent/kademlia/nodelookup.h b/libktorrent/kademlia/nodelookup.h new file mode 100644 index 0000000..ff19e92 --- /dev/null +++ b/libktorrent/kademlia/nodelookup.h @@ -0,0 +1,52 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef DHTNODELOOKUP_H +#define DHTNODELOOKUP_H + +#include "key.h" +#include "task.h" + +namespace dht +{ + class Node; + class RPCServer; + + /** + * @author Joris Guisson <joris.guisson@gmail.com> + * + * Task to do a node lookup. + */ + class NodeLookup : public Task + { + public: + NodeLookup(const dht::Key & node_id,RPCServer* rpc,Node* node); + virtual ~NodeLookup(); + + virtual void update(); + virtual void callFinished(RPCCall* c, MsgBase* rsp); + virtual void callTimeout(RPCCall* c); + private: + dht::Key node_id; + bt::Uint32 num_nodes_rsp; + }; + +} + +#endif diff --git a/libktorrent/kademlia/pack.cpp b/libktorrent/kademlia/pack.cpp new file mode 100644 index 0000000..a5acafb --- /dev/null +++ b/libktorrent/kademlia/pack.cpp @@ -0,0 +1,62 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <util/error.h> +#include <util/functions.h> +#include "pack.h" + +using namespace bt; +using namespace KNetwork; + +namespace dht +{ + + void PackBucketEntry(const KBucketEntry & e,QByteArray & ba,Uint32 off) + { + // first check size + if (off + 26 > ba.size()) + throw bt::Error("Not enough room in buffer"); + + Uint8* data = (Uint8*)ba.data(); + Uint8* ptr = data + off; + + const KInetSocketAddress & addr = e.getAddress(); + // copy ID, IP address and port into the buffer + memcpy(ptr,e.getID().getData(),20); + bt::WriteUint32(ptr,20,addr.ipAddress().IPv4Addr()); + bt::WriteUint16(ptr,24,addr.port()); + } + + KBucketEntry UnpackBucketEntry(const QByteArray & ba,Uint32 off) + { + if (off + 26 > ba.size()) + throw bt::Error("Not enough room in buffer"); + + const Uint8* data = (Uint8*)ba.data(); + const Uint8* ptr = data + off; + + // get the port, ip and key); + Uint16 port = bt::ReadUint16(ptr,24); + Uint8 key[20]; + memcpy(key,ptr,20); + + return KBucketEntry(KInetSocketAddress(KIpAddress(ptr+20,4),port),dht::Key(key)); + } + +} diff --git a/libktorrent/kademlia/pack.h b/libktorrent/kademlia/pack.h new file mode 100644 index 0000000..dab1523 --- /dev/null +++ b/libktorrent/kademlia/pack.h @@ -0,0 +1,48 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef DHTPACK_H +#define DHTPACK_H + +#include "kbucket.h" + +namespace dht +{ + + /** + * Pack a KBucketEntry into a byte array. + * If the array is not large enough, an error will be thrown + * @param e The entry + * @param ba The byte array + * @param off The offset into the array + */ + void PackBucketEntry(const KBucketEntry & e,QByteArray & ba,Uint32 off); + + /** + * Unpack a KBucketEntry from a byte array. + * If a full entry cannot be read an error will be thrown. + * @param ba The byte array + * @param off The offset + * @return The entry + */ + KBucketEntry UnpackBucketEntry(const QByteArray & ba,Uint32 off); + +} + +#endif diff --git a/libktorrent/kademlia/rpccall.cpp b/libktorrent/kademlia/rpccall.cpp new file mode 100644 index 0000000..b86e8f7 --- /dev/null +++ b/libktorrent/kademlia/rpccall.cpp @@ -0,0 +1,79 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 "dht.h" +#include "rpcmsg.h" +#include "rpccall.h" +#include "rpcserver.h" + +namespace dht +{ + RPCCallListener::RPCCallListener() + {} + + RPCCallListener::~RPCCallListener() + { + } + + RPCCall::RPCCall(RPCServer* rpc,MsgBase* msg,bool queued) : msg(msg),rpc(rpc),queued(queued) + { + connect(&timer,SIGNAL(timeout()),this,SLOT(onTimeout())); + if (!queued) + timer.start(30*1000,true); + } + + + RPCCall::~RPCCall() + { + delete msg; + } + + void RPCCall::start() + { + queued = false; + timer.start(30*1000,true); + } + + void RPCCall::onTimeout() + { + onCallTimeout(this); + rpc->timedOut(msg->getMTID()); + } + + void RPCCall::response(MsgBase* rsp) + { + onCallResponse(this,rsp); + } + + Method RPCCall::getMsgMethod() const + { + if (msg) + return msg->getMethod(); + else + return dht::NONE; + } + + void RPCCall::addListener(RPCCallListener* cl) + { + connect(this,SIGNAL(onCallResponse( RPCCall*, MsgBase* )),cl,SLOT(onResponse( RPCCall*, MsgBase* ))); + connect(this,SIGNAL(onCallTimeout( RPCCall* )),cl,SLOT(onTimeout( RPCCall* ))); + } + +} +#include "rpccall.moc" diff --git a/libktorrent/kademlia/rpccall.h b/libktorrent/kademlia/rpccall.h new file mode 100644 index 0000000..6e54933 --- /dev/null +++ b/libktorrent/kademlia/rpccall.h @@ -0,0 +1,110 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef DHTRPCCALL_H +#define DHTRPCCALL_H + +#include <qtimer.h> +#include "key.h" +#include "rpcmsg.h" + +namespace dht +{ + class RPCServer; + class RPCCall; + + /** + * Class which objects should derive from, if they want to know the result of a call. + */ + class RPCCallListener : public QObject + { + Q_OBJECT + public: + RPCCallListener(); + virtual ~RPCCallListener(); + + public slots: + /** + * A response was received. + * @param c The call + * @param rsp The response + */ + virtual void onResponse(RPCCall* c,MsgBase* rsp) = 0; + + /** + * The call has timed out. + * @param c The call + */ + virtual void onTimeout(RPCCall* c) = 0; + + }; + + /** + * @author Joris Guisson + */ + class RPCCall : public QObject + { + Q_OBJECT + public: + RPCCall(RPCServer* rpc,MsgBase* msg,bool queued); + virtual ~RPCCall(); + + /** + * Called when a queued call gets started. Starts the timeout timer. + */ + void start(); + + /** + * Called by the server if a response is received. + * @param rsp + */ + void response(MsgBase* rsp); + + /** + * Add a listener for this call + * @param cl The listener + */ + void addListener(RPCCallListener* cl); + + /// Get the message type + Method getMsgMethod() const; + + /// Get the request sent + const MsgBase* getRequest() const {return msg;} + + /// Get the request sent + MsgBase* getRequest() {return msg;} + + private slots: + void onTimeout(); + + signals: + void onCallResponse(RPCCall* c,MsgBase* rsp); + void onCallTimeout(RPCCall* c); + + private: + MsgBase* msg; + QTimer timer; + RPCServer* rpc; + bool queued; + }; + +} + +#endif diff --git a/libktorrent/kademlia/rpcmsg.cpp b/libktorrent/kademlia/rpcmsg.cpp new file mode 100644 index 0000000..97364e1 --- /dev/null +++ b/libktorrent/kademlia/rpcmsg.cpp @@ -0,0 +1,596 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <util/log.h> +#include <torrent/bnode.h> +#include <torrent/globals.h> +#include <torrent/bencoder.h> +#include "rpcmsg.h" +#include "rpccall.h" +#include "rpcserver.h" +#include "dht.h" + +using namespace bt; + +namespace dht +{ + const QString TID = "t"; + const QString REQ = "q"; + const QString RSP = "r"; + const QString TYP = "y"; + const QString ARG = "a"; + // ERR apparently is defined as a macro on solaris in some header file, + // which causes things not to compile on it, so we have changed it to ERR_DHT + const QString ERR_DHT = "e"; + + + MsgBase* MakeMsg(bt::BDictNode* dict); + + + MsgBase* ParseReq(bt::BDictNode* dict) + { + BValueNode* vn = dict->getValue(REQ); + BDictNode* args = dict->getDict(ARG); + if (!vn || !args) + return 0; + + if (!args->getValue("id")) + return 0; + + if (!dict->getValue(TID)) + return 0; + + Key id = Key(args->getValue("id")->data().toByteArray()); + QByteArray mtid_d = dict->getValue(TID)->data().toByteArray(); + if (mtid_d.size() == 0) + return 0; + Uint8 mtid = (Uint8)mtid_d.at(0); + MsgBase* msg = 0; + + QString str = vn->data().toString(); + if (str == "ping") + { + msg = new PingReq(id); + } + else if (str == "find_node") + { + if (args->getValue("target")) + msg = new FindNodeReq(id,Key(args->getValue("target")->data().toByteArray())); + } + else if (str == "get_peers") + { + if (args->getValue("info_hash")) + msg = new GetPeersReq(id,Key(args->getValue("info_hash")->data().toByteArray())); + } + else if (str == "announce_peer") + { + if (args->getValue("info_hash") && args->getValue("port") && args->getValue("token")) + { + msg = new AnnounceReq(id, + Key(args->getValue("info_hash")->data().toByteArray()), + args->getValue("port")->data().toInt(), + Key(args->getValue("token")->data().toByteArray())); + } + } + + if (msg) + msg->setMTID(mtid); + + return msg; + } + + MsgBase* ParseRsp(bt::BDictNode* dict,dht::Method req_method,Uint8 mtid) + { + BDictNode* args = dict->getDict(RSP); + if (!args || !args->getValue("id")) + return 0; + + Key id = Key(args->getValue("id")->data().toByteArray()); + + switch (req_method) + { + case PING : + return new PingRsp(mtid,id); + case FIND_NODE : + if (!args->getValue("nodes")) + return 0; + else + return new FindNodeRsp(mtid,id,args->getValue("nodes")->data().toByteArray()); + case GET_PEERS : + if (args->getValue("token")) + { + Key token = args->getValue("token")->data().toByteArray(); + QByteArray data; + BListNode* vals = args->getList("values"); + DBItemList dbl; + if (vals) + { + for (Uint32 i = 0;i < vals->getNumChildren();i++) + { + BValueNode* vn = dynamic_cast<BValueNode*>(vals->getChild(i)); + if (!vn) + continue; + dbl.append(DBItem((Uint8*)vn->data().toByteArray().data())); + } + return new GetPeersRsp(mtid,id,dbl,token); + } + else if (args->getValue("nodes")) + { + data = args->getValue("nodes")->data().toByteArray(); + return new GetPeersRsp(mtid,id,data,token); + } + else + { + Out(SYS_DHT|LOG_DEBUG) << "No nodes or values in get_peers response" << endl; + return 0; + } + } + else + { + Out(SYS_DHT|LOG_DEBUG) << "No token in get_peers response" << endl; + } + case ANNOUNCE_PEER : + return new AnnounceRsp(mtid,id); + default: + return 0; + } + return 0; + } + + MsgBase* ParseRsp(bt::BDictNode* dict,RPCServer* srv) + { + BDictNode* args = dict->getDict(RSP); + if (!args || !dict->getValue(TID)) + { + Out(SYS_DHT|LOG_DEBUG) << "ParseRsp : args || !args->getValue(id) || !dict->getValue(TID)" << endl; + return 0; + } + + + QByteArray ba = dict->getValue(TID)->data().toByteArray(); + // check for empty byte arrays should prevent 144416 + if (ba.size() == 0) + return 0; + + Uint8 mtid = (Uint8)ba.at(0); + // find the call + const RPCCall* c = srv->findCall(mtid); + if (!c) + { + Out(SYS_DHT|LOG_DEBUG) << "Cannot find RPC call" << endl; + return 0; + } + + return ParseRsp(dict,c->getMsgMethod(),mtid); + } + + MsgBase* ParseErr(bt::BDictNode* dict) + { + BValueNode* vn = dict->getValue(RSP); + BDictNode* args = dict->getDict(ARG); + if (!vn || !args || !args->getValue("id") || !dict->getValue(TID)) + return 0; + + Key id = Key(args->getValue("id")->data().toByteArray()); + QString mt_id = dict->getValue(TID)->data().toString(); + if (mt_id.length() == 0) + return 0; + + Uint8 mtid = (char)mt_id.at(0).latin1(); + QString str = vn->data().toString(); + + return new ErrMsg(mtid,id,str); + } + + + MsgBase* MakeRPCMsg(bt::BDictNode* dict,RPCServer* srv) + { + BValueNode* vn = dict->getValue(TYP); + if (!vn) + return 0; + + if (vn->data().toString() == REQ) + { + return ParseReq(dict); + } + else if (vn->data().toString() == RSP) + { + return ParseRsp(dict,srv); + } + else if (vn->data().toString() == ERR_DHT) + { + return ParseErr(dict); + } + + return 0; + } + + MsgBase* MakeRPCMsgTest(bt::BDictNode* dict,dht::Method req_method) + { + BValueNode* vn = dict->getValue(TYP); + if (!vn) + return 0; + + if (vn->data().toString() == REQ) + { + return ParseReq(dict); + } + else if (vn->data().toString() == RSP) + { + return ParseRsp(dict,req_method,0); + } + else if (vn->data().toString() == ERR_DHT) + { + return ParseErr(dict); + } + + return 0; + } + + MsgBase::MsgBase(Uint8 mtid,Method m,Type type,const Key & id) + : mtid(mtid),method(m),type(type),id(id) + {} + + MsgBase::~MsgBase() + {} + + //////////////////////////////// + + PingReq::PingReq(const Key & id) : MsgBase(0xFF,PING,REQ_MSG,id) + { + } + + PingReq::~PingReq() + {} + + void PingReq::apply(DHT* dh_table) + { + dh_table->ping(this); + } + + void PingReq::print() + { + Out(SYS_DHT|LOG_DEBUG) << QString("REQ: %1 %2 : ping").arg(mtid).arg(id.toString()) << endl; + } + + void PingReq::encode(QByteArray & arr) + { + BEncoder enc(new BEncoderBufferOutput(arr)); + enc.beginDict(); + { + enc.write(ARG); enc.beginDict(); + { + enc.write("id"); enc.write(id.getData(),20); + } + enc.end(); + enc.write(REQ); enc.write("ping"); + enc.write(TID); enc.write(&mtid,1); + enc.write(TYP); enc.write(REQ); + } + enc.end(); + } + + //////////////////////////////// + + FindNodeReq::FindNodeReq(const Key & id,const Key & target) + : MsgBase(0xFF,FIND_NODE,REQ_MSG,id),target(target) + {} + + FindNodeReq::~FindNodeReq() + {} + + void FindNodeReq::apply(DHT* dh_table) + { + dh_table->findNode(this); + } + + void FindNodeReq::print() + { + Out(SYS_DHT|LOG_NOTICE) << QString("REQ: %1 %2 : find_node %3") + .arg(mtid).arg(id.toString()).arg(target.toString()) << endl; + } + + void FindNodeReq::encode(QByteArray & arr) + { + BEncoder enc(new BEncoderBufferOutput(arr)); + enc.beginDict(); + { + enc.write(ARG); enc.beginDict(); + { + enc.write("id"); enc.write(id.getData(),20); + enc.write("target"); enc.write(target.getData(),20); + } + enc.end(); + enc.write(REQ); enc.write("find_node"); + enc.write(TID); enc.write(&mtid,1); + enc.write(TYP); enc.write(REQ); + } + enc.end(); + } + + //////////////////////////////// + + //////////////////////////////// + GetPeersReq::GetPeersReq(const Key & id,const Key & info_hash) + : MsgBase(0xFF,GET_PEERS,REQ_MSG,id),info_hash(info_hash) + {} + + GetPeersReq::~GetPeersReq() + {} + + void GetPeersReq::apply(DHT* dh_table) + { + dh_table->getPeers(this); + } + + void GetPeersReq::print() + { + Out(SYS_DHT|LOG_DEBUG) << QString("REQ: %1 %2 : get_peers %3") + .arg(mtid).arg(id.toString()).arg(info_hash.toString()) << endl; + } + + void GetPeersReq::encode(QByteArray & arr) + { + BEncoder enc(new BEncoderBufferOutput(arr)); + enc.beginDict(); + { + enc.write(ARG); enc.beginDict(); + { + enc.write("id"); enc.write(id.getData(),20); + enc.write("info_hash"); enc.write(info_hash.getData(),20); + } + enc.end(); + enc.write(REQ); enc.write("get_peers"); + enc.write(TID); enc.write(&mtid,1); + enc.write(TYP); enc.write(REQ); + } + enc.end(); + } + + //////////////////////////////// + + AnnounceReq::AnnounceReq(const Key & id,const Key & info_hash,Uint16 port,const Key & token) + : GetPeersReq(id,info_hash),port(port),token(token) + { + method = dht::ANNOUNCE_PEER; + } + + AnnounceReq::~AnnounceReq() {} + + void AnnounceReq::apply(DHT* dh_table) + { + dh_table->announce(this); + } + + void AnnounceReq::print() + { + Out(SYS_DHT|LOG_DEBUG) << QString("REQ: %1 %2 : announce_peer %3 %4 %5") + .arg(mtid).arg(id.toString()).arg(info_hash.toString()) + .arg(port).arg(token.toString()) << endl; + } + + void AnnounceReq::encode(QByteArray & arr) + { + BEncoder enc(new BEncoderBufferOutput(arr)); + enc.beginDict(); + { + enc.write(ARG); enc.beginDict(); + { + enc.write("id"); enc.write(id.getData(),20); + enc.write("info_hash"); enc.write(info_hash.getData(),20); + enc.write("port"); enc.write((Uint32)port); + enc.write("token"); enc.write(token.getData(),20); + } + enc.end(); + enc.write(REQ); enc.write("announce_peer"); + enc.write(TID); enc.write(&mtid,1); + enc.write(TYP); enc.write(REQ); + } + enc.end(); + } + + //////////////////////////////// + + PingRsp::PingRsp(Uint8 mtid,const Key & id) + : MsgBase(mtid,PING,RSP_MSG,id) + {} + + PingRsp::~PingRsp() {} + + void PingRsp::apply(DHT* dh_table) + { + dh_table->response(this); + } + + void PingRsp::print() + { + Out(SYS_DHT|LOG_DEBUG) << QString("RSP: %1 %2 : ping") + .arg(mtid).arg(id.toString()) << endl; + } + + void PingRsp::encode(QByteArray & arr) + { + BEncoder enc(new BEncoderBufferOutput(arr)); + enc.beginDict(); + { + enc.write(RSP); enc.beginDict(); + { + enc.write("id"); enc.write(id.getData(),20); + } + enc.end(); + enc.write(TID); enc.write(&mtid,1); + enc.write(TYP); enc.write(RSP); + } + enc.end(); + } + + //////////////////////////////// + + FindNodeRsp::FindNodeRsp(Uint8 mtid,const Key & id,const QByteArray & nodes) + : MsgBase(mtid,FIND_NODE,RSP_MSG,id),nodes(nodes) + {} + + FindNodeRsp::~FindNodeRsp() {} + + void FindNodeRsp::apply(DHT* dh_table) + { + dh_table->response(this); + } + + void FindNodeRsp::print() + { + Out(SYS_DHT|LOG_DEBUG) << QString("RSP: %1 %2 : find_node") + .arg(mtid).arg(id.toString()) << endl; + } + + void FindNodeRsp::encode(QByteArray & arr) + { + BEncoder enc(new BEncoderBufferOutput(arr)); + enc.beginDict(); + { + enc.write(RSP); enc.beginDict(); + { + enc.write("id"); enc.write(id.getData(),20); + enc.write("nodes"); enc.write(nodes); + } + enc.end(); + enc.write(TID); enc.write(&mtid,1); + enc.write(TYP); enc.write(RSP); + } + enc.end(); + } + + //////////////////////////////// + + GetPeersRsp::GetPeersRsp(Uint8 mtid,const Key & id,const QByteArray & data,const Key & token) + : MsgBase(mtid,dht::GET_PEERS,dht::RSP_MSG,id),token(token),data(data) + { + this->data.detach(); + } + + GetPeersRsp::GetPeersRsp(Uint8 mtid,const Key & id,const DBItemList & values,const Key & token) + : MsgBase(mtid,dht::GET_PEERS,dht::RSP_MSG,id),token(token),items(values) + {} + + GetPeersRsp::~GetPeersRsp() + {} + + void GetPeersRsp::apply(DHT* dh_table) + { + dh_table->response(this); + } + void GetPeersRsp::print() + { + Out() << QString("RSP: %1 %2 : get_peers(%3)") + .arg(mtid).arg(id.toString()).arg(data.size() > 0 ? "nodes" : "values") << endl; + } + + void GetPeersRsp::encode(QByteArray & arr) + { + BEncoder enc(new BEncoderBufferOutput(arr)); + enc.beginDict(); + { + enc.write(RSP); enc.beginDict(); + { + enc.write("id"); enc.write(id.getData(),20); + if (data.size() > 0) + { + enc.write("nodes"); enc.write(data); + enc.write("token"); enc.write(token.getData(),20); + } + else + { + enc.write("token"); enc.write(token.getData(),20); + enc.write("values"); enc.beginList(); + DBItemList::iterator i = items.begin(); + while (i != items.end()) + { + const DBItem & item = *i; + enc.write(item.getData(),6); + i++; + } + enc.end(); + } + } + enc.end(); + enc.write(TID); enc.write(&mtid,1); + enc.write(TYP); enc.write(RSP); + } + enc.end(); + } + + + //////////////////////////////// + //////////////////////////////// + + AnnounceRsp::AnnounceRsp(Uint8 mtid,const Key & id) : MsgBase(mtid,ANNOUNCE_PEER,RSP_MSG,id) + {} + + AnnounceRsp::~AnnounceRsp(){} + + void AnnounceRsp::apply(DHT* dh_table) + { + dh_table->response(this); + } + + void AnnounceRsp::print() + { + Out() << QString("RSP: %1 %2 : announce_peer") + .arg(mtid).arg(id.toString()) << endl; + } + + void AnnounceRsp::encode(QByteArray & arr) + { + BEncoder enc(new BEncoderBufferOutput(arr)); + enc.beginDict(); + { + enc.write(RSP); enc.beginDict(); + { + enc.write("id"); enc.write(id.getData(),20); + } + enc.end(); + enc.write(TID); enc.write(&mtid,1); + enc.write(TYP); enc.write(RSP); + } + enc.end(); + } + + + //////////////////////////////// + + ErrMsg::ErrMsg(Uint8 mtid,const Key & id,const QString & msg) + : MsgBase(mtid,NONE,ERR_MSG,id),msg(msg) + {} + + ErrMsg::~ErrMsg() + {} + + void ErrMsg::apply(DHT* dh_table) + { + dh_table->error(this); + } + + void ErrMsg::print() + { + Out(SYS_DHT|LOG_NOTICE) << "ERR: " << mtid << " " << msg << endl; + } + + void ErrMsg::encode(QByteArray & ) + {} +} diff --git a/libktorrent/kademlia/rpcmsg.h b/libktorrent/kademlia/rpcmsg.h new file mode 100644 index 0000000..4863ae2 --- /dev/null +++ b/libktorrent/kademlia/rpcmsg.h @@ -0,0 +1,269 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef DHTRPCMSG_H +#define DHTRPCMSG_H + +#include <ksocketaddress.h> +#include <util/constants.h> +#include "key.h" +#include "database.h" + +namespace bt +{ + class BDictNode; +} + +using bt::Uint8; +using bt::Uint32; + +namespace dht +{ + class DHT; + class RPCServer; + + enum Type + { + REQ_MSG, + RSP_MSG, + ERR_MSG, + INVALID + }; + + enum Method + { + PING, + FIND_NODE, + GET_PEERS, + ANNOUNCE_PEER, + NONE + }; + + + + /** + * Base class for all RPC messages. + */ + class MsgBase + { + public: + MsgBase(Uint8 mtid,Method m,Type type,const Key & id); + virtual ~MsgBase(); + + + /** + * When this message arrives this function will be called upon the DHT. + * The message should then call the appropriate DHT function (double dispatch) + * @param dh_table Pointer to DHT + */ + virtual void apply(DHT* dh_table) = 0; + + /** + * Print the message for debugging purposes. + */ + virtual void print() = 0; + + /** + * BEncode the message. + * @param arr Data array + */ + virtual void encode(QByteArray & arr) = 0; + + /// Set the origin (i.e. where the message came from) + void setOrigin(const KNetwork::KSocketAddress & o) {origin = o;} + + /// Get the origin + const KNetwork::KInetSocketAddress & getOrigin() const {return origin;} + + /// Set the origin (i.e. where the message came from) + void setDestination(const KNetwork::KSocketAddress & o) {origin = o;} + + /// Get the origin + const KNetwork::KInetSocketAddress & getDestination() const {return origin;} + + /// Get the MTID + Uint8 getMTID() const {return mtid;} + + /// Set the MTID + void setMTID(Uint8 m) {mtid = m;} + + /// Get the id of the sender + const Key & getID() const {return id;} + + /// Get the type of the message + Type getType() const {return type;} + + /// Get the message it's method + Method getMethod() const {return method;} + + protected: + Uint8 mtid; + Method method; + Type type; + Key id; + KNetwork::KInetSocketAddress origin; + }; + + /** + * Creates a message out of a BDictNode. + * @param dict The BDictNode + * @param srv The RPCServer + * @return A newly created message or 0 upon error + */ + MsgBase* MakeRPCMsg(bt::BDictNode* dict,RPCServer* srv); + + MsgBase* MakeRPCMsgTest(bt::BDictNode* dict,dht::Method req_method); + + class ErrMsg : public MsgBase + { + public: + ErrMsg(Uint8 mtid,const Key & id,const QString & msg); + virtual ~ErrMsg(); + + virtual void apply(DHT* dh_table); + virtual void print(); + virtual void encode(QByteArray & arr); + private: + QString msg; + }; + + class PingReq : public MsgBase + { + public: + PingReq(const Key & id); + virtual ~PingReq(); + + virtual void apply(DHT* dh_table); + virtual void print(); + virtual void encode(QByteArray & arr); + }; + + class FindNodeReq : public MsgBase + { + public: + FindNodeReq(const Key & id,const Key & target); + virtual ~FindNodeReq(); + + virtual void apply(DHT* dh_table); + virtual void print(); + virtual void encode(QByteArray & arr); + + const Key & getTarget() const {return target;} + + private: + Key target; + }; + + class GetPeersReq : public MsgBase + { + public: + GetPeersReq(const Key & id,const Key & info_hash); + virtual ~GetPeersReq(); + + const Key & getInfoHash() const {return info_hash;} + virtual void apply(DHT* dh_table); + virtual void print(); + virtual void encode(QByteArray & arr); + protected: + Key info_hash; + }; + + class AnnounceReq : public GetPeersReq + { + public: + AnnounceReq(const Key & id,const Key & info_hash,bt::Uint16 port,const Key & token); + virtual ~AnnounceReq(); + + virtual void apply(DHT* dh_table); + virtual void print(); + virtual void encode(QByteArray & arr); + + const Key & getToken() const {return token;} + bt::Uint16 getPort() const {return port;} + private: + bt::Uint16 port; + Key token; + }; + + class PingRsp : public MsgBase + { + public: + PingRsp(Uint8 mtid,const Key & id); + virtual ~PingRsp(); + + virtual void apply(DHT* dh_table); + virtual void print(); + virtual void encode(QByteArray & arr); + }; + + + + class FindNodeRsp : public MsgBase + { + public: + FindNodeRsp(Uint8 mtid,const Key & id,const QByteArray & nodes); + virtual ~FindNodeRsp(); + + virtual void apply(DHT* dh_table); + virtual void print(); + virtual void encode(QByteArray & arr); + + const QByteArray & getNodes() const {return nodes;} + protected: + QByteArray nodes; + }; + + class GetPeersRsp : public MsgBase + { + public: + GetPeersRsp(Uint8 mtid,const Key & id,const QByteArray & data,const Key & token); + GetPeersRsp(Uint8 mtid,const Key & id,const DBItemList & values,const Key & token); + virtual ~GetPeersRsp(); + + virtual void apply(DHT* dh_table); + virtual void print(); + virtual void encode(QByteArray & arr); + + const QByteArray & getData() const {return data;} + const DBItemList & getItemList() const {return items;} + const Key & getToken() const {return token;} + bool containsNodes() const {return data.size() > 0;} + bool containsValues() const {return data.size() == 0;} + private: + Key token; + QByteArray data; + DBItemList items; + }; + + + class AnnounceRsp : public MsgBase + { + public: + AnnounceRsp(Uint8 mtid,const Key & id); + virtual ~AnnounceRsp(); + + virtual void apply(DHT* dh_table); + virtual void print(); + virtual void encode(QByteArray & arr); + }; + + +} + +#endif diff --git a/libktorrent/kademlia/rpcserver.cpp b/libktorrent/kademlia/rpcserver.cpp new file mode 100644 index 0000000..1242dae --- /dev/null +++ b/libktorrent/kademlia/rpcserver.cpp @@ -0,0 +1,243 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <unistd.h> +#include <string.h> +#include <net/portlist.h> +#include <util/log.h> +#include <util/error.h> +#include <torrent/globals.h> +#include <torrent/bnode.h> +#include <torrent/bdecoder.h> +#include <torrent/bencoder.h> +#include <ksocketdevice.h> +#include "rpcserver.h" +#include "rpccall.h" +#include "rpcmsg.h" +#include "kbucket.h" +#include "node.h" +#include "dht.h" + +using namespace KNetwork; +using namespace bt; + +namespace dht +{ + + + + RPCServer::RPCServer(DHT* dh_table,Uint16 port,QObject *parent) : QObject(parent),dh_table(dh_table),next_mtid(0),port(port) + { + sock = new KDatagramSocket(this); + sock->setBlocking(false); + sock->setAddressReuseable(true); + } + + + RPCServer::~RPCServer() + { + bt::Globals::instance().getPortList().removePort(port,net::UDP); + sock->close(); + calls.setAutoDelete(true); + calls.clear(); + call_queue.setAutoDelete(true); + call_queue.clear(); + } + + void RPCServer::start() + { + sock->setBlocking(true); + if (!sock->bind(QString::null,QString::number(port))) + { + Out(SYS_DHT|LOG_IMPORTANT) << "DHT: Failed to bind to UDP port " << port << " for DHT" << endl; + } + else + { + bt::Globals::instance().getPortList().addNewPort(port,net::UDP,true); + } + sock->setBlocking(false); + connect(sock,SIGNAL(readyRead()),this,SLOT(readPacket())); + } + + void RPCServer::stop() + { + bt::Globals::instance().getPortList().removePort(port,net::UDP); + sock->close(); + } + + static void PrintRawData(const QByteArray & data) + { + QString tmp; + for (Uint32 i = 0;i < data.size();i++) + { + char c = QChar(data[i]).latin1(); + if (!QChar(data[i]).isPrint() || c == 0) + tmp += '#'; + else + tmp += c; + } + + Out(SYS_DHT|LOG_DEBUG) << tmp << endl; + } + + void RPCServer::readPacket() + { + if (sock->bytesAvailable() == 0) + { + Out(SYS_DHT|LOG_NOTICE) << "0 byte UDP packet " << endl; + // KDatagramSocket wrongly handles UDP packets with no payload + // so we need to deal with it oursleves + int fd = sock->socketDevice()->socket(); + char tmp; + read(fd,&tmp,1); + return; + } + + KDatagramPacket pck = sock->receive(); + /* + Out() << "RPCServer::readPacket" << endl; + PrintRawData(pck.data()); + */ + BNode* n = 0; + try + { + // read and decode the packet + BDecoder bdec(pck.data(),false); + n = bdec.decode(); + + if (!n || n->getType() != BNode::DICT) + { + delete n; + return; + } + + // try to make a RPCMsg of it + MsgBase* msg = MakeRPCMsg((BDictNode*)n,this); + if (msg) + { + msg->setOrigin(pck.address()); + msg->apply(dh_table); + // erase an existing call + if (msg->getType() == RSP_MSG && calls.contains(msg->getMTID())) + { + // delete the call, but first notify it off the response + RPCCall* c = calls.find(msg->getMTID()); + c->response(msg); + calls.erase(msg->getMTID()); + c->deleteLater(); + doQueuedCalls(); + } + delete msg; + } + } + catch (bt::Error & err) + { + Out(SYS_DHT|LOG_IMPORTANT) << "Error happened during parsing : " << err.toString() << endl; + } + delete n; + + if (sock->bytesAvailable() > 0) + readPacket(); + } + + + void RPCServer::send(const KNetwork::KSocketAddress & addr,const QByteArray & msg) + { + sock->send(KNetwork::KDatagramPacket(msg,addr)); + } + + RPCCall* RPCServer::doCall(MsgBase* msg) + { + Uint8 start = next_mtid; + while (calls.contains(next_mtid)) + { + next_mtid++; + if (next_mtid == start) // if this happens we cannot do any calls + { + // so queue the call + RPCCall* c = new RPCCall(this,msg,true); + call_queue.append(c); + Out(SYS_DHT|LOG_NOTICE) << "Queueing RPC call, no slots available at the moment" << endl; + return c; + } + } + + msg->setMTID(next_mtid++); + sendMsg(msg); + RPCCall* c = new RPCCall(this,msg,false); + calls.insert(msg->getMTID(),c); + return c; + } + + void RPCServer::sendMsg(MsgBase* msg) + { + QByteArray data; + msg->encode(data); + send(msg->getDestination(),data); + + // PrintRawData(data); + } + + void RPCServer::timedOut(Uint8 mtid) + { + // delete the call + RPCCall* c = calls.find(mtid); + if (c) + { + dh_table->timeout(c->getRequest()); + calls.erase(mtid); + c->deleteLater(); + } + doQueuedCalls(); + } + + void RPCServer::doQueuedCalls() + { + while (call_queue.count() > 0 && calls.count() < 256) + { + RPCCall* c = call_queue.first(); + call_queue.removeFirst(); + + while (calls.contains(next_mtid)) + next_mtid++; + + MsgBase* msg = c->getRequest(); + msg->setMTID(next_mtid++); + sendMsg(msg); + calls.insert(msg->getMTID(),c); + c->start(); + } + } + + const RPCCall* RPCServer::findCall(Uint8 mtid) const + { + return calls.find(mtid); + } + + void RPCServer::ping(const dht::Key & our_id,const KNetwork::KSocketAddress & addr) + { + Out(SYS_DHT|LOG_NOTICE) << "DHT: pinging " << addr.nodeName() << endl; + PingReq* pr = new PingReq(our_id); + pr->setOrigin(addr); + doCall(pr); + } + + +} +#include "rpcserver.moc" diff --git a/libktorrent/kademlia/rpcserver.h b/libktorrent/kademlia/rpcserver.h new file mode 100644 index 0000000..4e54076 --- /dev/null +++ b/libktorrent/kademlia/rpcserver.h @@ -0,0 +1,122 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef DHTRPCSERVER_H +#define DHTRPCSERVER_H + +#include <qptrlist.h> +#include <kdatagramsocket.h> +#include <util/constants.h> +#include <util/array.h> +#include <util/ptrmap.h> + + +using KNetwork::KDatagramSocket; +using bt::Uint32; +using bt::Uint16; +using bt::Uint8; + +namespace bt +{ + class BDictNode; +} + +namespace dht +{ + class Key; + class KBucketEntry; + class RPCCall; + class RPCMsg; + class Node; + class DHT; + class MsgBase; + + /** + * @author Joris Guisson + * + * Class to handle incoming and outgoing RPC messages. + */ + class RPCServer : public QObject + { + Q_OBJECT + public: + RPCServer(DHT* dh_table,Uint16 port,QObject *parent = 0); + virtual ~RPCServer(); + + /// Start the server + void start(); + + /// Stop the server + void stop(); + + /** + * Do a RPC call. + * @param msg The message to send + * @return The call object + */ + RPCCall* doCall(MsgBase* msg); + + /** + * Send a message, this only sends the message, it does not keep any call + * information. This should be used for replies. + * @param msg The message to send + */ + void sendMsg(MsgBase* msg); + + + /** + * A call was timed out. + * @param mtid mtid of call + */ + void timedOut(Uint8 mtid); + + /** + * Ping a node, we don't care about the MTID. + * @param addr The address + */ + void ping(const dht::Key & our_id,const KNetwork::KSocketAddress & addr); + + /** + * Find a RPC call, based on the mtid + * @param mtid The mtid + * @return The call + */ + const RPCCall* findCall(Uint8 mtid) const; + + /// Get the number of active calls + Uint32 getNumActiveRPCCalls() const {return calls.count();} + private slots: + void readPacket(); + + private: + void send(const KNetwork::KSocketAddress & addr,const QByteArray & msg); + void doQueuedCalls(); + + private: + KDatagramSocket* sock; + DHT* dh_table; + bt::PtrMap<bt::Uint8,RPCCall> calls; + QPtrList<RPCCall> call_queue; + bt::Uint8 next_mtid; + bt::Uint16 port; + }; + +} + +#endif diff --git a/libktorrent/kademlia/task.cpp b/libktorrent/kademlia/task.cpp new file mode 100644 index 0000000..877a698 --- /dev/null +++ b/libktorrent/kademlia/task.cpp @@ -0,0 +1,134 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <kresolver.h> +#include "task.h" +#include "kclosestnodessearch.h" +#include "rpcserver.h" +#include "kbucket.h" + +using namespace KNetwork; + +namespace dht +{ + + Task::Task(RPCServer* rpc,Node* node) + : node(node),rpc(rpc),outstanding_reqs(0),task_finished(false),queued(queued) + { + + } + + + Task::~Task() + { + } + + void Task::start(const KClosestNodesSearch & kns,bool queued) + { + // fill the todo list + for (KClosestNodesSearch::CItr i = kns.begin(); i != kns.end();i++) + todo.append(i->second); + this->queued = queued; + if (!queued) + update(); + } + + void Task::start() + { + if (queued) + { + queued = false; + update(); + } + } + + + void Task::onResponse(RPCCall* c, MsgBase* rsp) + { + if (outstanding_reqs > 0) + outstanding_reqs--; + + if (!isFinished()) + { + callFinished(c,rsp); + + if (canDoRequest() && !isFinished()) + update(); + } + } + + void Task::onTimeout(RPCCall* c) + { + if (outstanding_reqs > 0) + outstanding_reqs--; + + if (!isFinished()) + { + callTimeout(c); + + if (canDoRequest() && !isFinished()) + update(); + } + } + + bool Task::rpcCall(MsgBase* req) + { + if (!canDoRequest()) + return false; + + RPCCall* c = rpc->doCall(req); + c->addListener(this); + outstanding_reqs++; + return true; + } + + void Task::done() + { + task_finished = true; + finished(this); + } + + void Task::emitDataReady() + { + dataReady(this); + } + + void Task::kill() + { + task_finished = true; + finished(this); + } + + void Task::addDHTNode(const QString & ip,bt::Uint16 port) + { + KResolver::resolveAsync(this,SLOT(onResolverResults(KResolverResults )), + ip,QString::number(port)); + } + + void Task::onResolverResults(KResolverResults res) + { + if (res.count() == 0) + return; + + todo.append(KBucketEntry(res.front().address(),dht::Key())); + } + +} + +#include "task.moc" diff --git a/libktorrent/kademlia/task.h b/libktorrent/kademlia/task.h new file mode 100644 index 0000000..5a33ac0 --- /dev/null +++ b/libktorrent/kademlia/task.h @@ -0,0 +1,174 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef DHTTASK_H +#define DHTTASK_H + +#include <qvaluelist.h> +#include "rpccall.h" +//#include "kbucket.h" + +namespace KNetwork +{ + class KResolverResults; +} + +namespace dht +{ + class Node; + class Task; + class KClosestNodesSearch; + class KBucketEntry; + + const Uint32 MAX_CONCURRENT_REQS = 16; + + using KNetwork::KResolverResults; + + /** + * @author Joris Guisson <joris.guisson@gmail.com> + * + * Performs a task on K nodes provided by a KClosestNodesSearch. + * This is a base class for all tasks. + */ + class Task : public RPCCallListener + { + Q_OBJECT + public: + /** + * Create a task. + * @param rpc The RPC server to do RPC calls + * @param node The node + */ + Task(RPCServer* rpc,Node* node); + virtual ~Task(); + + /** + * This will copy the results from the KClosestNodesSearch + * object into the todo list. And call update if the task is not queued. + * @param kns The KClosestNodesSearch object + * @param queued Is the task queued + */ + void start(const KClosestNodesSearch & kns,bool queued); + + + /** + * Start the task, to be used when a task is queued. + */ + void start(); + + /// Decrements the outstanding_reqs + virtual void onResponse(RPCCall* c, MsgBase* rsp); + + /// Decrements the outstanding_reqs + virtual void onTimeout(RPCCall* c); + + /** + * Will continue the task, this will be called every time we have + * rpc slots available for this task. Should be implemented by derived classes. + */ + virtual void update() = 0; + + /** + * A call is finished and a response was received. + * @param c The call + * @param rsp The response + */ + virtual void callFinished(RPCCall* c, MsgBase* rsp) = 0; + + /** + * A call timedout + * @param c The call + */ + virtual void callTimeout(RPCCall* c) = 0; + + /** + * Do a call to the rpc server, increments the outstanding_reqs variable. + * @param req THe request to send + * @return true if call was made, false if not + */ + bool rpcCall(MsgBase* req); + + /// See if we can do a request + bool canDoRequest() const {return outstanding_reqs < MAX_CONCURRENT_REQS;} + + /// Is the task finished + bool isFinished() const {return task_finished;} + + /// Set the task ID + void setTaskID(bt::Uint32 tid) {task_id = tid;} + + /// Get the task ID + bt::Uint32 getTaskID() const {return task_id;} + + /// Get the number of outstanding requests + bt::Uint32 getNumOutstandingRequests() const {return outstanding_reqs;} + + bool isQueued() const {return queued;} + + /** + * Tell listeners data is ready. + */ + void emitDataReady(); + + /// Kills the task + void kill(); + + /** + * Add a node to the todo list + * @param ip The ip or hostname of the node + * @param port The port + */ + void addDHTNode(const QString & ip,bt::Uint16 port); + + signals: + /** + * The task is finsihed. + * @param t The Task + */ + void finished(Task* t); + + /** + * Called by the task when data is ready. + * Can be overrided if wanted. + * @param t The Task + */ + void dataReady(Task* t); + + protected: + void done(); + + protected slots: + void onResolverResults(KResolverResults res); + + protected: + QValueList<KBucketEntry> visited; // nodes visited + QValueList<KBucketEntry> todo; // nodes todo + Node* node; + + private: + RPCServer* rpc; + bt::Uint32 outstanding_reqs; + bt::Uint32 task_id; + bool task_finished; + bool queued; + }; + +} + +#endif diff --git a/libktorrent/kademlia/taskmanager.cpp b/libktorrent/kademlia/taskmanager.cpp new file mode 100644 index 0000000..f71fc0d --- /dev/null +++ b/libktorrent/kademlia/taskmanager.cpp @@ -0,0 +1,79 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <util/log.h> +#include <torrent/globals.h> +#include "taskmanager.h" +#include "nodelookup.h" +#include "dht.h" + +using namespace bt; + +namespace dht +{ + typedef bt::PtrMap<Uint32,Task>::iterator TaskItr; + + TaskManager::TaskManager() : next_id(0) + { + tasks.setAutoDelete(true); + } + + + TaskManager::~TaskManager() + { + queued.setAutoDelete(true); + tasks.clear(); + } + + + void TaskManager::addTask(Task* task) + { + Uint32 id = next_id++; + task->setTaskID(id); + if (task->isQueued()) + queued.append(task); + else + tasks.insert(id,task); + } + + void TaskManager::removeFinishedTasks(const DHT* dh_table) + { + QValueList<Uint32> rm; + for (TaskItr i = tasks.begin();i != tasks.end();i++) + { + if (i->second->isFinished()) + rm.append(i->first); + } + + for (QValueList<Uint32>::iterator i = rm.begin();i != rm.end();i++) + { + tasks.erase(*i); + } + + while (dh_table->canStartTask() && queued.count() > 0) + { + Task* t = queued.first(); + queued.removeFirst(); + Out(SYS_DHT|LOG_NOTICE) << "DHT: starting queued task" << endl; + t->start(); + tasks.insert(t->getTaskID(),t); + } + } + +} diff --git a/libktorrent/kademlia/taskmanager.h b/libktorrent/kademlia/taskmanager.h new file mode 100644 index 0000000..3df52b6 --- /dev/null +++ b/libktorrent/kademlia/taskmanager.h @@ -0,0 +1,69 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef DHTTASKMANAGER_H +#define DHTTASKMANAGER_H + +#include <qptrlist.h> +#include <util/ptrmap.h> +#include <util/constants.h> +#include "task.h" + +namespace dht +{ + class DHT; + + /** + * @author Joris Guisson <joris.guisson@gmail.com> + * + * Manages all dht tasks. + */ + class TaskManager + { + public: + TaskManager(); + virtual ~TaskManager(); + + /** + * Add a task to manage. + * @param task + */ + void addTask(Task* task); + + /** + * Remove all finished tasks. + * @param dh_table Needed to ask permission to start a task + */ + void removeFinishedTasks(const DHT* dh_table); + + /// Get the number of running tasks + bt::Uint32 getNumTasks() const {return tasks.count();} + + /// Get the number of queued tasks + bt::Uint32 getNumQueuedTasks() const {return queued.count();} + + private: + bt::PtrMap<Uint32,Task> tasks; + QPtrList<Task> queued; + bt::Uint32 next_id; + }; + +} + +#endif diff --git a/libktorrent/ktorrent.kcfg b/libktorrent/ktorrent.kcfg new file mode 100644 index 0000000..7d451b3 --- /dev/null +++ b/libktorrent/ktorrent.kcfg @@ -0,0 +1,233 @@ +<?xml version="1.0" encoding="UTF-8"?> +<kcfg xmlns="http://www.kde.org/standards/kcfg/1.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://www.kde.org/standards/kcfg/1.0 + http://www.kde.org/standards/kcfg/1.0/kcfg.xsd" > + + <kcfgfile name="ktorrentrc"/> + <group name="downloads"> + <entry name="maxDownloads" type="Int"> + <label>Maximum number of downloads (0 = no limit)</label> + <default>0</default> + <min>0</min> + </entry> + <entry name="maxSeeds" type="Int"> + <label>Maximum number of seeds (0 = no limit)</label> + <default>0</default> + <min>0</min> + </entry> + <entry name="startDownloadsOnLowDiskSpace" type="Int"> + <label>Start downloads on low disk space?</label> + <default>0</default> + </entry> + <entry name="maxConnections" type="Int"> + <label>Maximum number of connections per torrent (0 = no limit)</label> + <default>120</default> + <min>0</min> + </entry> + <entry name="maxTotalConnections" type="Int"> + <label>Maximum number of connections for all torrents (0 = no limit) </label> + <default>800</default> + <min>0</min> + </entry> + <entry name="maxUploadRate" type="Int"> + <label>Maximum upload speed in KB/sec (0 = no limit)</label> + <default>0</default> + <min>0</min> + </entry> + <entry name="maxDownloadRate" type="Int"> + <label>Maximum download speed in KB/sec (0 = no limit)</label> + <default>0</default> + <min>0</min> + </entry> + <entry name="maxRatio" type="Double"> + <label>Maximum share ratio(0 = no limit)</label> + <default>0</default> + <min>0</min> + </entry> + <entry name="maxSeedTime" type="Double"> + <label>Maximum seed time in hours (0 = no limit)</label> + <default>0</default> + <min>0</min> + </entry> + <entry name="port" type="Int"> + <label>Port</label> + <default>6881</default> + <min>0</min> + <max>65535</max> + </entry> + <entry name="udpTrackerPort" type="Int"> + <label>Port</label> + <default>4444</default> + <min>0</min> + <max>65535</max> + </entry> + <entry name="showSystemTrayIcon" type="Bool"> + <label>Show a system tray icon</label> + <default>true</default> + </entry> + <entry name="showSpeedBarInTrayIcon" type="Bool"> + <label>Show speed bar in tray icon</label> + <default>false</default> + </entry> + <entry name="downloadBandwidth" type="Int"> + <label>Download bandwidth (in kb/s):</label> + <default>500</default> + <min>0</min> + <max>1000000</max> + </entry> + <entry name="uploadBandwidth" type="Int"> + <label>Upload bandwidth (in kb/s):</label> + <default>500</default> + <min>0</min> + <max>1000000</max> + </entry> + <entry name="showPopups" type="Bool"> + <label>Show popup messages when torrent is finished.</label> + <default>true</default> + </entry> + <entry name="keepSeeding" type="Bool"> + <label>Keep seeding after download has finished</label> + <default>true</default> + </entry> + <entry name="tempDir" type="String"> + <label>Folder to store temporary files</label> + <default code="true">QString::null</default> + </entry> + <entry name="useSaveDir" type="Bool"> + <label>Whether to automatically save downloads to saveDir</label> + <default>false</default> + </entry> + <entry name="saveDir" type="String"> + <label>Folder to store downloaded files</label> + <default code="true">QString::null</default> + </entry> + <entry name="useCompletedDir" type="Bool"> + <label>Whether to automatically move completed downloads to completedDir</label> + <default>false</default> + </entry> + <entry name="completedDir" type="String"> + <label>Folder to move completed downloaded files to</label> + <default code="true">QString::null</default> + </entry> + <entry name="useTorrentCopyDir" type="Bool"> + <label>Whether to automatically copy .torrent files to torrentCopyDir</label> + <default>false</default> + </entry> + <entry name="torrentCopyDir" type="String"> + <label>Folder to copy .torrent files to</label> + <default code="true">QString::null</default> + </entry> + <entry name="useExternalIP" type="Bool"> + <label>Whether to use a custom IP to pass to the tracker</label> + <default>false</default> + </entry> + <entry name="lastSaveDir" type="String"> + <label>Directory which was used as the last save directory</label> + <default code="true">QString::null</default> + </entry> + <entry name="externalIP" type="String"> + <label>IP to pass to the tracker</label> + <default code="true">QString::null</default> + </entry> + <entry name="memoryUsage" type="Int"> + <label>Memory usage</label> + <default>0</default> + </entry> + <entry name="guiUpdateInterval" type="Int"> + <label>GUI update interval</label> + <default>0</default> + </entry> + <entry name="dhtSupport" type="Bool"> + <label>Support for DHT</label> + <default>false</default> + </entry> + <entry name="dhtPort" type="Int"> + <label>DHT port</label> + <default>6881</default> + <min>0</min> + <max>65535</max> + </entry> + <entry name="numUploadSlots" type="Int"> + <label>Number of upload slots</label> + <default>2</default> + <min>2</min> + <max>100</max> + </entry> + <entry name="useEncryption" type="Bool"> + <label>Use protocol encryption</label> + <default>false</default> + </entry> + <entry name="allowUnencryptedConnections" type="Bool"> + <label>Allow unencrypted connections</label> + <default>true</default> + </entry> + <entry name="allwaysDoUploadDataCheck" type="Bool"> + <default>true</default> + </entry> + <entry name="maxSizeForUploadDataCheck" type="Int"> + <default>512</default> + <min>128</min> + <max>8192</max> + </entry> + <entry name="typeOfService" type="Int"> + <default>8</default> + <min>0</min> + <max>255</max> + </entry> + <entry name="DSCP" type="Int"> + <default>0</default> + <min>0</min> + <max>63</max> + </entry> + <entry name="maxConnectingSockets" type="Int"> + <default>50</default> + <min>10</min> + <max>500</max> + </entry> + <entry name="autoRecheck" type="Bool"> + <default>true</default> + </entry> + <entry name="maxCorruptedBeforeRecheck" type="Int"> + <default>3</default> + <min>1</min> + </entry> + <entry name="shownColumns" type="IntList"> + <label>Columns shown in KTorrentView</label> + </entry> + <entry name="doNotUseKDEProxy" type="Bool"> + <default>false</default> + </entry> + <entry name="httpTrackerProxy" type="String"> + <default code="true">QString::null</default> + </entry> + <entry name="eta" type="Int"> + <label>ET algorithm</label> + <default>0</default> + </entry> + <entry name="diskPrealloc" type="Bool"> + <default>true</default> + </entry> + + <entry name="fullDiskPrealloc" type="Bool"> + <default>false</default> + </entry> + + <entry name="fullDiskPreallocMethod" type="Int"> + <default>0</default> + </entry> + + <entry name="cpuUsage" type="Int"> + <default>25</default> + <min>1</min> + <max>50</max> + </entry> + + <entry name="minDiskSpace" type="Int"> + <label>When there's no space left to complete download and free diskspace is less than minDiskSpace, torrent will be stopped.</label> + <default>100</default> + <min>10</min> + <max>10000</max> + </entry> + </group> +</kcfg> diff --git a/libktorrent/ktversion.h b/libktorrent/ktversion.h new file mode 100644 index 0000000..ffe3dbe --- /dev/null +++ b/libktorrent/ktversion.h @@ -0,0 +1,36 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef KTVERSION_HH +#define KTVERSION_HH + + +#include "util/constants.h" + +namespace kt +{ + const bt::Uint32 MAJOR = 2; + const bt::Uint32 MINOR = 2; + const char VERSION_STRING[] = "2.2.6"; + const char PEER_ID[] = "-KT2260-"; +} + +#define KT_VERSION_MACRO "2.2.6" + +#endif diff --git a/libktorrent/labelview.cpp b/libktorrent/labelview.cpp new file mode 100644 index 0000000..10c46d5 --- /dev/null +++ b/libktorrent/labelview.cpp @@ -0,0 +1,257 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <algorithm> +#include <qlayout.h> +#include <qlabel.h> +#include <kiconloader.h> +#include <kglobalsettings.h> +#include <util/log.h> +#include "labelview.h" + +using namespace bt; + +namespace kt +{ + LabelViewItem::LabelViewItem(const QString & icon,const QString & title,const QString & description,LabelView* view) + : LabelViewItemBase(view),odd(false),selected(false) + { + icon_lbl->setPixmap(DesktopIcon(icon)); + title_lbl->setText(title); + description_lbl->setText(description); + setOdd(false); + } + + LabelViewItem::~LabelViewItem() + { + } + + void LabelViewItem::setTitle(const QString & title) + { + title_lbl->setText(title); + } + + void LabelViewItem::setDescription(const QString & d) + { + description_lbl->setText(d); + } + + void LabelViewItem::setIcon(const QString & icon) + { + icon_lbl->setPixmap(DesktopIcon(icon)); + } + + void LabelViewItem::setOdd(bool o) + { + odd = o; + setSelected(selected); + } + + void LabelViewItem::setSelected(bool sel) + { + selected = sel; + + if (selected) + { + setPaletteBackgroundColor(KGlobalSettings::highlightColor()); + setPaletteForegroundColor(KGlobalSettings::highlightedTextColor()); + } + else if (odd) + { + setPaletteBackgroundColor(KGlobalSettings::baseColor()); + setPaletteForegroundColor(KGlobalSettings::textColor()); + } + else + { + setPaletteBackgroundColor(KGlobalSettings::alternateBackgroundColor()); + setPaletteForegroundColor(KGlobalSettings::textColor()); + } + } + + bool LabelViewItem::operator < (const LabelViewItem & item) + { + return title_lbl->text() < item.title_lbl->text(); + } + + void LabelViewItem::mousePressEvent(QMouseEvent *e) + { + if (e->button() == QMouseEvent::LeftButton) + { + clicked(this); + } + + setFocus(); + QWidget::mousePressEvent(e); + } + + typedef std::list<LabelViewItem*>::iterator LabelViewItr; + typedef std::list<LabelViewItem*>::const_iterator LabelViewCItr; + + class LabelViewBox : public QWidget + { + QVBoxLayout* layout; + public: + LabelViewBox(QWidget* parent) : QWidget(parent) + { + setPaletteBackgroundColor(KGlobalSettings::baseColor()); + layout = new QVBoxLayout(this); + layout->setMargin(0); + } + + virtual ~LabelViewBox() + {} + + void add(LabelViewItem* item) + { + item->reparent(this,QPoint(0,0)); + layout->add(item); + item->show(); + } + + void remove(LabelViewItem* item) + { + item->hide(); + layout->remove(item); + item->reparent(0,QPoint(0,0)); + } + + void sorted(const std::list<LabelViewItem*> items) + { + for (LabelViewCItr i = items.begin();i != items.end();i++) + layout->remove(*i); + + for (LabelViewCItr i = items.begin();i != items.end();i++) + layout->add(*i); + } + }; + + + + /////////////////////////////////////// + + LabelView::LabelView ( QWidget *parent, const char *name ) + : QScrollView ( parent, name ),selected(0) + { + item_box = new LabelViewBox(this->viewport()); + setResizePolicy(QScrollView::AutoOneFit); + + addChild(item_box, 0, 0); + item_box->show(); + } + + + LabelView::~LabelView() + {} + + void LabelView::addItem(LabelViewItem* item) + { + item_box->add(item); + items.push_back(item); + item->setOdd(items.size() % 2 == 1); + + connect(item, SIGNAL(clicked(LabelViewItem*)), + this, SLOT(onItemClicked(LabelViewItem*))); + } + + void LabelView::removeItem(LabelViewItem* item) + { + LabelViewItr i = std::find(items.begin(),items.end(),item); + if (i != items.end()) + { + item_box->remove(item); + items.erase(i); + disconnect(item, SIGNAL(clicked(LabelViewItem*)), + this, SLOT(onItemClicked(LabelViewItem*))); + + // check for selected being equal to item + if (item == selected) + selected = 0; + + // update odd status of each item + updateOddStatus(); + } + } + + void LabelView::updateOddStatus() + { + bool odd = true; + LabelViewItr i = items.begin(); + while (i != items.end()) + { + LabelViewItem* item = *i; + item->setOdd(odd); + odd = !odd; + i++; + } + } + + void LabelView::onItemClicked(LabelViewItem* it) + { + if (selected == it) + return; + + if (selected) + selected->setSelected(false); + + selected = it; + selected->setSelected(true); + currentChanged(selected); + } + + void LabelView::clear() + { + LabelViewItr i = items.begin(); + while (i != items.end()) + { + LabelViewItem* item = *i; + item_box->remove(item); + i = items.erase(i); + delete item; + } + selected = 0; + } + + void LabelView::update() + { + LabelViewItr i = items.begin(); + while (i != items.end()) + { + LabelViewItem* item = *i; + item->update(); + i++; + } + } + + struct LabelViewItemCmp + { + bool operator() (LabelViewItem* a,LabelViewItem* b) + { + return *a < *b; + } + }; + + void LabelView::sort() + { + items.sort(LabelViewItemCmp()); + item_box->sorted(items); + updateOddStatus(); + } + +} +#include "labelview.moc" diff --git a/libktorrent/labelview.h b/libktorrent/labelview.h new file mode 100644 index 0000000..5e83213 --- /dev/null +++ b/libktorrent/labelview.h @@ -0,0 +1,125 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef KTLABELVIEW_H +#define KTLABELVIEW_H + +#include <list> +#include <qscrollview.h> +#include "labelviewitembase.h" + +class QLabel; +class QHBoxLayout; +class QVBoxLayout; + +namespace kt +{ + class LabelView; + + /** + Item in a LabelView + */ + class LabelViewItem : public LabelViewItemBase + { + Q_OBJECT + public: + LabelViewItem(const QString & icon,const QString & title,const QString & description,LabelView* view); + virtual ~LabelViewItem(); + + /// Set the title of the item + void setTitle(const QString & title); + + /// Set the description + void setDescription(const QString & d); + + /// Set the name of the icon + void setIcon(const QString & icon); + + /// Set if this is an odd item (they have a different background color) + void setOdd(bool odd); + + /// Set if this item is selected + void setSelected(bool sel); + + /// Can be reimplemented to update the GUI of the item by base classes + virtual void update() {} + + /// Smaller then operator for sorting (by default we sort on title) + virtual bool operator < (const LabelViewItem & item); + + private: + virtual void mousePressEvent(QMouseEvent *e); + + signals: + void clicked(LabelViewItem* item); + + private: + bool odd; + bool selected; + }; + + class LabelViewBox; + + /** + @author Joris Guisson <joris.guisson@gmail.com> + */ + class LabelView : public QScrollView + { + Q_OBJECT + public: + LabelView(QWidget *parent = 0, const char *name = 0); + virtual ~LabelView(); + + /// Add an item to the label view + void addItem(LabelViewItem* item); + + /// Remove an item from the label view + void removeItem(LabelViewItem* item); + + /// Get the current selected item (0 if none is selected) + LabelViewItem* selectedItem() {return selected;} + + /// Clear the view + void clear(); + + /// Update all items in the view + void update(); + + /// Sort the items using the operator < + void sort(); + + private slots: + void onItemClicked(LabelViewItem* it); + + private: + void updateOddStatus(); + + signals: + /// The current item has changed + void currentChanged(LabelViewItem* item); + + private: + LabelViewBox* item_box; + std::list<LabelViewItem*> items; + LabelViewItem* selected; + }; + +} + +#endif diff --git a/libktorrent/labelviewitembase.ui b/libktorrent/labelviewitembase.ui new file mode 100644 index 0000000..174803f --- /dev/null +++ b/libktorrent/labelviewitembase.ui @@ -0,0 +1,73 @@ +<!DOCTYPE UI><UI version="3.3" stdsetdef="1"> +<class>LabelViewItemBase</class> +<widget class="QWidget"> + <property name="name"> + <cstring>LabelViewItemBase</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>600</width> + <height>100</height> + </rect> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="margin"> + <number>2</number> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>icon_lbl</cstring> + </property> + <property name="minimumSize"> + <size> + <width>64</width> + <height>64</height> + </size> + </property> + <property name="text"> + <string></string> + </property> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout3</cstring> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>title_lbl</cstring> + </property> + <property name="text"> + <string>textLabel2</string> + </property> + </widget> + <widget class="QLabel"> + <property name="name"> + <cstring>description_lbl</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>7</hsizetype> + <vsizetype>7</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>textLabel3</string> + </property> + </widget> + </vbox> + </widget> + </hbox> +</widget> +<layoutdefaults spacing="6" margin="11"/> +</UI> diff --git a/libktorrent/migrate/Makefile.am b/libktorrent/migrate/Makefile.am new file mode 100644 index 0000000..9bb5528 --- /dev/null +++ b/libktorrent/migrate/Makefile.am @@ -0,0 +1,7 @@ +INCLUDES = -I$(srcdir)/.. $(all_includes) +METASOURCES = AUTO +libmigrate_la_LDFLAGS = $(all_libraries) +noinst_LTLIBRARIES = libmigrate.la +noinst_HEADERS = migrate.h ccmigrate.h cachemigrate.h +libmigrate_la_SOURCES = migrate.cpp ccmigrate.cpp cachemigrate.cpp +KDE_CXXFLAGS = $(USE_EXCEPTIONS) $(USE_RTTI) diff --git a/libktorrent/migrate/cachemigrate.cpp b/libktorrent/migrate/cachemigrate.cpp new file mode 100644 index 0000000..f9b203c --- /dev/null +++ b/libktorrent/migrate/cachemigrate.cpp @@ -0,0 +1,120 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <qstringlist.h> +#include <qfileinfo.h> +#include <util/log.h> +#include <util/fileops.h> +#include <util/functions.h> +#include <torrent/torrent.h> +#include <torrent/globals.h> +#include "cachemigrate.h" + + +namespace bt +{ + + bool IsCacheMigrateNeeded(const Torrent & tor,const QString & cache) + { + // mutli files always need to be migrated + if (tor.isMultiFile()) + return true; + + // a single file and a symlink do not need to be migrated + QFileInfo finfo(cache); + if (finfo.isSymLink()) + return false; + + return true; + } + + static void MigrateSingleCache(const Torrent & tor,const QString & cache,const QString & output_dir) + { + Out() << "Migrating single cache " << cache << " to " << output_dir << endl; + + bt::Move(cache,output_dir + tor.getNameSuggestion()); + bt::SymLink(output_dir + tor.getNameSuggestion(),cache); + } + + static void MakePath(const QString & startdir,const QString & path) + { + QStringList sl = QStringList::split(bt::DirSeparator(),path); + + // create all necessary subdirs + QString ctmp = startdir; + + for (Uint32 i = 0;i < sl.count() - 1;i++) + { + ctmp += sl[i]; + // we need to make the same directory structure in the cache + // as the output dir + if (!bt::Exists(ctmp)) + MakeDir(ctmp); + + ctmp += bt::DirSeparator(); + } + } + + static void MigrateMultiCache(const Torrent & tor,const QString & cache,const QString & output_dir) + { + Out() << "Migrating multi cache " << cache << " to " << output_dir << endl; + // if the cache dir is a symlink, everything is OK + if (QFileInfo(cache).isSymLink()) + return; + + QString cache_dir = cache; + + + // make the output dir if it does not exists + if (!bt::Exists(output_dir + tor.getNameSuggestion())) + bt::MakeDir(output_dir + tor.getNameSuggestion()); + + QString odir = output_dir + tor.getNameSuggestion() + bt::DirSeparator(); + QString cdir = cache; + if (!cdir.endsWith(bt::DirSeparator())) + cdir += bt::DirSeparator(); + + // loop over all files in the cache and see if they are symlinks + for (Uint32 i = 0;i < tor.getNumFiles();i++) + { + const TorrentFile & tf = tor.getFile(i); + QFileInfo fi(cdir + tf.getPath()); + // symlinks are OK + if (fi.isSymLink()) + continue; + // make the path if necessary + MakePath(odir,tf.getPath()); + // no symlink so move to output_dir + bt::Move(cdir + tf.getPath(),odir + tf.getPath()); + bt::SymLink(odir + tf.getPath(),cdir + tf.getPath()); + } + } + + void MigrateCache(const Torrent & tor,const QString & cache,const QString & output_dir) + { + QString odir = output_dir; + if (!odir.endsWith(bt::DirSeparator())) + odir += bt::DirSeparator(); + + if (!tor.isMultiFile()) + MigrateSingleCache(tor,cache,odir); + else + MigrateMultiCache(tor,cache,odir); + } +} diff --git a/libktorrent/migrate/cachemigrate.h b/libktorrent/migrate/cachemigrate.h new file mode 100644 index 0000000..3eea231 --- /dev/null +++ b/libktorrent/migrate/cachemigrate.h @@ -0,0 +1,34 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef BTCACHEMIGRATE_H +#define BTCACHEMIGRATE_H + +namespace bt +{ + class Torrent; + + /// See if a cache migrate is needed + bool IsCacheMigrateNeeded(const Torrent & tor,const QString & cache); + + /// Migrate the cache + void MigrateCache(const Torrent & tor,const QString & cache,const QString & output_dir); +} + +#endif diff --git a/libktorrent/migrate/ccmigrate.cpp b/libktorrent/migrate/ccmigrate.cpp new file mode 100644 index 0000000..80153bf --- /dev/null +++ b/libktorrent/migrate/ccmigrate.cpp @@ -0,0 +1,167 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <klocale.h> +#include <util/log.h> +#include <util/file.h> +#include <util/error.h> +#include <util/array.h> +#include <util/bitset.h> +#include <util/fileops.h> +#include <torrent/downloader.h> +#include <torrent/torrent.h> +#include <torrent/globals.h> +#include <torrent/chunkdownload.h> +#include <ktversion.h> +#include "ccmigrate.h" + +namespace bt +{ + bool IsPreMMap(const QString & current_chunks) + { + File fptr; + if (!fptr.open(current_chunks,"rb")) + return false; + + CurrentChunksHeader chdr; + fptr.read(&chdr,sizeof(CurrentChunksHeader)); + if (chdr.magic != CURRENT_CHUNK_MAGIC) + { + // magic number not good, so pre + return true; + } + + if (chdr.major >= 2 || (chdr.major == 1 && chdr.minor >= 2)) + { + // version number is 1.2 or greater + return false; + } + + return false; + } + + static bool MigrateChunk(const Torrent & tor,File & new_cc,File & old_cc) + { + Uint32 ch = 0; + old_cc.read(&ch,sizeof(Uint32)); + + Out() << "Migrating chunk " << ch << endl; + if (ch >= tor.getNumChunks()) + return false; + + // calculate the size + Uint32 csize = 0; + if (ch == tor.getNumChunks() - 1) + { + // ch is the last chunk, so it might have a different size + csize = tor.getFileLength() % tor.getChunkSize(); + if (ch == 0) + csize = tor.getChunkSize(); + } + else + { + csize = tor.getChunkSize(); + } + + // calculate the number of pieces + Uint32 num_pieces = csize / MAX_PIECE_LEN; + if (csize % MAX_PIECE_LEN > 0) + num_pieces++; + + // load the pieces array + Array<bool> pieces(num_pieces); + old_cc.read(pieces,sizeof(bool)*num_pieces); + + // convert bool array to bitset + BitSet pieces_bs(num_pieces); + for (Uint32 i = 0;i < num_pieces;i++) + pieces_bs.set(i,pieces[i]); + + // load the actual data + Array<Uint8> data(csize); + old_cc.read(data,csize); + + // write to the new file + ChunkDownloadHeader hdr; + hdr.index = ch; + hdr.num_bits = num_pieces; + hdr.buffered = 1; // by default we will use buffered chunks + // save the chunk header + new_cc.write(&hdr,sizeof(ChunkDownloadHeader)); + // save the bitset + new_cc.write(pieces_bs.getData(),pieces_bs.getNumBytes()); + new_cc.write(data,csize); + return true; + } + + static void MigrateCC(const Torrent & tor,const QString & current_chunks) + { + Out() << "Migrating current_chunks file " << current_chunks << endl; + // open the old current_chunks file + File old_cc; + if (!old_cc.open(current_chunks,"rb")) + throw Error(i18n("Cannot open file %1 : %2").arg(current_chunks).arg(old_cc.errorString())); + + // open a new file in the /tmp dir + File new_cc; + QString tmp = current_chunks + ".tmp"; + if (!new_cc.open(tmp,"wb")) + throw Error(i18n("Cannot open file %1 : %2").arg(tmp).arg(old_cc.errorString())); + + // read the number of chunks + Uint32 num = 0; + old_cc.read(&num,sizeof(Uint32)); + Out() << "Found " << num << " chunks" << endl; + + // write the new current_chunks header + CurrentChunksHeader hdr; + hdr.magic = CURRENT_CHUNK_MAGIC; + hdr.major = kt::MAJOR; + hdr.minor = kt::MINOR; + hdr.num_chunks = num; + new_cc.write(&hdr,sizeof(CurrentChunksHeader)); + + for (Uint32 i = 0;i < num;i++) + { + if (!MigrateChunk(tor,new_cc,old_cc)) + break; + } + + // migrate done, close both files and move new_cc to old_cc + new_cc.close(); + old_cc.close(); + bt::Delete(current_chunks); + bt::Move(tmp,current_chunks); + } + + void MigrateCurrentChunks(const Torrent & tor,const QString & current_chunks) + { + try + { + MigrateCC(tor,current_chunks); + } + catch (...) + { + // cleanup tmp files upon error + bt::Delete("/tmp/kt_current_chunks",true); + throw; + } + } + +} diff --git a/libktorrent/migrate/ccmigrate.h b/libktorrent/migrate/ccmigrate.h new file mode 100644 index 0000000..890bdfa --- /dev/null +++ b/libktorrent/migrate/ccmigrate.h @@ -0,0 +1,36 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef BTCCMIGRATE_H +#define BTCCMIGRATE_H + +namespace bt +{ + class Torrent; + + /// Migrates the current_chunks file to the post-mmap era. + void MigrateCurrentChunks(const Torrent & tor,const QString & current_chunks); + + + /// Test if a current_chunks file is from the pre-mmap period + bool IsPreMMap(const QString & current_chunks); + +} + +#endif diff --git a/libktorrent/migrate/migrate.cpp b/libktorrent/migrate/migrate.cpp new file mode 100644 index 0000000..eddde83 --- /dev/null +++ b/libktorrent/migrate/migrate.cpp @@ -0,0 +1,75 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <kurl.h> +#include <klocale.h> +#include <util/log.h> +#include <util/error.h> +#include <util/fileops.h> +#include <util/functions.h> +#include <torrent/globals.h> +#include "migrate.h" +#include "ccmigrate.h" +#include "cachemigrate.h" + +namespace bt +{ + + Migrate::Migrate() + {} + + + Migrate::~Migrate() + {} + + void Migrate::migrate(const Torrent & tor,const QString & tor_dir,const QString & sdir) + { + // check if directory exists + if (!bt::Exists(tor_dir)) + throw Error(i18n("The directory %1 does not exist").arg(tor_dir)); + + // make sure it ends with a / + QString tdir = tor_dir; + if (!tdir.endsWith(bt::DirSeparator())) + tdir += bt::DirSeparator(); + + // see if the current_chunks file exists + if (bt::Exists(tdir + "current_chunks")) + { + // first see if it isn't a download started by a post-mmap version + if (!IsPreMMap(tdir + "current_chunks")) + { + // it's not pre, so it must be post, so just return + Out() << "No migrate needed" << endl; + return; + } + + MigrateCurrentChunks(tor,tdir + "current_chunks"); + } + + // now we need to migrate t + if (IsCacheMigrateNeeded(tor,tdir + "cache" + bt::DirSeparator())) + { + MigrateCache(tor,tdir + "cache" + bt::DirSeparator(),sdir); + } + } + + + +} diff --git a/libktorrent/migrate/migrate.h b/libktorrent/migrate/migrate.h new file mode 100644 index 0000000..ef862ec --- /dev/null +++ b/libktorrent/migrate/migrate.h @@ -0,0 +1,53 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef BTMIGRATE_H +#define BTMIGRATE_H + +namespace bt +{ + class Torrent; + + /** + @author Joris Guisson <joris.guisson@gmail.com> + + Class to migrate old pre-mmap downloads to new ones + */ + class Migrate + { + public: + Migrate(); + virtual ~Migrate(); + + /** + * Migrate a download to the new format. + * @param tor The torrent + * @param tor_dir TorX directory + * @param sdir The save directory + * @throw Error if something goes wrong + */ + void migrate(const Torrent & tor,const QString & tor_dir,const QString & sdir); + private: + bool preMMap(const QString & current_chunks); + void migrateCurrentChunks(const QString & current_chunks); + }; + +} + +#endif diff --git a/libktorrent/mse/Makefile.am b/libktorrent/mse/Makefile.am new file mode 100644 index 0000000..d6a8ac5 --- /dev/null +++ b/libktorrent/mse/Makefile.am @@ -0,0 +1,9 @@ +INCLUDES = -I$(srcdir)/.. $(all_includes) +METASOURCES = AUTO +libmse_la_LDFLAGS = -lgmp $(all_libraries) +noinst_LTLIBRARIES = libmse.la +noinst_HEADERS = bigint.h rc4encryptor.h streamsocket.h encryptedauthenticate.h \ + encryptedserverauthenticate.h functions.h +libmse_la_SOURCES = bigint.cpp rc4encryptor.cpp streamsocket.cpp \ + encryptedauthenticate.cpp encryptedserverauthenticate.cpp functions.cpp +KDE_CXXFLAGS = $(USE_EXCEPTIONS) $(USE_RTTI) diff --git a/libktorrent/mse/bigint.cpp b/libktorrent/mse/bigint.cpp new file mode 100644 index 0000000..90c6d9e --- /dev/null +++ b/libktorrent/mse/bigint.cpp @@ -0,0 +1,100 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <time.h> +#include <string.h> +#include <stdlib.h> +#include <util/log.h> +#include <util/functions.h> +#include <torrent/globals.h> +#include "bigint.h" + +using namespace bt; + +namespace mse +{ + + + BigInt::BigInt(Uint32 num_bits) + { + mpz_init2(val,num_bits); + } + + BigInt::BigInt(const QString & value) + { + mpz_init2(val,(value.length() - 2)*4); + mpz_set_str(val,value.ascii(),0); + } + + BigInt::BigInt(const BigInt & bi) + { + mpz_set(val,bi.val); + } + + BigInt::~BigInt() + { + mpz_clear(val); + } + + + BigInt & BigInt::operator = (const BigInt & bi) + { + mpz_set(val,bi.val); + return *this; + } + + BigInt BigInt::powerMod(const BigInt & x,const BigInt & e,const BigInt & d) + { + BigInt r; + mpz_powm(r.val,x.val,e.val,d.val); + return r; + } + + BigInt BigInt::random() + { + static Uint32 rnd = 0; + if (rnd % 10 == 0) + { + TimeStamp now = bt::GetCurrentTime(); + srand(now); + rnd = 0; + } + rnd++; + Uint8 tmp[20]; + for (Uint32 i = 0;i < 20;i++) + tmp[i] = (Uint8)rand() % 0x100; + + return BigInt::fromBuffer(tmp,20); + } + + Uint32 BigInt::toBuffer(Uint8* buf,Uint32 max_size) const + { + size_t foo; + mpz_export(buf,&foo,1,1,1,0,val); + return foo; + } + + BigInt BigInt::fromBuffer(const Uint8* buf,Uint32 size) + { + BigInt r(size*8); + mpz_import(r.val,size,1,1,1,0,buf); + return r; + } + +} diff --git a/libktorrent/mse/bigint.h b/libktorrent/mse/bigint.h new file mode 100644 index 0000000..ad94d20 --- /dev/null +++ b/libktorrent/mse/bigint.h @@ -0,0 +1,98 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef MSEBIGINT_H +#define MSEBIGINT_H + +#include <qstring.h> +#include <util/constants.h> +#include <stdio.h> +#include <gmp.h> + +using bt::Uint8; +using bt::Uint16; +using bt::Uint32; +using bt::Uint64; + +namespace mse +{ + + /** + * @author Joris Guisson <joris.guisson@gmail.com> + * + * Class which can hold an arbitrary large integer. This will be a very important part of our + * MSE implementation. + */ + class BigInt + { + public: + /** + * Create a big integer, with num_bits bits. + * All bits will be set to 0. + * @param num_bits The number of bits + */ + BigInt(Uint32 num_bits = 0); + + /** + * Create a big integer of a string. The string must be + * a hexadecimal representation of an integer. For example : + * 12AFFE123488BBBE123 + * + * Letters can be upper or lower case. Invalid chars will create an invalid number. + * @param value The hexadecimal representation of the number + */ + BigInt(const QString & value); + + /** + * Copy constructor. + * @param bi BigInt to copy + */ + BigInt(const BigInt & bi); + virtual ~BigInt(); + + /** + * Assignment operator. + * @param bi The BigInt to copy + * @return *this + */ + BigInt & operator = (const BigInt & bi); + + /** + * Calculates + * (x ^ e) mod d + * ^ is power + */ + static BigInt powerMod(const BigInt & x,const BigInt & e,const BigInt & d); + + /// Make a random BigInt + static BigInt random(); + + /// Export the bigint ot a buffer + Uint32 toBuffer(Uint8* buf,Uint32 max_size) const; + + /// Make a BigInt out of a buffer + static BigInt fromBuffer(const Uint8* buf,Uint32 size); + + private: + mpz_t val; + }; + +} + +#endif diff --git a/libktorrent/mse/encryptedauthenticate.cpp b/libktorrent/mse/encryptedauthenticate.cpp new file mode 100644 index 0000000..644ba7b --- /dev/null +++ b/libktorrent/mse/encryptedauthenticate.cpp @@ -0,0 +1,302 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <stdlib.h> +#include <algorithm> +#include <util/functions.h> +#include <util/log.h> +#include <torrent/globals.h> +#include <torrent/server.h> +#include "encryptedauthenticate.h" +#include "rc4encryptor.h" +#include "streamsocket.h" +#include "functions.h" + +using namespace bt; + +namespace mse +{ + + + + EncryptedAuthenticate::EncryptedAuthenticate( + const QString& ip, + Uint16 port, + const SHA1Hash& info_hash, + const PeerID& peer_id, + PeerManager* pman) + : Authenticate(ip, port, info_hash, peer_id, pman) + { + mse::GeneratePublicPrivateKey(xa,ya); + state = NOT_CONNECTED; + buf_size = 0; + our_rc4 = 0; + vc_off = 0; + dec_bytes = 0; + crypto_select = 0; + pad_D_len = 0; + end_of_crypto_handshake = 0; + //Out(SYS_CON|LOG_DEBUG) << "EncryptedAuthenticate : " << ip << ":" << port << endl; + } + + + EncryptedAuthenticate::~EncryptedAuthenticate() + { + delete our_rc4; + } + + + + void EncryptedAuthenticate::connected() + { + // we are connected so send ya and some padding + Uint8 tmp[608]; + ya.toBuffer(tmp,96); + sock->sendData(tmp,96 + rand() % 512); + state = SENT_YA; + } + + /* + 1 A->B: Diffie Hellman Ya, PadA + 2 B->A: Diffie Hellman Yb, PadB + 3 A->B: HASH('req1', S), HASH('req2', SKEY) xor HASH('req3', S), ENCRYPT(VC, crypto_provide, len(PadC), PadC, len(IA)), ENCRYPT(IA) + 4 B->A: ENCRYPT(VC, crypto_select, len(padD), padD), ENCRYPT2(Payload Stream) + 5 A->B: ENCRYPT2(Payload Stream) + */ + + + + void EncryptedAuthenticate::handleYB() + { + // if you can't sent 96 bytes you are not worth the effort + if (buf_size < 96) + { + Out(SYS_CON|LOG_DEBUG) << "Not enough data received, encrypted authentication failed" << endl; + onFinish(false); + return; + } + + // read Yb + yb = BigInt::fromBuffer(buf,96); + + // calculate s + s = mse::DHSecret(xa,yb); + + state = GOT_YB; + // now we must send line 3 + Uint8 tmp_buf[120]; // temporary buffer + bt::SHA1Hash h1,h2; // temporary hash + + // generate and send the first hash + memcpy(tmp_buf,"req1",4); + s.toBuffer(tmp_buf + 4,96); + h1 = SHA1Hash::generate(tmp_buf,100); + sock->sendData(h1.getData(),20); + + // generate second and third hash and xor them + memcpy(tmp_buf,"req2",4); + memcpy(tmp_buf+4,info_hash.getData(),20); + h1 = SHA1Hash::generate(tmp_buf,24); + + memcpy(tmp_buf,"req3",4); + s.toBuffer(tmp_buf + 4,96); + h2 = SHA1Hash::generate(tmp_buf,100); + sock->sendData((h1 ^ h2).getData(),20); + + // now we enter encrypted mode the keys are : + // HASH('keyA', S, SKEY) for the encryption key + // HASH('keyB', S, SKEY) for the decryption key + enc = mse::EncryptionKey(true,s,info_hash); + dec = mse::EncryptionKey(false,s,info_hash); + + our_rc4 = new RC4Encryptor(dec,enc); + + // now we must send ENCRYPT(VC, crypto_provide, len(PadC), PadC, len(IA)) + memset(tmp_buf,0,16); // VC are 8 0x00's + if (Globals::instance().getServer().unencryptedConnectionsAllowed()) + tmp_buf[11] = 0x03; // we support both plain text and rc4 + else + tmp_buf[11] = 0x02; + WriteUint16(tmp_buf,12,0x0000); // no padC + WriteUint16(tmp_buf,14,68); // length of IA, which will be the bittorrent handshake + // send IA which is the handshake + makeHandshake(tmp_buf+16,info_hash,our_peer_id); + sock->sendData(our_rc4->encrypt(tmp_buf,84),84); + + // search for the encrypted VC in the data + findVC(); + } + + void EncryptedAuthenticate::findVC() + { + Uint8 vc[8] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; + + RC4Encryptor rc4(enc,dec); + memcpy(vc,rc4.encrypt(vc,8),8); + + Uint32 max_i = buf_size - 8; + for (Uint32 i = 96;i < max_i;i++) + { + if (vc[0] == buf[i] && memcmp(buf+i,vc,8) == 0) + { + state = FOUND_VC; + vc_off = i; + handleCryptoSelect(); + return; + } + } + + // we haven't found it in the first 616 bytes (96 + max 512 padding + 8 bytes VC) + if (buf_size >= 616) + { + onFinish(false); + } + } + + void EncryptedAuthenticate::handleCryptoSelect() + { + // not enough data available so lets come back later + if (vc_off + 14 >= buf_size) + return; + + // now decrypt the first 14 bytes + our_rc4->decrypt(buf + vc_off,14); + // check the VC + for (Uint32 i = vc_off;i < vc_off + 8;i++) + { + if (buf[i]) + { + Out(SYS_CON|LOG_DEBUG) << "Invalid VC " << endl; + onFinish(false); + return; + } + } + + crypto_select = ReadUint32(buf,vc_off + 8); + pad_D_len = ReadUint16(buf,vc_off + 12); + if (pad_D_len > 512) + { + Out(SYS_CON|LOG_DEBUG) << "Invalid pad D length" << endl; + onFinish(false); + return; + } + + end_of_crypto_handshake = vc_off + 14 + pad_D_len; + if (!(vc_off + 14 + pad_D_len < buf_size)) + { + // padD is not complete, wait for that + state = WAIT_FOR_PAD_D; + return; + } + + handlePadD(); + } + + void EncryptedAuthenticate::handlePadD() + { + // decrypt the padding + our_rc4->decrypt(buf + (vc_off + 14),pad_D_len); + + bool rc4 = false; + if (crypto_select & 0x00000001) // plain_text selected + { + delete our_rc4; + our_rc4 = 0; + } + else if (crypto_select & 0x00000002) // now it must be rc4 if not exit + { + sock->setRC4Encryptor(our_rc4); + our_rc4 = 0; + rc4 = true; + } + else // we don't support anything else so error out + { + onFinish(false); + return; + } + + // noz we wait for the normal handshake + state = NORMAL_HANDSHAKE; + // if we have read more then the crypto handshake, reinsert it + if (buf_size > vc_off + 14 + pad_D_len) + { + Uint32 off = vc_off + 14 + pad_D_len; + sock->reinsert(buf + off,buf_size - off); + Authenticate::onReadyRead(); + } + } + + void EncryptedAuthenticate::onReadyRead() + { + if (finished) + return; + + + Uint32 ba = sock->bytesAvailable(); + if (ba == 0) + { + onFinish(false); + return; + } + + if (state != NORMAL_HANDSHAKE) + { + if (buf_size + ba > MAX_EA_BUF_SIZE) + ba = MAX_EA_BUF_SIZE - buf_size; + + // do not read past the end of padD + if (pad_D_len > 0 && buf_size + ba > vc_off + 14 + pad_D_len) + ba = (vc_off + 14 + pad_D_len) - buf_size; + // read data + buf_size += sock->readData(buf + buf_size,ba); + + } + + switch (state) + { + case SENT_YA: + if (ba > 608) + { + onFinish(false); + } + else + { + handleYB(); + } + break; + case GOT_YB: + findVC(); + break; + case FOUND_VC: + handleCryptoSelect(); + break; + case WAIT_FOR_PAD_D: + handlePadD(); + break; + case NORMAL_HANDSHAKE: + // let AuthenticateBase deal with the data + AuthenticateBase::onReadyRead(); + break; + }; + } + + +} + +#include "encryptedauthenticate.moc" diff --git a/libktorrent/mse/encryptedauthenticate.h b/libktorrent/mse/encryptedauthenticate.h new file mode 100644 index 0000000..74ccc1b --- /dev/null +++ b/libktorrent/mse/encryptedauthenticate.h @@ -0,0 +1,82 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef MSEENCRYPTEDAUTHENTICATE_H +#define MSEENCRYPTEDAUTHENTICATE_H + +#include <util/sha1hash.h> +#include <torrent/authenticate.h> +#include "bigint.h" + + +namespace mse +{ + class RC4Encryptor; + + const Uint32 MAX_EA_BUF_SIZE = 622 + 512; + + /** + * @author Joris Guisson <joris.guisson@gmail.com> + * + * Encrypted version of the Authenticate class + */ + class EncryptedAuthenticate : public bt::Authenticate + { + Q_OBJECT + public: + EncryptedAuthenticate(const QString& ip, Uint16 port, const bt::SHA1Hash& info_hash, const bt::PeerID& peer_id, bt::PeerManager* pman); + virtual ~EncryptedAuthenticate(); + + private slots: + virtual void connected(); + virtual void onReadyRead(); + + private: + void handleYB(); + void handleCryptoSelect(); + void findVC(); + void handlePadD(); + + private: + enum State + { + NOT_CONNECTED, + SENT_YA, + GOT_YB, + FOUND_VC, + WAIT_FOR_PAD_D, + NORMAL_HANDSHAKE + }; + + BigInt xa,ya,s,skey,yb; + State state; + RC4Encryptor* our_rc4; + Uint8 buf[MAX_EA_BUF_SIZE]; + Uint32 buf_size; + Uint32 vc_off; + Uint32 dec_bytes; + bt::SHA1Hash enc,dec; + Uint32 crypto_select; + Uint16 pad_D_len; + Uint32 end_of_crypto_handshake; + }; + +} + +#endif diff --git a/libktorrent/mse/encryptedserverauthenticate.cpp b/libktorrent/mse/encryptedserverauthenticate.cpp new file mode 100644 index 0000000..40353ad --- /dev/null +++ b/libktorrent/mse/encryptedserverauthenticate.cpp @@ -0,0 +1,354 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <stdlib.h> +#include <util/functions.h> +#include <util/log.h> +#include <torrent/server.h> +#include <torrent/globals.h> +#include "encryptedserverauthenticate.h" +#include "functions.h" +#include "streamsocket.h" +#include "rc4encryptor.h" + +using namespace bt; + +namespace mse +{ + EncryptedServerAuthenticate::EncryptedServerAuthenticate(mse::StreamSocket* sock, bt::Server* server): bt::ServerAuthenticate(sock, server) + { + mse::GeneratePublicPrivateKey(xb,yb); + state = WAITING_FOR_YA; + buf_size = 0; + req1_off = 0; + our_rc4 = 0; + pad_C_len = 0; + crypto_provide = crypto_select = 0; + } + + + EncryptedServerAuthenticate::~EncryptedServerAuthenticate() + { + delete our_rc4; + } + + void EncryptedServerAuthenticate::sendYB() + { + Uint8 tmp[608]; + yb.toBuffer(tmp,96); + // DumpBigInt("Xb",xb); + // DumpBigInt("Yb",yb); + sock->sendData(tmp,96 + rand() % 512); + //Out() << "Sent YB" << endl; + } + + + void EncryptedServerAuthenticate::handleYA() + { + sendYB(); + + ya = BigInt::fromBuffer(buf,96); + // DumpBigInt("Ya",ya); + // now calculate secret + s = mse::DHSecret(xb,ya); + // DumpBigInt("S",s); + state = WAITING_FOR_REQ1; + // see if we can find req1 + findReq1(); + } + + void EncryptedServerAuthenticate::findReq1() + { + if (buf_size < 116) // safety check + return; + + // Out() << "Find Req1" << endl; + Uint8 tmp[100]; + memcpy(tmp,"req1",4); + s.toBuffer(tmp + 4,96); + SHA1Hash req1 = SHA1Hash::generate(tmp,100); + for (Uint32 i = 96;i < buf_size - 20;i++) + { + if (buf[i] == req1.getData()[0] && memcmp(buf+i,req1.getData(),20) == 0) + { + state = FOUND_REQ1; + req1_off = i; + calculateSKey(); + return; + } + } + + if (buf_size > 608) + { + // Out(SYS_CON|LOG_DEBUG) << "Couldn't find req1" << endl; + onFinish(false); + } + } + + void EncryptedServerAuthenticate::calculateSKey() + { + // Out(SYS_CON|LOG_DEBUG) << "Calculate SKEY" << endl; + // not enough data return + if (req1_off + 40 > buf_size) + return; + + Uint8 tmp[100]; + memcpy(tmp,"req3",4); + s.toBuffer(tmp+4,96); + SHA1Hash r3 = SHA1Hash::generate(tmp,100); + SHA1Hash r(buf + req1_off + 20); + + // r = HASH('req2', SKEY) xor HASH('req3', S) + SHA1Hash r2 = r ^ r3; // now calculate HASH('req2', SKEY) + if (!server->findInfoHash(r2,info_hash)) + { + // Out(SYS_CON|LOG_DEBUG) << "Unknown info_hash" << endl; + onFinish(false); + return; + } + // we have found the info_hash, now process VC and the rest + state = FOUND_INFO_HASH; + processVC(); + } + + void EncryptedServerAuthenticate::processVC() + { + // Out(SYS_CON|LOG_DEBUG) << "Process VC" << endl; + if (!our_rc4) + { + // calculate the keys + SHA1Hash enc = mse::EncryptionKey(false,s,info_hash); + SHA1Hash dec = mse::EncryptionKey(true,s,info_hash); + //Out() << "enc = " << enc.toString() << endl; + //Out() << "dec = " << dec.toString() << endl; + our_rc4 = new RC4Encryptor(dec,enc); + } + + // if we do not have everything return + if (buf_size < req1_off + 40 + 14) + return; + + + Uint32 off = req1_off + 40; + // now decrypt the vc and crypto_provide and the length of pad_C + our_rc4->decrypt(buf + off,14); + + // check the VC + for (Uint32 i = 0;i < 8;i++) + { + if (buf[off + i]) + { + // Out(SYS_CON|LOG_DEBUG) << "Illegal VC" << endl; + onFinish(false); + return; + } + } + // get crypto_provide and the length of pad_C + crypto_provide = bt::ReadUint32(buf,off + 8); + pad_C_len = bt::ReadUint16(buf,off + 12); + if (pad_C_len > 512) + { + Out(SYS_CON|LOG_DEBUG) << "Illegal pad C length" << endl; + onFinish(false); + return; + } + + // now we have crypto_provide we can send + // ENCRYPT(VC, crypto_select, len(padD), padD) + Uint8 tmp[14]; + memset(tmp,0,14); // VC + if (crypto_provide & 0x0000002) // RC4 + { + WriteUint32(tmp,8,0x0000002); + crypto_select = 0x0000002; + } + else + { + WriteUint32(tmp,8,0x0000001); + crypto_select = 0x0000001; + } + bt::WriteUint16(tmp,12,0); // no pad D + + sock->sendData(our_rc4->encrypt(tmp,14),14); + + // handle pad C + if (buf_size < req1_off + 14 + pad_C_len) + { + // we do not have the full padC + state = WAIT_FOR_PAD_C; + return; + } + + handlePadC(); + } + + void EncryptedServerAuthenticate::handlePadC() + { + // Out(SYS_CON|LOG_DEBUG) << "Handle PAD C" << endl; + // not enough data, so return, we need padC and the length of IA + if (buf_size < req1_off + 54 + pad_C_len + 2) + return; + + // we have decrypted everything up to pad_C_len + Uint32 off = req1_off + 54; + our_rc4->decrypt(buf + off,pad_C_len + 2); + ia_len = bt::ReadUint16(buf,off + pad_C_len); + if (buf_size < off + ia_len) + { + // we do not have the IA, so wait for it + state = WAIT_FOR_IA; + return; + } + handleIA(); + } + + void EncryptedServerAuthenticate::handleIA() + { + // Out(SYS_CON|LOG_DEBUG) << "Handle IA" << endl; + // not enough data, so return, we need padC and the length of IA + if (buf_size < req1_off + 54 + pad_C_len + 2 + ia_len) + return; + + // decrypt the initial argument + if (ia_len > 0) + { + Uint32 off = req1_off + 54 + pad_C_len + 2; + // reinsert everything so that the normal authentication can handle it + sock->reinsert(buf + off,buf_size - off); + } + + bool allow_unenc = Globals::instance().getServer().unencryptedConnectionsAllowed(); + + if (crypto_select & 0x0000002) + { + sock->setRC4Encryptor(our_rc4); + our_rc4 = 0; + } + else if (!allow_unenc && crypto_select & 0x00000001) + { + // if no encrypted connections + Out(SYS_CON|LOG_DEBUG) << "Unencrypted connections not allowed" << endl; + onFinish(false); + return; + } + else + { + delete our_rc4; + our_rc4 = 0; + } + + // hand it over to ServerAuthenticate + state = NON_ENCRYPTED_HANDSHAKE; + ServerAuthenticate::onReadyRead(); + } + + void EncryptedServerAuthenticate::onReadyRead() + { + if (!sock) + return; + + Uint32 ba = sock->bytesAvailable(); + if (!ba) + { + onFinish(false); + return; + } + + // make sure we don't write past the end of the buffer + if (buf_size + ba > MAX_SEA_BUF_SIZE) + ba = MAX_SEA_BUF_SIZE - buf_size; + + switch (state) + { + case WAITING_FOR_YA: + if (ba <= 68 && Globals::instance().getServer().unencryptedConnectionsAllowed()) + { + // this is most likely an unencrypted handshake, so if we can find a peer manager + // for the info hash in it, add it to the list of potential peers of that peer manager + // so it will be contacted later on + /* buf_size += sock->readData(buf + buf_size,ba); + if (buf_size >= 48) + { + SHA1Hash rh(buf+28); + PeerManager* pman = server->findPeerManager(rh); + if (pman) + { + PotentialPeer pp; + pp.ip = sock->getRemoteIPAddress(); + pp.port = sock->getRemotePort(); + pman->addPotentialPeer(pp); + } + } + onFinish(false); + */ + Out(SYS_CON|LOG_DEBUG) << "Switching back to normal server authenticate" << endl; + state = NON_ENCRYPTED_HANDSHAKE; + ServerAuthenticate::onReadyRead(); + } + else + { + buf_size += sock->readData(buf + buf_size,ba); + if (buf_size >= 96) + handleYA(); + } + break; + case WAITING_FOR_REQ1: + if (buf_size + ba > MAX_SEA_BUF_SIZE) + ba = MAX_SEA_BUF_SIZE - buf_size; + + buf_size += sock->readData(buf + buf_size,ba); + findReq1(); + break; + case FOUND_REQ1: + if (buf_size + ba > MAX_SEA_BUF_SIZE) + ba = MAX_SEA_BUF_SIZE - buf_size; + + buf_size += sock->readData(buf + buf_size,ba); + calculateSKey(); + break; + case FOUND_INFO_HASH: + if (buf_size + ba > MAX_SEA_BUF_SIZE) + ba = MAX_SEA_BUF_SIZE - buf_size; + + buf_size += sock->readData(buf + buf_size,ba); + processVC(); + break; + case WAIT_FOR_PAD_C: + if (buf_size + ba > MAX_SEA_BUF_SIZE) + ba = MAX_SEA_BUF_SIZE - buf_size; + + buf_size += sock->readData(buf + buf_size,ba); + handlePadC(); + break; + case WAIT_FOR_IA: + if (buf_size + ba > MAX_SEA_BUF_SIZE) + ba = MAX_SEA_BUF_SIZE - buf_size; + + buf_size += sock->readData(buf + buf_size,ba); + handleIA(); + break; + case NON_ENCRYPTED_HANDSHAKE: + ServerAuthenticate::onReadyRead(); + break; + } + } + + +} +#include "encryptedserverauthenticate.moc" diff --git a/libktorrent/mse/encryptedserverauthenticate.h b/libktorrent/mse/encryptedserverauthenticate.h new file mode 100644 index 0000000..3c358cd --- /dev/null +++ b/libktorrent/mse/encryptedserverauthenticate.h @@ -0,0 +1,80 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef MSEENCRYPTEDSERVERAUTHENTICATE_H +#define MSEENCRYPTEDSERVERAUTHENTICATE_H + +#include <util/sha1hash.h> +#include <torrent/serverauthenticate.h> +#include "bigint.h" + +namespace mse +{ + class RC4Encryptor; + + + const Uint32 MAX_SEA_BUF_SIZE = 608 + 20 + 20 + 8 + 4 + 2 + 512 + 2 + 68; + /** + @author Joris Guisson <joris.guisson@gmail.com> + */ + class EncryptedServerAuthenticate : public bt::ServerAuthenticate + { + Q_OBJECT + public: + EncryptedServerAuthenticate(mse::StreamSocket* sock, bt::Server* server); + virtual ~EncryptedServerAuthenticate(); + + private slots: + virtual void onReadyRead(); + + private: + void handleYA(); + void sendYB(); + void findReq1(); + void calculateSKey(); + void processVC(); + void handlePadC(); + void handleIA(); + + private: + enum State + { + WAITING_FOR_YA, + WAITING_FOR_REQ1, + FOUND_REQ1, + FOUND_INFO_HASH, + WAIT_FOR_PAD_C, + WAIT_FOR_IA, + NON_ENCRYPTED_HANDSHAKE + }; + BigInt xb,yb,s,ya; + bt::SHA1Hash skey,info_hash; + State state; + Uint8 buf[MAX_SEA_BUF_SIZE]; + Uint32 buf_size; + Uint32 req1_off; + Uint32 crypto_provide,crypto_select; + Uint16 pad_C_len; + Uint16 ia_len; + RC4Encryptor* our_rc4; + }; + +} + +#endif diff --git a/libktorrent/mse/functions.cpp b/libktorrent/mse/functions.cpp new file mode 100644 index 0000000..bb19b93 --- /dev/null +++ b/libktorrent/mse/functions.cpp @@ -0,0 +1,74 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <util/log.h> +#include <torrent/globals.h> +#include <util/sha1hash.h> +#include "functions.h" +#include "bigint.h" + +using namespace bt; + +namespace mse +{ + /* + static const BigInt P = BigInt( + "0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD" + "129024E088A67CC74020BBEA63B139B22514A08798E3404" + "DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C" + "245E485B576625E7EC6F44C42E9A63A36210000000000090563"); + */ + static const BigInt P = BigInt("0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A63A36210000000000090563"); + + void GeneratePublicPrivateKey(BigInt & priv,BigInt & pub) + { + BigInt G = BigInt("0x02"); + priv = BigInt::random(); + pub = BigInt::powerMod(G,priv,P); + } + + BigInt DHSecret(const BigInt & our_priv,const BigInt & peer_pub) + { + return BigInt::powerMod(peer_pub,our_priv,P); + } + + bt::SHA1Hash EncryptionKey(bool a,const BigInt & s,const bt::SHA1Hash & skey) + { + Uint8 buf[120]; + memcpy(buf,"key",3); + buf[3] = (Uint8)(a ? 'A' : 'B'); + s.toBuffer(buf + 4,96); + memcpy(buf + 100,skey.getData(),20); + return bt::SHA1Hash::generate(buf,120); + } + + void DumpBigInt(const QString & name,const BigInt & bi) + { + static Uint8 buf[512]; + Uint32 nb = bi.toBuffer(buf,512); + bt::Log & lg = Out(); + lg << name << " (" << nb << ") = "; + for (Uint32 i = 0;i < nb;i++) + { + lg << QString("0x%1 ").arg(buf[i],0,16); + } + lg << endl; + } + +} diff --git a/libktorrent/mse/functions.h b/libktorrent/mse/functions.h new file mode 100644 index 0000000..4be1667 --- /dev/null +++ b/libktorrent/mse/functions.h @@ -0,0 +1,39 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef MSEFUNCTIONS_H +#define MSEFUNCTIONS_H + +namespace bt +{ + class SHA1Hash; +} + +namespace mse +{ + class BigInt; + + void GeneratePublicPrivateKey(BigInt & pub,BigInt & priv); + BigInt DHSecret(const BigInt & our_priv,const BigInt & peer_pub); + bt::SHA1Hash EncryptionKey(bool a,const BigInt & s,const bt::SHA1Hash & skey); + + void DumpBigInt(const QString & name,const BigInt & bi); +} + +#endif diff --git a/libktorrent/mse/rc4encryptor.cpp b/libktorrent/mse/rc4encryptor.cpp new file mode 100644 index 0000000..422fe5d --- /dev/null +++ b/libktorrent/mse/rc4encryptor.cpp @@ -0,0 +1,100 @@ + /*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 "rc4encryptor.h" + +namespace mse +{ + static void swap(Uint8 & a,Uint8 & b) + { + Uint8 tmp = a; + a = b; + b = tmp; + } + + static Uint8 rc4_enc_buffer[bt::MAX_MSGLEN]; + + RC4::RC4(const Uint8* key,Uint32 size) : i(0),j(0) + { + // initialize state + for (Uint32 t = 0;t < 256;t++) + s[t] = t; + + j = 0; + for (Uint32 t=0;t < 256;t++) + { + j = (j + s[t] + key[t % size]) & 0xff; + swap(s[t],s[j]); + } + + i = j = 0; + } + + RC4::~RC4() + { + } + + void RC4::process(const Uint8* in,Uint8* out,Uint32 size) + { + for (Uint32 k = 0;k < size;k++) + { + out[k] = process(in[k]); + } + } + + Uint8 RC4::process(Uint8 b) + { + i = (i + 1) & 0xff; + j = (j + s[i]) & 0xff; + swap(s[i],s[j]); + Uint8 tmp = s[ (s[i] + s[j]) & 0xff]; + return tmp ^ b; + } + + + RC4Encryptor::RC4Encryptor(const bt::SHA1Hash & dk,const bt::SHA1Hash & ek) + : enc(ek.getData(),20),dec(dk.getData(),20) + { + Uint8 tmp[1024]; + enc.process(tmp,tmp,1024); + dec.process(tmp,tmp,1024); + } + + + RC4Encryptor::~RC4Encryptor() + {} + + + void RC4Encryptor::decrypt(Uint8* data,Uint32 len) + { + dec.process(data,data,len); + } + + const Uint8* RC4Encryptor::encrypt(const Uint8* data,Uint32 len) + { + enc.process(data,rc4_enc_buffer,len); + return rc4_enc_buffer; + } + + void RC4Encryptor::encryptReplace(Uint8* data,Uint32 len) + { + enc.process(data,data,len); + } + +} diff --git a/libktorrent/mse/rc4encryptor.h b/libktorrent/mse/rc4encryptor.h new file mode 100644 index 0000000..650b54e --- /dev/null +++ b/libktorrent/mse/rc4encryptor.h @@ -0,0 +1,96 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef MSERC4ENCRYPTOR_H +#define MSERC4ENCRYPTOR_H + +#include <util/sha1hash.h> +#include <util/constants.h> + +using bt::Uint8; +using bt::Uint32; + +namespace mse +{ + /** + * Helper class to do the actual encryption / decryption + */ + class RC4 + { + Uint8 i,j; + Uint8 s[256]; + public: + RC4(const Uint8* key,Uint32 size); + virtual ~RC4(); + + void process(const Uint8* in,Uint8* out,Uint32 size); + Uint8 process(Uint8 b); + }; + + /** + * @author Joris Guisson <joris.guisson@gmail.com> + * + * RC4 encryptor. Uses the RC4 algorithm to encrypt and decrypt data. + * This class has a static encryption buffer, which makes it not thread safe + * because the buffer is not protected by mutexes. + */ + class RC4Encryptor + { + RC4 enc,dec; + public: + RC4Encryptor(const bt::SHA1Hash & dkey,const bt::SHA1Hash & ekey); + virtual ~RC4Encryptor(); + + /** + * Decrypt some data, decryption happens in place (original data gets overwritten) + * @param data The data + * @param len Size of the data + */ + void decrypt(Uint8* data,Uint32 len); + + /** + * Encrypt the data. Encryption happens into the static buffer. + * So that the data passed to this function is never overwritten. + * If we send pieces we point directly to the mmap region of data, + * this cannot be overwritten, hence the static buffer. + * @param data The data + * @param len The length of the data + * @return Pointer to the static buffer + */ + const Uint8* encrypt(const Uint8* data,Uint32 len); + + /** + * Encrypt data, encryption will happen in the same buffer. So data will + * be changed replaced by it's encrypted version. + * @param data The data to encrypt + * @param len The length of the data + */ + void encryptReplace(Uint8* data,Uint32 len); + + /** + * Encrypts one byte. + * @param b The byte to encrypt + * @return The encrypted byte + */ + Uint8 encrypt(Uint8 b) {return enc.process(b);} + }; + +} + +#endif diff --git a/libktorrent/mse/streamsocket.cpp b/libktorrent/mse/streamsocket.cpp new file mode 100644 index 0000000..19a0a2e --- /dev/null +++ b/libktorrent/mse/streamsocket.cpp @@ -0,0 +1,326 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <errno.h> +#include <qsocket.h> +#include <qsocketdevice.h> +#include <util/sha1hash.h> +#include <util/log.h> +#include <torrent/peer.h> +#include <torrent/globals.h> +#include <torrent/authenticatebase.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netinet/in_systm.h> +#include <netinet/ip.h> +#include <netinet/tcp.h> +#include <net/socketmonitor.h> +#include "streamsocket.h" +#include "rc4encryptor.h" + +using namespace bt; +using namespace net; + +namespace mse +{ + + Uint8 StreamSocket::tos = IPTOS_THROUGHPUT; + Uint32 StreamSocket::num_connecting = 0; + Uint32 StreamSocket::max_connecting = 50; + + StreamSocket::StreamSocket() : sock(0),enc(0),monitored(false) + { + sock = new BufferedSocket(true); + sock->setNonBlocking(); + reinserted_data = 0; + reinserted_data_size = 0; + reinserted_data_read = 0; + + } + + StreamSocket::StreamSocket(int fd) : sock(0),enc(0),monitored(false) + { + sock = new BufferedSocket(fd); + sock->setNonBlocking(); + reinserted_data = 0; + reinserted_data_size = 0; + reinserted_data_read = 0; + sock->setTOS(tos); + } + + StreamSocket::~StreamSocket() + { + // make sure the number of connecting sockets is updated + if (connecting() && num_connecting > 0) + num_connecting--; + + SocketMonitor::instance().remove(sock); + delete [] reinserted_data; + delete enc; + delete sock; + } + + void StreamSocket::startMonitoring(net::SocketReader* rdr,net::SocketWriter* wrt) + { + this->rdr = rdr; + this->wrt = wrt; + sock->setReader(this); + sock->setWriter(this); + SocketMonitor::instance().add(sock); + monitored = true; + if (reinserted_data) + { + if (enc) + enc->decrypt(reinserted_data + reinserted_data_read, + reinserted_data_size - reinserted_data_read); + + rdr->onDataReady(reinserted_data + reinserted_data_read, + reinserted_data_size - reinserted_data_read); + delete [] reinserted_data; + reinserted_data = 0; + reinserted_data_size = 0; + } + } + + + Uint32 StreamSocket::sendData(const Uint8* data,Uint32 len) + { + if (enc) + { + // we need to make sure all data is sent because of the encryption + Uint32 ds = 0; + const Uint8* ed = enc->encrypt(data,len); + while (sock->ok() && ds < len) + { + Uint32 ret = sock->send(ed + ds,len - ds); + ds += ret; + if (ret == 0) + { + Out(SYS_CON|LOG_DEBUG) << "ret = 0" << endl; + } + } + if (ds != len) + Out() << "ds != len" << endl; + return ds; + } + else + { + Uint32 ret = sock->send(data,len); + if (ret != len) + Out() << "ret != len" << endl; + return ret; + } + } + + Uint32 StreamSocket::readData(Uint8* buf,Uint32 len) + { + Uint32 ret2 = 0; + if (reinserted_data) + { + Uint32 tr = reinserted_data_size - reinserted_data_read; + if (tr < len) + { + memcpy(buf,reinserted_data + reinserted_data_read,tr); + delete [] reinserted_data; + reinserted_data = 0; + reinserted_data_size = reinserted_data_read = 0; + ret2 = tr; + if (enc) + enc->decrypt(buf,tr); + } + else + { + tr = len; + memcpy(buf,reinserted_data + reinserted_data_read,tr); + reinserted_data_read += tr; + if (enc) + enc->decrypt(buf,tr); + return tr; + } + } + + if (len == ret2) + return ret2; + + Uint32 ret = sock->recv(buf + ret2,len - ret2); + if (ret + ret2 > 0 && enc) + enc->decrypt(buf,ret + ret2); + + return ret; + } + + Uint32 StreamSocket::bytesAvailable() const + { + Uint32 ba = sock->bytesAvailable(); + if (reinserted_data_size - reinserted_data_read > 0) + return ba + (reinserted_data_size - reinserted_data_read); + else + return ba; + } + + void StreamSocket::close() + { + sock->close(); + } + + bool StreamSocket::connectTo(const QString & ip,Uint16 port) + { + // do a safety check + if (ip.isNull() || ip.length() == 0) + return false; + + // we don't wanna block the current thread so set non blocking + sock->setNonBlocking(); + if (sock->connectTo(Address(ip,port))) + { + sock->setTOS(tos); + return true; + } + else if (connecting()) + { + num_connecting++; + } + + return false; + } + + void StreamSocket::initCrypt(const bt::SHA1Hash & dkey,const bt::SHA1Hash & ekey) + { + if (enc) + delete enc; + + enc = new RC4Encryptor(dkey,ekey); + } + + void StreamSocket::disableCrypt() + { + delete enc; + enc = 0; + } + + bool StreamSocket::ok() const + { + return sock->ok(); + } + + QString StreamSocket::getRemoteIPAddress() const + { + return sock->getPeerName().toString(); + } + + bt::Uint16 StreamSocket::getRemotePort() const + { + return sock->getPeerName().port(); + } + + net::Address StreamSocket::getRemoteAddress() const + { + return sock->getPeerName(); + } + + void StreamSocket::setRC4Encryptor(RC4Encryptor* e) + { + if (enc) + delete enc; + + enc = e; + } + + void StreamSocket::reinsert(const Uint8* d,Uint32 size) + { +// Out() << "Reinsert : " << size << endl; + Uint32 off = 0; + if (reinserted_data) + { + off = reinserted_data_size; + reinserted_data = (Uint8*)realloc(reinserted_data,reinserted_data_size + size); + reinserted_data_size += size; + } + else + { + reinserted_data = new Uint8[size]; + reinserted_data_size = size; + } + memcpy(reinserted_data + off,d,size); + } + + bool StreamSocket::connecting() const + { + return sock->state() == net::Socket::CONNECTING; + } + + void StreamSocket::onDataReady(Uint8* buf,Uint32 size) + { + if (enc) + enc->decrypt(buf,size); + + if (rdr) + rdr->onDataReady(buf,size); + } + + Uint32 StreamSocket::onReadyToWrite(Uint8* data,Uint32 max_to_write) + { + if (!wrt) + return 0; + + Uint32 ret = wrt->onReadyToWrite(data,max_to_write); + if (enc && ret > 0) // do encryption if necessary + enc->encryptReplace(data,ret); + + + return ret; + } + + bool StreamSocket::hasBytesToWrite() const + { + return wrt ? wrt->hasBytesToWrite() : false; + } + + float StreamSocket::getDownloadRate() const + { + return sock ? sock->getDownloadRate() : 0.0f; + } + + float StreamSocket::getUploadRate() const + { + return sock ? sock->getUploadRate() : 0.0f; + } + + bool StreamSocket::connectSuccesFull() const + { + bool ret = sock->connectSuccesFull(); + if (ret) + sock->setTOS(tos); + + if (num_connecting > 0) + num_connecting--; + + return ret; + } + + void StreamSocket::setGroupIDs(Uint32 up,Uint32 down) + { + sock->setGroupID(up,true); + sock->setGroupID(down,false); + } +} + +#include "streamsocket.moc" diff --git a/libktorrent/mse/streamsocket.h b/libktorrent/mse/streamsocket.h new file mode 100644 index 0000000..5006a7b --- /dev/null +++ b/libktorrent/mse/streamsocket.h @@ -0,0 +1,185 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef MSESTREAMSOCKET_H +#define MSESTREAMSOCKET_H + +#include <qobject.h> +#include <util/constants.h> +#include <net/bufferedsocket.h> + +class QString; + +using bt::Uint8; +using bt::Uint16; +using bt::Uint32; + +namespace bt +{ + class SHA1Hash; + class Peer; + class AuthenticateBase; +} + +namespace mse +{ + class RC4Encryptor; + + + + + /** + * @author Joris Guisson <joris.guisson@gmail.com> + * + * Wrapper around a TCP socket which handles RC4 encryption. + * Once authentication is done, the sendData and readData interfaces should + * not be used anymore, a SocketReader and SocketWriter should be provided, + * so that reading and writing is controlled from the monitor thread. + */ + class StreamSocket : public QObject,public net::SocketReader,public net::SocketWriter + { + Q_OBJECT + public: + StreamSocket(); + StreamSocket(int fd); + virtual ~StreamSocket(); + + /** + * Send a chunk of data. (Does not encrypt the data) + * @param data The data + * @param len The length + * @return Number of bytes written + */ + Uint32 sendData(const Uint8* data,Uint32 len); + + /** + * Reads data from the peer. + * @param buf The buffer to store the data + * @param len The maximum number of bytes to read + * @return The number of bytes read + */ + Uint32 readData(Uint8* buf,Uint32 len); + + /// Get the number of bytes available to read. + Uint32 bytesAvailable() const; + + /// Are we using encryption + bool encrypted() const {return enc != 0;} + + /** + * Initialize the RC4 encryption algorithm. + * @param dkey + * @param ekey + */ + void initCrypt(const bt::SHA1Hash & dkey,const bt::SHA1Hash & ekey); + + /// Set the encryptor + void setRC4Encryptor(RC4Encryptor* enc); + + /// Disables encryption. All data will be sent over as plain text. + void disableCrypt(); + + /// Close the socket + void close(); + + /// Connect the socket to a remote host + bool connectTo(const QString & ip,Uint16 port); + + /// Get the IP address of the remote peer + QString getRemoteIPAddress() const; + + /// Get the port of the remote peer + bt::Uint16 getRemotePort() const; + + /// Get the full address + net::Address getRemoteAddress() const; + + /** + * Reinsert data, this is needed when we read to much during the crypto handshake. + * This data will be the first to read out. The data will be copied to a temporary buffer + * which will be destroyed when the reinserted data has been read. + */ + void reinsert(const Uint8* d,Uint32 size); + + /// see if the socket is still OK + bool ok() const; + + /// Get the file descriptor + int fd() const {return sock->fd();} + + /// Start monitoring of this socket by the monitor thread + void startMonitoring(net::SocketReader* rdr,net::SocketWriter* wrt); + + /// Is this socket connecting to a remote host + bool connecting() const; + + /// See if a connect was success full + bool connectSuccesFull() const; + + /// Get the current download rate + float getDownloadRate() const; + + /// Get the current download rate + float getUploadRate() const; + + /** + * Set the TOS byte for new sockets. + * @param t TOS value + */ + static void setTOS(Uint8 t) {tos = t;} + + /** + * Set the download and upload group ID's + * @param up Upload group ID + * @param down Download group ID + */ + void setGroupIDs(Uint32 up,Uint32 down); + + /** + * Check if we are allowed to initiate another outgoing connection. + */ + static bool canInitiateNewConnection() {return num_connecting < max_connecting;} + + /** + * Set the maximum number of connecting sockets we are allowed to have. + */ + static void setMaxConnecting(Uint32 mc) {max_connecting = mc;} + private: + virtual void onDataReady(Uint8* buf,Uint32 size); + virtual Uint32 onReadyToWrite(Uint8* data,Uint32 max_to_write); + virtual bool hasBytesToWrite() const; + + private: + net::BufferedSocket* sock; + RC4Encryptor* enc; + Uint8* reinserted_data; + Uint32 reinserted_data_size; + Uint32 reinserted_data_read; + bool monitored; + net::SocketReader* rdr; + net::SocketWriter* wrt; + + static Uint8 tos; + static Uint32 num_connecting; // the number of connections we have in SYN_SENT state + static Uint32 max_connecting; + }; + +} + +#endif diff --git a/libktorrent/net/Makefile.am b/libktorrent/net/Makefile.am new file mode 100644 index 0000000..e67354c --- /dev/null +++ b/libktorrent/net/Makefile.am @@ -0,0 +1,10 @@ +INCLUDES = -I$(srcdir)/.. -I$(srcdir)/. $(all_includes) +METASOURCES = AUTO +libnet_la_LDFLAGS = $(all_libraries) +noinst_LTLIBRARIES = libnet.la +noinst_HEADERS = address.h bufferedsocket.h circularbuffer.h downloadthread.h \ + networkthread.h portlist.h socket.h socketmonitor.h speed.h uploadthread.h +libnet_la_SOURCES = address.cpp bufferedsocket.cpp circularbuffer.cpp \ + downloadthread.cpp networkthread.cpp portlist.cpp socket.cpp socketgroup.cpp \ + socketmonitor.cpp speed.cpp uploadthread.cpp +KDE_CXXFLAGS = $(USE_EXCEPTIONS) $(USE_RTTI) diff --git a/libktorrent/net/address.cpp b/libktorrent/net/address.cpp new file mode 100644 index 0000000..4a4da3c --- /dev/null +++ b/libktorrent/net/address.cpp @@ -0,0 +1,67 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <sys/types.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include "address.h" + +namespace net +{ + + Address::Address() : m_ip(0),m_port(0) {} + + Address::Address(const QString & host,Uint16 port) : m_ip(0),m_port(port) + { + struct in_addr a; + if (inet_aton(host.ascii(),&a)) + m_ip = ntohl(a.s_addr); + } + + Address::Address(const Address & addr) : m_ip(addr.ip()),m_port(addr.port()) + { + } + + Address:: ~Address() + {} + + + Address & Address::operator = (const Address & a) + { + m_ip = a.ip(); + m_port = a.port(); + return *this; + } + + + bool Address::operator == (const Address & a) + { + return m_ip == a.ip() && m_port == a.port(); + } + + QString Address::toString() const + { + return QString("%1.%2.%3.%4") + .arg((m_ip & 0xFF000000) >> 24) + .arg((m_ip & 0x00FF0000) >> 16) + .arg((m_ip & 0x0000FF00) >> 8) + .arg(m_ip & 0x000000FF); + } + +} diff --git a/libktorrent/net/address.h b/libktorrent/net/address.h new file mode 100644 index 0000000..28c4e2c --- /dev/null +++ b/libktorrent/net/address.h @@ -0,0 +1,60 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef NETADDRESS_H +#define NETADDRESS_H + +#include <qstring.h> +#include <util/constants.h> + +namespace net +{ + using bt::Uint32; + using bt::Uint16; + + /** + @author Joris Guisson <joris.guisson@gmail.com> + */ + class Address + { + Uint32 m_ip; + Uint16 m_port; + public: + Address(); + Address(const QString & host,Uint16 port); + Address(const Address & addr); + virtual ~Address(); + + + Address & operator = (const Address & a); + bool operator == (const Address & a); + + Uint32 ip() const {return m_ip;} + void setIP(Uint32 ip) {m_ip = ip;} + + Uint16 port() const {return m_port;} + void setPort(Uint16 p) {m_port = p;} + + QString toString() const; + + }; + +} + +#endif diff --git a/libktorrent/net/bufferedsocket.cpp b/libktorrent/net/bufferedsocket.cpp new file mode 100644 index 0000000..2165f70 --- /dev/null +++ b/libktorrent/net/bufferedsocket.cpp @@ -0,0 +1,217 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <util/log.h> +#include <torrent/globals.h> +#include "bufferedsocket.h" +#include "circularbuffer.h" +#include "speed.h" + +using namespace bt; + +namespace net +{ +#define OUTPUT_BUFFER_SIZE 16393 + + BufferedSocket::BufferedSocket(int fd) : Socket(fd),rdr(0),wrt(0),up_gid(0),down_gid(0) + { + bytes_in_output_buffer = 0; + bytes_sent = 0; + down_speed = new Speed(); + up_speed = new Speed(); + output_buffer = new Uint8[OUTPUT_BUFFER_SIZE]; + poll_index = -1; + } + + BufferedSocket::BufferedSocket(bool tcp) : Socket(tcp),rdr(0),wrt(0),up_gid(0),down_gid(0) + { + bytes_in_output_buffer = 0; + bytes_sent = 0; + down_speed = new Speed(); + up_speed = new Speed(); + output_buffer = new Uint8[OUTPUT_BUFFER_SIZE]; + poll_index = -1; + } + + + BufferedSocket::~BufferedSocket() + { + delete [] output_buffer; + delete up_speed; + delete down_speed; + } + + void BufferedSocket::setGroupID(Uint32 gid,bool upload) + { + if (upload) + up_gid = gid; + else + down_gid = gid; + } + + float BufferedSocket::getDownloadRate() const + { + mutex.lock(); + float ret = down_speed->getRate(); + mutex.unlock(); + return ret; + } + + float BufferedSocket::getUploadRate() const + { + mutex.lock(); + float ret = up_speed->getRate(); + mutex.unlock(); + return ret; + } + + static Uint8 input_buffer[OUTPUT_BUFFER_SIZE]; + + Uint32 BufferedSocket::readBuffered(Uint32 max_bytes_to_read,bt::TimeStamp now) + { + Uint32 br = 0; + bool no_limit = (max_bytes_to_read == 0); + + if (bytesAvailable() == 0) + { + close(); + return 0; + } + + while ((br < max_bytes_to_read || no_limit) && bytesAvailable() > 0) + { + Uint32 tr = bytesAvailable(); + if (tr > OUTPUT_BUFFER_SIZE) + tr = OUTPUT_BUFFER_SIZE; + if (!no_limit && tr + br > max_bytes_to_read) + tr = max_bytes_to_read - br; + + int ret = Socket::recv(input_buffer,tr); + if (ret != 0) + { + mutex.lock(); + down_speed->onData(ret,now); + mutex.unlock(); + if (rdr) + rdr->onDataReady(input_buffer,ret); + br += ret; + } + else + { + // connection closed, so just return the number of bytes read + return br; + } + } + return br; + } + + Uint32 BufferedSocket::sendOutputBuffer(Uint32 max,bt::TimeStamp now) + { + if (bytes_in_output_buffer == 0) + return 0; + + if (max == 0 || bytes_in_output_buffer <= max) + { + // try to send everything + Uint32 bw = bytes_in_output_buffer; + Uint32 off = bytes_sent; + Uint32 ret = Socket::send(output_buffer + off,bw); + if (ret > 0) + { + mutex.lock(); + up_speed->onData(ret,now); + mutex.unlock(); + bytes_in_output_buffer -= ret; + bytes_sent += ret; + if (bytes_sent == bytes_in_output_buffer) + bytes_in_output_buffer = bytes_sent = 0; + return ret; + } + else + { + return 0; + } + } + else + { + Uint32 bw = max; + Uint32 off = bytes_sent; + Uint32 ret = Socket::send(output_buffer + off,bw); + if (ret > 0) + { + mutex.lock(); + up_speed->onData(ret,now); + mutex.unlock(); + bytes_in_output_buffer -= ret; + bytes_sent += ret; + return ret; + } + else + { + return 0; + } + } + } + + Uint32 BufferedSocket::writeBuffered(Uint32 max,bt::TimeStamp now) + { + if (!wrt) + return 0; + + Uint32 bw = 0; + bool no_limit = max == 0; + if (bytes_in_output_buffer > 0) + { + Uint32 ret = sendOutputBuffer(max,now); + if (bytes_in_output_buffer > 0) + { + // haven't sent it fully so return + return ret; + } + + bw += ret; + } + + // run as long as we do not hit the limit and we can send everything + while ((no_limit || bw < max) && bytes_in_output_buffer == 0) + { + // fill output buffer + bytes_in_output_buffer = wrt->onReadyToWrite(output_buffer,OUTPUT_BUFFER_SIZE); + bytes_sent = 0; + if (bytes_in_output_buffer > 0) + { + // try to send + bw += sendOutputBuffer(max - bw,now); + } + else + { + // no bytes available in output buffer so break + break; + } + } + + return bw; + } + + void BufferedSocket::updateSpeeds(bt::TimeStamp now) + { + up_speed->update(now); + down_speed->update(now); + } +} diff --git a/libktorrent/net/bufferedsocket.h b/libktorrent/net/bufferedsocket.h new file mode 100644 index 0000000..2c0c3ec --- /dev/null +++ b/libktorrent/net/bufferedsocket.h @@ -0,0 +1,150 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef NETBUFFEREDSOCKET_H +#define NETBUFFEREDSOCKET_H + +#include <qmutex.h> +#include <net/socket.h> + +namespace net +{ + using bt::Uint8; + using bt::Uint32; + + class Speed; + + class SocketReader + { + public: + SocketReader() {} + virtual ~SocketReader() {} + + /** + * Function which will be called whenever data has been read from the socket. + * This data should be dealt with, otherwise it will be discarded. + * @param buf The buffer + * @param size The size of the buffer + */ + virtual void onDataReady(Uint8* buf,Uint32 size) = 0; + }; + + class SocketWriter + { + public: + SocketWriter() {} + virtual ~SocketWriter() {} + + /** + * The socket is ready to write, the writer is asked to provide the data. + * The data will be fully sent, before another request is done. + * @param data The data + * @param max_to_write The maximum number of bytes to put in the buffer + * @param The number of bytes placed in the buffer + */ + virtual Uint32 onReadyToWrite(Uint8* data,Uint32 max_to_write) = 0; + + /// Check if data is ready to write + virtual bool hasBytesToWrite() const = 0; + + }; + + /** + * @author Joris Guisson <joris.guisson@gmail.com> + * + * Extends the Socket class with + */ + class BufferedSocket : public Socket + { + mutable QMutex mutex; + SocketReader* rdr; + SocketWriter* wrt; + Uint8* output_buffer; + Uint32 bytes_in_output_buffer; // bytes in the output buffer + Uint32 bytes_sent; // bytes written of the output buffer + Speed* down_speed; + Speed* up_speed; + int poll_index; + + Uint32 up_gid; + Uint32 down_gid; // group id which this torrent belongs to, group 0 means the default group + + public: + BufferedSocket(int fd); + BufferedSocket(bool tcp); + virtual ~BufferedSocket(); + + /** + * Set the group ID of the socket + * @param gid THe ID (0 is default group) + * @param upload Wether this is an upload group or a download group + */ + void setGroupID(Uint32 gid,bool upload); + + /// Get the download group ID + Uint32 downloadGroupID() const {return down_gid;} + + /// Get the upload group ID + Uint32 uploadGroupID() const {return up_gid;} + + void setReader(SocketReader* r) {rdr = r;} + void setWriter(SocketWriter* r) {wrt = r;} + + /** + * Reads data from the socket to the buffer. + * @param max_bytes_to_read Maximum number of bytes to read (0 is no limit) + * @param now Current time stamp + * @return The number of bytes read + */ + Uint32 readBuffered(Uint32 max_bytes_to_read,bt::TimeStamp now); + + /** + * Writes data from the buffer to the socket. + * @param max The maximum number of bytes to send over the socket (0 = no limit) + * * @param now Current time stamp + * @return The number of bytes written + */ + Uint32 writeBuffered(Uint32 max,bt::TimeStamp now); + + /// See if the socket has something ready to write + bool bytesReadyToWrite() const + { + return bytes_in_output_buffer > 0 || (!wrt ? false : wrt->hasBytesToWrite()); + } + + + /// Get the current download rate + float getDownloadRate() const; + + /// Get the current download rate + float getUploadRate() const; + + /// Update up and down speed + void updateSpeeds(bt::TimeStamp now); + + int getPollIndex() const {return poll_index;} + void setPollIndex(int pi) {poll_index = pi;} + + private: + Uint32 sendOutputBuffer(Uint32 max,bt::TimeStamp now); + }; + +} + +#endif diff --git a/libktorrent/net/circularbuffer.cpp b/libktorrent/net/circularbuffer.cpp new file mode 100644 index 0000000..abce80a --- /dev/null +++ b/libktorrent/net/circularbuffer.cpp @@ -0,0 +1,146 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <util/log.h> +#include <torrent/globals.h> +#include "circularbuffer.h" +#include "bufferedsocket.h" + +using namespace bt; + +namespace net +{ + + CircularBuffer::CircularBuffer(Uint32 max_size) : buf(0),max_size(max_size),first(0),size(0) + { + buf = new Uint8[max_size]; + } + + + CircularBuffer::~CircularBuffer() + { + delete [] buf; + } + + Uint32 CircularBuffer::freeSpace() const + { + return max_size - size; + } + + Uint32 CircularBuffer::write(const Uint8* data,Uint32 dsize) + { + if (size == max_size) + return 0; + + mutex.lock(); + Uint32 wp = (first + size) % max_size; + Uint32 j = 0; + while (size < max_size && (dsize == 0 || j < dsize)) + { + buf[wp] = data[j]; + j++; + wp = (wp + 1) % max_size; + size++; + } + + mutex.unlock(); + return j; + } + + Uint32 CircularBuffer::read(Uint8* data,Uint32 max_to_read) + { + if (!size) + return 0; + + mutex.lock(); + Uint32 j = 0; + while (size > 0 && j < max_to_read) + { + data[j] = buf[first]; + j++; + first = (first + 1) % max_size; + size--; + } + mutex.unlock(); + return j; + } + + Uint32 CircularBuffer::send(BufferedSocket* s,Uint32 max) + { + if (!size) + return 0; + + Uint32 ret = 0; + mutex.lock(); + + if (first + size <= max_size) + { + Uint32 ts = size; + if (max > 0 && size > max) + ts = max; + ret = s->send(buf + first,ts); + first += ret; + size -= ret; + } + else if (max > 0) // if there is a limit + { + // write from first to the end of the buffer + Uint32 to_send = max_size - first; + if (to_send > max) + to_send = max; + + ret = s->send(buf + first,to_send); + + // update first, wrap around if necessary + first = (first + ret) % max_size; + size -= ret; // ret bytes less in the buffer + max -= ret; // decrease limit + + if (max > 0 && ret == to_send && size > 0) + { + // we have sent everything so we can send more + to_send = size > max ? max : size; + Uint32 ret2 = s->send(buf,to_send); + + ret += ret2; + first += ret2; + size -= ret2; + } + } + else // no limit + { + Uint32 to_send = max_size - first; + ret = s->send(buf + first,to_send); + // update first, wrap around if necessary + first = (first + ret) % max_size; + size -= ret; // ret bytes less in the buffer + if (ret == to_send && size > 0) + { + // we have sent everything so we can send more + Uint32 ret2 = s->send(buf,size); + ret += ret2; + first += ret2; + size -= ret2; + } + } + mutex.unlock(); + return ret; + } + +} diff --git a/libktorrent/net/circularbuffer.h b/libktorrent/net/circularbuffer.h new file mode 100644 index 0000000..63e271e --- /dev/null +++ b/libktorrent/net/circularbuffer.h @@ -0,0 +1,89 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef NETCIRCULARBUFFER_H +#define NETCIRCULARBUFFER_H + +#include <qmutex.h> +#include <util/constants.h> + +namespace net +{ + using bt::Uint8; + using bt::Uint32; + + class BufferedSocket; + + /** + * @author Joris Guisson <joris.guisson@gmail.com> + * + * Simple circular buffer, to simulate a queue. + * Writes happen at the end, reads at the beginning. + * The buffer is protected by a mutex. + */ + class CircularBuffer + { + Uint8* buf; + Uint32 max_size; + Uint32 first; // index of first byte in the buffer + Uint32 size; // number of bytes in use + mutable QMutex mutex; + public: + /** + * Create the buffer. + * @param max_size Maximum size of the buffer. + */ + CircularBuffer(Uint32 max_size); + virtual ~CircularBuffer(); + + /// How much capacity does the buffer have + Uint32 capacity() const {return max_size;} + + /// How much free space is there + Uint32 freeSpace() const; + + + /** + * Write a bunch of data at the back of the buffer. + * @param data Data to write + * @param size How many bytes to write + * @return The number of bytes written in the buffer + */ + Uint32 write(const Uint8* data,Uint32 size); + + /** + * Read from the buffer. + * @param data Buffer to store read data + * @param max_to_read Maximum amount of bytes to read + * @return The number of bytes read + */ + Uint32 read(Uint8* data,Uint32 max_to_read); + + /** + * Send the data in the buffer over the socket + * @param s THe socket + * @param max Maximum bytes to send + * @return The number of bytes written + */ + Uint32 send(BufferedSocket* s,Uint32 max); + }; + +} + +#endif diff --git a/libktorrent/net/downloadthread.cpp b/libktorrent/net/downloadthread.cpp new file mode 100644 index 0000000..ae0f0b9 --- /dev/null +++ b/libktorrent/net/downloadthread.cpp @@ -0,0 +1,137 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <math.h> +#include <sys/poll.h> +#include <util/functions.h> +#include "socketgroup.h" +#include "downloadthread.h" +#include "socketmonitor.h" +#include "bufferedsocket.h" + +using namespace bt; + +namespace net +{ + Uint32 DownloadThread::dcap = 0; + Uint32 DownloadThread::sleep_time = 3; + + DownloadThread::DownloadThread(SocketMonitor* sm) : NetworkThread(sm) + { + } + + + DownloadThread::~DownloadThread() + {} + + void DownloadThread::update() + { + sm->lock(); + int num = fillPollVector(); + sm->unlock(); + + int timeout = 10; + if (poll(&fd_vec[0],num,timeout) > 0) + { + sm->lock(); + TimeStamp now = bt::Now(); + Uint32 num_ready = 0; + SocketMonitor::Itr itr = sm->begin(); + while (itr != sm->end()) + { + BufferedSocket* s = *itr; + int pi = s->getPollIndex(); + if (pi >= 0 && s->ok() && fd_vec[pi].revents & POLLIN) + { + // add to the correct group + Uint32 gid = s->downloadGroupID(); + SocketGroup* g = groups.find(gid); + if (!g) + g = groups.find(0); + + g->add(s); + num_ready++; + } + itr++; + } + + if (num_ready > 0) + doGroups(num_ready,now,dcap); + prev_run_time = now; + sm->unlock(); + } + + if (dcap > 0 || groups.count() > 0) + msleep(sleep_time); + } + + int DownloadThread::fillPollVector() + { + TimeStamp ts = bt::Now(); + int i = 0; + + // fill the poll vector with all sockets + SocketMonitor::Itr itr = sm->begin(); + while (itr != sm->end()) + { + BufferedSocket* s = *itr; + if (s && s->ok() && s->fd() > 0) + { + if (fd_vec.size() <= i) + { + // expand pollfd vector if necessary + struct pollfd pfd; + pfd.fd = s->fd(); + pfd.revents = 0; + pfd.events = POLLIN; + fd_vec.push_back(pfd); + } + else + { + // use existing slot + struct pollfd & pfd = fd_vec[i]; + pfd.fd = s->fd(); + pfd.revents = 0; + pfd.events = POLLIN; + } + s->setPollIndex(i); + i++; + s->updateSpeeds(ts); + } + else + { + s->setPollIndex(-1); + } + itr++; + } + + return i; + } + + void DownloadThread::setSleepTime(Uint32 stime) + { + if (stime >= 1 && stime <= 10) + sleep_time = stime; + } + + bool DownloadThread::doGroup(SocketGroup* g,Uint32 & allowance,bt::TimeStamp now) + { + return g->download(allowance,now); + } +} diff --git a/libktorrent/net/downloadthread.h b/libktorrent/net/downloadthread.h new file mode 100644 index 0000000..08e9e46 --- /dev/null +++ b/libktorrent/net/downloadthread.h @@ -0,0 +1,64 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef NETDOWNLOADTHREAD_H +#define NETDOWNLOADTHREAD_H + +#include <vector> +#include "networkthread.h" + +struct pollfd; + +namespace net +{ + + /** + * @author Joris Guisson <joris.guisson@gmail.com> + * + * Thread which processes incoming data + */ + class DownloadThread : public NetworkThread + { + static bt::Uint32 dcap; + static bt::Uint32 sleep_time; + + std::vector<struct pollfd> fd_vec; + + public: + DownloadThread(SocketMonitor* sm); + virtual ~DownloadThread(); + + + /// Set the download cap + static void setCap(bt::Uint32 cap) {dcap = cap;} + + /// Set the sleep time when using download caps + static void setSleepTime(bt::Uint32 stime); + private: + int fillPollVector(); + + virtual void update(); + virtual bool doGroup(SocketGroup* g,Uint32 & allowance,bt::TimeStamp now); + +// void processIncomingData(bt::TimeStamp now); + }; + +} + +#endif diff --git a/libktorrent/net/networkthread.cpp b/libktorrent/net/networkthread.cpp new file mode 100644 index 0000000..40791c9 --- /dev/null +++ b/libktorrent/net/networkthread.cpp @@ -0,0 +1,165 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <math.h> +#include <util/functions.h> +#include <util/log.h> +#include "socketgroup.h" +#include "socketmonitor.h" +#include "networkthread.h" + +using namespace bt; + +namespace net +{ + + NetworkThread::NetworkThread(SocketMonitor* sm) + : sm(sm),running(false) + { + groups.setAutoDelete(true); + groups.insert(0,new SocketGroup(0)); + } + + + NetworkThread::~NetworkThread() + {} + + void NetworkThread::run() + { + running = true; + prev_run_time = bt::Now(); + while (running) + update(); + } + + void NetworkThread::addGroup(Uint32 gid,Uint32 limit) + { + // if group already exists, just change the limit + SocketGroup* g = groups.find(gid); + if (g) + { + g->setLimit(limit); + } + else + { + g = new SocketGroup(limit); + groups.insert(gid,g); + } + } + + void NetworkThread::removeGroup(Uint32 gid) + { + // make sure the 0 group is never erased + if (gid != 0) + groups.erase(gid); + } + + void NetworkThread::setGroupLimit(Uint32 gid,Uint32 limit) + { + SocketGroup* g = groups.find(gid); + if (g) + { + g->setLimit(limit); + } + } + + Uint32 NetworkThread::doGroupsLimited(Uint32 num_ready,bt::TimeStamp now,Uint32 & allowance) + { + Uint32 num_still_ready = 0; + + // this is one pass over all the groups + bt::PtrMap<Uint32,SocketGroup>::iterator itr = groups.begin(); + while (itr != groups.end() && allowance > 0) + { + SocketGroup* g = itr->second; + if (g->numSockets() > 0) + { + Uint32 group_allowance = (Uint32)ceil(((double)g->numSockets() / num_ready) * allowance); + + // lets not do to much and make sure we don't pass 0 to the socket group (0 is unlimited) + if (group_allowance > allowance || group_allowance == 0) + group_allowance = allowance; + + Uint32 ga = group_allowance; + + if (!doGroup(g,ga,now)) + g->clear(); // group is done, so clear it + else + num_still_ready += g->numSockets(); // keep track of the number of sockets which are still ready + + Uint32 done = group_allowance - ga; + if (allowance >= done) + allowance -= done; + else + allowance = 0; + } + itr++; + } + + return num_still_ready > 0; + } + + void NetworkThread::doGroups(Uint32 num_ready,bt::TimeStamp now,bt::Uint32 limit) + { + if (limit == 0) + { + Uint32 allowance = 0; + bt::PtrMap<Uint32,SocketGroup>::iterator itr = groups.begin(); + while (itr != groups.end()) + { + SocketGroup* g = itr->second; + if (g->numSockets() > 0) + { + g->calcAllowance(now); + doGroup(g,allowance,now); + g->clear(); + } + itr++; + } + } + else + { + // calculate group allowance for each group + bt::PtrMap<Uint32,SocketGroup>::iterator itr = groups.begin(); + while (itr != groups.end()) + { + SocketGroup* g = itr->second; + g->calcAllowance(now); + itr++; + } + + Uint32 allowance = (Uint32)ceil(1.02 * limit * (now - prev_run_time) * 0.001); + + while (allowance > 0 && num_ready > 0) + { + // loop until nobody is ready anymore or the allowance is up + num_ready = doGroupsLimited(num_ready,now,allowance); + } + + // make sure all groups are cleared + itr = groups.begin(); + while (itr != groups.end()) + { + SocketGroup* g = itr->second; + g->clear(); + itr++; + } + } + } +} diff --git a/libktorrent/net/networkthread.h b/libktorrent/net/networkthread.h new file mode 100644 index 0000000..7472c15 --- /dev/null +++ b/libktorrent/net/networkthread.h @@ -0,0 +1,113 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef NETNETWORKTHREAD_H +#define NETNETWORKTHREAD_H + +#include <qthread.h> +#include <util/constants.h> +#include <util/ptrmap.h> + +using bt::Uint32; + +namespace net +{ + class SocketMonitor; + class SocketGroup; + class BufferedSocket; + + /** + @author Joris Guisson <joris.guisson@gmail.com> + + Base class for the 2 networking threads. Handles the socket groups. + */ + class NetworkThread : public QThread + { + protected: + SocketMonitor* sm; + bool running; + bt::PtrMap<Uint32,SocketGroup> groups; + bt::TimeStamp prev_run_time; + + public: + NetworkThread(SocketMonitor* sm); + virtual ~NetworkThread(); + + + /** + * Add a new group with a given limit + * @param gid The group ID (cannot be 0, 0 is the default group) + * @param limit The limit in bytes per sec + */ + void addGroup(Uint32 gid,Uint32 limit); + + /** + * Remove a group + * @param gid The group ID + */ + void removeGroup(Uint32 gid); + + /** + * Set the limit for a group + * @param gid The group ID + * @param limit The limit + */ + void setGroupLimit(Uint32 gid,Uint32 limit); + + /** + * The main function of the thread + */ + void run(); + + /** + * Subclasses must implement this function + */ + virtual void update() = 0; + + /** + * Do one SocketGroup + * @param g The group + * @param allowance The groups allowance + * @param now The current time + * @return true if the group can go again + */ + virtual bool doGroup(SocketGroup* g,Uint32 & allowance,bt::TimeStamp now) = 0; + + /// Stop before the next update + void stop() {running = false;} + + /// Is the thread running + bool isRunning() const {return running;} + + protected: + /** + * Go over all groups and do them + * @param num_ready The number of ready sockets + * @param now The current time + * @param limit The global limit in bytes per sec + */ + void doGroups(Uint32 num_ready,bt::TimeStamp now,bt::Uint32 limit); + + private: + Uint32 doGroupsLimited(Uint32 num_ready,bt::TimeStamp now,Uint32 & allowance); + }; + +} + +#endif diff --git a/libktorrent/net/portlist.cpp b/libktorrent/net/portlist.cpp new file mode 100644 index 0000000..56076ed --- /dev/null +++ b/libktorrent/net/portlist.cpp @@ -0,0 +1,73 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 "portlist.h" + +namespace net +{ + Port::Port() : number(0),proto(TCP),forward(false) + { + } + + Port::Port(bt::Uint16 number,Protocol proto,bool forward) + : number(number),proto(proto),forward(forward) + { + } + + Port::Port(const Port & p) : number(p.number),proto(p.proto),forward(p.forward) + { + } + + bool Port::operator == (const Port & p) const + { + return number == p.number && proto == p.proto; + } + + PortList::PortList() : lst(0) + {} + + + PortList::~PortList() + {} + + + void PortList::addNewPort(bt::Uint16 number,Protocol proto,bool forward) + { + Port p = Port(number,proto,forward); + append(p); + if (lst) + lst->portAdded(p); + } + + + void PortList::removePort(bt::Uint16 number,Protocol proto) + { + PortList::iterator itr = find(Port(number,proto,false)); + if (itr == end()) + return; + + if (lst) + lst->portRemoved(*itr); + + erase(itr); + } + + + +} diff --git a/libktorrent/net/portlist.h b/libktorrent/net/portlist.h new file mode 100644 index 0000000..af60c1c --- /dev/null +++ b/libktorrent/net/portlist.h @@ -0,0 +1,103 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef NETPORTLIST_H +#define NETPORTLIST_H + +#include <qvaluelist.h> +#include <util/constants.h> + +namespace net +{ + enum Protocol + { + TCP, + UDP + }; + + struct Port + { + bt::Uint16 number; + Protocol proto; + bool forward; + + Port(); + Port(bt::Uint16 number,Protocol proto,bool forward); + Port(const Port & p); + + bool operator == (const Port & p) const; + }; + + /** + * Listener class for the PortList. + */ + class PortListener + { + public: + /** + * A port has been added. + * @param port The port + */ + virtual void portAdded(const Port & port) = 0; + + /** + * A port has been removed + * @param port The port + */ + virtual void portRemoved(const Port & port) = 0; + }; + + /** + * @author Joris Guisson <joris.guisson@gmail.com> + * + * List of ports which are currently being used. + * + */ + class PortList : public QValueList<Port> + { + PortListener* lst; + public: + PortList(); + virtual ~PortList(); + + /** + * When a port is in use, this function needs to be called. + * @param number Port number + * @param proto Protocol + * @param forward Wether or not it needs to be forwarded + */ + void addNewPort(bt::Uint16 number,Protocol proto,bool forward); + + /** + * Needs to be called when a port is not being using anymore. + * @param number Port number + * @param proto Protocol + */ + void removePort(bt::Uint16 number,Protocol proto); + + /** + * Set the port listener. + * @param pl Port listener + */ + void setListener(PortListener* pl) {lst = pl;} + }; + +} + +#endif diff --git a/libktorrent/net/socket.cpp b/libktorrent/net/socket.cpp new file mode 100644 index 0000000..b9a53f3 --- /dev/null +++ b/libktorrent/net/socket.cpp @@ -0,0 +1,326 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <qglobal.h> + +#include <unistd.h> +#include <string.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/ioctl.h> +#include <errno.h> +#include <netinet/in.h> +#include <netinet/tcp.h> +#include <arpa/inet.h> + +#if defined(Q_OS_LINUX) && !defined(__FreeBSD_kernel__) +#include <asm/ioctls.h> +#endif + +#ifdef Q_OS_SOLARIS +#include <sys/filio.h> +#endif + +#ifndef MSG_NOSIGNAL +#define MSG_NOSIGNAL 0 +#endif + +#include <unistd.h> +#include <fcntl.h> + +#include <torrent/globals.h> +#include <util/log.h> +#include "socket.h" + +using namespace bt; + +namespace net +{ + + Socket::Socket(int fd) : m_fd(fd),m_state(IDLE) + { +#if defined(Q_OS_MACX) || defined(Q_OS_DARWIN) || (defined(Q_OS_FREEBSD) && !defined(__DragonFly__) && __FreeBSD_version < 600020) + int val = 1; + if (setsockopt(m_fd,SOL_SOCKET,SO_NOSIGPIPE,&val,sizeof(int)) < 0) + { + Out(SYS_CON|LOG_NOTICE) << QString("Failed to set the NOSIGPIPE option : %1").arg(strerror(errno)) << endl; + } +#endif + cacheAddress(); + } + + Socket::Socket(bool tcp) : m_fd(-1),m_state(IDLE) + { + int fd = socket(PF_INET,tcp ? SOCK_STREAM : SOCK_DGRAM,0); + if (fd < 0) + { + Out(SYS_GEN|LOG_IMPORTANT) << QString("Cannot create socket : %1").arg(strerror(errno)) << endl; + } + m_fd = fd; +#if defined(Q_OS_MACX) || defined(Q_OS_DARWIN) || (defined(Q_OS_FREEBSD) && !defined(__DragonFly__) && __FreeBSD_version < 600020) + int val = 1; + if (setsockopt(m_fd,SOL_SOCKET,SO_NOSIGPIPE,&val,sizeof(int)) < 0) + { + Out(SYS_CON|LOG_NOTICE) << QString("Failed to set the NOSIGPIPE option : %1").arg(strerror(errno)) << endl; + } +#endif + } + + Socket::~Socket() + { + if (m_fd >= 0) + { + shutdown(m_fd, SHUT_RDWR); + ::close(m_fd); + } + } + + void Socket::close() + { + if (m_fd >= 0) + { + shutdown(m_fd, SHUT_RDWR); + ::close(m_fd); + m_fd = -1; + m_state = CLOSED; + } + } + + void Socket::setNonBlocking() + { + fcntl(m_fd, F_SETFL, O_NONBLOCK); + } + + bool Socket::connectTo(const Address & a) + { + struct sockaddr_in addr; + memset(&addr,0,sizeof(struct sockaddr_in)); + addr.sin_family = AF_INET; + addr.sin_port = htons(a.port()); + addr.sin_addr.s_addr = htonl(a.ip()); + + if (::connect(m_fd,(struct sockaddr*)&addr,sizeof(struct sockaddr)) < 0) + { + if (errno == EINPROGRESS) + { + // Out(SYS_CON|LOG_DEBUG) << "Socket is connecting" << endl; + m_state = CONNECTING; + return false; + } + else + { + Out(SYS_CON|LOG_NOTICE) << QString("Cannot connect to host %1:%2 : %3") + .arg(a.toString()).arg(a.port()).arg(strerror(errno)) << endl; + return false; + } + } + m_state = CONNECTED; + cacheAddress(); + return true; + } + + bool Socket::bind(Uint16 port,bool also_listen) + { + struct sockaddr_in addr; + memset(&addr,0,sizeof(struct sockaddr_in)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + + if (::bind(m_fd,(struct sockaddr*)&addr,sizeof(struct sockaddr)) < 0) + { + Out(SYS_CON|LOG_IMPORTANT) << QString("Cannot bind to port %1 : %2").arg(port).arg(strerror(errno)) << endl; + return false; + } + + if (also_listen && listen(m_fd,5) < 0) + { + Out(SYS_CON|LOG_IMPORTANT) << QString("Cannot listen to port %1 : %2").arg(port).arg(strerror(errno)) << endl; + return false; + } + + int val = 1; + if (setsockopt(m_fd,SOL_SOCKET,SO_REUSEADDR,&val,sizeof(int)) < 0) + { + Out(SYS_CON|LOG_NOTICE) << QString("Failed to set the reuseaddr option : %1").arg(strerror(errno)) << endl; + } + m_state = BOUND; + return true; + } + + int Socket::send(const bt::Uint8* buf,int len) + { + int ret = ::send(m_fd,buf,len,MSG_NOSIGNAL); + if (ret < 0) + { + if (errno != EAGAIN && errno != EWOULDBLOCK) + { + // Out(SYS_CON|LOG_DEBUG) << "Send error : " << QString(strerror(errno)) << endl; + close(); + } + return 0; + } + return ret; + } + + int Socket::recv(bt::Uint8* buf,int max_len) + { + int ret = ::recv(m_fd,buf,max_len,0); + if (ret < 0) + { + if (errno != EAGAIN && errno != EWOULDBLOCK) + { + // Out(SYS_CON|LOG_DEBUG) << "Receive error : " << QString(strerror(errno)) << endl; + close(); + } + return 0; + } + else if (ret == 0) + { + // connection closed + close(); + return 0; + } + return ret; + } + + int Socket::sendTo(const bt::Uint8* buf,int len,const Address & a) + { + struct sockaddr_in addr; + memset(&addr,0,sizeof(struct sockaddr_in)); + addr.sin_family = AF_INET; + addr.sin_port = htons(a.port()); + addr.sin_addr.s_addr = htonl(a.ip()); + + int ns = 0; + while (ns < len) + { + int left = len - ns; + int ret = ::sendto(m_fd,(char*)buf + ns,left,0,(struct sockaddr*)&addr,sizeof(struct sockaddr)); + if (ret < 0) + { + Out(SYS_CON|LOG_DEBUG) << "Send error : " << QString(strerror(errno)) << endl; + return 0; + } + + ns += ret; + } + return ns; + } + + int Socket::recvFrom(bt::Uint8* buf,int max_len,Address & a) + { + struct sockaddr_in addr; + memset(&addr,0,sizeof(struct sockaddr_in)); + socklen_t sl = sizeof(struct sockaddr); + + int ret = ::recvfrom(m_fd,buf,max_len,0,(struct sockaddr*)&addr,&sl); + if (ret < 0) + { + Out(SYS_CON|LOG_DEBUG) << "Receive error : " << QString(strerror(errno)) << endl; + return 0; + } + + a.setPort(ntohs(addr.sin_port)); + a.setIP(ntohl(addr.sin_addr.s_addr)); + return ret; + } + + int Socket::accept(Address & a) + { + struct sockaddr_in addr; + memset(&addr,0,sizeof(struct sockaddr_in)); + socklen_t slen = sizeof(struct sockaddr_in); + + int sfd = ::accept(m_fd,(struct sockaddr*)&addr,&slen); + if (sfd < 0) + { + Out(SYS_CON|LOG_DEBUG) << "Accept error : " << QString(strerror(errno)) << endl; + return -1; + } + + a.setPort(ntohs(addr.sin_port)); + a.setIP(ntohl(addr.sin_addr.s_addr)); + + Out(SYS_CON|LOG_DEBUG) << "Accepted connection from " << QString(inet_ntoa(addr.sin_addr)) << endl; + return sfd; + } + + bool Socket::setTOS(unsigned char type_of_service) + { +#if defined(Q_OS_MACX) || defined(Q_OS_DARWIN) || (defined(Q_OS_FREEBSD) && __FreeBSD_version < 600020) || defined(Q_OS_NETBSD) || defined(Q_OS_OPENBSD) || defined(Q_OS_BSD4) + unsigned int c = type_of_service; +#else + unsigned char c = type_of_service; +#endif + if (setsockopt(m_fd,IPPROTO_IP,IP_TOS,&c,sizeof(c)) < 0) + { + Out(SYS_CON|LOG_NOTICE) << QString("Failed to set TOS to %1 : %2") + .arg(type_of_service).arg(strerror(errno)) << endl; + return false; + } + return true; + } + + Uint32 Socket::bytesAvailable() const + { + int ret = 0; + if (ioctl(m_fd,FIONREAD,&ret) < 0) + return 0; + + return ret; + } + + bool Socket::connectSuccesFull() + { + if (m_state != CONNECTING) + return false; + + int err = 0; + socklen_t len = sizeof(int); + if (getsockopt(m_fd,SOL_SOCKET,SO_ERROR,&err,&len) < 0) + return false; + + if (err == 0) + { + m_state = CONNECTED; + cacheAddress(); + } + + return err == 0; + } + + void Socket::cacheAddress() + { + struct sockaddr_in raddr; + socklen_t slen = sizeof(struct sockaddr_in); + if (getpeername(m_fd,(struct sockaddr*)&raddr,&slen) == 0) + addr = Address(inet_ntoa(raddr.sin_addr),ntohs(raddr.sin_port)); + } + + /* + void Socket::setReadBufferSize(int rbs) + { + if (setsockopt(m_fd, SOL_SOCKET, SO_RCVBUF, (char *)&rbs,sizeof(int)) < 0) + { + Out(SYS_CON|LOG_DEBUG) << "Failed to set read buffer size " << endl; + } + } + */ +} diff --git a/libktorrent/net/socket.h b/libktorrent/net/socket.h new file mode 100644 index 0000000..db8953b --- /dev/null +++ b/libktorrent/net/socket.h @@ -0,0 +1,83 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef NETSOCKET_H +#define NETSOCKET_H + +#include <util/constants.h> +#include "address.h" + +namespace net +{ + + /** + @author Joris Guisson <joris.guisson@gmail.com> + */ + class Socket + { + public: + enum State + { + IDLE, + CONNECTING, + CONNECTED, + BOUND, + CLOSED + }; + + Socket(int fd); + Socket(bool tcp); + virtual ~Socket(); + + void setNonBlocking(); + bool connectTo(const Address & addr); + /// See if a connectTo was succesfull in non blocking mode + bool connectSuccesFull(); + bool bind(Uint16 port,bool also_listen); + int send(const bt::Uint8* buf,int len); + int recv(bt::Uint8* buf,int max_len); + int sendTo(const bt::Uint8* buf,int size,const Address & addr); + int recvFrom(bt::Uint8* buf,int max_size,Address & addr); + int accept(Address & a); + bool ok() const {return m_fd >= 0;} + int fd() const {return m_fd;} + bool setTOS(unsigned char type_of_service); + const Address & getPeerName() const {return addr;} + void close(); + State state() const {return m_state;} + + /** + * Set the size of the TCP read buffer. + * @param rbs + */ +// void setReadBufferSize(Uint32 rbs); + + Uint32 bytesAvailable() const; + private: + void cacheAddress(); + + private: + int m_fd; + State m_state; + Address addr; + }; + +} + +#endif diff --git a/libktorrent/net/socketgroup.cpp b/libktorrent/net/socketgroup.cpp new file mode 100644 index 0000000..8c9c5e7 --- /dev/null +++ b/libktorrent/net/socketgroup.cpp @@ -0,0 +1,186 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <math.h> +#include <util/log.h> +#include <util/functions.h> +#include "socketgroup.h" +#include "bufferedsocket.h" + +using namespace bt; + +namespace net +{ + + SocketGroup::SocketGroup(Uint32 limit) : limit(limit) + { + prev_run_time = bt::GetCurrentTime(); + group_allowance = 0; + } + + + SocketGroup::~SocketGroup() + {} + + void SocketGroup::processUnlimited(bool up,bt::TimeStamp now) + { + std::list<BufferedSocket*>::iterator i = sockets.begin(); + while (i != sockets.end()) + { + BufferedSocket* s = *i; + if (s) + { + if (up) + s->writeBuffered(0,now); + else + s->readBuffered(0,now); + } + i++; + } + } + + bool SocketGroup::processLimited(bool up,bt::TimeStamp now,Uint32 & allowance) + { + Uint32 bslot = allowance / sockets.size() + 1; + + std::list<BufferedSocket*>::iterator itr = sockets.begin(); + + // while we can send and there are sockets left to send + while (sockets.size() > 0 && allowance > 0) + { + Uint32 as = bslot; + if (as > allowance) + as = allowance; + + BufferedSocket* s = *itr; + if (s) + { + Uint32 ret = 0; + if (up) + ret = s->writeBuffered(as,now); + else + ret = s->readBuffered(as,now); + + // if this socket did what it was supposed to do, + // it can have another go if stuff is leftover + // if it doesn't, we erase it from the list + if (ret != as) + itr = sockets.erase(itr); + else + itr++; + + if (ret > allowance) + allowance = 0; + else + allowance -= ret; + } + else + { + // 0 pointer so just erase + itr = sockets.erase(itr); + } + + // wrap around if necessary + if (itr == sockets.end()) + itr = sockets.begin(); + } + + return sockets.size() > 0; + } + + bool SocketGroup::download(Uint32 & global_allowance,bt::TimeStamp now) + { + return process(false,now,global_allowance); + } + + bool SocketGroup::upload(Uint32 & global_allowance,bt::TimeStamp now) + { + return process(true,now,global_allowance); + } + + void SocketGroup::calcAllowance(bt::TimeStamp now) + { + if (limit > 0) + group_allowance = (Uint32)ceil(1.02 * limit * (now - prev_run_time) * 0.001); + else + group_allowance = 0; + prev_run_time = now; + } + + bool SocketGroup::process(bool up,bt::TimeStamp now,Uint32 & global_allowance) + { + if (limit > 0) + { + bool ret = false; + if (global_allowance == 0) + { + Uint32 p = group_allowance; + ret = processLimited(up,now,p); + group_allowance = p; + } + else if (global_allowance <= group_allowance) + { + Uint32 tmp = global_allowance; + ret = processLimited(up,now,tmp); + + Uint32 done = (global_allowance - tmp); + if (group_allowance < done) + group_allowance = 0; + else + group_allowance -= done; + + global_allowance = tmp; + } + else + { + Uint32 p = group_allowance; + ret = processLimited(up,now,p); + + Uint32 done = (group_allowance - p); + if (global_allowance < done) + global_allowance = 0; + else + global_allowance -= done; + + group_allowance = p; + } + + // if group allowance is used up, this group can no longer do anything + if (group_allowance == 0) + { + clear(); + return false; + } + else + return ret; + } + else if (global_allowance > 0) + { + return processLimited(up,now,global_allowance); + } + else + { + processUnlimited(up,now); + return false; + } + } + + + +} diff --git a/libktorrent/net/socketgroup.h b/libktorrent/net/socketgroup.h new file mode 100644 index 0000000..ba08029 --- /dev/null +++ b/libktorrent/net/socketgroup.h @@ -0,0 +1,90 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef NETSOCKETGROUP_H +#define NETSOCKETGROUP_H + +#include <list> +#include <util/constants.h> + +namespace net +{ + using bt::Uint32; + + class BufferedSocket; + + /** + @author Joris Guisson <joris.guisson@gmail.com> + */ + class SocketGroup + { + Uint32 limit; + std::list<BufferedSocket*> sockets; + bt::TimeStamp prev_run_time; + Uint32 group_allowance; + public: + SocketGroup(Uint32 limit); + virtual ~SocketGroup(); + + /// Clear the lists of sockets + void clear() {sockets.clear();} + + /// Add a socket for processing + void add(BufferedSocket* s) {sockets.push_back(s);} + + /** + Process all the sockets in the vector for download. + @param global_allowance How much the group can do, this will be updated, 0 means no limit + @param now Current time + @return true if we can download more data, false otherwise + */ + bool download(Uint32 & global_allowance,bt::TimeStamp now); + + /** + Process all the sockets in the vector for upload + @param global_allowance How much the group can do, this will be updated, 0 means no limit + @param now Current time + @return true if we can upload more data, false otherwise + */ + bool upload(Uint32 & global_allowance,bt::TimeStamp now); + + /** + * Set the group limit in bytes per sec + * @param lim The limit + */ + void setLimit(Uint32 lim) {limit = lim;} + + /// Get the number of sockets + Uint32 numSockets() const {return sockets.size();} + + /** + * Calculate the allowance for this group + * @param now Current timestamp + */ + void calcAllowance(bt::TimeStamp now); + private: + void processUnlimited(bool up,bt::TimeStamp now); + bool processLimited(bool up,bt::TimeStamp now,Uint32 & allowance); + bool process(bool up,bt::TimeStamp now,Uint32 & global_allowance); + }; + + +} + +#endif diff --git a/libktorrent/net/socketmonitor.cpp b/libktorrent/net/socketmonitor.cpp new file mode 100644 index 0000000..38225ab --- /dev/null +++ b/libktorrent/net/socketmonitor.cpp @@ -0,0 +1,173 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <math.h> +#include <unistd.h> +#include <util/functions.h> +#include <util/log.h> +#include <torrent/globals.h> +#include "socketmonitor.h" +#include "bufferedsocket.h" +#include "uploadthread.h" +#include "downloadthread.h" + +using namespace bt; + +namespace net +{ + SocketMonitor SocketMonitor::self; + + SocketMonitor::SocketMonitor() : ut(0),dt(0),next_group_id(1) + { + dt = new DownloadThread(this); + ut = new UploadThread(this); + } + + + SocketMonitor::~SocketMonitor() + { + if (ut && ut->isRunning()) + { + ut->stop(); + ut->signalDataReady(); // kick it in the nuts, if the thread is waiting for data + if (!ut->wait(250)) + { + ut->terminate(); + ut->wait(); + } + } + + + if (dt && dt->isRunning()) + { + dt->stop(); + if (!dt->wait(250)) + { + dt->terminate(); + dt->wait(); + } + } + + delete ut; + delete dt; + } + + void SocketMonitor::lock() + { + mutex.lock(); + } + + void SocketMonitor::unlock() + { + mutex.unlock(); + } + + void SocketMonitor::setDownloadCap(Uint32 bytes_per_sec) + { + DownloadThread::setCap(bytes_per_sec); + } + + void SocketMonitor::setUploadCap(Uint32 bytes_per_sec) + { + UploadThread::setCap(bytes_per_sec); + } + + void SocketMonitor::setSleepTime(Uint32 sleep_time) + { + DownloadThread::setSleepTime(sleep_time); + UploadThread::setSleepTime(sleep_time); + } + + void SocketMonitor::add(BufferedSocket* sock) + { + QMutexLocker lock(&mutex); + + bool start_threads = smap.count() == 0; + smap.append(sock); + + if (start_threads) + { + Out(SYS_CON|LOG_DEBUG) << "Starting socketmonitor threads" << endl; + + if (!dt->isRunning()) + dt->start(QThread::IdlePriority); + if (!ut->isRunning()) + ut->start(QThread::IdlePriority); + } + } + + void SocketMonitor::remove(BufferedSocket* sock) + { + QMutexLocker lock(&mutex); + if (smap.count() == 0) + return; + + smap.remove(sock); + if (smap.count() == 0) + { + Out(SYS_CON|LOG_DEBUG) << "Stopping socketmonitor threads" << endl; + if (dt && dt->isRunning()) + dt->stop(); + if (ut && ut->isRunning()) + { + ut->stop(); + ut->signalDataReady(); + } + } + } + + void SocketMonitor::signalPacketReady() + { + if (ut) + ut->signalDataReady(); + } + + Uint32 SocketMonitor::newGroup(GroupType type,Uint32 limit) + { + lock(); + Uint32 gid = next_group_id++; + if (type == UPLOAD_GROUP) + ut->addGroup(gid,limit); + else + dt->addGroup(gid,limit); + unlock(); + return gid; + } + + void SocketMonitor::setGroupLimit(GroupType type,Uint32 gid,Uint32 limit) + { + lock(); + if (type == UPLOAD_GROUP) + ut->setGroupLimit(gid,limit); + else + dt->setGroupLimit(gid,limit); + unlock(); + } + + void SocketMonitor::removeGroup(GroupType type,Uint32 gid) + { + lock(); + if (type == UPLOAD_GROUP) + ut->removeGroup(gid); + else + dt->removeGroup(gid); + unlock(); + } + +} diff --git a/libktorrent/net/socketmonitor.h b/libktorrent/net/socketmonitor.h new file mode 100644 index 0000000..79e4a2e --- /dev/null +++ b/libktorrent/net/socketmonitor.h @@ -0,0 +1,118 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef NETSOCKETMONITOR_H +#define NETSOCKETMONITOR_H + + +#include <qmutex.h> +#include <qptrlist.h> +#include <util/constants.h> + + +namespace net +{ + using bt::Uint32; + + class BufferedSocket; + class UploadThread; + class DownloadThread; + + /** + * @author Joris Guisson <joris.guisson@gmail.com> + * + * Monitors all sockets for upload and download traffic. + * It uses two threads to do this. + */ + class SocketMonitor + { + static SocketMonitor self; + + QMutex mutex; + UploadThread* ut; + DownloadThread* dt; + QPtrList<BufferedSocket> smap; + Uint32 next_group_id; + + SocketMonitor(); + public: + virtual ~SocketMonitor(); + + /// Add a new socket, will start the threads if necessary + void add(BufferedSocket* sock); + + /// Remove a socket, will stop threads if no more sockets are left + void remove(BufferedSocket* sock); + + enum GroupType + { + UPLOAD_GROUP, + DOWNLOAD_GROUP + }; + + + /** + * Creata a new upload or download group + * @param type Wether it is an upload or download group + * @param limit Limit of group in bytes/s + * @return The group ID + */ + Uint32 newGroup(GroupType type,Uint32 limit); + + /** + * Change the group limit + * @param type The group type + * @param gid The group id + * @param limit The limit + */ + void setGroupLimit(GroupType type,Uint32 gid,Uint32 limit); + + /** + * Remove a group + * @param type The group type + * @param gid The group id + */ + void removeGroup(GroupType type,Uint32 gid); + + typedef QPtrList<BufferedSocket>::iterator Itr; + + /// Get the begin of the list of sockets + Itr begin() {return smap.begin();} + + /// Get the end of the list of sockets + Itr end() {return smap.end();} + + /// lock the monitor + void lock(); + + /// unlock the monitor + void unlock(); + + /// Tell upload thread a packet is ready + void signalPacketReady(); + + static void setDownloadCap(Uint32 bytes_per_sec); + static void setUploadCap(Uint32 bytes_per_sec); + static void setSleepTime(Uint32 sleep_time); + static SocketMonitor & instance() {return self;} + }; + +} + +#endif diff --git a/libktorrent/net/speed.cpp b/libktorrent/net/speed.cpp new file mode 100644 index 0000000..aa57513 --- /dev/null +++ b/libktorrent/net/speed.cpp @@ -0,0 +1,78 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <util/log.h> +#include <util/timer.h> +#include <util/functions.h> +#include "speed.h" + +using namespace bt; + +namespace net +{ + const Uint64 SPEED_INTERVAL = 5000; + + Speed::Speed() : rate(0),bytes(0) + {} + + + Speed::~Speed() + {} + + void Speed::onData(Uint32 b,bt::TimeStamp ts) + { + dlrate.append(qMakePair(b,ts)); + bytes += b; + } + + void Speed::update(bt::TimeStamp now) + { + QValueList<QPair<Uint32,TimeStamp> >::iterator i = dlrate.begin(); + while (i != dlrate.end()) + { + QPair<Uint32,TimeStamp> & p = *i; + if (now - p.second > SPEED_INTERVAL || now < p.second) + { + if (bytes >= p.first) // make sure we don't wrap around + bytes -= p.first; // subtract bytes + else + bytes = 0; + i = dlrate.erase(i); + } + else + { + // seeing that newer entries are appended, they are in the list chronologically + // so once we hit an entry which is in the interval, we can just break out of the loop + // because all following entries will be in the interval + break; + } + } + + if (bytes == 0) + { + rate = 0; + } + else + { + // Out() << "bytes = " << bytes << " d = " << d << endl; + rate = (float) bytes / (float)(SPEED_INTERVAL * 0.001); + } + } + +} diff --git a/libktorrent/net/speed.h b/libktorrent/net/speed.h new file mode 100644 index 0000000..d5825e9 --- /dev/null +++ b/libktorrent/net/speed.h @@ -0,0 +1,51 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef NETSPEED_H +#define NETSPEED_H + +#include <qpair.h> +#include <qvaluelist.h> +#include <util/constants.h> + +namespace net +{ + + /** + @author Joris Guisson <joris.guisson@gmail.com> + + Measures the download and upload speed. + */ + class Speed + { + float rate; + bt::Uint32 bytes; + QValueList<QPair<bt::Uint32,bt::TimeStamp> > dlrate; + public: + Speed(); + virtual ~Speed(); + + void onData(bt::Uint32 bytes,bt::TimeStamp ts); + void update(bt::TimeStamp now); + float getRate() const {return rate;} + }; + +} + +#endif diff --git a/libktorrent/net/uploadthread.cpp b/libktorrent/net/uploadthread.cpp new file mode 100644 index 0000000..0023cf6 --- /dev/null +++ b/libktorrent/net/uploadthread.cpp @@ -0,0 +1,91 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <math.h> +#include <util/functions.h> +#include "uploadthread.h" +#include "socketmonitor.h" +#include "bufferedsocket.h" +#include "socketgroup.h" + +using namespace bt; + +namespace net +{ + Uint32 UploadThread::ucap = 0; + Uint32 UploadThread::sleep_time = 3; + + UploadThread::UploadThread(SocketMonitor* sm) : NetworkThread(sm) + {} + + + UploadThread::~UploadThread() + {} + + + void UploadThread::update() + { + sm->lock(); + bt::TimeStamp now = bt::Now(); + + Uint32 num_ready = 0; + // loop over all sockets and see which ones have data ready + SocketMonitor::Itr itr = sm->begin(); + while (itr != sm->end()) + { + BufferedSocket* s = *itr; + if (s && s->ok() && s->bytesReadyToWrite()) + { + SocketGroup* g = groups.find(s->uploadGroupID()); + if (!g) + g = groups.find(0); + + g->add(s); + num_ready++; + } + itr++; + } + + if (num_ready > 0) + doGroups(num_ready,now,ucap); + prev_run_time = now; + sm->unlock(); + + if (num_ready == 0) // nobody was ready so go to sleep + data_ready.wait(); + else + msleep(sleep_time); + } + + void UploadThread::signalDataReady() + { + data_ready.wakeOne(); + } + + void UploadThread::setSleepTime(Uint32 stime) + { + if (stime >= 1 && stime <= 10) + sleep_time = stime; + } + + bool UploadThread::doGroup(SocketGroup* g,Uint32 & allowance,bt::TimeStamp now) + { + return g->upload(allowance,now); + } +} diff --git a/libktorrent/net/uploadthread.h b/libktorrent/net/uploadthread.h new file mode 100644 index 0000000..265abac --- /dev/null +++ b/libktorrent/net/uploadthread.h @@ -0,0 +1,61 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef NETUPLOADTHREAD_H +#define NETUPLOADTHREAD_H + + + +#include <qwaitcondition.h> +#include "networkthread.h" + +namespace net +{ + class SocketMonitor; + class BufferedSocket; + + /** + @author Joris Guisson <joris.guisson@gmail.com> + */ + class UploadThread : public NetworkThread + { + static bt::Uint32 ucap; + static bt::Uint32 sleep_time; + + QWaitCondition data_ready; + public: + UploadThread(SocketMonitor* sm); + virtual ~UploadThread(); + + /// Wake up thread, data is ready to be sent + void signalDataReady(); + + /// Set the upload cap + static void setCap(bt::Uint32 uc) {ucap = uc;} + + /// Set the sleep time when using upload caps + static void setSleepTime(bt::Uint32 stime); + private: + virtual void update(); + virtual bool doGroup(SocketGroup* g,Uint32 & allowance,bt::TimeStamp now); + }; + +} + +#endif diff --git a/libktorrent/pluginmanager.cpp b/libktorrent/pluginmanager.cpp new file mode 100644 index 0000000..db9e0a3 --- /dev/null +++ b/libktorrent/pluginmanager.cpp @@ -0,0 +1,312 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <qfile.h> +#include <qtextstream.h> +#include <kparts/componentfactory.h> +#include <util/log.h> +#include <util/error.h> +#include <util/fileops.h> +#include <util/waitjob.h> +#include <torrent/globals.h> +#include <interfaces/guiinterface.h> +#include "pluginmanager.h" +#include "pluginmanagerprefpage.h" + +using namespace bt; + +namespace kt +{ + + PluginManager::PluginManager(CoreInterface* core,GUIInterface* gui) : core(core),gui(gui) + { + unloaded.setAutoDelete(false); + plugins.setAutoDelete(false); + prefpage = 0; + pltoload.append("Info Widget"); + pltoload.append("Search"); + } + + PluginManager::~PluginManager() + { + delete prefpage; + unloaded.setAutoDelete(true); + plugins.setAutoDelete(true); + } + + void PluginManager::loadPluginList() + { + KTrader::OfferList offers = KTrader::self()->query("KTorrent/Plugin"); + + KTrader::OfferList::ConstIterator iter; + for(iter = offers.begin(); iter != offers.end(); ++iter) + { + KService::Ptr service = *iter; + int errCode = 0; + Plugin* plugin = + KParts::ComponentFactory::createInstanceFromService<kt::Plugin> + (service, 0, 0, QStringList(),&errCode); + + if (!plugin) + continue; + + + if (!plugin->versionCheck(kt::VERSION_STRING)) + { + Out(SYS_GEN|LOG_NOTICE) << + QString("Plugin %1 version does not match KTorrent version, unloading it.") + .arg(service->library()) << endl; + + delete plugin; + // unload the library again, no need to have it loaded + KLibLoader::self()->unloadLibrary(service->library().local8Bit()); + continue; + } + + unloaded.insert(plugin->getName(),plugin); + if (pltoload.contains(plugin->getName())) + load(plugin->getName()); + } + + if (!prefpage) + { + prefpage = new PluginManagerPrefPage(this); + gui->addPrefPage(prefpage); + } + prefpage->updatePluginList(); + } + + + void PluginManager::load(const QString & name) + { + Plugin* p = unloaded.find(name); + if (!p) + return; + + Out(SYS_GEN|LOG_NOTICE) << "Loading plugin "<< p->getName() << endl; + p->setCore(core); + p->setGUI(gui); + p->load(); + gui->mergePluginGui(p); + unloaded.erase(name); + plugins.insert(p->getName(),p); + p->loaded = true; + + if (!cfg_file.isNull()) + saveConfigFile(cfg_file); + } + + void PluginManager::unload(const QString & name) + { + Plugin* p = plugins.find(name); + if (!p) + return; + + // first shut it down properly + bt::WaitJob* wjob = new WaitJob(2000); + try + { + p->shutdown(wjob); + if (wjob->needToWait()) + bt::WaitJob::execute(wjob); + else + delete wjob; + } + catch (Error & err) + { + Out(SYS_GEN|LOG_NOTICE) << "Error when unloading plugin: " << err.toString() << endl; + } + + + gui->removePluginGui(p); + p->unload(); + plugins.erase(name); + unloaded.insert(p->getName(),p); + p->loaded = false; + + if (!cfg_file.isNull()) + saveConfigFile(cfg_file); + } + + void PluginManager::loadAll() + { + bt::PtrMap<QString,Plugin>::iterator i = unloaded.begin(); + while (i != unloaded.end()) + { + Plugin* p = i->second; + p->setCore(core); + p->setGUI(gui); + p->load(); + gui->mergePluginGui(p); + plugins.insert(p->getName(),p); + p->loaded = true; + i++; + } + unloaded.clear(); + if (!cfg_file.isNull()) + saveConfigFile(cfg_file); + } + + void PluginManager::unloadAll(bool save) + { + // first properly shutdown all plugins + bt::WaitJob* wjob = new WaitJob(2000); + try + { + bt::PtrMap<QString,Plugin>::iterator i = plugins.begin(); + while (i != plugins.end()) + { + Plugin* p = i->second; + p->shutdown(wjob); + i++; + } + + if (wjob->needToWait()) + bt::WaitJob::execute(wjob); + else + delete wjob; + } + catch (Error & err) + { + Out(SYS_GEN|LOG_NOTICE) << "Error when unloading all plugins: " << err.toString() << endl; + } + + // then unload them + bt::PtrMap<QString,Plugin>::iterator i = plugins.begin(); + while (i != plugins.end()) + { + Plugin* p = i->second; + gui->removePluginGui(p); + p->unload(); + unloaded.insert(p->getName(),p); + p->loaded = false; + i++; + } + plugins.clear(); + if (save && !cfg_file.isNull()) + saveConfigFile(cfg_file); + } + + void PluginManager::updateGuiPlugins() + { + bt::PtrMap<QString,Plugin>::iterator i = plugins.begin(); + while (i != plugins.end()) + { + Plugin* p = i->second; + p->guiUpdate(); + i++; + } + } + + void PluginManager::fillPluginList(QPtrList<Plugin> & plist) + { + bt::PtrMap<QString,Plugin>::iterator i = plugins.begin(); + while (i != plugins.end()) + { + Plugin* p = i->second; + plist.append(p); + i++; + } + + + i = unloaded.begin(); + while (i != unloaded.end()) + { + Plugin* p = i->second; + plist.append(p); + i++; + } + } + + bool PluginManager::isLoaded(const QString & name) const + { + const Plugin* p = plugins.find(name); + return p != 0; + } + + void PluginManager::loadConfigFile(const QString & file) + { + cfg_file = file; + // make a default config file if doesn't exist + if (!bt::Exists(file)) + { + writeDefaultConfigFile(file); + return; + } + + QFile f(file); + if (!f.open(IO_ReadOnly)) + { + Out(SYS_GEN|LOG_DEBUG) << "Cannot open file " << file << " : " << f.errorString() << endl; + return; + } + + pltoload.clear(); + + QTextStream in(&f); + while (!in.atEnd()) + { + QString l = in.readLine(); + if (l.isNull()) + break; + + pltoload.append(l); + } + } + + void PluginManager::saveConfigFile(const QString & file) + { + cfg_file = file; + QFile f(file); + if (!f.open(IO_WriteOnly)) + { + Out(SYS_GEN|LOG_DEBUG) << "Cannot open file " << file << " : " << f.errorString() << endl; + return; + } + + QTextStream out(&f); + bt::PtrMap<QString,Plugin>::iterator i = plugins.begin(); + while (i != plugins.end()) + { + Plugin* p = i->second; + out << p->getName() << endl; + i++; + } + } + + + void PluginManager::writeDefaultConfigFile(const QString & file) + { + // by default we will load the infowidget and searchplugin + QFile f(file); + if (!f.open(IO_WriteOnly)) + { + Out(SYS_GEN|LOG_DEBUG) << "Cannot open file " << file << " : " << f.errorString() << endl; + return; + } + + QTextStream out(&f); + + out << "Info Widget" << endl << "Search" << endl; + + pltoload.clear(); + pltoload.append("Info Widget"); + pltoload.append("Search"); + } +} diff --git a/libktorrent/pluginmanager.h b/libktorrent/pluginmanager.h new file mode 100644 index 0000000..611ec66 --- /dev/null +++ b/libktorrent/pluginmanager.h @@ -0,0 +1,118 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef KTPLUGINMANAGER_H +#define KTPLUGINMANAGER_H + +#include <qptrlist.h> +#include <util/ptrmap.h> +#include <interfaces/plugin.h> +#include <qstringlist.h> + + +namespace kt +{ + class CoreInterface; + class GUIInterface; + class PluginManagerPrefPage; + + /** + * @author Joris Guisson + * @brief Class to manage plugins + * + * This class manages all plugins. Plugins are stored in a map + */ + class PluginManager + { + bt::PtrMap<QString,Plugin> plugins,unloaded; + CoreInterface* core; + GUIInterface* gui; + PluginManagerPrefPage* prefpage; + QStringList pltoload; + QString cfg_file; + public: + PluginManager(CoreInterface* core,GUIInterface* gui); + virtual ~PluginManager(); + + /** + * Load the list of plugins. + * This basically uses KTrader to get a list of available plugins, and + * loads those, but does not initialize them. We will consider a plugin loaded + * when it's load method is called. + */ + void loadPluginList(); + + /** + * Loads which plugins need to be loaded from a file. + * @param file The file + */ + void loadConfigFile(const QString & file); + + /** + * Saves which plugins are loaded to a file. + * @param file The file + */ + void saveConfigFile(const QString & file); + + /** + * Fill a list with all available plugins. + * @param pllist The plugin list + */ + void fillPluginList(QPtrList<Plugin> & plist); + + /** + * Is a plugin loaded + * @param name Naame of plugin. + * @return True if it is, false if it isn't + */ + bool isLoaded(const QString & name) const; + + /** + * Load a plugin. + * @param name Name of the plugin + */ + void load(const QString & name); + + /** + * Unload a plugin. + * @param name Name of the plugin + */ + void unload(const QString & name); + + /** + * Load all unloaded plugins. + */ + void loadAll(); + + /** + * Unload all loaded plugins. + */ + void unloadAll(bool save = true); + + /** + * Update all plugins who need a periodical GUI update. + */ + void updateGuiPlugins(); + private: + void writeDefaultConfigFile(const QString & file); + }; + +} + +#endif diff --git a/libktorrent/pluginmanagerprefpage.cpp b/libktorrent/pluginmanagerprefpage.cpp new file mode 100644 index 0000000..8151be4 --- /dev/null +++ b/libktorrent/pluginmanagerprefpage.cpp @@ -0,0 +1,213 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <klocale.h> +#include <kpushbutton.h> +#include <klistview.h> +#include <qheader.h> +#include <kglobal.h> +#include <kiconloader.h> +#include <util/constants.h> +#include "pluginmanager.h" +#include "pluginmanagerwidget.h" +#include "pluginmanagerprefpage.h" +#include "labelview.h" + +using namespace bt; + +namespace kt +{ + + class PluginViewItem : public LabelViewItem + { + Plugin* p; + public: + PluginViewItem(Plugin* p,LabelView* parent) + : LabelViewItem(p->getIcon(),p->getGuiName(),p->getDescription(),parent),p(p) + { + update(); + } + + virtual ~PluginViewItem() + {} + + virtual void update() + { + setTitle("<h3>" + p->getGuiName() + "</h3>"); + setDescription( + i18n("%1<br>Status: <b>%2</b><br>Author: %3").arg(p->getDescription()) + .arg(p->isLoaded() ? i18n("Loaded") : i18n("Not loaded")) + .arg(p->getAuthor())); + } + + QString pluginName() {return p->getName();} + }; + + PluginManagerPrefPage::PluginManagerPrefPage(PluginManager* pman) + : PrefPageInterface(i18n("Plugins"), i18n("Plugin Options"),KGlobal::iconLoader()->loadIcon("ktplugins",KIcon::NoGroup)),pman(pman) + { + pmw = 0; + } + + + PluginManagerPrefPage::~PluginManagerPrefPage() + {} + + bool PluginManagerPrefPage::apply() + { + return true; + } + + void PluginManagerPrefPage::createWidget(QWidget* parent) + { + pmw = new PluginManagerWidget(parent); + + connect(pmw->load_btn,SIGNAL(clicked()),this,SLOT(onLoad())); + connect(pmw->unload_btn,SIGNAL(clicked()),this,SLOT(onUnload())); + connect(pmw->load_all_btn,SIGNAL(clicked()),this,SLOT(onLoadAll())); + connect(pmw->unload_all_btn,SIGNAL(clicked()),this,SLOT(onUnloadAll())); + LabelView* lv = pmw->plugin_view; + connect(lv,SIGNAL(currentChanged(LabelViewItem * )),this,SLOT(onCurrentChanged( LabelViewItem* ))); + } + + void PluginManagerPrefPage::updatePluginList() + { + LabelView* lv = pmw->plugin_view; + lv->clear(); + // get list of plugins + QPtrList<Plugin> pl; + pman->fillPluginList(pl); + + // Add them all + QPtrList<Plugin>::iterator i = pl.begin(); + while (i != pl.end()) + { + Plugin* p = *i; + lv->addItem(new PluginViewItem(p,lv)); + i++; + } + lv->sort(); + } + + void PluginManagerPrefPage::updateData() + { + updateAllButtons(); + } + + + + void PluginManagerPrefPage::deleteWidget() + { + delete pmw; + pmw = 0; + } + + void PluginManagerPrefPage::onCurrentChanged(LabelViewItem* item) + { + PluginViewItem* pvi = (PluginViewItem*)item; + if (!item) + { + pmw->load_btn->setEnabled(false); + pmw->unload_btn->setEnabled(false); + } + else + { + bool loaded = pman->isLoaded(pvi->pluginName()); + pmw->load_btn->setEnabled(!loaded); + pmw->unload_btn->setEnabled(loaded); + } + } + + void PluginManagerPrefPage::updateAllButtons() + { + Uint32 tot = 0; + Uint32 loaded = 0; + // get list of plugins + QPtrList<Plugin> pl; + pman->fillPluginList(pl); + + QPtrList<Plugin>::iterator i = pl.begin(); + while (i != pl.end()) + { + Plugin* p = *i; + tot++; + if (p->isLoaded()) + loaded++; + i++; + } + + if (loaded == tot) + { + pmw->load_all_btn->setEnabled(false); + pmw->unload_all_btn->setEnabled(true); + } + else if (loaded < tot && loaded > 0) + { + pmw->unload_all_btn->setEnabled(true); + pmw->load_all_btn->setEnabled(true); + } + else + { + pmw->unload_all_btn->setEnabled(false); + pmw->load_all_btn->setEnabled(true); + } + onCurrentChanged(pmw->plugin_view->selectedItem()); + } + + void PluginManagerPrefPage::onLoad() + { + LabelView* lv = pmw->plugin_view; + PluginViewItem* vi = (PluginViewItem*)lv->selectedItem(); + if (vi && !pman->isLoaded(vi->pluginName())) + { + pman->load(vi->pluginName()); + vi->update(); + updateAllButtons(); + } + } + + void PluginManagerPrefPage::onUnload() + { + LabelView* lv = pmw->plugin_view; + PluginViewItem* vi = (PluginViewItem*)lv->selectedItem(); + if (vi && pman->isLoaded(vi->pluginName())) + { + pman->unload(vi->pluginName()); + vi->update(); + updateAllButtons(); + } + } + + void PluginManagerPrefPage::onLoadAll() + { + pman->loadAll(); + LabelView* lv = pmw->plugin_view; + lv->update(); + updateAllButtons(); + } + + void PluginManagerPrefPage::onUnloadAll() + { + pman->unloadAll(); + LabelView* lv = pmw->plugin_view; + lv->update(); + updateAllButtons(); + } + +} diff --git a/libktorrent/pluginmanagerprefpage.h b/libktorrent/pluginmanagerprefpage.h new file mode 100644 index 0000000..47df97f --- /dev/null +++ b/libktorrent/pluginmanagerprefpage.h @@ -0,0 +1,70 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef KTPLUGINMANAGERPREFPAGE_H +#define KTPLUGINMANAGERPREFPAGE_H + +#include <qobject.h> +#include <interfaces/prefpageinterface.h> + +class QListViewItem; +class PluginManagerWidget; + +namespace kt +{ + class PluginManager; + class LabelViewItem; + + /** + * @author Joris Guisson + * + * Pref page which allows to load and unload plugins. + */ + class PluginManagerPrefPage : public QObject,public PrefPageInterface + { + Q_OBJECT + public: + PluginManagerPrefPage(PluginManager* pman); + virtual ~PluginManagerPrefPage(); + + virtual bool apply(); + virtual void createWidget(QWidget* parent); + virtual void updateData(); + virtual void deleteWidget(); + + void updatePluginList(); + + private slots: + void onLoad(); + void onUnload(); + void onLoadAll(); + void onUnloadAll(); + void onCurrentChanged(LabelViewItem* item); + + private: + void updateAllButtons(); + + private: + PluginManager* pman; + PluginManagerWidget* pmw; + }; + +} + +#endif diff --git a/libktorrent/pluginmanagerwidget.ui b/libktorrent/pluginmanagerwidget.ui new file mode 100644 index 0000000..f83efe0 --- /dev/null +++ b/libktorrent/pluginmanagerwidget.ui @@ -0,0 +1,127 @@ +<!DOCTYPE UI><UI version="3.3" stdsetdef="1"> +<class>PluginManagerWidget</class> +<widget class="QWidget"> + <property name="name"> + <cstring>PluginManagerWidget</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>600</width> + <height>320</height> + </rect> + </property> + <property name="caption"> + <string>Plugin Manager</string> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="kt::LabelView"> + <property name="name"> + <cstring>plugin_view</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>7</hsizetype> + <vsizetype>5</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout1</cstring> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="KPushButton"> + <property name="name"> + <cstring>load_btn</cstring> + </property> + <property name="text"> + <string>Load</string> + </property> + </widget> + <widget class="KPushButton"> + <property name="name"> + <cstring>unload_btn</cstring> + </property> + <property name="text"> + <string>U&nload</string> + </property> + </widget> + <widget class="KPushButton"> + <property name="name"> + <cstring>load_all_btn</cstring> + </property> + <property name="text"> + <string>Load &All</string> + </property> + </widget> + <widget class="KPushButton"> + <property name="name"> + <cstring>unload_all_btn</cstring> + </property> + <property name="text"> + <string>&Unload All</string> + </property> + </widget> + <spacer> + <property name="name"> + <cstring>spacer1</cstring> + </property> + <property name="orientation"> + <enum>Vertical</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>20</width> + <height>31</height> + </size> + </property> + </spacer> + </vbox> + </widget> + </hbox> +</widget> +<customwidgets> + <customwidget> + <class>kt::LabelView</class> + <header location="global">labelview.h</header> + <sizehint> + <width>-1</width> + <height>-1</height> + </sizehint> + <container>0</container> + <sizepolicy> + <hordata>5</hordata> + <verdata>5</verdata> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + <pixmap>image0</pixmap> + </customwidget> +</customwidgets> +<images> + <image name="image0"> + <data format="PNG" length="1125">89504e470d0a1a0a0000000d4948445200000016000000160806000000c4b46c3b0000042c49444154388db5954f6c14551cc73fefcd7476b65bdaae4bb78bb5502a14d404e4801c88182d1c4c2c693da847400f9c24c68b878684238660e2b1e01f12c19493012ef2478c814412d354a46017a8a564bb6da5bbedccee767776e63d0ffb073751d483bfe49799974c3eeffb7ebf37df9fd05a530b2184040cc0042420aaf9a4d0d554800f045a6b256ae0e1e1e1d6bebebe838ee31c48a7d39b5cd7fd075e251cc7617272f2ded8d8d819cff33e0316819259537aead4a9839d5dd6d1784f91f55b0a94830242088404d304292bef68a89f520802a598fecddaa04f1a876f5c250c7c0a64cdeac686e33807e23d45e6b297c8b877f1831542614550b6599835c83c2a81b6786a75134faf2f1169f12997350881d9021d0903e06de0745d3160a6d3e94dbd5b0a64dcbb94b5831d0e3375ab892b1772dcf9790528543f8dd0d367b36768153b5e31503a0f1aecb004580b44ffac58baae8b1714f0833c7638cc8dab303a320f4822ab4c7a37c69196203de3319d5ce1c4d13c733331dedc67a129a154fd128401ab0616d55a130ac3d42d93d1913940d13fd0c9ee0183685c60da01c5421bd72f7a8c8efccef9afd374267ad93d642365be0636a0d28ec7600941d9e6f23917f0e97f23ce5bef35d19ec863da0ed9059b2be70bec196c66dfa10ec0e49b338f7017258651bf95021035c595429bb0903248fe52a2b5b595dd7b4d945cc2340cdca536be389ee3f67886c5798f773fe8e0dac508c989659277a2180da4ca4ff07821058b8b251445d63d6b13ed1098a6417e39cac85197dbe31962ab9bd9f1f22a226d45366f6d0620fdb08c900d281af6110284b20085b414861d905d88f2e52739ee8cbb8022143259d3dd84691730aa2d52da441a8de0c6958068870022a41e9629ad3473fd3b8fdbe319dadb9b4924da994d2d716c7896fbe35152f78b48245d6b2da4507faf582be8eaf159b721cc837b05ae7debb1f79d08cb8b515edad942a22bc4b1c33eb3d34b1c797f06af90a72d16e2f96d9a74aa11dca8586b222d01af0fb60070f6c402d72f15d97f28c6f6d7027a5f5ce6c3233dc4e2ede496b278be4fff608cee8d3e1add806aeca51094cbb06397c1ecc328e746537c7e3ccdb5cb1136bf60635882d4d41c6ec6836ab37efa214f72208ed9f4d7cdd38ee310280542e38b1c43fb6de26b3672e1ec3cc99bcb246f66a938a3241ab3e91f7c861fbf77710b1e5e49915bae974203ba0e9e9c9cbc373d6d6d305a040a89c2a77f50b27d5782bbbf7acccf28349235dd16cf6dd374f7295e1de8a45c02d37499182b01cc0201a085d61a2144d8b2ac8fb6ed340e77240c4261890e04c250185262546d534a032154b59e0ad394e41c98182bf268ce6721ed9f064e0253356f6da2e24c1f030f783c15fe6da680af8021602bd051532ca9b8521488559f61aa86c29343578fbf0264a94c906c7d3409214c20043457a116ff6de6795578012889ff6b98fe016ea0ce1c6a2573410000000049454e44ae426082</data> + </image> +</images> +<layoutdefaults spacing="6" margin="11"/> +<includehints> + <includehint>labelview.h</includehint> + <includehint>kpushbutton.h</includehint> + <includehint>kpushbutton.h</includehint> + <includehint>kpushbutton.h</includehint> + <includehint>kpushbutton.h</includehint> +</includehints> +</UI> diff --git a/libktorrent/settings.kcfgc b/libktorrent/settings.kcfgc new file mode 100644 index 0000000..f5a6064 --- /dev/null +++ b/libktorrent/settings.kcfgc @@ -0,0 +1,6 @@ +# Code generation options for kconfig_compiler +File=ktorrent.kcfg +ClassName=Settings +Singleton=true +Mutators=true +# will create the necessary code for setting those variables diff --git a/libktorrent/torrent/Makefile.am b/libktorrent/torrent/Makefile.am new file mode 100644 index 0000000..d546228 --- /dev/null +++ b/libktorrent/torrent/Makefile.am @@ -0,0 +1,33 @@ +INCLUDES = -I$(top_builddir)/ktorrent/libktorrent -I$(top_builddir)/libktorrent \ + -I$(srcdir)/.. $(all_includes) + +METASOURCES = AUTO + +noinst_LTLIBRARIES = libtorrent.la +libtorrent_la_LDFLAGS = $(all_libraries) +noinst_HEADERS = advancedchokealgorithm.h announcelist.h authenticate.h \ + authenticatebase.h authenticationmonitor.h bdecoder.h bencoder.h bnode.h cache.h \ + cachefile.h cap.h choker.h chunk.h chunkcounter.h chunkdownload.h chunkmanager.h \ + chunkselector.h dndfile.h downloadcap.h downloader.h globals.h httptracker.h \ + ipblocklist.h movedatafilesjob.h multifilecache.h newchokealgorithm.h \ + oldchokealgorithm.h packet.h packetreader.h packetwriter.h peer.h peerdownloader.h peerid.h \ + peermanager.h peersourcemanager.h peeruploader.h piece.h preallocationthread.h \ + queuemanager.h request.h server.h serverauthenticate.h singlefilecache.h \ + speedestimater.h statsfile.h timeestimator.h torrent.h torrentcontrol.h torrentcreator.h \ + torrentfile.h tracker.h udptracker.h udptrackersocket.h uploadcap.h uploader.h \ + upspeedestimater.h utpex.h value.h + +libtorrent_la_SOURCES = advancedchokealgorithm.cpp announcelist.cpp \ + authenticate.cpp authenticatebase.cpp authenticationmonitor.cpp bdecoder.cpp \ + bencoder.cpp bnode.cpp cache.cpp cachefile.cpp cap.cpp choker.cpp chunk.cpp \ + chunkcounter.cpp chunkdownload.cpp chunkmanager.cpp chunkselector.cpp dndfile.cpp \ + downloadcap.cpp downloader.cpp globals.cpp httptracker.cpp ipblocklist.cpp \ + movedatafilesjob.cpp multifilecache.cpp newchokealgorithm.cpp packet.cpp packetreader.cpp \ + packetwriter.cpp peer.cpp peerdownloader.cpp peerid.cpp peermanager.cpp \ + peersourcemanager.cpp peeruploader.cpp piece.cpp preallocationthread.cpp queuemanager.cpp \ + request.cpp server.cpp serverauthenticate.cpp singlefilecache.cpp \ + speedestimater.cpp statsfile.cpp timeestimator.cpp torrent.cpp torrentcontrol.cpp \ + torrentcreator.cpp torrentfile.cpp tracker.cpp udptracker.cpp udptrackersocket.cpp \ + uploadcap.cpp uploader.cpp upspeedestimater.cpp utpex.cpp value.cpp + +KDE_CXXFLAGS = $(USE_EXCEPTIONS) $(USE_RTTI) diff --git a/libktorrent/torrent/advancedchokealgorithm.cpp b/libktorrent/torrent/advancedchokealgorithm.cpp new file mode 100644 index 0000000..7ca0578 --- /dev/null +++ b/libktorrent/torrent/advancedchokealgorithm.cpp @@ -0,0 +1,259 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <stdlib.h> +#include <util/functions.h> +#include <interfaces/torrentinterface.h> +#include "chunkmanager.h" +#include "peer.h" +#include "peermanager.h" +#include "packetwriter.h" +#include "advancedchokealgorithm.h" + +using namespace kt; + +namespace bt +{ + + + const Uint32 OPT_SEL_INTERVAL = 30*1000; // we switch optimistic peer each 30 seconds + const double NEWBIE_BONUS = 1.0; + const double SNUB_PENALTY = 10.0; + const double ONE_MB = 1024*1024; + + + AdvancedChokeAlgorithm::AdvancedChokeAlgorithm() + : ChokeAlgorithm() + { + last_opt_sel_time = 0; + } + + + AdvancedChokeAlgorithm::~AdvancedChokeAlgorithm() + {} + + bool AdvancedChokeAlgorithm::calcACAScore(Peer* p,ChunkManager & cman,const kt::TorrentStats & stats) + { + const PeerInterface::Stats & s = p->getStats(); + if (p->isSeeder()) + { + /* + double bd = 0; + if (stats.trk_bytes_downloaded > 0) + bd = s.bytes_downloaded / stats.trk_bytes_downloaded; + double ds = 0; + if (stats.download_rate > 0) + ds = s.download_rate/ stats.download_rate; + p->setACAScore(5*bd + 5*ds); + */ + p->setACAScore(0.0); + return false; + } + + bool should_be_interested = false; + bool should_we_be_interested = false; + // before we start calculating first check if we have piece that the peer doesn't have + const BitSet & ours = cman.getBitSet(); + const BitSet & theirs = p->getBitSet(); + for (Uint32 i = 0;i < ours.getNumBits();i++) + { + if (ours.get(i) && !theirs.get(i)) + { + should_be_interested = true; + break; + } + } + + if (!should_be_interested || !p->isInterested()) + { + // not interseted so it doesn't make sense to unchoke it + p->setACAScore(-50.0); + return false; + } + + + + double nb = 0.0; // newbie bonus + double cp = 0.0; // choke penalty + double sp = 0.0; // snubbing penalty + double lb = s.local ? 10.0 : 0.0; // local peers get a bonus of 10 + double bd = s.bytes_downloaded; // bytes downloaded + double tbd = stats.trk_bytes_downloaded; // total bytes downloaded + double ds = s.download_rate; // current download rate + double tds = stats.download_rate; // total download speed + + // if the peer has less than 1 MB or 0.5 % of the torrent it is a newbie + if (p->percentAvailable() < 0.5 && stats.total_bytes * p->percentAvailable() < 1024*1024) + { + nb = NEWBIE_BONUS; + } + + if (p->isChoked()) + { + cp = NEWBIE_BONUS; // cp cancels out newbie bonus + } + + // if the evil bit is on (!choked, snubbed and requests have timed out) + if (s.evil) + { + sp = SNUB_PENALTY; + } + + // NB + K * (BD/TBD) - CP - SP + L * (DS / TDS) + double K = 5.0; + double L = 5.0; + double aca = lb + nb + (tbd > 0 ? K * (bd/tbd) : 0.0) + (tds > 0 ? L* (ds / tds) : 0.0) - cp - sp; + + p->setACAScore(aca); + return true; + } + + static int ACACmp(Peer* a,Peer* b) + { + if (a->getStats().aca_score < b->getStats().aca_score) + return 1; + else if (a->getStats().aca_score > b->getStats().aca_score) + return -1; + else + return 0; + } + + + void AdvancedChokeAlgorithm::doChokingLeechingState(PeerManager & pman,ChunkManager & cman,const kt::TorrentStats & stats) + { + PeerPtrList ppl; + Uint32 np = pman.getNumConnectedPeers(); + // add all non seeders + for (Uint32 i = 0;i < np;i++) + { + Peer* p = pman.getPeer(i); + if (p) + { + if (calcACAScore(p,cman,stats)) + ppl.append(p); + else + // choke seeders they do not want to download from us anyway + p->choke(); + } + } + + // sort list by ACA score + ppl.setCompareFunc(ACACmp); + ppl.sort(); + + doUnchoking(ppl,updateOptimisticPeer(pman,ppl)); + } + + void AdvancedChokeAlgorithm::doUnchoking(PeerPtrList & ppl,Peer* poup) + { + // Get the number of upload slots + Uint32 num_slots = Choker::getNumUploadSlots(); + // Do the choking and unchoking + Uint32 num_unchoked = 0; + for (Uint32 i = 0;i < ppl.count();i++) + { + Peer* p = ppl.at(i); + if (!poup && num_unchoked < num_slots) + { + p->getPacketWriter().sendUnchoke(); + num_unchoked++; + } + else if (num_unchoked < num_slots -1 || p == poup) + { + p->getPacketWriter().sendUnchoke(); + if (p != poup) + num_unchoked++; + } + else + { + p->choke(); + } + } + } + + static int UpRateCmp(Peer* a,Peer* b) + { + if (a->getStats().upload_rate < b->getStats().upload_rate) + return -1; + else if (a->getStats().upload_rate > b->getStats().upload_rate) + return 1; + else + return 0; + } + + void AdvancedChokeAlgorithm::doChokingSeedingState(PeerManager & pman,ChunkManager & cman,const kt::TorrentStats & stats) + { + PeerPtrList ppl; + Uint32 np = pman.getNumConnectedPeers(); + // add all non seeders + for (Uint32 i = 0;i < np;i++) + { + Peer* p = pman.getPeer(i); + if (p) + { + // update the ACA score in the process + if (calcACAScore(p,cman,stats)) + ppl.append(p); + else + // choke seeders they do not want to download from us anyway + p->choke(); + } + } + + ppl.setCompareFunc(UpRateCmp); + ppl.sort(); + + doUnchoking(ppl,updateOptimisticPeer(pman,ppl)); + } + + static Uint32 FindPlannedOptimisticUnchokedPeer(PeerManager& pman,const PeerPtrList & ppl) + { + Uint32 num_peers = pman.getNumConnectedPeers(); + if (num_peers == 0) + return UNDEFINED_ID; + + // find a random peer that is choked and interested + Uint32 start = rand() % num_peers; + Uint32 i = (start + 1) % num_peers; + while (i != start) + { + Peer* p = pman.getPeer(i); + if (p && p->isChoked() && p->isInterested() && !p->isSeeder() && ppl.contains(p)) + return p->getID(); + i = (i + 1) % num_peers; + } + + // we do not expect to have 4 billion peers + return UNDEFINED_ID; + } + + Peer* AdvancedChokeAlgorithm::updateOptimisticPeer(PeerManager & pman,const PeerPtrList & ppl) + { + // get the planned optimistic unchoked peer and change it if necessary + Peer* poup = pman.findPeer(opt_unchoked_peer_id); + TimeStamp now = GetCurrentTime(); + if (now - last_opt_sel_time > OPT_SEL_INTERVAL || !poup) + { + opt_unchoked_peer_id = FindPlannedOptimisticUnchokedPeer(pman,ppl); + last_opt_sel_time = now; + poup = pman.findPeer(opt_unchoked_peer_id); + } + return poup; + } +} diff --git a/libktorrent/torrent/advancedchokealgorithm.h b/libktorrent/torrent/advancedchokealgorithm.h new file mode 100644 index 0000000..f8a1086 --- /dev/null +++ b/libktorrent/torrent/advancedchokealgorithm.h @@ -0,0 +1,52 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef BTADVANCEDCHOKEALGORITHM_H +#define BTADVANCEDCHOKEALGORITHM_H + +#include "choker.h" + +namespace bt +{ + class Peer; + class PeerPtrList; + + + /** + @author Joris Guisson <joris.guisson@gmail.com> + */ + class AdvancedChokeAlgorithm : public ChokeAlgorithm + { + TimeStamp last_opt_sel_time; // last time we updated the optimistic unchoked peer + public: + AdvancedChokeAlgorithm(); + virtual ~AdvancedChokeAlgorithm(); + + virtual void doChokingLeechingState(PeerManager & pman,ChunkManager & cman,const kt::TorrentStats & stats); + virtual void doChokingSeedingState(PeerManager & pman,ChunkManager & cman,const kt::TorrentStats & stats); + + private: + bool calcACAScore(Peer* p,ChunkManager & cman,const kt::TorrentStats & stats); + Peer* updateOptimisticPeer(PeerManager & pman,const PeerPtrList & ppl); + void doUnchoking(PeerPtrList & ppl,Peer* poup); + }; + +} + +#endif diff --git a/libktorrent/torrent/announcelist.cpp b/libktorrent/torrent/announcelist.cpp new file mode 100644 index 0000000..74b3397 --- /dev/null +++ b/libktorrent/torrent/announcelist.cpp @@ -0,0 +1,195 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#if 0 +#include "announcelist.h" +#include "bnode.h" +#include <util/error.h> +#include "globals.h" +#include <util/log.h> + +#include <klocale.h> +#include <qstringlist.h> +#include <qfile.h> +#include <qtextstream.h> + +namespace bt +{ + + AnnounceList::AnnounceList() + :m_datadir(QString::null) + { + curr = 0; + } + + + AnnounceList::~AnnounceList() + { + saveTrackers(); + } + + void AnnounceList::load(BNode* node) + { + BListNode* ml = dynamic_cast<BListNode*>(node); + if (!ml) + return; + + //ml->printDebugInfo(); + for (Uint32 i = 0;i < ml->getNumChildren();i++) + { + BListNode* url = dynamic_cast<BListNode*>(ml->getChild(i)); + if (!url) + throw Error(i18n("Parse Error")); + + for (Uint32 j = 0;j < url->getNumChildren();j++) + { + BValueNode* vn = dynamic_cast<BValueNode*>(url->getChild(j)); + if (!vn) + throw Error(i18n("Parse Error")); + + KURL url(vn->data().toString().stripWhiteSpace()); + trackers.append(url); + //Out() << "Added tracker " << url << endl; + } + } + } + + const KURL::List AnnounceList::getTrackerURLs() + { + KURL::List complete(trackers); + complete += custom_trackers; + return complete; + } + + void AnnounceList::addTracker(KURL url, bool custom) + { + if(custom) + custom_trackers.append(url); + else + trackers.append(url); + } + + bool AnnounceList::removeTracker(KURL url) + { + KURL::List::iterator i = custom_trackers.find(url); + if(i != custom_trackers.end()) + { + custom_trackers.remove(i); + return true; + } + else + return false; + } + + KURL AnnounceList::getTrackerURL(bool last_was_succesfull) const + { + int defaults = trackers.count(); + int customs = custom_trackers.count(); + int total = defaults + customs; + + if (total == 0) + return KURL(); // return invalid url is there are no trackers + + if (last_was_succesfull) + return curr < defaults ? *trackers.at(curr) : *custom_trackers.at(curr % customs); + + curr = (curr + 1) % total; + return curr < defaults ? *trackers.at(curr) : *custom_trackers.at(curr % customs); + } + + void AnnounceList::debugPrintURLList() + { + Out() << "Announce List : " << endl; + for (KURL::List::iterator i = trackers.begin();i != trackers.end();i++) + Out() << "URL : " << *i << endl; + } + + void AnnounceList::saveTrackers() + { + QFile file(m_datadir + "trackers"); + if(!file.open(IO_WriteOnly)) + return; + + QTextStream stream(&file); + for (KURL::List::iterator i = custom_trackers.begin();i != custom_trackers.end();i++) + stream << (*i).prettyURL() << ::endl; + file.close(); + } + + void AnnounceList::loadTrackers() + { + QFile file(m_datadir + "trackers"); + if(!file.open(IO_ReadOnly)) + return; + + QTextStream stream(&file); + while (!stream.atEnd()) + { + KURL url(stream.readLine().stripWhiteSpace()); + custom_trackers.append(url); + } + + file.close(); + } + + void AnnounceList::setDatadir(const QString& theValue) + { + m_datadir = theValue; + loadTrackers(); + } + + void AnnounceList::setTracker(KURL url) + { + int defaults = trackers.count(); + int customs = custom_trackers.count(); + int total = defaults + customs; + + int backup = curr; + + for(curr=0; curr<defaults; ++curr) + { + if( *trackers.at(curr) == url ) + return; + } + + for( ; curr<total; ++curr) + { + if( *custom_trackers.at(curr % customs) == url ) + return; + } + + curr = backup; + } + + void AnnounceList::restoreDefault() + { + curr = 0; + } + + void AnnounceList::merge(const AnnounceList* al) + { + for (Uint32 i = 0;i < al->getNumTrackerURLs();i++) + { + KURL url = *al->trackers.at(i); + if (!trackers.contains(url) && !custom_trackers.contains(url)) + custom_trackers.append(url); + } + } +} +#endif diff --git a/libktorrent/torrent/announcelist.h b/libktorrent/torrent/announcelist.h new file mode 100644 index 0000000..38f9e72 --- /dev/null +++ b/libktorrent/torrent/announcelist.h @@ -0,0 +1,107 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef BTANNOUNCELIST_H +#define BTANNOUNCELIST_H + +#if 0 +#include <kurl.h> +#include <qstring.h> +#include <interfaces/trackerslist.h> + +namespace bt +{ + class BNode; + + /** + * @author Joris Guisson + * @brief Keep track of a list of trackers + * + * This class keeps track of a list of tracker URL. + */ + class AnnounceList : public kt::TrackersList + { + KURL::List trackers; + KURL::List custom_trackers; + + public: + AnnounceList(); + virtual ~AnnounceList(); + + /** + * Load the list from a bencoded list of lists. + * @param node The BNode + */ + void load(BNode* node); + + /** + * Get a new tracker url. + * @param last_was_succesfull Wether or not the last url was succesfull + * @return An URL + */ + KURL getTrackerURL(bool last_was_succesfull) const; + + + ///Gets a list of trackers (URLs) + const KURL::List getTrackerURLs(); + + ///Adds new tracker URL to the list + void addTracker(KURL url, bool custom = true); + + /** + * Removes a tracker from the list + * @param url Tracker URL to remove from custom trackers list. + * @returns TRUE if URL is in custom list and it is removed or FALSE if it could not be removed or it's a default tracker + */ + bool removeTracker(KURL url); + + ///Changes current tracker + void setTracker(KURL url); + + ///Restores the default torrent tracker + void restoreDefault(); + + /// Get the number of tracker URLs + unsigned int getNumTrackerURLs() const {return trackers.count();} + + void debugPrintURLList(); + + ///Saves custom trackers in a file + void saveTrackers(); + + ///Loads custom trackers from a file + void loadTrackers(); + + void setDatadir(const QString& theValue); + + /** + * Merge an other announce list to this one. + * @param al The AnnounceList + */ + void merge(const AnnounceList* al); + + private: + QString m_datadir; + + }; + +} +#endif + +#endif diff --git a/libktorrent/torrent/authenticate.cpp b/libktorrent/torrent/authenticate.cpp new file mode 100644 index 0000000..14e34ea --- /dev/null +++ b/libktorrent/torrent/authenticate.cpp @@ -0,0 +1,156 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <util/log.h> +#include <mse/streamsocket.h> +#include "authenticate.h" +#include "ipblocklist.h" +#include "peermanager.h" + +namespace bt +{ + + Authenticate::Authenticate(const QString & ip,Uint16 port, + const SHA1Hash & info_hash,const PeerID & peer_id,PeerManager* pman) + : info_hash(info_hash),our_peer_id(peer_id),pman(pman) + { + finished = succes = false; + sock = new mse::StreamSocket(); + host = ip; + this->port = port; + + Out(SYS_CON|LOG_NOTICE) << "Initiating connection to " << host << endl; + + if (sock->connectTo(host,port)) + { + connected(); + } + else if (sock->connecting()) + { + // do nothing the monitor will notify us when we are connected + } + else + { + onFinish(false); + } + } + + Authenticate::~Authenticate() + { + } + + void Authenticate::onReadyWrite() + { +// Out() << "Authenticate::onReadyWrite()" << endl; + if (sock->connectSuccesFull()) + { + connected(); + } + else + { + onFinish(false); + } + } + + void Authenticate::connected() + { + sendHandshake(info_hash,our_peer_id); + } + + void Authenticate::onFinish(bool succes) + { + Out(SYS_CON|LOG_NOTICE) << "Authentication to " << host << " : " << (succes ? "ok" : "failure") << endl; + finished = true; + this->succes = succes; + + if (!succes) + { + sock->deleteLater(); + sock = 0; + } + timer.stop(); + if (pman) + pman->peerAuthenticated(this,succes); + } + + void Authenticate::handshakeRecieved(bool full) + { + const Uint8* hs = handshake; + // Out() << "Authenticate::handshakeRecieved" << endl; + IPBlocklist& ipfilter = IPBlocklist::instance(); + //Out() << "Dodo " << pp.ip << endl; + if (ipfilter.isBlocked(host)) + { + onFinish(false); + return; + } + + SHA1Hash rh(hs+28); + if (rh != info_hash) + { + Out() << "Wrong info_hash : " << rh.toString() << endl; + onFinish(false); + return; + } + + char tmp[21]; + tmp[20] = '\0'; + memcpy(tmp,hs+48,20); + peer_id = PeerID(tmp); + + if (our_peer_id == peer_id /*|| peer_id.startsWith("Yoda")*/) + { + Out(SYS_CON|LOG_DEBUG) << "Lets not connect to our selves " << endl; + onFinish(false); + return; + } + + // check if we aren't already connected to the client + if (pman->connectedTo(peer_id)) + { + Out(SYS_CON|LOG_NOTICE) << "Already connected to " << peer_id.toString() << endl; + onFinish(false); + return; + } + + // only finish when the handshake was fully received + if (full) + onFinish(true); + } + + + mse::StreamSocket* Authenticate::takeSocket() + { + mse::StreamSocket* s = sock; + sock = 0; + return s; + } + + void Authenticate::onPeerManagerDestroyed() + { + // Out(SYS_CON|LOG_NOTICE) << "Authenticate::onPeerManagerDestroyed()" << endl; + pman = 0; + if (finished) + return; + + onFinish(false); + } + +} +#include "authenticate.moc" diff --git a/libktorrent/torrent/authenticate.h b/libktorrent/torrent/authenticate.h new file mode 100644 index 0000000..03c8d75 --- /dev/null +++ b/libktorrent/torrent/authenticate.h @@ -0,0 +1,98 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef BTAUTHENTICATE_H +#define BTAUTHENTICATE_H + + +#include <util/sha1hash.h> +#include "authenticatebase.h" +#include "globals.h" +#include "peerid.h" + + +namespace bt +{ + + + class PeerManager; + + + /** + * @author Joris Guisson + * @brief Authenicate a peer + * + * After we connect to a peer, + * we need to authenticate the peer. This class handles this. + */ + class Authenticate : public AuthenticateBase + { + Q_OBJECT + public: + + /** + * Connect to a remote host first and authenicate it. + * @param ip IP-address of host + * @param port Port of host + * @param info_hash Info hash + * @param peer_id Peer ID + * @param pman PeerManager + */ + Authenticate(const QString & ip,Uint16 port, + const SHA1Hash & info_hash,const PeerID & peer_id, + PeerManager* pman); + + virtual ~Authenticate(); + + /** + * Get a pointer to the socket, and set it internally + * to NULL. After a succesfull authentication, this is used + * to transfer ownership to a Peer object. + * @return The socket + */ + mse::StreamSocket* takeSocket(); + + const PeerID & getPeerID() const {return peer_id;} + + /// See if the authentication is succesfull + bool isSuccesfull() const {return succes;} + + const QString & getIP() const {return host;} + Uint16 getPort() const {return port;} + + protected slots: + void onReadyWrite(); + void onPeerManagerDestroyed(); + + protected: + void onFinish(bool succes); + void handshakeRecieved(bool full); + virtual void connected(); + + protected: + SHA1Hash info_hash; + PeerID our_peer_id,peer_id; + QString host; + Uint16 port; + bool succes; + PeerManager* pman; + }; +} + +#endif diff --git a/libktorrent/torrent/authenticatebase.cpp b/libktorrent/torrent/authenticatebase.cpp new file mode 100644 index 0000000..9ee2ad7 --- /dev/null +++ b/libktorrent/torrent/authenticatebase.cpp @@ -0,0 +1,159 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <mse/streamsocket.h> +#include <util/sha1hash.h> +#include <util/log.h> +#include <kademlia/dhtbase.h> +#include "globals.h" +#include "peerid.h" +#include "authenticatebase.h" + +namespace bt +{ + + + + AuthenticateBase::AuthenticateBase(mse::StreamSocket* s) : sock(s),finished(false),local(false) + { + connect(&timer,SIGNAL(timeout()),this,SLOT(onTimeout())); + timer.start(20000,true); + memset(handshake,0x00,68); + bytes_of_handshake_recieved = 0; + ext_support = 0; + poll_index = -1; + } + + + AuthenticateBase::~AuthenticateBase() + { + if (sock) + sock->deleteLater(); + } + + void AuthenticateBase::sendHandshake(const SHA1Hash & info_hash,const PeerID & our_peer_id) + { + // Out() << "AuthenticateBase::sendHandshake" << endl; + if (!sock) return; + + Uint8 hs[68]; + makeHandshake(hs,info_hash,our_peer_id); + sock->sendData(hs,68); + } + + void AuthenticateBase::makeHandshake(Uint8* hs,const SHA1Hash & info_hash,const PeerID & our_peer_id) + { + const char* pstr = "BitTorrent protocol"; + hs[0] = 19; + memcpy(hs+1,pstr,19); + memset(hs+20,0x00,8); + if (Globals::instance().getDHT().isRunning()) + hs[27] |= 0x01; // DHT support + + hs[25] |= 0x10; // extension protocol + hs[27] |= 0x04; // fast extensions + memcpy(hs+28,info_hash.getData(),20); + memcpy(hs+48,our_peer_id.data(),20); + } + + void AuthenticateBase::onReadyRead() + { + Uint32 ba = sock->bytesAvailable(); + // Out() << "AuthenticateBase::onReadyRead " << ba << endl; + if (ba == 0) + { + onFinish(false); + return; + } + + if (!sock || finished || ba < 48) + return; + + // first see if we already have some bytes from the handshake + if (bytes_of_handshake_recieved == 0) + { + if (ba < 68) + { + // read partial + sock->readData(handshake,ba); + bytes_of_handshake_recieved += ba; + if (ba >= 27 && handshake[27] & 0x01) + ext_support |= bt::DHT_SUPPORT; + // tell subclasses of a partial handshake + handshakeRecieved(false); + return; + } + else + { + // read full handshake + sock->readData(handshake,68); + } + } + else + { + // read remaining part + Uint32 to_read = 68 - bytes_of_handshake_recieved; + sock->readData(handshake + bytes_of_handshake_recieved,to_read); + } + + if (handshake[0] != 19) + { + onFinish(false); + return; + } + + const char* pstr = "BitTorrent protocol"; + if (memcmp(pstr,handshake+1,19) != 0) + { + onFinish(false); + return; + } + + if (Globals::instance().getDHT().isRunning() && (handshake[27] & 0x01)) + ext_support |= bt::DHT_SUPPORT; + + if (handshake[27] & 0x04) + ext_support |= bt::FAST_EXT_SUPPORT; + + if (handshake[25] & 0x10) + ext_support |= bt::EXT_PROT_SUPPORT; + + handshakeRecieved(true); + } + + void AuthenticateBase::onError(int) + { + if (finished) + return; + onFinish(false); + } + + void AuthenticateBase::onTimeout() + { + if (finished) + return; + + Out(SYS_CON|LOG_DEBUG) << "Timeout occurred" << endl; + onFinish(false); + } + + void AuthenticateBase::onReadyWrite() + {} +} +#include "authenticatebase.moc" diff --git a/libktorrent/torrent/authenticatebase.h b/libktorrent/torrent/authenticatebase.h new file mode 100644 index 0000000..fdab158 --- /dev/null +++ b/libktorrent/torrent/authenticatebase.h @@ -0,0 +1,125 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef BTAUTHENTICATEBASE_H +#define BTAUTHENTICATEBASE_H + +#include <qobject.h> +#include <qsocket.h> +#include <qtimer.h> +#include <util/constants.h> + + +namespace mse +{ + class StreamSocket; +} + + +namespace bt +{ + class SHA1Hash; + class PeerID; + + /** + * @author Joris Guisson + * + * Base class for authentication classes. This class just groups + * some common stuff between Authenticate and ServerAuthentciate. + * It has a socket, handles the timing out, provides a function to send + * the handshake. + */ + class AuthenticateBase : public QObject + { + Q_OBJECT + public: + AuthenticateBase(mse::StreamSocket* s = 0); + virtual ~AuthenticateBase(); + + /// Set wether this is a local peer + void setLocal(bool loc) {local = loc;} + + /// Is this a local peer + bool isLocal() const {return local;} + + /// See if the authentication is finished + bool isFinished() const {return finished;} + + /// Flags indicating which extensions are supported + Uint32 supportedExtensions() const {return ext_support;} + + /// get teh socket + const mse::StreamSocket* getSocket() const {return sock;} + + /// We can read from the socket + virtual void onReadyRead(); + + /// We can write to the socket (used to detect a succesfull connection) + virtual void onReadyWrite(); + + int getPollIndex() const {return poll_index;} + void setPollIndex(int pi) {poll_index = pi;} + + protected: + /** + * Send a handshake + * @param info_hash The info_hash to include + * @param our_peer_id Our PeerID + */ + void sendHandshake(const SHA1Hash & info_hash,const PeerID & our_peer_id); + + /** + * Authentication finished. + * @param succes Succes or not + */ + virtual void onFinish(bool succes) = 0; + + /** + * The other side send a handshake. The first 20 bytes + * of the handshake will already have been checked. + * @param full Indicates wether we have a full handshake + * if this is not full, we should just send our own + */ + virtual void handshakeRecieved(bool full) = 0; + + /** + * Fill in the handshake in a buffer. + */ + void makeHandshake(bt::Uint8* buf,const SHA1Hash & info_hash,const PeerID & our_peer_id); + + + + protected slots: + void onTimeout(); + void onError(int err); + + protected: + mse::StreamSocket* sock; + QTimer timer; + bool finished; + Uint8 handshake[68]; + Uint32 bytes_of_handshake_recieved; + Uint32 ext_support; + bool local; + int poll_index; + }; + +} + +#endif diff --git a/libktorrent/torrent/authenticationmonitor.cpp b/libktorrent/torrent/authenticationmonitor.cpp new file mode 100644 index 0000000..08215d0 --- /dev/null +++ b/libktorrent/torrent/authenticationmonitor.cpp @@ -0,0 +1,149 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <math.h> +#include <unistd.h> +#include <sys/poll.h> +#include <util/functions.h> +#include <util/log.h> +#include <mse/streamsocket.h> +#include "authenticationmonitor.h" +#include "authenticatebase.h" + +#include <util/profiler.h> + + +namespace bt +{ + AuthenticationMonitor AuthenticationMonitor::self; + + AuthenticationMonitor::AuthenticationMonitor() + {} + + + AuthenticationMonitor::~AuthenticationMonitor() + { + + } + + void AuthenticationMonitor::clear() + { + std::list<AuthenticateBase*>::iterator itr = auths.begin(); + while (itr != auths.end()) + { + AuthenticateBase* ab = *itr; + if (ab) + ab->deleteLater(); + itr++; + } + auths.clear(); + } + + + void AuthenticationMonitor::add(AuthenticateBase* s) + { + auths.push_back(s); + } + + void AuthenticationMonitor::remove(AuthenticateBase* s) + { + auths.remove(s); + } + + void AuthenticationMonitor::update() + { + if (auths.size() == 0) + return; + + int i = 0; + + std::list<AuthenticateBase*>::iterator itr = auths.begin(); + while (itr != auths.end()) + { + AuthenticateBase* ab = *itr; + if (!ab || ab->isFinished()) + { + if (ab) + ab->deleteLater(); + + itr = auths.erase(itr); + } + else + { + ab->setPollIndex(-1); + if (ab->getSocket() && ab->getSocket()->fd() >= 0) + { + int fd = ab->getSocket()->fd(); + if (i >= fd_vec.size()) + { + struct pollfd pfd = {-1,0,0}; + fd_vec.push_back(pfd); + } + + struct pollfd & pfd = fd_vec[i]; + pfd.fd = fd; + pfd.revents = 0; + if (!ab->getSocket()->connecting()) + pfd.events = POLLIN; + else + pfd.events = POLLOUT; + ab->setPollIndex(i); + i++; + } + itr++; + } + } + + if (poll(&fd_vec[0],i,1) > 0) + { + handleData(); + } + } + + void AuthenticationMonitor::handleData() + { + std::list<AuthenticateBase*>::iterator itr = auths.begin(); + while (itr != auths.end()) + { + AuthenticateBase* ab = *itr; + if (ab && ab->getSocket() && ab->getSocket()->fd() >= 0 && ab->getPollIndex() >= 0) + { + int pi = ab->getPollIndex(); + if (fd_vec[pi].revents & POLLIN) + { + ab->onReadyRead(); + } + else if (fd_vec[pi].revents & POLLOUT) + { + ab->onReadyWrite(); + } + } + + if (!ab || ab->isFinished()) + { + if (ab) + ab->deleteLater(); + itr = auths.erase(itr); + } + else + itr++; + } + } + +} diff --git a/libktorrent/torrent/authenticationmonitor.h b/libktorrent/torrent/authenticationmonitor.h new file mode 100644 index 0000000..43a4ebb --- /dev/null +++ b/libktorrent/torrent/authenticationmonitor.h @@ -0,0 +1,80 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef BTAUTHENTICATIONMONITOR_H +#define BTAUTHENTICATIONMONITOR_H + +#include <list> +#include <vector> + +struct pollfd; + +namespace bt +{ + class AuthenticateBase; + + /** + @author Joris Guisson <joris.guisson@gmail.com> + + Monitors ongoing authentication attempts. This class is a singleton. + */ + class AuthenticationMonitor + { + std::list<AuthenticateBase*> auths; + std::vector<struct pollfd> fd_vec; + + static AuthenticationMonitor self; + + AuthenticationMonitor(); + public: + + virtual ~AuthenticationMonitor(); + + + /** + * Add a new AuthenticateBase object. + * @param s + */ + void add(AuthenticateBase* s); + + /** + * Remove an AuthenticateBase object + * @param s + */ + void remove(AuthenticateBase* s); + + /** + * Check all AuthenticateBase objects. + */ + void update(); + + /** + * Clear all AuthenticateBase objects, also delets them + */ + void clear(); + + static AuthenticationMonitor & instance() {return self;} + + private: + void handleData(); + }; + +} + +#endif diff --git a/libktorrent/torrent/bdecoder.cpp b/libktorrent/torrent/bdecoder.cpp new file mode 100644 index 0000000..6c5a179 --- /dev/null +++ b/libktorrent/torrent/bdecoder.cpp @@ -0,0 +1,224 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <util/log.h> +#include <util/error.h> +#include <klocale.h> +#include "bdecoder.h" +#include "bnode.h" +#include "globals.h" + +namespace bt +{ + + BDecoder::BDecoder(const QByteArray & data,bool verbose,Uint32 off) + : data(data),pos(off),verbose(verbose) + { + } + + + BDecoder::~BDecoder() + {} + + BNode* BDecoder::decode() + { + if (pos >= data.size()) + return 0; + + if (data[pos] == 'd') + { + return parseDict(); + } + else if (data[pos] == 'l') + { + return parseList(); + } + else if (data[pos] == 'i') + { + return parseInt(); + } + else if (data[pos] >= '0' && data[pos] <= '9') + { + return parseString(); + } + else + { + throw Error(i18n("Illegal token: %1").arg(data[pos])); + } + } + + BDictNode* BDecoder::parseDict() + { + Uint32 off = pos; + // we're now entering a dictionary + BDictNode* curr = new BDictNode(off); + pos++; + if (verbose) Out() << "DICT" << endl; + try + { + while (pos < data.size() && data[pos] != 'e') + { + if (verbose) Out() << "Key : " << endl; + BNode* kn = decode(); + BValueNode* k = dynamic_cast<BValueNode*>(kn); + if (!k || k->data().getType() != Value::STRING) + { + delete kn; + throw Error(i18n("Decode error")); + } + + QByteArray key = k->data().toByteArray(); + delete kn; + + BNode* data = decode(); + curr->insert(key,data); + } + pos++; + } + catch (...) + { + delete curr; + throw; + } + if (verbose) Out() << "END" << endl; + curr->setLength(pos - off); + return curr; + } + + BListNode* BDecoder::parseList() + { + Uint32 off = pos; + if (verbose) Out() << "LIST" << endl; + BListNode* curr = new BListNode(off); + pos++; + try + { + while (pos < data.size() && data[pos] != 'e') + { + BNode* n = decode(); + curr->append(n); + } + pos++; + } + catch (...) + { + delete curr; + throw; + } + if (verbose) Out() << "END" << endl; + curr->setLength(pos - off); + return curr; + } + + BValueNode* BDecoder::parseInt() + { + Uint32 off = pos; + pos++; + QString n; + // look for e and add everything between i and e to n + while (pos < data.size() && data[pos] != 'e') + { + n += data[pos]; + pos++; + } + + // check if we aren't at the end of the data + if (pos >= data.size()) + { + throw Error(i18n("Unexpected end of input")); + } + + // try to decode the int + bool ok = true; + int val = 0; + val = n.toInt(&ok); + if (ok) + { + pos++; + if (verbose) Out() << "INT = " << val << endl; + BValueNode* vn = new BValueNode(Value(val),off); + vn->setLength(pos - off); + return vn; + } + else + { + Int64 bi = 0LL; + bi = n.toLongLong(&ok); + if (!ok) + throw Error(i18n("Cannot convert %1 to an int").arg(n)); + + pos++; + if (verbose) Out() << "INT64 = " << n << endl; + BValueNode* vn = new BValueNode(Value(bi),off); + vn->setLength(pos - off); + return vn; + } + } + + BValueNode* BDecoder::parseString() + { + Uint32 off = pos; + // string are encoded 4:spam (length:string) + + // first get length by looking for the : + QString n; + while (pos < data.size() && data[pos] != ':') + { + n += data[pos]; + pos++; + } + // check if we aren't at the end of the data + if (pos >= data.size()) + { + throw Error(i18n("Unexpected end of input")); + } + + // try to decode length + bool ok = true; + int len = 0; + len = n.toInt(&ok); + if (!ok) + { + throw Error(i18n("Cannot convert %1 to an int").arg(n)); + } + // move pos to the first part of the string + pos++; + if (pos + len > data.size()) + throw Error(i18n("Torrent is incomplete!")); + + QByteArray arr(len); + for (unsigned int i = pos;i < pos + len;i++) + arr.at(i-pos) = data[i]; + pos += len; + // read the string into n + + // pos should be positioned right after the string + BValueNode* vn = new BValueNode(Value(arr),off); + vn->setLength(pos - off); + if (verbose) + { + if (arr.size() < 200) + Out() << "STRING " << QString(arr) << endl; + else + Out() << "STRING " << "really long string" << endl; + } + return vn; + } +} + diff --git a/libktorrent/torrent/bdecoder.h b/libktorrent/torrent/bdecoder.h new file mode 100644 index 0000000..dfffce5 --- /dev/null +++ b/libktorrent/torrent/bdecoder.h @@ -0,0 +1,70 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef BTBDECODER_H +#define BTBDECODER_H + +#include <qstring.h> +#include <util/constants.h> + +namespace bt +{ + + class BNode; + class BListNode; + class BDictNode; + class BValueNode; + + /** + * @author Joris Guisson + * @brief Decodes b-encoded data + * + * Class to decode b-encoded data. + */ + class BDecoder + { + const QByteArray & data; + Uint32 pos; + bool verbose; + public: + /** + * Constructor, passes in the data to decode. + * @param data The data + * @param verbose Verbose output to the log + * @param off Offset to start parsing + */ + BDecoder(const QByteArray & data,bool verbose,Uint32 off = 0); + virtual ~BDecoder(); + + /** + * Decode the data, the root node gets + * returned. (Note that the caller must delete this node) + * @return The root node + */ + BNode* decode(); + private: + BDictNode* parseDict(); + BListNode* parseList(); + BValueNode* parseInt(); + BValueNode* parseString(); + }; + +} + +#endif diff --git a/libktorrent/torrent/bencoder.cpp b/libktorrent/torrent/bencoder.cpp new file mode 100644 index 0000000..e4a80a0 --- /dev/null +++ b/libktorrent/torrent/bencoder.cpp @@ -0,0 +1,137 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 "bencoder.h" +#include <util/file.h> + +namespace bt +{ + + + BEncoderFileOutput::BEncoderFileOutput(File* fptr) : fptr(fptr) + { + } + + void BEncoderFileOutput::write(const char* str,Uint32 len) + { + if (!fptr) + return; + + fptr->write(str,len); + } + + //////////////////////////////////// + + BEncoderBufferOutput::BEncoderBufferOutput(QByteArray & data) : data(data),ptr(0) + { + } + + void BEncoderBufferOutput::write(const char* str,Uint32 len) + { + if (ptr + len > data.size()) + data.resize(ptr + len); + + for (Uint32 i = 0;i < len;i++) + data[ptr++] = str[i]; + } + + //////////////////////////////////// + + BEncoder::BEncoder(File* fptr) : out(0),del(true) + { + out = new BEncoderFileOutput(fptr); + } + + BEncoder::BEncoder(BEncoderOutput* out) : out(out),del(true) + { + } + + + BEncoder::~BEncoder() + { + if (del) + delete out; + } + + void BEncoder::beginDict() + { + if (!out) return; + + out->write("d",1); + } + + void BEncoder::beginList() + { + if (!out) return; + + out->write("l",1); + } + + void BEncoder::write(Uint32 val) + { + if (!out) return; + + QCString s = QString("i%1e").arg(val).utf8(); + out->write(s,s.length()); + } + + void BEncoder::write(Uint64 val) + { + if (!out) return; + + QCString s = QString("i%1e").arg(val).utf8(); + out->write(s,s.length()); + } + + void BEncoder::write(const QString & str) + { + if (!out) return; + + QCString u = str.utf8(); + QCString s = QString("%1:").arg(u.length()).utf8(); + out->write(s,s.length()); + out->write(u,u.length()); + } + + void BEncoder::write(const QByteArray & data) + { + if (!out) return; + + QCString s = QString::number(data.size()).utf8(); + out->write(s,s.length()); + out->write(":",1); + out->write(data.data(),data.size()); + } + + void BEncoder::write(const Uint8* data,Uint32 size) + { + if (!out) return; + + QCString s = QString("%1:").arg(size).utf8(); + out->write(s,s.length()); + out->write((const char*)data,size); + } + + void BEncoder::end() + { + if (!out) return; + + out->write("e",1); + } +} diff --git a/libktorrent/torrent/bencoder.h b/libktorrent/torrent/bencoder.h new file mode 100644 index 0000000..8760d14 --- /dev/null +++ b/libktorrent/torrent/bencoder.h @@ -0,0 +1,150 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef BTBENCODER_H +#define BTBENCODER_H + + +#include <util/file.h> + + +namespace bt +{ + class File; + + /** + * @author Joris Guisson + * + * Interface for classes which wish to receive the output from a BEncoder. + */ + class BEncoderOutput + { + public: + virtual ~BEncoderOutput() {} + /** + * Write a string of characters. + * @param str The string + * @param len The length of the string + */ + virtual void write(const char* str,Uint32 len) = 0; + }; + + /** + * Writes the output of a bencoder to a file + */ + class BEncoderFileOutput : public BEncoderOutput + { + File* fptr; + public: + BEncoderFileOutput(File* fptr); + + void write(const char* str,Uint32 len); + }; + + /** + * Write the output of a BEncoder to a QByteArray + */ + class BEncoderBufferOutput : public BEncoderOutput + { + QByteArray & data; + Uint32 ptr; + public: + BEncoderBufferOutput(QByteArray & data); + + void write(const char* str,Uint32 len); + }; + + + /** + * @author Joris Guisson + * @brief Helper class to b-encode stuff. + * + * This class b-encodes data. For more details about b-encoding, see + * the BitTorrent protocol docs. The data gets written to a BEncoderOutput + * thing. + */ + class BEncoder + { + BEncoderOutput* out; + bool del; + public: + /** + * Constructor, output gets written to a file. + * @param fptr The File to write to + */ + BEncoder(File* fptr); + + + /** + * Constructor, output gets written to a BEncoderOutput object. + * @param out The BEncoderOutput + */ + BEncoder(BEncoderOutput* out); + virtual ~BEncoder(); + + /** + * Begin a dictionary.Should have a corresponding end call. + */ + void beginDict(); + + /** + * Begin a list. Should have a corresponding end call. + */ + void beginList(); + + /** + * Write an int + * @param val + */ + void write(Uint32 val); + + /** + * Write an int64 + * @param val + */ + void write(Uint64 val); + + /** + * Write a string + * @param str + */ + void write(const QString & str); + + /** + * Write a QByteArray + * @param data + */ + void write(const QByteArray & data); + + /** + * Write a data array + * @param data + * @param size of data + */ + void write(const Uint8* data,Uint32 size); + + /** + * End a beginDict or beginList call. + */ + void end(); + }; + +} + +#endif diff --git a/libktorrent/torrent/bnode.cpp b/libktorrent/torrent/bnode.cpp new file mode 100644 index 0000000..e76dcf3 --- /dev/null +++ b/libktorrent/torrent/bnode.cpp @@ -0,0 +1,177 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <util/log.h> +#include "bnode.h" +#include "globals.h" + +namespace bt +{ + + BNode::BNode(Type type,Uint32 off) : type(type),off(off),len(0) + { + } + + + BNode::~BNode() + {} + + //////////////////////////////////////////////// + + BValueNode::BValueNode(const Value & v,Uint32 off) : BNode(VALUE,off),v(v) + {} + + BValueNode::~BValueNode() + {} + + void BValueNode::printDebugInfo() + { + if (v.getType() == Value::INT) + Out() << "Value = " << v.toInt() << endl; + else + Out() << "Value = " << v.toString() << endl; + } + + //////////////////////////////////////////////// + + BDictNode::BDictNode(Uint32 off) : BNode(DICT,off) + { + } + + BDictNode::~BDictNode() + { + QValueList<DictEntry>::iterator i = children.begin(); + while (i != children.end()) + { + DictEntry & e = *i; + delete e.node; + i++; + } + } + + void BDictNode::insert(const QByteArray & key,BNode* node) + { + DictEntry entry; + entry.key = key; + entry.node = node; + children.append(entry); + } + + BNode* BDictNode::getData(const QString & key) + { + QValueList<DictEntry>::iterator i = children.begin(); + while (i != children.end()) + { + DictEntry & e = *i; + if (QString(e.key) == key) + return e.node; + i++; + } + return 0; + } + + BDictNode* BDictNode::getDict(const QByteArray & key) + { + QValueList<DictEntry>::iterator i = children.begin(); + while (i != children.end()) + { + DictEntry & e = *i; + if (e.key == key) + return dynamic_cast<BDictNode*>(e.node); + i++; + } + return 0; + } + + BListNode* BDictNode::getList(const QString & key) + { + BNode* n = getData(key); + return dynamic_cast<BListNode*>(n); + } + + BDictNode* BDictNode::getDict(const QString & key) + { + BNode* n = getData(key); + return dynamic_cast<BDictNode*>(n); + } + + BValueNode* BDictNode::getValue(const QString & key) + { + BNode* n = getData(key); + return dynamic_cast<BValueNode*>(n); + } + + void BDictNode::printDebugInfo() + { + Out() << "DICT" << endl; + QValueList<DictEntry>::iterator i = children.begin(); + while (i != children.end()) + { + DictEntry & e = *i; + Out() << QString(e.key) << ": " << endl; + e.node->printDebugInfo(); + i++; + } + Out() << "END" << endl; + } + + //////////////////////////////////////////////// + + BListNode::BListNode(Uint32 off) : BNode(LIST,off) + { + children.setAutoDelete(true); + } + + + BListNode::~BListNode() + {} + + + void BListNode::append(BNode* node) + { + children.append(node); + } + + BListNode* BListNode::getList(Uint32 idx) + { + return dynamic_cast<BListNode*>(getChild(idx)); + } + + BDictNode* BListNode::getDict(Uint32 idx) + { + return dynamic_cast<BDictNode*>(getChild(idx)); + } + + BValueNode* BListNode::getValue(Uint32 idx) + { + return dynamic_cast<BValueNode*>(getChild(idx)); + } + + void BListNode::printDebugInfo() + { + Out() << "LIST " << children.count() << endl; + for (Uint32 i = 0;i < children.count();i++) + { + BNode* n = children.at(i); + n->printDebugInfo(); + } + Out() << "END" << endl; + } +} + diff --git a/libktorrent/torrent/bnode.h b/libktorrent/torrent/bnode.h new file mode 100644 index 0000000..685291c --- /dev/null +++ b/libktorrent/torrent/bnode.h @@ -0,0 +1,210 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef BTBNODE_H +#define BTBNODE_H + +#include <qptrlist.h> +#include <qvaluelist.h> +#include <util/constants.h> +#include "value.h" + + +namespace bt +{ + class BListNode; + + /** + * @author Joris Guisson + * @brief Base class for a node in a b-encoded piece of data + * + * There are 3 possible pieces of data in b-encoded piece of data. + * This is the base class for all those 3 things. + */ + class BNode + { + public: + enum Type + { + VALUE,DICT,LIST + }; + + /** + * Constructor, sets the Type, and the offset into + * the data. + * @param type Type of node + * @param off The offset into the data + */ + BNode(Type type,Uint32 off); + virtual ~BNode(); + + /// Get the type of node + Type getType() const {return type;} + + /// Get the offset in the bytearray where this node starts. + Uint32 getOffset() const {return off;} + + /// Get the length this node takes up in the bytearray. + Uint32 getLength() const {return len;} + + /// Set the length + void setLength(Uint32 l) {len = l;} + + /// Print some debugging info + virtual void printDebugInfo() = 0; + private: + Type type; + Uint32 off,len; + }; + + /** + * @author Joris Guisson + * @brief Represents a value (string,bytearray or int) in bencoded data + * + * @todo Use QVariant + */ + class BValueNode : public BNode + { + Value v; + public: + BValueNode(const Value & v,Uint32 off); + virtual ~BValueNode(); + + const Value & data() const {return v;} + void printDebugInfo(); + }; + + /** + * @author Joris Guisson + * @brief Represents a dictionary in bencoded data + * + */ + class BDictNode : public BNode + { + struct DictEntry + { + QByteArray key; + BNode* node; + }; + QValueList<DictEntry> children; + public: + BDictNode(Uint32 off); + virtual ~BDictNode(); + + /** + * Insert a BNode in the dictionary. + * @param key The key + * @param node The node + */ + void insert(const QByteArray & key,BNode* node); + + /** + * Get a BNode. + * @param key The key + * @return The node or 0 if there is no node with has key @a key + */ + BNode* getData(const QString & key); + + /** + * Get a BListNode. + * @param key The key + * @return The node or 0 if there is no list node with has key @a key + */ + BListNode* getList(const QString & key); + + /** + * Get a BDictNode. + * @param key The key + * @return The node or 0 if there is no dict node with has key @a key + */ + BDictNode* getDict(const QString & key); + + /** + * Get a BDictNode. + * @param key The key + * @return The node or 0 if there is no dict node with has key @a key + */ + BDictNode* getDict(const QByteArray & key); + + /** + * Get a BValueNode. + * @param key The key + * @return The node or 0 if there is no value node with has key @a key + */ + BValueNode* getValue(const QString & key); + + void printDebugInfo(); + }; + + /** + * @author Joris Guisson + * @brief Represents a list in bencoded data + * + */ + class BListNode : public BNode + { + QPtrList<BNode> children; + public: + BListNode(Uint32 off); + virtual ~BListNode(); + + /** + * Append a node to the list. + * @param node The node + */ + void append(BNode* node); + void printDebugInfo(); + + /// Get the number of nodes in the list. + Uint32 getNumChildren() const {return children.count();} + + /** + * Get a node from the list + * @param idx The index + * @return The node or 0 if idx is out of bounds + */ + BNode* getChild(Uint32 idx) {return children.at(idx);} + + /** + * Get a BListNode. + * @param idx The index + * @return The node or 0 if the index is out of bounds or the element + * at postion @a idx isn't a BListNode. + */ + BListNode* getList(Uint32 idx); + + /** + * Get a BDictNode. + * @param idx The index + * @return The node or 0 if the index is out of bounds or the element + * at postion @a idx isn't a BDictNode. + */ + BDictNode* getDict(Uint32 idx); + + /** + * Get a BValueNode. + * @param idx The index + * @return The node or 0 if the index is out of bounds or the element + * at postion @a idx isn't a BValueNode. + */ + BValueNode* getValue(Uint32 idx); + }; +} + +#endif diff --git a/libktorrent/torrent/cache.cpp b/libktorrent/torrent/cache.cpp new file mode 100644 index 0000000..dcf9a77 --- /dev/null +++ b/libktorrent/torrent/cache.cpp @@ -0,0 +1,55 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 "torrent.h" +#include "chunk.h" +#include "cache.h" +#include "peermanager.h" +#include <util/functions.h> + +namespace bt +{ + + Cache::Cache(Torrent & tor,const QString & tmpdir,const QString & datadir) + : tor(tor),tmpdir(tmpdir),datadir(datadir),mmap_failures(0) + { + if (!datadir.endsWith(bt::DirSeparator())) + this->datadir += bt::DirSeparator(); + + if (!tmpdir.endsWith(bt::DirSeparator())) + this->tmpdir += bt::DirSeparator(); + + preexisting_files = false; + } + + + Cache::~Cache() + {} + + + void Cache::changeTmpDir(const QString & ndir) + { + tmpdir = ndir; + } + + bool Cache::mappedModeAllowed() + { + return MaxOpenFiles() - bt::PeerManager::getTotalConnections() < 100; + } +} diff --git a/libktorrent/torrent/cache.h b/libktorrent/torrent/cache.h new file mode 100644 index 0000000..4c373ee --- /dev/null +++ b/libktorrent/torrent/cache.h @@ -0,0 +1,165 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef BTCACHE_H +#define BTCACHE_H + +#include <kio/job.h> + +class QStringList; + +namespace bt +{ + class Torrent; + class TorrentFile; + class Chunk; + class PreallocationThread; + + + /** + * @author Joris Guisson + * @brief Manages the temporary data + * + * Interface for a class which manages downloaded data. + * Subclasses should implement the load and save methods. + */ + class Cache + { + protected: + Torrent & tor; + QString tmpdir; + QString datadir; + bool preexisting_files; + Uint32 mmap_failures; + public: + Cache(Torrent & tor,const QString & tmpdir,const QString & datadir); + virtual ~Cache(); + + /// Get the datadir + QString getDataDir() const {return datadir;} + + /** + * Get the actual output path. + * @return The output path + */ + virtual QString getOutputPath() const = 0; + + /** + * Changes the tmp dir. All data files should already been moved. + * This just modifies the tmpdir variable. + * @param ndir The new tmpdir + */ + virtual void changeTmpDir(const QString & ndir); + + /** + * Move the data files to a new directory. + * @param ndir The directory + * @return The KIO::Job doing the move + */ + virtual KIO::Job* moveDataFiles(const QString & ndir) = 0; + + /** + * The move data files job is done. + * @param job The job that did it + */ + virtual void moveDataFilesCompleted(KIO::Job* job) = 0; + + /** + * Changes output path. All data files should already been moved. + * This just modifies the datadir variable. + * @param outputpath New output path + */ + virtual void changeOutputPath(const QString & outputpath) = 0; + + /** + * Load a chunk into memory. If something goes wrong, + * an Error should be thrown. + * @param c The Chunk + */ + virtual void load(Chunk* c) = 0; + + /** + * Save a chunk to disk. If something goes wrong, + * an Error should be thrown. + * @param c The Chunk + */ + virtual void save(Chunk* c) = 0; + + /** + * Prepare a chunk for downloading. + * @param c The Chunk + * @return true if ok, false otherwise + */ + virtual bool prep(Chunk* c) = 0; + + /** + * Create all the data files to store the data. + */ + virtual void create() = 0; + + /** + * Close the cache file(s). + */ + virtual void close() = 0; + + /** + * Open the cache file(s) + */ + virtual void open() = 0; + + /// Does nothing, can be overridden to be alerted of download status changes of a TorrentFile + virtual void downloadStatusChanged(TorrentFile*, bool) {}; + + /** + * Preallocate diskspace for all files + * @param prealloc The thread doing the preallocation + */ + virtual void preallocateDiskSpace(PreallocationThread* prealloc) = 0; + + /// See if the download has existing files + bool hasExistingFiles() const {return preexisting_files;} + + + /** + * Test all files and see if they are not missing. + * If so put them in a list + */ + virtual bool hasMissingFiles(QStringList & sl) = 0; + + /** + * Delete all data files, in case of multi file torrents + * empty directories should also be deleted. + */ + virtual void deleteDataFiles() = 0; + + /** + * See if we are allowed to use mmap, when loading chunks. + * This will return false if we are close to system limits. + */ + static bool mappedModeAllowed(); + + /** + * Get the number of bytes all the files of this torrent are currently using on disk. + * */ + virtual Uint64 diskUsage() = 0; + }; + +} + +#endif diff --git a/libktorrent/torrent/cachefile.cpp b/libktorrent/torrent/cachefile.cpp new file mode 100644 index 0000000..6367b7f --- /dev/null +++ b/libktorrent/torrent/cachefile.cpp @@ -0,0 +1,507 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + + +#include <fcntl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/mman.h> +#include <unistd.h> +#include <errno.h> +#include <qfile.h> +#include <kio/netaccess.h> +#include <klocale.h> +#include <kfileitem.h> +#include <util/array.h> +#include <util/fileops.h> +#include <torrent/globals.h> +#include <interfaces/functions.h> +#include <kapplication.h> +#include <util/log.h> +#include <util/error.h> +#include "cachefile.h" +#include "preallocationthread.h" +#include "settings.h" + + +// Not all systems have an O_LARGEFILE - Solaris depending +// on command-line defines, FreeBSD never - so in those cases, +// make it a zero bitmask. As long as it's only OR'ed into +// open(2) flags, that's fine. +// +#ifndef O_LARGEFILE +#define O_LARGEFILE (0) +#endif + + + + +namespace bt +{ + + CacheFile::CacheFile() : fd(-1),max_size(0),file_size(0),mutex(true) + { + read_only = false; + } + + + CacheFile::~CacheFile() + { + if (fd != -1) + close(); + } + + void CacheFile::changePath(const QString & npath) + { + path = npath; + } + + void CacheFile::openFile(Mode mode) + { + int flags = O_LARGEFILE; + + // by default allways try read write + fd = ::open(QFile::encodeName(path),flags | O_RDWR); + if (fd < 0 && mode == READ) + { + // in case RDWR fails, try readonly if possible + fd = ::open(QFile::encodeName(path),flags | O_RDONLY); + if (fd >= 0) + read_only = true; + } + + if (fd < 0) + { + throw Error(i18n("Cannot open %1 : %2").arg(path).arg(strerror(errno))); + } + + file_size = FileSize(fd); + } + + void CacheFile::open(const QString & path,Uint64 size) + { + QMutexLocker lock(&mutex); + // only set the path and the max size, we only open the file when it is needed + this->path = path; + max_size = size; + } + + void* CacheFile::map(MMappeable* thing,Uint64 off,Uint32 size,Mode mode) + { + QMutexLocker lock(&mutex); + // reopen the file if necessary + if (fd == -1) + { + // Out() << "Reopening " << path << endl; + openFile(mode); + } + + if (read_only && mode != READ) + { + throw Error(i18n("Cannot open %1 for writing : readonly filesystem").arg(path)); + } + + if (off + size > max_size) + { + Out() << "Warning : writing past the end of " << path << endl; + Out() << (off + size) << " " << max_size << endl; + return 0; + } + + int mmap_flag = 0; + switch (mode) + { + case READ: + mmap_flag = PROT_READ; + break; + case WRITE: + mmap_flag = PROT_WRITE; + break; + case RW: + mmap_flag = PROT_READ|PROT_WRITE; + break; + } + + if (off + size > file_size) + { + Uint64 to_write = (off + size) - file_size; + // Out() << "Growing file with " << to_write << " bytes" << endl; + growFile(to_write); + } + + Uint32 page_size = sysconf(_SC_PAGESIZE); + if (off % page_size > 0) + { + // off is not a multiple of the page_size + // so we play around a bit + Uint32 diff = (off % page_size); + Uint64 noff = off - diff; + // Out() << "Offsetted mmap : " << diff << endl; +#if HAVE_MMAP64 + char* ptr = (char*)mmap64(0, size + diff, mmap_flag, MAP_SHARED, fd, noff); +#else + char* ptr = (char*)mmap(0, size + diff, mmap_flag, MAP_SHARED, fd, noff); +#endif + if (ptr == MAP_FAILED) + { + Out() << "mmap failed : " << QString(strerror(errno)) << endl; + return 0; + } + else + { + CacheFile::Entry e; + e.thing = thing; + e.offset = off; + e.diff = diff; + e.ptr = ptr; + e.size = size + diff; + e.mode = mode; + mappings.insert((void*)(ptr + diff),e); + return ptr + diff; + } + } + else + { +#if HAVE_MMAP64 + void* ptr = mmap64(0, size, mmap_flag, MAP_SHARED, fd, off); +#else + void* ptr = mmap(0, size, mmap_flag, MAP_SHARED, fd, off); +#endif + if (ptr == MAP_FAILED) + { + Out() << "mmap failed : " << QString(strerror(errno)) << endl; + return 0; + } + else + { + CacheFile::Entry e; + e.thing = thing; + e.offset = off; + e.ptr = ptr; + e.diff = 0; + e.size = size; + e.mode = mode; + mappings.insert(ptr,e); + return ptr; + } + } + } + + void CacheFile::growFile(Uint64 to_write) + { + // reopen the file if necessary + if (fd == -1) + { + // Out() << "Reopening " << path << endl; + openFile(RW); + } + + if (read_only) + throw Error(i18n("Cannot open %1 for writing : readonly filesystem").arg(path)); + + // jump to the end of the file + SeekFile(fd,0,SEEK_END); + + if (file_size + to_write > max_size) + { + Out() << "Warning : writing past the end of " << path << endl; + Out() << (file_size + to_write) << " " << max_size << endl; + } + + Uint8 buf[1024]; + memset(buf,0,1024); + Uint64 num = to_write; + // write data until to_write is 0 + while (to_write > 0) + { + int nb = to_write > 1024 ? 1024 : to_write; + int ret = ::write(fd,buf,nb); + if (ret < 0) + throw Error(i18n("Cannot expand file %1 : %2").arg(path).arg(strerror(errno))); + else if (ret != nb) + throw Error(i18n("Cannot expand file %1 : incomplete write").arg(path)); + to_write -= nb; + } + file_size += num; +// + // Out() << QString("growing %1 = %2").arg(path).arg(kt::BytesToString(file_size)) << endl; + + if (file_size != FileSize(fd)) + { +// Out() << QString("Homer Simpson %1 %2").arg(file_size).arg(sb.st_size) << endl; + fsync(fd); + if (file_size != FileSize(fd)) + { + throw Error(i18n("Cannot expand file %1").arg(path)); + } + } + } + + void CacheFile::unmap(void* ptr,Uint32 size) + { + int ret = 0; + QMutexLocker lock(&mutex); + // see if it wasn't an offsetted mapping + if (mappings.contains(ptr)) + { + CacheFile::Entry & e = mappings[ptr]; +#if HAVE_MUNMAP64 + if (e.diff > 0) + ret = munmap64((char*)ptr - e.diff,e.size); + else + ret = munmap64(ptr,e.size); +#else + if (e.diff > 0) + ret = munmap((char*)ptr - e.diff,e.size); + else + ret = munmap(ptr,e.size); +#endif + mappings.erase(ptr); + // no mappings, close temporary + if (mappings.count() == 0) + closeTemporary(); + } + else + { +#if HAVE_MUNMAP64 + ret = munmap64(ptr,size); +#else + ret = munmap(ptr,size); +#endif + } + + if (ret < 0) + { + Out(SYS_DIO|LOG_IMPORTANT) << QString("Munmap failed with error %1 : %2").arg(errno).arg(strerror(errno)) << endl; + } + } + + void CacheFile::close() + { + QMutexLocker lock(&mutex); + + if (fd == -1) + return; + + QMap<void*,Entry>::iterator i = mappings.begin(); + while (i != mappings.end()) + { + int ret = 0; + CacheFile::Entry & e = i.data(); +#if HAVE_MUNMAP64 + if (e.diff > 0) + ret = munmap64((char*)e.ptr - e.diff,e.size); + else + ret = munmap64(e.ptr,e.size); +#else + if (e.diff > 0) + ret = munmap((char*)e.ptr - e.diff,e.size); + else + ret = munmap(e.ptr,e.size); +#endif + e.thing->unmapped(); + + i++; + mappings.erase(e.ptr); + + if (ret < 0) + { + Out(SYS_DIO|LOG_IMPORTANT) << QString("Munmap failed with error %1 : %2").arg(errno).arg(strerror(errno)) << endl; + } + } + ::close(fd); + fd = -1; + } + + void CacheFile::read(Uint8* buf,Uint32 size,Uint64 off) + { + QMutexLocker lock(&mutex); + bool close_again = false; + + // reopen the file if necessary + if (fd == -1) + { + // Out() << "Reopening " << path << endl; + openFile(READ); + close_again = true; + } + + if (off >= file_size || off >= max_size) + { + throw Error(i18n("Error : Reading past the end of the file %1").arg(path)); + } + + // jump to right position + SeekFile(fd,(Int64)off,SEEK_SET); + if ((Uint32)::read(fd,buf,size) != size) + { + if (close_again) + closeTemporary(); + + throw Error(i18n("Error reading from %1").arg(path)); + } + + if (close_again) + closeTemporary(); + } + + void CacheFile::write(const Uint8* buf,Uint32 size,Uint64 off) + { + QMutexLocker lock(&mutex); + bool close_again = false; + + // reopen the file if necessary + if (fd == -1) + { + // Out() << "Reopening " << path << endl; + openFile(RW); + close_again = true; + } + + if (read_only) + throw Error(i18n("Cannot open %1 for writing : readonly filesystem").arg(path)); + + if (off + size > max_size) + { + Out() << "Warning : writing past the end of " << path << endl; + Out() << (off + size) << " " << max_size << endl; + } + + if (file_size < off) + { + //Out() << QString("Writing %1 bytes at %2").arg(size).arg(off) << endl; + growFile(off - file_size); + } + + // jump to right position + SeekFile(fd,(Int64)off,SEEK_SET); + int ret = ::write(fd,buf,size); + if (close_again) + closeTemporary(); + + if (ret == -1) + throw Error(i18n("Error writing to %1 : %2").arg(path).arg(strerror(errno))); + else if ((Uint32)ret != size) + { + Out() << QString("Incomplete write of %1 bytes, should be %2").arg(ret).arg(size) << endl; + throw Error(i18n("Error writing to %1").arg(path)); + } + + if (off + size > file_size) + file_size = off + size; + } + + void CacheFile::closeTemporary() + { + if (fd == -1 || mappings.count() > 0) + return; + + ::close(fd); + fd = -1; + } + + + + void CacheFile::preallocate(PreallocationThread* prealloc) + { + QMutexLocker lock(&mutex); + + if (FileSize(path) == max_size) + { + Out(SYS_GEN|LOG_NOTICE) << "File " << path << " already big enough" << endl; + return; + } + + Out(SYS_GEN|LOG_NOTICE) << "Preallocating file " << path << " (" << max_size << " bytes)" << endl; + bool close_again = false; + if (fd == -1) + { + openFile(RW); + close_again = true; + } + + if (read_only) + { + if (close_again) + closeTemporary(); + + throw Error(i18n("Cannot open %1 for writing : readonly filesystem").arg(path)); + } + + try + { + bool res = false; + + #ifdef HAVE_XFS_XFS_H + if( (! res) && Settings::fullDiskPrealloc() && (Settings::fullDiskPreallocMethod() == 1) ) + { + res = XfsPreallocate(fd, max_size); + } + #endif + + if(! res) + { + bt::TruncateFile(fd,max_size,!Settings::fullDiskPrealloc()); + } + } + catch (bt::Error & e) + { + // first attempt failed, must be fat so try that + if (!FatPreallocate(fd,max_size)) + { + if (close_again) + closeTemporary(); + + throw Error(i18n("Cannot preallocate diskspace : %1").arg(strerror(errno))); + } + } + + file_size = FileSize(fd); + Out(SYS_GEN|LOG_DEBUG) << "file_size = " << file_size << endl; + if (close_again) + closeTemporary(); + } + + Uint64 CacheFile::diskUsage() + { + Uint64 ret = 0; + bool close_again = false; + if (fd == -1) + { + openFile(READ); + close_again = true; + } + + struct stat sb; + if (fstat(fd,&sb) == 0) + { + ret = (Uint64)sb.st_blocks * 512; + } + + // Out(SYS_GEN|LOG_NOTICE) << "CF: " << path << " is taking up " << ret << " bytes" << endl; + if (close_again) + closeTemporary(); + + return ret; + } +} diff --git a/libktorrent/torrent/cachefile.h b/libktorrent/torrent/cachefile.h new file mode 100644 index 0000000..9c4ebc6 --- /dev/null +++ b/libktorrent/torrent/cachefile.h @@ -0,0 +1,149 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef BTCACHEFILE_H +#define BTCACHEFILE_H + +#include <qmap.h> +#include <qmutex.h> +#include <qstring.h> +#include <util/constants.h> + +namespace bt +{ + class PreallocationThread; + + + /** + * Interface which classes must implement to be able to map something from a CacheFile + * It will also be used to notify when things get unmapped or remapped + */ + class MMappeable + { + public: + virtual ~MMappeable() {} + + /** + * When a CacheFile is closed, this will be called on all existing mappings. + */ + virtual void unmapped() = 0; + }; + + /** + @author Joris Guisson <joris.guisson@gmail.com> + + Used by Single and MultiFileCache to write to disk. + */ + class CacheFile + { + public: + CacheFile(); + virtual ~CacheFile(); + + enum Mode + { + READ,WRITE,RW + }; + + + /** + * Open the file. + * @param path Path of the file + * @param size Max size of the file + * @throw Error when something goes wrong + */ + void open(const QString & path,Uint64 size); + + /// Change the path of the file + void changePath(const QString & npath); + + /** + * Map a part of the file into memory, will expand the file + * if it is to small, but will not go past the limit set in open. + * @param thing The thing that wishes to map the mmapping + * @param off Offset into the file + * @param size Size of the region to map + * @param mode How the region will be mapped + * @return A ptr to the mmaped region, or 0 if something goes wrong + */ + void* map(MMappeable* thing,Uint64 off,Uint32 size,Mode mode); + + /** + * Unmap a previously mapped region. + * @param ptr Ptr to the region + * @param size Size of the region + */ + void unmap(void* ptr,Uint32 size); + + /** + * Close the file, everything will be unmapped. + * @param to_be_reopened Indicates if the close is temporarely (i.e. it will be reopened) + */ + void close(); + + /** + * Read from the file. + * @param buf Buffer to store data + * @param size Size to read + * @param off Offset to read from in file + */ + void read(Uint8* buf,Uint32 size,Uint64 off); + + /** + * Write to the file. + * @param buf Buffer to write + * @param size Size to read + * @param off Offset to read from in file + */ + void write(const Uint8* buf,Uint32 size,Uint64 off); + + /** + * Preallocate disk space + */ + void preallocate(PreallocationThread* prealloc); + + /// Get the number of bytes this cache file is taking up + Uint64 diskUsage(); + + private: + void growFile(Uint64 to_write); + void closeTemporary(); + void openFile(Mode mode); + + private: + int fd; + bool read_only; + Uint64 max_size,file_size; + QString path; + struct Entry + { + MMappeable* thing; + void* ptr; + Uint32 size; + Uint64 offset; + Uint32 diff; + Mode mode; + }; + QMap<void*,Entry> mappings; // mappings where offset wasn't a multiple of 4K + mutable QMutex mutex; + }; + +} + +#endif diff --git a/libktorrent/torrent/cap.cpp b/libktorrent/torrent/cap.cpp new file mode 100644 index 0000000..a785520 --- /dev/null +++ b/libktorrent/torrent/cap.cpp @@ -0,0 +1,123 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#if 0 +#include <math.h> +#include "cap.h" + +namespace bt +{ + typedef QValueList<Cap::Entry>::iterator CapItr; + + Cap::Cap(bool percentage_check) : max_bytes_per_sec(0),leftover(0),current_speed(0),percentage_check(percentage_check) + { + timer.update(); + } + + + Cap::~Cap() + {} + + void Cap::setMaxSpeed(Uint32 max) + { + max_bytes_per_sec = max; + // tell everybody to go wild + if (max_bytes_per_sec == 0) + { + CapItr i = entries.begin(); + while (i != entries.end()) + { + Cap::Entry & e = *i; + e.obj->proceed(0); + i++; + } + entries.clear(); + leftover = 0; + } + } + + bool Cap::allow(Cappable* pd,Uint32 bytes) + { + if (max_bytes_per_sec == 0 || (percentage_check && (double)current_speed / (double)max_bytes_per_sec < 0.75)) + { + timer.update(); + return true; + } + + // append pd to queue + entries.append(Cap::Entry(pd,bytes)); + return false; + } + + void Cap::killed(Cappable* pd) + { + CapItr i = entries.begin(); + while (i != entries.end()) + { + Cap::Entry & e = *i; + if (e.obj == pd) + i = entries.erase(i); + else + i++; + } + } + + void Cap::update() + { + if (entries.count() == 0) + { + timer.update(); + return; + } + + // first calculate the time since the last update + double el = timer.getElapsedSinceUpdate(); + + // calculate the number of bytes we can send, including those leftover from the last time + Uint32 nb = (Uint32)round((el / 1000.0) * max_bytes_per_sec) + leftover; + leftover = 0; + // Out() << "nb = " << nb << endl; + + while (entries.count() > 0 && nb > 0) + { + // get the first + Cap::Entry & e = entries.first(); + + if (e.num_bytes <= nb) + { + nb -= e.num_bytes; + // we can send all remaining bytes of the packet + e.obj->proceed(e.num_bytes); + entries.pop_front(); + } + else + { + // sent nb bytes of the packets + e.obj->proceed(nb); + e.num_bytes -= nb; + nb = 0; + } + } + + leftover = nb; + timer.update(); + } + +} +#endif diff --git a/libktorrent/torrent/cap.h b/libktorrent/torrent/cap.h new file mode 100644 index 0000000..a3a365e --- /dev/null +++ b/libktorrent/torrent/cap.h @@ -0,0 +1,113 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef BTCAP_H +#define BTCAP_H + +#if 0 +#include <qvaluelist.h> +#include <util/timer.h> +#include <util/constants.h> + +namespace bt +{ + /** + * Base class for all cappable objects. + */ + class Cappable + { + public: + /** + * Proceed with doing some bytes + * @param bytes The number of bytes it can do (0 = no limit) + * @return true if finished, false otherwise + */ + virtual void proceed(Uint32 bytes) = 0; + }; + + /** + * @author Joris Guisson <joris.guisson@gmail.com> + * + * A Cap is something which caps something. + */ + class Cap + { + public: + Cap(bool percentage_check); + virtual ~Cap(); + + struct Entry + { + Cappable* obj; + Uint32 num_bytes; + + Entry() : obj(0),num_bytes(0) {} + Entry(Cappable* obj,Uint32 nb) : obj(obj),num_bytes(nb) {} + }; + + /** + * Set the speed cap in bytes per second. 0 indicates + * no limit. + * @param max Maximum number of bytes per second. + */ + void setMaxSpeed(Uint32 max); + + /// Get max bytes/sec + Uint32 getMaxSpeed() const {return max_bytes_per_sec;} + + /// Set the current speed + void setCurrentSpeed(Uint32 cs) {current_speed = cs;} + + /// Get the current speed + Uint32 getCurrrentSpeed() const {return current_speed;} + + /** + * Allow or disallow somebody from proceeding. If somebody + * is disallowed they will be stored in a queue, and will be notified + * when there turn is up. + * @param pd Thing which is doing the request + * @param bytes Bytes it wants to send + * @return true if the piece is allowed or not + */ + bool allow(Cappable* pd,Uint32 bytes); + + /** + * A thing in the queue should call this when it get destroyed. To + * remove them from the queue. + * @param pd The Cappable thing + */ + void killed(Cappable* pd); + + /** + * Update the downloadcap. + */ + void update(); + + private: + QValueList<Entry> entries; + Uint32 max_bytes_per_sec; + Timer timer; + Uint32 leftover; + Uint32 current_speed; + bool percentage_check; + }; + +} +#endif +#endif diff --git a/libktorrent/torrent/choker.cpp b/libktorrent/torrent/choker.cpp new file mode 100644 index 0000000..0cb08e9 --- /dev/null +++ b/libktorrent/torrent/choker.cpp @@ -0,0 +1,86 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <qptrlist.h> +#include <interfaces/functions.h> +#include "choker.h" +#include "peermanager.h" +#include "newchokealgorithm.h" +#include "advancedchokealgorithm.h" + +using namespace kt; + +namespace bt +{ + + PeerPtrList::PeerPtrList(PeerCompareFunc pcmp) : pcmp(pcmp) + {} + + PeerPtrList::~PeerPtrList() + {} + + int PeerPtrList::compareItems(QPtrCollection::Item a, QPtrCollection::Item b) + { + if (pcmp) + return pcmp((Peer*)a,(Peer*)b); + else + return CompareVal(a,b); + } + + //////////////////////////////////////////// + + ChokeAlgorithm::ChokeAlgorithm() : opt_unchoked_peer_id(0) + { + } + + ChokeAlgorithm::~ChokeAlgorithm() + { + } + + + ///////////////////////////////// + + Uint32 Choker::num_upload_slots = 2; + + Choker::Choker(PeerManager & pman,ChunkManager & cman) : pman(pman),cman(cman) + { +#ifdef USE_OLD_CHOKE + choke = new NewChokeAlgorithm(); +#else + choke = new AdvancedChokeAlgorithm(); +#endif + } + + + Choker::~Choker() + { + delete choke; + } + + void Choker::update(bool have_all,const kt::TorrentStats & stats) + { + if (have_all) + choke->doChokingSeedingState(pman,cman,stats); + else + choke->doChokingLeechingState(pman,cman,stats); + } + +} diff --git a/libktorrent/torrent/choker.h b/libktorrent/torrent/choker.h new file mode 100644 index 0000000..ba78f3c --- /dev/null +++ b/libktorrent/torrent/choker.h @@ -0,0 +1,123 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef BTCHOKER_H +#define BTCHOKER_H + +#include <qptrlist.h> +#include <util/constants.h> +#include "peer.h" + +namespace kt +{ + struct TorrentStats; +} + +namespace bt +{ + const Uint32 UNDEFINED_ID = 0xFFFFFFFF; + + class PeerManager; + class ChunkManager; + + + typedef int (*PeerCompareFunc)(Peer* a,Peer* b); + + class PeerPtrList : public QPtrList<Peer> + { + PeerCompareFunc pcmp; + public: + PeerPtrList(PeerCompareFunc pcmp = NULL); + virtual ~PeerPtrList(); + + void setCompareFunc(PeerCompareFunc p) {pcmp = p;} + + virtual int compareItems(QPtrCollection::Item a, QPtrCollection::Item b); + }; + + /** + * Base class for all choke algorithms. + */ + class ChokeAlgorithm + { + protected: + Uint32 opt_unchoked_peer_id; + public: + ChokeAlgorithm(); + virtual ~ChokeAlgorithm(); + + /** + * Do the actual choking when we are still downloading. + * @param pman The PeerManager + * @param cman The ChunkManager + * @param stats The torrent stats + */ + virtual void doChokingLeechingState(PeerManager & pman,ChunkManager & cman,const kt::TorrentStats & stats) = 0; + + /** + * Do the actual choking when we are seeding + * @param pman The PeerManager + * @param cman The ChunkManager + * @param stats The torrent stats + */ + virtual void doChokingSeedingState(PeerManager & pman,ChunkManager & cman,const kt::TorrentStats & stats) = 0; + + /// Get the optimisticly unchoked peer ID + Uint32 getOptimisticlyUnchokedPeerID() const {return opt_unchoked_peer_id;} + }; + + + + /** + * @author Joris Guisson + * @brief Handles the choking + * + * This class handles the choking and unchoking of Peer's. + * This class needs to be updated every 10 seconds. + */ + class Choker + { + ChokeAlgorithm* choke; + PeerManager & pman; + ChunkManager & cman; + static Uint32 num_upload_slots; + public: + Choker(PeerManager & pman,ChunkManager & cman); + virtual ~Choker(); + + /** + * Update which peers are choked or not. + * @param have_all Indicates wether we have the entire file + * @param stats Statistic of the torrent + */ + void update(bool have_all,const kt::TorrentStats & stats); + + /// Get the PeerID of the optimisticly unchoked peer. + Uint32 getOptimisticlyUnchokedPeerID() const {return choke->getOptimisticlyUnchokedPeerID();} + + /// Set the number of upload slots + static void setNumUploadSlots(Uint32 n) {num_upload_slots = n;} + + /// Get the number of upload slots + static Uint32 getNumUploadSlots() {return num_upload_slots;} + }; + +} + +#endif diff --git a/libktorrent/torrent/chunk.cpp b/libktorrent/torrent/chunk.cpp new file mode 100644 index 0000000..6873713 --- /dev/null +++ b/libktorrent/torrent/chunk.cpp @@ -0,0 +1,81 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <util/sha1hash.h> +#include "chunk.h" +#include "globals.h" + + +namespace bt +{ + + Chunk::Chunk(unsigned int index,Uint32 size) + : status(Chunk::NOT_DOWNLOADED),index(index), + data(0),size(size),ref_count(0),priority(NORMAL_PRIORITY) + { + } + + + Chunk::~Chunk() + { + clear(); + } + + void Chunk::setData(Uint8* d,Status nstatus) + { + clear(); + status = nstatus; + data = d; + } + + void Chunk::allocate() + { + clear(); + status = BUFFERED; + data = new Uint8[size]; + } + + void Chunk::clear() + { + if (data) + { + if (status == BUFFERED) + delete [] data; + data = 0; + } + } + + void Chunk::unmapped() + { + setData(0,Chunk::ON_DISK); + } + + bool Chunk::checkHash(const SHA1Hash & h) const + { + if (status != BUFFERED && status != MMAPPED) + { + return false; + } + else + { + return SHA1Hash::generate(data,size) == h; + } + } + +} diff --git a/libktorrent/torrent/chunk.h b/libktorrent/torrent/chunk.h new file mode 100644 index 0000000..0896e96 --- /dev/null +++ b/libktorrent/torrent/chunk.h @@ -0,0 +1,165 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef BTCHUNK_H +#define BTCHUNK_H + +#include <util/constants.h> +#include "cachefile.h" + +namespace bt +{ + class SHA1Hash; + + /** + * @author Joris Guisson + * @brief Keep track of a piece of the file + * + * Keeps track of a piece of the file. The Chunk has 3 possible states : + * - MMAPPED : It is memory mapped + * - BUFFERED : It is in a buffer in dynamically allocated memory + * (because the chunk is located in 2 or more separate files, so we cannot just set a pointer + * to a region of mmapped memory) + * - ON_DISK : On disk + * - NOT_DOWNLOADED : It hasn't been dowloaded yet, and there is no buffer allocated + */ + class Chunk : public MMappeable + { + public: + Chunk(unsigned int index,Uint32 size); + ~Chunk(); + + enum Status + { + MMAPPED, + BUFFERED, + ON_DISK, + NOT_DOWNLOADED + }; + + /// Get the chunks status. + Status getStatus() const; + + /** + * Set the chunks status + * @param s + */ + void setStatus(Status s); + + /// Get the data + const Uint8* getData() const; + + /// Get the data + Uint8* getData(); + + /// Set the data and the new status + void setData(Uint8* d,Status nstatus); + + /// Clear the chunk (delete data depending on the mode) + void clear(); + + /// Get the chunk's index + Uint32 getIndex() const; + + /// Get the chunk's size + Uint32 getSize() const; + + /// Add one to the reference counter + void ref(); + + /// --reference counter + void unref(); + + /// reference coun > 0 + bool taken() const; + + /// allocate data if not already done, sets the status to buffered + void allocate(); + + /// get chunk priority + Priority getPriority() const; + + /// set chunk priority + void setPriority(Priority newpriority = NORMAL_PRIORITY); + + /// Is chunk excluded + bool isExcluded() const; + + /// Is this a seed only chunk + bool isExcludedForDownloading() const; + + /// In/Exclude chunk + void setExclude(bool yes); + + /** + * Check wehter the chunk matches it's hash. + * @param h The hash + * @return true if the data matches the hash + */ + bool checkHash(const SHA1Hash & h) const; + + private: + virtual void unmapped(); + + private: + Status status; + Uint32 index; + Uint8* data; + Uint32 size; + int ref_count; + Priority priority; + }; + + inline Chunk::Status Chunk::getStatus() const + { + return status; + } + + inline void Chunk::setStatus(Chunk::Status s) + { + status = s; + } + + inline const Uint8* Chunk::getData() const {return data;} + inline Uint8* Chunk::getData() {return data;} + + inline Uint32 Chunk::getIndex() const {return index;} + inline Uint32 Chunk::getSize() const {return size;} + + inline void Chunk::ref() {ref_count++;} + inline void Chunk::unref() {ref_count--;} + inline bool Chunk::taken() const {return ref_count > 0;} + + inline Priority Chunk::getPriority() const {return priority;} + inline void Chunk::setPriority(Priority newpriority) {priority = newpriority;} + inline bool Chunk::isExcluded() const + { + return priority == EXCLUDED; + } + + inline bool Chunk::isExcludedForDownloading() const + { + return priority == ONLY_SEED_PRIORITY; + } + + inline void Chunk::setExclude(bool yes) + {if(yes) priority = EXCLUDED; else priority = NORMAL_PRIORITY;} +} + +#endif diff --git a/libktorrent/torrent/chunkcounter.cpp b/libktorrent/torrent/chunkcounter.cpp new file mode 100644 index 0000000..95b7535 --- /dev/null +++ b/libktorrent/torrent/chunkcounter.cpp @@ -0,0 +1,80 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <util/bitset.h> +#include "chunkcounter.h" + +namespace bt +{ + + ChunkCounter::ChunkCounter(Uint32 num_chunks) : cnt(num_chunks) + { + // fill with 0 + cnt.fill(0); + } + + + ChunkCounter::~ChunkCounter() + { + } + + void ChunkCounter::reset() + { + cnt.fill(0); + } + + void ChunkCounter::incBitSet(const BitSet & bs) + { + for (Uint32 i = 0;i < cnt.size();i++) + { + if(bs.get(i)) + cnt[i]++; + } + } + + void ChunkCounter::decBitSet(const BitSet & bs) + { + for (Uint32 i = 0;i < cnt.size();i++) + { + if(bs.get(i)) + dec(i); + } + } + + void ChunkCounter::inc(Uint32 idx) + { + if (idx < cnt.size()) + cnt[idx]++; + } + + void ChunkCounter::dec(Uint32 idx) + { + if (idx < cnt.size() && cnt[idx] > 0) + cnt[idx]--; + } + + Uint32 ChunkCounter::get(Uint32 idx) const + { + if (idx < cnt.size()) + return cnt[idx]; + else + return 0; + } + +} diff --git a/libktorrent/torrent/chunkcounter.h b/libktorrent/torrent/chunkcounter.h new file mode 100644 index 0000000..ac2ec49 --- /dev/null +++ b/libktorrent/torrent/chunkcounter.h @@ -0,0 +1,83 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef BTCHUNKCOUNTER_H +#define BTCHUNKCOUNTER_H + +#include <util/constants.h> +#include <util/array.h> + +namespace bt +{ + class BitSet; + + /** + * @author Joris Guisson + * + * Class to keep track of how many peers have a chunk. + */ + class ChunkCounter + { + Array<Uint32> cnt; + public: + ChunkCounter(Uint32 num_chunks); + virtual ~ChunkCounter(); + + /** + * If a bit in the bitset is one, increment the corresponding counter. + * @param bs The BitSet + */ + void incBitSet(const BitSet & bs); + + + /** + * If a bit in the bitset is one, decrement the corresponding counter. + * @param bs The BitSet + */ + void decBitSet(const BitSet & bs); + + /** + * Increment the counter for the idx'th chunk + * @param idx Index of the chunk + */ + void inc(Uint32 idx); + + + /** + * Decrement the counter for the idx'th chunk + * @param idx Index of the chunk + */ + void dec(Uint32 idx); + + + /** + * Get the counter for the idx'th chunk + * @param idx Index of the chunk + */ + Uint32 get(Uint32 idx) const; + + /** + * Reset all values to 0 + */ + void reset(); + }; + +} + +#endif diff --git a/libktorrent/torrent/chunkdownload.cpp b/libktorrent/torrent/chunkdownload.cpp new file mode 100644 index 0000000..51e9db9 --- /dev/null +++ b/libktorrent/torrent/chunkdownload.cpp @@ -0,0 +1,484 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <algorithm> +#include <util/file.h> +#include <util/log.h> +#include <util/array.h> +#include "chunkdownload.h" +#include "downloader.h" +#include "chunk.h" +#include "peer.h" +#include "peermanager.h" +#include "piece.h" +#include "peerdownloader.h" + +#include <klocale.h> + +namespace bt +{ + + class DownloadStatus : public std::set<Uint32> + { + public: + // typedef std::set<Uint32>::iterator iterator; + + DownloadStatus() + { + + } + + ~DownloadStatus() + { + } + + void add(Uint32 p) + { + insert(p); + } + + void remove(Uint32 p) + { + erase(p); + } + + bool contains(Uint32 p) + { + return count(p) > 0; + } + }; + + ChunkDownload::ChunkDownload(Chunk* chunk) : chunk(chunk) + { + num = num_downloaded = 0; + + num = chunk->getSize() / MAX_PIECE_LEN; + + if (chunk->getSize() % MAX_PIECE_LEN != 0) + { + last_size = chunk->getSize() % MAX_PIECE_LEN; + num++; + } + else + { + last_size = MAX_PIECE_LEN; + } + + pieces = BitSet(num); + pieces.clear(); + + for (Uint32 i = 0;i < num;i++) + piece_queue.append(i); + + dstatus.setAutoDelete(true); + chunk->ref(); + + num_pieces_in_hash = 0; + if (usingContinuousHashing()) + hash_gen.start(); + + } + + ChunkDownload::~ChunkDownload() + { + chunk->unref(); + } + + bool ChunkDownload::piece(const Piece & p,bool & ok) + { + ok = false; + timer.update(); + + Uint32 pp = p.getOffset() / MAX_PIECE_LEN; + if (pieces.get(pp)) + return false; + + + DownloadStatus* ds = dstatus.find(p.getPeer()); + if (ds) + ds->remove(pp); + + Uint8* buf = chunk->getData(); + if (buf) + { + ok = true; + memcpy(buf + p.getOffset(),p.getData(),p.getLength()); + pieces.set(pp,true); + piece_queue.remove(pp); + piece_providers.insert(p.getPeer()); + num_downloaded++; + if (pdown.count() > 1) + { + endgameCancel(p); + } + + if (usingContinuousHashing()) + updateHash(); + + if (num_downloaded >= num) + { + // finalize hash + if (usingContinuousHashing()) + hash_gen.end(); + + releaseAllPDs(); + return true; + } + } + + for (QPtrList<PeerDownloader>::iterator i = pdown.begin();i != pdown.end();++i) + sendRequests(*i); + + return false; + } + + void ChunkDownload::releaseAllPDs() + { + for (Uint32 i = 0;i < pdown.count();i++) + { + PeerDownloader* pd = pdown.at(i); + pd->release(); + disconnect(pd,SIGNAL(timedout(const Request& )),this,SLOT(onTimeout(const Request& ))); + disconnect(pd,SIGNAL(rejected( const Request& )),this,SLOT(onRejected( const Request& ))); + } + dstatus.clear(); + pdown.clear(); + } + + bool ChunkDownload::assignPeer(PeerDownloader* pd) + { + if (!pd || pdown.contains(pd)) + return false; + + pd->grab(); + pdown.append(pd); + dstatus.insert(pd->getPeer()->getID(),new DownloadStatus()); + sendRequests(pd); + connect(pd,SIGNAL(timedout(const Request& )),this,SLOT(onTimeout(const Request& ))); + connect(pd,SIGNAL(rejected( const Request& )),this,SLOT(onRejected( const Request& ))); + return true; + } + + void ChunkDownload::notDownloaded(const Request & r,bool reject) + { + // find the peer + DownloadStatus* ds = dstatus.find(r.getPeer()); + if (ds) + { + // Out() << "ds != 0" << endl; + Uint32 p = r.getOffset() / MAX_PIECE_LEN; + ds->remove(p); + } + + // go over all PD's and do requets again + for (QPtrList<PeerDownloader>::iterator i = pdown.begin();i != pdown.end();++i) + sendRequests(*i); + } + + void ChunkDownload::onRejected(const Request & r) + { + if (chunk->getIndex() == r.getIndex()) + { +// Out(SYS_CON|LOG_DEBUG) << QString("Request rejected %1 %2 %3 %4").arg(r.getIndex()).arg(r.getOffset()).arg(r.getLength()).arg(r.getPeer()) << endl; + + notDownloaded(r,true); + } + } + + void ChunkDownload::onTimeout(const Request & r) + { + // see if we are dealing with a piece of ours + if (chunk->getIndex() == r.getIndex()) + { + Out(SYS_CON|LOG_DEBUG) << QString("Request timed out %1 %2 %3 %4").arg(r.getIndex()).arg(r.getOffset()).arg(r.getLength()).arg(r.getPeer()) << endl; + + notDownloaded(r,false); + } + } + + void ChunkDownload::sendRequests(PeerDownloader* pd) + { + timer.update(); + DownloadStatus* ds = dstatus.find(pd->getPeer()->getID()); + if (!ds) + return; + + // if the peer is choked and we are not downloading an allowed fast chunk + if (pd->isChoked()) + return; + + Uint32 num_visited = 0; + while (num_visited < piece_queue.count() && pd->canAddRequest()) + { + // get the first one in the queue + Uint32 i = piece_queue.first(); + if (!ds->contains(i)) + { + // send request + pd->download( + Request( + chunk->getIndex(), + i*MAX_PIECE_LEN, + i+1<num ? MAX_PIECE_LEN : last_size, + pd->getPeer()->getID())); + ds->add(i); + } + // move to the back so that it will take a while before it's turn is up + piece_queue.pop_front(); + piece_queue.append(i); + num_visited++; + } + + if (piece_queue.count() < 2 && piece_queue.count() > 0) + pd->setNearlyDone(true); + } + + + + void ChunkDownload::update() + { + // go over all PD's and do requets again + for (QPtrList<PeerDownloader>::iterator i = pdown.begin();i != pdown.end();++i) + sendRequests(*i); + } + + + void ChunkDownload::sendCancels(PeerDownloader* pd) + { + DownloadStatus* ds = dstatus.find(pd->getPeer()->getID()); + if (!ds) + return; + + DownloadStatus::iterator itr = ds->begin(); + while (itr != ds->end()) + { + Uint32 i = *itr; + pd->cancel( + Request( + chunk->getIndex(), + i*MAX_PIECE_LEN, + i+1<num ? MAX_PIECE_LEN : last_size,0)); + itr++; + } + ds->clear(); + timer.update(); + } + + void ChunkDownload::endgameCancel(const Piece & p) + { + QPtrList<PeerDownloader>::iterator i = pdown.begin(); + while (i != pdown.end()) + { + PeerDownloader* pd = *i; + DownloadStatus* ds = dstatus.find(pd->getPeer()->getID()); + Uint32 pp = p.getOffset() / MAX_PIECE_LEN; + if (ds && ds->contains(pp)) + { + pd->cancel(Request(p)); + ds->remove(pp); + } + i++; + } + } + + void ChunkDownload::peerKilled(PeerDownloader* pd) + { + if (!pdown.contains(pd)) + return; + + dstatus.erase(pd->getPeer()->getID()); + pdown.remove(pd); + disconnect(pd,SIGNAL(timedout(const Request& )),this,SLOT(onTimeout(const Request& ))); + disconnect(pd,SIGNAL(rejected( const Request& )),this,SLOT(onRejected( const Request& ))); + } + + + const Peer* ChunkDownload::getCurrentPeer() const + { + if (pdown.count() == 0) + return 0; + else + return pdown.getFirst()->getPeer(); + } + + Uint32 ChunkDownload::getChunkIndex() const + { + return chunk->getIndex(); + } + + QString ChunkDownload::getCurrentPeerID() const + { + if (pdown.count() == 0) + { + return QString::null; + } + else if (pdown.count() == 1) + { + const Peer* p = pdown.getFirst()->getPeer(); + return p->getPeerID().identifyClient(); + } + else + { + return i18n("1 peer","%n peers",pdown.count()); + } + } + + Uint32 ChunkDownload::getDownloadSpeed() const + { + Uint32 r = 0; + QPtrList<PeerDownloader>::const_iterator i = pdown.begin(); + while (i != pdown.end()) + { + const PeerDownloader* pd = *i; + r += pd->getPeer()->getDownloadRate(); + i++; + } + return r; + } + + + + void ChunkDownload::save(File & file) + { + ChunkDownloadHeader hdr; + hdr.index = chunk->getIndex(); + hdr.num_bits = pieces.getNumBits(); + hdr.buffered = chunk->getStatus() == Chunk::BUFFERED ? 1 : 0; + // save the chunk header + file.write(&hdr,sizeof(ChunkDownloadHeader)); + // save the bitset + file.write(pieces.getData(),pieces.getNumBytes()); + if (hdr.buffered) + { + // if it's a buffered chunk, save the contents to + file.write(chunk->getData(),chunk->getSize()); + chunk->clear(); + chunk->setStatus(Chunk::ON_DISK); + } + } + + bool ChunkDownload::load(File & file,ChunkDownloadHeader & hdr) + { + // read pieces + if (hdr.num_bits != num) + return false; + + pieces = BitSet(hdr.num_bits); + Array<Uint8> data(pieces.getNumBytes()); + file.read(data,pieces.getNumBytes()); + pieces = BitSet(data,hdr.num_bits); + num_downloaded = pieces.numOnBits(); + if (hdr.buffered) + { + // if it's a buffered chunk, load the data to + if (file.read(chunk->getData(),chunk->getSize()) != chunk->getSize()) + return false; + } + + for (Uint32 i = 0;i < pieces.getNumBits();i++) + if (pieces.get(i)) + piece_queue.remove(i); + + updateHash(); + return true; + } + + Uint32 ChunkDownload::bytesDownloaded() const + { + Uint32 num_bytes = 0; + for (Uint32 i = 0;i < num;i++) + { + if (pieces.get(i)) + { + num_bytes += i == num-1 ? last_size : MAX_PIECE_LEN; + } + } + return num_bytes; + } + + void ChunkDownload::cancelAll() + { + QPtrList<PeerDownloader>::iterator i = pdown.begin(); + while (i != pdown.end()) + { + sendCancels(*i); + i++; + } + } + + bool ChunkDownload::getOnlyDownloader(Uint32 & pid) + { + if (piece_providers.size() == 1) + { + pid = *piece_providers.begin(); + return true; + } + else + { + return false; + } + } + + void ChunkDownload::getStats(Stats & s) + { + s.chunk_index = chunk->getIndex(); + s.current_peer_id = getCurrentPeerID(); + s.download_speed = getDownloadSpeed(); + s.num_downloaders = getNumDownloaders(); + s.pieces_downloaded = num_downloaded; + s.total_pieces = num; + } + + bool ChunkDownload::isChoked() const + { + QPtrList<PeerDownloader>::const_iterator i = pdown.begin(); + while (i != pdown.end()) + { + const PeerDownloader* pd = *i; + // if there is one which isn't choked + if (!pd->isChoked()) + return false; + i++; + } + return true; + } + + void ChunkDownload::updateHash() + { + // update the hash until where we can + Uint32 nn = num_pieces_in_hash; + while (pieces.get(nn) && nn < num) + nn++; + + for (Uint32 i = num_pieces_in_hash;i < nn;i++) + { + const Uint8* data = chunk->getData() + i * MAX_PIECE_LEN; + hash_gen.update(data,i == num - 1 ? last_size : MAX_PIECE_LEN); + } + num_pieces_in_hash = nn; + } + + bool ChunkDownload::usingContinuousHashing() const + { + // if the pieces are larger then 1 MB we will be using the continuous hashing feature + return pieces.getNumBits() > 64; + } +} +#include "chunkdownload.moc" diff --git a/libktorrent/torrent/chunkdownload.h b/libktorrent/torrent/chunkdownload.h new file mode 100644 index 0000000..4119a5b --- /dev/null +++ b/libktorrent/torrent/chunkdownload.h @@ -0,0 +1,207 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef BTCHUNKDOWNLOAD_H +#define BTCHUNKDOWNLOAD_H + +#include <set> +#include <qobject.h> +#include <qptrlist.h> +#include <util/timer.h> +#include <util/ptrmap.h> +#include <util/sha1hashgen.h> +#include <interfaces/chunkdownloadinterface.h> +#include <util/bitset.h> +#include "globals.h" +#include "peerid.h" + + +namespace bt +{ + + class File; + class Chunk; + class Piece; + class Peer; + class Request; + class PeerDownloader; + class DownloadStatus; + + struct ChunkDownloadHeader + { + Uint32 index; + Uint32 num_bits; + Uint32 buffered; + }; + + + + + /** + * @author Joris Guisson + * @brief Handles the download off one Chunk off a Peer + * + * This class handles the download of one Chunk. + */ + class ChunkDownload : public QObject,public kt::ChunkDownloadInterface + { + Q_OBJECT + public: + /** + * Constructor, set the chunk and the PeerManager. + * @param chunk The Chunk + */ + ChunkDownload(Chunk* chunk); + + virtual ~ChunkDownload(); + + /// Get the chunk + Chunk* getChunk() {return chunk;} + + /// Get the total number of pieces + Uint32 getTotalPieces() const {return num;} + + /// Get the number of pieces downloaded + Uint32 getPiecesDownloaded() const {return num_downloaded;} + + /// Get the number of bytes downloaded. + Uint32 bytesDownloaded() const; + + /// Get the index of the chunk + Uint32 getChunkIndex() const; + + /// Get the current peer + const Peer* getCurrentPeer() const; + + /// Get the PeerID of the current peer + QString getCurrentPeerID() const; + + /// Get the download speed + Uint32 getDownloadSpeed() const; + + /// Get download stats + void getStats(Stats & s); + + /// See if a chunkdownload is idle (i.e. has no downloaders) + bool isIdle() const {return pdown.count() == 0;} + + /** + * A Piece has arived. + * @param p The Piece + * @param ok Wether or not the piece was needed + * @return true If Chunk is complete + */ + bool piece(const Piece & p,bool & ok); + + /** + * Assign the downloader to download from. + * @param pd The downloader + * @return true if the peer was asigned, false if not + */ + bool assignPeer(PeerDownloader* pd); + + Uint32 getNumDownloaders() const {return pdown.count();} + + /** + * A Peer has been killed. We need to remove it's + * PeerDownloader. + * @param pd The PeerDownloader + */ + void peerKilled(PeerDownloader* pd); + + /** + * Save to a File + * @param file The File + */ + void save(File & file); + + /** + * Load from a File + * @param file The File + */ + bool load(File & file,ChunkDownloadHeader & hdr); + + /** + * Cancel all requests. + */ + void cancelAll(); + + /** + * When a Chunk is downloaded, this function checks if all + * pieces are delivered by the same peer and if so sets + * that peers' ID. + * @param pid The peers' ID (!= PeerID) + * @return true if there is only one downloader + */ + bool getOnlyDownloader(Uint32 & pid); + + /// See if a PeerDownloader is assigned to this chunk + bool containsPeer(PeerDownloader *pd) {return pdown.contains(pd);} + + /// See if the download is choked (i.e. all downloaders are choked) + bool isChoked() const; + + /// Release all PD's and clear the requested chunks + void releaseAllPDs(); + + /// Send requests to peers + void update(); + + /// See if this CD hasn't been active in the last update + bool needsToBeUpdated() const {return timer.getElapsedSinceUpdate() > 60 * 1000;} + + /// Get the SHA1 hash of the downloaded chunk + SHA1Hash getHash() const {return hash_gen.get();} + + /// Are we using the continous hashing feature for this chunk + bool usingContinuousHashing() const; + + private slots: + void sendRequests(PeerDownloader* pd); + void sendCancels(PeerDownloader* pd); + void endgameCancel(const Piece & p); + void onTimeout(const Request & r); + void onRejected(const Request & r); + + private: + void notDownloaded(const Request & r,bool reject); + void updateHash(); + + private: + BitSet pieces; + QValueList<Uint32> piece_queue; + Chunk* chunk; + Uint32 num; + Uint32 num_downloaded; + Uint32 last_size; + Timer timer; + QPtrList<PeerDownloader> pdown; + PtrMap<Uint32,DownloadStatus> dstatus; + std::set<Uint32> piece_providers; + + + SHA1HashGen hash_gen; + Uint32 num_pieces_in_hash; + + friend File & operator << (File & out,const ChunkDownload & cd); + friend File & operator >> (File & in,ChunkDownload & cd); + }; +} + +#endif diff --git a/libktorrent/torrent/chunkmanager.cpp b/libktorrent/torrent/chunkmanager.cpp new file mode 100644 index 0000000..08aac97 --- /dev/null +++ b/libktorrent/torrent/chunkmanager.cpp @@ -0,0 +1,1157 @@ +/*************************************************************************** + * 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 <algorithm> +#include <util/file.h> +#include <util/array.h> +#include <qstringlist.h> +#include "chunkmanager.h" +#include "torrent.h" +#include <util/error.h> +#include <util/bitset.h> +#include <util/fileops.h> +#include "singlefilecache.h" +#include "multifilecache.h" +#include <util/log.h> +#include <util/functions.h> +#include "globals.h" + +#include <klocale.h> + +namespace bt +{ + + Uint32 ChunkManager::max_chunk_size_for_data_check = 0; + + + ChunkManager::ChunkManager( + Torrent & tor, + const QString & tmpdir, + const QString & datadir, + bool custom_output_name) + : tor(tor),chunks(tor.getNumChunks()), + bitset(tor.getNumChunks()),excluded_chunks(tor.getNumChunks()),only_seed_chunks(tor.getNumChunks()),todo(tor.getNumChunks()) + { + during_load = false; + only_seed_chunks.setAll(false); + todo.setAll(true); + if (tor.isMultiFile()) + cache = new MultiFileCache(tor,tmpdir,datadir,custom_output_name); + else + cache = new SingleFileCache(tor,tmpdir,datadir); + + index_file = tmpdir + "index"; + file_info_file = tmpdir + "file_info"; + file_priority_file = tmpdir + "file_priority"; + Uint64 tsize = tor.getFileLength(); // total size + Uint64 csize = tor.getChunkSize(); // chunk size + Uint64 lsize = tsize - (csize * (tor.getNumChunks() - 1)); // size of last chunk + + for (Uint32 i = 0;i < tor.getNumChunks();i++) + { + if (i + 1 < tor.getNumChunks()) + chunks.insert(i,new Chunk(i,csize)); + else + chunks.insert(i,new Chunk(i,lsize)); + } + chunks.setAutoDelete(true); + chunks_left = 0; + recalc_chunks_left = true; + corrupted_count = recheck_counter = 0; + + for (Uint32 i = 0;i < tor.getNumFiles();i++) + { + TorrentFile & tf = tor.getFile(i); + connect(&tf,SIGNAL(downloadPriorityChanged(TorrentFile*, Priority, Priority )), + this,SLOT(downloadPriorityChanged(TorrentFile*, Priority, Priority ))); + + if (tf.getPriority() != NORMAL_PRIORITY) + { + downloadPriorityChanged(&tf,tf.getPriority(),tf.getOldPriority()); + } + } + + if(tor.isMultiFile()) + { + for(Uint32 i=0; i<tor.getNumFiles(); ++i) + { + bt::TorrentFile & file = tor.getFile(i); + if (!file.isMultimedia() || file.getPriority() == bt::ONLY_SEED_PRIORITY) + continue; + + if (file.getFirstChunk() == file.getLastChunk()) + { + // prioritise whole file + prioritise(file.getFirstChunk(),file.getLastChunk(),PREVIEW_PRIORITY); + } + else + { + Uint32 chunkOffset; + chunkOffset = ((file.getLastChunk() - file.getFirstChunk()) / 100) + 1; + prioritise(file.getFirstChunk(), file.getFirstChunk()+chunkOffset, PREVIEW_PRIORITY); + if (file.getLastChunk() - file.getFirstChunk() > chunkOffset) + { + prioritise(file.getLastChunk() - chunkOffset, file.getLastChunk(), PREVIEW_PRIORITY); + } + } + } + } + else + { + if(tor.isMultimedia()) + { + Uint32 chunkOffset; + chunkOffset = (tor.getNumChunks() / 100) + 1; + + prioritise(0,chunkOffset,PREVIEW_PRIORITY); + if (tor.getNumChunks() > chunkOffset) + { + prioritise(tor.getNumChunks() - chunkOffset, tor.getNumChunks() - 1,PREVIEW_PRIORITY); + } + } + } + } + + + ChunkManager::~ChunkManager() + { + delete cache; + } + + QString ChunkManager::getDataDir() const + { + return cache->getDataDir(); + } + + void ChunkManager::changeDataDir(const QString & data_dir) + { + cache->changeTmpDir(data_dir); + index_file = data_dir + "index"; + file_info_file = data_dir + "file_info"; + file_priority_file = data_dir + "file_priority"; + } + + KIO::Job* ChunkManager::moveDataFiles(const QString & ndir) + { + return cache->moveDataFiles(ndir); + } + + void ChunkManager::moveDataFilesCompleted(KIO::Job* job) + { + cache->moveDataFilesCompleted(job); + } + + void ChunkManager::changeOutputPath(const QString & output_path) + { + cache->changeOutputPath(output_path); + } + + void ChunkManager::loadIndexFile() + { + during_load = true; + loadPriorityInfo(); + + File fptr; + if (!fptr.open(index_file,"rb")) + { + // no index file, so assume it's empty + bt::Touch(index_file,true); + Out(SYS_DIO|LOG_IMPORTANT) << "Can't open index file : " << fptr.errorString() << endl; + during_load = false; + return; + } + + if (fptr.seek(File::END,0) != 0) + { + fptr.seek(File::BEGIN,0); + + while (!fptr.eof()) + { + NewChunkHeader hdr; + fptr.read(&hdr,sizeof(NewChunkHeader)); + Chunk* c = getChunk(hdr.index); + if (c) + { + c->setStatus(Chunk::ON_DISK); + bitset.set(hdr.index,true); + todo.set(hdr.index,false); + recalc_chunks_left = true; + } + } + } + tor.updateFilePercentage(bitset); + during_load = false; + } + + void ChunkManager::saveIndexFile() + { + File fptr; + if (!fptr.open(index_file,"wb")) + throw Error(i18n("Cannot open index file %1 : %2").arg(index_file).arg(fptr.errorString())); + + for (unsigned int i = 0;i < tor.getNumChunks();i++) + { + Chunk* c = getChunk(i); + if (c->getStatus() != Chunk::NOT_DOWNLOADED) + { + NewChunkHeader hdr; + hdr.index = i; + fptr.write(&hdr,sizeof(NewChunkHeader)); + } + } + savePriorityInfo(); + } + + void ChunkManager::createFiles(bool check_priority) + { + if (!bt::Exists(index_file)) + { + File fptr; + fptr.open(index_file,"wb"); + } + cache->create(); + if (check_priority) + { + for (Uint32 i = 0;i < tor.getNumFiles();i++) + { + TorrentFile & tf = tor.getFile(i); + connect(&tf,SIGNAL(downloadPriorityChanged(TorrentFile*, Priority, Priority )), + this,SLOT(downloadPriorityChanged(TorrentFile*, Priority, Priority ))); + + if (tf.getPriority() != NORMAL_PRIORITY) + { + downloadPriorityChanged(&tf,tf.getPriority(),tf.getOldPriority()); + } + } + } + } + + bool ChunkManager::hasMissingFiles(QStringList & sl) + { + return cache->hasMissingFiles(sl); + } + + Chunk* ChunkManager::getChunk(unsigned int i) + { + if (i >= chunks.count()) + return 0; + else + return chunks[i]; + } + + void ChunkManager::start() + { + cache->open(); + } + + void ChunkManager::stop() + { + // unmmap all chunks which can + for (Uint32 i = 0;i < bitset.getNumBits();i++) + { + Chunk* c = chunks[i]; + if (c->getStatus() == Chunk::MMAPPED) + { + cache->save(c); + c->clear(); + c->setStatus(Chunk::ON_DISK); + } + else if (c->getStatus() == Chunk::BUFFERED) + { + c->clear(); + c->setStatus(Chunk::ON_DISK); + } + } + cache->close(); + } + + Chunk* ChunkManager::grabChunk(unsigned int i) + { + if (i >= chunks.size()) + return 0; + + Chunk* c = chunks[i]; + if (c->getStatus() == Chunk::NOT_DOWNLOADED || c->isExcluded()) + { + return 0; + } + else if (c->getStatus() == Chunk::ON_DISK) + { + // load the chunk if it is on disk + cache->load(c); + loaded.insert(i,bt::GetCurrentTime()); + bool check_allowed = (max_chunk_size_for_data_check == 0 || tor.getChunkSize() <= max_chunk_size_for_data_check); + + // when no corruptions have been found, only check once every 5 chunks + if (check_allowed && recheck_counter < 5 && corrupted_count == 0) + check_allowed = false; + + if (c->getData() && check_allowed) + { + recheck_counter = 0; + if (!c->checkHash(tor.getHash(i))) + { + Out(SYS_DIO|LOG_IMPORTANT) << "Chunk " << i + << " has been found invalid, redownloading" << endl; + + resetChunk(i); + tor.updateFilePercentage(i,bitset); + saveIndexFile(); + recalc_chunks_left = true; + corrupted_count++; + corrupted(i); + return 0; + } + } + else + { + recheck_counter++; + } + } + + loaded.insert(i,bt::GetCurrentTime()); + return c; + } + + void ChunkManager::releaseChunk(unsigned int i) + { + if (i >= chunks.size()) + return; + + Chunk* c = chunks[i]; + if (!c->taken()) + { + if (c->getStatus() == Chunk::MMAPPED) + cache->save(c); + c->clear(); + c->setStatus(Chunk::ON_DISK); + loaded.remove(i); + } + } + + void ChunkManager::resetChunk(unsigned int i) + { + if (i >= chunks.size()) + return; + + Chunk* c = chunks[i]; + if (c->getStatus() == Chunk::MMAPPED) + cache->save(c); + c->clear(); + c->setStatus(Chunk::NOT_DOWNLOADED); + bitset.set(i,false); + todo.set(i,!excluded_chunks.get(i) && !only_seed_chunks.get(i)); + loaded.remove(i); + tor.updateFilePercentage(i,bitset); + } + + void ChunkManager::checkMemoryUsage() + { + Uint32 num_removed = 0; + QMap<Uint32,TimeStamp>::iterator i = loaded.begin(); + while (i != loaded.end()) + { + Chunk* c = chunks[i.key()]; + // get rid of chunk if nobody asked for it in the last 5 seconds + if (!c->taken() && bt::GetCurrentTime() - i.data() > 5000) + { + if (c->getStatus() == Chunk::MMAPPED) + cache->save(c); + c->clear(); + c->setStatus(Chunk::ON_DISK); + QMap<Uint32,TimeStamp>::iterator j = i; + i++; + loaded.erase(j); + num_removed++; + } + else + { + i++; + } + } + // Uint32 num_in_mem = loaded.count(); + // Out() << QString("Cleaned %1 chunks, %2 still in memory").arg(num_removed).arg(num_in_mem) << endl; + } + + void ChunkManager::saveChunk(unsigned int i,bool update_index) + { + if (i >= chunks.size()) + return; + + Chunk* c = chunks[i]; + if (!c->isExcluded()) + { + cache->save(c); + + // update the index file + if (update_index) + { + bitset.set(i,true); + todo.set(i,false); + recalc_chunks_left = true; + writeIndexFileEntry(c); + tor.updateFilePercentage(i,bitset); + } + } + else + { + c->clear(); + c->setStatus(Chunk::NOT_DOWNLOADED); + Out(SYS_DIO|LOG_IMPORTANT) << "Warning: attempted to save a chunk which was excluded" << endl; + } + } + + void ChunkManager::writeIndexFileEntry(Chunk* c) + { + File fptr; + if (!fptr.open(index_file,"r+b")) + { + // no index file, so assume it's empty + bt::Touch(index_file,true); + Out(SYS_DIO|LOG_IMPORTANT) << "Can't open index file : " << fptr.errorString() << endl; + // try again + if (!fptr.open(index_file,"r+b")) + // panick if it failes + throw Error(i18n("Cannot open index file %1 : %2").arg(index_file).arg(fptr.errorString())); + } + + + fptr.seek(File::END,0); + NewChunkHeader hdr; + hdr.index = c->getIndex(); + fptr.write(&hdr,sizeof(NewChunkHeader)); + } + + Uint32 ChunkManager::onlySeedChunks() const + { + return only_seed_chunks.numOnBits(); + } + + bool ChunkManager::completed() const + { + return todo.numOnBits() == 0 && bitset.numOnBits() > 0; + } + + Uint64 ChunkManager::bytesLeft() const + { + Uint32 num_left = bitset.getNumBits() - bitset.numOnBits(); + Uint32 last = chunks.size() - 1; + if (last < chunks.size() && !bitset.get(last)) + { + Chunk* c = chunks[last]; + if (c) + return (num_left - 1)*tor.getChunkSize() + c->getSize(); + else + return num_left*tor.getChunkSize(); + } + else + { + return num_left*tor.getChunkSize(); + } + } + + Uint64 ChunkManager::bytesLeftToDownload() const + { + Uint32 num_left = todo.numOnBits(); + Uint32 last = chunks.size() - 1; + if (last < chunks.size() && todo.get(last)) + { + Chunk* c = chunks[last]; + if (c) + return (num_left - 1)*tor.getChunkSize() + c->getSize(); + else + return num_left*tor.getChunkSize(); + } + else + { + return num_left*tor.getChunkSize(); + } + } + + Uint32 ChunkManager::chunksLeft() const + { + if (!recalc_chunks_left) + return chunks_left; + + Uint32 num = 0; + Uint32 tot = chunks.size(); + for (Uint32 i = 0;i < tot;i++) + { + const Chunk* c = chunks[i]; + if (!bitset.get(i) && !c->isExcluded()) + num++; + } + chunks_left = num; + recalc_chunks_left = false; + return num; + } + + bool ChunkManager::haveAllChunks() const + { + return bitset.numOnBits() == bitset.getNumBits(); + } + + Uint64 ChunkManager::bytesExcluded() const + { + Uint64 excl = 0; + if (excluded_chunks.get(tor.getNumChunks() - 1)) + { + Chunk* c = chunks[tor.getNumChunks() - 1]; + Uint32 num = excluded_chunks.numOnBits() - 1; + excl = tor.getChunkSize() * num + c->getSize(); + } + else + { + excl = tor.getChunkSize() * excluded_chunks.numOnBits(); + } + + if (only_seed_chunks.get(tor.getNumChunks() - 1)) + { + Chunk* c = chunks[tor.getNumChunks() - 1]; + Uint32 num = only_seed_chunks.numOnBits() - 1; + excl += tor.getChunkSize() * num + c->getSize(); + } + else + { + excl += tor.getChunkSize() * only_seed_chunks.numOnBits(); + } + return excl; + } + + Uint32 ChunkManager::chunksExcluded() const + { + return excluded_chunks.numOnBits() + only_seed_chunks.numOnBits(); + } + + Uint32 ChunkManager::chunksDownloaded() const + { + return bitset.numOnBits(); + } + + void ChunkManager::debugPrintMemUsage() + { + Out(SYS_DIO|LOG_DEBUG) << "Active Chunks : " << loaded.count()<< endl; + } + + void ChunkManager::prioritise(Uint32 from,Uint32 to,Priority priority) + { + if (from > to) + std::swap(from,to); + + Uint32 i = from; + while (i <= to && i < chunks.count()) + { + Chunk* c = chunks[i]; + c->setPriority(priority); + + if (priority == ONLY_SEED_PRIORITY) + { + only_seed_chunks.set(i,true); + todo.set(i,false); + } + else if (priority == EXCLUDED) + { + only_seed_chunks.set(i,false); + todo.set(i,false); + } + else + { + only_seed_chunks.set(i,false); + todo.set(i,!bitset.get(i)); + } + + i++; + } + updateStats(); + } + + void ChunkManager::exclude(Uint32 from,Uint32 to) + { + if (from > to) + std::swap(from,to); + + Uint32 i = from; + while (i <= to && i < chunks.count()) + { + Chunk* c = chunks[i]; + c->setExclude(true); + excluded_chunks.set(i,true); + only_seed_chunks.set(i,false); + todo.set(i,false); + bitset.set(i,false); + i++; + } + recalc_chunks_left = true; + excluded(from,to); + updateStats(); + } + + void ChunkManager::include(Uint32 from,Uint32 to) + { + if (from > to) + std::swap(from,to); + + Uint32 i = from; + while (i <= to && i < chunks.count()) + { + Chunk* c = chunks[i]; + c->setExclude(false); + excluded_chunks.set(i,false); + if (!bitset.get(i)) + todo.set(i,true); + i++; + } + recalc_chunks_left = true; + updateStats(); + included(from,to); + } + + void ChunkManager::saveFileInfo() + { + // saves which TorrentFiles do not need to be downloaded + File fptr; + if (!fptr.open(file_info_file,"wb")) + { + Out(SYS_DIO|LOG_IMPORTANT) << "Warning : Can't save chunk_info file : " << fptr.errorString() << endl; + return; + } + + // first write the number of excluded ones + // don't know this yet, so write 0 for the time being + Uint32 tmp = 0; + fptr.write(&tmp,sizeof(Uint32)); + + Uint32 i = 0; + Uint32 cnt = 0; + while (i < tor.getNumFiles()) + { + if (tor.getFile(i).doNotDownload()) + { + fptr.write(&i,sizeof(Uint32)); + cnt++; + } + i++; + } + + // go back to the beginning and write the number of files + fptr.seek(File::BEGIN,0); + fptr.write(&cnt,sizeof(Uint32)); + fptr.flush(); + } + + void ChunkManager::loadFileInfo() + { + if (during_load) + return; + + File fptr; + if (!fptr.open(file_info_file,"rb")) + return; + + Uint32 num = 0,tmp = 0; + // first read the number of dnd files + if (fptr.read(&num,sizeof(Uint32)) != sizeof(Uint32)) + { + Out(SYS_DIO|LOG_IMPORTANT) << "Warning : error reading chunk_info file" << endl; + return; + } + + for (Uint32 i = 0;i < num;i++) + { + if (fptr.read(&tmp,sizeof(Uint32)) != sizeof(Uint32)) + { + Out(SYS_DIO|LOG_IMPORTANT) << "Warning : error reading chunk_info file" << endl; + return; + } + + bt::TorrentFile & tf = tor.getFile(tmp); + if (!tf.isNull()) + { + Out(SYS_DIO|LOG_DEBUG) << "Excluding : " << tf.getPath() << endl; + tf.setDoNotDownload(true); + } + } + } + + void ChunkManager::savePriorityInfo() + { + if (during_load) + return; + + //save priority info and call saveFileInfo + saveFileInfo(); + File fptr; + if (!fptr.open(file_priority_file,"wb")) + { + Out(SYS_DIO|LOG_IMPORTANT) << "Warning : Can't save chunk_info file : " << fptr.errorString() << endl; + return; + } + + try + { + // first write the number of excluded ones + // don't know this yet, so write 0 for the time being + Uint32 tmp = 0; + fptr.write(&tmp,sizeof(Uint32)); + + Uint32 i = 0; + Uint32 cnt = 0; + while (i < tor.getNumFiles()) + { + const TorrentFile & tf = tor.getFile(i); + if (tf.getPriority() != NORMAL_PRIORITY) + { + tmp = tf.getPriority(); + fptr.write(&i,sizeof(Uint32)); + fptr.write(&tmp,sizeof(Uint32)); + cnt+=2; + } + i++; + } + + // go back to the beginning and write the number of items + fptr.seek(File::BEGIN,0); + fptr.write(&cnt,sizeof(Uint32)); + fptr.flush(); + } + catch (bt::Error & err) + { + Out(SYS_DIO|LOG_IMPORTANT) << "Failed to save priority file " << err.toString() << endl; + bt::Delete(file_priority_file,true); + } + } + + void ChunkManager::loadPriorityInfo() + { + //load priority info and if that fails load file info + File fptr; + if (!fptr.open(file_priority_file,"rb")) + { + loadFileInfo(); + return; + } + + Uint32 num = 0; + // first read the number of lines + if (fptr.read(&num,sizeof(Uint32)) != sizeof(Uint32) || num > 2*tor.getNumFiles()) + { + Out(SYS_DIO|LOG_IMPORTANT) << "Warning : error reading chunk_info file" << endl; + loadFileInfo(); + return; + } + + Array<Uint32> buf(num); + if (fptr.read(buf,sizeof(Uint32)*num) != sizeof(Uint32)*num) + { + Out(SYS_DIO|LOG_IMPORTANT) << "Warning : error reading chunk_info file" << endl; + loadFileInfo(); + return; + } + + fptr.close(); + + for (Uint32 i = 0;i < num;i += 2) + { + Uint32 idx = buf[i]; + if (idx >= tor.getNumFiles()) + { + Out(SYS_DIO|LOG_IMPORTANT) << "Warning : error reading chunk_info file" << endl; + loadFileInfo(); + return; + } + + bt::TorrentFile & tf = tor.getFile(idx); + + if (!tf.isNull()) + { + // numbers are to be compatible with old chunk info files + switch(buf[i+1]) + { + case FIRST_PRIORITY: + case 3: + tf.setPriority(FIRST_PRIORITY); + break; + case NORMAL_PRIORITY: + case 2: + tf.setPriority(NORMAL_PRIORITY); + break; + case EXCLUDED: + case 0: + //tf.setDoNotDownload(true); + tf.setPriority(EXCLUDED); + break; + case ONLY_SEED_PRIORITY: + case -1: + tf.setPriority(ONLY_SEED_PRIORITY); + break; + default: + tf.setPriority(LAST_PRIORITY); + break; + } + } + } + } + + void ChunkManager::downloadStatusChanged(TorrentFile* tf,bool download) + { + Uint32 first = tf->getFirstChunk(); + Uint32 last = tf->getLastChunk(); + if (download) + { + // include the chunks + include(first,last); + + // if it is a multimedia file, prioritise first and last chunks of file + if (tf->isMultimedia()) + { + Uint32 chunkOffset; + chunkOffset = ((last - first) / 100) + 1; + + prioritise(first,first+chunkOffset,PREVIEW_PRIORITY); + if (last - first > 2) + { + prioritise(last - chunkOffset, last, PREVIEW_PRIORITY); + //prioritise(last -1,last, PREVIEW_PRIORITY); + } + } + } + else + { + // Out(SYS_DIO|LOG_DEBUG) << "Excluding chunks " << first << " to " << last << endl; + // first and last chunk may be part of multiple files + // so we can't just exclude them + QValueList<Uint32> files,last_files; + + // get list of files where first chunk lies in + tor.calcChunkPos(first,files); + tor.calcChunkPos(last,last_files); + // check for exceptional case which causes very long loops + if (first == last && files.count() > 1) + { + cache->downloadStatusChanged(tf,download); + savePriorityInfo(); + return; + } + + // go over all chunks from first to last and mark them as not downloaded + // (first and last not included) + for (Uint32 i = first + 1;i < last;i++) + resetChunk(i); + + // if the first chunk only lies in one file, reset it + if (files.count() == 1 && first != 0) + { + // Out(SYS_DIO|LOG_DEBUG) << "Resetting first " << first << endl; + resetChunk(first); + } + + // if the last chunk only lies in one file reset it + if (last != first && last_files.count() == 1) + { + // Out(SYS_DIO|LOG_DEBUG) << "Resetting last " << last << endl; + resetChunk(last); + } + + Priority maxp = ONLY_SEED_PRIORITY; + bool reprioritise_border_chunk = false; + bool modified = false; + + // if one file in the list needs to be downloaded,increment first + for (QValueList<Uint32>::iterator i = files.begin();i != files.end();i++) + { + if (*i == tf->getIndex()) + continue; + + const TorrentFile & other = tor.getFile(*i); + if (!other.doNotDownload()) + { + if (first != last && !modified) + { + first++; + reprioritise_border_chunk = true; + modified = true; + } + + if (other.getPriority() > maxp) + maxp = other.getPriority(); + } + } + + // in case we have incremented first, we better reprioritise the border chunk + if (reprioritise_border_chunk) + prioritise(first-1,first-1,maxp); + + maxp = ONLY_SEED_PRIORITY; + reprioritise_border_chunk = false; + modified = false; + + // if one file in the list needs to be downloaded,decrement last + for (QValueList<Uint32>::iterator i = last_files.begin();i != last_files.end();i++) + { + if (*i == tf->getIndex()) + continue; + + const TorrentFile & other = tor.getFile(*i); + if (!other.doNotDownload()) + { + if (first != last && last > 0 && !modified) + { + last--; + reprioritise_border_chunk = true; + modified = true; + } + + if (other.getPriority() > maxp) + maxp = other.getPriority(); + } + } + + if (reprioritise_border_chunk) + prioritise(last+1,last+1,maxp); + + // last smaller then first is not normal, so just return + if (last < first) + { + cache->downloadStatusChanged(tf,download); + savePriorityInfo(); + return; + } + + // Out(SYS_DIO|LOG_DEBUG) << "exclude " << first << " to " << last << endl; + exclude(first,last); + } + // alert the cache but first put things in critical operation mode + cache->downloadStatusChanged(tf,download); + savePriorityInfo(); + } + + void ChunkManager::downloadPriorityChanged(TorrentFile* tf,Priority newpriority,Priority oldpriority) + { + if (newpriority == EXCLUDED) + { + downloadStatusChanged(tf, false); + return; + } + if (oldpriority == EXCLUDED) + { + downloadStatusChanged(tf, true); + return; + } + + savePriorityInfo(); + + Uint32 first = tf->getFirstChunk(); + Uint32 last = tf->getLastChunk(); + + // first and last chunk may be part of multiple files + // so we can't just exclude them + QValueList<Uint32> files; + + // get list of files where first chunk lies in + tor.calcChunkPos(first,files); + + Chunk* c = chunks[first]; + // if one file in the list needs to be downloaded,increment first + for (QValueList<Uint32>::iterator i = files.begin();i != files.end();i++) + { + Priority np = tor.getFile(*i).getPriority(); + if (np > newpriority && *i != tf->getIndex()) + { + // make sure we don't go past last + if (first == last) + return; + + first++; + break; + } + } + + files.clear(); + // get list of files where last chunk lies in + tor.calcChunkPos(last,files); + c = chunks[last]; + // if one file in the list needs to be downloaded,decrement last + for (QValueList<Uint32>::iterator i = files.begin();i != files.end();i++) + { + Priority np = tor.getFile(*i).getPriority(); + if (np > newpriority && *i != tf->getIndex()) + { + // make sure we don't wrap around + if (last == 0 || last == first) + return; + + last--; + break; + } + } + + // last smaller then first is not normal, so just return + if (last < first) + { + return; + } + + + prioritise(first,last,newpriority); + if (newpriority == ONLY_SEED_PRIORITY) + excluded(first,last); + } + + bool ChunkManager::prepareChunk(Chunk* c,bool allways) + { + if (!allways && c->getStatus() != Chunk::NOT_DOWNLOADED) + return false; + + return cache->prep(c); + } + + QString ChunkManager::getOutputPath() const + { + return cache->getOutputPath(); + } + + void ChunkManager::preallocateDiskSpace(PreallocationThread* prealloc) + { + cache->preallocateDiskSpace(prealloc); + } + + void ChunkManager::dataChecked(const BitSet & ok_chunks) + { + // go over all chunks at check each of them + for (Uint32 i = 0;i < chunks.count();i++) + { + Chunk* c = chunks[i]; + if (ok_chunks.get(i) && !bitset.get(i)) + { + // We think we do not hae a chunk, but we do have it + bitset.set(i,true); + todo.set(i,false); + // the chunk must be on disk + c->setStatus(Chunk::ON_DISK); + tor.updateFilePercentage(i,bitset); + } + else if (!ok_chunks.get(i) && bitset.get(i)) + { + Out(SYS_DIO|LOG_IMPORTANT) << "Previously OK chunk " << i << " is corrupt !!!!!" << endl; + // We think we have a chunk, but we don't + bitset.set(i,false); + todo.set(i,!only_seed_chunks.get(i) && !excluded_chunks.get(i)); + if (c->getStatus() == Chunk::ON_DISK) + { + c->setStatus(Chunk::NOT_DOWNLOADED); + tor.updateFilePercentage(i,bitset); + } + else if (c->getStatus() == Chunk::MMAPPED || c->getStatus() == Chunk::BUFFERED) + { + resetChunk(i); + } + else + { + tor.updateFilePercentage(i,bitset); + } + } + } + recalc_chunks_left = true; + try + { + saveIndexFile(); + } + catch (bt::Error & err) + { + Out(SYS_DIO|LOG_DEBUG) << "Failed to save index file : " << err.toString() << endl; + } + catch (...) + { + Out(SYS_DIO|LOG_DEBUG) << "Failed to save index file : unkown exception" << endl; + } + chunksLeft(); + corrupted_count = 0; + } + + bool ChunkManager::hasExistingFiles() const + { + return cache->hasExistingFiles(); + } + + + void ChunkManager::recreateMissingFiles() + { + createFiles(); + if (tor.isMultiFile()) + { + // loop over all files and mark all chunks of all missing files as + // not downloaded + for (Uint32 i = 0;i < tor.getNumFiles();i++) + { + TorrentFile & tf = tor.getFile(i); + if (!tf.isMissing()) + continue; + + for (Uint32 j = tf.getFirstChunk(); j <= tf.getLastChunk();j++) + resetChunk(j); + tf.setMissing(false); + } + } + else + { + // reset all chunks in case of single file torrent + for (Uint32 j = 0; j < tor.getNumChunks();j++) + resetChunk(j); + } + saveIndexFile(); + recalc_chunks_left = true; + chunksLeft(); + } + + void ChunkManager::dndMissingFiles() + { + // createFiles(); // create them again + // loop over all files and mark all chunks of all missing files as + // not downloaded + for (Uint32 i = 0;i < tor.getNumFiles();i++) + { + TorrentFile & tf = tor.getFile(i); + if (!tf.isMissing()) + continue; + + for (Uint32 j = tf.getFirstChunk(); j <= tf.getLastChunk();j++) + resetChunk(j); + tf.setMissing(false); + tf.setDoNotDownload(true); // set do not download + } + savePriorityInfo(); + saveIndexFile(); + recalc_chunks_left = true; + chunksLeft(); + } + + void ChunkManager::deleteDataFiles() + { + cache->deleteDataFiles(); + } + + Uint64 ChunkManager::diskUsage() + { + return cache->diskUsage(); + } + +} + +#include "chunkmanager.moc" diff --git a/libktorrent/torrent/chunkmanager.h b/libktorrent/torrent/chunkmanager.h new file mode 100644 index 0000000..daa2300 --- /dev/null +++ b/libktorrent/torrent/chunkmanager.h @@ -0,0 +1,366 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef BTCHUNKMANAGER_H +#define BTCHUNKMANAGER_H + +#include <qmap.h> +#include <qstring.h> +#include <qobject.h> +#include <qptrvector.h> +#include <util/bitset.h> +#include "chunk.h" +#include "globals.h" + +class QStringList; + +namespace KIO +{ + class Job; +} + +namespace bt +{ + class Torrent; + class Cache; + class TorrentFile; + class PreallocationThread; + + struct NewChunkHeader + { + unsigned int index; // the Chunks index + unsigned int deprecated; // offset in cache file + }; + + /** + * @author Joris Guisson + * + * Manages all Chunk's and the cache file, where all the chunk's are stored. + * It also manages a separate index file, where the position of each piece + * in the cache file is stored. + * + * The chunks are stored in the cache file in the correct order. Eliminating + * the need for a file reconstruction algorithm for single files. + */ + class ChunkManager : public QObject + { + Q_OBJECT + + Torrent & tor; + QString index_file,file_info_file,file_priority_file; + QPtrVector<Chunk> chunks; + Cache* cache; + QMap<Uint32,TimeStamp> loaded; // loaded chunks and when they were loaded + BitSet bitset; + BitSet excluded_chunks; + BitSet only_seed_chunks; + BitSet todo; + mutable Uint32 chunks_left; + mutable bool recalc_chunks_left; + Uint32 corrupted_count; + Uint32 recheck_counter; + bool during_load; + public: + ChunkManager(Torrent & tor, + const QString & tmpdir, + const QString & datadir, + bool custom_output_name); + virtual ~ChunkManager(); + + /// Get the torrent + const Torrent & getTorrent() const {return tor;} + + /// Get the data dir + QString getDataDir() const; + + /// Get the actual output path + QString getOutputPath() const; + + void changeOutputPath(const QString& output_path); + + /// Remove obsolete chunks + void checkMemoryUsage(); + + /** + * Change the data dir. + * @param data_dir + */ + void changeDataDir(const QString & data_dir); + + /** + * Move the data files of the torrent. + * @param ndir The new directory + * @return The job doing the move + */ + KIO::Job* moveDataFiles(const QString & ndir); + + /** + * The move data files job has finished + * @param job The move job + */ + void moveDataFilesCompleted(KIO::Job* job); + + /** + * Loads the index file. + * @throw Error When it can be loaded + */ + void loadIndexFile(); + + /** + * Create the cache file, and index files. + * @param check_priority Make sure chunk priorities and dnd status of files match + * @throw Error When it can be created + */ + void createFiles(bool check_priority = false); + + /** + * Test all files and see if they are not missing. + * If so put them in a list + */ + bool hasMissingFiles(QStringList & sl); + + /** + * Preallocate diskspace for all files + * @param prealloc The thread doing the preallocation + */ + void preallocateDiskSpace(PreallocationThread* prealloc); + + /** + * Open the necessary files when the download gets started. + */ + void start(); + + /** + * Closes files when the download gets stopped. + */ + void stop(); + + /** + * Get's the i'th Chunk. + * @param i The Chunk's index + * @return The Chunk, or 0 when i is out of bounds + */ + Chunk* getChunk(unsigned int i); + + /** + * Get's the i'th Chunk. Makes sure that the Chunk's data + * is in memory. If the Chunk hasn't been downloaded yet 0 + * is returned. Whenever the Chunk needs to be uploaded, call + * this function. This changes the status to MMAPPED or BUFFERED. + * @param i The Chunk's index + * @return The Chunk, or 0 when i is out of bounds + */ + Chunk* grabChunk(unsigned int i); + + /** + * Prepare a chunk for downloading + * @param c The Chunk + * @param allways Always do this, even if the chunk is not NOT_DOWNLOADED + * @return true if ok, false if the chunk is not NOT_DOWNLOADED + */ + bool prepareChunk(Chunk* c,bool allways = false); + + /** + * The upload is done, and the Chunk is no longer needed. + * The Chunk's data might be cleared, if we are using up to much + * memory. + * @param i The Chunk's index + */ + void releaseChunk(unsigned int i); + + /** + * Reset a chunk as if it were never downloaded. + * @param i The chunk + */ + void resetChunk(unsigned int i); + + /** + * Save the i'th Chunk to the cache_file. + * Also changes the Chunk's status to ON_DISK. + * The Chunk's data is immediately cleared. + * @param i The Chunk's index + * @param update_index Update the index or not + */ + void saveChunk(unsigned int i,bool update_index = true); + + /** + * Calculates the number of bytes left for the tracker. Does include + * excluded chunks (this should be used for the tracker). + * @return The number of bytes to download + the number of bytes excluded + */ + Uint64 bytesLeft() const; + + /** + * Calculates the number of bytes left to download. + */ + Uint64 bytesLeftToDownload() const; + + /** + * Calculates the number of bytes which have been excluded. + * @return The number of bytes excluded + */ + Uint64 bytesExcluded() const; + + /** + * Calculates the number of chunks left to download. + * Does not include excluded chunks. + * @return The number of chunks to download + */ + Uint32 chunksLeft() const; + + /** + * Check if we have all chunks, this is not the same as + * chunksLeft() == 0, it does not look at excluded chunks. + * @return true if all chunks have been downloaded + */ + bool haveAllChunks() const; + + /** + * Get the number of chunks which have been excluded. + * @return The number of excluded chunks + */ + Uint32 chunksExcluded() const; + + /** + * Get the number of downloaded chunks + * @return + */ + Uint32 chunksDownloaded() const; + + /** + * Get the number of only seed chunks. + */ + Uint32 onlySeedChunks() const; + + /** + * Get a BitSet of the status of all Chunks + */ + const BitSet & getBitSet() const {return bitset;} + + /** + * Get the excluded bitset + */ + const BitSet & getExcludedBitSet() const {return excluded_chunks;} + + /** + * Get the only seed bitset. + */ + const BitSet & getOnlySeedBitSet() const {return only_seed_chunks;} + + /// Get the number of chunks into the file. + Uint32 getNumChunks() const {return chunks.count();} + + /// Print memory usage to log file + void debugPrintMemUsage(); + + /** + * Make sure that a range will get priority over other chunks. + * @param from First chunk in range + * @param to Last chunk in range + */ + void prioritise(Uint32 from,Uint32 to, Priority priority); + + /** + * Make sure that a range will not be downloaded. + * @param from First chunk in range + * @param to Last chunk in range + */ + void exclude(Uint32 from,Uint32 to); + + /** + * Make sure that a range will be downloaded. + * Does the opposite of exclude. + * @param from First chunk in range + * @param to Last chunk in range + */ + void include(Uint32 from,Uint32 to); + + + /** + * Data has been checked, and these chunks are OK. + * The ChunkManager will update it's internal structures + * @param ok_chunks The ok_chunks + */ + void dataChecked(const BitSet & ok_chunks); + + /// Test if the torrent has existing files, only works the first time a torrent is loaded + bool hasExistingFiles() const; + + /// Recreates missing files + void recreateMissingFiles(); + + /// Set missing files as do not download + void dndMissingFiles(); + + /// Delete all data files + void deleteDataFiles(); + + /// Are all not deselected chunks downloaded. + bool completed() const; + + /// Set the maximum chunk size for a data check, 0 means alllways check + static void setMaxChunkSizeForDataCheck(Uint32 mcs) {max_chunk_size_for_data_check = mcs;} + + /// Get the current disk usage of all the files in this torrent + Uint64 diskUsage(); + signals: + /** + * Emitted when a range of chunks has been excluded + * @param from First chunk in range + * @param to Last chunk in range + */ + void excluded(Uint32 from,Uint32 to); + + /** + * Emitted when a range of chunks has been included back. + * @param from First chunk in range + * @param to Last chunk in range + */ + void included(Uint32 from,Uint32 to); + + /** + * Emitted when chunks get excluded or included, so + * that the statistics can be updated. + */ + void updateStats(); + + /** + * A corrupted chunk has been found during uploading. + * @param chunk The chunk + */ + void corrupted(Uint32 chunk); + + private: + void saveIndexFile(); + void writeIndexFileEntry(Chunk* c); + void saveFileInfo(); + void loadFileInfo(); + void savePriorityInfo(); + void loadPriorityInfo(); + + private slots: + void downloadStatusChanged(TorrentFile* tf,bool download); + void downloadPriorityChanged(TorrentFile* tf,Priority newpriority,Priority oldpriority); + + static Uint32 max_chunk_size_for_data_check; + }; + +} + +#endif diff --git a/libktorrent/torrent/chunkselector.cpp b/libktorrent/torrent/chunkselector.cpp new file mode 100644 index 0000000..b1c42fa --- /dev/null +++ b/libktorrent/torrent/chunkselector.cpp @@ -0,0 +1,185 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <stdlib.h> +#include <vector> +#include <algorithm> +#include <util/log.h> +#include <util/bitset.h> +#include "chunkcounter.h" +#include "chunkselector.h" +#include "chunkmanager.h" +#include "downloader.h" +#include "peerdownloader.h" +#include "globals.h" +#include "peer.h" +#include "peermanager.h" + +namespace bt +{ + struct RareCmp + { + ChunkManager & cman; + ChunkCounter & cc; + bool warmup; + + RareCmp(ChunkManager & cman,ChunkCounter & cc,bool warmup) : cman(cman),cc(cc),warmup(warmup) {} + + bool operator()(Uint32 a,Uint32 b) + { + // do some sanity checks + if (a >= cman.getNumChunks() || b >= cman.getNumChunks()) + return false; + + // the sorting is done on two criteria, priority and rareness + Priority pa = cman.getChunk(a)->getPriority(); + Priority pb = cman.getChunk(b)->getPriority(); + if (pa == pb) + return normalCmp(a,b); // if both have same priority compare on rareness + else if (pa > pb) // pa has priority over pb, so select pa + return true; + else // pb has priority over pa, so select pb + return false; + } + + bool normalCmp(Uint32 a,Uint32 b) + { + // during warmup mode choose most common chunks + if (!warmup) + return cc.get(a) < cc.get(b); + else + return cc.get(a) > cc.get(b); + } + }; + + ChunkSelector::ChunkSelector(ChunkManager & cman,Downloader & downer,PeerManager & pman) + : cman(cman),downer(downer),pman(pman) + { + std::vector<Uint32> tmp; + for (Uint32 i = 0;i < cman.getNumChunks();i++) + { + if (!cman.getBitSet().get(i)) + { + tmp.push_back(i); + } + } + std::random_shuffle(tmp.begin(),tmp.end()); + // std::list does not support random_shuffle so we use a vector as a temporary storage + // for the random_shuffle + chunks.insert(chunks.begin(),tmp.begin(),tmp.end()); + sort_timer.update(); + } + + + ChunkSelector::~ChunkSelector() + {} + + + bool ChunkSelector::select(PeerDownloader* pd,Uint32 & chunk) + { + const BitSet & bs = cman.getBitSet(); + + + // sort the chunks every 2 seconds + if (sort_timer.getElapsedSinceUpdate() > 2000) + { + bool warmup = cman.getNumChunks() - cman.chunksLeft() <= 4; +// dataChecked(bs); + chunks.sort(RareCmp(cman,pman.getChunkCounter(),warmup)); + sort_timer.update(); + } + + std::list<Uint32>::iterator itr = chunks.begin(); + while (itr != chunks.end()) + { + Uint32 i = *itr; + Chunk* c = cman.getChunk(*itr); + + // if we have the chunk remove it from the list + if (bs.get(i)) + { + std::list<Uint32>::iterator tmp = itr; + itr++; + chunks.erase(tmp); + } + else + { + // pd has to have the selected chunk and it needs to be not excluded + if (pd->hasChunk(i) && !downer.areWeDownloading(i) && + !c->isExcluded() && !c->isExcludedForDownloading()) + { + // we have a chunk + chunk = i; + return true; + } + itr++; + } + } + + return false; + } + + void ChunkSelector::dataChecked(const BitSet & ok_chunks) + { + for (Uint32 i = 0;i < ok_chunks.getNumBits();i++) + { + bool in_chunks = std::find(chunks.begin(),chunks.end(),i) != chunks.end(); + if (in_chunks && ok_chunks.get(i)) + { + // if we have the chunk, remove it from the chunks list + chunks.remove(i); + } + else if (!in_chunks && !ok_chunks.get(i)) + { + // if we don't have the chunk, add it to the list if it wasn't allrready in there + chunks.push_back(i); + } + } + } + + void ChunkSelector::reincluded(Uint32 from, Uint32 to) + { + // lets do a safety check first + if (from >= cman.getNumChunks() || to >= cman.getNumChunks()) + { + Out(SYS_DIO|LOG_NOTICE) << "Internal error in chunkselector" << endl; + return; + } + + for (Uint32 i = from;i <= to;i++) + { + bool in_chunks = std::find(chunks.begin(),chunks.end(),i) != chunks.end(); + if (!in_chunks && cman.getChunk(i)->getStatus() != Chunk::ON_DISK) + { + // Out(SYS_DIO|LOG_DEBUG) << "ChunkSelector::reIncluded " << i << endl; + chunks.push_back(i); + } + } + } + + void ChunkSelector::reinsert(Uint32 chunk) + { + bool in_chunks = std::find(chunks.begin(),chunks.end(),chunk) != chunks.end(); + if (!in_chunks) + chunks.push_back(chunk); + } + + +} + diff --git a/libktorrent/torrent/chunkselector.h b/libktorrent/torrent/chunkselector.h new file mode 100644 index 0000000..3ba2f8a --- /dev/null +++ b/libktorrent/torrent/chunkselector.h @@ -0,0 +1,80 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef BTCHUNKSELECTOR_H +#define BTCHUNKSELECTOR_H + +#include <list> +#include <util/timer.h> + +namespace bt +{ + class BitSet; + class PeerDownloader; + class ChunkManager; + class Downloader; + class PeerManager; + /** + * @author Joris Guisson + * + * Selects which Chunks to download. + */ + class ChunkSelector + { + ChunkManager & cman; + Downloader & downer; + PeerManager & pman; + std::list<Uint32> chunks; + Timer sort_timer; + public: + ChunkSelector(ChunkManager & cman,Downloader & downer,PeerManager &pman); + virtual ~ChunkSelector(); + + /** + * Select which chunk to download for a PeerDownloader. + * @param pd The PeerDownloader + * @param chunk Index of chunk gets stored here + * @return true upon succes, false otherwise + */ + bool select(PeerDownloader* pd,Uint32 & chunk); + + /** + * Data has been checked, and these chunks are OK. + * @param ok_chunks The ok_chunks + */ + void dataChecked(const BitSet & ok_chunks); + + /** + * A range of chunks has been reincluded. + * @param from The first chunk + * @param to The last chunk + */ + void reincluded(Uint32 from, Uint32 to); + + /** + * Reinsert a chunk. + * @param chunk The chunk + */ + void reinsert(Uint32 chunk); + }; + +} + +#endif + diff --git a/libktorrent/torrent/dndfile.cpp b/libktorrent/torrent/dndfile.cpp new file mode 100644 index 0000000..deace69 --- /dev/null +++ b/libktorrent/torrent/dndfile.cpp @@ -0,0 +1,268 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <klocale.h> +#include <util/file.h> +#include <util/error.h> +#include <util/fileops.h> +#include <util/sha1hash.h> +#include "dndfile.h" + +namespace bt +{ + const Uint32 DND_FILE_HDR_MAGIC = 0xD1234567; + + struct DNDFileHeader + { + Uint32 magic; + Uint32 first_size; + Uint32 last_size; + Uint8 data_sha1[20]; + }; + + DNDFile::DNDFile(const QString & path) : path(path) + {} + + + DNDFile::~DNDFile() + {} + + void DNDFile::changePath(const QString & npath) + { + path = npath; + } + + void DNDFile::checkIntegrity() + { + File fptr; + if (!fptr.open(path,"rb")) + { + create(); + return; + } + + DNDFileHeader hdr; + if (fptr.read(&hdr,sizeof(DNDFileHeader)) != sizeof(DNDFileHeader)) + { + create(); + return; + } + + if (hdr.magic != DND_FILE_HDR_MAGIC && bt::FileSize(path) != sizeof(DNDFileHeader) + hdr.first_size + hdr.last_size) + { + create(); + return; + } + +#if 0 + if (hdr.first_size > 0 || hdr.last_size > 0) + { + // check hash + Uint32 data_size = hdr.first_size + hdr.last_size; + Uint8* buf = new Uint8[data_size]; + if (fptr.read(buf,data_size) != data_size) + { + delete [] buf; + create(); + return; + } + + if (SHA1Hash::generate(buf,data_size) != SHA1Hash(hdr.data_sha1)) + { + delete [] buf; + create(); + return; + } + + delete [] buf; + } +#endif + } + + void DNDFile::create() + { + DNDFileHeader hdr; + hdr.magic = DND_FILE_HDR_MAGIC; + hdr.first_size = 0; + hdr.last_size = 0; + memset(hdr.data_sha1,0,20); + + File fptr; + if (!fptr.open(path,"wb")) + throw Error(i18n("Cannot create file %1 : %2").arg(path).arg(fptr.errorString())); + + fptr.write(&hdr,sizeof(DNDFileHeader)); + fptr.close(); + } + + + + Uint32 DNDFile::readFirstChunk(Uint8* buf,Uint32 off,Uint32 buf_size) + { + File fptr; + if (!fptr.open(path,"rb")) + { + create(); + return 0; + } + + DNDFileHeader hdr; + if (fptr.read(&hdr,sizeof(DNDFileHeader)) != sizeof(DNDFileHeader)) + { + create(); + return 0; + } + + if (hdr.first_size == 0) + return 0; + + if (hdr.first_size + off > buf_size) + return 0; + + return fptr.read(buf + off,hdr.first_size); + } + + Uint32 DNDFile::readLastChunk(Uint8* buf,Uint32 off,Uint32 buf_size) + { + File fptr; + if (!fptr.open(path,"rb")) + { + create(); + return 0; + } + + DNDFileHeader hdr; + if (fptr.read(&hdr,sizeof(DNDFileHeader)) != sizeof(DNDFileHeader)) + { + create(); + return 0; + } + + if (hdr.last_size == 0) + return 0; + + if (hdr.last_size + off > buf_size) + return 0; + + fptr.seek(File::BEGIN,sizeof(DNDFileHeader) + hdr.first_size); + return fptr.read(buf + off,hdr.last_size); + } + + void DNDFile::writeFirstChunk(const Uint8* buf,Uint32 fc_size) + { + File fptr; + if (!fptr.open(path,"r+b")) + { + create(); + if (!fptr.open(path,"r+b")) + { + throw Error(i18n("Failed to write first chunk to DND file : %1").arg(fptr.errorString())); + } + } + + DNDFileHeader hdr; + fptr.read(&hdr,sizeof(DNDFileHeader)); + if (hdr.last_size == 0) + { + hdr.first_size = fc_size; + fptr.seek(File::BEGIN,0); + // update hash first + // SHA1Hash h = SHA1Hash::generate(buf,fc_size); + // memcpy(hdr.data_sha1,h.getData(),20); + // write header + fptr.write(&hdr,sizeof(DNDFileHeader)); + // write data + fptr.write(buf,fc_size); + } + else + { + hdr.first_size = fc_size; + Uint8* tmp = new Uint8[hdr.first_size + hdr.last_size]; + try + { + + // put everything in tmp buf + memcpy(tmp,buf,hdr.first_size); + fptr.seek(File::BEGIN,sizeof(DNDFileHeader) + hdr.first_size); + fptr.read(tmp + hdr.first_size,hdr.last_size); + + // update the hash of the header + // SHA1Hash h = SHA1Hash::generate(tmp,hdr.first_size + hdr.last_size); + // memcpy(hdr.data_sha1,h.getData(),20); + + // write header + data + fptr.seek(File::BEGIN,0); + fptr.write(&hdr,sizeof(DNDFileHeader)); + fptr.write(tmp,hdr.first_size + hdr.last_size); + delete [] tmp; + + } + catch (...) + { + delete [] tmp; + throw; + } + } + } + + + void DNDFile::writeLastChunk(const Uint8* buf,Uint32 lc_size) + { + File fptr; + if (!fptr.open(path,"r+b")) + { + create(); + if (!fptr.open(path,"r+b")) + { + throw Error(i18n("Failed to write last chunk to DND file : %1").arg(fptr.errorString())); + } + } + + DNDFileHeader hdr; + fptr.read(&hdr,sizeof(DNDFileHeader)); + hdr.last_size = lc_size; + Uint8* tmp = new Uint8[hdr.first_size + hdr.last_size]; + try + { + // put everything in tmp buf + memcpy(tmp + hdr.first_size,buf,lc_size); + if (hdr.first_size > 0) + { + fptr.seek(File::BEGIN,sizeof(DNDFileHeader)); + fptr.read(tmp,hdr.first_size); + } + + // update the hash of the header + // SHA1Hash h = SHA1Hash::generate(tmp,hdr.first_size + hdr.last_size); + // memcpy(hdr.data_sha1,h.getData(),20); + + // write header + data + fptr.seek(File::BEGIN,0); + fptr.write(&hdr,sizeof(DNDFileHeader)); + fptr.write(tmp,hdr.first_size + hdr.last_size); + delete [] tmp; + } + catch (...) + { + delete [] tmp; + throw; + } + } + +} diff --git a/libktorrent/torrent/dndfile.h b/libktorrent/torrent/dndfile.h new file mode 100644 index 0000000..a7a7e7b --- /dev/null +++ b/libktorrent/torrent/dndfile.h @@ -0,0 +1,89 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef BTDNDFILE_H +#define BTDNDFILE_H + +#include <qstring.h> +#include <util/constants.h> + +namespace bt +{ + + + /** + * @author Joris Guisson <joris.guisson@gmail.com> + * + * Special file where we keep the first and last chunk of a file which is marked as do not download. + * THe first and last chunk of a file will most certainly be partial chunks. + */ + class DNDFile + { + public: + DNDFile(const QString & path); + virtual ~DNDFile(); + + /// Change the path of the file + void changePath(const QString & npath); + + /** + * CHeck integrity of the file, create it if it doesn't exist. + */ + void checkIntegrity(); + + /** + * Read the (partial)first chunk into a buffer. + * @param buf The buffer + * @param off OFfset into the buffer + * @param buf_size Size of the buffer + */ + Uint32 readFirstChunk(Uint8* buf,Uint32 off,Uint32 buf_size); + + /** + * Read the (partial)last chunk into a buffer. + * @param buf The buffer + * @param off OFfset into the buffer + * @param buf_size Size of the buffer + */ + Uint32 readLastChunk(Uint8* buf,Uint32 off,Uint32 buf_size); + + /** + * Write the partial first chunk. + * @param buf The buffer + * @param fc_size Size to write + */ + void writeFirstChunk(const Uint8* buf,Uint32 fc_size); + + /** + * Write the partial last chunk. + * @param buf The buffer + * @param lc_size Size to write + */ + void writeLastChunk(const Uint8* buf,Uint32 lc_size); + + private: + void create(); + + private: + QString path; + }; + +} + +#endif diff --git a/libktorrent/torrent/downloadcap.cpp b/libktorrent/torrent/downloadcap.cpp new file mode 100644 index 0000000..73e0cbb --- /dev/null +++ b/libktorrent/torrent/downloadcap.cpp @@ -0,0 +1,43 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#if 0 +#include <math.h> +#include <util/log.h> +#include "downloadcap.h" +#include "globals.h" + +namespace bt +{ + DownloadCap DownloadCap::self; + + const Uint32 SLOT_SIZE = 5*1024; + + DownloadCap::DownloadCap() : Cap(true) + { + } + + DownloadCap::~ DownloadCap() + { + } + + + +} +#endif diff --git a/libktorrent/torrent/downloadcap.h b/libktorrent/torrent/downloadcap.h new file mode 100644 index 0000000..2bda73c --- /dev/null +++ b/libktorrent/torrent/downloadcap.h @@ -0,0 +1,48 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef BTDOWNLOADCAP_H +#define BTDOWNLOADCAP_H + +#if 0 +#include <qvaluelist.h> +#include <util/timer.h> +#include "globals.h" +#include "cap.h" + +namespace bt +{ + + /** + * @author Joris Guisson + */ + class DownloadCap : public Cap + { + static DownloadCap self; + + DownloadCap(); + public: + ~DownloadCap(); + + static DownloadCap & instance() {return self;} + }; + +} +#endif +#endif diff --git a/libktorrent/torrent/downloader.cpp b/libktorrent/torrent/downloader.cpp new file mode 100644 index 0000000..b8acdc7 --- /dev/null +++ b/libktorrent/torrent/downloader.cpp @@ -0,0 +1,688 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <util/file.h> +#include <util/log.h> +#include "downloader.h" +#include "chunkmanager.h" +#include "torrent.h" +#include "peermanager.h" +#include <util/error.h> +#include "chunkdownload.h" +#include <util/sha1hash.h> +#include <util/array.h> +#include "peer.h" +#include "piece.h" +#include "peerdownloader.h" +#include <interfaces/functions.h> +#include <interfaces/monitorinterface.h> +#include "packetwriter.h" +#include "chunkselector.h" +#include "ipblocklist.h" +#include "ktversion.h" + +namespace bt +{ + + + + Downloader::Downloader(Torrent & tor,PeerManager & pman,ChunkManager & cman) + : tor(tor),pman(pman),cman(cman),downloaded(0),tmon(0) + { + chunk_selector = new ChunkSelector(cman,*this,pman); + Uint64 total = tor.getFileLength(); + downloaded = (total - cman.bytesLeft()); + curr_chunks_downloaded = 0; + unnecessary_data = 0; + + current_chunks.setAutoDelete(true); + connect(&pman,SIGNAL(newPeer(Peer* )),this,SLOT(onNewPeer(Peer* ))); + connect(&pman,SIGNAL(peerKilled(Peer* )),this,SLOT(onPeerKilled(Peer*))); + } + + + Downloader::~Downloader() + { + delete chunk_selector; + } + + void Downloader::pieceRecieved(const Piece & p) + { + if (cman.completed()) + return; + + ChunkDownload* cd = 0; + + for (CurChunkItr j = current_chunks.begin();j != current_chunks.end();++j) + { + if (p.getIndex() != j->first) + continue; + + cd = j->second; + break; + } + + if (!cd) + { + unnecessary_data += p.getLength(); + Out(SYS_DIO|LOG_DEBUG) << + "Unnecessary piece, total unnecessary data : " << kt::BytesToString(unnecessary_data) << endl; + return; + } + + // if the chunk is not in memory, reload it + if (cd->getChunk()->getStatus() == Chunk::ON_DISK) + { + cman.prepareChunk(cd->getChunk(),true); + } + + bool ok = false; + + if (cd->piece(p,ok)) + { + if (tmon) + tmon->downloadRemoved(cd); + + if (ok) + downloaded += p.getLength(); + + if (!finished(cd)) + { + // if the chunk fails don't count the bytes downloaded + if (cd->getChunk()->getSize() > downloaded) + downloaded = 0; + else + downloaded -= cd->getChunk()->getSize(); + } + current_chunks.erase(p.getIndex()); + update(); // run an update to assign new pieces + } + else + { + if (ok) + downloaded += p.getLength(); + + // save to disk again, if it is idle + if (cd->isIdle() && cd->getChunk()->getStatus() == Chunk::MMAPPED) + { + cman.saveChunk(cd->getChunk()->getIndex(),false); + } + } + + if (!ok) + { + unnecessary_data += p.getLength(); + Out(SYS_DIO|LOG_DEBUG) << + "Unnecessary piece, total unnecessary data : " << kt::BytesToString(unnecessary_data) << endl; + } + } + + void Downloader::update() + { + if (cman.completed()) + return; + + /* + Normal update should now handle all modes properly. + */ + normalUpdate(); + + // now see if there aren't any timed out pieces + for (Uint32 i = 0;i < pman.getNumConnectedPeers();i++) + { + Peer* p = pman.getPeer(i); + p->getPeerDownloader()->checkTimeouts(); + } + } + + + void Downloader::normalUpdate() + { + for (CurChunkItr j = current_chunks.begin();j != current_chunks.end();++j) + { + ChunkDownload* cd = j->second; + if (cd->isIdle()) // idle chunks do not need to be in memory + { + Chunk* c = cd->getChunk(); + if (c->getStatus() == Chunk::MMAPPED) + { + cman.saveChunk(cd->getChunk()->getIndex(),false); + } + } + else if (cd->isChoked()) + { + cd->releaseAllPDs(); + Chunk* c = cd->getChunk(); + if (c->getStatus() == Chunk::MMAPPED) + { + cman.saveChunk(cd->getChunk()->getIndex(),false); + } + } + else if (cd->needsToBeUpdated()) + { + cd->update(); + } + } + + for (Uint32 i = 0; i < pman.getNumConnectedPeers();++i) + { + PeerDownloader* pd = pman.getPeer(i)->getPeerDownloader(); + + if (pd->isNull()) + continue; + + bool ok = + (pd->getNumGrabbed() < pd->getMaxChunkDownloads() || + pd->isNearlyDone()) && + pd->canAddRequest(); + + + if (ok) + { + if (!pd->isChoked()) + downloadFrom(pd); + + pd->setNearlyDone(false); + } + } + } + + Uint32 Downloader::maxMemoryUsage() + { + Uint32 max = 1024 * 1024; + switch (mem_usage) + { + case 1: // Medium + max *= 60; // 60 MB + break; + case 2: // High + max *= 80; // 90 MB + break; + case 0: // LOW + default: + max *= 40; // 30 MB + break; + } + return max; + } + + Uint32 Downloader::numNonIdle() + { + Uint32 num_non_idle = 0; + for (CurChunkItr j = current_chunks.begin();j != current_chunks.end();++j) + { + ChunkDownload* cd = j->second; + if (!cd->isIdle()) + num_non_idle++; + } + return num_non_idle; + } + + ChunkDownload* Downloader::selectCD(PeerDownloader* pd,Uint32 num) + { + ChunkDownload* sel = 0; + Uint32 sel_left = 0xFFFFFFFF; + + for (CurChunkItr j = current_chunks.begin();j != current_chunks.end();++j) + { + ChunkDownload* cd = j->second; + if (pd->isChoked() || !pd->hasChunk(cd->getChunk()->getIndex())) + continue; + + if (cd->getNumDownloaders() == num) + { + // lets favor the ones which are nearly finished + if (!sel || cd->getTotalPieces() - cd->getPiecesDownloaded() < sel_left) + { + sel = cd; + sel_left = sel->getTotalPieces() - sel->getPiecesDownloaded(); + } + } + } + return sel; + } + + bool Downloader::findDownloadForPD(PeerDownloader* pd,bool warmup) + { + ChunkDownload* sel = 0; + + // first see if there are ChunkDownload's which need a PeerDownloader + sel = selectCD(pd,0); + + if (!sel && warmup) + { + // if we couldn't find one, try to select another + // which only has one downloader + // so that during warmup, there are at the most 2 downloaders + // assigned to one peer + sel = selectCD(pd,1); + } + + if (sel) + { + // if it is on disk, reload it + if (sel->getChunk()->getStatus() == Chunk::ON_DISK) + cman.prepareChunk(sel->getChunk(),true); + + sel->assignPeer(pd); + return true; + } + + return false; + } + + ChunkDownload* Downloader::selectWorst(PeerDownloader* pd) + { + ChunkDownload* cdmin = NULL; + for (CurChunkItr j = current_chunks.begin();j != current_chunks.end();++j) + { + ChunkDownload* cd = j->second; + if (!pd->hasChunk(cd->getChunk()->getIndex()) || cd->containsPeer(pd)) + continue; + + if (!cdmin) + cdmin = cd; + else if (cd->getDownloadSpeed() < cdmin->getDownloadSpeed()) + cdmin = cd; + else if (cd->getNumDownloaders() < cdmin->getNumDownloaders()) + cdmin = cd; + } + + return cdmin; + } + + void Downloader::downloadFrom(PeerDownloader* pd) + { + // calculate the max memory usage + Uint32 max = maxMemoryUsage(); + // calculate number of non idle chunks + Uint32 num_non_idle = numNonIdle(); + + // first see if we can use an existing dowload + if (findDownloadForPD(pd,cman.getNumChunks() - cman.chunksLeft() <= 4)) + return; + + bool limit_exceeded = num_non_idle * tor.getChunkSize() >= max; + + Uint32 chunk = 0; + if (!limit_exceeded && chunk_selector->select(pd,chunk)) + { + Chunk* c = cman.getChunk(chunk); + if (cman.prepareChunk(c)) + { + ChunkDownload* cd = new ChunkDownload(c); + current_chunks.insert(chunk,cd); + cd->assignPeer(pd); + if (tmon) + tmon->downloadStarted(cd); + } + } + else if (pd->getNumGrabbed() == 0) + { + // If the peer hasn't got a chunk we want, + ChunkDownload *cdmin = selectWorst(pd); + + if (cdmin) + { + // if it is on disk, reload it + if (cdmin->getChunk()->getStatus() == Chunk::ON_DISK) + { + cman.prepareChunk(cdmin->getChunk(),true); + } + + cdmin->assignPeer(pd); + } + } + } + + + bool Downloader::areWeDownloading(Uint32 chunk) const + { + return current_chunks.find(chunk) != 0; + } + + void Downloader::onNewPeer(Peer* peer) + { + PeerDownloader* pd = peer->getPeerDownloader(); + connect(pd,SIGNAL(downloaded(const Piece& )), + this,SLOT(pieceRecieved(const Piece& ))); + } + + void Downloader::onPeerKilled(Peer* peer) + { + PeerDownloader* pd = peer->getPeerDownloader(); + if (pd) + { + for (CurChunkItr i = current_chunks.begin();i != current_chunks.end();++i) + { + ChunkDownload* cd = i->second; + cd->peerKilled(pd); + } + } + } + + bool Downloader::finished(ChunkDownload* cd) + { + Chunk* c = cd->getChunk(); + // verify the data + SHA1Hash h; + if (cd->usingContinuousHashing()) + h = cd->getHash(); + else + h = SHA1Hash::generate(c->getData(),c->getSize()); + + if (tor.verifyHash(h,c->getIndex())) + { + // hash ok so save it + try + { + cman.saveChunk(c->getIndex()); + Out(SYS_GEN|LOG_NOTICE) << "Chunk " << c->getIndex() << " downloaded " << endl; + // tell everybody we have the Chunk + for (Uint32 i = 0;i < pman.getNumConnectedPeers();i++) + { + pman.getPeer(i)->getPacketWriter().sendHave(c->getIndex()); + } + } + catch (Error & e) + { + Out(SYS_DIO|LOG_IMPORTANT) << "Error " << e.toString() << endl; + emit ioError(e.toString()); + return false; + } + } + else + { + Out(SYS_GEN|LOG_IMPORTANT) << "Hash verification error on chunk " << c->getIndex() << endl; + Out(SYS_GEN|LOG_IMPORTANT) << "Is : " << h << endl; + Out(SYS_GEN|LOG_IMPORTANT) << "Should be : " << tor.getHash(c->getIndex()) << endl; + + cman.resetChunk(c->getIndex()); + chunk_selector->reinsert(c->getIndex()); + Uint32 pid; + if (cd->getOnlyDownloader(pid)) + { + Peer* p = pman.findPeer(pid); + if (!p) + return false; + QString IP(p->getIPAddresss()); + Out(SYS_GEN|LOG_NOTICE) << "Peer " << IP << " sent bad data" << endl; + IPBlocklist & ipfilter = IPBlocklist::instance(); + ipfilter.insert( IP ); + p->kill(); + } + return false; + } + return true; + } + + void Downloader::clearDownloads() + { + for (CurChunkItr i = current_chunks.begin();i != current_chunks.end();++i) + { + Uint32 ch = i->first; + Chunk* c = i->second->getChunk(); + if (c->getStatus() == Chunk::MMAPPED) + cman.saveChunk(ch,false); + + c->setStatus(Chunk::NOT_DOWNLOADED); + } + current_chunks.clear(); + } + + Uint32 Downloader::downloadRate() const + { + // sum of the download rate of each peer + Uint32 rate = 0; + for (Uint32 i = 0;i < pman.getNumConnectedPeers();i++) + { + Peer* p = pman.getPeer(i); + rate += p->getDownloadRate(); + } + return rate; + } + + void Downloader::setMonitor(kt::MonitorInterface* tmo) + { + tmon = tmo; + if (!tmon) + return; + + for (CurChunkItr i = current_chunks.begin();i != current_chunks.end();++i) + { + ChunkDownload* cd = i->second; + tmon->downloadStarted(cd); + } + } + + + + void Downloader::saveDownloads(const QString & file) + { + File fptr; + if (!fptr.open(file,"wb")) + return; + + // Save all the current downloads to a file + CurrentChunksHeader hdr; + hdr.magic = CURRENT_CHUNK_MAGIC; + hdr.major = kt::MAJOR; + hdr.minor = kt::MINOR; + hdr.num_chunks = current_chunks.count(); + fptr.write(&hdr,sizeof(CurrentChunksHeader)); + +// Out() << "sizeof(CurrentChunksHeader)" << sizeof(CurrentChunksHeader) << endl; + Out() << "Saving " << current_chunks.count() << " chunk downloads" << endl; + for (CurChunkItr i = current_chunks.begin();i != current_chunks.end();++i) + { + ChunkDownload* cd = i->second; + cd->save(fptr); + } + } + + void Downloader::loadDownloads(const QString & file) + { + // don't load stuff if download is finished + if (cman.completed()) + return; + + // Load all partial downloads + File fptr; + if (!fptr.open(file,"rb")) + return; + + // recalculate downloaded bytes + downloaded = (tor.getFileLength() - cman.bytesLeft()); + + CurrentChunksHeader chdr; + fptr.read(&chdr,sizeof(CurrentChunksHeader)); + if (chdr.magic != CURRENT_CHUNK_MAGIC) + { + Out() << "Warning : current_chunks file corrupted" << endl; + return; + } + + Out() << "Loading " << chdr.num_chunks << " active chunk downloads" << endl; + for (Uint32 i = 0;i < chdr.num_chunks;i++) + { + ChunkDownloadHeader hdr; + // first read header + fptr.read(&hdr,sizeof(ChunkDownloadHeader)); + Out() << "Loading chunk " << hdr.index << endl; + if (hdr.index >= tor.getNumChunks()) + { + Out() << "Warning : current_chunks file corrupted, invalid index " << hdr.index << endl; + return; + } + + if (!cman.getChunk(hdr.index) || current_chunks.contains(hdr.index)) + { + Out() << "Illegal chunk " << hdr.index << endl; + return; + } + Chunk* c = cman.getChunk(hdr.index); + if (!c->isExcluded() && cman.prepareChunk(c)) + { + ChunkDownload* cd = new ChunkDownload(c); + bool ret = false; + try + { + ret = cd->load(fptr,hdr); + } + catch (...) + { + ret = false; + } + + if (!ret) + { + delete cd; + } + else + { + current_chunks.insert(hdr.index,cd); + downloaded += cd->bytesDownloaded(); + + if (tmon) + tmon->downloadStarted(cd); + } + } + } + + // reset curr_chunks_downloaded to 0 + curr_chunks_downloaded = 0; + } + + Uint32 Downloader::getDownloadedBytesOfCurrentChunksFile(const QString & file) + { + // Load all partial downloads + File fptr; + if (!fptr.open(file,"rb")) + return 0; + + // read the number of chunks + CurrentChunksHeader chdr; + fptr.read(&chdr,sizeof(CurrentChunksHeader)); + if (chdr.magic != CURRENT_CHUNK_MAGIC) + { + Out() << "Warning : current_chunks file corrupted" << endl; + return 0; + } + Uint32 num_bytes = 0; + + // load all chunks and calculate how much is downloaded + for (Uint32 i = 0;i < chdr.num_chunks;i++) + { + // read the chunkdownload header + ChunkDownloadHeader hdr; + fptr.read(&hdr,sizeof(ChunkDownloadHeader)); + + Chunk* c = cman.getChunk(hdr.index); + if (!c) + return num_bytes; + + Uint32 last_size = c->getSize() % MAX_PIECE_LEN; + if (last_size == 0) + last_size = MAX_PIECE_LEN; + + // create the bitset and read it + BitSet bs(hdr.num_bits); + fptr.read(bs.getData(),bs.getNumBytes()); + + for (Uint32 j = 0;j < hdr.num_bits;j++) + { + if (bs.get(j)) + num_bytes += j == hdr.num_bits - 1 ? + last_size : MAX_PIECE_LEN; + } + + if (hdr.buffered) + fptr.seek(File::CURRENT,c->getSize()); + } + curr_chunks_downloaded = num_bytes; + return num_bytes; + } + + bool Downloader::isFinished() const + { + return cman.completed(); + } + + void Downloader::onExcluded(Uint32 from,Uint32 to) + { + for (Uint32 i = from;i <= to;i++) + { + ChunkDownload* cd = current_chunks.find(i); + // let only seed chunks finish + if (!cd || cman.getChunk(i)->getPriority() == ONLY_SEED_PRIORITY) + continue; + + cd->cancelAll(); + cd->releaseAllPDs(); + if (tmon) + tmon->downloadRemoved(cd); + current_chunks.erase(i); + cman.resetChunk(i); // reset chunk it is not fully downloaded yet + } + } + + void Downloader::onIncluded(Uint32 from,Uint32 to) + { + chunk_selector->reincluded(from,to); + } + + void Downloader::corrupted(Uint32 chunk) + { + chunk_selector->reinsert(chunk); + } + + Uint32 Downloader::mem_usage = 0; + + void Downloader::setMemoryUsage(Uint32 m) + { + mem_usage = m; +// PeerDownloader::setMemoryUsage(m); + } + + void Downloader::dataChecked(const BitSet & ok_chunks) + { + for (Uint32 i = 0;i < ok_chunks.getNumBits();i++) + { + ChunkDownload* cd = current_chunks.find(i); + if (ok_chunks.get(i) && cd) + { + // we have a chunk and we are downloading it so kill it + cd->releaseAllPDs(); + if (tmon) + tmon->downloadRemoved(cd); + + current_chunks.erase(i); + } + } + chunk_selector->dataChecked(ok_chunks); + } + + void Downloader::recalcDownloaded() + { + Uint64 total = tor.getFileLength(); + downloaded = (total - cman.bytesLeft()); + } +} + +#include "downloader.moc" diff --git a/libktorrent/torrent/downloader.h b/libktorrent/torrent/downloader.h new file mode 100644 index 0000000..5b39eeb --- /dev/null +++ b/libktorrent/torrent/downloader.h @@ -0,0 +1,221 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef BTDOWNLOADER_H +#define BTDOWNLOADER_H + +#include <qobject.h> +#include <util/ptrmap.h> +#include "globals.h" + +namespace kt +{ + class MonitorInterface; +} + + +namespace bt +{ + class BitSet; + class Torrent; + class ChunkManager; + class PeerManager; + class Peer; + class Chunk; + class ChunkDownload; + class PeerDownloader; + class Piece; + class Request; + class ChunkSelector; + + typedef PtrMap<Uint32,ChunkDownload>::iterator CurChunkItr; + typedef PtrMap<Uint32,ChunkDownload>::const_iterator CurChunkCItr; + + #define CURRENT_CHUNK_MAGIC 0xABCDEF00 + + struct CurrentChunksHeader + { + Uint32 magic; // CURRENT_CHUNK_MAGIC + Uint32 major; + Uint32 minor; + Uint32 num_chunks; + }; + + /** + * @author Joris Guisson + * @brief Manages the downloading + * + * This class manages the downloading of the file. It should + * regurarly be updated. + */ + class Downloader : public QObject + { + Q_OBJECT + + public: + /** + * Constructor. + * @param tor The Torrent + * @param pman The PeerManager + * @param cman The ChunkManager + */ + Downloader(Torrent & tor,PeerManager & pman,ChunkManager & cman); + virtual ~Downloader(); + + /// Get the number of bytes we have downloaded + Uint64 bytesDownloaded() const {return downloaded + curr_chunks_downloaded;} + + /// Get the current dowload rate + Uint32 downloadRate() const; + + /// Get the number of chunks we are dowloading + Uint32 numActiveDownloads() const {return current_chunks.count();} + + /// See if the download is finished. + bool isFinished() const; + + /** + * Clear all downloads. Deletes all active downloads. + */ + void clearDownloads(); + + CurChunkCItr beginDownloads() const {return current_chunks.begin();} + CurChunkCItr endDownloads() const {return current_chunks.end();} + + /** + * See if we are downloading a Chunk + * @param chunk ID of Chunk + * @return true if we are, false if not + */ + bool areWeDownloading(Uint32 chunk) const; + + /** + * Save the current downloads. + * @param file The file to save to + */ + void saveDownloads(const QString & file); + + /** + * Load the current downloads. + * @param file The file to load from + */ + void loadDownloads(const QString & file); + + /** + * Get the number of bytes already downloaded in the current_chunks file. + * @param file The path of the current_chunks file + * @return The bytes already downloading + */ + Uint32 getDownloadedBytesOfCurrentChunksFile(const QString & file); + + /** + * A corrupted chunk has been detected, make sure we redownload it. + * @param chunk The chunk + */ + void corrupted(Uint32 chunk); + public slots: + /** + * Update the downloader. + */ + void update(); + + /** + * We got a new connection. + * @param peer The Peer + */ + void onNewPeer(Peer* peer); + + /** + * A Peer has disconnected. + * @param peer The Peer + */ + void onPeerKilled(Peer* peer); + + /** + * Set the TorrentMonitor. + * @param tmo + */ + void setMonitor(kt::MonitorInterface* tmo); + + static void setMemoryUsage(Uint32 m); + + /** + * Data has been checked, and these chunks are OK. + * @param ok_chunks The ok_chunks + */ + void dataChecked(const BitSet & ok_chunks); + + /** + * Recalculate the number of bytes downloaded. + */ + void recalcDownloaded(); + + private slots: + void pieceRecieved(const Piece & p); + bool finished(ChunkDownload* c); + + /** + * Kill all ChunkDownload's which have been excluded. + * @param from First chunk of range + * @param to Last chunk of range + */ + void onExcluded(Uint32 from,Uint32 to); + + /** + * Make sure chunk selector is back OK, when chunks are included back again. + * @param from First chunk + * @param to Last chunk + */ + void onIncluded(Uint32 from,Uint32 to); + + signals: + /** + * An error occurred while we we're writing or reading from disk. + * @param msg Message + */ + void ioError(const QString & msg); + + private: + void downloadFrom(PeerDownloader* pd); + void normalUpdate(); + Uint32 maxMemoryUsage(); + Uint32 numNonIdle(); + bool findDownloadForPD(PeerDownloader* pd,bool warmup); + ChunkDownload* selectCD(PeerDownloader* pd,Uint32 num); + ChunkDownload* selectWorst(PeerDownloader* pd); + + private: + Torrent & tor; + PeerManager & pman; + ChunkManager & cman; + Uint64 downloaded; + Uint64 curr_chunks_downloaded; + Uint64 unnecessary_data; + PtrMap<Uint32,ChunkDownload> current_chunks; + ChunkSelector* chunk_selector; + + kt::MonitorInterface* tmon; + static Uint32 mem_usage; + }; + + + +} + +#endif diff --git a/libktorrent/torrent/globals.cpp b/libktorrent/torrent/globals.cpp new file mode 100644 index 0000000..0221c17 --- /dev/null +++ b/libktorrent/torrent/globals.cpp @@ -0,0 +1,97 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <util/log.h> +#include <util/error.h> +#include <net/portlist.h> +#include <kademlia/dht.h> + +#include "globals.h" +#include "server.h" + +namespace bt +{ + + Globals* Globals::inst = 0; + + Globals::Globals() + { + plist = new net::PortList(); + debug_mode = false; + log = new Log(); + server = 0; + dh_table = new dht::DHT(); + } + + Globals::~ Globals() + { + delete server; + delete log; + delete dh_table; + delete plist; + } + + Globals & Globals::instance() + { + if (!inst) + inst = new Globals(); + return *inst; + } + + void Globals::cleanup() + { + delete inst; + inst = 0; + } + + void Globals::initLog(const QString & file) + { + log->setOutputFile(file); + log->setOutputToConsole(debug_mode); + } + + void Globals::initServer(Uint16 port) + { + if (server) + { + delete server; + server = 0; + } + + server = new Server(port); + } + + void Globals::shutdownServer() + { + if (server) + { + server->close(); + } + } + + Log& Globals::getLog(unsigned int arg) + { + log->setFilter(arg); + return *log; + } + + +} + diff --git a/libktorrent/torrent/globals.h b/libktorrent/torrent/globals.h new file mode 100644 index 0000000..7cfe3f5 --- /dev/null +++ b/libktorrent/torrent/globals.h @@ -0,0 +1,78 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef BTGLOBALS_H +#define BTGLOBALS_H + +#include <util/constants.h> + +class QString; + +namespace net +{ + class PortList; +} + +namespace dht +{ + class DHTBase; +} + +namespace bt +{ + class Log; + class Server; + + + + class Globals + { + public: + virtual ~Globals(); + + void initLog(const QString & file); + void initServer(Uint16 port); + void setDebugMode(bool on) {debug_mode = on;} + bool isDebugModeSet() const {return debug_mode;} + void shutdownServer(); + + Log & getLog(unsigned int arg); + Server & getServer() {return *server;} + dht::DHTBase & getDHT() {return *dh_table;} + net::PortList & getPortList() {return *plist;} + + static Globals & instance(); + static void cleanup(); + private: + Globals(); + + bool debug_mode; + Log* log; + Server* server; + dht::DHTBase* dh_table; + net::PortList* plist; + + friend Log& Out(unsigned int arg); + + static Globals* inst; + + }; +} + +#endif diff --git a/libktorrent/torrent/httptracker.cpp b/libktorrent/torrent/httptracker.cpp new file mode 100644 index 0000000..b220bc0 --- /dev/null +++ b/libktorrent/torrent/httptracker.cpp @@ -0,0 +1,462 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <config.h> + +#include <kurl.h> +#include <klocale.h> +#include <qhostaddress.h> +#include <util/log.h> +#include <util/functions.h> +#include <util/error.h> +#include <util/waitjob.h> +#include <interfaces/exitoperation.h> +#include <kio/job.h> +#include <kio/netaccess.h> +#include <kio/scheduler.h> +#include "bnode.h" +#include "httptracker.h" +#include "torrentcontrol.h" +#include "bdecoder.h" +#include "peermanager.h" +#include "server.h" +#include "globals.h" +#include "settings.h" + + +using namespace kt; + +namespace bt +{ + + HTTPTracker::HTTPTracker(const KURL & url,kt::TorrentInterface* tor,const PeerID & id,int tier) + : Tracker(url,tor,id,tier) + { + active_job = 0; + + interval = 5 * 60; // default interval 5 minutes + failures = 0; + seeders = leechers = 0; + } + + + HTTPTracker::~HTTPTracker() + { + } + + void HTTPTracker::start() + { + event = "started"; + doRequest(); + } + + void HTTPTracker::stop(WaitJob* wjob) + { + if (!started) + return; + + event = "stopped"; + doRequest(wjob); + started = false; + } + + void HTTPTracker::completed() + { + event = "completed"; + doRequest(); + event = QString::null; + } + + void HTTPTracker::manualUpdate() + { + if (!started) + event = "started"; + doRequest(); + } + + void HTTPTracker::scrape() + { + if (!url.isValid()) + { + Out(SYS_TRK|LOG_NOTICE) << "Invalid tracker url, canceling scrape" << endl; + return; + } + + if (!url.fileName(false).startsWith("announce")) + { + Out(SYS_TRK|LOG_NOTICE) << "Tracker " << url << " does not support scraping" << endl; + return; + } + + KURL scrape_url = url; + scrape_url.setFileName(url.fileName(false).replace("announce","scrape")); + + QString epq = scrape_url.encodedPathAndQuery(); + const SHA1Hash & info_hash = tor->getInfoHash(); + if (scrape_url.queryItems().count() > 0) + epq += "&info_hash=" + info_hash.toURLString(); + else + epq += "?info_hash=" + info_hash.toURLString(); + scrape_url.setEncodedPathAndQuery(epq); + + Out(SYS_TRK|LOG_NOTICE) << "Doing scrape request to url : " << scrape_url.prettyURL() << endl; + KIO::MetaData md; + setupMetaData(md); + + KIO::StoredTransferJob* j = KIO::storedGet(scrape_url,false,false); + // set the meta data + j->setMetaData(md); + KIO::Scheduler::scheduleJob(j); + + connect(j,SIGNAL(result(KIO::Job* )),this,SLOT(onScrapeResult( KIO::Job* ))); + } + + void HTTPTracker::onScrapeResult(KIO::Job* j) + { + if (j->error()) + { + Out(SYS_TRK|LOG_IMPORTANT) << "Scrape failed : " << j->errorString() << endl; + return; + } + + KIO::StoredTransferJob* st = (KIO::StoredTransferJob*)j; + BDecoder dec(st->data(),false,0); + BNode* n = 0; + + try + { + n = dec.decode(); + } + catch (bt::Error & err) + { + Out(SYS_TRK|LOG_IMPORTANT) << "Invalid scrape data " << err.toString() << endl; + return; + } + + if (n && n->getType() == BNode::DICT) + { + BDictNode* d = (BDictNode*)n; + d = d->getDict("files"); + if (d) + { + d = d->getDict(tor->getInfoHash().toByteArray()); + if (d) + { + BValueNode* vn = d->getValue("complete"); + if (vn && vn->data().getType() == Value::INT) + { + seeders = vn->data().toInt(); + } + + + vn = d->getValue("incomplete"); + if (vn && vn->data().getType() == Value::INT) + { + leechers = vn->data().toInt(); + } + + Out(SYS_TRK|LOG_DEBUG) << "Scrape : leechers = " << leechers + << ", seeders = " << seeders << endl; + } + } + } + + delete n; + } + + void HTTPTracker::doRequest(WaitJob* wjob) + { + const TorrentStats & s = tor->getStats(); + + KURL u = url; + if (!url.isValid()) + { + requestPending(); + QTimer::singleShot(500,this,SLOT(emitInvalidURLFailure())); + return; + } + + Uint16 port = Globals::instance().getServer().getPortInUse();; + + u.addQueryItem("peer_id",peer_id.toString()); + u.addQueryItem("port",QString::number(port)); + u.addQueryItem("uploaded",QString::number(s.trk_bytes_uploaded)); + u.addQueryItem("downloaded",QString::number(s.trk_bytes_downloaded)); + + if (event == "completed") + u.addQueryItem("left","0"); // need to send 0 when we are completed + else + u.addQueryItem("left",QString::number(s.bytes_left)); + + u.addQueryItem("compact","1"); + if (event != "stopped") + u.addQueryItem("numwant","100"); + else + u.addQueryItem("numwant","0"); + + u.addQueryItem("key",QString::number(key)); + QString cip = Tracker::getCustomIP(); + if (!cip.isNull()) + u.addQueryItem("ip",cip); + + if (event != QString::null) + u.addQueryItem("event",event); + QString epq = u.encodedPathAndQuery(); + const SHA1Hash & info_hash = tor->getInfoHash(); + epq += "&info_hash=" + info_hash.toURLString(); + + + u.setEncodedPathAndQuery(epq); + + if (active_job) + { + announce_queue.append(u); + Out(SYS_TRK|LOG_NOTICE) << "Announce ongoing, queueing announce" << endl; + } + else + { + doAnnounce(u); + // if there is a wait job, add this job to the waitjob + if (wjob) + wjob->addExitOperation(new kt::ExitJobOperation(active_job)); + } + } + + bool HTTPTracker::updateData(const QByteArray & data) + { +//#define DEBUG_PRINT_RESPONSE +#ifdef DEBUG_PRINT_RESPONSE + Out() << "Data : " << endl; + Out() << QString(data) << endl; +#endif + // search for dictionary, there might be random garbage infront of the data + Uint32 i = 0; + while (i < data.size()) + { + if (data[i] == 'd') + break; + i++; + } + + if (i == data.size()) + { + failures++; + requestFailed(i18n("Invalid response from tracker")); + return false; + } + + BDecoder dec(data,false,i); + BNode* n = 0; + try + { + n = dec.decode(); + } + catch (...) + { + failures++; + requestFailed(i18n("Invalid data from tracker")); + return false; + } + + if (!n || n->getType() != BNode::DICT) + { + failures++; + requestFailed(i18n("Invalid response from tracker")); + return false; + } + + BDictNode* dict = (BDictNode*)n; + if (dict->getData("failure reason")) + { + BValueNode* vn = dict->getValue("failure reason"); + QString msg = vn->data().toString(); + delete n; + failures++; + requestFailed(msg); + return false; + } + + BValueNode* vn = dict->getValue("interval"); + + // if no interval is specified, use 5 minutes + if (vn) + interval = vn->data().toInt(); + else + interval = 5 * 60; + + vn = dict->getValue("incomplete"); + if (vn) + leechers = vn->data().toInt(); + + vn = dict->getValue("complete"); + if (vn) + seeders = vn->data().toInt(); + + BListNode* ln = dict->getList("peers"); + if (!ln) + { + // no list, it might however be a compact response + vn = dict->getValue("peers"); + if (!vn) + { + delete n; + failures++; + requestFailed(i18n("Invalid response from tracker")); + return false; + } + + QByteArray arr = vn->data().toByteArray(); + for (Uint32 i = 0;i < arr.size();i+=6) + { + Uint8 buf[6]; + for (int j = 0;j < 6;j++) + buf[j] = arr[i + j]; + + addPeer(QHostAddress(ReadUint32(buf,0)).toString(),ReadUint16(buf,4)); + } + } + else + { + for (Uint32 i = 0;i < ln->getNumChildren();i++) + { + BDictNode* dict = dynamic_cast<BDictNode*>(ln->getChild(i)); + + if (!dict) + continue; + + BValueNode* ip_node = dict->getValue("ip"); + BValueNode* port_node = dict->getValue("port"); + + if (!ip_node || !port_node) + continue; + + addPeer(ip_node->data().toString(),port_node->data().toInt()); + } + } + + delete n; + return true; + } + + + void HTTPTracker::onAnnounceResult(KIO::Job* j) + { + if (j->error()) + { + KIO::StoredTransferJob* st = (KIO::StoredTransferJob*)j; + KURL u = st->url(); + active_job = 0; + + Out(SYS_TRK|LOG_IMPORTANT) << "Error : " << st->errorString() << endl; + if (u.queryItem("event") != "stopped") + { + failures++; + requestFailed(j->errorString()); + } + else + { + stopDone(); + } + } + else + { + KIO::StoredTransferJob* st = (KIO::StoredTransferJob*)j; + KURL u = st->url(); + active_job = 0; + + if (u.queryItem("event") != "stopped") + { + try + { + if (updateData(st->data())) + { + failures = 0; + peersReady(this); + requestOK(); + if (u.queryItem("event") == "started") + started = true; + } + } + catch (bt::Error & err) + { + failures++; + requestFailed(i18n("Invalid response from tracker")); + } + event = QString::null; + } + else + { + failures = 0; + stopDone(); + } + } + doAnnounceQueue(); + } + + void HTTPTracker::emitInvalidURLFailure() + { + failures++; + requestFailed(i18n("Invalid tracker URL")); + } + + void HTTPTracker::setupMetaData(KIO::MetaData & md) + { + md["UserAgent"] = "ktorrent/" VERSION; + md["SendLanguageSettings"] = "false"; + md["Cookies"] = "none"; + // md["accept"] = "text/plain"; + md["accept"] = "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2"; + if (Settings::doNotUseKDEProxy()) + { + // set the proxy if the doNotUseKDEProxy ix enabled (URL must be valid to) + KURL url = KURL::fromPathOrURL(Settings::httpTrackerProxy()); + if (url.isValid()) + md["UseProxy"] = url.pathOrURL(); + else + md["UseProxy"] = QString::null; + } + } + + void HTTPTracker::doAnnounceQueue() + { + if (announce_queue.empty()) + return; + + KURL u = announce_queue.front(); + announce_queue.pop_front(); + doAnnounce(u); + } + + void HTTPTracker::doAnnounce(const KURL & u) + { + Out(SYS_TRK|LOG_NOTICE) << "Doing tracker request to url : " << u.prettyURL() << endl; + KIO::MetaData md; + setupMetaData(md); + KIO::StoredTransferJob* j = KIO::storedGet(u,false,false); + // set the meta data + j->setMetaData(md); + KIO::Scheduler::scheduleJob(j); + + connect(j,SIGNAL(result(KIO::Job* )),this,SLOT(onAnnounceResult( KIO::Job* ))); + + active_job = j; + requestPending(); + } +} +#include "httptracker.moc" diff --git a/libktorrent/torrent/httptracker.h b/libktorrent/torrent/httptracker.h new file mode 100644 index 0000000..8ac7e69 --- /dev/null +++ b/libktorrent/torrent/httptracker.h @@ -0,0 +1,77 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef BTHTTPTRACKER_H +#define BTHTTPTRACKER_H + +#include <qtimer.h> +#include "tracker.h" + +namespace KIO +{ + class Job; + class MetaData; +} + +namespace bt +{ + + + /** + * @author Joris Guisson + * @brief Communicates with the tracker + * + * This class uses the HTTP protocol to communicate with the tracker. + */ + class HTTPTracker : public Tracker + { + Q_OBJECT + public: + HTTPTracker(const KURL & url,kt::TorrentInterface* tor,const PeerID & id,int tier); + virtual ~HTTPTracker(); + + virtual void start(); + virtual void stop(WaitJob* wjob = 0); + virtual void completed(); + virtual void manualUpdate(); + virtual Uint32 failureCount() const {return failures;} + virtual void scrape(); + + private slots: + void onAnnounceResult(KIO::Job* j); + void onScrapeResult(KIO::Job* j); + void emitInvalidURLFailure(); + + private: + void doRequest(WaitJob* wjob = 0); + bool updateData(const QByteArray & data); + void setupMetaData(KIO::MetaData & md); + void doAnnounceQueue(); + void doAnnounce(const KURL & u); + + private: + KIO::Job* active_job; + KURL::List announce_queue; + QString event; + Uint32 failures; + }; + +} + +#endif diff --git a/libktorrent/torrent/ipblocklist.cpp b/libktorrent/torrent/ipblocklist.cpp new file mode 100644 index 0000000..de30968 --- /dev/null +++ b/libktorrent/torrent/ipblocklist.cpp @@ -0,0 +1,400 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@gmail.com * + * ivasic@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "ipblocklist.h" +#include <qmap.h> +#include <qstring.h> +#include <qstringlist.h> +#include <util/constants.h> +#include <util/log.h> +#include "globals.h" +#include <interfaces/ipblockinginterface.h> + + +namespace bt +{ + Uint32 toUint32(const QString& ip, bool* ok) + { + bool test; + *ok = true; + + Uint32 ret = ip.section('.',0,0).toULongLong(&test); + if(!test) *ok=false; + ret <<= 8; + ret |= ip.section('.',1,1).toULong(&test); + if(!test) *ok=false; + ret <<= 8; + ret |= ip.section('.',2,2).toULong(&test); + if(!test) *ok=false; + ret <<= 8; + ret |= ip.section('.',3,3).toULong(&test); + if(!test) *ok=false; + + if(*ok) + { + // Out() << "IP: " << ip << " parsed: " << ret << endl; + return ret; + } + else + { + // Out() << "Could not parse IP " << ip << ". IP blocklist might not be working." << endl; + return 0; + } + } + + IPBlocklist::IPBlocklist() + { + this->pluginInterface = 0; + insert("0.0.0.0",3); + addRange("3.*.*.*"); + } + + IPBlocklist::IPBlocklist(const IPBlocklist & ) {} + + void IPBlocklist::insert( QString ip, int state ) + { + bool ok; + Uint32 ipi = toUint32(ip, &ok); + if(!ok) + return; + IPKey key(ipi,0xFFFFFFFF); //-- you can test ranges here. Just specify your mask. + insertRangeIP(key, state); + Out(SYS_IPF|LOG_NOTICE) << "IP " << ip << " banned." << endl; + } + + void IPBlocklist::addRange(QString ip) + { + bool ok; + int tmp = 0; + Uint32 addr = 0; + Uint32 mask = 0xFFFFFFFF; + + tmp = ip.section('.',0,0).toInt(&ok); + if(!ok) + { + if(ip.section('.',0,0) == "*") + mask &= 0x00FFFFFF; + else return; //illegal character + } + else + addr = tmp; + + tmp = ip.section('.',1,1).toInt(&ok); + if(!ok) + { + addr <<= 8; + if(ip.section('.',1,1) == "*") + mask &= 0xFF00FFFF; + else return; //illegal character + } + else + { + addr <<= 8; + addr |= tmp; + } + + tmp = ip.section('.',2,2).toInt(&ok); + if(!ok) + { + addr <<= 8; + if(ip.section('.',2,2) == "*") + mask &= 0xFFFF00FF; + else return; //illegal character + } + else + { + addr <<= 8; + addr |= tmp; + } + + tmp = ip.section('.',3,3).toInt(&ok); + if(!ok) + { + addr <<= 8; + if(ip.section('.',3,3) == "*") + mask &=0xFFFFFF00; + else return; //illegal character + } + else + { + addr <<= 8; + addr |= tmp; + } + + IPKey key(addr, mask); + this->insertRangeIP(key); + } + + void IPBlocklist::insertRangeIP(IPKey& key, int state) + { +// Out() << "Blocked range: " << key.m_ip << " - " << key.m_mask << endl; + QMap<IPKey, int>::iterator it; + if ((it = m_peers.find(key)) != m_peers.end()) + { + + if(it.key().m_mask != key.m_mask) + { + int st = it.data(); + IPKey key1(key.m_ip, it.key().m_mask | key.m_mask); + m_peers.insert(key1, state+st); + return; + } + m_peers[key]+= state; + } + else + m_peers.insert(key,state); + } + + void IPBlocklist::removeRange(QString ip) + { + bool ok; + int tmp = 0; + Uint32 addr = 0; + Uint32 mask = 0xFFFFFFFF; + + tmp = ip.section('.',0,0).toInt(&ok); + if(!ok) + { + if(ip.section('.',0,0) == "*") + mask &= 0x00FFFFFF; + else return; //illegal character + } + else + addr = tmp; + + tmp = ip.section('.',1,1).toInt(&ok); + if(!ok) + { + addr <<= 8; + if(ip.section('.',1,1) == "*") + mask &= 0xFF00FFFF; + else return; //illegal character + } + else + { + addr <<= 8; + addr |= tmp; + } + + tmp = ip.section('.',2,2).toInt(&ok); + if(!ok) + { + addr <<= 8; + if(ip.section('.',2,2) == "*") + mask &= 0xFFFF00FF; + else return; //illegal character + } + else + { + addr <<= 8; + addr |= tmp; + } + + tmp = ip.section('.',3,3).toInt(&ok); + if(!ok) + { + addr <<= 8; + if(ip.section('.',3,3) == "*") + mask &=0xFFFFFF00; + else return; //illegal character + } + else + { + addr <<= 8; + addr |= tmp; + } + + IPKey key(addr, mask); + + QMap<IPKey, int>::iterator it = m_peers.find(key); + if (it == m_peers.end()) + return; + + m_peers.remove(key); + } + + void IPBlocklist::setPluginInterfacePtr( kt::IPBlockingInterface* ptr ) + { + this->pluginInterface = ptr; + } + + bool IPBlocklist::isBlocked(const QString& ip ) + { + //First check local filter list + if(isBlockedLocal(ip)) + { + Out(SYS_IPF|LOG_NOTICE) << "IP " << ip << " is blacklisted. Connection denied." << endl; + return true; + } + + //Then we ask plugin + if(isBlockedPlugin(ip)) + { + Out(SYS_IPF|LOG_NOTICE) << "IP " << ip << " is blacklisted. Connection denied." << endl; + return true; + } + + return false; + } + + bool IPBlocklist::isBlockedLocal(const QString& ip ) + { + bool ok; + Uint32 ipi = toUint32(ip,&ok); + if (!ok) + return false; + IPKey key(ipi); + + QMap<IPKey, int>::iterator it; + it = m_peers.find(key); + if (it==m_peers.end()) + return false; + + return m_peers[key] >= 3; + } + + bool IPBlocklist::isBlockedPlugin(const QString& ip ) + { + if (pluginInterface == 0) //the plugin is not loaded + return false; + else + return pluginInterface->isBlockedIP(ip); + } + + QStringList* IPBlocklist::getBlocklist() + { + QStringList* ret = new QStringList(); + QMap<IPKey,int>::iterator it = m_peers.begin(); + for( ;it!=m_peers.end();++it) + { + IPKey key = it.key(); + *ret << key.toString(); + } + + return ret; + } + + void IPBlocklist::setBlocklist(QStringList* list) + { + m_peers.clear(); + for (QStringList::Iterator it = list->begin(); it != list->end(); ++it ) + addRange(*it); + } + + /*** IPKey *****************************************************************************************************************/ + + IPKey::IPKey() + { + m_ip = 0; + m_mask = 0xFFFFFFFF; + } + + IPKey::IPKey(QString& ip, Uint32 mask) + : m_mask(mask) + { + bool ok; + this->m_ip = toUint32(ip, &ok); + } + + IPKey::IPKey(const IPKey& ip) + { + m_ip = ip.m_ip; + m_mask = ip.m_mask; + } + + IPKey::IPKey(Uint32 ip, Uint32 mask) + : m_ip(ip), m_mask(mask) + {} + + QString IPKey::toString() + { + Uint32 tmp, tmpmask; + Uint32 ip = m_ip; + Uint32 mask = m_mask; + QString out; + + tmp = ip; + tmpmask = mask; + tmp &= 0x000000FF; + tmpmask &= 0x000000FF; + if(tmpmask == 0) + out.prepend("*"); + else + out.prepend(QString("%1").arg(tmp)); + ip >>= 8; + mask >>= 8; + tmp = ip; + tmpmask = mask; + tmp &= 0x000000FF; + tmpmask &= 0x000000FF; + if(tmpmask == 0) + out.prepend("*."); + else + out.prepend(QString("%1.").arg(tmp)); + ip >>= 8; + mask >>= 8; + tmp = ip; + tmpmask = mask; + tmp &= 0x000000FF; + tmpmask &= 0x000000FF; + if(tmpmask == 0) + out.prepend("*."); + else + out.prepend(QString("%1.").arg(tmp)); + ip >>= 8; + mask >>= 8; + tmp = ip; + tmpmask = mask; + tmp &= 0x000000FF; + tmpmask &= 0x000000FF; + if(tmpmask == 0) + out.prepend("*."); + else + out.prepend(QString("%1.").arg(tmp)); + + return out; + } + + bool IPKey::operator ==(const IPKey& ip) const + { + return (m_ip & m_mask) == m_mask & ip.m_ip; + } + + bool IPKey::operator !=(const IPKey& ip) const + { + return (m_ip & m_mask) != m_mask & ip.m_ip; + } + + bool IPKey::operator < (const IPKey& ip) const + { + return (m_ip & m_mask) < (m_mask & ip.m_ip); + } + + IPKey& IPKey::operator =(const IPKey& ip) + { + m_ip = ip.m_ip; + m_mask = ip.m_mask; + return *this; + } + + IPKey::~ IPKey() + {} +} diff --git a/libktorrent/torrent/ipblocklist.h b/libktorrent/torrent/ipblocklist.h new file mode 100644 index 0000000..b30a856 --- /dev/null +++ b/libktorrent/torrent/ipblocklist.h @@ -0,0 +1,175 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@gmail.com * + * ivasic@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ +#ifndef IPBLOCKLIST_H +#define IPBLOCKLIST_H + +#include <interfaces/ipblockinginterface.h> + +#include <qmap.h> +#include <qstringlist.h> +#include <util/constants.h> + +class QString; + +namespace bt +{ + class IPKey + { + public: + IPKey(); + IPKey(QString& ip, Uint32 mask = 0xFFFFFFFF); + IPKey(Uint32 ip, Uint32 mask = 0xFFFFFFFF); + IPKey(const IPKey& ip); + ~IPKey(); + + bool operator== (const IPKey& ip) const; + bool operator!= (const IPKey& ip) const; + bool operator < (const IPKey & ip) const; + IPKey& operator= (const IPKey& ip); + + QString toString(); + + Uint32 m_ip; + Uint32 m_mask; + }; + + /** + * @author Ivan Vasic <ivasic@gmail.com> + * @brief Keeps track of blocked peers + * + * This class is used for keeping the IP addresses list of peers that + * have sent bad chunks. + * + * Peers that have sent >= 3 bad chunks are blocked. + */ + class IPBlocklist + { + IPBlocklist(); + IPBlocklist(const IPBlocklist & ); + const IPBlocklist& operator=(const IPBlocklist&); + + public: + + inline static IPBlocklist & instance() + { + static IPBlocklist singleton; + return singleton; + } + + /** + * @brief Adds ip address to the list. + * It also increases the number of times this IP appeared in the list. + * @param ip QString containing the peer IP address + * @param state int number of bad chunks client from ip sent. Basically this parameter + * is used only to permanently block some IP (by setting this param to 3) + */ + void insert(QString ip, int state=1); + + /** + * @brief Adds IP range to the list + * It is used for blocking plugin. For single IP use insert() instead. + * @param ip QString peer IP address. Uses ''*" for ranges. + **/ + void addRange(QString ip); + + + /** + * @brief Removes IP range from list + * It is used for blocking plugin. + * @param ip QString peer IP address. Uses ''*" for ranges. + **/ + void removeRange(QString ip); + + /** + * Checks if IP is in the blocking list + * @param ip - IP address to check + * @returns true if IP is blocked + */ + bool isBlocked(const QString& ip); + + /** + * @brief Sets the pointer to the IPBlockingInterface (IPBlocking plugin) + * Call this function from IPBlocking plugin when it gets loaded. + * @arg ptr - pointer to be set + */ + void setPluginInterfacePtr(kt::IPBlockingInterface* ptr); + + /** + * @brief Unsets the interface pointer + * Call this when IPBlockingPlugin gets unloaded or deleted + */ + void unsetPluginInterfacePtr() { pluginInterface = 0; } + + + /** + * @brief This function will fill QStringList with all banned peer IP addresses. + * @return QStringList filled with blacklisted peers. + * It will create a new QStringList object so don't forget to delete it after using. + */ + QStringList* getBlocklist(); + + + /** + * @brief This function will load blacklisted peers to IPFilter. + * @param list QStringList containing all banned peers. + * @note This function will remove current peers from blocklist before setting new list!!! + */ + void setBlocklist(QStringList* list); + + private: + + /** + * Pointer to the IPBlocking plugin which implements IPBlockingInterface + * Used to provide a way to use this plugin functions from within this class + */ + kt::IPBlockingInterface* pluginInterface; + + /** + * @param IPKey - Key: Peer IP address and bit mask if it is a range + * @param int - Number of bad chunks sent. + **/ + QMap<IPKey, int> m_peers; + + /** + * @brief Adds IP range to the list. + * @param key IPKey that represents this IP range + * @param state int Number of 'warnings' for the range. + * Default is 3 - that means range is blocked permanently. + */ + void insertRangeIP(IPKey& key, int state=3); + + + /** + * Checks if IP is listed in local database (IPBlocklist::m_peers) + * @return TRUE if IP is to be blocked + */ + bool isBlockedLocal(const QString& ip); + + /** + * Checks if IP is listed in plugins antip2p file + * @return TRUE if IP is to be blocked + */ + bool isBlockedPlugin(const QString& ip); + }; +} + +#endif + diff --git a/libktorrent/torrent/movedatafilesjob.cpp b/libktorrent/torrent/movedatafilesjob.cpp new file mode 100644 index 0000000..c0c24e7 --- /dev/null +++ b/libktorrent/torrent/movedatafilesjob.cpp @@ -0,0 +1,103 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <util/log.h> +#include "movedatafilesjob.h" + +namespace bt +{ + + MoveDataFilesJob::MoveDataFilesJob() : KIO::Job(false),err(false),active_job(0) + {} + + + MoveDataFilesJob::~MoveDataFilesJob() + {} + + void MoveDataFilesJob::addMove(const QString & src,const QString & dst) + { + todo.insert(src,dst); + } + + void MoveDataFilesJob::onJobDone(KIO::Job* j) + { + if (j->error() || err) + { + if (!err) + m_error = KIO::ERR_INTERNAL; + + active_job = 0; + if (j->error()) + j->showErrorDialog(); + + // shit happened cancel all previous moves + err = true; + recover(); + } + else + { + success.insert(active_src,active_dst); + active_src = active_dst = QString::null; + active_job = 0; + startMoving(); + } + } + + void MoveDataFilesJob::onCanceled(KIO::Job* j) + { + m_error = KIO::ERR_USER_CANCELED; + active_job = 0; + err = true; + recover(); + } + + void MoveDataFilesJob::startMoving() + { + if (todo.isEmpty()) + { + m_error = 0; + emitResult(); + return; + } + + QMap<QString,QString>::iterator i = todo.begin(); + active_job = KIO::move(KURL::fromPathOrURL(i.key()),KURL::fromPathOrURL(i.data()),false); + active_src = i.key(); + active_dst = i.data(); + Out(SYS_GEN|LOG_DEBUG) << "Moving " << active_src << " -> " << active_dst << endl; + connect(active_job,SIGNAL(result(KIO::Job*)),this,SLOT(onJobDone(KIO::Job*))); + connect(active_job,SIGNAL(canceled(KIO::Job*)),this,SLOT(onCanceled(KIO::Job*))); + todo.erase(i); + } + + void MoveDataFilesJob::recover() + { + if (success.isEmpty()) + { + emitResult(); + return; + } + QMap<QString,QString>::iterator i = success.begin(); + active_job = KIO::move(KURL::fromPathOrURL(i.data()),KURL::fromPathOrURL(i.key()),false); + connect(active_job,SIGNAL(result(KIO::Job*)),this,SLOT(onJobDone(KIO::Job*))); + connect(active_job,SIGNAL(canceled(KIO::Job*)),this,SLOT(onCanceled(KIO::Job*))); + success.erase(i); + } +} +#include "movedatafilesjob.moc" diff --git a/libktorrent/torrent/movedatafilesjob.h b/libktorrent/torrent/movedatafilesjob.h new file mode 100644 index 0000000..b0002d9 --- /dev/null +++ b/libktorrent/torrent/movedatafilesjob.h @@ -0,0 +1,68 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef BTMOVEDATAFILESJOB_H +#define BTMOVEDATAFILESJOB_H + +#include <kio/job.h> + +namespace bt +{ + + /** + * @author Joris Guisson <joris.guisson@gmail.com> + * KIO::Job to move all the files of a torrent. + */ + class MoveDataFilesJob : public KIO::Job + { + Q_OBJECT + public: + MoveDataFilesJob(); + virtual ~MoveDataFilesJob(); + + /** + * Add a move to the todo list. + * @param src File to move + * @param dst Where to move it to + */ + void addMove(const QString & src,const QString & dst); + + /** + * Start moving the files. + */ + void startMoving(); + + private slots: + void onJobDone(KIO::Job* j); + void onCanceled(KIO::Job* j); + + private: + void recover(); + + private: + bool err; + KIO::Job* active_job; + QString active_src,active_dst; + QMap<QString,QString> todo; + QMap<QString,QString> success; + }; + +} + +#endif diff --git a/libktorrent/torrent/multifilecache.cpp b/libktorrent/torrent/multifilecache.cpp new file mode 100644 index 0000000..c6af92c --- /dev/null +++ b/libktorrent/torrent/multifilecache.cpp @@ -0,0 +1,867 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <errno.h> +#include <qdir.h> +#include <qstringlist.h> +#include <qfileinfo.h> +#include <klocale.h> +#include <kio/netaccess.h> +#include <util/file.h> +#include <util/fileops.h> +#include <util/functions.h> +#include <util/error.h> +#include <util/log.h> +#include "torrent.h" +#include "cache.h" +#include "multifilecache.h" +#include "globals.h" +#include "chunk.h" +#include "cachefile.h" +#include "dndfile.h" +#include "preallocationthread.h" +#include "movedatafilesjob.h" + + + +namespace bt +{ + static Uint64 FileOffset(Chunk* c,const TorrentFile & f,Uint64 chunk_size); + static Uint64 FileOffset(Uint32 cindex,const TorrentFile & f,Uint64 chunk_size); + static void DeleteEmptyDirs(const QString & output_dir,const QString & fpath); + + + MultiFileCache::MultiFileCache(Torrent& tor,const QString & tmpdir,const QString & datadir,bool custom_output_name) : Cache(tor, tmpdir,datadir) + { + cache_dir = tmpdir + "cache" + bt::DirSeparator(); + if (datadir.length() == 0) + this->datadir = guessDataDir(); + if (!custom_output_name) + output_dir = this->datadir + tor.getNameSuggestion() + bt::DirSeparator(); + else + output_dir = this->datadir; + files.setAutoDelete(true); + } + + + MultiFileCache::~MultiFileCache() + {} + + QString MultiFileCache::guessDataDir() + { + for (Uint32 i = 0;i < tor.getNumFiles();i++) + { + TorrentFile & tf = tor.getFile(i); + if (tf.doNotDownload()) + continue; + + QString p = cache_dir + tf.getPath(); + QFileInfo fi(p); + if (!fi.isSymLink()) + continue; + + QString dst = fi.readLink(); + QString tmp = tor.getNameSuggestion() + bt::DirSeparator() + tf.getPath(); + dst = dst.left(dst.length() - tmp.length()); + if (dst.length() == 0) + continue; + + if (!dst.endsWith(bt::DirSeparator())) + dst += bt::DirSeparator(); + Out() << "Guessed outputdir to be " << dst << endl; + return dst; + } + + return QString::null; + } + + QString MultiFileCache::getOutputPath() const + { + return output_dir; + } + + void MultiFileCache::close() + { + files.clear(); + } + + void MultiFileCache::open() + { + QString dnd_dir = tmpdir + "dnd" + bt::DirSeparator(); + // open all files + for (Uint32 i = 0;i < tor.getNumFiles();i++) + { + TorrentFile & tf = tor.getFile(i); + CacheFile* fd = 0; + DNDFile* dfd = 0; + try + { + if (!tf.doNotDownload()) + { + if (files.contains(i)) + files.erase(i); + + fd = new CacheFile(); + fd->open(cache_dir + tf.getPath(),tf.getSize()); + files.insert(i,fd); + } + else + { + if (dnd_files.contains(i)) + dnd_files.erase(i); + + dfd = new DNDFile(dnd_dir + tf.getPath() + ".dnd"); + dfd->checkIntegrity(); + dnd_files.insert(i,dfd); + } + } + catch (...) + { + delete fd; + fd = 0; + delete dfd; + dfd = 0; + throw; + } + } + } + + void MultiFileCache::changeTmpDir(const QString& ndir) + { + Cache::changeTmpDir(ndir); + cache_dir = tmpdir + "cache/"; + QString dnd_dir = tmpdir + "dnd" + bt::DirSeparator(); + + // change paths for individual files, it should not + // be a problem to move these files when they are open + for (Uint32 i = 0;i < tor.getNumFiles();i++) + { + TorrentFile & tf = tor.getFile(i); + if (tf.doNotDownload()) + { + DNDFile* dfd = dnd_files.find(i); + if (dfd) + dfd->changePath(dnd_dir + tf.getPath() + ".dnd"); + } + else + { + CacheFile* fd = files.find(i); + if (fd) + fd->changePath(cache_dir + tf.getPath()); + } + } + } + + void MultiFileCache::changeOutputPath(const QString & outputpath) + { + output_dir = outputpath; + if (!output_dir.endsWith(bt::DirSeparator())) + output_dir += bt::DirSeparator(); + + datadir = output_dir; + + if (!bt::Exists(cache_dir)) + MakeDir(cache_dir); + + for (Uint32 i = 0;i < tor.getNumFiles();i++) + { + TorrentFile & tf = tor.getFile(i); + if (!tf.doNotDownload()) + { + QString fpath = tf.getPath(); + if (bt::Exists(output_dir + fpath)) + { + bt::Delete(cache_dir + fpath,true); // delete any existing symlinks + // create new one + bt::SymLink(output_dir + fpath,cache_dir + fpath,true); + } + } + } + } + + KIO::Job* MultiFileCache::moveDataFiles(const QString & ndir) + { + if (!bt::Exists(ndir)) + bt::MakeDir(ndir); + + QString nd = ndir; + if (!nd.endsWith(bt::DirSeparator())) + nd += bt::DirSeparator(); + + try + { + MoveDataFilesJob* mvd = new MoveDataFilesJob(); + for (Uint32 i = 0;i < tor.getNumFiles();i++) + { + TorrentFile & tf = tor.getFile(i); + if (tf.doNotDownload()) + continue; + + // check if every directory along the path exists, and if it doesn't + // create it + QStringList sl = QStringList::split(bt::DirSeparator(),nd + tf.getPath()); + QString odir = bt::DirSeparator(); + for (Uint32 i = 0;i < sl.count() - 1;i++) + { + odir += sl[i] + bt::DirSeparator(); + if (!bt::Exists(odir)) + { + bt::MakeDir(odir); + } + } + + mvd->addMove(output_dir + tf.getPath(),nd + tf.getPath()); + } + + mvd->startMoving(); + return mvd; + } + catch (bt::Error & err) + { + throw; // rethrow error + } + return 0; + } + + void MultiFileCache::moveDataFilesCompleted(KIO::Job* job) + { + if (!job->error()) + { + for (Uint32 i = 0;i < tor.getNumFiles();i++) + { + TorrentFile & tf = tor.getFile(i); + // check for empty directories and delete them + DeleteEmptyDirs(output_dir,tf.getPath()); + } + } + } + + void MultiFileCache::create() + { + if (!bt::Exists(cache_dir)) + MakeDir(cache_dir); + if (!bt::Exists(output_dir)) + MakeDir(output_dir); + if (!bt::Exists(tmpdir + "dnd")) + bt::MakeDir(tmpdir + "dnd"); + + // update symlinks + for (Uint32 i = 0;i < tor.getNumFiles();i++) + { + TorrentFile & tf = tor.getFile(i); + touch(tf); + } + } + + void MultiFileCache::touch(TorrentFile & tf) + { + QString fpath = tf.getPath(); + bool dnd = tf.doNotDownload(); + // first split fpath by / separator + QStringList sl = QStringList::split(bt::DirSeparator(),fpath); + // create all necessary subdirs + QString ctmp = cache_dir; + QString otmp = output_dir; + QString dtmp = tmpdir + "dnd" + bt::DirSeparator(); + for (Uint32 i = 0;i < sl.count() - 1;i++) + { + otmp += sl[i]; + ctmp += sl[i]; + dtmp += sl[i]; + // we need to make the same directory structure in the cache, + // the output_dir and the dnd directory + if (!bt::Exists(ctmp)) + MakeDir(ctmp); + if (!bt::Exists(otmp)) + MakeDir(otmp); + if (!bt::Exists(dtmp)) + MakeDir(dtmp); + otmp += bt::DirSeparator(); + ctmp += bt::DirSeparator(); + dtmp += bt::DirSeparator(); + } + + + bt::Delete(cache_dir + fpath,true); // delete any existing symlinks + + // then make the file + QString tmp = dnd ? tmpdir + "dnd" + bt::DirSeparator() : output_dir; + if (dnd) + { + // only symlink, when we open the files a default dnd file will be made if the file is corrupt or doesn't exist + bt::SymLink(tmp + fpath + ".dnd",cache_dir + fpath); + } + else + { + if (!bt::Exists(tmp + fpath)) + { + bt::Touch(tmp + fpath); + } + else + { + preexisting_files = true; + tf.setPreExisting(true); // mark the file as preexisting + } + + bt::SymLink(tmp + fpath,cache_dir + fpath); + } + } + + void MultiFileCache::load(Chunk* c) + { + QValueList<Uint32> tflist; + tor.calcChunkPos(c->getIndex(),tflist); + + // one file is simple, just mmap it + if (tflist.count() == 1) + { + const TorrentFile & f = tor.getFile(tflist.first()); + CacheFile* fd = files.find(tflist.first()); + if (!fd) + return; + + if (Cache::mappedModeAllowed() && mmap_failures < 3) + { + Uint64 off = FileOffset(c,f,tor.getChunkSize()); + Uint8* buf = (Uint8*)fd->map(c,off,c->getSize(),CacheFile::READ); + if (buf) + { + c->setData(buf,Chunk::MMAPPED); + // only return when the mapping is OK + // if mmap fails we will just load it buffered + return; + } + else + mmap_failures++; + } + } + + Uint8* data = new Uint8[c->getSize()]; + Uint64 read = 0; // number of bytes read + for (Uint32 i = 0;i < tflist.count();i++) + { + const TorrentFile & f = tor.getFile(tflist[i]); + CacheFile* fd = files.find(tflist[i]); + DNDFile* dfd = dnd_files.find(tflist[i]); + + // first calculate offset into file + // only the first file can have an offset + // the following files will start at the beginning + Uint64 off = 0; + if (i == 0) + off = FileOffset(c,f,tor.getChunkSize()); + + Uint32 to_read = 0; + // then the amount of data we can read from this file + if (tflist.count() == 1) + to_read = c->getSize(); + else if (i == 0) + to_read = f.getLastChunkSize(); + else if (i == tflist.count() - 1) + to_read = c->getSize() - read; + else + to_read = f.getSize(); + + + // read part of data + if (fd) + fd->read(data + read,to_read,off); + else if (dfd) + { + Uint32 ret = 0; + if (i == 0) + ret = dfd->readLastChunk(data,read,c->getSize()); + else if (i == tflist.count() - 1) + ret = dfd->readFirstChunk(data,read,c->getSize()); + else + ret = dfd->readFirstChunk(data,read,c->getSize()); + + if (ret > 0 && ret != to_read) + Out() << "Warning : MultiFileCache::load ret != to_read" << endl; + } + read += to_read; + } + c->setData(data,Chunk::BUFFERED); + } + + + bool MultiFileCache::prep(Chunk* c) + { + // find out in which files a chunk lies + QValueList<Uint32> tflist; + tor.calcChunkPos(c->getIndex(),tflist); + +// Out() << "Prep " << c->getIndex() << endl; + if (tflist.count() == 1) + { + // in one so just mmap it + Uint64 off = FileOffset(c,tor.getFile(tflist.first()),tor.getChunkSize()); + CacheFile* fd = files.find(tflist.first()); + Uint8* buf = 0; + if (fd && Cache::mappedModeAllowed() && mmap_failures < 3) + { + buf = (Uint8*)fd->map(c,off,c->getSize(),CacheFile::RW); + if (!buf) + mmap_failures++; + } + + if (!buf) + { + // if mmap fails or is not possible use buffered mode + c->allocate(); + c->setStatus(Chunk::BUFFERED); + } + else + { + c->setData(buf,Chunk::MMAPPED); + } + } + else + { + // just allocate it + c->allocate(); + c->setStatus(Chunk::BUFFERED); + } + return true; + } + + void MultiFileCache::save(Chunk* c) + { + QValueList<Uint32> tflist; + tor.calcChunkPos(c->getIndex(),tflist); + + if (c->getStatus() == Chunk::MMAPPED) + { + // mapped chunks are easy + CacheFile* fd = files.find(tflist[0]); + if (!fd) + return; + + fd->unmap(c->getData(),c->getSize()); + c->clear(); + c->setStatus(Chunk::ON_DISK); + return; + } + + // Out() << "Writing to " << tflist.count() << " files " << endl; + Uint64 written = 0; // number of bytes written + for (Uint32 i = 0;i < tflist.count();i++) + { + const TorrentFile & f = tor.getFile(tflist[i]); + CacheFile* fd = files.find(tflist[i]); + DNDFile* dfd = dnd_files.find(tflist[i]); + + // first calculate offset into file + // only the first file can have an offset + // the following files will start at the beginning + Uint64 off = 0; + Uint32 to_write = 0; + if (i == 0) + { + off = FileOffset(c,f,tor.getChunkSize()); + } + + // the amount of data we can write to this file + if (tflist.count() == 1) + to_write = c->getSize(); + else if (i == 0) + to_write = f.getLastChunkSize(); + else if (i == tflist.count() - 1) + to_write = c->getSize() - written; + else + to_write = f.getSize(); + + // Out() << "to_write " << to_write << endl; + // write the data + if (fd) + fd->write(c->getData() + written,to_write,off); + else if (dfd) + { + if (i == 0) + dfd->writeLastChunk(c->getData() + written,to_write); + else if (i == tflist.count() - 1) + dfd->writeFirstChunk(c->getData() + written,to_write); + else + dfd->writeFirstChunk(c->getData() + written,to_write); + } + + written += to_write; + } + + // set the chunk to on disk and clear it + c->clear(); + c->setStatus(Chunk::ON_DISK); + } + + void MultiFileCache::downloadStatusChanged(TorrentFile* tf, bool download) + { + bool dnd = !download; + QString dnd_dir = tmpdir + "dnd" + bt::DirSeparator(); + // if it is dnd and it is already in the dnd tree do nothing + if (dnd && bt::Exists(dnd_dir + tf->getPath() + ".dnd")) + return; + + // if it is !dnd and it is already in the output_dir tree do nothing + if (!dnd && bt::Exists(output_dir + tf->getPath())) + return; + + + DNDFile* dfd = 0; + CacheFile* fd = 0; + try + { + + if (dnd && bt::Exists(dnd_dir + tf->getPath())) + { + // old download, we need to convert it + // save first and last chunk of the file + saveFirstAndLastChunk(tf,dnd_dir + tf->getPath(),dnd_dir + tf->getPath() + ".dnd"); + // delete symlink + bt::Delete(cache_dir + tf->getPath()); + bt::Delete(dnd_dir + tf->getPath()); // delete old dnd file + // recreate it + bt::SymLink(dnd_dir + tf->getPath() + ".dnd",cache_dir + tf->getPath()); + + files.erase(tf->getIndex()); + dfd = new DNDFile(dnd_dir + tf->getPath() + ".dnd"); + dfd->checkIntegrity(); + dnd_files.insert(tf->getIndex(),dfd); + } + else if (dnd) + { + // save first and last chunk of the file + if (bt::Exists(output_dir + tf->getPath())) + saveFirstAndLastChunk(tf,output_dir + tf->getPath(),dnd_dir + tf->getPath() + ".dnd"); + + // delete symlink + bt::Delete(cache_dir + tf->getPath()); + // delete data file + bt::Delete(output_dir + tf->getPath(),true); + // recreate it + bt::SymLink(dnd_dir + tf->getPath() + ".dnd",cache_dir + tf->getPath()); + + files.erase(tf->getIndex()); + dfd = new DNDFile(dnd_dir + tf->getPath() + ".dnd"); + dfd->checkIntegrity(); + dnd_files.insert(tf->getIndex(),dfd); + } + else + { + // recreate the file + recreateFile(tf,dnd_dir + tf->getPath() + ".dnd",output_dir + tf->getPath()); + // delete symlink and dnd file + bt::Delete(cache_dir + tf->getPath()); + bt::Delete(dnd_dir + tf->getPath() + ".dnd"); + // recreate it + bt::SymLink(output_dir + tf->getPath(),cache_dir + tf->getPath()); + dnd_files.erase(tf->getIndex()); + + fd = new CacheFile(); + fd->open(output_dir + tf->getPath(),tf->getSize()); + files.insert(tf->getIndex(),fd); + } + } + catch (bt::Error & err) + { + delete fd; + delete dfd; + Out() << err.toString() << endl; + } + } + + + + void MultiFileCache::saveFirstAndLastChunk(TorrentFile* tf,const QString & src_file,const QString & dst_file) + { + DNDFile out(dst_file); + File fptr; + if (!fptr.open(src_file,"rb")) + throw Error(i18n("Cannot open file %1 : %2").arg(src_file).arg(fptr.errorString())); + + Uint32 cs = 0; + if (tf->getFirstChunk() == tor.getNumChunks() - 1) + { + cs = tor.getFileLength() % tor.getChunkSize(); + if (cs == 0) + cs = tor.getChunkSize(); + } + else + cs = tor.getChunkSize(); + + Uint8* tmp = new Uint8[tor.getChunkSize()]; + try + { + fptr.read(tmp,cs - tf->getFirstChunkOffset()); + out.writeFirstChunk(tmp,cs - tf->getFirstChunkOffset()); + + if (tf->getFirstChunk() != tf->getLastChunk()) + { + Uint64 off = FileOffset(tf->getLastChunk(),*tf,tor.getChunkSize()); + fptr.seek(File::BEGIN,off); + fptr.read(tmp,tf->getLastChunkSize()); + out.writeLastChunk(tmp,tf->getLastChunkSize()); + } + delete [] tmp; + } + catch (...) + { + delete [] tmp; + throw; + } + } + + void MultiFileCache::recreateFile(TorrentFile* tf,const QString & dnd_file,const QString & output_file) + { + DNDFile dnd(dnd_file); + + // create the output file + bt::Touch(output_file); + // truncate it + try + { + bool res = false; + + #ifdef HAVE_XFS_XFS_H + if( (! res) && (Settings::fullDiskPreallocMethod() == 1) ) + { + res = XfsPreallocate(output_file, tf->getSize()); + } + #endif + + if(! res) + { + bt::TruncateFile(output_file,tf->getSize()); + } + } + catch (bt::Error & e) + { + // first attempt failed, must be fat so try that + if (!FatPreallocate(output_file,tf->getSize())) + { + throw Error(i18n("Cannot preallocate diskspace : %1").arg(strerror(errno))); + } + } + + Uint32 cs = 0; + if (tf->getFirstChunk() == tor.getNumChunks() - 1) + { + cs = tor.getFileLength() % tor.getChunkSize(); + if (cs == 0) + cs = tor.getChunkSize(); + } + else + cs = tor.getChunkSize(); + + File fptr; + if (!fptr.open(output_file,"r+b")) + throw Error(i18n("Cannot open file %1 : %2").arg(output_file).arg(fptr.errorString())); + + + Uint32 ts = cs - tf->getFirstChunkOffset() > tf->getLastChunkSize() ? + cs - tf->getFirstChunkOffset() : tf->getLastChunkSize(); + Uint8* tmp = new Uint8[ts]; + + try + { + dnd.readFirstChunk(tmp,0,cs - tf->getFirstChunkOffset()); + fptr.write(tmp,cs - tf->getFirstChunkOffset()); + + if (tf->getFirstChunk() != tf->getLastChunk()) + { + Uint64 off = FileOffset(tf->getLastChunk(),*tf,tor.getChunkSize()); + fptr.seek(File::BEGIN,off); + dnd.readLastChunk(tmp,0,tf->getLastChunkSize()); + fptr.write(tmp,tf->getLastChunkSize()); + } + delete [] tmp; + } + catch (...) + { + delete [] tmp; + throw; + } + } + + void MultiFileCache::preallocateDiskSpace(PreallocationThread* prealloc) + { + Out() << "MultiFileCache::preallocateDiskSpace" << endl; + PtrMap<Uint32,CacheFile>::iterator i = files.begin(); + while (i != files.end()) + { + CacheFile* cf = i->second; + if (!prealloc->isStopped()) + { + cf->preallocate(prealloc); + } + else + { + // we got interrupted tell the thread we are not finished and return + prealloc->setNotFinished(); + return; + } + i++; + } + } + + bool MultiFileCache::hasMissingFiles(QStringList & sl) + { + bool ret = false; + for (Uint32 i = 0;i < tor.getNumFiles();i++) + { + TorrentFile & tf = tor.getFile(i); + if (tf.doNotDownload()) + continue; + + QString p = cache_dir + tf.getPath(); + QFileInfo fi(p); + // always use symlink first, file might have been moved + if (!fi.exists()) + { + ret = true; + p = fi.readLink(); + if (p.isNull()) + p = output_dir + tf.getPath(); + sl.append(p); + tf.setMissing(true); + } + else + { + p = output_dir + tf.getPath(); + // no symlink so try the actual file + if (!bt::Exists(p)) + { + ret = true; + sl.append(p); + tf.setMissing(true); + } + } + } + return ret; + } + + static void DeleteEmptyDirs(const QString & output_dir,const QString & fpath) + { + QStringList sl = QStringList::split(bt::DirSeparator(),fpath); + // remove the last, which is just the filename + sl.pop_back(); + + while (sl.count() > 0) + { + QString path = output_dir; + // reassemble the full directory path + for (QStringList::iterator itr = sl.begin(); itr != sl.end();itr++) + path += *itr + bt::DirSeparator(); + + QDir dir(path); + QStringList el = dir.entryList(QDir::All|QDir::System|QDir::Hidden); + el.remove("."); + el.remove(".."); + if (el.count() == 0) + { + // no childern so delete the directory + Out(SYS_GEN|LOG_IMPORTANT) << "Deleting empty directory : " << path << endl; + bt::Delete(path,true); + sl.pop_back(); // remove the last so we can go one higher + } + else + { + + // children, so we cannot delete any more directories higher up + return; + } + } + + // now the output_dir itself + QDir dir(output_dir); + QStringList el = dir.entryList(QDir::All|QDir::System|QDir::Hidden); + el.remove("."); + el.remove(".."); + if (el.count() == 0) + { + Out(SYS_GEN|LOG_IMPORTANT) << "Deleting empty directory : " << output_dir << endl; + bt::Delete(output_dir,true); + } + } + + void MultiFileCache::deleteDataFiles() + { + for (Uint32 i = 0;i < tor.getNumFiles();i++) + { + TorrentFile & tf = tor.getFile(i); + QString fpath = tf.getPath(); + if (!tf.doNotDownload()) + { + // first delete the file + bt::Delete(output_dir + fpath); + } + + // check for subdirectories + DeleteEmptyDirs(output_dir,fpath); + } + } + + Uint64 MultiFileCache::diskUsage() + { + Uint64 sum = 0; + + for (Uint32 i = 0;i < tor.getNumFiles();i++) + { + TorrentFile & tf = tor.getFile(i); + if (tf.doNotDownload()) + continue; + + try + { + CacheFile* cf = files.find(i); + if (cf) + { + sum += cf->diskUsage(); + } + else + { + // doesn't exist yet, must be before open is called + // so create one and delete it right after + cf = new CacheFile(); + cf->open(cache_dir + tf.getPath(),tf.getSize()); + sum += cf->diskUsage(); + delete cf; + } + } + catch (bt::Error & err) // make sure we catch any exceptions + { + Out(SYS_DIO|LOG_DEBUG) << "Error: " << err.toString() << endl; + } + } + + return sum; + } + + /////////////////////////////// + + Uint64 FileOffset(Chunk* c,const TorrentFile & f,Uint64 chunk_size) + { + return FileOffset(c->getIndex(),f,chunk_size); + } + + Uint64 FileOffset(Uint32 cindex,const TorrentFile & f,Uint64 chunk_size) + { + return f.fileOffset(cindex,chunk_size); + } + +} diff --git a/libktorrent/torrent/multifilecache.h b/libktorrent/torrent/multifilecache.h new file mode 100644 index 0000000..9c1280e --- /dev/null +++ b/libktorrent/torrent/multifilecache.h @@ -0,0 +1,74 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef BTMULTIFILECACHE_H +#define BTMULTIFILECACHE_H + + +#include <util/ptrmap.h> +#include "cache.h" +#include "settings.h" + +namespace bt +{ + class DNDFile; + class CacheFile; + + /** + * @author Joris Guisson + * @brief Cache for multi file torrents + * + * This class manages a multi file torrent cache. Everything gets stored in the + * correct files immediately. + */ + class MultiFileCache : public Cache + { + QString cache_dir,output_dir; + PtrMap<Uint32,CacheFile> files; + PtrMap<Uint32,DNDFile> dnd_files; + public: + MultiFileCache(Torrent& tor,const QString & tmpdir,const QString & datadir,bool custom_output_name); + virtual ~MultiFileCache(); + + virtual void changeTmpDir(const QString& ndir); + virtual void create(); + virtual void load(Chunk* c); + virtual void save(Chunk* c); + virtual bool prep(Chunk* c); + virtual void close(); + virtual void open(); + virtual QString getOutputPath() const; + virtual void changeOutputPath(const QString & outputpath); + virtual KIO::Job* moveDataFiles(const QString & ndir); + virtual void moveDataFilesCompleted(KIO::Job* job); + virtual void preallocateDiskSpace(PreallocationThread* prealloc); + virtual bool hasMissingFiles(QStringList & sl); + virtual void deleteDataFiles(); + virtual Uint64 diskUsage(); + private: + void touch(TorrentFile & tf); + virtual void downloadStatusChanged(TorrentFile*, bool); + QString guessDataDir(); + void saveFirstAndLastChunk(TorrentFile* tf,const QString & src_file,const QString & dst_file); + void recreateFile(TorrentFile* tf,const QString & dnd_file,const QString & output_file); + }; + +} + +#endif diff --git a/libktorrent/torrent/newchokealgorithm.cpp b/libktorrent/torrent/newchokealgorithm.cpp new file mode 100644 index 0000000..875f356 --- /dev/null +++ b/libktorrent/torrent/newchokealgorithm.cpp @@ -0,0 +1,345 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#if 0 +#include <util/log.h> +#include <util/timer.h> +#include <util/functions.h> +#include <torrent/globals.h> +#include <interfaces/functions.h> +#include "newchokealgorithm.h" +#include "peermanager.h" +#include "peer.h" +#include "packetwriter.h" +#include "peeruploader.h" + + +using namespace kt; + +namespace bt +{ + + + + NewChokeAlgorithm::NewChokeAlgorithm(): ChokeAlgorithm() + { + round_state = 1; + } + + + NewChokeAlgorithm::~NewChokeAlgorithm() + {} + + int RevDownloadRateCmp(Peer* a,Peer* b) + { + if (b->getDownloadRate() > a->getDownloadRate()) + return 1; + else if (a->getDownloadRate() > b->getDownloadRate()) + return -1; + else + return 0; + } + + void NewChokeAlgorithm::doChokingLeechingState(PeerManager & pman,ChunkManager & cman,const kt::TorrentStats & stats) + { + Uint32 num_peers = pman.getNumConnectedPeers(); + if (num_peers == 0) + return; + + Uint32 now = GetCurrentTime(); + Peer* poup = pman.findPeer(opt_unchoked_peer_id); + Peer* unchokers[] = {0,0,0,0}; + + // first find the planned optimistic unchoked peer if we are in the correct round + if (round_state == 1 || poup == 0) + { + opt_unchoked_peer_id = findPlannedOptimisticUnchokedPeer(pman); + poup = pman.findPeer(opt_unchoked_peer_id); + } + + PeerPtrList peers,other; + // now get all the peers who are interested and have sent us a piece in the + // last 30 seconds + for (Uint32 i = 0;i < num_peers;i++) + { + Peer* p = pman.getPeer(i); + if (!p) + continue; + + if (!p->isSeeder()) + { + if (p->isInterested() && now - p->getTimeSinceLastPiece() <= 30000) + peers.append(p); + else + other.append(p); + } + else + { + p->choke(); + } + } + + // sort them using a reverse download rate compare + // so that the fastest downloaders are in front + peers.setCompareFunc(RevDownloadRateCmp); + peers.sort(); + other.setCompareFunc(RevDownloadRateCmp); + other.sort(); + + // get the first tree and punt them in the unchokers + for (Uint32 i = 0;i < 3;i++) + { + if (i < peers.count()) + { + unchokers[i] = peers.at(i); + } + } + + // see if poup if part of the first 3 + // and if necessary replace it + bool poup_in_unchokers = false; + Uint32 attempts = 0; + do + { + poup_in_unchokers = false; + for (Uint32 i = 0;i < 3;i++) + { + if (unchokers[i] != poup) + continue; + + opt_unchoked_peer_id = findPlannedOptimisticUnchokedPeer(pman); + poup = pman.findPeer(opt_unchoked_peer_id); + poup_in_unchokers = true; + break; + } + // we don't want to keep trying this forever, so limit it to 5 atttempts + attempts++; + }while (poup_in_unchokers && attempts < 5); + + unchokers[3] = poup; + + Uint32 other_idx = 0; + Uint32 peers_idx = 3; + // unchoke the 4 unchokers + for (Uint32 i = 0;i < 4;i++) + { + if (!unchokers[i]) + { + // pick some other peer to unchoke + unchokers[i] = peers.at(peers_idx++); + if (unchokers[i] == poup) // it must not be equal to the poup + unchokers[i] = peers.at(peers_idx++); + + // nobody in the peers list, try the others list + if (!unchokers[i]) + unchokers[i] = other.at(other_idx++); + } + + if (unchokers[i]) + unchokers[i]->getPacketWriter().sendUnchoke(); + } + + // choke the rest + for (Uint32 i = 0;i < num_peers;i++) + { + Peer* p = pman.getPeer(i); + if (p == unchokers[0] || p == unchokers[1] || p == unchokers[2] || p == unchokers[3]) + continue; + if (p) + p->choke(); + } + + round_state++; + if (round_state > 3) + round_state = 1; + } + + Uint32 NewChokeAlgorithm::findPlannedOptimisticUnchokedPeer(PeerManager& pman) + { + Uint32 num_peers = pman.getNumConnectedPeers(); + if (num_peers == 0) + return UNDEFINED_ID; + + // find a random peer that is choked and interested + Uint32 start = rand() % num_peers; + Uint32 i = (start + 1) % num_peers; + while (i != start) + { + Peer* p = pman.getPeer(i); + if (p && p->isChoked() && p->isInterested() && !p->isSeeder()) + return p->getID(); + i = (i + 1) % num_peers; + } + + // we do not expect to have 4 billion peers + return 0xFFFFFFFF; + } + + ////////////////////////////////////////////// + + int NChokeCmp(Peer* a,Peer* b) + { + Uint32 now = GetCurrentTime(); + // if they have pending upload requests or they were unchoked in the last 20 seconds, + // they are category 1 + bool a_first_class = a->getPeerUploader()->getNumRequests() > 0 || + (now - a->getUnchokeTime() <= 20000); + bool b_first_class = b->getPeerUploader()->getNumRequests() > 0 || + (now - b->getUnchokeTime() <= 20000); + + if (a_first_class && !b_first_class) + { + // category 1 come first + return -1; + } + else if (!a_first_class && b_first_class) + { + // category 1 come first + return 1; + } + else + { + // use upload rate to differentiate peers of the same class + if (a->getUploadRate() > b->getUploadRate()) + return -1; + else if (b->getUploadRate() > a->getUploadRate()) + return 1; + else + return 0; + } + } + + + void NewChokeAlgorithm::doChokingSeedingState(PeerManager & pman,ChunkManager & cman,const kt::TorrentStats & stats) + { + Uint32 num_peers = pman.getNumConnectedPeers(); + if (num_peers == 0) + return; + + // first get all unchoked and interested peers + PeerPtrList peers,others; + for (Uint32 i = 0;i < num_peers;i++) + { + Peer* p = pman.getPeer(i); + if (!p) + continue; + + if (!p->isSeeder()) + { + if (!p->isChoked() && p->isInterested()) + peers.append(p); + else + others.append(p); + } + else + { + p->choke(); + } + } + + // sort them + peers.setCompareFunc(NChokeCmp); + peers.sort(); + others.setCompareFunc(NChokeCmp); + others.sort(); + + // first round so take the 4 first peers + if (round_state == 1) + { + Uint32 num_unchoked = 0; + for (Uint32 i = 0;i < peers.count();i++) + { + Peer* p = peers.at(i); + if (!p) + continue; + + if (num_unchoked < 4) + { + p->getPacketWriter().sendUnchoke(); + num_unchoked++; + } + else + p->choke(); + } + // go over the other peers and unchoke, if we do not have enough + for (Uint32 i = 0;i < others.count();i++) + { + Peer* p = others.at(i); + if (!p) + continue; + + if (num_unchoked < 4) + { + p->getPacketWriter().sendUnchoke(); + num_unchoked++; + } + else + p->choke(); + } + } + else + { + Uint32 rnd = 0; + if (peers.count() > 3) + rnd = 3 + rand() % (peers.count() - 3); + + Uint32 num_unchoked = 0; + // take the first 3 and a random one + for (Uint32 i = 0;i < peers.count();i++) + { + Peer* p = peers.at(i); + if (!p) + continue; + + if (num_unchoked < 4 || i == rnd) + { + p->getPacketWriter().sendUnchoke(); + num_unchoked++; + } + else + p->choke(); + } + + // go over the other peers and unchoke, if we do not have enough + for (Uint32 i = 0;i < others.count();i++) + { + Peer* p = others.at(i); + if (!p) + continue; + + if (num_unchoked < 4 || i == rnd) + { + p->getPacketWriter().sendUnchoke(); + num_unchoked++; + } + else + p->choke(); + } + } + + round_state++; + if (round_state > 3) + round_state = 1; + } + + + +} +#endif + diff --git a/libktorrent/torrent/newchokealgorithm.h b/libktorrent/torrent/newchokealgorithm.h new file mode 100644 index 0000000..9a0738a --- /dev/null +++ b/libktorrent/torrent/newchokealgorithm.h @@ -0,0 +1,54 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#if 0 +#ifndef BTNEWCHOKEALGORITHM_H +#define BTNEWCHOKEALGORITHM_H + +#include <choker.h> + +namespace bt +{ + + /** + * @author Joris Guisson + * + * The new choking algorithm. + */ + class NewChokeAlgorithm : public ChokeAlgorithm + { + Uint32 round_state; + public: + NewChokeAlgorithm(); + virtual ~NewChokeAlgorithm(); + + virtual void doChokingLeechingState(PeerManager & pman,ChunkManager & cman,const kt::TorrentStats & stats); + virtual void doChokingSeedingState(PeerManager & pman,ChunkManager & cman,const kt::TorrentStats & stats); + private: + void doChokingLeecherState(PeerManager& pman); + void doChokingSeederState(PeerManager& pman); + + Uint32 findPlannedOptimisticUnchokedPeer(PeerManager& pman); + }; + +} + +#endif +#endif + diff --git a/libktorrent/torrent/oldchokealgorithm.cpp b/libktorrent/torrent/oldchokealgorithm.cpp new file mode 100644 index 0000000..e24d63a --- /dev/null +++ b/libktorrent/torrent/oldchokealgorithm.cpp @@ -0,0 +1,223 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <interfaces/functions.h> +#include "oldchokealgorithm.h" +#include "peer.h" +#include "packetwriter.h" +#include "peermanager.h" + + +using namespace kt; + +namespace bt +{ + int UploadRateCmp(Peer* pa,Peer* pb) + { + return CompareVal(pa->getUploadRate(),pb->getUploadRate()); + } + + int DownloadRateCmp(Peer* pa,Peer* pb) + { + return CompareVal(pa->getDownloadRate(),pb->getDownloadRate()); + } + + + OldChokeAlgorithm::OldChokeAlgorithm(): ChokeAlgorithm() + { + opt_unchoke_index = 0; + opt_unchoke = 1; + } + + + OldChokeAlgorithm::~OldChokeAlgorithm() + {} + + + void OldChokeAlgorithm::doChoking(PeerManager& pman, bool have_all) + { + if (pman.getNumConnectedPeers() == 0) + return; + + downloaders.clear(); + interested.clear(); + not_interested.clear(); + + // first alert everybody that we're interested or not + sendInterested(pman,have_all); + // get who is interested and not + updateInterested(pman); + // them sort them; + if (have_all) + { + interested.setCompareFunc(DownloadRateCmp); + interested.sort(); + not_interested.setCompareFunc(DownloadRateCmp); + not_interested.sort(); + } + else + { + interested.setCompareFunc(UploadRateCmp); + interested.sort(); + not_interested.setCompareFunc(UploadRateCmp); + not_interested.sort(); + } + // determine the downloaders + updateDownloaders(); + // unchoke the not_interested peers + // which have a faster upload rate then the downloaders + sendUnchokes(have_all); + // optimisticly unchoke somebody + optimisticUnchoke(pman); + } + + void OldChokeAlgorithm::updateInterested(PeerManager& pman) + { + for (Uint32 i = 0;i < pman.getNumConnectedPeers();i++) + { + Peer* p = pman.getPeer(i); + + if (p->getID() == opt_unchoked_peer_id) + continue; + + if (p->isInterested()) + { + interested.append(p); + } + else + { + not_interested.append(p); + } + } + } + + void OldChokeAlgorithm::updateDownloaders() + { + QPtrList<Peer>::iterator itr = interested.begin(); + int num = 0; + // send all downloaders an unchoke + for (;itr != interested.end();itr++) + { + Peer* p = *itr; + + if (p->getID() == opt_unchoked_peer_id) + continue; + + if (num < 4) + { + p->choke(); + downloaders.append(p); + num++; + } + else + { + p->choke(); + } + } + } + + void OldChokeAlgorithm::sendInterested(PeerManager& pman,bool have_all) + { + for (Uint32 i = 0;i < pman.getNumConnectedPeers();i++) + { + Peer* p = pman.getPeer(i); + PacketWriter & pout = p->getPacketWriter(); + // if we don't have the entire file, send an intereseted message, + // else we're not intereseted + if (have_all && p->areWeInterested()) + pout.sendNotInterested(); + else if (!have_all && !p->areWeInterested()) + pout.sendInterested(); + } + } + + void OldChokeAlgorithm::sendUnchokes(bool have_all) + { + if (downloaders.count() == 0) + return; + + QPtrList<Peer>::iterator itr = not_interested.begin(); + // fd = fastest_downloader + Peer* fd = downloaders.first(); + // send all downloaders an unchoke + for (;itr != not_interested.end();itr++) + { + Peer* p = *itr; + if (p->getID() == opt_unchoked_peer_id) + continue; + + if ((have_all && p->getDownloadRate() > fd->getDownloadRate()) || + (!have_all && p->getUploadRate() > fd->getUploadRate())) + { + p->getPacketWriter().sendUnchoke(); + } + else + { + p->getPacketWriter().sendChoke(); + } + } + } + + void OldChokeAlgorithm::optimisticUnchoke(PeerManager& pman) + { + if (pman.getNumConnectedPeers() == 0) + return; + + // only switch optimistic unchoked peer every 30 seconds + // (update interval of choker is 10 seconds) + if (opt_unchoke != 3) + { + opt_unchoke++; + return; + } + + // Get current time + QTime now = QTime::currentTime(); + QPtrList<Peer> peers; // list to store peers to select from + + // recently connected peers == peers connected in the last 5 minutes + const int RECENTLY_CONNECT_THRESH = 5*60; + + for (Uint32 i = 0;i < pman.getNumConnectedPeers();i++) + { + Peer* p = pman.getPeer(i); + if (p->getConnectTime().secsTo(now) < RECENTLY_CONNECT_THRESH) + { + // we favor recently connected peers 3 times over other peers + // so we add them 3 times to the list + peers.append(p); + peers.append(p); + peers.append(p); + } + else + { + // not recent, so just add one time + peers.append(p); + } + } + + // draw a random one from the list and send it an unchoke + opt_unchoke_index = rand() % peers.count(); + Peer* lucky_one = peers.at(opt_unchoke_index); + lucky_one->getPacketWriter().sendUnchoke(); + opt_unchoked_peer_id = lucky_one->getID(); + opt_unchoke = 1; + } + +} diff --git a/libktorrent/torrent/oldchokealgorithm.h b/libktorrent/torrent/oldchokealgorithm.h new file mode 100644 index 0000000..bc813a8 --- /dev/null +++ b/libktorrent/torrent/oldchokealgorithm.h @@ -0,0 +1,54 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef BTOLDCHOKEALGORITHM_H +#define BTOLDCHOKEALGORITHM_H + +#include <choker.h> + +namespace bt +{ + + /** + * @author Joris Guisson + * + * The old choking algorithm as it is described on wiki.theory.org. + */ + class OldChokeAlgorithm : public ChokeAlgorithm + { + int opt_unchoke_index; + int opt_unchoke; + + PeerPtrList downloaders,interested,not_interested; + public: + OldChokeAlgorithm(); + virtual ~OldChokeAlgorithm(); + + virtual void doChoking(PeerManager& pman, bool have_all); + private: + void updateInterested(PeerManager& pman); + void updateDownloaders(); + void sendInterested(PeerManager& pman,bool have_all); + void sendUnchokes(bool have_all); + void optimisticUnchoke(PeerManager& pman); + }; + +} + +#endif diff --git a/libktorrent/torrent/packet.cpp b/libktorrent/torrent/packet.cpp new file mode 100644 index 0000000..febede2 --- /dev/null +++ b/libktorrent/torrent/packet.cpp @@ -0,0 +1,175 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <qstring.h> +#include <string.h> +#include <util/log.h> +#include <util/bitset.h> +#include <util/functions.h> +#include <torrent/globals.h> +#include "packet.h" +#include "request.h" +#include "chunk.h" +#include "peer.h" + +namespace bt +{ + + static Uint8* AllocPacket(Uint32 size,Uint8 type) + { + Uint8* data = new Uint8[size]; + WriteUint32(data,0,size - 4); + data[4] = type; + return data; + } + + + Packet::Packet(Uint8 type) : data(0),size(0),written(0) + { + size = 5; + data = AllocPacket(size,type); + } + + Packet::Packet(Uint16 port) : data(0),size(0),written(0) + { + size = 7; + data = AllocPacket(size,PORT); + WriteUint16(data,5,port); + + } + + Packet::Packet(Uint32 chunk,Uint8 type) : data(0),size(0),written(0) + { + size = 9; + data = AllocPacket(size,type); + WriteUint32(data,5,chunk); + } + + Packet::Packet(const BitSet & bs) : data(0),size(0),written(0) + { + size = 5 + bs.getNumBytes(); + data = AllocPacket(size,BITFIELD); + memcpy(data+5,bs.getData(),bs.getNumBytes()); + } + + Packet::Packet(const Request & r,Uint8 type) : data(0),size(0),written(0) + { + size = 17; + data = AllocPacket(size,type); + WriteUint32(data,5,r.getIndex()); + WriteUint32(data,9,r.getOffset()); + WriteUint32(data,13,r.getLength()); + } + + Packet::Packet(Uint32 index,Uint32 begin,Uint32 len,Chunk* ch) : data(0),size(0),written(0) + { + size = 13 + len; + data = AllocPacket(size,PIECE); + WriteUint32(data,5,index); + WriteUint32(data,9,begin); + memcpy(data+13,ch->getData() + begin,len); + } + + Packet::Packet(Uint8 ext_id,const QByteArray & ext_data) : data(0),size(0),written(0) + { + size = 6 + ext_data.size(); + data = AllocPacket(size,EXTENDED); + data[5] = ext_id; + memcpy(data + 6,ext_data.data(),ext_data.size()); + } + + Packet::~Packet() + { + delete [] data; + } + + bool Packet::isPiece(const Request & req) const + { + if (data[4] == PIECE) + { + if (ReadUint32(data,5) != req.getIndex()) + return false; + + if (ReadUint32(data,9) != req.getOffset()) + return false; + + if (ReadUint32(data,13) != req.getLength()) + return false; + + return true; + } + return false; + } + + Packet* Packet::makeRejectOfPiece() + { + if (getType() != PIECE) + return 0; + + Uint32 idx = bt::ReadUint32(data,5); + Uint32 off = bt::ReadUint32(data,9); + Uint32 len = size - 13; + + // Out(SYS_CON|LOG_DEBUG) << "Packet::makeRejectOfPiece " << idx << " " << off << " " << len << endl; + return new Packet(Request(idx,off,len,0),bt::REJECT_REQUEST); + } + + /* + QString Packet::debugString() const + { + if (!data) + return QString::null; + + switch (data[4]) + { + case CHOKE : return QString("CHOKE %1 %2").arg(hdr_length).arg(data_length); + case UNCHOKE : return QString("UNCHOKE %1 %2").arg(hdr_length).arg(data_length); + case INTERESTED : return QString("INTERESTED %1 %2").arg(hdr_length).arg(data_length); + case NOT_INTERESTED : return QString("NOT_INTERESTED %1 %2").arg(hdr_length).arg(data_length); + case HAVE : return QString("HAVE %1 %2").arg(hdr_length).arg(data_length); + case BITFIELD : return QString("BITFIELD %1 %2").arg(hdr_length).arg(data_length); + case PIECE : return QString("PIECE %1 %2").arg(hdr_length).arg(data_length); + case REQUEST : return QString("REQUEST %1 %2").arg(hdr_length).arg(data_length); + case CANCEL : return QString("CANCEL %1 %2").arg(hdr_length).arg(data_length); + default: return QString("UNKNOWN %1 %2").arg(hdr_length).arg(data_length); + } + } + */ + bool Packet::isOK() const + { + if (!data) + return false; + + return true; + } + + Uint32 Packet::putInOutputBuffer(Uint8* buf,Uint32 max_to_put,bool & piece) + { + piece = data[4] == PIECE; + Uint32 bw = size - written; + if (!bw) // nothing to write + return 0; + + if (bw > max_to_put) + bw = max_to_put; + memcpy(buf,data + written,bw); + written += bw; + return bw; + } +} diff --git a/libktorrent/torrent/packet.h b/libktorrent/torrent/packet.h new file mode 100644 index 0000000..9259d31 --- /dev/null +++ b/libktorrent/torrent/packet.h @@ -0,0 +1,91 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef BTPACKET_H +#define BTPACKET_H + +#include "globals.h" + +class QString; + +namespace bt +{ + class BitSet; + class Request; + class Chunk; + class Peer; + + /** + * @author Joris Guisson + * + * Packet off data, which gets sent to a Peer + */ + class Packet + { + Uint8* data; + Uint32 size; + Uint32 written; + public: + Packet(Uint8 type); + Packet(Uint16 port); + Packet(Uint32 chunk,Uint8 type); + Packet(const BitSet & bs); + Packet(const Request & req,Uint8 type); + Packet(Uint32 index,Uint32 begin,Uint32 len,Chunk* ch); + Packet(Uint8 ext_id,const QByteArray & ext_data); // extension protocol packet + virtual ~Packet(); + + Uint8 getType() const {return data ? data[4] : 0;} + + bool isOK() const; + + const Uint8* getData() const {return data;} + Uint32 getDataLength() const {return size;} + + Uint32 isSent() const {return written == size;} + + /** + * If this packet is a piece, make a reject for it. + * @return The newly created Packet, 0 if this is not a piece + */ + Packet* makeRejectOfPiece(); + + /// Are we sending this packet ? + bool sending() const {return written > 0;} + + /** + * Is this a piece packet which matches a request + * @param req The request + * @return If this is a piece in response of this request + */ + bool isPiece(const Request & req) const; + + /** + * Put the packet in an output buffer. + * @param buf The buffer + * @param max_to_put Maximum bytes to put + * @param piece Set to true if this is a piece + * @return The number of bytes put in the buffer + */ + Uint32 putInOutputBuffer(Uint8* buf,Uint32 max_to_put,bool & piece); + }; + +} + +#endif diff --git a/libktorrent/torrent/packetreader.cpp b/libktorrent/torrent/packetreader.cpp new file mode 100644 index 0000000..8df348b --- /dev/null +++ b/libktorrent/torrent/packetreader.cpp @@ -0,0 +1,247 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ + +//#define LOG_PACKET +#ifdef LOG_PACKET +#include <sys/types.h> +#include <unistd.h> +#endif + +#include <util/log.h> +#include <util/file.h> +#include <util/functions.h> +#include "packetreader.h" +#include "peer.h" + + +namespace bt +{ +#ifdef LOG_PACKET + static void LogPacket(const Uint8* data,Uint32 size,Uint32 len) + { + QString file = QString("/tmp/kt-packetreader-%1.log").arg(getpid()); + File fptr; + if (!fptr.open(file,"a")) + return; + + + QString tmp = QString("PACKET len = %1, type = %2\nDATA: \n").arg(len).arg(data[0]); + + fptr.write(tmp.ascii(),tmp.length()); + + Uint32 j = 0; + if (size <= 40) + { + for (Uint32 i = 0;i < size;i++) + { + tmp = QString("0x%1 ").arg(data[i],0,16); + fptr.write(tmp.ascii(),tmp.length()); + j++; + if (j > 10) + { + fptr.write("\n",1); + j = 0; + } + } + } + else + { + for (Uint32 i = 0;i < 20;i++) + { + tmp = QString("0x%1 ").arg(data[i],0,16); + fptr.write(tmp.ascii(),tmp.length()); + j++; + if (j > 10) + { + fptr.write("\n",1); + j = 0; + } + } + tmp = QString("\n ... \n"); + fptr.write(tmp.ascii(),tmp.length()); + for (Uint32 i = size - 20;i < size;i++) + { + tmp = QString("0x%1 ").arg(data[i],0,16); + fptr.write(tmp.ascii(),tmp.length()); + j++; + if (j > 10) + { + fptr.write("\n",1); + j = 0; + } + } + } + fptr.write("\n",1); + } +#endif + + IncomingPacket::IncomingPacket(Uint32 size) : data(0),size(size),read(0) + { + data = new Uint8[size]; + } + + IncomingPacket::~IncomingPacket() + { + delete [] data; + } + + PacketReader::PacketReader(Peer* peer) + : peer(peer),error(false) + { + packet_queue.setAutoDelete(true); + len_received = -1; + } + + + PacketReader::~PacketReader() + { + } + + + void PacketReader::update() + { + if (error) + return; + + mutex.lock(); + // pass packets to peer + while (packet_queue.count() > 0) + { + IncomingPacket* pck = packet_queue.first(); + if (pck->read == pck->size) + { + // full packet is read pass it to peer + peer->packetReady(pck->data,pck->size); + packet_queue.removeFirst(); + } + else + { + // packet is not yet full, break out of loop + break; + } + } + mutex.unlock(); + } + + Uint32 PacketReader::newPacket(Uint8* buf,Uint32 size) + { + Uint32 packet_length = 0; + Uint32 am_of_len_read = 0; + if (len_received > 0) + { + if (size < 4 - len_received) + { + memcpy(len + len_received,buf,size); + len_received += size; + return size; + } + else + { + memcpy(len + len_received,buf,4 - len_received); + am_of_len_read = 4 - len_received; + len_received = 0; + packet_length = ReadUint32(len,0); + + } + } + else if (size < 4) + { + memcpy(len,buf,size); + len_received = size; + return size; + } + else + { + packet_length = ReadUint32(buf,0); + am_of_len_read = 4; + } + + if (packet_length == 0) + return am_of_len_read; + + if (packet_length > MAX_PIECE_LEN + 13) + { + Out(SYS_CON|LOG_DEBUG) << " packet_length too large " << packet_length << endl; + + error = true; + return size; + } + + IncomingPacket* pck = new IncomingPacket(packet_length); + packet_queue.append(pck); + return am_of_len_read + readPacket(buf + am_of_len_read,size - am_of_len_read); + } + + Uint32 PacketReader::readPacket(Uint8* buf,Uint32 size) + { + if (!size) + return 0; + + IncomingPacket* pck = packet_queue.last(); + if (pck->read + size >= pck->size) + { + // we can read the full packet + Uint32 tr = pck->size - pck->read; + memcpy(pck->data + pck->read,buf,tr); + pck->read += tr; + return tr; + } + else + { + // we can do a partial read + Uint32 tr = size; + memcpy(pck->data + pck->read,buf,tr); + pck->read += tr; + return tr; + } + } + + + void PacketReader::onDataReady(Uint8* buf,Uint32 size) + { + if (error) + return; + + mutex.lock(); + if (packet_queue.count() == 0) + { + Uint32 ret = 0; + while (ret < size && !error) + { + ret += newPacket(buf + ret,size - ret); + } + } + else + { + Uint32 ret = 0; + IncomingPacket* pck = packet_queue.last(); + if (pck->read == pck->size) // last packet in queue is fully read + ret = newPacket(buf,size); + else + ret = readPacket(buf,size); + + while (ret < size && !error) + { + ret += newPacket(buf + ret,size - ret); + } + } + mutex.unlock(); + } +} diff --git a/libktorrent/torrent/packetreader.h b/libktorrent/torrent/packetreader.h new file mode 100644 index 0000000..da1e03e --- /dev/null +++ b/libktorrent/torrent/packetreader.h @@ -0,0 +1,68 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef BTPACKETREADER_H +#define BTPACKETREADER_H + +#include <qmutex.h> +#include <qptrlist.h> +#include <net/bufferedsocket.h> +#include "globals.h" + +namespace bt +{ + class Peer; + + struct IncomingPacket + { + Uint8* data; + Uint32 size; + Uint32 read; + + IncomingPacket(Uint32 size); + virtual ~IncomingPacket(); + }; + + /** + @author Joris Guisson + */ + class PacketReader : public net::SocketReader + { + Peer* peer; + bool error; + QPtrList<IncomingPacket> packet_queue; + QMutex mutex; + Uint8 len[4]; + int len_received; + public: + PacketReader(Peer* peer); + virtual ~PacketReader(); + + void update(); + bool ok() const {return !error;} + private: + Uint32 newPacket(Uint8* buf,Uint32 size); + Uint32 readPacket(Uint8* buf,Uint32 size); + virtual void onDataReady(Uint8* buf,Uint32 size); + + }; + +} + +#endif diff --git a/libktorrent/torrent/packetwriter.cpp b/libktorrent/torrent/packetwriter.cpp new file mode 100644 index 0000000..888d23d --- /dev/null +++ b/libktorrent/torrent/packetwriter.cpp @@ -0,0 +1,399 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +//#define LOG_PACKET + +#include <util/log.h> +#include <util/file.h> +#include <util/functions.h> +#include <net/socketmonitor.h> +#include <ktversion.h> +#include "packetwriter.h" +#include "peer.h" +#include "request.h" +#include "chunk.h" +#include <util/bitset.h> +#include "packet.h" +#include "uploadcap.h" +#include <util/log.h> +#include "globals.h" +#include "bencoder.h" + + + +namespace bt +{ + + + PacketWriter::PacketWriter(Peer* peer) : peer(peer),mutex(true) // this is a recursive mutex + { + uploaded = 0; + uploaded_non_data = 0; + curr_packet = 0; + ctrl_packets_sent = 0; + } + + + PacketWriter::~PacketWriter() + { + std::list<Packet*>::iterator i = data_packets.begin(); + while (i != data_packets.end()) + { + Packet* p = *i; + delete p; + i++; + } + + i = control_packets.begin(); + while (i != control_packets.end()) + { + Packet* p = *i; + delete p; + i++; + } + } + + void PacketWriter::queuePacket(Packet* p) + { + QMutexLocker locker(&mutex); + if (p->getType() == PIECE) + data_packets.push_back(p); + else + control_packets.push_back(p); + // tell upload thread we have data ready should it be sleeping + net::SocketMonitor::instance().signalPacketReady(); + } + + + + void PacketWriter::sendChoke() + { + if (peer->am_choked == true) + return; + + queuePacket(new Packet(CHOKE)); + peer->am_choked = true; + peer->stats.has_upload_slot = false; + } + + void PacketWriter::sendUnchoke() + { + if (peer->am_choked == false) + return; + + queuePacket(new Packet(UNCHOKE)); + peer->am_choked = false; + peer->stats.has_upload_slot = true; + } + + void PacketWriter::sendEvilUnchoke() + { + queuePacket(new Packet(UNCHOKE)); + peer->am_choked = true; + peer->stats.has_upload_slot = false; + } + + void PacketWriter::sendInterested() + { + if (peer->am_interested == true) + return; + + queuePacket(new Packet(INTERESTED)); + peer->am_interested = true; + } + + void PacketWriter::sendNotInterested() + { + if (peer->am_interested == false) + return; + + queuePacket(new Packet(NOT_INTERESTED)); + peer->am_interested = false; + } + + void PacketWriter::sendRequest(const Request & r) + { + queuePacket(new Packet(r,bt::REQUEST)); + } + + void PacketWriter::sendCancel(const Request & r) + { + queuePacket(new Packet(r,bt::CANCEL)); + } + + void PacketWriter::sendReject(const Request & r) + { + queuePacket(new Packet(r,bt::REJECT_REQUEST)); + } + + void PacketWriter::sendHave(Uint32 index) + { + queuePacket(new Packet(index,bt::HAVE)); + } + + void PacketWriter::sendPort(Uint16 port) + { + queuePacket(new Packet(port)); + } + + void PacketWriter::sendBitSet(const BitSet & bs) + { + queuePacket(new Packet(bs)); + } + + void PacketWriter::sendHaveAll() + { + queuePacket(new Packet(bt::HAVE_ALL)); + } + + void PacketWriter::sendHaveNone() + { + queuePacket(new Packet(bt::HAVE_NONE)); + } + + void PacketWriter::sendSuggestPiece(Uint32 index) + { + queuePacket(new Packet(index,bt::SUGGEST_PIECE)); + } + + void PacketWriter::sendAllowedFast(Uint32 index) + { + queuePacket(new Packet(index,bt::ALLOWED_FAST)); + } + + bool PacketWriter::sendChunk(Uint32 index,Uint32 begin,Uint32 len,Chunk * ch) + { +// Out() << "sendChunk " << index << " " << begin << " " << len << endl; + if (begin >= ch->getSize() || begin + len > ch->getSize()) + { + Out(SYS_CON|LOG_NOTICE) << "Warning : Illegal piece request" << endl; + Out(SYS_CON|LOG_NOTICE) << "\tChunk : index " << index << " size = " << ch->getSize() << endl; + Out(SYS_CON|LOG_NOTICE) << "\tPiece : begin = " << begin << " len = " << len << endl; + return false; + } + else if (!ch || ch->getData() == 0) + { + Out(SYS_CON|LOG_NOTICE) << "Warning : attempted to upload an invalid chunk" << endl; + return false; + } + else + { + /* Out(SYS_CON|LOG_DEBUG) << QString("Uploading %1 %2 %3 %4 %5") + .arg(index).arg(begin).arg(len).arg((Q_ULLONG)ch,0,16).arg((Q_ULLONG)ch->getData(),0,16) + << endl;; + */ + queuePacket(new Packet(index,begin,len,ch)); + return true; + } + } + + void PacketWriter::sendExtProtHandshake(Uint16 port,bool pex_on) + { + QByteArray arr; + BEncoder enc(new BEncoderBufferOutput(arr)); + enc.beginDict(); + enc.write("m"); + // supported messages + enc.beginDict(); + enc.write("ut_pex");enc.write((Uint32)(pex_on ? 1 : 0)); + enc.end(); + if (port > 0) + { + enc.write("p"); + enc.write((Uint32)port); + } + enc.write("v"); enc.write(QString("KTorrent %1").arg(kt::VERSION_STRING)); + enc.end(); + sendExtProtMsg(0,arr); + } + + void PacketWriter::sendExtProtMsg(Uint8 id,const QByteArray & data) + { + queuePacket(new Packet(id,data)); + } + + Packet* PacketWriter::selectPacket() + { + Packet* ret = 0; + // this function should ensure that between + // each data packet at least 3 control packets are sent + // so requests can get through + + if (ctrl_packets_sent < 3) + { + // try to send another control packet + if (control_packets.size() > 0) + ret = control_packets.front(); + else if (data_packets.size() > 0) + ret = data_packets.front(); + } + else + { + if (data_packets.size() > 0) + { + ctrl_packets_sent = 0; + ret = data_packets.front(); + } + else if (control_packets.size() > 0) + ret = control_packets.front(); + } + + return ret; + } + + Uint32 PacketWriter::onReadyToWrite(Uint8* data,Uint32 max_to_write) + { + QMutexLocker locker(&mutex); + + if (!curr_packet) + curr_packet = selectPacket(); + + Uint32 written = 0; + while (curr_packet && written < max_to_write) + { + Packet* p = curr_packet; + bool count_as_data = false; + Uint32 ret = p->putInOutputBuffer(data + written,max_to_write - written,count_as_data); + written += ret; + if (count_as_data) + uploaded += ret; + else + uploaded_non_data += ret; + + if (p->isSent()) + { + // packet sent, so remove it + if (p->getType() == PIECE) + { + // remove data packet + data_packets.pop_front(); + delete p; + // reset ctrl_packets_sent so the next packet should be a ctrl packet + ctrl_packets_sent = 0; + curr_packet = selectPacket(); + } + else + { + // remove control packet and select another one to send + control_packets.pop_front(); + delete p; + ctrl_packets_sent++; + curr_packet = selectPacket(); + } + } + else + { + // we can't send it fully, so break out of loop + break; + } + } + + return written; + } + + bool PacketWriter::hasBytesToWrite() const + { + return getNumPacketsToWrite() > 0; + } + + Uint32 PacketWriter::getUploadedDataBytes() const + { + QMutexLocker locker(&mutex); + Uint32 ret = uploaded; + uploaded = 0; + return ret; + } + + Uint32 PacketWriter::getUploadedNonDataBytes() const + { + QMutexLocker locker(&mutex); + Uint32 ret = uploaded_non_data; + uploaded_non_data = 0; + return ret; + } + + Uint32 PacketWriter::getNumPacketsToWrite() const + { + QMutexLocker locker(&mutex); + return data_packets.size() + control_packets.size(); + } + + Uint32 PacketWriter::getNumDataPacketsToWrite() const + { + QMutexLocker locker(&mutex); + return data_packets.size(); + } + + void PacketWriter::doNotSendPiece(const Request & req,bool reject) + { + QMutexLocker locker(&mutex); + std::list<Packet*>::iterator i = data_packets.begin(); + while (i != data_packets.end()) + { + Packet* p = *i; + if (p->isPiece(req) && !p->sending()) + { + // remove current item + if (curr_packet == p) + curr_packet = 0; + + i = data_packets.erase(i); + if (reject) + { + // queue a reject packet + sendReject(req); + } + delete p; + } + else + { + i++; + } + } + } + + void PacketWriter::clearPieces(bool reject) + { + QMutexLocker locker(&mutex); + + std::list<Packet*>::iterator i = data_packets.begin(); + while (i != data_packets.end()) + { + Packet* p = *i; + if (p->getType() == bt::PIECE && !p->sending()) + { + // remove current item + if (curr_packet == p) + curr_packet = 0; + + if (reject) + { + queuePacket(p->makeRejectOfPiece()); + } + + i = data_packets.erase(i); + delete p; + } + else + { + i++; + } + } + } +} diff --git a/libktorrent/torrent/packetwriter.h b/libktorrent/torrent/packetwriter.h new file mode 100644 index 0000000..9b77731 --- /dev/null +++ b/libktorrent/torrent/packetwriter.h @@ -0,0 +1,185 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef BTPACKETWRITER_H +#define BTPACKETWRITER_H + +#include <list> +#include <qmutex.h> +#include <net/bufferedsocket.h> +#include "globals.h" + +namespace bt +{ + class Peer; + class Request; + class Chunk; + class BitSet; + class Packet; + + /** + @author Joris Guisson + */ + class PacketWriter : public net::SocketWriter + { + Peer* peer; + std::list<Packet*> control_packets; + std::list<Packet*> data_packets; + Packet* curr_packet; + Uint32 ctrl_packets_sent; + mutable Uint32 uploaded; + mutable Uint32 uploaded_non_data; + mutable QMutex mutex; + public: + PacketWriter(Peer* peer); + virtual ~PacketWriter(); + + /** + * Send a choke packet. + */ + void sendChoke(); + + /** + * Send an unchoke packet. + */ + void sendUnchoke(); + + /** + * Sends an unchoke message but doesn't update the am_choked field so KT still thinks + * it is choked (and will not upload to it), this is to punish snubbers. + */ + void sendEvilUnchoke(); + + /** + * Send an interested packet. + */ + void sendInterested(); + + /** + * Send a not interested packet. + */ + void sendNotInterested(); + + /** + * Send a request for data. + * @param req The Request + */ + void sendRequest(const Request & r); + + /** + * Cancel a request. + * @param req The Request + */ + void sendCancel(const Request & r); + + + /** + * Send a reject for a request + * @param req The Request + */ + void sendReject(const Request & r); + + /** + * Send a have packet. + * @param index + */ + void sendHave(Uint32 index); + + /** + * Send an allowed fast packet + * @param index + */ + void sendAllowedFast(Uint32 index); + + /** + * Send a chunk of data. + * @param index Index of chunk + * @param begin Offset into chunk + * @param len Length of data + * @param ch The Chunk + * @return true If we satisfy the request, false otherwise + */ + bool sendChunk(Uint32 index,Uint32 begin,Uint32 len,Chunk * ch); + + /** + * Send a BitSet. The BitSet indicates which chunks we have. + * @param bs The BitSet + */ + void sendBitSet(const BitSet & bs); + + /** + * Send a port message + * @param port The port + */ + void sendPort(Uint16 port); + + /// Send a have all message + void sendHaveAll(); + + /// Send a have none message + void sendHaveNone(); + + /** + * Send a suggest piece packet + * @param index Index of the chunk + */ + void sendSuggestPiece(Uint32 index); + + /// Send the extension protocol handshake + void sendExtProtHandshake(Uint16 port,bool pex_on = true); + + /// Send an extended protocol message + void sendExtProtMsg(Uint8 id,const QByteArray & data); + + /// Get the number of packets which need to be written + Uint32 getNumPacketsToWrite() const; + + /// Get the number of data packets to write + Uint32 getNumDataPacketsToWrite() const; + + /// Get the number of data bytes uploaded + Uint32 getUploadedDataBytes() const; + + /// Get the number of bytes uploaded + Uint32 getUploadedNonDataBytes() const; + + /** + * Do not send a piece which matches this request. + * But only if we are not allready sending the piece. + * @param req The request + * @param reject Wether we can send a reject instead + */ + void doNotSendPiece(const Request & req,bool reject); + + /** + * Clear all pieces we are not in the progress of sending. + * @param reject Send a reject packet + */ + void clearPieces(bool reject); + + private: + void queuePacket(Packet* p); + Packet* selectPacket(); + virtual Uint32 onReadyToWrite(Uint8* data,Uint32 max_to_write); + virtual bool hasBytesToWrite() const; + }; + +} + +#endif diff --git a/libktorrent/torrent/peer.cpp b/libktorrent/torrent/peer.cpp new file mode 100644 index 0000000..7a5727b --- /dev/null +++ b/libktorrent/torrent/peer.cpp @@ -0,0 +1,593 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <math.h> +#include <util/log.h> +#include <util/functions.h> +#include <net/address.h> +#include <mse/streamsocket.h> + +#include "peer.h" +#include "chunk.h" +#include "piece.h" +#include "request.h" +#include "packetreader.h" +#include "packetwriter.h" +#include "peerdownloader.h" +#include "peeruploader.h" +#include "bdecoder.h" +#include "bnode.h" +#include "utpex.h" +#include "server.h" + +using namespace net; + +namespace bt +{ + + + + static Uint32 peer_id_counter = 1; + + + Peer::Peer(mse::StreamSocket* sock,const PeerID & peer_id, + Uint32 num_chunks,Uint32 chunk_size,Uint32 support,bool local) + : sock(sock),pieces(num_chunks),peer_id(peer_id) + { + id = peer_id_counter; + peer_id_counter++; + + ut_pex = 0; + preader = new PacketReader(this); + choked = am_choked = true; + interested = am_interested = false; + killed = false; + downloader = new PeerDownloader(this,chunk_size); + uploader = new PeerUploader(this); + + + pwriter = new PacketWriter(this); + time_choked = GetCurrentTime(); + time_unchoked = 0; + + connect_time = QTime::currentTime(); + //sock->attachPeer(this); + stats.client = peer_id.identifyClient(); + stats.ip_address = getIPAddresss(); + stats.choked = true; + stats.download_rate = 0; + stats.upload_rate = 0; + stats.perc_of_file = 0; + stats.snubbed = false; + stats.dht_support = support & DHT_SUPPORT; + stats.fast_extensions = support & FAST_EXT_SUPPORT; + stats.extension_protocol = support & EXT_PROT_SUPPORT; + stats.bytes_downloaded = stats.bytes_uploaded = 0; + stats.aca_score = 0.0; + stats.evil = false; + stats.has_upload_slot = false; + stats.num_up_requests = stats.num_down_requests = 0; + stats.encrypted = sock->encrypted(); + stats.local = local; + if (stats.ip_address == "0.0.0.0") + { + Out(SYS_CON|LOG_DEBUG) << "No more 0.0.0.0" << endl; + kill(); + } + else + { + sock->startMonitoring(preader,pwriter); + } + pex_allowed = stats.extension_protocol; + utorrent_pex_id = 0; + } + + + Peer::~Peer() + { + delete ut_pex; + delete uploader; + delete downloader; + delete sock; + delete pwriter; + delete preader; + } + + void Peer::closeConnection() + { + sock->close(); + } + + + void Peer::kill() + { + sock->close(); + killed = true; + } + + + + + void Peer::packetReady(const Uint8* packet,Uint32 len) + { + if (killed) return; + + if (len == 0) + return; + const Uint8* tmp_buf = packet; + //Out() << "Got packet : " << len << " type = " << type << endl; + Uint8 type = tmp_buf[0]; + switch (type) + { + case CHOKE: + if (len != 1) + { + Out() << "len err CHOKE" << endl; + kill(); + return; + } + + if (!choked) + { + time_choked = GetCurrentTime(); + } + choked = true; + downloader->choked(); + break; + case UNCHOKE: + if (len != 1) + { + Out() << "len err UNCHOKE" << endl; + kill(); + return; + } + + if (choked) + time_unchoked = GetCurrentTime(); + choked = false; + break; + case INTERESTED: + if (len != 1) + { + Out() << "len err INTERESTED" << endl; + kill(); + return; + } + if (!interested) + { + interested = true; + rerunChoker(); + } + break; + case NOT_INTERESTED: + if (len != 1) + { + Out() << "len err NOT_INTERESTED" << endl; + kill(); + return; + } + if (interested) + { + interested = false; + rerunChoker(); + } + break; + case HAVE: + if (len != 5) + { + Out() << "len err HAVE" << endl; + kill(); + } + else + { + Uint32 ch = ReadUint32(tmp_buf,1); + if (ch < pieces.getNumBits()) + { + haveChunk(this,ch); + pieces.set(ch,true); + } + else + { + Out(SYS_CON|LOG_NOTICE) << "Received invalid have value, kicking peer" << endl; + kill(); + } + } + break; + case BITFIELD: + if (len != 1 + pieces.getNumBytes()) + { + Out() << "len err BITFIELD" << endl; + kill(); + return; + } + + pieces = BitSet(tmp_buf+1,pieces.getNumBits()); + bitSetRecieved(pieces); + break; + case REQUEST: + if (len != 13) + { + Out() << "len err REQUEST" << endl; + kill(); + return; + } + + { + Request r( + ReadUint32(tmp_buf,1), + ReadUint32(tmp_buf,5), + ReadUint32(tmp_buf,9), + id); + + if (!am_choked) + uploader->addRequest(r); + else if (stats.fast_extensions) + pwriter->sendReject(r); + // Out() << "REQUEST " << r.getIndex() << " " << r.getOffset() << endl; + } + break; + case PIECE: + if (len < 9) + { + Out() << "len err PIECE" << endl; + kill(); + return; + } + + snub_timer.update(); + + { + stats.bytes_downloaded += (len - 9); + // turn on evil bit + if (stats.evil) + stats.evil = false; + Piece p(ReadUint32(tmp_buf,1), + ReadUint32(tmp_buf,5), + len - 9,id,tmp_buf+9); + piece(p); + } + break; + case CANCEL: + if (len != 13) + { + Out() << "len err CANCEL" << endl; + kill(); + return; + } + + { + Request r(ReadUint32(tmp_buf,1), + ReadUint32(tmp_buf,5), + ReadUint32(tmp_buf,9), + id); + uploader->removeRequest(r); + } + break; + case REJECT_REQUEST: + if (len != 13) + { + Out() << "len err REJECT_REQUEST" << endl; + kill(); + return; + } + + { + Request r(ReadUint32(tmp_buf,1), + ReadUint32(tmp_buf,5), + ReadUint32(tmp_buf,9), + id); + downloader->onRejected(r); + } + break; + case PORT: + if (len != 3) + { + Out() << "len err PORT" << endl; + kill(); + return; + } + + { + Uint16 port = ReadUint16(tmp_buf,1); + // Out() << "Got PORT packet : " << port << endl; + gotPortPacket(getIPAddresss(),port); + } + break; + case HAVE_ALL: + if (len != 1) + { + Out() << "len err HAVE_ALL" << endl; + kill(); + return; + } + pieces.setAll(true); + bitSetRecieved(pieces); + break; + case HAVE_NONE: + if (len != 1) + { + Out() << "len err HAVE_NONE" << endl; + kill(); + return; + } + pieces.setAll(false); + bitSetRecieved(pieces); + break; + case SUGGEST_PIECE: + // ignore suggestions for the moment + break; + case ALLOWED_FAST: + // we no longer support this, so do nothing + break; + case EXTENDED: + handleExtendedPacket(packet,len); + break; + } + } + + void Peer::handleExtendedPacket(const Uint8* packet,Uint32 size) + { + if (size <= 2 || packet[1] > 1) + return; + + if (packet[1] == 1) + { + if (ut_pex) + ut_pex->handlePexPacket(packet,size); + return; + } + + QByteArray tmp; + tmp.setRawData((const char*)packet,size); + BNode* node = 0; + try + { + BDecoder dec(tmp,false,2); + node = dec.decode(); + if (node && node->getType() == BNode::DICT) + { + BDictNode* dict = (BDictNode*)node; + + // handshake packet, so just check if the peer supports ut_pex + dict = dict->getDict("m"); + BValueNode* val = 0; + if (dict && (val = dict->getValue("ut_pex"))) + { + utorrent_pex_id = val->data().toInt(); + if (ut_pex) + { + if (utorrent_pex_id > 0) + ut_pex->changeID(utorrent_pex_id); + else + { + // id 0 means disabled + delete ut_pex; + ut_pex = 0; + } + } + else if (!ut_pex && utorrent_pex_id != 0 && pex_allowed) + { + // Don't create it when the id is 0 + ut_pex = new UTPex(this,utorrent_pex_id); + } + } + } + } + catch (...) + { + // just ignore invalid packets + Out(SYS_CON|LOG_DEBUG) << "Invalid extended packet" << endl; + } + delete node; + tmp.resetRawData((const char*)packet,size); + } + + Uint32 Peer::sendData(const Uint8* data,Uint32 len) + { + if (killed) return 0; + + Uint32 ret = sock->sendData(data,len); + if (!sock->ok()) + kill(); + + return ret; + } + + Uint32 Peer::readData(Uint8* buf,Uint32 len) + { + if (killed) return 0; + + Uint32 ret = sock->readData(buf,len); + + if (!sock->ok()) + kill(); + + return ret; + } + + Uint32 Peer::bytesAvailable() const + { + return sock->bytesAvailable(); + } + + void Peer::dataWritten(int ) + { + // Out() << "dataWritten " << bytes << endl; + + } + + Uint32 Peer::getUploadRate() const + { + if (sock) + return (Uint32)ceil(sock->getUploadRate()); + else + return 0; + } + + Uint32 Peer::getDownloadRate() const + { + if (sock) + return (Uint32)ceil(sock->getDownloadRate()); + else + return 0; + } + + bool Peer::readyToSend() const + { + return true; + } + + void Peer::update(PeerManager* pman) + { + if (killed) + return; + + if (!sock->ok() || !preader->ok()) + { + Out(SYS_CON|LOG_DEBUG) << "Connection closed" << endl; + kill(); + return; + } + + preader->update(); + + Uint32 data_bytes = pwriter->getUploadedDataBytes(); + + if (data_bytes > 0) + { + stats.bytes_uploaded += data_bytes; + uploader->addUploadedBytes(data_bytes); + } + + if (ut_pex && ut_pex->needsUpdate()) + ut_pex->update(pman); + } + + bool Peer::isSnubbed() const + { + // 4 minutes + return snub_timer.getElapsedSinceUpdate() >= 2*60*1000 && stats.num_down_requests > 0; + } + + bool Peer::isSeeder() const + { + return pieces.allOn(); + } + + QString Peer::getIPAddresss() const + { + if (sock) + return sock->getRemoteIPAddress(); + else + return QString::null; + } + + Uint16 Peer::getPort() const + { + if (!sock) + return 0; + else + return sock->getRemotePort(); + } + + net::Address Peer::getAddress() const + { + if (!sock) + return net::Address(); + else + return sock->getRemoteAddress(); + } + + Uint32 Peer::getTimeSinceLastPiece() const + { + return snub_timer.getElapsedSinceUpdate(); + } + + float Peer::percentAvailable() const + { + return (float)pieces.numOnBits() / (float)pieces.getNumBits() * 100.0; + } + + const kt::PeerInterface::Stats & Peer::getStats() const + { + stats.choked = this->isChoked(); + stats.download_rate = this->getDownloadRate(); + stats.upload_rate = this->getUploadRate(); + stats.perc_of_file = this->percentAvailable(); + stats.snubbed = this->isSnubbed(); + stats.num_up_requests = uploader->getNumRequests(); + stats.num_down_requests = downloader->getNumRequests(); + return stats; + } + + void Peer::setACAScore(double s) + { + stats.aca_score = s; + } + + void Peer::choke() + { + if (am_choked) + return; + + pwriter->sendChoke(); + uploader->clearAllRequests(); + } + + void Peer::emitPortPacket() + { + gotPortPacket(sock->getRemoteIPAddress(),sock->getRemotePort()); + } + + void Peer::emitPex(const QByteArray & data) + { + pex(data); + } + + void Peer::setPexEnabled(bool on) + { + if (!stats.extension_protocol) + return; + + // send extension protocol handshake + bt::Uint16 port = Globals::instance().getServer().getPortInUse(); + + if (ut_pex && !on) + { + delete ut_pex; + ut_pex = 0; + } + else if (!ut_pex && on && utorrent_pex_id > 0) + { + // if the other side has enabled it to, create a new UTPex object + ut_pex = new UTPex(this,utorrent_pex_id); + } + + pwriter->sendExtProtHandshake(port,on); + + pex_allowed = on; + } + + void Peer::setGroupIDs(Uint32 up_gid,Uint32 down_gid) + { + sock->setGroupIDs(up_gid,down_gid); + } +} + +#include "peer.moc" diff --git a/libktorrent/torrent/peer.h b/libktorrent/torrent/peer.h new file mode 100644 index 0000000..68dfecc --- /dev/null +++ b/libktorrent/torrent/peer.h @@ -0,0 +1,324 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef BTPEER_H +#define BTPEER_H + +#include <qobject.h> +#include <qdatetime.h> +#include <util/timer.h> +#include <interfaces/peerinterface.h> +#include <util/bitset.h> +#include "globals.h" +#include "peerid.h" + +namespace net +{ + class Address; +} + + +namespace mse +{ + class RC4Encryptor; + class StreamSocket; +} + +namespace bt +{ + class Chunk; + class Peer; + class Request; + class Piece; + class PacketReader; + class PacketWriter; + class PeerDownloader; + class PeerUploader; + class PeerManager; + class UTPex; + + + + + /** + * @author Joris Guisson + * @brief Manages the connection with a peer + * + * This class manages a connection with a peer in the P2P network. + * It provides functions for sending packets. Packets it receives + * get relayed to the outside world using a bunch of signals. + */ + class Peer : public QObject, public kt::PeerInterface + //,public Object + { + Q_OBJECT + public: + /** + * Constructor, set the socket. + * The socket is already opened. + * @param sock The socket + * @param peer_id The Peer's BitTorrent ID + * @param num_chunks The number of chunks in the file + * @param chunk_size Size of each chunk + * @param support Which extensions the peer supports + * @param local Wether or not it is a local peer + */ + Peer(mse::StreamSocket* sock, + const PeerID & peer_id, + Uint32 num_chunks, + Uint32 chunk_size, + Uint32 support, + bool local); + + virtual ~Peer(); + + /// Get the peer's unique ID. + Uint32 getID() const {return id;} + + /// Get the IP address of the Peer. + QString getIPAddresss() const; + + /// Get the port of the Peer + Uint16 getPort() const; + + /// Get the address of the peer + net::Address getAddress() const; + + /// See if the peer has been killed. + bool isKilled() const {return killed;} + + /// Get the PacketWriter + PacketWriter & getPacketWriter() {return *pwriter;} + + /// Is the Peer choked + bool isChoked() const {return choked;} + + /// Is the Peer interested + bool isInterested() const {return interested;} + + /// Are we interested in the Peer + bool areWeInterested() const {return am_interested;} + + /// Are we choked for the Peer + bool areWeChoked() const {return am_choked;} + + /// Are we being snubbed by the Peer + bool isSnubbed() const; + + /// Get the upload rate in bytes per sec + Uint32 getUploadRate() const; + + /// Get the download rate in bytes per sec + Uint32 getDownloadRate() const; + + /// Get the Peer's BitSet + const BitSet & getBitSet() const {return pieces;} + + /// Get the Peer's ID + const PeerID & getPeerID() const {return peer_id;} + + /// Update the up- and down- speed and handle incoming packets + void update(PeerManager* pman); + + /// Get the PeerDownloader. + PeerDownloader* getPeerDownloader() {return downloader;} + + /// Get the PeerUploader. + PeerUploader* getPeerUploader() {return uploader;} + + /** + * Send a chunk of data. + * @param data The data + * @param len The length + * @param proto Indicates wether the packed is data or a protocol message + * @return Number of bytes written + */ + Uint32 sendData(const Uint8* data,Uint32 len); + + /** + * Reads data from the peer. + * @param buf The buffer to store the data + * @param len The maximum number of bytes to read + * @return The number of bytes read + */ + Uint32 readData(Uint8* buf,Uint32 len); + + /// Get the number of bytes available to read. + Uint32 bytesAvailable() const; + + /** + * See if all previously written data, has been sent. + */ + bool readyToSend() const; + + + /** + * Close the peers connection. + */ + void closeConnection(); + + /** + * Kill the Peer. + */ + void kill(); + + /** + * Get the time when this Peer was choked. + */ + TimeStamp getChokeTime() const {return time_choked;} + + /** + * Get the time when this Peer was unchoked. + */ + TimeStamp getUnchokeTime() const {return time_unchoked;} + + /** + * See if the peer is a seeder. + */ + bool isSeeder() const; + + /// Get the time in milliseconds since the last time a piece was received. + Uint32 getTimeSinceLastPiece() const; + + /// Get the time the peer connection was established. + const QTime & getConnectTime() const {return connect_time;} + + /** + * Get the percentual amount of data available from peer. + */ + float percentAvailable() const; + + /// See if the peer supports DHT + bool isDHTSupported() const {return stats.dht_support;} + + /// Set the ACA score + void setACAScore(double s); + + /// Get the stats of the peer + virtual const Stats & getStats() const; + + /// Choke the peer + void choke(); + + /** + * Emit the port packet signal. + */ + void emitPortPacket(); + + /** + * Emit the pex signal + */ + void emitPex(const QByteArray & data); + + /// Disable or enable pex + void setPexEnabled(bool on); + + /** + * Set the peer's group IDs for traffic + * @param up_gid The upload gid + * @param down_gid The download gid + */ + void setGroupIDs(Uint32 up_gid,Uint32 down_gid); + + private slots: + void dataWritten(int bytes); + + signals: + /** + * The Peer has a Chunk. + * @param p The Peer + * @param index Index of Chunk + */ + void haveChunk(Peer* p,Uint32 index); + + /** + * The Peer sent a request. + * @param req The Request + */ + void request(const Request & req); + + /** + * The Peer sent a cancel. + * @param req The Request + */ + void canceled(const Request & req); + + /** + * The Peer sent a piece of a Chunk. + * @param p The Piece + */ + void piece(const Piece & p); + + /** + * Recieved a BitSet + * @param bs The BitSet + */ + void bitSetRecieved(const BitSet & bs); + + /** + * Emitted when the peer is unchoked and interested changes value. + */ + void rerunChoker(); + + /** + * Got a port packet from this peer. + * @param ip The IP + * @param port The port + */ + void gotPortPacket(const QString & ip,Uint16 port); + + /** + * A Peer Exchange has been received, the QByteArray contains the data. + */ + void pex(const QByteArray & data); + + private: + void packetReady(const Uint8* packet,Uint32 size); + void handleExtendedPacket(const Uint8* packet,Uint32 size); + + private: + mse::StreamSocket* sock; + bool choked; + bool interested; + bool am_choked; + bool am_interested; + bool killed; + TimeStamp time_choked; + TimeStamp time_unchoked; + Uint32 id; + BitSet pieces; + PeerID peer_id; + Timer snub_timer; + PacketReader* preader; + PacketWriter* pwriter; + PeerDownloader* downloader; + PeerUploader* uploader; + mutable kt::PeerInterface::Stats stats; + QTime connect_time; + UTPex* ut_pex; + bool pex_allowed; + Uint32 utorrent_pex_id; + + friend class PacketWriter; + friend class PacketReader; + friend class PeerDownloader; + }; +} + +#endif diff --git a/libktorrent/torrent/peerdownloader.cpp b/libktorrent/torrent/peerdownloader.cpp new file mode 100644 index 0000000..0c6cdd8 --- /dev/null +++ b/libktorrent/torrent/peerdownloader.cpp @@ -0,0 +1,311 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <math.h> +#include <util/functions.h> +#include <util/log.h> +#include "globals.h" +#include "peerdownloader.h" +#include "peer.h" +#include "piece.h" +#include "packetwriter.h" + + +namespace bt +{ + TimeStampedRequest::TimeStampedRequest() + { + time_stamp = bt::GetCurrentTime(); + } + + TimeStampedRequest::TimeStampedRequest(const Request & r) : req(r) + { + time_stamp = bt::GetCurrentTime(); + } + + TimeStampedRequest::TimeStampedRequest(const TimeStampedRequest & t) + : req(t.req),time_stamp(t.time_stamp) + { + } + + bool TimeStampedRequest::operator == (const Request & r) + { + return r == req; + } + + bool TimeStampedRequest::operator == (const TimeStampedRequest & r) + { + return r.req == req; + } + + TimeStampedRequest & TimeStampedRequest::operator = (const Request & r) + { + time_stamp = bt::GetCurrentTime(); + req = r; + return *this; + } + + TimeStampedRequest & TimeStampedRequest::operator = (const TimeStampedRequest & r) + { + time_stamp = r.time_stamp; + req = r.req; + return *this; + } + + PeerDownloader::PeerDownloader(Peer* peer,Uint32 chunk_size) : peer(peer),grabbed(0),chunk_size(chunk_size / MAX_PIECE_LEN) + { + connect(peer,SIGNAL(piece(const Piece& )),this,SLOT(piece(const Piece& ))); + connect(peer,SIGNAL(destroyed()),this,SLOT(peerDestroyed())); + nearly_done = false; + max_wait_queue_size = 25; + } + + + PeerDownloader::~PeerDownloader() + { + } +#if 0 + void PeerDownloader::retransmitRequests() + { + for (QValueList<Request>::iterator i = reqs.begin();i != reqs.end();i++) + peer->getPacketWriter().sendRequest(*i); + + } +#endif + + bool PeerDownloader::canAddRequest() const + { + return wait_queue.count() < max_wait_queue_size; + } + + Uint32 PeerDownloader::getNumRequests() const + { + return reqs.count() /*+ wait_queue.count() */; + } + + int PeerDownloader::grab() + { + grabbed++; + return grabbed; + } + + void PeerDownloader::release() + { + grabbed--; + if (grabbed < 0) + grabbed = 0; + } + + void PeerDownloader::download(const Request & req) + { + if (!peer) + return; + + wait_queue.append(req); + update(); + } + + void PeerDownloader::cancel(const Request & req) + { + if (!peer) + return; + + if (wait_queue.contains(req)) + { + wait_queue.remove(req); + } + else if (reqs.contains(req)) + { + reqs.remove(req); + peer->getPacketWriter().sendCancel(req); + } + } + + void PeerDownloader::onRejected(const Request & req) + { + if (!peer) + return; + +// Out(SYS_CON|LOG_DEBUG) << "Rejected : " << req.getIndex() << " " +// << req.getOffset() << " " << req.getLength() << endl; + if (reqs.contains(req)) + { + reqs.remove(req); + rejected(req); + } + } + + void PeerDownloader::cancelAll() + { + if (peer) + { + QValueList<TimeStampedRequest>::iterator i = reqs.begin(); + while (i != reqs.end()) + { + TimeStampedRequest & tr = *i; + peer->getPacketWriter().sendCancel(tr.req); + i++; + } + } + + wait_queue.clear(); + reqs.clear(); + } + + void PeerDownloader::piece(const Piece & p) + { + Request r(p); + if (wait_queue.contains(r)) + wait_queue.remove(r); + else if (reqs.contains(r)) + reqs.remove(r); + + downloaded(p); + update(); + } + + void PeerDownloader::peerDestroyed() + { + peer = 0; + } + + bool PeerDownloader::isChoked() const + { + if (peer) + return peer->isChoked(); + else + return true; + } + + bool PeerDownloader::hasChunk(Uint32 idx) const + { + if (peer) + return peer->getBitSet().get(idx); + else + return false; + } + + Uint32 PeerDownloader::getDownloadRate() const + { + if (peer) + return peer->getDownloadRate(); + else + return 0; + } + + void PeerDownloader::checkTimeouts() + { + TimeStamp now = bt::GetCurrentTime(); + // we use a 60 second interval + const Uint32 MAX_INTERVAL = 60 * 1000; + QValueList<TimeStampedRequest>::iterator i = reqs.begin(); + while (i != reqs.end()) + { + TimeStampedRequest & tr = *i; + if (now - tr.time_stamp > MAX_INTERVAL) + { + // cancel it + TimeStampedRequest r = tr; + peer->getPacketWriter().sendCancel(r.req); + + // retransmit it + peer->getPacketWriter().sendRequest(r.req); + r.time_stamp = now; + + // reappend it at the end of the list + i = reqs.erase(i); + reqs.append(r); + Out(SYS_CON|LOG_DEBUG) << "Retransmitting " << r.req.getIndex() << ":" << r.req.getOffset() << endl; + } + else + { + // new requests get appended so once we have found one + // which hasn't timed out all the following will also not have timed out + break; + } + } + } + + + Uint32 PeerDownloader::getMaxChunkDownloads() const + { + // get the download rate in KB/sec + Uint32 rate_kbs = peer->getDownloadRate(); + rate_kbs = rate_kbs / 1024; + Uint32 num_extra = rate_kbs / 50; + + if (chunk_size >= 16) + { + return 1 + 16 * num_extra / chunk_size; + } + else + { + return 1 + (16 / chunk_size) * num_extra; + } + } + + void PeerDownloader::choked() + { + // choke doesn't mean reject when fast extensions are enabled + if (peer->getStats().fast_extensions) + return; + + QValueList<TimeStampedRequest>::iterator i = reqs.begin(); + while (i != reqs.end()) + { + TimeStampedRequest & tr = *i; + rejected(tr.req); + i++; + } + reqs.clear(); + + QValueList<Request>::iterator j = wait_queue.begin(); + while (j != wait_queue.end()) + { + Request & req = *j; + rejected(req); + j++; + } + wait_queue.clear(); + } + + void PeerDownloader::update() + { + // modify the interval if necessary + double pieces_per_sec = (double)peer->getDownloadRate() / MAX_PIECE_LEN; + + Uint32 max_reqs = 1 + (Uint32)ceil(10*pieces_per_sec); + + while (wait_queue.count() > 0 && reqs.count() < max_reqs) + { + // get a request from the wait queue and send that + Request req = wait_queue.front(); + wait_queue.pop_front(); + TimeStampedRequest r = TimeStampedRequest(req); + reqs.append(r); + peer->getPacketWriter().sendRequest(req); + } + + max_wait_queue_size = 2*max_reqs; + if (max_wait_queue_size < 10) + max_wait_queue_size = 10; + } +} + +#include "peerdownloader.moc" diff --git a/libktorrent/torrent/peerdownloader.h b/libktorrent/torrent/peerdownloader.h new file mode 100644 index 0000000..4eb37d2 --- /dev/null +++ b/libktorrent/torrent/peerdownloader.h @@ -0,0 +1,231 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef BTPEERDOWNLOADER_H +#define BTPEERDOWNLOADER_H + +#include <set> +#include <qvaluelist.h> +#include <qobject.h> +#include "globals.h" +#include "request.h" + +namespace bt +{ + class Peer; + class Request; + class Piece; + + typedef std::set<Uint32> AllowedFastSet; + /** + * Request with a timestamp. + */ + struct TimeStampedRequest + { + Request req; + TimeStamp time_stamp; + + TimeStampedRequest(); + + /** + * Constructor, set the request and calculate the timestamp. + * @param r The Request + */ + TimeStampedRequest(const Request & r); + + /** + * Copy constructor, copy the request and the timestamp + * @param r The Request + */ + TimeStampedRequest(const TimeStampedRequest & t); + + /** + * Equality operator, compares requests only. + * @param r The Request + * @return true if equal + */ + bool operator == (const Request & r); + + /** + * Equality operator, compares requests only. + * @param r The Request + * @return true if equal + */ + bool operator == (const TimeStampedRequest & r); + + /** + * Assignment operator. + * @param r The Request to copy + * @return *this + */ + TimeStampedRequest & operator = (const Request & r); + + /** + * Assignment operator. + * @param r The TimeStampedRequest to copy + * @return *this + */ + TimeStampedRequest & operator = (const TimeStampedRequest & r); + }; + + + /** + * @author Joris Guisson + * @brief Class which downloads pieces from a Peer + * + * This class downloads Piece's from a Peer. + */ + class PeerDownloader : public QObject + { + Q_OBJECT + public: + /** + * Constructor, set the Peer + * @param peer The Peer + * @param chunk_size Size of a chunk in bytes + */ + PeerDownloader(Peer* peer,Uint32 chunk_size); + virtual ~PeerDownloader(); + + /// See if we can add a request to the wait_queue + bool canAddRequest() const; + + /// Get the number of active requests + Uint32 getNumRequests() const; + + /// Is the Peer choked. + bool isChoked() const; + + /// Is NULL (is the Peer set) + bool isNull() const {return peer == 0;} + + /** + * See if the Peer has a Chunk + * @param idx The Chunk's index + */ + bool hasChunk(Uint32 idx) const; + + /// See if this PeerDownloader has nearly finished a chunk + bool isNearlyDone() const {return grabbed == 1 && nearly_done;} + + /// Set the nearly done status of the PeerDownloader + void setNearlyDone(bool nd) {nearly_done = nd;} + + /** + * Grab the Peer, indicates how many ChunkDownload's + * are using this PeerDownloader. + * @return The number of times this PeerDownloader was grabbed + */ + int grab(); + + /** + * When a ChunkDownload is ready with this PeerDownloader, + * it will release it, so that others can use it. + */ + void release(); + + /// Get the number of times this PeerDownloader was grabbed. + int getNumGrabbed() const {return grabbed;} + + /// Get the Peer + const Peer* getPeer() const {return peer;} + + /// Get the current download rate + Uint32 getDownloadRate() const; + + /** + * Check for timed out requests. + */ + void checkTimeouts(); + + /// Get the maximum number of chunk downloads + Uint32 getMaxChunkDownloads() const; + + /** + * The peer has been choked, all pending requests are rejected. + * (except for allowed fast ones) + */ + void choked(); + + public slots: + /** + * Send a Request. Note that the DownloadCap + * may not allow this. (In which case it will + * be stored temporarely in the unsent_reqs list) + * @param req The Request + */ + void download(const Request & req); + + /** + * Cancel a Request. + * @param req The Request + */ + void cancel(const Request & req); + + /** + * Cancel all Requests + */ + void cancelAll(); + + /** + * Handles a rejected request. + * @param req + */ + void onRejected(const Request & req); + + private slots: + void piece(const Piece & p); + void peerDestroyed(); + void update(); + + signals: + /** + * Emited when a Piece has been downloaded. + * @param p The Piece + */ + void downloaded(const Piece & p); + + /** + * Emitted when a request takes longer then 60 seconds to download. + * The sender of the request will have to request it again. This does not apply for + * unsent requests. Their timestamps will be updated when they get transmitted. + * @param r The request + */ + void timedout(const Request & r); + + /** + * A request was rejected. + * @param req The Request + */ + void rejected(const Request & req); + + + private: + Peer* peer; + QValueList<TimeStampedRequest> reqs; + QValueList<Request> wait_queue; + Uint32 max_wait_queue_size; + int grabbed; + Uint32 chunk_size; + bool nearly_done; + }; + +} + +#endif diff --git a/libktorrent/torrent/peerid.cpp b/libktorrent/torrent/peerid.cpp new file mode 100644 index 0000000..5f314b3 --- /dev/null +++ b/libktorrent/torrent/peerid.cpp @@ -0,0 +1,253 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <time.h> +#include <stdlib.h> +#include <qmap.h> +#include <klocale.h> +#include "peerid.h" +#include "ktversion.h" + +namespace bt +{ + char RandomLetterOrNumber() + { + int i = rand() % 62; + if (i < 26) + return 'a' + i; + else if (i < 52) + return 'A' + (i - 26); + else + return '0' + (i - 52); + } + + + PeerID::PeerID() + { + srand(time(0)); + memcpy(id,kt::PEER_ID,8); + for (int i = 8;i < 20;i++) + id[i] = RandomLetterOrNumber(); + client_name = identifyClient(); + } + + PeerID::PeerID(const char* pid) + { + if (pid) + memcpy(id,pid,20); + else + memset(id,0,20); + client_name = identifyClient(); + } + + PeerID::PeerID(const PeerID & pid) + { + memcpy(id,pid.id,20); + client_name = pid.client_name; + } + + PeerID::~PeerID() + {} + + + + PeerID & PeerID::operator = (const PeerID & pid) + { + memcpy(id,pid.id,20); + client_name = pid.client_name; + return *this; + } + + bool operator == (const PeerID & a,const PeerID & b) + { + for (int i = 0;i < 20;i++) + if (a.id[i] != b.id[i]) + return false; + + return true; + } + + bool operator != (const PeerID & a,const PeerID & b) + { + return ! operator == (a,b); + } + + bool operator < (const PeerID & a,const PeerID & b) + { + for (int i = 0;i < 20;i++) + if (a.id[i] < b.id[i]) + return true; + + return false; + } + + QString PeerID::toString() const + { + QString r; + for (int i = 0;i < 20;i++) + r += id[i] == 0 ? ' ' : id[i]; + return r; + } + + QString PeerID::identifyClient() const + { + if (!client_name.isNull()) + return client_name; + + QString peer_id = toString(); + // we only need to create this map once + // so make it static + static QMap<QString, QString> Map; + static bool first = true; + + if (first) + { + // Keep things a bit alphabetic to make it easier add new ones + //AZUREUS STYLE + Map["AG"] = "Ares"; + Map["A~"] = "Ares"; + Map["AV"] = "Avicora"; + Map["AX"] = "BitPump"; + Map["AR"] = "Arctic"; + Map["AZ"] = "Azureus"; + Map["BB"] = "BitBuddy"; + Map["BC"] = "BitComet"; + Map["BF"] = "Bitflu"; + Map["BG"] = "BTGetit"; + Map["BM"] = "BitMagnet"; + Map["BO"] = "BitsOnWheels"; + Map["BR"] = "BitRocket"; + Map["BS"] = "BTSlave"; + Map["BX"] = "BitTorrent X"; + Map["CD"] = "Enhanced CTorrent"; + Map["CT"] = "CTorrent"; + Map["DE"] = "DelugeTorrent"; + Map["DP"] = "Propagate Data Client"; + Map["EB"] = "EBit"; + Map["ES"] = "electric sheep"; + Map["FT"] = "FoxTorrent"; + Map["GS"] = "GSTorrent"; + Map["G3"] = "G3 Torrent"; + Map["HL"] = "Halite"; + Map["HN"] = "Hydranode"; + Map["KG"] = "KGet"; + Map["KT"] = "KTorrent"; // lets not forget our own client + Map["LH"] = "LH-ABC"; + Map["lt"] = "libTorrent"; + Map["LT"] = "libtorrent"; + Map["LP"] = "Lphant"; + Map["LW"] = "LimeWire"; + Map["ML"] = "MLDonkey"; + Map["MO"] = "MonoTorrent"; + Map["MP"] = "MooPolice"; + Map["MT"] = "MoonLight"; + Map["PD"] = "Pando"; + Map["qB"] = "qBittorrent"; + Map["QD"] = "QQDownload"; + Map["QT"] = "Qt 4 Torrent example"; + Map["RS"] = "Rufus"; + Map["RT"] = "Retriever"; + Map["S~"] = "Shareaza alpha/beta"; + Map["SB"] = "Swiftbit"; + Map["SS"] = "SwarmScope"; + Map["ST"] = "SymTorrent"; + Map["st"] = "sharktorrent"; + Map["SZ"] = "Shareaza"; + Map["TN"] = "Torrent .NET"; + Map["TR"] = "Transmission"; + Map["TS"] = "Torrent Storm"; + Map["TT"] = "TuoTu"; + Map["UL"] = "uLeecher!"; + Map["UT"] = QString("%1Torrent").arg(QChar(0x00B5)); // µTorrent, 0x00B5 is unicode for µ + Map["WT"] = "BitLet"; + Map["WY"] = "FireTorrent"; + Map["XL"] = "Xunlei"; + Map["XT"] = "Xan Torrent"; + Map["XX"] = "Xtorrent"; + Map["ZT"] = "Zip Torrent"; + + //SHADOWS STYLE + Map["A"] = "ABC"; + Map["O"] = "Osprey Permaseed"; + Map["Q"] = "BTQueue"; + Map["R"] = "Tribler"; + Map["S"] = "Shadow's"; + Map["T"] = "BitTornado"; + Map["U"] = "UPnP NAT BitTorrent"; + //OTHER + Map["Plus"] = "Plus! II"; + Map["OP"] = "Opera"; + Map["BOW"] = "Bits on Wheels"; + Map["M"] = "BitTorrent"; + Map["exbc"] = "BitComet"; + Map["Mbrst"] = "Burst!"; + first = false; + } + + QString name = i18n("Unknown client"); + if (peer_id.at(0) == '-' && + peer_id.at(1).isLetter() && + peer_id.at(2).isLetter() ) //AZ style + { + QString ID(peer_id.mid(1,2)); + if (Map.contains(ID)) + name = Map[ID] + " " + peer_id.at(3) + "." + peer_id.at(4) + "." + + peer_id.at(5) + "." + peer_id.at(6); + } + else if (peer_id.at(0).isLetter() && + peer_id.at(1).isDigit() && + peer_id.at(2).isDigit() ) //Shadow's style + { + QString ID = QString(peer_id.at(0)); + if (Map.contains(ID)) + name = Map[ID] + " " + peer_id.at(1) + "." + + peer_id.at(2) + "." + peer_id.at(3); + } + else if (peer_id.at(0) == 'M' && peer_id.at(2) == '-' && (peer_id.at(4) == '-' || peer_id.at(5) == '-')) + { + name = Map["M"] + " " + peer_id.at(1) + "." + peer_id.at(3); + if(peer_id.at(4) == '-') + name += "." + peer_id.at(5); + else + name += peer_id.at(4) + "." + peer_id.at(6); + } + else if (peer_id.startsWith("OP")) + { + name = Map["OP"]; + } + else if ( peer_id.startsWith("exbc") ) + { + name = Map["exbc"]; + } + else if ( peer_id.mid(1,3) == "BOW") + { + name = Map["BOW"]; + } + else if ( peer_id.startsWith("Plus")) + { + name = Map["Plus"]; + } + else if ( peer_id.startsWith("Mbrst")) + { + name = Map["Mbrst"] + " " + peer_id.at(5) + "." + peer_id.at(7); + } + + return name; + } +} diff --git a/libktorrent/torrent/peerid.h b/libktorrent/torrent/peerid.h new file mode 100644 index 0000000..90ba439 --- /dev/null +++ b/libktorrent/torrent/peerid.h @@ -0,0 +1,61 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef BTPEERID_H +#define BTPEERID_H + +#include <qstring.h> + +namespace bt +{ + + /** + @author Joris Guisson + */ + class PeerID + { + char id[20]; + QString client_name; + public: + PeerID(); + PeerID(const char* pid); + PeerID(const PeerID & pid); + virtual ~PeerID(); + + PeerID & operator = (const PeerID & pid); + + const char* data() const {return id;} + + QString toString() const; + + /** + * Interprets the PeerID to figure out which client it is. + * @author Ivan + Joris + * @return The name of the client + */ + QString identifyClient() const; + + friend bool operator == (const PeerID & a,const PeerID & b); + friend bool operator != (const PeerID & a,const PeerID & b); + friend bool operator < (const PeerID & a,const PeerID & b); + }; + +} + +#endif diff --git a/libktorrent/torrent/peermanager.cpp b/libktorrent/torrent/peermanager.cpp new file mode 100644 index 0000000..d3744fc --- /dev/null +++ b/libktorrent/torrent/peermanager.cpp @@ -0,0 +1,607 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <util/log.h> +#include <util/file.h> +#include <util/error.h> +#include <net/address.h> +#include "peermanager.h" +#include "peer.h" +#include "bnode.h" +#include "globals.h" +#include "server.h" +#include "authenticate.h" +#include "torrent.h" +#include "uploader.h" +#include "downloader.h" +#include <util/functions.h> +#include <qhostaddress.h> +#include <mse/streamsocket.h> +#include <mse/encryptedauthenticate.h> +#include <klocale.h> +#include "ipblocklist.h" +#include "chunkcounter.h" +#include "authenticationmonitor.h" +#include <qdatetime.h> + +using namespace kt; + +namespace bt +{ + Uint32 PeerManager::max_connections = 0; + Uint32 PeerManager::max_total_connections = 0; + Uint32 PeerManager::total_connections = 0; + + PeerManager::PeerManager(Torrent & tor) + : tor(tor),available_chunks(tor.getNumChunks()) + { + killed.setAutoDelete(true); + started = false; + + cnt = new ChunkCounter(tor.getNumChunks()); + num_pending = 0; + pex_on = !tor.isPrivate(); + } + + + PeerManager::~PeerManager() + { + delete cnt; + Globals::instance().getServer().removePeerManager(this); + + if (peer_list.count() <= total_connections) + total_connections -= peer_list.count(); + else + total_connections = 0; + + peer_list.setAutoDelete(true); + peer_list.clear(); + } + + void PeerManager::update() + { + if (!started) + return; + + // update the speed of each peer, + // and get ridd of some killed peers + QPtrList<Peer>::iterator i = peer_list.begin(); + while (i != peer_list.end()) + { + Peer* p = *i; + if (p->isKilled()) + { + cnt->decBitSet(p->getBitSet()); + updateAvailableChunks(); + i = peer_list.erase(i); + killed.append(p); + peer_map.erase(p->getID()); + if (total_connections > 0) + total_connections--; + peerKilled(p); + } + else + { + p->update(this); + i++; + } + } + + // connect to some new peers + connectToPeers(); + } + + void PeerManager::killChokedPeers(Uint32 older_then) + { + Out() << "Getting rid of peers which have been choked for a long time" << endl; + TimeStamp now = bt::GetCurrentTime(); + QPtrList<Peer>::iterator i = peer_list.begin(); + Uint32 num_killed = 0; + while (i != peer_list.end() && num_killed < 20) + { + Peer* p = *i; + if (p->isChoked() && (now - p->getChokeTime()) > older_then) + { + p->kill(); + num_killed++; + } + + i++; + } + } + + void PeerManager::setMaxConnections(Uint32 max) + { + max_connections = max; + } + + void PeerManager::setMaxTotalConnections(Uint32 max) + { + Uint32 sys_max = bt::MaxOpenFiles() - 50; // leave about 50 free for regular files + max_total_connections = max; + if (max == 0 || max_total_connections > sys_max) + max_total_connections = sys_max; + } + + void PeerManager::addPotentialPeer(const PotentialPeer & pp) + { + if (potential_peers.size() > 150) + return; + + // avoid duplicates in the potential_peers map + std::pair<PPItr,PPItr> r = potential_peers.equal_range(pp.ip); + for (PPItr i = r.first;i != r.second;i++) + { + if (i->second.port == pp.port) // port and IP are the same so return + return; + } + + potential_peers.insert(std::make_pair(pp.ip,pp)); + } + + void PeerManager::killSeeders() + { + QPtrList<Peer>::iterator i = peer_list.begin(); + while (i != peer_list.end()) + { + Peer* p = *i; + if ( p->isSeeder() ) + p->kill(); + i++; + } + } + + void PeerManager::killUninterested() + { + QPtrList<Peer>::iterator i = peer_list.begin(); + while (i != peer_list.end()) + { + Peer* p = *i; + if ( !p->isInterested() && (p->getConnectTime().secsTo(QTime::currentTime()) > 30) ) + p->kill(); + i++; + } + } + + void PeerManager::onHave(Peer*,Uint32 index) + { + available_chunks.set(index,true); + cnt->inc(index); + } + + void PeerManager::onBitSetRecieved(const BitSet & bs) + { + for (Uint32 i = 0;i < bs.getNumBits();i++) + { + if (bs.get(i)) + { + available_chunks.set(i,true); + cnt->inc(i); + } + } + } + + + void PeerManager::newConnection(mse::StreamSocket* sock,const PeerID & peer_id,Uint32 support) + { + Uint32 total = peer_list.count() + num_pending; + bool local_not_ok = (max_connections > 0 && total >= max_connections); + bool global_not_ok = (max_total_connections > 0 && total_connections >= max_total_connections); + + if (!started || local_not_ok || global_not_ok) + { + // get rid of bad peer and replace it by another one + if (!killBadPeer()) + { + // we failed to find a bad peer, so just delete this one + delete sock; + return; + } + } + + createPeer(sock,peer_id,support,false); + } + + void PeerManager::peerAuthenticated(Authenticate* auth,bool ok) + { + if (!started) + return; + + if (total_connections > 0) + total_connections--; + + num_pending--; + if (!ok) + { + mse::EncryptedAuthenticate* a = dynamic_cast<mse::EncryptedAuthenticate*>(auth); + if (a && Globals::instance().getServer().unencryptedConnectionsAllowed()) + { + // if possible try unencrypted + QString ip = a->getIP(); + Uint16 port = a->getPort(); + Authenticate* st = new Authenticate(ip,port,tor.getInfoHash(),tor.getPeerID(),this); + if (auth->isLocal()) + st->setLocal(true); + + connect(this,SIGNAL(stopped()),st,SLOT(onPeerManagerDestroyed())); + AuthenticationMonitor::instance().add(st); + num_pending++; + total_connections++; + } + return; + } + + if (connectedTo(auth->getPeerID())) + { + return; + } + + createPeer(auth->takeSocket(),auth->getPeerID(),auth->supportedExtensions(),auth->isLocal()); + } + + void PeerManager::createPeer(mse::StreamSocket* sock,const PeerID & peer_id,Uint32 support,bool local) + { + Peer* peer = new Peer(sock,peer_id,tor.getNumChunks(),tor.getChunkSize(),support,local); + + connect(peer,SIGNAL(haveChunk(Peer*, Uint32 )),this,SLOT(onHave(Peer*, Uint32 ))); + connect(peer,SIGNAL(bitSetRecieved(const BitSet& )), + this,SLOT(onBitSetRecieved(const BitSet& ))); + connect(peer,SIGNAL(rerunChoker()),this,SLOT(onRerunChoker())); + connect(peer,SIGNAL(pex( const QByteArray& )),this,SLOT(pex( const QByteArray& ))); + + peer_list.append(peer); + peer_map.insert(peer->getID(),peer); + total_connections++; + newPeer(peer); + peer->setPexEnabled(pex_on); + } + + bool PeerManager::connectedTo(const PeerID & peer_id) + { + if (!started) + return false; + + for (Uint32 j = 0;j < peer_list.count();j++) + { + Peer* p = peer_list.at(j); + if (p->getPeerID() == peer_id) + { + return true; + } + } + return false; + } + + bool PeerManager::connectedTo(const QString & ip,Uint16 port) const + { + PtrMap<Uint32,Peer>::const_iterator i = peer_map.begin(); + while (i != peer_map.end()) + { + const Peer* p = i->second; + if (p->getPort() == port && p->getStats().ip_address == ip) + return true; + i++; + } + return false; + } + + void PeerManager::connectToPeers() + { + if (potential_peers.size() == 0) + return; + + if (peer_list.count() + num_pending >= max_connections && max_connections > 0) + return; + + if (total_connections >= max_total_connections && max_total_connections > 0) + return; + + if (num_pending > MAX_SIMULTANIOUS_AUTHS) + return; + + if (!mse::StreamSocket::canInitiateNewConnection()) + return; // to many sockets in SYN_SENT state + + Uint32 num = 0; + if (max_connections > 0) + { + Uint32 available = max_connections - (peer_list.count() + num_pending); + num = available >= potential_peers.size() ? + potential_peers.size() : available; + } + else + { + num = potential_peers.size(); + } + + if (num + total_connections >= max_total_connections && max_total_connections > 0) + num = max_total_connections - total_connections; + + for (Uint32 i = 0;i < num;i++) + { + if (num_pending > MAX_SIMULTANIOUS_AUTHS) + return; + + PPItr itr = potential_peers.begin(); + + IPBlocklist& ipfilter = IPBlocklist::instance(); + + if (!ipfilter.isBlocked(itr->first) && !connectedTo(itr->first,itr->second.port)) + { + // Out() << "EncryptedAuthenticate : " << pp.ip << ":" << pp.port << endl; + Authenticate* auth = 0; + const PotentialPeer & pp = itr->second; + + if (Globals::instance().getServer().isEncryptionEnabled()) + auth = new mse::EncryptedAuthenticate(pp.ip,pp.port,tor.getInfoHash(),tor.getPeerID(),this); + else + auth = new Authenticate(pp.ip,pp.port,tor.getInfoHash(),tor.getPeerID(),this); + + if (pp.local) + auth->setLocal(true); + + connect(this,SIGNAL(stopped()),auth,SLOT(onPeerManagerDestroyed())); + + AuthenticationMonitor::instance().add(auth); + num_pending++; + total_connections++; + } + potential_peers.erase(itr); + } + } + + + + Uint32 PeerManager::clearDeadPeers() + { + Uint32 num = killed.count(); + killed.clear(); + return num; + } + + void PeerManager::closeAllConnections() + { + killed.clear(); + + if (peer_list.count() <= total_connections) + total_connections -= peer_list.count(); + else + total_connections = 0; + + peer_map.clear(); + peer_list.setAutoDelete(true); + peer_list.clear(); + peer_list.setAutoDelete(false); + } + + // pick a random magic number + const Uint32 PEER_LIST_HDR_MAGIC = 0xEF12AB34; + + struct PeerListHeader + { + Uint32 magic; + Uint32 num_peers; + Uint32 ip_version; // 4 or 6, 6 is for future purposes only (when we support IPv6) + }; + + struct PeerListEntry + { + Uint32 ip; + Uint16 port; + }; + + void PeerManager::savePeerList(const QString & file) + { + bt::File fptr; + if (!fptr.open(file,"wb")) + return; + + try + { + PeerListHeader hdr; + hdr.magic = PEER_LIST_HDR_MAGIC; + // we will save both the active and potential peers + hdr.num_peers = peer_list.count() + potential_peers.size(); + hdr.ip_version = 4; + + fptr.write(&hdr,sizeof(PeerListHeader)); + + Out(SYS_GEN|LOG_DEBUG) << "Saving list of peers to " << file << endl; + // first the active peers + for (QPtrList<Peer>::iterator itr = peer_list.begin(); itr != peer_list.end();itr++) + { + Peer* p = *itr; + PeerListEntry e; + net::Address addr = p->getAddress(); + e.ip = addr.ip(); + e.port = addr.port(); + fptr.write(&e,sizeof(PeerListEntry)); + } + + // now the potential_peers + PPItr i = potential_peers.begin(); + while (i != potential_peers.end()) + { + net::Address addr(i->first,i->second.port); + PeerListEntry e; + e.ip = addr.ip(); + e.port = addr.port(); + fptr.write(&e,sizeof(PeerListEntry)); + i++; + } + } + catch (bt::Error & err) + { + Out(SYS_GEN|LOG_DEBUG) << "Error happened during saving of peer list : " << err.toString() << endl; + } + } + + void PeerManager::loadPeerList(const QString & file) + { + bt::File fptr; + if (!fptr.open(file,"rb")) + return; + + try + { + PeerListHeader hdr; + fptr.read(&hdr,sizeof(PeerListHeader)); + if (hdr.magic != PEER_LIST_HDR_MAGIC || hdr.ip_version != 4) + throw Error("Peer list file corrupted"); + + Out(SYS_GEN|LOG_DEBUG) << "Loading list of peers from " << file << " (num_peers = " << hdr.num_peers << ")" << endl; + + for (Uint32 i = 0;i < hdr.num_peers && !fptr.eof();i++) + { + PeerListEntry e; + fptr.read(&e,sizeof(PeerListEntry)); + PotentialPeer pp; + + // convert IP address to string + pp.ip = QString("%1.%2.%3.%4") + .arg((e.ip & 0xFF000000) >> 24) + .arg((e.ip & 0x00FF0000) >> 16) + .arg((e.ip & 0x0000FF00) >> 8) + .arg( e.ip & 0x000000FF); + pp.port = e.port; + addPotentialPeer(pp); + } + + } + catch (bt::Error & err) + { + Out(SYS_GEN|LOG_DEBUG) << "Error happened during saving of peer list : " << err.toString() << endl; + } + } + + void PeerManager::start() + { + started = true; + Globals::instance().getServer().addPeerManager(this); + } + + + void PeerManager::stop() + { + cnt->reset(); + available_chunks.clear(); + started = false; + Globals::instance().getServer().removePeerManager(this); + stopped(); + num_pending = 0; + } + + Peer* PeerManager::findPeer(Uint32 peer_id) + { + return peer_map.find(peer_id); + } + + void PeerManager::onRerunChoker() + { + // append a 0 ptr to killed + // so that the next update in TorrentControl + // will be forced to do the choking + killed.append(0); + } + + void PeerManager::updateAvailableChunks() + { + for (Uint32 i = 0;i < available_chunks.getNumBits();i++) + { + available_chunks.set(i,cnt->get(i) > 0); + } + } + + void PeerManager::peerSourceReady(kt::PeerSource* ps) + { + PotentialPeer pp; + while (ps->takePotentialPeer(pp)) + addPotentialPeer(pp); + } + + bool PeerManager::killBadPeer() + { + for (PtrMap<Uint32,Peer>::iterator i = peer_map.begin();i != peer_map.end();i++) + { + Peer* p = i->second; + if (p->getStats().aca_score <= -5.0 && p->getStats().aca_score > -50.0) + { + Out(SYS_GEN|LOG_DEBUG) << "Killing bad peer, to make room for other peers" << endl; + p->kill(); + return true; + } + } + return false; + } + + void PeerManager::pex(const QByteArray & arr) + { + if (!pex_on) + return; + + Out(SYS_CON|LOG_NOTICE) << "PEX: found " << (arr.size() / 6) << " peers" << endl; + for (Uint32 i = 0;i+6 <= arr.size();i+=6) + { + Uint8 tmp[6]; + memcpy(tmp,arr.data() + i,6); + PotentialPeer pp; + pp.port = ReadUint16(tmp,4); + Uint32 ip = ReadUint32(tmp,0); + pp.ip = QString("%1.%2.%3.%4") + .arg((ip & 0xFF000000) >> 24) + .arg((ip & 0x00FF0000) >> 16) + .arg((ip & 0x0000FF00) >> 8) + .arg( ip & 0x000000FF); + pp.local = false; + + addPotentialPeer(pp); + } + } + + + void PeerManager::setPexEnabled(bool on) + { + if (on && tor.isPrivate()) + return; + + if (pex_on == on) + return; + + QPtrList<Peer>::iterator i = peer_list.begin(); + while (i != peer_list.end()) + { + Peer* p = *i; + if (!p->isKilled()) + p->setPexEnabled(on); + i++; + } + pex_on = on; + } + + void PeerManager::setGroupIDs(Uint32 up,Uint32 down) + { + for (PtrMap<Uint32,Peer>::iterator i = peer_map.begin();i != peer_map.end();i++) + { + Peer* p = i->second; + p->setGroupIDs(up,down); + } + } +} + +#include "peermanager.moc" diff --git a/libktorrent/torrent/peermanager.h b/libktorrent/torrent/peermanager.h new file mode 100644 index 0000000..d5fdb9f --- /dev/null +++ b/libktorrent/torrent/peermanager.h @@ -0,0 +1,251 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef BTPEERMANAGER_H +#define BTPEERMANAGER_H + +#include <map> +#include <qobject.h> +#include <qptrlist.h> +#include <util/ptrmap.h> +#include "globals.h" +#include "peerid.h" +#include <util/bitset.h> +#include <interfaces/peersource.h> + +namespace mse +{ + class StreamSocket; +} + +namespace bt +{ + class Peer; + class ChunkManager; + class Torrent; + class Authenticate; + class ChunkCounter; + + + + const Uint32 MAX_SIMULTANIOUS_AUTHS = 20; + + /** + * @author Joris Guisson + * @brief Manages all the Peers + * + * This class manages all Peer objects. + * It can also open connections to other peers. + */ + class PeerManager : public QObject + { + Q_OBJECT + public: + /** + * Constructor. + * @param tor The Torrent + */ + PeerManager(Torrent & tor); + virtual ~PeerManager(); + + + /** + * Check for new connections, update down and upload speed of each Peer. + * Initiate new connections. + */ + void update(); + + /** + * Remove dead peers. + * @return The number of dead ones removed + */ + Uint32 clearDeadPeers(); + + /** + * Get the i'th Peer. + * @param index + * @return Peer or 0 if out of range + */ + Peer* getPeer(Uint32 index) {return peer_list.at(index);} + + /** + * Find a Peer based on it's ID + * @param peer_id The ID + * @return A Peer or 0, if nothing could be found + */ + Peer* findPeer(Uint32 peer_id); + + /** + * Try to connect to some peers + */ + void connectToPeers(); + + /** + * Close all Peer connections. + */ + void closeAllConnections(); + + /** + * Start listening to incoming requests. + */ + void start(); + + /** + * Stop listening to incoming requests. + */ + void stop(); + + /** + * Kill all peers who have been choked longer then @a older_then time. + * @param older_then Time in milliseconds + */ + void killChokedPeers(Uint32 older_then); + + Uint32 getNumConnectedPeers() const {return peer_list.count();} + Uint32 getNumPending() const {return num_pending;} + + static void setMaxConnections(Uint32 max); + static Uint32 getMaxConnections() {return max_connections;} + + static void setMaxTotalConnections(Uint32 max); + static Uint32 getMaxTotalConnections() {return max_total_connections;} + + static Uint32 getTotalConnections() {return total_connections;} + + /// Is the peer manager started + bool isStarted() const {return started;} + + /// Get the Torrent + Torrent & getTorrent() {return tor;} + + /** + * A new connection is ready for this PeerManager. + * @param sock The socket + * @param peer_id The Peer's ID + * @param support What extensions the peer supports + */ + void newConnection(mse::StreamSocket* sock,const PeerID & peer_id,Uint32 support); + + /** + * Add a potential peer + * @param pp The PotentialPeer + */ + void addPotentialPeer(const kt::PotentialPeer & pp); + + /** + * Kills all connections to seeders. + * This is used when torrent download gets finished + * and we should drop all connections to seeders + */ + void killSeeders(); + + /** + * Kills all peers that are not interested for a long time. + * This should be used when torrent is seeding ONLY. + */ + void killUninterested(); + + /// Get a BitSet of all available chunks + const BitSet & getAvailableChunksBitSet() const {return available_chunks;} + + /// Get the chunk counter. + ChunkCounter & getChunkCounter() {return *cnt;}; + + /// Are we connected to a Peer given it's PeerID ? + bool connectedTo(const PeerID & peer_id); + + /** + * A peer has authenticated. + * @param auth The Authenticate object + * @param ok Wether or not the attempt was succesfull + */ + void peerAuthenticated(Authenticate* auth,bool ok); + + /** + * Save the IP's and port numbers of all peers. + */ + void savePeerList(const QString & file); + + /** + * Load the peer list again and add them to the potential peers + */ + void loadPeerList(const QString & file); + + typedef QPtrList<Peer>::const_iterator CItr; + + CItr beginPeerList() const {return peer_list.begin();} + CItr endPeerList() const {return peer_list.end();} + + /// Is PEX eanbled + bool isPexEnabled() const {return pex_on;} + + /// Enable or disable PEX + void setPexEnabled(bool on); + + /// Set the group IDs of each peer + void setGroupIDs(Uint32 up,Uint32 down); + + public slots: + /** + * A PeerSource, has new potential peers. + * @param ps The PeerSource + */ + void peerSourceReady(kt::PeerSource* ps); + + private: + void updateAvailableChunks(); + bool killBadPeer(); + void createPeer(mse::StreamSocket* sock,const PeerID & peer_id,Uint32 support,bool local); + bool connectedTo(const QString & ip,Uint16 port) const; + + private slots: + void onHave(Peer* p,Uint32 index); + void onBitSetRecieved(const BitSet & bs); + void onRerunChoker(); + void pex(const QByteArray & arr); + + + signals: + void newPeer(Peer* p); + void peerKilled(Peer* p); + void stopped(); + + private: + PtrMap<Uint32,Peer> peer_map; + QPtrList<Peer> peer_list; + QPtrList<Peer> killed; + Torrent & tor; + bool started; + BitSet available_chunks; + ChunkCounter* cnt; + Uint32 num_pending; + bool pex_on; + + static Uint32 max_connections; + static Uint32 max_total_connections; + static Uint32 total_connections; + + std::multimap<QString,kt::PotentialPeer> potential_peers; + + typedef std::multimap<QString,kt::PotentialPeer>::iterator PPItr; + }; + +} + +#endif diff --git a/libktorrent/torrent/peersourcemanager.cpp b/libktorrent/torrent/peersourcemanager.cpp new file mode 100644 index 0000000..fef55b5 --- /dev/null +++ b/libktorrent/torrent/peersourcemanager.cpp @@ -0,0 +1,556 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <qfile.h> +#include <klocale.h> +#include <functions.h> +#include <util/log.h> +#include <torrent/globals.h> +#include <kademlia/dhtbase.h> +#include <kademlia/dhttrackerbackend.h> +#include "tracker.h" +#include "udptracker.h" +#include "httptracker.h" +#include "torrentcontrol.h" +#include "torrent.h" +#include "peermanager.h" +#include "peersourcemanager.h" + +namespace bt +{ + const Uint32 INITIAL_WAIT_TIME = 30; + const Uint32 LONGER_WAIT_TIME = 300; + const Uint32 FINAL_WAIT_TIME = 1800; + + PeerSourceManager::PeerSourceManager(TorrentControl* tor,PeerManager* pman) + : tor(tor),pman(pman),curr(0),m_dht(0),started(false),pending(false) + { + failures = 0; + trackers.setAutoDelete(true); + no_save_custom_trackers = false; + + const TrackerTier* t = tor->getTorrent().getTrackerList(); + int tier = 1; + while (t) + { + // add all standard trackers + const KURL::List & tr = t->urls; + KURL::List::const_iterator i = tr.begin(); + while (i != tr.end()) + { + addTracker(*i,false,tier); + i++; + } + + tier++; + t = t->next; + } + + //load custom trackers + loadCustomURLs(); + + connect(&timer,SIGNAL(timeout()),this,SLOT(updateCurrentManually())); + } + + PeerSourceManager::~PeerSourceManager() + { + saveCustomURLs(); + additional.setAutoDelete(true); + QPtrList<kt::PeerSource>::iterator itr = additional.begin(); + while (itr != additional.end()) + { + kt::PeerSource* ps = *itr; + ps->aboutToBeDestroyed(); + itr++; + } + additional.clear(); + } + + void PeerSourceManager::addTracker(Tracker* trk) + { + trackers.insert(trk->trackerURL(),trk); + connect(trk,SIGNAL(peersReady( kt::PeerSource* )), + pman,SLOT(peerSourceReady( kt::PeerSource* ))); + } + + void PeerSourceManager::addPeerSource(kt::PeerSource* ps) + { + additional.append(ps); + connect(ps,SIGNAL(peersReady( kt::PeerSource* )), + pman,SLOT(peerSourceReady( kt::PeerSource* ))); + } + + void PeerSourceManager::removePeerSource(kt::PeerSource* ps) + { + disconnect(ps,SIGNAL(peersReady( kt::PeerSource* )), + pman,SLOT(peerSourceReady( kt::PeerSource* ))); + additional.remove(ps); + } + + void PeerSourceManager::start() + { + if (started) + return; + + started = true; + QPtrList<kt::PeerSource>::iterator i = additional.begin(); + while (i != additional.end()) + { + (*i)->start(); + i++; + } + + if (!curr) + { + if (trackers.count() > 0) + { + switchTracker(selectTracker()); + tor->resetTrackerStats(); + curr->start(); + } + } + else + { + tor->resetTrackerStats(); + curr->start(); + } + } + + void PeerSourceManager::stop(WaitJob* wjob) + { + if (!started) + return; + + started = false; + QPtrList<kt::PeerSource>::iterator i = additional.begin(); + while (i != additional.end()) + { + (*i)->stop(); + i++; + } + + if (curr) + curr->stop(wjob); + + timer.stop(); + statusChanged(i18n("Stopped")); + } + + void PeerSourceManager::completed() + { + QPtrList<kt::PeerSource>::iterator i = additional.begin(); + while (i != additional.end()) + { + (*i)->completed(); + i++; + } + + if (curr) + curr->completed(); + } + + void PeerSourceManager::manualUpdate() + { + QPtrList<kt::PeerSource>::iterator i = additional.begin(); + while (i != additional.end()) + { + (*i)->manualUpdate(); + i++; + } + + if (curr) + { + timer.stop(); + curr->manualUpdate(); + } + } + + + + KURL PeerSourceManager::getTrackerURL() const + { + if (curr) + return curr->trackerURL(); + else + return KURL(); + } + + KURL::List PeerSourceManager::getTrackerURLs() + { + KURL::List urls; + const TrackerTier* t = tor->getTorrent().getTrackerList(); + while (t) + { + urls += t->urls; + t = t->next; + } + + urls += custom_trackers; + return urls; + } + + void PeerSourceManager::addTracker(KURL url, bool custom,int tier) + { + if (trackers.contains(url)) + return; + + Tracker* trk = 0; + if (url.protocol() == "udp") + trk = new UDPTracker(url,tor,tor->getTorrent().getPeerID(),tier); + else + trk = new HTTPTracker(url,tor,tor->getTorrent().getPeerID(),tier); + + addTracker(trk); + if (custom) + { + custom_trackers.append(url); + if (!no_save_custom_trackers) + saveCustomURLs(); + } + } + + bool PeerSourceManager::removeTracker(KURL url) + { + if (!custom_trackers.contains(url)) + return false; + + custom_trackers.remove(url); + Tracker* trk = trackers.find(url); + if (curr == trk) + { + // do a timed delete on the tracker, so the stop signal + // has plenty of time to reach it + trk->stop(); + trk->timedDelete(10 * 1000); + trackers.setAutoDelete(false); + trackers.erase(url); + trackers.setAutoDelete(true); + + if (trackers.count() > 0) + { + switchTracker(selectTracker()); + tor->resetTrackerStats(); + curr->start(); + } + } + else + { + // just delete if not the current one + trackers.erase(url); + } + saveCustomURLs(); + return true; + } + + void PeerSourceManager::setTracker(KURL url) + { + Tracker* trk = trackers.find(url); + if (!trk) + return; + + if (curr != trk) + { + if (curr) + curr->stop(); + switchTracker(trk); + tor->resetTrackerStats(); + trk->start(); + } + } + + void PeerSourceManager::restoreDefault() + { + KURL::List::iterator i = custom_trackers.begin(); + while (i != custom_trackers.end()) + { + Tracker* t = trackers.find(*i); + if (t) + { + if (curr == t) + { + if (t->isStarted()) + t->stop(); + + curr = 0; + trackers.erase(*i); + if (trackers.count() > 0) + { + switchTracker(trackers.begin()->second); + if (started) + { + tor->resetTrackerStats(); + curr->start(); + } + } + } + else + { + trackers.erase(*i); + } + } + i++; + } + + custom_trackers.clear(); + saveCustomURLs(); + } + + void PeerSourceManager::saveCustomURLs() + { + QString trackers_file = tor->getTorDir() + "trackers"; + QFile file(trackers_file); + if(!file.open(IO_WriteOnly)) + return; + + QTextStream stream(&file); + for (KURL::List::iterator i = custom_trackers.begin();i != custom_trackers.end();i++) + stream << (*i).prettyURL() << ::endl; + } + + void PeerSourceManager::loadCustomURLs() + { + QString trackers_file = tor->getTorDir() + "trackers"; + QFile file(trackers_file); + if(!file.open(IO_ReadOnly)) + return; + + no_save_custom_trackers = true; + QTextStream stream(&file); + while (!stream.atEnd()) + { + KURL url = stream.readLine(); + addTracker(url,true); + } + no_save_custom_trackers = false; + } + + Tracker* PeerSourceManager::selectTracker() + { + Tracker* n = 0; + PtrMap<KURL,Tracker>::iterator i = trackers.begin(); + while (i != trackers.end()) + { + Tracker* t = i->second; + if (!n) + n = t; + else if (t->failureCount() < n->failureCount()) + n = t; + else if (t->failureCount() == n->failureCount()) + n = t->getTier() < n->getTier() ? t : n; + i++; + } + + if (n) + { + Out(SYS_TRK|LOG_DEBUG) << "Selected tracker " << n->trackerURL().prettyURL() + << " (tier = " << n->getTier() << ")" << endl; + } + + return n; + } + + void PeerSourceManager::onTrackerError(const QString & err) + { + failures++; + pending = false; + if (started) + statusChanged(err); + + if (!started) + return; + + // select an other tracker + Tracker* trk = selectTracker(); + + if (!trk) + { + if (curr->failureCount() > 5) + { + // we failed to contact the only tracker 5 times in a row, so try again in + // 30 minutes + curr->setInterval(FINAL_WAIT_TIME); + timer.start(FINAL_WAIT_TIME * 1000,true); + request_time = QDateTime::currentDateTime(); + } + else if (curr->failureCount() > 2) + { + // we failed to contact the only tracker 3 times in a row, so try again in + // a minute or 5, no need for hammering every 30 seconds + curr->setInterval(LONGER_WAIT_TIME); + timer.start(LONGER_WAIT_TIME * 1000,true); + request_time = QDateTime::currentDateTime(); + } + else + { + // lets not hammer and wait 30 seconds + curr->setInterval(INITIAL_WAIT_TIME); + timer.start(INITIAL_WAIT_TIME * 1000,true); + request_time = QDateTime::currentDateTime(); + } + } + else + { + curr->stop(); + // switch to another one + switchTracker(trk); + if (trk->failureCount() == 0) + { + tor->resetTrackerStats(); + curr->start(); + } + else if (trk->failureCount() > 5) + { + curr->setInterval(FINAL_WAIT_TIME); + timer.start(FINAL_WAIT_TIME * 1000,true); + request_time = QDateTime::currentDateTime(); + } + else if (trk->failureCount() > 2) + { + // we tried everybody 3 times and it didn't work + // wait 5 minutes and try again + curr->setInterval(LONGER_WAIT_TIME); + timer.start(LONGER_WAIT_TIME * 1000,true); + request_time = QDateTime::currentDateTime(); + } + else + { + // wait 30 seconds and try again + curr->setInterval(INITIAL_WAIT_TIME); + timer.start(INITIAL_WAIT_TIME * 1000,true); + request_time = QDateTime::currentDateTime(); + } + } + } + + void PeerSourceManager::onTrackerOK() + { + failures = 0; + if (started) + { + timer.start(curr->getInterval() * 1000,true); + curr->scrape(); + } + pending = false; + if (started) + statusChanged(i18n("OK")); + request_time = QDateTime::currentDateTime(); + } + + void PeerSourceManager::onTrackerRequestPending() + { + if (started) + statusChanged(i18n("Announcing")); + pending = true; + } + + void PeerSourceManager::updateCurrentManually() + { + if (!curr) + return; + + if (!curr->isStarted()) + tor->resetTrackerStats(); + + curr->manualUpdate(); + } + + void PeerSourceManager::switchTracker(Tracker* trk) + { + if (curr == trk) + return; + + if (curr) + { + disconnect(curr,SIGNAL(requestFailed( const QString& )), + this,SLOT(onTrackerError( const QString& ))); + disconnect(curr,SIGNAL(requestOK()),this,SLOT(onTrackerOK())); + disconnect(curr,SIGNAL(requestPending()),this,SLOT(onTrackerRequestPending())); + curr = 0; + } + + curr = trk; + if (curr) + { + Out(SYS_TRK|LOG_NOTICE) << "Switching to tracker " << trk->trackerURL() << endl; + QObject::connect(curr,SIGNAL(requestFailed( const QString& )), + this,SLOT(onTrackerError( const QString& ))); + + QObject::connect(curr,SIGNAL(requestOK()), + this,SLOT(onTrackerOK())); + + QObject::connect(curr,SIGNAL(requestPending()), + this,SLOT(onTrackerRequestPending())); + } + } + + Uint32 PeerSourceManager::getTimeToNextUpdate() const + { + if (pending || !started || !curr) + return 0; + + return curr->getInterval() - request_time.secsTo(QDateTime::currentDateTime()); + } + + Uint32 PeerSourceManager::getNumSeeders() const + { + return curr ? curr->getNumSeeders() : 0; + } + + + Uint32 PeerSourceManager::getNumLeechers() const + { + return curr ? curr->getNumLeechers() : 0; + } + + void PeerSourceManager::addDHT() + { + if(m_dht) + { + removePeerSource(m_dht); + delete m_dht; + } + + m_dht = new dht::DHTTrackerBackend(Globals::instance().getDHT(),tor); + + // add the DHT source + addPeerSource(m_dht); + } + + void PeerSourceManager::removeDHT() + { + if(m_dht == 0) + { + removePeerSource(m_dht); + return; + } + + removePeerSource(m_dht); + delete m_dht; + m_dht = 0; + } + + bool PeerSourceManager::dhtStarted() + { + return m_dht != 0; + } + + +} + +#include "peersourcemanager.moc" diff --git a/libktorrent/torrent/peersourcemanager.h b/libktorrent/torrent/peersourcemanager.h new file mode 100644 index 0000000..cdace4e --- /dev/null +++ b/libktorrent/torrent/peersourcemanager.h @@ -0,0 +1,182 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef BTPEERSOURCEMANAGER_H +#define BTPEERSOURCEMANAGER_H + +#include <qtimer.h> +#include <qdatetime.h> +#include <qptrlist.h> +#include <util/ptrmap.h> +#include <interfaces/trackerslist.h> + +namespace kt +{ + class PeerSource; +} + +namespace dht +{ + class DHTTrackerBackend; +} + +namespace bt +{ + class Tracker; + class PeerManager; + class Torrent; + class TorrentControl; + + /** + * @author Joris Guisson <joris.guisson@gmail.com> + * + * This class manages all PeerSources. + */ + class PeerSourceManager : public QObject, public kt::TrackersList + { + Q_OBJECT + + TorrentControl* tor; + PeerManager* pman; + PtrMap<KURL,Tracker> trackers; + QPtrList<kt::PeerSource> additional; + Tracker* curr; + dht::DHTTrackerBackend* m_dht; + bool started; + bool pending; + KURL::List custom_trackers; + QDateTime request_time; + QTimer timer; + Uint32 failures; + bool no_save_custom_trackers; + public: + PeerSourceManager(TorrentControl* tor,PeerManager* pman); + virtual ~PeerSourceManager(); + + + /** + * Add a PeerSource, the difference between PeerSource and Tracker + * is that only one Tracker can be used at the same time, + * PeerSource can always be used. + * @param ps The PeerSource + */ + void addPeerSource(kt::PeerSource* ps); + + /** + * See if the PeerSourceManager has been started + */ + bool isStarted() const {return started;} + + /** + * Start gathering peers + */ + void start(); + + /** + * Stop gathering peers + * @param wjob WaitJob to wait at exit for the completion of stopped events to the trackers + */ + void stop(WaitJob* wjob = 0); + + /** + * Notify peersources and trackrs that the download is complete. + */ + void completed(); + + /** + * Do a manual update on all peer sources and trackers. + */ + void manualUpdate(); + + /** + * Remove a Tracker or PeerSource. + * @param ps + */ + void removePeerSource(kt::PeerSource* ps); + + virtual KURL getTrackerURL() const; + virtual KURL::List getTrackerURLs(); + virtual void addTracker(KURL url, bool custom = true,int tier = 1); + virtual bool removeTracker(KURL url); + virtual void setTracker(KURL url); + virtual void restoreDefault(); + + /** + * Get the time to the next tracker update. + * @return The time in seconds + */ + Uint32 getTimeToNextUpdate() const; + + /// Get the number of potential seeders + Uint32 getNumSeeders() const; + + /// Get the number of potential leechers + Uint32 getNumLeechers() const; + + /// Get the number of failures + Uint32 getNumFailures() const {return failures;} + + ///Adds DHT as PeerSource for this torrent + void addDHT(); + ///Removes DHT from PeerSourceManager for this torrent. + void removeDHT(); + ///Checks if DHT is enabled + bool dhtStarted(); + + private slots: + /** + * The an error happened contacting the tracker. + * @param err The error + */ + void onTrackerError(const QString & err); + + /** + * Tracker update was OK. + * @param + */ + void onTrackerOK(); + + /** + * Tracker is doing a request. + */ + void onTrackerRequestPending(); + + /** + * Update the current tracker manually + */ + void updateCurrentManually(); + + signals: + /** + * Status has changed of the tracker. + * @param ns The new status + */ + void statusChanged(const QString & ns); + + private: + void saveCustomURLs(); + void loadCustomURLs(); + void addTracker(Tracker* trk); + void switchTracker(Tracker* trk); + Tracker* selectTracker(); + }; + +} + +#endif diff --git a/libktorrent/torrent/peeruploader.cpp b/libktorrent/torrent/peeruploader.cpp new file mode 100644 index 0000000..1e0dbca --- /dev/null +++ b/libktorrent/torrent/peeruploader.cpp @@ -0,0 +1,130 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <set> +#include <ksocketaddress.h> +#include <util/log.h> +#include <util/functions.h> +#include <util/sha1hash.h> +#include "peeruploader.h" +#include "peer.h" +#include "chunkmanager.h" +#include "packetwriter.h" +#include "torrent.h" + +using namespace KNetwork; + +namespace bt +{ + + PeerUploader::PeerUploader(Peer* peer) : peer(peer) + { + uploaded = 0; + } + + + PeerUploader::~PeerUploader() + {} + + void PeerUploader::addRequest(const Request & r) + { + // Out(SYS_CON|LOG_DEBUG) << + // QString("PeerUploader::addRequest %1 %2 %3\n").arg(r.getIndex()).arg(r.getOffset()).arg(r.getLength()) << endl; + + // allowed fast chunks go to the front of the queue + requests.append(r); + } + + void PeerUploader::removeRequest(const Request & r) + { + // Out(SYS_CON|LOG_DEBUG) << + // QString("PeerUploader::removeRequest %1 %2 %3\n").arg(r.getIndex()).arg(r.getOffset()).arg(r.getLength()) << endl; + requests.remove(r); + peer->getPacketWriter().doNotSendPiece(r,peer->getStats().fast_extensions); + } + + Uint32 PeerUploader::update(ChunkManager & cman,Uint32 opt_unchoked) + { + Uint32 ret = uploaded; + uploaded = 0; + + PacketWriter & pw = peer->getPacketWriter(); + + // if we have choked the peer do not upload + if (peer->areWeChoked()) + return ret; + + if (peer->isSnubbed() && !peer->areWeChoked() && + !cman.completed() && peer->getID() != opt_unchoked) + return ret; + + + while (requests.count() > 0) + { + Request r = requests.front(); + + Chunk* c = cman.grabChunk(r.getIndex()); + if (c && c->getData()) + { + if (!pw.sendChunk(r.getIndex(),r.getOffset(),r.getLength(),c)) + { + if (peer->getStats().fast_extensions) + pw.sendReject(r); + } + requests.pop_front(); + } + else + { + // remove requests we can't satisfy + Out(SYS_CON|LOG_DEBUG) << "Cannot satisfy request" << endl; + if (peer->getStats().fast_extensions) + pw.sendReject(r); + requests.pop_front(); + } + } + + return ret; + } + + void PeerUploader::clearAllRequests() + { + bool fast_ext = peer->getStats().fast_extensions; + PacketWriter & pw = peer->getPacketWriter(); + pw.clearPieces(fast_ext); + + if (fast_ext) + { + // reject all requests + // if the peer supports fast extensions, + // choke doesn't mean reject all + QValueList<Request>::iterator i = requests.begin(); + while (i != requests.end()) + { + pw.sendReject(*i); + i++; + } + } + requests.clear(); + } + + Uint32 PeerUploader::getNumRequests() const + { + return requests.count() + peer->getPacketWriter().getNumDataPacketsToWrite(); + } +} diff --git a/libktorrent/torrent/peeruploader.h b/libktorrent/torrent/peeruploader.h new file mode 100644 index 0000000..94aea74 --- /dev/null +++ b/libktorrent/torrent/peeruploader.h @@ -0,0 +1,93 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef BTPEERUPLOADER_H +#define BTPEERUPLOADER_H + +#include <set> +#include <qvaluelist.h> +#include "request.h" + + + +namespace bt +{ + class Peer; + class ChunkManager; + + const Uint32 ALLOWED_FAST_SIZE = 8; + + /** + * @author Joris Guisson + * @brief Uploads pieces to a Peer + * + * This class handles the uploading of pieces to a Peer. It keeps + * track of a list of Request objects. All these Requests where sent + * by the Peer. It will upload the pieces to the Peer, making sure + * that the maximum upload rate isn't surpassed. + */ + class PeerUploader + { + Peer* peer; + QValueList<Request> requests; + Uint32 uploaded; + public: + /** + * Constructor. Set the Peer. + * @param peer The Peer + */ + PeerUploader(Peer* peer); + virtual ~PeerUploader(); + + /** + * Add a Request to the list of Requests. + * @param r The Request + */ + void addRequest(const Request & r); + + /** + * Remove a Request from the list of Requests. + * @param r The Request + */ + void removeRequest(const Request & r); + + /** + * Update the PeerUploader. This will check if there are Request, and + * will try to handle them. + * @param cman The ChunkManager + * @param opt_unchoked ID of optimisticly unchoked peer + * @return The number of bytes uploaded + */ + Uint32 update(ChunkManager & cman,Uint32 opt_unchoked); + + /// Get the number of requests + Uint32 getNumRequests() const; + + + void addUploadedBytes(Uint32 bytes) {uploaded += bytes;} + + /** + * Clear all pending requests. + */ + void clearAllRequests(); + }; + +} + +#endif diff --git a/libktorrent/torrent/piece.cpp b/libktorrent/torrent/piece.cpp new file mode 100644 index 0000000..0fff862 --- /dev/null +++ b/libktorrent/torrent/piece.cpp @@ -0,0 +1,34 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 "piece.h" + +namespace bt +{ + + Piece::Piece(Uint32 index, Uint32 off, Uint32 len, Uint32 peer,const Uint8* data) + : Request(index, off, len, peer),data(data) + {} + + + Piece::~Piece() + {} + + +} diff --git a/libktorrent/torrent/piece.h b/libktorrent/torrent/piece.h new file mode 100644 index 0000000..9e749db --- /dev/null +++ b/libktorrent/torrent/piece.h @@ -0,0 +1,44 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef BTPIECE_H +#define BTPIECE_H + +#include "request.h" + +namespace bt +{ + + /** + @author Joris Guisson + */ + class Piece : public Request + { + public: + Piece(Uint32 index, Uint32 off, Uint32 len, Uint32 peer,const Uint8* data); + virtual ~Piece(); + + const Uint8* getData() const {return data;} + private: + const Uint8* data; + }; + +} + +#endif diff --git a/libktorrent/torrent/preallocationthread.cpp b/libktorrent/torrent/preallocationthread.cpp new file mode 100644 index 0000000..6fb2100 --- /dev/null +++ b/libktorrent/torrent/preallocationthread.cpp @@ -0,0 +1,134 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <errno.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/types.h> +#include <sys/stat.h> + +#include <util/log.h> +#include <util/error.h> +#include <qfile.h> +#include <klocale.h> +#include "preallocationthread.h" +#include "chunkmanager.h" +#include "globals.h" + +#ifndef O_LARGEFILE +# define O_LARGEFILE 0 +#endif + +namespace bt +{ + + PreallocationThread::PreallocationThread(ChunkManager* cman) : cman(cman),stopped(false),not_finished(false),done(false) + { + bytes_written = 0; + } + + + PreallocationThread::~PreallocationThread() + {} + + void PreallocationThread::run() + { + try + { + cman->preallocateDiskSpace(this); + } + catch (Error & err) + { + setErrorMsg(err.toString()); + } + + mutex.lock(); + done = true; + mutex.unlock(); + Out(SYS_GEN|LOG_NOTICE) << "PreallocationThread has finished" << endl; + } + + void PreallocationThread::stop() + { + mutex.lock(); + stopped = true; + mutex.unlock(); + } + + void PreallocationThread::setErrorMsg(const QString & msg) + { + mutex.lock(); + error_msg = msg; stopped = true; + mutex.unlock(); + } + + bool PreallocationThread::isStopped() const + { + mutex.lock(); + bool tmp = stopped; + mutex.unlock(); + return tmp; + } + + bool PreallocationThread::errorHappened() const + { + mutex.lock(); + bool ret = !error_msg.isNull(); + mutex.unlock(); + return ret; + } + + void PreallocationThread::written(Uint64 nb) + { + mutex.lock(); + bytes_written += nb; + mutex.unlock(); + } + + Uint64 PreallocationThread::bytesWritten() + { + mutex.lock(); + Uint64 tmp = bytes_written; + mutex.unlock(); + return tmp; + } + + bool PreallocationThread::isDone() const + { + mutex.lock(); + bool tmp = done; + mutex.unlock(); + return tmp; + } + + bool PreallocationThread::isNotFinished() const + { + mutex.lock(); + bool tmp = not_finished; + mutex.unlock(); + return tmp; + } + + void PreallocationThread::setNotFinished() + { + mutex.lock(); + not_finished = true; + mutex.unlock(); + } +} diff --git a/libktorrent/torrent/preallocationthread.h b/libktorrent/torrent/preallocationthread.h new file mode 100644 index 0000000..31bd668 --- /dev/null +++ b/libktorrent/torrent/preallocationthread.h @@ -0,0 +1,94 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef BTPREALLOCATIONTHREAD_H +#define BTPREALLOCATIONTHREAD_H + +#include <qstring.h> +#include <qthread.h> +#include <qmap.h> +#include <qmutex.h> +#include <util/constants.h> + + + +namespace bt +{ + class ChunkManager; + + /** + * @author Joris Guisson <joris.guisson@gmail.com> + * + * Thread to preallocate diskspace + */ + class PreallocationThread : public QThread + { + ChunkManager* cman; + bool stopped,not_finished,done; + QString error_msg; + Uint64 bytes_written; + mutable QMutex mutex; + public: + PreallocationThread(ChunkManager* cman); + virtual ~PreallocationThread(); + + virtual void run(); + + + /** + * Stop the thread. + */ + void stop(); + + /** + * Set an error message, also calls stop + * @param msg The message + */ + void setErrorMsg(const QString & msg); + + /// See if the thread has been stopped + bool isStopped() const; + + /// Did an error occur during the preallocation ? + bool errorHappened() const; + + /// Get the error_msg + const QString & errorMessage() const {return error_msg;} + + /// nb Number of bytes have been written + void written(Uint64 nb); + + /// Get the number of bytes written + Uint64 bytesWritten(); + + /// Allocation was aborted, so the next time the torrent is started it needs to be started again + void setNotFinished(); + + /// See if the allocation hasn't completed yet + bool isNotFinished() const; + + /// See if the thread was done + bool isDone() const; + private: + bool expand(const QString & path,Uint64 max_size); + }; + +} + +#endif diff --git a/libktorrent/torrent/queuemanager.cpp b/libktorrent/torrent/queuemanager.cpp new file mode 100644 index 0000000..4135f8f --- /dev/null +++ b/libktorrent/torrent/queuemanager.cpp @@ -0,0 +1,811 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@gmail.com * + * ivasic@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ +#include "queuemanager.h" + +#include <qstring.h> +#include <kmessagebox.h> +#include <klocale.h> +#include <util/log.h> +#include <util/error.h> +#include <util/sha1hash.h> +#include <util/waitjob.h> +#include <util/fileops.h> +#include <torrent/globals.h> +#include <torrent/torrent.h> +#include <torrent/torrentcontrol.h> +#include <interfaces/torrentinterface.h> +#include <interfaces/trackerslist.h> +#include <settings.h> + + +using namespace kt; + +namespace bt +{ + + QueueManager::QueueManager() : QObject(),exiting(false) + { + downloads.setAutoDelete(true); + max_downloads = 0; + max_seeds = 0; //for testing. Needs to be added to Settings:: + + keep_seeding = true; //test. Will be passed from Core + paused_state = false; + } + + + QueueManager::~QueueManager() + {} + + void QueueManager::append(kt::TorrentInterface* tc) + { + downloads.append(tc); + downloads.sort(); + + connect(tc, SIGNAL(diskSpaceLow(kt::TorrentInterface*, bool)), this, SLOT(onLowDiskSpace(kt::TorrentInterface*, bool))); + connect(tc, SIGNAL(torrentStopped(kt::TorrentInterface*)), this, SLOT(torrentStopped(kt::TorrentInterface*))); + } + + void QueueManager::remove(kt::TorrentInterface* tc) + { + paused_torrents.erase(tc); + + int index = downloads.findRef(tc); + + if (index != -1) + downloads.remove(index); + else + Out(SYS_GEN | LOG_IMPORTANT) << "Could not delete removed torrent control." << endl; + } + + void QueueManager::clear() + { + Uint32 nd = downloads.count(); + + paused_torrents.clear(); + downloads.clear(); + + // wait for a second to allow all http jobs to send the stopped event + if (nd > 0) + SynchronousWait(1000); + } + + kt::TorrentStartResponse QueueManager::start(kt::TorrentInterface* tc, bool user) + { + const TorrentStats & s = tc->getStats(); + + bool start_tc = user; + + bool check_done = false; + + if (tc->isCheckingData(check_done) && !check_done) + return kt::BUSY_WITH_DATA_CHECK; + + if (!user) + { + if (s.completed) + start_tc = (max_seeds == 0 || getNumRunning(false, true) < max_seeds); + else + start_tc = (max_downloads == 0 || getNumRunning(true) < max_downloads); + } + else + { + //User started this torrent so make it user controlled + tc->setPriority(0); + } + + if (start_tc) + { + + if (!s.completed) //no need to check diskspace for seeding torrents + { + //check diskspace + bool shortDiskSpace = !tc->checkDiskSpace(false); + + if (shortDiskSpace) + { + //we're short! + + switch (Settings::startDownloadsOnLowDiskSpace()) + { + + case 0: //don't start! + tc->setPriority(0); + return kt::NOT_ENOUGH_DISKSPACE; + + case 1: //ask user + if (KMessageBox::questionYesNo(0, i18n("You don't have enough disk space to download this torrent. Are you sure you want to continue?"), i18n("Insufficient disk space for %1").arg(s.torrent_name)) == KMessageBox::No) + { + tc->setPriority(0); + return kt::USER_CANCELED; + } + else + break; + + case 2: //force start + break; + } + } + } + + Out(SYS_GEN | LOG_NOTICE) << "Starting download" << endl; + + float ratio = kt::ShareRatio(s); + + float max_ratio = tc->getMaxShareRatio(); + + if (s.completed && max_ratio > 0 && ratio >= max_ratio) + { + if (KMessageBox::questionYesNo(0, i18n("Torrent \"%1\" has reached its maximum share ratio. Ignore the limit and start seeding anyway?").arg(s.torrent_name), i18n("Maximum share ratio limit reached.")) == KMessageBox::Yes) + { + tc->setMaxShareRatio(0.00f); + startSafely(tc); + } + else + return kt::USER_CANCELED; + } + else + startSafely(tc); + } + else + { + return kt::QM_LIMITS_REACHED; + } + + return kt::START_OK; + } + + void QueueManager::stop(kt::TorrentInterface* tc, bool user) + { + bool check_done = false; + + if (tc->isCheckingData(check_done) && !check_done) + return; + + const TorrentStats & s = tc->getStats(); + + if (s.running) + { + stopSafely(tc, user); + } + + if (user) //dequeue it + tc->setPriority(0); + } + + void QueueManager::startall(int type) + { + QPtrList<kt::TorrentInterface>::iterator i = downloads.begin(); + + while (i != downloads.end()) + { + kt::TorrentInterface* tc = *i; + + if (type >= 3) + start(tc, true); + else + { + if ((tc->getStats().completed && type == 2) || (!tc->getStats().completed && type == 1) || (type == 3)) + start(tc, true); + } + + i++; + } + } + + void QueueManager::stopall(int type) + { + QPtrList<kt::TorrentInterface>::iterator i = downloads.begin(); + + while (i != downloads.end()) + { + kt::TorrentInterface* tc = *i; + + const TorrentStats & s = tc->getStats(); + + if (tc->getStats().running) + { + try + { + if (type >= 3) + stopSafely(tc, true); + else if ((s.completed && type == 2) || (!s.completed && type == 1)) + stopSafely(tc, true); + } + catch (bt::Error & err) + { + QString msg = + i18n("Error stopping torrent %1 : %2") + .arg(s.torrent_name).arg(err.toString()); + KMessageBox::error(0, msg, i18n("Error")); + } + } + else //if torrent is not running but it is queued we need to make it user controlled + if ((s.completed && type == 2) || (!s.completed && type == 1) || (type == 3)) + tc->setPriority(0); + + i++; + } + } + + void QueueManager::onExit(WaitJob* wjob) + { + exiting = true; + QPtrList<kt::TorrentInterface>::iterator i = downloads.begin(); + + while (i != downloads.end()) + { + kt::TorrentInterface* tc = *i; + + if (tc->getStats().running) + { + stopSafely(tc, false, wjob); + } + + i++; + } + } + + void QueueManager::startNext() + { + orderQueue(); + } + + int QueueManager::countDownloads() + { + int nr = 0; + QPtrList<TorrentInterface>::const_iterator i = downloads.begin(); + + while (i != downloads.end()) + { + if (!(*i)->getStats().completed) + ++nr; + + ++i; + } + + return nr; + } + + int QueueManager::countSeeds() + { + int nr = 0; + QPtrList<TorrentInterface>::const_iterator i = downloads.begin(); + + while (i != downloads.end()) + { + if ((*i)->getStats().completed) + ++nr; + + ++i; + } + + return nr; + } + + int QueueManager::getNumRunning(bool onlyDownload, bool onlySeed) + { + int nr = 0; + // int test = 1; + QPtrList<TorrentInterface>::const_iterator i = downloads.begin(); + + while (i != downloads.end()) + { + const TorrentInterface* tc = *i; + + const TorrentStats & s = tc->getStats(); + + //Out() << "Torrent " << test++ << s.torrent_name << " priority: " << tc->getPriority() << endl; + if (s.running) + { + if (onlyDownload) + { + if (!s.completed) nr++; + } + else + { + if (onlySeed) + { + if (s.completed) nr++; + } + else + nr++; + } + } + + i++; + } + + // Out() << endl; + return nr; + } + + int QueueManager::getNumRunning(bool userControlled, bool onlyDownloads, bool onlySeeds) + { + int nr = 0; + // int test = 1; + QPtrList<TorrentInterface>::const_iterator i = downloads.begin(); + + while (i != downloads.end()) + { + const TorrentInterface* tc = *i; + + const TorrentStats & s = tc->getStats(); + + //Out() << "Torrent " << test++ << s.torrent_name << " priority: " << tc->getPriority() << endl; + if (s.running) + { + if (onlyDownloads) + { + if (!s.completed && (userControlled && s.user_controlled)) nr++; + } + else + { + if (onlySeeds) + { + if (s.completed && (userControlled && s.user_controlled)) nr++; + } + else + if (userControlled && s.user_controlled) nr++; + } + } + + i++; + } + + // Out() << endl; + return nr; + } + + QPtrList<kt::TorrentInterface>::iterator QueueManager::begin() + { + return downloads.begin(); + } + + QPtrList<kt::TorrentInterface>::iterator QueueManager::end() + { + return downloads.end(); + } + + void QueueManager::setMaxDownloads(int m) + { + max_downloads = m; + } + + void QueueManager::setMaxSeeds(int m) + { + max_seeds = m; + } + + void QueueManager::setKeepSeeding(bool ks) + { + keep_seeding = ks; + } + + bool QueueManager::allreadyLoaded(const SHA1Hash & ih) const + { + QPtrList<kt::TorrentInterface>::const_iterator itr = downloads.begin(); + + while (itr != downloads.end()) + { + const TorrentControl* tor = (const TorrentControl*)(*itr); + + if (tor->getTorrent().getInfoHash() == ih) + return true; + + itr++; + } + + return false; + } + + void QueueManager::mergeAnnounceList(const SHA1Hash & ih, const TrackerTier* trk) + + { + QPtrList<kt::TorrentInterface>::iterator itr = downloads.begin(); + + while (itr != downloads.end()) + { + TorrentControl* tor = (TorrentControl*)(*itr); + + if (tor->getTorrent().getInfoHash() == ih) + { + TrackersList* ta = tor->getTrackersList(); + ta->merge(trk); + return; + } + + itr++; + } + } + + void QueueManager::orderQueue() + { + if (!downloads.count()) + return; + + if (paused_state || exiting) + return; + + downloads.sort(); + + QPtrList<TorrentInterface>::const_iterator it = downloads.begin(); + QPtrList<TorrentInterface>::const_iterator its = downloads.end(); + + + if (max_downloads != 0 || max_seeds != 0) + { + bt::QueuePtrList download_queue; + bt::QueuePtrList seed_queue; + + int user_downloading = 0; + int user_seeding = 0; + + for (; it != downloads.end(); ++it) + { + TorrentInterface* tc = *it; + + const TorrentStats & s = tc->getStats(); + + if (s.running && s.user_controlled) + { + if (!s.completed) + ++user_downloading; + else + ++user_seeding; + } + + if (!s.user_controlled && !tc->isMovingFiles() && !s.stopped_by_error) + { + if (s.completed) + seed_queue.append(tc); + else + download_queue.append(tc); + } + } + + int max_qm_downloads = max_downloads - user_downloading; + + int max_qm_seeds = max_seeds - user_seeding; + + //stop all QM started torrents + + for (Uint32 i = max_qm_downloads; i < download_queue.count() && max_downloads; ++i) + { + TorrentInterface* tc = download_queue.at(i); + + const TorrentStats & s = tc->getStats(); + + if (s.running && !s.user_controlled && !s.completed) + { + Out(SYS_GEN | LOG_DEBUG) << "QM Stopping: " << s.torrent_name << endl; + stop(tc); + } + } + + //stop all QM started torrents + for (Uint32 i = max_qm_seeds; i < seed_queue.count() && max_seeds; ++i) + { + TorrentInterface* tc = seed_queue.at(i); + + const TorrentStats & s = tc->getStats(); + + if (s.running && !s.user_controlled && s.completed) + { + Out(SYS_GEN | LOG_NOTICE) << "QM Stopping: " << s.torrent_name << endl; + stop(tc); + } + } + + //Now start all needed torrents + if (max_downloads == 0) + max_qm_downloads = download_queue.count(); + + if (max_seeds == 0) + max_qm_seeds = seed_queue.count(); + + int counter = 0; + + for (Uint32 i = 0; counter < max_qm_downloads && i < download_queue.count(); ++i) + { + TorrentInterface* tc = download_queue.at(i); + + const TorrentStats & s = tc->getStats(); + + if (!s.running && !s.completed && !s.user_controlled) + { + start(tc, false); + + if (tc->getStats().stopped_by_error) + { + tc->setPriority(0); + continue; + } + } + + ++counter; + } + + counter = 0; + + for (Uint32 i = 0; counter < max_qm_seeds && i < seed_queue.count(); ++i) + { + TorrentInterface* tc = seed_queue.at(i); + + const TorrentStats & s = tc->getStats(); + + if (!s.running && s.completed && !s.user_controlled) + { + start(tc, false); + + if (tc->getStats().stopped_by_error) + { + tc->setPriority(0); + continue; + } + } + + ++counter; + } + } + else + { + //no limits at all + + for (it = downloads.begin(); it != downloads.end(); ++it) + { + TorrentInterface* tc = *it; + + const TorrentStats & s = tc->getStats(); + + if (!s.running && !s.user_controlled && !s.stopped_by_error && !tc->isMovingFiles()) + { + start(tc, false); + if (tc->getStats().stopped_by_error) + tc->setPriority(0); + } + } + } + + } + + void QueueManager::torrentFinished(kt::TorrentInterface* tc) + { + //dequeue this tc + tc->setPriority(0); + //make sure the max_seeds is not reached +// if(max_seeds !=0 && max_seeds < getNumRunning(false,true)) +// tc->stop(true); + + if (keep_seeding) + torrentAdded(tc, false, false); + else + stop(tc,true); + + orderQueue(); + } + + void QueueManager::torrentAdded(kt::TorrentInterface* tc, bool user, bool start_torrent) + { + if (!user) + { + QPtrList<TorrentInterface>::const_iterator it = downloads.begin(); + + while (it != downloads.end()) + { + TorrentInterface* _tc = *it; + int p = _tc->getPriority(); + + if (p == 0) + break; + else + _tc->setPriority(++p); + + ++it; + } + + tc->setPriority(1); + } + else + { + tc->setPriority(0); + if(start_torrent) + start(tc, true); + } + + orderQueue(); + } + + void QueueManager::torrentRemoved(kt::TorrentInterface* tc) + { + remove(tc); + + orderQueue(); + } + + void QueueManager::setPausedState(bool pause) + { + paused_state = pause; + if (!pause) + { + std::set<kt::TorrentInterface*>::iterator it = paused_torrents.begin(); + while (it != paused_torrents.end()) + { + TorrentInterface* tc = *it; + startSafely(tc); + it++; + } + + paused_torrents.clear(); + orderQueue(); + } + else + { + QPtrList<TorrentInterface>::const_iterator it = downloads.begin(); + for (; it != downloads.end(); it++) + { + TorrentInterface* tc = *it; + + const TorrentStats & s = tc->getStats(); + + if (s.running) + { + paused_torrents.insert(tc); + stopSafely(tc, false); + } + } + } + } + + void QueueManager::enqueue(kt::TorrentInterface* tc) + { + //if a seeding torrent reached its maximum share ratio or maximum seed time don't enqueue it... + if (tc->getStats().completed && (tc->overMaxRatio() || tc->overMaxSeedTime())) + { + Out(SYS_GEN | LOG_IMPORTANT) << "Torrent has reached max share ratio or max seed time and cannot be started automatically." << endl; + emit queuingNotPossible(tc); + return; + } + + torrentAdded(tc, false, false); + } + + void QueueManager::dequeue(kt::TorrentInterface* tc) + { + int tp = tc->getPriority(); + bool completed = tc->getStats().completed; + QPtrList<TorrentInterface>::const_iterator it = downloads.begin(); + + while (it != downloads.end()) + { + TorrentInterface* _tc = *it; + bool _completed = _tc->getStats().completed; + + if (tc == _tc || (_completed != completed)) + { + ++it; + continue; + } + + int p = _tc->getPriority(); + + if (p < tp) + break; + else + _tc->setPriority(--p); + + ++it; + } + + tc->setPriority(0); + + orderQueue(); + } + + void QueueManager::queue(kt::TorrentInterface* tc) + { + if (tc->getPriority() == 0) + enqueue(tc); + else + dequeue(tc); + } + + void QueueManager::startSafely(kt::TorrentInterface* tc) + { + try + { + tc->start(); + } + catch (bt::Error & err) + { + const TorrentStats & s = tc->getStats(); + + QString msg = + i18n("Error starting torrent %1 : %2") + .arg(s.torrent_name).arg(err.toString()); + + KMessageBox::error(0, msg, i18n("Error")); + } + } + + void QueueManager::stopSafely(kt::TorrentInterface* tc, bool user, WaitJob* wjob) + { + try + { + tc->stop(user, wjob); + } + catch (bt::Error & err) + { + const TorrentStats & s = tc->getStats(); + + QString msg = + i18n("Error stopping torrent %1 : %2") + .arg(s.torrent_name).arg(err.toString()); + + KMessageBox::error(0, msg, i18n("Error")); + } + } + + void QueueManager::onLowDiskSpace(kt::TorrentInterface* tc, bool toStop) + { + if(toStop) + { + stop(tc, false); + } + + //then emit the signal to inform trayicon to show passive popup + emit lowDiskSpace(tc, toStop); + } + + void QueueManager::torrentStopped(kt::TorrentInterface* tc ) + { + orderQueue(); + } + + ///////////////////////////////////////////////////////////////////////////////////////////// + + + QueuePtrList::QueuePtrList() : QPtrList<kt::TorrentInterface>() + {} + + QueuePtrList::~QueuePtrList() + {} + + int QueuePtrList::compareItems(QPtrCollection::Item item1, QPtrCollection::Item item2) + { + kt::TorrentInterface* tc1 = (kt::TorrentInterface*) item1; + kt::TorrentInterface* tc2 = (kt::TorrentInterface*) item2; + + if (tc1->getPriority() == tc2->getPriority()) + return 0; + + if (tc1->getPriority() == 0 && tc2->getPriority() != 0) + return 1; + else if (tc1->getPriority() != 0 && tc2->getPriority() == 0) + return -1; + + return tc1->getPriority() > tc2->getPriority() ? -1 : 1; + + return 0; + } +} + +#include "queuemanager.moc" diff --git a/libktorrent/torrent/queuemanager.h b/libktorrent/torrent/queuemanager.h new file mode 100644 index 0000000..658c252 --- /dev/null +++ b/libktorrent/torrent/queuemanager.h @@ -0,0 +1,173 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@gmail.com * + * ivasic@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ +#ifndef QUEUEMANAGER_H +#define QUEUEMANAGER_H + +#include <set> +#include <qobject.h> +#include <qptrlist.h> + +#include <interfaces/torrentinterface.h> + +namespace kt +{ + class TrackersList; +} + +namespace bt +{ + class SHA1Hash; + class AnnounceList; + struct TrackerTier; + class WaitJob; + + class QueuePtrList : public QPtrList<kt::TorrentInterface> + { + public: + QueuePtrList(); + virtual ~QueuePtrList(); + + protected: + int compareItems(QPtrCollection::Item item1, QPtrCollection::Item item2); + }; + + /** + * @author Ivan Vasic + * @brief This class contains list of all TorrentControls and is responsible for starting/stopping them + */ + class QueueManager : public QObject + { + Q_OBJECT + + public: + QueueManager(); + virtual ~QueueManager(); + + void append(kt::TorrentInterface* tc); + void remove(kt::TorrentInterface* tc); + void clear(); + + kt::TorrentStartResponse start(kt::TorrentInterface* tc, bool user = true); + void stop(kt::TorrentInterface* tc, bool user = false); + + void stopall(int type); + void startall(int type); + + /** + * Stop all running torrents + * @param wjob WaitJob which waits for stopped events to reach the tracker + */ + void onExit(WaitJob* wjob); + + /** + * Enqueue/Dequeue function. Places a torrent in queue. + * If the torrent is already in queue this will remove it from queue. + * @param tc TorrentControl pointer. + */ + void queue(kt::TorrentInterface* tc); + + int count() { return downloads.count(); } + int countDownloads(); + int countSeeds(); + + int getNumRunning(bool onlyDownload = false, bool onlySeed = false); + int getNumRunning(bool userControlled, bool onlyDownloads, bool onlySeeds); + + void startNext(); + + typedef QPtrList<kt::TorrentInterface>::iterator iterator; + + iterator begin(); + iterator end(); + + /** + * See if we already loaded a torrent. + * @param ih The info hash of a torrent + * @return true if we do, false if we don't + */ + bool allreadyLoaded(const SHA1Hash & ih) const; + + + /** + * Merge announce lists to a torrent + * @param ih The info_hash of the torrent to merge to + * @param trk First tier of trackers + */ + void mergeAnnounceList(const SHA1Hash & ih,const TrackerTier* trk); + + void setMaxDownloads(int m); + void setMaxSeeds(int m); + + void setKeepSeeding(bool ks); + + /** + * Sets global paused state for QueueManager and stopps all running torrents. + * No torrents will be automatically started/stopped with QM. + */ + void setPausedState(bool pause); + + /** + * Places all torrents from downloads in the right order in queue. + * Use this when torrent priorities get changed + */ + void orderQueue(); + + signals: + /** + * User tried to enqueue a torrent that has reached max share ratio. It's not possible. + * Signal should be connected to SysTray slot which shows appropriate KPassivePopup info. + * @param tc The torrent in question. + */ + void queuingNotPossible(kt::TorrentInterface* tc); + + /** + * Diskspace is running low. + * Signal should be connected to SysTray slot which shows appropriate KPassivePopup info. + * @param tc The torrent in question. + */ + void lowDiskSpace(kt::TorrentInterface* tc, bool stopped); + + public slots: + void torrentFinished(kt::TorrentInterface* tc); + void torrentAdded(kt::TorrentInterface* tc, bool user, bool start_torrent); + void torrentRemoved(kt::TorrentInterface* tc); + void torrentStopped(kt::TorrentInterface* tc); + void onLowDiskSpace(kt::TorrentInterface* tc, bool toStop); + + private: + void enqueue(kt::TorrentInterface* tc); + void dequeue(kt::TorrentInterface* tc); + void startSafely(kt::TorrentInterface* tc); + void stopSafely(kt::TorrentInterface* tc,bool user,WaitJob* wjob = 0); + + bt::QueuePtrList downloads; + std::set<kt::TorrentInterface*> paused_torrents; + + int max_downloads; + int max_seeds; + + + bool paused_state; + bool keep_seeding; + bool exiting; + }; +} +#endif diff --git a/libktorrent/torrent/request.cpp b/libktorrent/torrent/request.cpp new file mode 100644 index 0000000..413eb11 --- /dev/null +++ b/libktorrent/torrent/request.cpp @@ -0,0 +1,52 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 "request.h" + +namespace bt +{ + Request::Request() : index(0),off(0),len(0),peer(0) + {} + + Request::Request(Uint32 index,Uint32 off,Uint32 len,Uint32 peer) + : index(index),off(off),len(len),peer(peer) + {} + + Request::Request(const Request & r) + : index(r.index),off(r.off),len(r.len),peer(r.peer) + {} + + Request::~Request() + {} + + + Request & Request::operator = (const Request & r) + { + index = r.index; + off = r.off; + len = r.len; + peer = r.peer; + return *this; + } + + bool operator == (const Request & a,const Request & b) + { + return a.index == b.index && a.len == b.len && a.off == b.off; + } +} diff --git a/libktorrent/torrent/request.h b/libktorrent/torrent/request.h new file mode 100644 index 0000000..aeeff78 --- /dev/null +++ b/libktorrent/torrent/request.h @@ -0,0 +1,96 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef BTREQUEST_H +#define BTREQUEST_H + +#include "globals.h" + + +namespace bt +{ + + /** + * @author Joris Guisson + * @brief Request of a piece sent to other peers + * + * This class keeps track of a request of a piece. + * The Request consists of an index (the index of the chunk), + * offset into the chunk and the length of a piece. + * + * The PeerID of the Peer who sent the request is also kept. + */ + class Request + { + public: + /** + * Constructor, set everything to 0. + */ + Request(); + + /** + * Constructor, set the index, offset,length and peer + * @param index The index of the chunk + * @param off The offset into the chunk + * @param len The length of the piece + * @param peer The ID of the Peer who sent the request + */ + Request(Uint32 index,Uint32 off,Uint32 len,Uint32 peer); + + /** + * Copy constructor. + * @param r Request to copy + */ + Request(const Request & r); + virtual ~Request(); + + /// Get the index of the chunk + Uint32 getIndex() const {return index;} + + /// Get the offset into the chunk + Uint32 getOffset() const {return off;} + + /// Get the length of a the piece + Uint32 getLength() const {return len;} + + /// Get the sending Peer + Uint32 getPeer() const {return peer;} + + /** + * Assignmenth operator. + * @param r The Request to copy + */ + Request & operator = (const Request & r); + + /** + * Compare two requests. Return true if they are the same. + * This only compares the index,offset and length. + * @param a The first request + * @param b The second request + * @return true if they are equal + */ + friend bool operator == (const Request & a,const Request & b); + private: + Uint32 index,off,len; + Uint32 peer; + }; + +} + +#endif diff --git a/libktorrent/torrent/server.cpp b/libktorrent/torrent/server.cpp new file mode 100644 index 0000000..e3d00ae --- /dev/null +++ b/libktorrent/torrent/server.cpp @@ -0,0 +1,200 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <qserversocket.h> +#include <mse/streamsocket.h> +#include <util/sha1hash.h> +#include <util/log.h> +#include <net/portlist.h> +#include <mse/encryptedserverauthenticate.h> +#include "globals.h" +#include "torrent.h" +#include "server.h" +#include "peermanager.h" +#include "serverauthenticate.h" +#include "ipblocklist.h" +#include "authenticationmonitor.h" + + +namespace bt +{ + + class ServerSocket : public QServerSocket + { + Server* srv; + public: + ServerSocket(Server* srv,Uint16 port) : QServerSocket(port),srv(srv) + { + QSocketDevice* sd = socketDevice(); + if (sd) + sd->setAddressReusable(true); + } + + virtual ~ServerSocket() + {} + + virtual void newConnection(int socket) + { + srv->newConnection(socket); + } + }; + + + + + Server::Server(Uint16 port) : sock(0),port(0) + { + changePort(port); + encryption = false; + allow_unencrypted = true; + } + + + Server::~Server() + { + delete sock; + } + + bool Server::isOK() const + { + return sock->ok(); + } + + void Server::changePort(Uint16 p) + { + if (p == port) + return; + + + if (sock && sock->ok()) + Globals::instance().getPortList().removePort(port,net::TCP); + + port = p; + delete sock; + sock = new ServerSocket(this,port); + if (isOK()) + Globals::instance().getPortList().addNewPort(port,net::TCP,true); + } + + void Server::addPeerManager(PeerManager* pman) + { + peer_managers.append(pman); + } + + void Server::removePeerManager(PeerManager* pman) + { + peer_managers.remove(pman); + } + + void Server::newConnection(int socket) + { + mse::StreamSocket* s = new mse::StreamSocket(socket); + if (peer_managers.count() == 0) + { + s->close(); + delete s; + } + else + { + IPBlocklist& ipfilter = IPBlocklist::instance(); + QString IP(s->getRemoteIPAddress()); + if (ipfilter.isBlocked( IP )) + { + delete s; + return; + } + + ServerAuthenticate* auth = 0; + + if (encryption) + auth = new mse::EncryptedServerAuthenticate(s,this); + else + auth = new ServerAuthenticate(s,this); + + AuthenticationMonitor::instance().add(auth); + } + } + + void Server::close() + { + delete sock; + sock= 0; + } + + Uint16 Server::getPortInUse() const + { + return port; + } + + PeerManager* Server::findPeerManager(const SHA1Hash & hash) + { + QPtrList<PeerManager>::iterator i = peer_managers.begin(); + while (i != peer_managers.end()) + { + PeerManager* pm = *i; + if (pm && pm->getTorrent().getInfoHash() == hash) + { + if (!pm->isStarted()) + return 0; + else + return pm; + } + i++; + } + return 0; + } + + bool Server::findInfoHash(const SHA1Hash & skey,SHA1Hash & info_hash) + { + Uint8 buf[24]; + memcpy(buf,"req2",4); + QPtrList<PeerManager>::iterator i = peer_managers.begin(); + while (i != peer_managers.end()) + { + PeerManager* pm = *i; + memcpy(buf+4,pm->getTorrent().getInfoHash().getData(),20); + if (SHA1Hash::generate(buf,24) == skey) + { + info_hash = pm->getTorrent().getInfoHash(); + return true; + } + i++; + } + return false; + } + + void Server::onError(int) + { + } + + + void Server::enableEncryption(bool allow_unencrypted) + { + encryption = true; + this->allow_unencrypted = allow_unencrypted; + } + + void Server::disableEncryption() + { + encryption = false; + } +} + +#include "server.moc" diff --git a/libktorrent/torrent/server.h b/libktorrent/torrent/server.h new file mode 100644 index 0000000..99c06eb --- /dev/null +++ b/libktorrent/torrent/server.h @@ -0,0 +1,125 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef BTSERVER_H +#define BTSERVER_H + +#include <qptrlist.h> +#include <qobject.h> +#include "globals.h" + +namespace bt +{ + class PeerManager; + class ServerAuthenticate; + class SHA1Hash; + class ServerSocket; + + + /** + * @author Joris Guisson + * + * Class which listens for incoming connections. + * Handles authentication and then hands of the new + * connections to a PeerManager. + * + * All PeerManager's should register with this class when they + * are created and should unregister when they are destroyed. + */ + class Server : public QObject + { + Q_OBJECT + + QPtrList<PeerManager> peer_managers; + ServerSocket* sock; + Uint16 port; + bool encryption; + bool allow_unencrypted; + public: + Server(Uint16 port); + virtual ~Server(); + + /// Check if everything is ok (are we successfully listening on the port) + bool isOK() const; + + /** + * Change the port. + * @param port The new port + */ + void changePort(Uint16 port); + + /// Get the port in use + Uint16 getPortInUse() const; + + /** + * Add a PeerManager. + * @param pman The PeerManager + */ + void addPeerManager(PeerManager* pman); + + /** + * Remove a PeerManager. + * @param pman The PeerManager + */ + void removePeerManager(PeerManager* pman); + + /** + * Find the PeerManager given the info_hash of it's torrent. + * @param hash The info_hash + * @return The PeerManager or 0 if one can't be found + */ + PeerManager* findPeerManager(const SHA1Hash & hash); + + /** + * Find the info_hash based on the skey hash. The skey hash is a hash + * of 'req2' followed by the info_hash. This function finds the info_hash + * which matches the skey hash. + * @param skey HASH('req2',info_hash) + * @param info_hash which matches + * @return true If one was found + */ + bool findInfoHash(const SHA1Hash & skey,SHA1Hash & info_hash); + + /** + * Enable encryption. + * @param allow_unencrypted Allow unencrypted connections (if encryption fails) + */ + void enableEncryption(bool allow_unencrypted); + + /** + * Disable encrypted authentication. + */ + void disableEncryption(); + + bool isEncryptionEnabled() const {return encryption;} + bool unencryptedConnectionsAllowed() const {return allow_unencrypted;} + + void close(); + + private slots: + void newConnection(int sock); + void onError(int); + + private: + friend class ServerSocket; + }; + +} + +#endif diff --git a/libktorrent/torrent/serverauthenticate.cpp b/libktorrent/torrent/serverauthenticate.cpp new file mode 100644 index 0000000..479f0ce --- /dev/null +++ b/libktorrent/torrent/serverauthenticate.cpp @@ -0,0 +1,126 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <mse/streamsocket.h> +#include <util/sha1hash.h> +#include <util/log.h> +#include <util/log.h> +#include "globals.h" +#include "server.h" +#include "peermanager.h" +#include "serverauthenticate.h" +#include "peerid.h" +#include "torrent.h" +#include "ipblocklist.h" + + +namespace bt +{ + bool ServerAuthenticate::s_firewalled = true; + + + ServerAuthenticate::ServerAuthenticate(mse::StreamSocket* sock,Server* server) + : AuthenticateBase(sock),server(server) + { + } + + + ServerAuthenticate::~ServerAuthenticate() + { + } + + + void ServerAuthenticate::onFinish(bool succes) + { + Out(SYS_CON|LOG_NOTICE) << "Authentication(S) to " << sock->getRemoteIPAddress() + << " : " << (succes ? "ok" : "failure") << endl; + finished = true; + setFirewalled(false); + + if (!succes) + { + sock->deleteLater(); + sock = 0; + } + + timer.stop(); + } + + void ServerAuthenticate::handshakeRecieved(bool full) + { + Uint8* hs = handshake; + IPBlocklist& ipfilter = IPBlocklist::instance(); + + QString IP = sock->getRemoteIPAddress(); + + if (ipfilter.isBlocked( IP )) + { + onFinish(false); + return; + } + + // try to find a PeerManager which has te right info hash + SHA1Hash rh(hs+28); + PeerManager* pman = server->findPeerManager(rh); + if (!pman) + { + Out(SYS_GEN|LOG_DEBUG) << "Cannot find PeerManager for hash : " << rh.toString() << endl; + onFinish(false); + return; + } + + if (full) + { + // check if we aren't connecting to ourself + char tmp[21]; + tmp[20] = '\0'; + memcpy(tmp,hs+48,20); + PeerID peer_id = PeerID(tmp); + if (pman->getTorrent().getPeerID() == peer_id) + { + Out(SYS_CON|LOG_NOTICE) << "Lets not connect to our self" << endl; + onFinish(false); + return; + } + + // check if we aren't already connected to the client + if (pman->connectedTo(peer_id)) + { + Out(SYS_CON|LOG_NOTICE) << "Already connected to " << peer_id.toString() << endl; + onFinish(false); + return; + } + + + // send handshake and finish off + sendHandshake(rh,pman->getTorrent().getPeerID()); + onFinish(true); + // hand over connection + pman->newConnection(sock,peer_id,supportedExtensions()); + sock = 0; + } + else + { + // if the handshake wasn't fully received just send our handshake + sendHandshake(rh,pman->getTorrent().getPeerID()); + } + } +} + +#include "serverauthenticate.moc" diff --git a/libktorrent/torrent/serverauthenticate.h b/libktorrent/torrent/serverauthenticate.h new file mode 100644 index 0000000..70d3739 --- /dev/null +++ b/libktorrent/torrent/serverauthenticate.h @@ -0,0 +1,72 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef BTSERVERAUTHENTICATE_H +#define BTSERVERAUTHENTICATE_H + +#include "authenticatebase.h" + +namespace bt +{ + class Server; + class SHA1Hash; + class PeerID; + + /** + * @author Joris Guisson + * + * Handles the authentication of incoming connections on the Server. + * Once the authentication is finished, the socket gets handed over + * to the right PeerManager. + */ + class ServerAuthenticate : public AuthenticateBase + { + Q_OBJECT + public: + ServerAuthenticate(mse::StreamSocket* sock,Server* server); + virtual ~ServerAuthenticate(); + + static bool isFirewalled(); + static void setFirewalled(bool Firewalled); + + protected: + void onFinish(bool succes); + void handshakeRecieved(bool full); + + protected: + Server* server; + + private: + static bool s_firewalled; + }; + +} + +inline bool bt::ServerAuthenticate::isFirewalled() +{ + return s_firewalled; +} + +inline void bt::ServerAuthenticate::setFirewalled(bool Firewalled) +{ + s_firewalled = Firewalled; +} + + +#endif diff --git a/libktorrent/torrent/singlefilecache.cpp b/libktorrent/torrent/singlefilecache.cpp new file mode 100644 index 0000000..7d31bef --- /dev/null +++ b/libktorrent/torrent/singlefilecache.cpp @@ -0,0 +1,232 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <klocale.h> +#include <qfileinfo.h> +#include <qstringlist.h> +#include <util/fileops.h> +#include <util/error.h> +#include <util/functions.h> +#include <util/log.h> +#include "torrent.h" +#include "chunk.h" +#include "globals.h" +#include "cachefile.h" +#include "singlefilecache.h" +#include "preallocationthread.h" + + +namespace bt +{ + + SingleFileCache::SingleFileCache(Torrent& tor,const QString & tmpdir,const QString & datadir) + : Cache(tor,tmpdir,datadir),fd(0) + { + cache_file = tmpdir + "cache"; + output_file = QFileInfo(cache_file).readLink(); + } + + + SingleFileCache::~SingleFileCache() + {} + + void SingleFileCache::changeTmpDir(const QString & ndir) + { + Cache::changeTmpDir(ndir); + cache_file = tmpdir + "cache"; + } + + KIO::Job* SingleFileCache::moveDataFiles(const QString & ndir) + { + return KIO::move(KURL::fromPathOrURL(output_file),KURL::fromPathOrURL(ndir)); + } + + void SingleFileCache::moveDataFilesCompleted(KIO::Job* /*job*/) + { + } + + void bt::SingleFileCache::changeOutputPath(const QString & outputpath) + { + bt::Delete(cache_file); + output_file = outputpath; + datadir = output_file.left(output_file.findRev(bt::DirSeparator())); + + bt::SymLink(output_file, cache_file); + } + + bool SingleFileCache::prep(Chunk* c) + { + if (mmap_failures >= 3) + { + // mmap continuously fails, so stop using it + c->allocate(); + c->setStatus(Chunk::BUFFERED); + } + else + { + Uint64 off = c->getIndex() * tor.getChunkSize(); + Uint8* buf = (Uint8*)fd->map(c,off,c->getSize(),CacheFile::RW); + if (!buf) + { + mmap_failures++; + // buffer it if mmapping fails + Out(SYS_GEN|LOG_IMPORTANT) << "Warning : mmap failure, falling back to buffered mode" << endl; + c->allocate(); + c->setStatus(Chunk::BUFFERED); + } + else + { + c->setData(buf,Chunk::MMAPPED); + } + } + return true; + } + + void SingleFileCache::load(Chunk* c) + { + Uint64 off = c->getIndex() * tor.getChunkSize(); + Uint8* buf = 0; + if (mmap_failures >= 3 || !(buf = (Uint8*)fd->map(c,off,c->getSize(),CacheFile::READ))) + { + c->allocate(); + c->setStatus(Chunk::BUFFERED); + fd->read(c->getData(),c->getSize(),off); + if (mmap_failures < 3) + mmap_failures++; + } + else + { + c->setData(buf,Chunk::MMAPPED); + } + } + + void SingleFileCache::save(Chunk* c) + { + // unmap the chunk if it is mapped + if (c->getStatus() == Chunk::MMAPPED) + { + fd->unmap(c->getData(),c->getSize()); + c->clear(); + c->setStatus(Chunk::ON_DISK); + } + else if (c->getStatus() == Chunk::BUFFERED) + { + Uint64 off = c->getIndex() * tor.getChunkSize(); + fd->write(c->getData(),c->getSize(),off); + c->clear(); + c->setStatus(Chunk::ON_DISK); + } + } + + void SingleFileCache::create() + { + QFileInfo fi(cache_file); + if (!fi.exists()) + { + QString out_file = fi.readLink(); + + if (out_file.isNull()) + out_file = datadir + tor.getNameSuggestion(); + + if (!bt::Exists(out_file)) + bt::Touch(out_file); + else + preexisting_files = true; + + if (bt::Exists(cache_file)) + bt::Delete(cache_file); + + bt::SymLink(out_file,cache_file); + output_file = out_file; + } + else + { + QString out_file = fi.readLink(); + if (!bt::Exists(out_file)) + bt::Touch(out_file); + else + preexisting_files = true; + } + } + + void SingleFileCache::close() + { + if (fd) + { + fd->close(); + delete fd; + fd = 0; + } + } + + void SingleFileCache::open() + { + if (fd) + return; + + try + { + fd = new CacheFile(); + fd->open(cache_file,tor.getFileLength()); + } + catch (...) + { + fd->close(); + delete fd; + fd = 0; + throw; + } + } + + void SingleFileCache::preallocateDiskSpace(PreallocationThread* prealloc) + { + if (!fd) + open(); + + if (!prealloc->isStopped()) + fd->preallocate(prealloc); + else + prealloc->setNotFinished(); + } + + bool SingleFileCache::hasMissingFiles(QStringList & sl) + { + QFileInfo fi(cache_file); + if (!fi.exists()) + { + QString out_file = fi.readLink(); + sl.append(fi.readLink()); + return true; + } + return false; + } + + void SingleFileCache::deleteDataFiles() + { + bt::Delete(output_file); + } + + Uint64 SingleFileCache::diskUsage() + { + if (!fd) + open(); + + return fd->diskUsage(); + } +} diff --git a/libktorrent/torrent/singlefilecache.h b/libktorrent/torrent/singlefilecache.h new file mode 100644 index 0000000..faa71b6 --- /dev/null +++ b/libktorrent/torrent/singlefilecache.h @@ -0,0 +1,64 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef BTSINGLEFILECACHE_H +#define BTSINGLEFILECACHE_H + +#include "cache.h" + +namespace bt +{ + class CacheFile; + + + /** + * @author Joris Guisson + * @brief Cache for single file torrents + * + * This class implements Cache for a single file torrent + */ + class SingleFileCache : public Cache + { + QString cache_file; + QString output_file; + CacheFile* fd; + public: + SingleFileCache(Torrent& tor,const QString & tmpdir,const QString & datadir); + virtual ~SingleFileCache(); + + virtual bool prep(Chunk* c); + virtual void load(Chunk* c); + virtual void save(Chunk* c); + virtual void create(); + virtual void close(); + virtual void open(); + virtual void changeTmpDir(const QString & ndir); + virtual KIO::Job* moveDataFiles(const QString & ndir); + virtual void moveDataFilesCompleted(KIO::Job* job); + virtual void changeOutputPath(const QString& outputpath); + virtual QString getOutputPath() const {return output_file;} + virtual void preallocateDiskSpace(PreallocationThread* prealloc); + virtual bool hasMissingFiles(QStringList & sl); + virtual void deleteDataFiles(); + virtual Uint64 diskUsage(); + }; + +} + +#endif diff --git a/libktorrent/torrent/speedestimater.cpp b/libktorrent/torrent/speedestimater.cpp new file mode 100644 index 0000000..f12b5ac --- /dev/null +++ b/libktorrent/torrent/speedestimater.cpp @@ -0,0 +1,105 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <qpair.h> +#include <qvaluelist.h> +#include <util/log.h> +#include <util/timer.h> +#include "speedestimater.h" +#include <util/functions.h> + +namespace bt +{ + class SpeedEstimater::SpeedEstimaterPriv + { + float rate; + QValueList<QPair<Uint32,TimeStamp> > dlrate; + public: + SpeedEstimaterPriv() : rate(0) {} + ~SpeedEstimaterPriv() {} + + void data(Uint32 bytes) + { + dlrate.append(qMakePair(bytes,GetCurrentTime())); + } + + void update() + { + TimeStamp now = GetCurrentTime(); + + Uint32 bytes = 0,oldest = now; + QValueList<QPair<Uint32,TimeStamp> >::iterator i = dlrate.begin(); + while (i != dlrate.end()) + { + QPair<Uint32,TimeStamp> & p = *i; + if (now - p.second > 3000) + { + i = dlrate.erase(i); + } + else + { + if (p.second < oldest) + oldest = p.second; + + bytes += p.first; + i++; + } + } + + Uint32 d = 3000; + + if (bytes == 0) + { + rate = 0; + } + else + { + // Out() << "bytes = " << bytes << " d = " << d << endl; + rate = (float) bytes / (d * 0.001f); + } + } + + float getRate() const {return rate;} + }; + + SpeedEstimater::SpeedEstimater() + { + download_rate = 0; + down = new SpeedEstimaterPriv(); + } + + + SpeedEstimater::~SpeedEstimater() + { + delete down; + } + + + + void SpeedEstimater::onRead(Uint32 bytes) + { + down->data(bytes); + } + + void SpeedEstimater::update() + { + down->update(); + download_rate = down->getRate(); + } +} diff --git a/libktorrent/torrent/speedestimater.h b/libktorrent/torrent/speedestimater.h new file mode 100644 index 0000000..16bbdcc --- /dev/null +++ b/libktorrent/torrent/speedestimater.h @@ -0,0 +1,55 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef BTSPEEDESTIMATER_H +#define BTSPEEDESTIMATER_H + + +#include "globals.h" + +namespace bt +{ + + /** + * @author Joris Guisson + * @brief Estimates download speed + * + * This class estimates the download speed. + */ + class SpeedEstimater + { + class SpeedEstimaterPriv; + public: + SpeedEstimater(); + virtual ~SpeedEstimater(); + + + void onRead(Uint32 bytes); + void update(); + + double downloadRate() const {return download_rate;} + + private: + double download_rate; + SpeedEstimaterPriv* down; + }; + +} + +#endif diff --git a/libktorrent/torrent/statsfile.cpp b/libktorrent/torrent/statsfile.cpp new file mode 100644 index 0000000..2ffd3ae --- /dev/null +++ b/libktorrent/torrent/statsfile.cpp @@ -0,0 +1,120 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 "statsfile.h" + +#include "globals.h" +#include <util/log.h> +#include <util/functions.h> + +#include <qstring.h> +#include <qfile.h> +#include <qtextstream.h> + +namespace bt +{ + + StatsFile::StatsFile(QString filename) + :m_filename(filename) + { + m_file.setName(filename); + readSync(); + } + + StatsFile::~StatsFile() + { + close(); + } + + void StatsFile::close() + { + m_file.close(); + } + + void StatsFile::write(QString key, QString value) + { + m_values.insert(key.stripWhiteSpace(), value.stripWhiteSpace()); + } + + QString StatsFile::readString(QString key) + { + return m_values[key].stripWhiteSpace(); + } + + Uint64 StatsFile::readUint64(QString key) + { + bool ok = true; + Uint64 val = readString(key).toULongLong(&ok); + return val; + } + + int StatsFile::readInt(QString key) + { + bool ok = true; + int val = readString(key).toInt(&ok); + return val; + } + + bool StatsFile::readBoolean(QString key) + { + return (bool) readInt(key); + } + + unsigned long StatsFile::readULong(QString key) + { + bool ok = true; + return readString(key).toULong(&ok); + } + + float bt::StatsFile::readFloat( QString key ) + { + bool ok = true; + return readString(key).toFloat(&ok); + } + + void StatsFile::readSync() + { + if (!m_file.open(IO_ReadOnly)) + return; + + QTextStream in(&m_file); + while (!in.atEnd()) + { + QString line = in.readLine(); + QString tmp = line.left(line.find('=')); + m_values.insert(tmp, line.mid(tmp.length()+1)); + } + close(); + } + + void StatsFile::writeSync() + { + if (!m_file.open(IO_WriteOnly)) + return; + QTextStream out(&m_file); + QMap<QString, QString>::iterator it = m_values.begin(); + while(it!=m_values.end()) + { + out << it.key() << "=" << it.data() << ::endl; + ++it; + } + close(); + } + +} diff --git a/libktorrent/torrent/statsfile.h b/libktorrent/torrent/statsfile.h new file mode 100644 index 0000000..9f7a145 --- /dev/null +++ b/libktorrent/torrent/statsfile.h @@ -0,0 +1,91 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef BTSTATSFILE_H +#define BTSTATSFILE_H + +#include <qstring.h> +#include <qfile.h> +#include <qmap.h> + +#include <util/constants.h> + +namespace bt +{ + + /** + * @brief This class is used for loading/storing torrent stats in a file. + * @author Ivan Vasic <ivasic@gmail.com> + */ + class StatsFile + { + public: + /** + * @brief A constructor. + * Constructs StatsFile object and calls readSync(). + */ + StatsFile(QString filename); + ~StatsFile(); + + ///Closes QFile + void close(); + + /** + * @brief Main read function. + * @return QString value that correspodents to key. + * @param key - QString stats key. + */ + QString readString(QString key); + + Uint64 readUint64(QString key); + bool readBoolean(QString key); + int readInt(QString key); + unsigned long readULong(QString key); + float readFloat(QString key); + + /** + * @brief Writes key and value. + * It only inserts pair of key/value to the m_values. To make changes to file call writeSync(). + * @param key - QString key + * @param value - QString value. + */ + void write(QString key, QString value); + + ///Reads data from stats file to m_values. + void readSync(); + + ///Writes data from m_values to stats file. + void writeSync(); + + /** + * See if there is a key in the stats file + * @param key The key + * @return true if key is in the stats file + */ + bool hasKey(const QString & key) const {return m_values.contains(key);} + + private: + QString m_filename; + QFile m_file; + + QMap<QString, QString> m_values; + }; +} + +#endif diff --git a/libktorrent/torrent/timeestimator.cpp b/libktorrent/torrent/timeestimator.cpp new file mode 100644 index 0000000..7d18300 --- /dev/null +++ b/libktorrent/torrent/timeestimator.cpp @@ -0,0 +1,278 @@ +/*************************************************************************** + * Copyright (C) 2006 by Ivan Vasić * + * 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 <math.h> +#include "timeestimator.h" +#include "torrentcontrol.h" +#include "settings.h" + +#include <torrent/globals.h> +#include <util/log.h> +#include <util/constants.h> + +using namespace kt; + +namespace bt +{ + TimeEstimator::TimeEstimator(TorrentControl* tc) + : m_tc(tc) + { + m_samples = new SampleQueue(20); + m_lastAvg = 0; + m_perc = -1; + + //default is KT algorithm + m_algorithm = (ETAlgorithm) Settings::eta(); + } + + + TimeEstimator::~TimeEstimator() + { + delete m_samples; + } + + Uint32 TimeEstimator::estimate() + { + const TorrentStats& s = m_tc->getStats(); + + // only estimate when we are downloading or stalled + if (!(s.status == kt::DOWNLOADING || s.status == kt::STALLED)) + return (Uint32) - 1; + + //ones without pre-calculations + switch (m_algorithm) + { + + case ETA_CSA: + return estimateCSA(); + + case ETA_GASA: + return estimateGASA(); + + case ETA_KT: + return estimateKT(); + } + + //complicated ones :) + + Uint32 sample = (Uint32) s.download_rate; + //push new sample + m_samples->push(sample); + + + switch (m_algorithm) + { + case ETA_MAVG: + return estimateMAVG(); + + case ETA_WINX: + return estimateWINX(); + + default: + return -1; + } + } + + Uint32 TimeEstimator::estimateCSA() + { + const TorrentStats& s = m_tc->getStats(); + + if (s.download_rate == 0) + return (Uint32) - 1; + + return (int)floor((float)s.bytes_left_to_download / (float)s.download_rate); + } + + Uint32 TimeEstimator::estimateGASA() + { + const TorrentStats& s = m_tc->getStats(); + + if (m_tc->getRunningTimeDL() > 0 && s.bytes_downloaded > 0) + { + double avg_speed = (double) s.bytes_downloaded / (double) m_tc->getRunningTimeDL(); + return (Uint32) floor((double) s.bytes_left_to_download / avg_speed); + } + + return (Uint32) - 1; + } + + Uint32 TimeEstimator::estimateWINX() + { + const TorrentStats& s = m_tc->getStats(); + + if (m_samples->sum() > 0 && m_samples->count() > 0) + return (Uint32) floor((double) s.bytes_left_to_download / ((double) m_samples->sum() / (double) m_samples->count())); + + return (Uint32) - 1; + } + + Uint32 TimeEstimator::estimateMAVG() + { + const TorrentStats& s = m_tc->getStats(); + + if (m_samples->count() > 0) + { + double lavg; + + if (m_lastAvg == 0) + lavg = (Uint32) m_samples->sum() / m_samples->count(); + else + lavg = m_lastAvg - ((double) m_samples->first() / (double) m_samples->count()) + ((double) m_samples->last() / (double) m_samples->count()); + + m_lastAvg = (Uint32) floor(lavg); + + if (lavg > 0) + return (Uint32) floor((double) s.bytes_left_to_download / ((lavg + (m_samples->sum() / m_samples->count())) / 2)); + + return (Uint32) - 1; + } + + return (Uint32) - 1; + } + +} + +bt::SampleQueue::SampleQueue(int max) + : m_size(max), m_count(0) +{ + m_samples = new Uint32[max]; + + for (int i = 0; i < m_size; ++i) + m_samples[i] = 0; + + m_end = -1; + + m_start = 0; +} + +bt::SampleQueue::~ SampleQueue() +{ + delete [] m_samples; +} + +void bt::SampleQueue::push(Uint32 sample) +{ + if (m_count < m_size) + { + //it's not full yet + m_samples[(++m_end) % m_size ] = sample; + m_count++; + + return; + } + + //since it's full I'll just replace the oldest value with new one and update all variables. + m_end = (++m_end) % m_size; + + m_start = (++m_start) % m_size; + + m_samples[m_end] = sample; +} + +Uint32 bt::SampleQueue::first() +{ + return m_samples[m_start]; +} + +Uint32 bt::SampleQueue::last() +{ + return m_samples[m_end]; +} + +bool bt::SampleQueue::isFull() +{ + return m_count >= m_size; +} + +int bt::SampleQueue::count() +{ + return m_count; +} + +Uint32 bt::SampleQueue::sum() +{ + Uint32 s = 0; + + for (int i = 0; i < m_count; ++i) + s += m_samples[i]; + + return s; +} + +void bt::TimeEstimator::setAlgorithm(const ETAlgorithm& theValue) +{ + m_algorithm = theValue; +} + +Uint32 bt::TimeEstimator::estimateKT() +{ + const TorrentStats& s = m_tc->getStats(); + + Uint32 sample = (Uint32) s.download_rate; + + //push new sample + m_samples->push(sample); + + double perc = (double) s.bytes_downloaded / (double) s.total_bytes; + + int percentage = (int)(perc) * 100; + + //calculate percentage increasement + double delta = 1 - 1 / (perc / m_perc); + + //remember last percentage + m_perc = perc; + + + if (s.bytes_downloaded < 1024*1024*100 && sample > 0) // < 100KB + { + m_lastETA = estimateGASA(); + return m_lastETA; + } + + if (percentage >= 99 && sample > 0 && s.bytes_left_to_download <= 10*1024*1024*1024) //1% of a very large torrent could be hundreds of MB so limit it to 10MB + { + + if (!m_samples->isFull()) + { + m_lastETA = estimateWINX(); + + if (m_lastETA == (Uint32) - 1) + m_lastETA = estimateGASA(); + + return m_lastETA; + } + else + { + m_lastETA = (Uint32) - 1; + + if (delta > 0.0001) + m_lastETA = estimateMAVG(); + + if (m_lastETA == (Uint32) - 1) + m_lastETA = estimateGASA(); + } + + return m_lastETA; + } + + m_lastETA = estimateGASA(); + + return m_lastETA; +} diff --git a/libktorrent/torrent/timeestimator.h b/libktorrent/torrent/timeestimator.h new file mode 100644 index 0000000..972e239 --- /dev/null +++ b/libktorrent/torrent/timeestimator.h @@ -0,0 +1,119 @@ +/*************************************************************************** + * Copyright (C) 2006 by Ivan Vasić * + * 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. * + ***************************************************************************/ +#ifndef BTTIMEESTIMATOR_H +#define BTTIMEESTIMATOR_H + +#include <util/constants.h> + +namespace bt +{ + class TorrentControl; + + /** + * Simple queue class for samples. Optimized for speed and size + * without posibility to dynamically resize itself. + * @author Ivan Vasic <ivasic@gmail.com> + */ + class SampleQueue + { + public: + SampleQueue(int max); + ~SampleQueue(); + + /** + * Inserts new sample into the queue. The oldest sample is overwritten. + */ + void push(Uint32 sample); + + Uint32 first(); + Uint32 last(); + + bool isFull(); + + /** + * This function will return the number of samples in queue until it counts m_size number of elements. + * After this point it will always return m_size since no samples are being deleted. + */ + int count(); + + /** + * Returns the sum of all samples. + */ + Uint32 sum(); + + private: + int m_size; + int m_count; + + int m_start; + int m_end; + + Uint32* m_samples; + }; + + /** + * ETA estimator class. It will use different algorithms for different download phases. + * @author Ivan Vasic <ivasic@gmail.com> + */ + class TimeEstimator + { + public: + + enum ETAlgorithm + { + ETA_KT, //ktorrent default algorithm - combination of the following according to our tests + ETA_CSA, //current speed algorithm + ETA_GASA, //global average speed algorithm + ETA_WINX, //window of X algorithm + ETA_MAVG //moving average algorithm + }; + + TimeEstimator(TorrentControl* tc); + ~TimeEstimator(); + + ///Returns ETA for m_tc torrent. + Uint32 estimate(); + + void setAlgorithm(const ETAlgorithm& theValue); + ETAlgorithm algorithm() const { return m_algorithm; } + + private: + + Uint32 estimateCSA(); + Uint32 estimateGASA(); + Uint32 estimateWINX(); + Uint32 estimateMAVG(); + Uint32 estimateKT(); + + TorrentControl* m_tc; + SampleQueue* m_samples; + + Uint32 m_lastAvg; + Uint32 m_lastETA; + + //last percentage + double m_perc; + + ETAlgorithm m_algorithm; + }; + +} + +#endif diff --git a/libktorrent/torrent/torrent.cpp b/libktorrent/torrent/torrent.cpp new file mode 100644 index 0000000..6b8739b --- /dev/null +++ b/libktorrent/torrent/torrent.cpp @@ -0,0 +1,449 @@ +/*************************************************************************** + * 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 <qfile.h> +#include <qdatastream.h> +#include <qstringlist.h> +#include <util/log.h> +#include <util/functions.h> +#include <util/error.h> +#include <util/sha1hashgen.h> +#include <time.h> +#include <stdlib.h> +#include "torrent.h" +#include "bdecoder.h" +#include "bnode.h" +#include "announcelist.h" + +#include <klocale.h> + +namespace bt +{ + + Torrent::Torrent() : piece_length(0),file_length(0),priv_torrent(false) + { + encoding = "utf8"; + trackers = 0; + } + + + Torrent::~Torrent() + { + delete trackers; + } + + + void Torrent::load(const QByteArray & data,bool verbose) + { + BNode* node = 0; + + try + { + BDecoder decoder(data,verbose); + node = decoder.decode(); + BDictNode* dict = dynamic_cast<BDictNode*>(node); + if (!dict) + throw Error(i18n("Corrupted torrent!")); + + // see if we can find an encoding node + BValueNode* enc = dict->getValue("encoding"); + if (enc) + { + encoding = enc->data().toString(); + Out() << "Encoding : " << encoding << endl; + } + + BValueNode* announce = dict->getValue("announce"); + BListNode* nodes = dict->getList("nodes"); + if (!announce && !nodes) + throw Error(i18n("Torrent has no announce or nodes field")); + + if (announce) + loadTrackerURL(announce); + + if (nodes) // DHT torrrents have a node key + loadNodes(nodes); + + loadInfo(dict->getDict("info")); + loadAnnounceList(dict->getData("announce-list")); + BNode* n = dict->getData("info"); + SHA1HashGen hg; + Uint8* info = (Uint8*)data.data(); + info_hash = hg.generate(info + n->getOffset(),n->getLength()); + delete node; + } + catch (...) + { + delete node; + throw; + } + } + + void Torrent::load(const QString & file,bool verbose) + { + QFile fptr(file); + if (!fptr.open(IO_ReadOnly)) + throw Error(i18n(" Unable to open torrent file %1 : %2") + .arg(file).arg(fptr.errorString())); + + QByteArray data(fptr.size()); + // Out() << "File size = " << fptr.size() << endl; + fptr.readBlock(data.data(),fptr.size()); + + load(data,verbose); + } + + void Torrent::loadInfo(BDictNode* dict) + { + if (!dict) + throw Error(i18n("Corrupted torrent!")); + + loadPieceLength(dict->getValue("piece length")); + BValueNode* n = dict->getValue("length"); + if (n) + loadFileLength(n); + else + loadFiles(dict->getList("files")); + + loadHash(dict->getValue("pieces")); + loadName(dict->getValue("name")); + n = dict->getValue("private"); + if (n && n->data().toInt() == 1) + priv_torrent = true; + + // do a safety check to see if the number of hashes matches the file_length + Uint32 num_chunks = (file_length / this->piece_length); + if (file_length % piece_length > 0) + num_chunks++; + + if (num_chunks != hash_pieces.count()) + { + Out(SYS_GEN|LOG_DEBUG) << "File sizes and number of hashes do not match for " << name_suggestion << endl; + throw Error(i18n("Corrupted torrent!")); + } + } + + void Torrent::loadFiles(BListNode* node) + { + Out() << "Multi file torrent" << endl; + if (!node) + throw Error(i18n("Corrupted torrent!")); + Uint32 idx = 0; + BListNode* fl = node; + for (Uint32 i = 0;i < fl->getNumChildren();i++) + { + BDictNode* d = fl->getDict(i); + if (!d) + throw Error(i18n("Corrupted torrent!")); + + BListNode* ln = d->getList("path"); + if (!ln) + throw Error(i18n("Corrupted torrent!")); + + QString path; + for (Uint32 j = 0;j < ln->getNumChildren();j++) + { + BValueNode* v = ln->getValue(j); + if (!v || v->data().getType() != Value::STRING) + throw Error(i18n("Corrupted torrent!")); + + QString sd = v->data().toString(encoding); + path += sd; + if (j + 1 < ln->getNumChildren()) + path += bt::DirSeparator(); + } + + // we do not want empty dirs + if (path.endsWith(bt::DirSeparator())) + continue; + + if (!checkPathForDirectoryTraversal(path)) + throw Error(i18n("Corrupted torrent!")); + + BValueNode* v = d->getValue("length"); + if (!v) + throw Error(i18n("Corrupted torrent!")); + + if (v->data().getType() == Value::INT || v->data().getType() == Value::INT64) + { + Uint64 s = v->data().toInt64(); + TorrentFile file(idx,path,file_length,s,piece_length); + + // update file_length + file_length += s; + files.append(file); + } + else + { + throw Error(i18n("Corrupted torrent!")); + } + idx++; + } + } + + void Torrent::loadTrackerURL(BValueNode* node) + { + if (!node || node->data().getType() != Value::STRING) + throw Error(i18n("Corrupted torrent!")); + + // tracker_urls.append(KURL(node->data().toString(encoding).stripWhiteSpace())); + if (!trackers) + trackers = new TrackerTier(); + + trackers->urls.append(KURL(node->data().toString(encoding).stripWhiteSpace())); + } + + void Torrent::loadPieceLength(BValueNode* node) + { + if (!node) + throw Error(i18n("Corrupted torrent!")); + + if (node->data().getType() == Value::INT) + piece_length = node->data().toInt(); + else if (node->data().getType() == Value::INT64) + piece_length = node->data().toInt64(); + else + throw Error(i18n("Corrupted torrent!")); + } + + void Torrent::loadFileLength(BValueNode* node) + { + if (!node) + throw Error(i18n("Corrupted torrent!")); + + if (node->data().getType() == Value::INT) + file_length = node->data().toInt(); + else if (node->data().getType() == Value::INT64) + file_length = node->data().toInt64(); + else + throw Error(i18n("Corrupted torrent!")); + } + + void Torrent::loadHash(BValueNode* node) + { + if (!node || node->data().getType() != Value::STRING) + throw Error(i18n("Corrupted torrent!")); + + + QByteArray hash_string = node->data().toByteArray(); + for (unsigned int i = 0;i < hash_string.size();i+=20) + { + Uint8 h[20]; + memcpy(h,hash_string.data()+i,20); + SHA1Hash hash(h); + hash_pieces.append(hash); + } + } + + void Torrent::loadName(BValueNode* node) + { + if (!node || node->data().getType() != Value::STRING) + throw Error(i18n("Corrupted torrent!")); + + name_suggestion = node->data().toString(encoding); + } + + void Torrent::loadAnnounceList(BNode* node) + { + if (!node) + return; + + BListNode* ml = dynamic_cast<BListNode*>(node); + if (!ml) + return; + + if (!trackers) + trackers = new TrackerTier(); + + TrackerTier* tier = trackers; + //ml->printDebugInfo(); + for (Uint32 i = 0;i < ml->getNumChildren();i++) + { + BListNode* url = dynamic_cast<BListNode*>(ml->getChild(i)); + if (!url) + throw Error(i18n("Parse Error")); + + for (Uint32 j = 0;j < url->getNumChildren();j++) + { + BValueNode* vn = dynamic_cast<BValueNode*>(url->getChild(j)); + if (!vn) + throw Error(i18n("Parse Error")); + + KURL url(vn->data().toString().stripWhiteSpace()); + tier->urls.append(url); + //Out() << "Added tracker " << url << endl; + } + tier->next = new TrackerTier(); + tier = tier->next; + } + } + + void Torrent::loadNodes(BListNode* node) + { + for (Uint32 i = 0;i < node->getNumChildren();i++) + { + BListNode* c = node->getList(i); + if (!c || c->getNumChildren() != 2) + throw Error(i18n("Corrupted torrent!")); + + // first child is the IP, second the port + BValueNode* ip = c->getValue(0); + BValueNode* port = c->getValue(1); + if (!ip || !port) + throw Error(i18n("Corrupted torrent!")); + + if (ip->data().getType() != Value::STRING) + throw Error(i18n("Corrupted torrent!")); + + if (port->data().getType() != Value::INT) + throw Error(i18n("Corrupted torrent!")); + + // add the DHT node + kt::DHTNode n; + n.ip = ip->data().toString(); + n.port = port->data().toInt(); + nodes.append(n); + } + } + + void Torrent::debugPrintInfo() + { + Out() << "Name : " << name_suggestion << endl; + +// for (KURL::List::iterator i = tracker_urls.begin();i != tracker_urls.end();i++) +// Out() << "Tracker URL : " << *i << endl; + + Out() << "Piece Length : " << piece_length << endl; + if (this->isMultiFile()) + { + Out() << "Files : " << endl; + Out() << "===================================" << endl; + for (Uint32 i = 0;i < getNumFiles();i++) + { + TorrentFile & tf = getFile(i); + Out() << "Path : " << tf.getPath() << endl; + Out() << "Size : " << tf.getSize() << endl; + Out() << "First Chunk : " << tf.getFirstChunk() << endl; + Out() << "Last Chunk : " << tf.getLastChunk() << endl; + Out() << "First Chunk Off : " << tf.getFirstChunkOffset() << endl; + Out() << "Last Chunk Size : " << tf.getLastChunkSize() << endl; + Out() << "===================================" << endl; + } + } + else + { + Out() << "File Length : " << file_length << endl; + } + Out() << "Pieces : " << hash_pieces.size() << endl; + } + + bool Torrent::verifyHash(const SHA1Hash & h,Uint32 index) + { + if (index >= hash_pieces.count()) + return false; + + const SHA1Hash & ph = hash_pieces[index]; + return ph == h; + } + + const SHA1Hash & Torrent::getHash(Uint32 idx) const + { + if (idx >= hash_pieces.count()) + throw Error(QString("Torrent::getHash %1 is out of bounds").arg(idx)); + + return hash_pieces[idx]; + } + + TorrentFile & Torrent::getFile(Uint32 idx) + { + if (idx >= files.size()) + return TorrentFile::null; + + return files.at(idx); + } + + const TorrentFile & Torrent::getFile(Uint32 idx) const + { + if (idx >= files.size()) + return TorrentFile::null; + + return files.at(idx); + } + + unsigned int Torrent::getNumTrackerURLs() const + { + Uint32 count = 0; + TrackerTier* tt = trackers; + while (tt) + { + count += tt->urls.count(); + tt = tt->next; + } + return count; + } + + void Torrent::calcChunkPos(Uint32 chunk,QValueList<Uint32> & file_list) const + { + file_list.clear(); + if (chunk >= hash_pieces.size() || files.empty()) + return; + + for (Uint32 i = 0;i < files.count();i++) + { + const TorrentFile & f = files[i]; + if (chunk >= f.getFirstChunk() && chunk <= f.getLastChunk() && f.getSize() != 0) + file_list.append(f.getIndex()); + } + } + + bool Torrent::isMultimedia() const + { + return IsMultimediaFile(this->getNameSuggestion()); + } + + void Torrent::updateFilePercentage(const BitSet & bs) + { + for (Uint32 i = 0;i < files.count();i++) + { + TorrentFile & f = files[i]; + f.updateNumDownloadedChunks(bs); + } + } + + void Torrent::updateFilePercentage(Uint32 chunk,const BitSet & bs) + { + QValueList<Uint32> cfiles; + calcChunkPos(chunk,cfiles); + + QValueList<Uint32>::iterator i = cfiles.begin(); + while (i != cfiles.end()) + { + TorrentFile & f = getFile(*i); + f.updateNumDownloadedChunks(bs); + i++; + } + } + + bool Torrent::checkPathForDirectoryTraversal(const QString & p) + { + QStringList sl = QStringList::split(bt::DirSeparator(),p); + return !sl.contains(".."); + } +} diff --git a/libktorrent/torrent/torrent.h b/libktorrent/torrent/torrent.h new file mode 100644 index 0000000..04c45cd --- /dev/null +++ b/libktorrent/torrent/torrent.h @@ -0,0 +1,218 @@ +/*************************************************************************** + * 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. * + ***************************************************************************/ +#ifndef BTTORRENT_H +#define BTTORRENT_H + +#include <kurl.h> +#include <qvaluevector.h> +#include <util/sha1hash.h> +#include <util/constants.h> +#include <interfaces/torrentinterface.h> +#include "globals.h" +#include "peerid.h" +#include "torrentfile.h" + + + +namespace bt +{ + class BNode; + class BValueNode; + class BDictNode; + class BListNode; + + + struct TrackerTier + { + KURL::List urls; + TrackerTier* next; + + TrackerTier() : next(0) + {} + + ~TrackerTier() + { + delete next; + } + }; + + + /** + * @author Joris Guisson + * @brief Loads a .torrent file + * + * Loads a torrent file and calculates some miscelanious other data, + * like the info_hash and the peer_id. + */ + class Torrent + { + public: + Torrent(); + virtual ~Torrent(); + + /** + * Load a .torrent file. + * @param file The file + * @param verbose Wether to print information to the log + * @throw Error if something goes wrong + */ + void load(const QString & file,bool verbose); + + /** + * Load a .torrent file. + * @param data The data + * @param verbose Wether to print information to the log + * @throw Error if something goes wrong + */ + void load(const QByteArray & data,bool verbose); + + void debugPrintInfo(); + + /// Get the number of chunks. + Uint32 getNumChunks() const {return hash_pieces.size();} + + /// Get the size of a chunk. + Uint64 getChunkSize() const {return piece_length;} + + /// Get the info_hash. + const SHA1Hash & getInfoHash() const {return info_hash;} + + /// Get our peer_id. + const PeerID & getPeerID() const {return peer_id;} + + /// Get the file size in number of bytes. + Uint64 getFileLength() const {return file_length;} + + /// Get the suggested name. + QString getNameSuggestion() const {return name_suggestion;} + + /** + * Verify wether a hash matches the hash + * of a Chunk + * @param h The hash + * @param index The index of the chunk + * @return true if they match + */ + bool verifyHash(const SHA1Hash & h,Uint32 index); + + /// Get the number of tracker URL's + unsigned int getNumTrackerURLs() const; + + /** + * Get the hash of a Chunk. Throws an Error + * if idx is out of bounds. + * @param idx Index of Chunk + * @return The SHA1 hash of the chunk + */ + const SHA1Hash & getHash(Uint32 idx) const; + + /// See if we have a multi file torrent. + bool isMultiFile() const {return files.count() > 0;} + + /// Get the number of files in a multi file torrent. + /// If we have a single file torrent, this will return 0. + Uint32 getNumFiles() const {return files.count();} + + /** + * Get a TorrentFile. If the index is out of range, or + * we have a single file torrent we return a null TorrentFile. + * @param idx Index of the file + * @param A reference to the file + */ + TorrentFile & getFile(Uint32 idx); + + /** + * Get a TorrentFile. If the index is out of range, or + * we have a single file torrent we return a null TorrentFile. + * @param idx Index of the file + * @param A reference to the file + */ + const TorrentFile & getFile(Uint32 idx) const; + + /** + * Calculate in which file(s) a Chunk lies. A list will + * get filled with the indices of all the files. The list gets cleared at + * the beginning. If something is wrong only the list will + * get cleared. + * @param chunk The index of the chunk + * @param file_list This list will be filled with all the indices + */ + void calcChunkPos(Uint32 chunk,QValueList<Uint32> & file_list) const; + + /** + * Checks if torrent file is audio or video. + **/ + bool isMultimedia() const; + + /// See if the torrent is private + bool isPrivate() const {return priv_torrent;} + + ///Gets a pointer to AnnounceList + const TrackerTier* getTrackerList() const { return trackers; } + + /// Get the number of initial DHT nodes + Uint32 getNumDHTNodes() const {return nodes.count();} + + /// Get a DHT node + const kt::DHTNode & getDHTNode(Uint32 i) {return nodes[i];} + + /** + * Update the percentage of all files. + * @param bs The BitSet with all downloaded chunks + */ + void updateFilePercentage(const BitSet & bs); + + /** + * Update the percentage of a all files which have a particular chunk. + * @param bs The BitSet with all downloaded chunks + */ + void updateFilePercentage(Uint32 chunk,const BitSet & bs); + + + private: + void loadInfo(BDictNode* node); + void loadTrackerURL(BValueNode* node); + void loadPieceLength(BValueNode* node); + void loadFileLength(BValueNode* node); + void loadHash(BValueNode* node); + void loadName(BValueNode* node); + void loadFiles(BListNode* node); + void loadNodes(BListNode* node); + void loadAnnounceList(BNode* node); + bool checkPathForDirectoryTraversal(const QString & p); + + private: + TrackerTier* trackers; + QString name_suggestion; + Uint64 piece_length; + Uint64 file_length; + SHA1Hash info_hash; + PeerID peer_id; + QValueVector<SHA1Hash> hash_pieces; + QValueVector<TorrentFile> files; + QValueVector<kt::DHTNode> nodes; + QString encoding; + bool priv_torrent; + }; + +} + +#endif diff --git a/libktorrent/torrent/torrentcontrol.cpp b/libktorrent/torrent/torrentcontrol.cpp new file mode 100644 index 0000000..71b4e64 --- /dev/null +++ b/libktorrent/torrent/torrentcontrol.cpp @@ -0,0 +1,1770 @@ +/*************************************************************************** + * 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" diff --git a/libktorrent/torrent/torrentcontrol.h b/libktorrent/torrent/torrentcontrol.h new file mode 100644 index 0000000..33610de --- /dev/null +++ b/libktorrent/torrent/torrentcontrol.h @@ -0,0 +1,394 @@ +/*************************************************************************** + * 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. * + ***************************************************************************/ +#ifndef BTTORRENTCONTROL_H +#define BTTORRENTCONTROL_H + +#include <qobject.h> +#include <qcstring.h> +#include <qtimer.h> +#include <kurl.h> +#include "globals.h" +#include <util/timer.h> +#include <interfaces/torrentinterface.h> +#include <interfaces/monitorinterface.h> +#include <interfaces/trackerslist.h> + +class QStringList; +class QString; + +namespace KIO +{ + class Job; +} + + +namespace bt +{ + class Choker; + class Torrent; + class PeerSourceManager; + class ChunkManager; + class PeerManager; + class Downloader; + class Uploader; + class Peer; + class BitSet; + class QueueManager; + class PreallocationThread; + class TimeEstimator; + class DataCheckerThread; + class WaitJob; + + /** + * @author Joris Guisson + * @brief Controls just about everything + * + * This is the interface which any user gets to deal with. + * This class controls the uploading, downloading, choking, + * updating the tracker and chunk management. + */ + class TorrentControl : public kt::TorrentInterface + { + Q_OBJECT + public: + TorrentControl(); + virtual ~TorrentControl(); + + /** + * Get a BitSet of the status of all Chunks + */ + const BitSet & downloadedChunksBitSet() const; + + /** + * Get a BitSet of the availability of all Chunks + */ + const BitSet & availableChunksBitSet() const; + + /** + * Get a BitSet of the excluded Chunks + */ + const BitSet & excludedChunksBitSet() const; + + /** + * Get a BitSet of the only seed chunks + */ + const BitSet & onlySeedChunksBitSet() const; + + /** + * Initialize the TorrentControl. + * @param qman The QueueManager + * @param torrent The filename of the torrent file + * @param tmpdir The directory to store temporary data + * @param datadir The directory to store the actual file(s) + * (only used the first time we load a torrent) + * @param default_save_dir Default save directory (null if not set) + * @throw Error when something goes wrong + */ + void init(QueueManager* qman, + const QString & torrent, + const QString & tmpdir, + const QString & datadir, + const QString & default_save_dir); + + /** + * Initialize the TorrentControl. + * @param qman The QueueManager + * @param data The data of the torrent + * @param tmpdir The directory to store temporary data + * @param datadir The directory to store the actual file(s) + * (only used the first time we load a torrent) + * @param default_save_dir Default save directory (null if not set) + * @throw Error when something goes wrong + */ + void init(QueueManager* qman, + const QByteArray & data, + const QString & tmpdir, + const QString & datadir, + const QString & default_save_dir); + + /** + * Change to a new data dir. If this fails + * we will fall back on the old directory. + * @param new_dir The new directory + * @return true upon succes + */ + bool changeDataDir(const QString & new_dir); + + + /** + * Change torrents output directory. If this fails we will fall back on the old directory. + * @param new_dir The new directory + * @param moveFiles Wheather to actually move the files or just change the directory without moving them. + * @return true upon success. + */ + bool changeOutputDir(const QString& new_dir, bool moveFiles = true); + + /** + * Roll back the previous changeDataDir call. + * Does nothing if there was no previous changeDataDir call. + */ + void rollback(); + + /// Gets the TrackersList interface + kt::TrackersList* getTrackersList(); + + /// Gets the TrackersList interface + const kt::TrackersList* getTrackersList() const; + + /// Get the data directory of this torrent + QString getDataDir() const {return outputdir;} + + /// Get the torX dir. + QString getTorDir() const {return datadir;} + + /// Set the monitor + void setMonitor(kt::MonitorInterface* tmo); + + /// Get the Torrent. + const Torrent & getTorrent() const {return *tor;} + + /** + * Get the download running time of this torrent in seconds + * @return Uint32 - time in seconds + */ + Uint32 getRunningTimeDL() const; + + /** + * Get the upload running time of this torrent in seconds + * @return Uint32 - time in seconds + */ + Uint32 getRunningTimeUL() const; + + /** + * Checks if torrent is multimedial and chunks needed for preview are downloaded + * @param start_chunk The index of starting chunk to check + * @param end_chunk The index of the last chunk to check + * In case of single torrent file defaults can be used (0,1) + **/ + bool readyForPreview(int start_chunk = 0, int end_chunk = 1); + + /// Get the time to the next tracker update in seconds. + Uint32 getTimeToNextTrackerUpdate() const; + + /// Get a short error message + QString getShortErrorMessage() const {return error_msg;} + + virtual Uint32 getNumFiles() const; + virtual kt::TorrentFileInterface & getTorrentFile(Uint32 index); + virtual void recreateMissingFiles(); + virtual void dndMissingFiles(); + virtual void addPeerSource(kt::PeerSource* ps); + virtual void removePeerSource(kt::PeerSource* ps); + + int getPriority() const { return istats.priority; } + void setPriority(int p); + + virtual bool overMaxRatio(); + virtual void setMaxShareRatio(float ratio); + virtual float getMaxShareRatio() const { return stats.max_share_ratio; } + + virtual bool overMaxSeedTime(); + virtual void setMaxSeedTime(float hours); + virtual float getMaxSeedTime() const {return stats.max_seed_time;} + + /// Tell the TorrentControl obj to preallocate diskspace in the next update + void setPreallocateDiskSpace(bool pa) {prealloc = pa;} + + /// Make a string out of the status message + virtual QString statusToString() const; + + /// Checks if tracker announce is allowed (minimum interval 60 seconds) + bool announceAllowed(); + + void startDataCheck(bt::DataCheckerListener* lst,bool auto_import); + + /// Test if the torrent has existing files, only works the first time a torrent is loaded + bool hasExistingFiles() const; + + /** + * Test all files and see if they are not missing. + * If so put them in a list + */ + bool hasMissingFiles(QStringList & sl); + + + virtual Uint32 getNumDHTNodes() const; + virtual const kt::DHTNode & getDHTNode(Uint32 i) const; + virtual void deleteDataFiles(); + virtual const SHA1Hash & getInfoHash() const; + virtual const bt::PeerID & getOwnPeerID() const; + + /** + * Called by the PeerSourceManager when it is going to start a new tracker. + */ + void resetTrackerStats(); + + /** + * Returns estimated time left for finishing download. Returned value is in seconds. + * Uses TimeEstimator class to calculate this value. + */ + Uint32 getETA(); + + /// Is a feature enabled + bool isFeatureEnabled(kt::TorrentFeature tf); + + /// Disable or enable a feature + void setFeatureEnabled(kt::TorrentFeature tf,bool on); + + /// Create all the necessary files + void createFiles(); + + ///Checks if diskspace is low + bool checkDiskSpace(bool emit_sig = true); + + virtual void setTrafficLimits(Uint32 up,Uint32 down); + virtual void getTrafficLimits(Uint32 & up,Uint32 & down); + + ///Get the PeerManager + const PeerManager * getPeerMgr() const; + + /// Are we in the process of moving files + bool isMovingFiles() const {return moving_files;} + + public slots: + /** + * Update the object, should be called periodically. + */ + void update(); + + /** + * Start the download of the torrent. + */ + void start(); + + /** + * Stop the download, closes all connections. + * @param user wether or not the user did this explicitly + * @param wjob WaitJob to wait at exit for the completion of stopped requests + */ + void stop(bool user,WaitJob* wjob = 0); + + /** + * Update the tracker, this should normally handled internally. + * We leave it public so that the user can do a manual announce. + */ + void updateTracker(); + + /** + * The tracker status has changed. + * @param ns New status + */ + void trackerStatusChanged(const QString & ns); + + private slots: + void onNewPeer(Peer* p); + void onPeerRemoved(Peer* p); + void doChoking(); + void onIOError(const QString & msg); + void onPortPacket(const QString & ip,Uint16 port); + /// Update the stats of the torrent. + void updateStats(); + void corrupted(Uint32 chunk); + void moveDataFilesJobDone(KIO::Job* job); + + private: + void updateTracker(const QString & ev,bool last_succes = true); + void updateStatusMsg(); + void saveStats(); + void loadStats(); + void loadOutputDir(); + void getSeederInfo(Uint32 & total,Uint32 & connected_to) const; + void getLeecherInfo(Uint32 & total,Uint32 & connected_to) const; + void migrateTorrent(const QString & default_save_dir); + void continueStart(); + virtual void handleError(const QString & err); + + void initInternal(QueueManager* qman,const QString & tmpdir, + const QString & ddir,const QString & default_save_dir,bool first_time); + + void checkExisting(QueueManager* qman); + void setupDirs(const QString & tmpdir,const QString & ddir); + void setupStats(); + void setupData(const QString & ddir); + virtual void afterDataCheck(); + virtual bool isCheckingData(bool & finished) const; + + private: + Torrent* tor; + PeerSourceManager* psman; + ChunkManager* cman; + PeerManager* pman; + Downloader* down; + Uploader* up; + Choker* choke; + TimeEstimator* m_eta; + kt::MonitorInterface* tmon; + + Timer choker_update_timer; + Timer stats_save_timer; + Timer stalled_timer; + + QString datadir; + QString old_datadir; + QString outputdir; + QString error_msg; + + QString move_data_files_destination_path; + bool restart_torrent_after_move_data_files; + + bool prealloc; + PreallocationThread* prealoc_thread; + DataCheckerThread* dcheck_thread; + TimeStamp last_diskspace_check; + bool moving_files; + + struct InternalStats + { + QDateTime time_started_dl; + QDateTime time_started_ul; + Uint32 running_time_dl; + Uint32 running_time_ul; + Uint64 prev_bytes_dl; + Uint64 prev_bytes_ul; + Uint64 trk_prev_bytes_dl; + Uint64 trk_prev_bytes_ul; + Uint64 session_bytes_uploaded; + bool io_error; + bool custom_output_name; + Uint16 port; + int priority; + bool dht_on; + TimeStamp last_announce; + bool diskspace_warning_emitted; + }; + + Uint32 upload_gid; // group ID for upload + Uint32 upload_limit; + Uint32 download_gid; // group ID for download + Uint32 download_limit; + + InternalStats istats; + }; + + +} + +#endif diff --git a/libktorrent/torrent/torrentcreator.cpp b/libktorrent/torrent/torrentcreator.cpp new file mode 100644 index 0000000..7b132b8 --- /dev/null +++ b/libktorrent/torrent/torrentcreator.cpp @@ -0,0 +1,388 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <qfileinfo.h> +#include <klocale.h> +#include <time.h> +#include <util/error.h> +#include <ktversion.h> +#include "torrentcontrol.h" +#include "torrentcreator.h" +#include "bencoder.h" +#include <util/file.h> +#include <util/sha1hash.h> +#include <util/fileops.h> +#include <util/log.h> +#include <util/array.h> +#include <util/functions.h> +#include "globals.h" +#include "chunkmanager.h" +#include "statsfile.h" + +namespace bt +{ + + TorrentCreator::TorrentCreator(const QString & tar, + const QStringList & track, + Uint32 cs, + const QString & name, + const QString & comments,bool priv, bool decentralized) + : target(tar),trackers(track),chunk_size(cs), + name(name),comments(comments),cur_chunk(0),priv(priv),tot_size(0), decentralized(decentralized) + { + this->chunk_size *= 1024; + QFileInfo fi(target); + if (fi.isDir()) + { + if (!this->target.endsWith(bt::DirSeparator())) + this->target += bt::DirSeparator(); + + tot_size = 0; + buildFileList(""); + num_chunks = tot_size / chunk_size; + if (tot_size % chunk_size > 0) + num_chunks++; + last_size = tot_size % chunk_size; + Out() << "Tot Size : " << tot_size << endl; + } + else + { + tot_size = bt::FileSize(target); + num_chunks = tot_size / chunk_size; + if (tot_size % chunk_size > 0) + num_chunks++; + last_size = tot_size % chunk_size; + Out() << "Tot Size : " << tot_size << endl; + } + + if (last_size == 0) + last_size = chunk_size; + + Out() << "Num Chunks : " << num_chunks << endl; + Out() << "Chunk Size : " << chunk_size << endl; + Out() << "Last Size : " << last_size << endl; + } + + + TorrentCreator::~TorrentCreator() + {} + + void TorrentCreator::buildFileList(const QString & dir) + { + QDir d(target + dir); + // first get all files (we ignore symlinks) + QStringList dfiles = d.entryList(QDir::Files|QDir::NoSymLinks); + Uint32 cnt = 0; // counter to keep track of file index + for (QStringList::iterator i = dfiles.begin();i != dfiles.end();++i) + { + // add a TorrentFile to the list + Uint64 fs = bt::FileSize(target + dir + *i); + TorrentFile f(cnt,dir + *i,tot_size,fs,chunk_size); + files.append(f); + // update total size + tot_size += fs; + cnt++; + } + + // now for each subdir do a buildFileList + QStringList subdirs = d.entryList(QDir::Dirs|QDir::NoSymLinks); + for (QStringList::iterator i = subdirs.begin();i != subdirs.end();++i) + { + if (*i == "." || *i == "..") + continue; + + QString sd = dir + *i; + if (!sd.endsWith(bt::DirSeparator())) + sd += bt::DirSeparator(); + buildFileList(sd); + } + } + + + void TorrentCreator::saveTorrent(const QString & url) + { + File fptr; + if (!fptr.open(url,"wb")) + throw Error(i18n("Cannot open file %1: %2").arg(url).arg(fptr.errorString())); + + BEncoder enc(&fptr); + enc.beginDict(); // top dict + + if(!decentralized) + { + enc.write("announce"); enc.write(trackers[0]); + if (trackers.count() > 1) + { + enc.write("announce-list"); + enc.beginList(); + enc.beginList(); + for (Uint32 i = 0;i < trackers.count();i++) + enc.write(trackers[i]); + enc.end(); + enc.end(); + + } + } + + + if (comments.length() > 0) + { + enc.write("comments"); + enc.write(comments); + } + enc.write("created by");enc.write(QString("KTorrent %1").arg(kt::VERSION_STRING)); + enc.write("creation date");enc.write((Uint64)time(0)); + enc.write("info"); + saveInfo(enc); + // save the nodes list after the info hash, keys must be sorted ! + if (decentralized) + { + //DHT torrent + enc.write("nodes"); + enc.beginList(); + + for(int i=0; i < trackers.count(); ++i) + { + QString t = trackers[i]; + enc.beginList(); + enc.write(t.section(',',0,0)); + enc.write((Uint32)t.section(',',1,1).toInt()); + enc.end(); + } + enc.end(); + } + + enc.end(); + } + + void TorrentCreator::saveInfo(BEncoder & enc) + { + enc.beginDict(); + + QFileInfo fi(target); + if (fi.isDir()) + { + enc.write("files"); + enc.beginList(); + QValueList<TorrentFile>::iterator i = files.begin(); + while (i != files.end()) + { + saveFile(enc,*i); + i++; + } + enc.end(); + } + else + { + enc.write("length"); enc.write(bt::FileSize(target)); + } + enc.write("name"); enc.write(name); + enc.write("piece length"); enc.write((Uint64)chunk_size); + enc.write("pieces"); savePieces(enc); + if (priv) + { + enc.write("private"); + enc.write((Uint64)1); + } + enc.end(); + } + + void TorrentCreator::saveFile(BEncoder & enc,const TorrentFile & file) + { + enc.beginDict(); + enc.write("length");enc.write(file.getSize()); + enc.write("path"); + enc.beginList(); + QStringList sl = QStringList::split(bt::DirSeparator(),file.getPath()); + for (QStringList::iterator i = sl.begin();i != sl.end();i++) + enc.write(*i); + enc.end(); + enc.end(); + } + + void TorrentCreator::savePieces(BEncoder & enc) + { + if (hashes.empty()) + while (!calculateHash()) + ; + + Array<Uint8> big_hash(num_chunks*20); + for (Uint32 i = 0;i < num_chunks;++i) + { + memcpy(big_hash+(20*i),hashes[i].getData(),20); + } + enc.write(big_hash,num_chunks*20); + } + + bool TorrentCreator::calcHashSingle() + { + Array<Uint8> buf(chunk_size); + File fptr; + if (!fptr.open(target,"rb")) + throw Error(i18n("Cannot open file %1: %2") + .arg(target).arg(fptr.errorString())); + + Uint32 s = cur_chunk != num_chunks - 1 ? chunk_size : last_size; + fptr.seek(File::BEGIN,(Int64)cur_chunk*chunk_size); + + fptr.read(buf,s); + SHA1Hash h = SHA1Hash::generate(buf,s); + hashes.append(h); + cur_chunk++; + return cur_chunk >= num_chunks; + } + + bool TorrentCreator::calcHashMulti() + { + Uint32 s = cur_chunk != num_chunks - 1 ? chunk_size : last_size; + // first find the file(s) the chunk lies in + Array<Uint8> buf(s); + QValueList<TorrentFile> file_list; + Uint32 i = 0; + while (i < files.size()) + { + const TorrentFile & tf = files[i]; + if (cur_chunk >= tf.getFirstChunk() && cur_chunk <= tf.getLastChunk()) + { + file_list.append(tf); + } + + i++; + } + + Uint32 read = 0; + for (i = 0;i < file_list.count();i++) + { + const TorrentFile & f = file_list[i]; + File fptr; + if (!fptr.open(target + f.getPath(),"rb")) + { + throw Error(i18n("Cannot open file %1: %2") + .arg(f.getPath()).arg(fptr.errorString())); + } + + // first calculate offset into file + // only the first file can have an offset + // the following files will start at the beginning + Uint64 off = 0; + if (i == 0) + off = f.fileOffset(cur_chunk,chunk_size); + + Uint32 to_read = 0; + // then the amount of data we can read from this file + if (file_list.count() == 1) + to_read = s; + else if (i == 0) + to_read = f.getLastChunkSize(); + else if (i == file_list.count() - 1) + to_read = s - read; + else + to_read = f.getSize(); + + // read part of data + fptr.seek(File::BEGIN,(Int64)off); + fptr.read(buf + read,to_read); + read += to_read; + } + + // generate hash + SHA1Hash h = SHA1Hash::generate(buf,s); + hashes.append(h); + + cur_chunk++; + // Out() << "=============================================" << endl; + return cur_chunk >= num_chunks; + } + + bool TorrentCreator::calculateHash() + { + if (cur_chunk >= num_chunks) + return true; + if (files.empty()) + return calcHashSingle(); + else + return calcHashMulti(); + } + + TorrentControl* TorrentCreator::makeTC(const QString & data_dir) + { + QString dd = data_dir; + if (!dd.endsWith(bt::DirSeparator())) + dd += bt::DirSeparator(); + + // make data dir if necessary + if (!bt::Exists(dd)) + bt::MakeDir(dd); + + // save the torrent + saveTorrent(dd + "torrent"); + // write full index file + File fptr; + if (!fptr.open(dd + "index","wb")) + throw Error(i18n("Cannot create index file: %1").arg(fptr.errorString())); + + for (Uint32 i = 0;i < num_chunks;i++) + { + NewChunkHeader hdr; + hdr.index = i; + fptr.write(&hdr,sizeof(NewChunkHeader)); + } + fptr.close(); + + // now create the torrentcontrol object + TorrentControl* tc = new TorrentControl(); + try + { + // get the parent dir of target + QFileInfo fi = QFileInfo(target); + + QString odir; + StatsFile st(dd + "stats"); + if (fi.fileName() == name) + { + st.write("OUTPUTDIR", fi.dirPath(true)); + odir = fi.dirPath(true); + } + else + { + st.write("CUSTOM_OUTPUT_NAME","1"); + st.write("OUTPUTDIR", target); + odir = target; + } + st.write("UPLOADED", "0"); + st.write("RUNNING_TIME_DL","0"); + st.write("RUNNING_TIME_UL","0"); + st.write("PRIORITY", "0"); + st.write("AUTOSTART", "1"); + st.write("IMPORTED", QString::number(tot_size)); + st.writeSync(); + + tc->init(0,dd + "torrent",dd,odir,QString::null); + tc->createFiles(); + } + catch (...) + { + delete tc; + throw; + } + + return tc; + } +} diff --git a/libktorrent/torrent/torrentcreator.h b/libktorrent/torrent/torrentcreator.h new file mode 100644 index 0000000..c7057e2 --- /dev/null +++ b/libktorrent/torrent/torrentcreator.h @@ -0,0 +1,114 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef BTTORRENTCREATOR_H +#define BTTORRENTCREATOR_H + +#include <qstringlist.h> +#include "torrent.h" +#include <util/sha1hash.h> + +namespace bt +{ + class BEncoder; + class TorrentControl; + + /** + * @author Joris Guisson + * @brief Class to generate torrent files + * + * This class generates torrent files. + * It also allows to create a TorrentControl object, so + * that we immediately can start to share the torrent. + */ + class TorrentCreator + { + // input values + QString target; + QStringList trackers; + int chunk_size; + QString name,comments; + // calculated values + Uint32 num_chunks; + Uint64 last_size; + QValueList<TorrentFile> files; + QValueList<SHA1Hash> hashes; + // + Uint32 cur_chunk; + bool priv; + Uint64 tot_size; + bool decentralized; + public: + /** + * Constructor. + * @param target The file or directory to make a torrent of + * @param trackers A list of tracker urls + * @param chunk_size The size of each chunk + * @param name The name suggestion + * @param comments The comments field of the torrent + * @param priv Private torrent or not + */ + TorrentCreator(const QString & target,const QStringList & trackers, + Uint32 chunk_size,const QString & name, + const QString & comments,bool priv,bool decentralized); + virtual ~TorrentCreator(); + + + /** + * Calculate the hash of a chunk, this function should be called + * until it returns true. We do it this way so that the calling + * function can display a progress dialog. + * @return true if all hashes are calculated, false otherwise + */ + bool calculateHash(); + + /// Get the number of chunks + Uint32 getNumChunks() const {return num_chunks;} + + /** + * Save the torrent file. + * @param url Filename + * @throw Error if something goes wrong + */ + void saveTorrent(const QString & url); + + /** + * Make a TorrentControl object for this torrent. + * This will also create the files : + * data_dir/index + * data_dir/torrent + * data_dir/cache (symlink to target) + * @param data_dir The data directory + * @throw Error if something goes wrong + * @return The newly created object + */ + TorrentControl* makeTC(const QString & data_dir); + + private: + void saveInfo(BEncoder & enc); + void saveFile(BEncoder & enc,const TorrentFile & file); + void savePieces(BEncoder & enc); + void buildFileList(const QString & dir); + bool calcHashSingle(); + bool calcHashMulti(); + }; + +} + +#endif diff --git a/libktorrent/torrent/torrentfile.cpp b/libktorrent/torrent/torrentfile.cpp new file mode 100644 index 0000000..9c21a4a --- /dev/null +++ b/libktorrent/torrent/torrentfile.cpp @@ -0,0 +1,200 @@ +/*************************************************************************** + * 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 <math.h> +#include <util/log.h> +#include <util/bitset.h> +#include <util/functions.h> +#include "globals.h" +#include "torrentfile.h" + +namespace bt +{ + + TorrentFile::TorrentFile() : TorrentFileInterface(QString::null,0),missing(false),filetype(UNKNOWN) + {} + + TorrentFile::TorrentFile(Uint32 index,const QString & path, + Uint64 off,Uint64 size,Uint64 chunk_size) + : TorrentFileInterface(path,size),index(index),cache_offset(off),missing(false),filetype(UNKNOWN) + { + first_chunk = off / chunk_size; + first_chunk_off = off % chunk_size; + if (size > 0) + last_chunk = (off + size - 1) / chunk_size; + else + last_chunk = first_chunk; + last_chunk_size = (off + size) - last_chunk * chunk_size; + priority = old_priority = NORMAL_PRIORITY; + } + + TorrentFile::TorrentFile(const TorrentFile & tf) + : TorrentFileInterface(QString::null,0) + { + index = tf.getIndex(); + path = tf.getPath(); + size = tf.getSize(); + cache_offset = tf.getCacheOffset(); + first_chunk = tf.getFirstChunk(); + first_chunk_off = tf.getFirstChunkOffset(); + last_chunk = tf.getLastChunk(); + last_chunk_size = tf.getLastChunkSize(); + old_priority = priority = tf.getPriority(); + missing = tf.isMissing(); + filetype = UNKNOWN; + } + + TorrentFile::~TorrentFile() + {} + + void TorrentFile::setDoNotDownload(bool dnd) + { + if (priority != EXCLUDED && dnd) + { + if(m_emitDlStatusChanged) + old_priority = priority; + + priority = EXCLUDED; + + if(m_emitDlStatusChanged) + emit downloadPriorityChanged(this,priority,old_priority); + } + if (priority == EXCLUDED && (!dnd)) + { + if(m_emitDlStatusChanged) + old_priority = priority; + + priority = NORMAL_PRIORITY; + + if(m_emitDlStatusChanged) + emit downloadPriorityChanged(this,priority,old_priority); + } + } + + void TorrentFile::emitDownloadStatusChanged() + { + // only emit when old_priority is not equal to the new priority + if (priority != old_priority) + emit downloadPriorityChanged(this,priority,old_priority); + } + + + bool TorrentFile::isMultimedia() const + { + if (filetype == UNKNOWN) + { + if (IsMultimediaFile(getPath())) + { + filetype = MULTIMEDIA; + return true; + } + else + { + filetype = NORMAL; + return false; + } + } + return filetype == MULTIMEDIA; + } + + void TorrentFile::setPriority(Priority newpriority) + { + if(priority != newpriority) + { + if (priority == EXCLUDED) + { + setDoNotDownload(false); + } + if (newpriority == EXCLUDED) + { + setDoNotDownload(true); + } + else + { + old_priority = priority; + priority = newpriority; + emit downloadPriorityChanged(this,newpriority,old_priority); + } + } + } + + TorrentFile & TorrentFile::operator = (const TorrentFile & tf) + { + index = tf.getIndex(); + path = tf.getPath(); + size = tf.getSize(); + cache_offset = tf.getCacheOffset(); + first_chunk = tf.getFirstChunk(); + first_chunk_off = tf.getFirstChunkOffset(); + last_chunk = tf.getLastChunk(); + last_chunk_size = tf.getLastChunkSize(); + priority = tf.getPriority(); + missing = tf.isMissing(); + return *this; + } + + TorrentFile TorrentFile::null; + + + Uint64 TorrentFile::fileOffset(Uint32 cindex,Uint64 chunk_size) const + { + Uint64 off = 0; + if (getFirstChunkOffset() == 0) + { + off = (cindex - getFirstChunk()) * chunk_size; + } + else + { + if (cindex - this->getFirstChunk() > 0) + off = (cindex - this->getFirstChunk() - 1) * chunk_size; + if (cindex > 0) + off += (chunk_size - this->getFirstChunkOffset()); + } + return off; + } + + void TorrentFile::updateNumDownloadedChunks(const BitSet & bs) + { + float p = getDownloadPercentage(); + num_chunks_downloaded = 0; + bool prev = preview; + preview = true; + for (Uint32 i = first_chunk;i <= last_chunk;i++) + { + if (bs.get(i)) + { + num_chunks_downloaded++; + } + else if (i == first_chunk || i == first_chunk + 1) + { + preview = false; + } + } + preview = isMultimedia() && preview; + + float np = getDownloadPercentage(); + if (fabs(np - p) >= 0.01f) + downloadPercentageChanged(np); + + if (prev != preview) + previewAvailable(preview); + } +} +#include "torrentfile.moc" diff --git a/libktorrent/torrent/torrentfile.h b/libktorrent/torrent/torrentfile.h new file mode 100644 index 0000000..9e0c397 --- /dev/null +++ b/libktorrent/torrent/torrentfile.h @@ -0,0 +1,158 @@ +/*************************************************************************** + * 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. * + ***************************************************************************/ +#ifndef BTTORRENTFILE_H +#define BTTORRENTFILE_H + +#include <qstring.h> +#include <util/constants.h> +#include <interfaces/torrentfileinterface.h> + +namespace bt +{ + class BitSet; + + /** + * @author Joris Guisson + * + * File in a multi file torrent. Keeps track of the path of the file, + * it's size, offset into the cache and between which chunks it lies. + */ + class TorrentFile : public kt::TorrentFileInterface + { + Q_OBJECT + + Uint32 index; + Uint64 cache_offset; + Uint64 first_chunk_off; + Uint64 last_chunk_size; + Priority priority; + Priority old_priority; + bool missing; + enum FileType + { + UNKNOWN, + MULTIMEDIA, + NORMAL + }; + mutable FileType filetype; + public: + /** + * Default constructor. Creates a null TorrentFile. + */ + TorrentFile(); + + /** + * Constructor. + * @param index Index number of the file + * @param path Path of the file + * @param off Offset into the torrent + * (i.e. how many bytes were all the previous files in the torrent combined) + * @param size Size of the file + * @param chunk_size Size of each chunk + */ + TorrentFile(Uint32 index,const QString & path,Uint64 off,Uint64 size,Uint64 chunk_size); + + /** + * Copy constructor. + * @param tf The TorrentFile to copy + */ + TorrentFile(const TorrentFile & tf); + virtual ~TorrentFile(); + + /// Get the index of the file + Uint32 getIndex() const {return index;} + + /// Get the offset into the torrent + Uint64 getCacheOffset() const {return cache_offset;} + + /// Get the offset at which the file starts in the first chunk + Uint64 getFirstChunkOffset() const {return first_chunk_off;} + + /// Get how many bytes the files takes up of the last chunk + Uint64 getLastChunkSize() const {return last_chunk_size;} + + /// Check if this file doesn't have to be downloaded + bool doNotDownload() const + {if(priority == EXCLUDED) return true; else return false;} + + /// Set wether we have to not download this file + void setDoNotDownload(bool dnd); + + /// Checks if this file is multimedial + bool isMultimedia() const; + + /// Gets the priority of the file + Priority getPriority() const {return priority;} + + /// Sets the priority of the file + void setPriority(Priority newpriority = NORMAL_PRIORITY); + + /// Get the previous priority value + Priority getOldPriority() const {return old_priority;} + + + /// emits signal. + void emitDownloadStatusChanged(); + + void setEmitDownloadStatusChanged(bool show) { m_emitDlStatusChanged = show; } + + /** + * Assignment operator + * @param tf The file to copy + * @return *this + */ + TorrentFile & operator = (const TorrentFile & tf); + + /// See if the file is missing + bool isMissing() const {return missing;} + + /// Set the file to be missing or not + void setMissing(bool m) {missing = m;} + + /** + * Calculate the offset of a chunk in the file + * @param cindex Index of chunk + * @param chunk_size Size of each chunk + */ + Uint64 fileOffset(Uint32 cindex,Uint64 chunk_size) const; + + static TorrentFile null; + + /** + * Update the number of downloaded chunks for this file. + * @param bs The current bitset of all chunks + */ + void updateNumDownloadedChunks(const BitSet & bs); + + signals: + /** + * Signal emitted when the Priority variable changes. + * @param tf The TorrentFile which emitted the signal + * @param newpriority THe new priority of the file + * @param oldpriority Previous priority + */ + void downloadPriorityChanged(TorrentFile* tf,Priority newpriority,Priority oldpriority); + + }; + +} + +#endif diff --git a/libktorrent/torrent/torrentmonitor.cpp b/libktorrent/torrent/torrentmonitor.cpp new file mode 100644 index 0000000..ff33acb --- /dev/null +++ b/libktorrent/torrent/torrentmonitor.cpp @@ -0,0 +1,33 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 "torrentmonitor.h" + +namespace bt +{ + + TorrentMonitor::TorrentMonitor() + {} + + + TorrentMonitor::~TorrentMonitor() + {} + + +} diff --git a/libktorrent/torrent/torrentmonitor.h b/libktorrent/torrent/torrentmonitor.h new file mode 100644 index 0000000..52e3835 --- /dev/null +++ b/libktorrent/torrent/torrentmonitor.h @@ -0,0 +1,47 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef BTTORRENTMONITOR_H +#define BTTORRENTMONITOR_H + +namespace bt +{ + class Peer; + class ChunkDownload; + + /** + @author Joris Guisson + */ + class TorrentMonitor + { + public: + TorrentMonitor(); + virtual ~TorrentMonitor(); + + virtual void peerAdded(Peer* peer) = 0; + virtual void peerRemoved(Peer* peer) = 0; + virtual void downloadStarted(ChunkDownload* cd) = 0; + virtual void downloadRemoved(ChunkDownload* cd) = 0; + virtual void stopped() = 0; + virtual void destroyed() = 0; + }; + +} + +#endif diff --git a/libktorrent/torrent/tracker.cpp b/libktorrent/torrent/tracker.cpp new file mode 100644 index 0000000..261169c --- /dev/null +++ b/libktorrent/torrent/tracker.cpp @@ -0,0 +1,93 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <stdlib.h> +#include <time.h> +#include <kurl.h> +#include <kresolver.h> +#include <util/functions.h> +#include <util/log.h> +#include <torrent/globals.h> +#include <interfaces/torrentinterface.h> +#include <kademlia/dhtbase.h> +#include <kademlia/dhttrackerbackend.h> +#include "server.h" +#include "tracker.h" +#include "udptracker.h" +#include "httptracker.h" + +using namespace KNetwork; + +namespace bt +{ + static QString custom_ip; + static QString custom_ip_resolved; + + Tracker::Tracker(const KURL & url,kt::TorrentInterface* tor,const PeerID & id,int tier) + : url(url),tier(tier),peer_id(id),tor(tor) + { + // default 5 minute interval + interval = 5 * 60 * 1000; + seeders = leechers = 0; + srand(time(0)); + key = rand(); + started = false; + } + + Tracker::~Tracker() + { + } + + void Tracker::setCustomIP(const QString & ip) + { + if (custom_ip == ip) + return; + + Out(SYS_TRK|LOG_NOTICE) << "Setting custom ip to " << ip << endl; + custom_ip = ip; + custom_ip_resolved = QString::null; + if (ip.isNull()) + return; + + KResolverResults res = KResolver::resolve(ip,QString::null); + if (res.error() || res.empty()) + { + custom_ip = custom_ip_resolved = QString::null; + } + else + { + custom_ip_resolved = res.first().address().nodeName(); + Out(SYS_TRK|LOG_NOTICE) << "custom_ip_resolved = " << custom_ip_resolved << endl; + } + } + + QString Tracker::getCustomIP() + { + return custom_ip_resolved; + } + + void Tracker::timedDelete(int ms) + { + QTimer::singleShot(ms,this,SLOT(deleteLater())); + connect(this,SIGNAL(stopDone()),this,SLOT(deleteLater())); + } + +} + +#include "tracker.moc" diff --git a/libktorrent/torrent/tracker.h b/libktorrent/torrent/tracker.h new file mode 100644 index 0000000..d254b63 --- /dev/null +++ b/libktorrent/torrent/tracker.h @@ -0,0 +1,136 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef BTTRACKER_H +#define BTTRACKER_H + +#include <kurl.h> +#include <util/sha1hash.h> +#include <interfaces/peersource.h> +#include "globals.h" +#include "peerid.h" + +class KURL; + +namespace kt +{ + class TorrentInterface; +} + + +namespace bt +{ + class Tracker; + + /** + * Base class for all tracker classes. + */ + class Tracker : public kt::PeerSource + { + Q_OBJECT + public: + Tracker(const KURL & url,kt::TorrentInterface* tor,const PeerID & id,int tier); + virtual ~Tracker(); + + /// See if a start request succeeded + bool isStarted() const {return started;} + + /** + * Set the custom IP + * @param str + */ + static void setCustomIP(const QString & str); + + /// get the tracker url + KURL trackerURL() const {return url;} + + /** + * Delete the tracker in ms milliseconds, or when the stopDone signal is emitted. + * @param ms Number of ms to wait + */ + void timedDelete(int ms); + + /** + * Get the number of failed attempts to reach a tracker. + * @return The number of failed attempts + */ + virtual Uint32 failureCount() const = 0; + + /** + * Do a tracker scrape to get more accurate stats about a torrent. + * Does nothing if the tracker does not support this. + */ + virtual void scrape() = 0; + + /// Get the trackers tier + int getTier() const {return tier;} + + /** + * Get the update interval in ms + * @return interval + */ + Uint32 getInterval() const {return interval;} + + /// Set the interval + void setInterval(Uint32 i) {interval = i;} + + /// Get the number of seeders + Uint32 getNumSeeders() const {return seeders;} + + /// Get the number of leechers + Uint32 getNumLeechers() const {return leechers;} + + /// Get the custom ip to use, null if none is set + static QString getCustomIP(); + signals: + /** + * Emitted when an error happens. + * @param failure_reason The reason why we couldn't reach the tracker + */ + void requestFailed(const QString & failure_reason); + + /** + * Emitted when a stop is done. + */ + void stopDone(); + + /** + * Emitted when a request to the tracker succeeded + */ + void requestOK(); + + /** + * A request to the tracker has been started. + */ + void requestPending(); + + protected: + KURL url; + int tier; + PeerID peer_id; + kt::TorrentInterface* tor; + Uint32 interval,seeders,leechers,key; + bool started; + private: + //static QString custom_ip,custom_ip_resolved; + }; + +} + +#endif diff --git a/libktorrent/torrent/udptracker.cpp b/libktorrent/torrent/udptracker.cpp new file mode 100644 index 0000000..2dd4a01 --- /dev/null +++ b/libktorrent/torrent/udptracker.cpp @@ -0,0 +1,291 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <stdlib.h> +#include <kresolver.h> +#include <util/functions.h> +#include <util/log.h> +#include <ksocketaddress.h> +#include "peermanager.h" +#include "udptracker.h" +#include "torrentcontrol.h" +#include "globals.h" +#include "server.h" +#include "udptrackersocket.h" + + +using namespace kt; +using namespace KNetwork; + +namespace bt +{ + + UDPTrackerSocket* UDPTracker::socket = 0; + Uint32 UDPTracker::num_instances = 0; + + + UDPTracker::UDPTracker(const KURL & url,kt::TorrentInterface* tor,const PeerID & id,int tier) + : Tracker(url,tor,id,tier) + { + num_instances++; + if (!socket) + socket = new UDPTrackerSocket(); + + connection_id = 0; + transaction_id = 0; + interval = 0; + + connect(&conn_timer,SIGNAL(timeout()),this,SLOT(onConnTimeout())); + connect(socket,SIGNAL(announceRecieved(Int32, const QByteArray &)), + this,SLOT(announceRecieved(Int32, const QByteArray& ))); + connect(socket,SIGNAL(connectRecieved(Int32, Int64 )), + this,SLOT(connectRecieved(Int32, Int64 ))); + connect(socket,SIGNAL(error(Int32, const QString& )), + this,SLOT(onError(Int32, const QString& ))); + + KResolver::resolveAsync(this,SLOT(onResolverResults(KResolverResults )), + url.host(),QString::number(url.port())); + } + + + UDPTracker::~UDPTracker() + { + num_instances--; + if (num_instances == 0) + { + delete socket; + socket = 0; + } + } + + void UDPTracker::start() + { + event = STARTED; + conn_timer.stop(); + doRequest(); + } + + void UDPTracker::stop(WaitJob* ) + { + if (!started) + return; + + event = STOPPED; + conn_timer.stop(); + doRequest(); + started = false; + } + + void UDPTracker::completed() + { + event = COMPLETED; + conn_timer.stop(); + doRequest(); + } + + void UDPTracker::manualUpdate() + { + conn_timer.stop(); + if (!started) + event = STARTED; + doRequest(); + } + + void UDPTracker::connectRecieved(Int32 tid,Int64 cid) + { + if (tid != transaction_id) + return; + + connection_id = cid; + n = 0; + sendAnnounce(); + } + + void UDPTracker::announceRecieved(Int32 tid,const QByteArray & data) + { + if (tid != transaction_id) + return; + + const Uint8* buf = (const Uint8*)data.data(); + + /* + 0 32-bit integer action 1 + 4 32-bit integer transaction_id + 8 32-bit integer interval + 12 32-bit integer leechers + 16 32-bit integer seeders + 20 + 6 * n 32-bit integer IP address + 24 + 6 * n 16-bit integer TCP port + 20 + 6 * N + */ + interval = ReadInt32(buf,8); + leechers = ReadInt32(buf,12); + seeders = ReadInt32(buf,16); + + Uint32 nip = leechers + seeders; + Uint32 j = 0; + for (Uint32 i = 20;i < data.size() && j < nip;i+=6,j++) + { + Uint32 ip = ReadUint32(buf,i); + addPeer(QString("%1.%2.%3.%4") + .arg((ip & (0xFF000000)) >> 24) + .arg((ip & (0x00FF0000)) >> 16) + .arg((ip & (0x0000FF00)) >> 8) + .arg(ip & 0x000000FF), + ReadUint16(buf,i+4)); + } + + peersReady(this); + connection_id = 0; + conn_timer.stop(); + if (event != STOPPED) + { + if (event == STARTED) + started = true; + event = NONE; + requestOK(); + } + else + { + stopDone(); + requestOK(); + } + } + + void UDPTracker::onError(Int32 tid,const QString & error_string) + { + if (tid != transaction_id) + return; + + Out(SYS_TRK|LOG_IMPORTANT) << "UDPTracker::error : " << error_string << endl; + requestFailed(error_string); + } + + + bool UDPTracker::doRequest() + { + Out(SYS_TRK|LOG_NOTICE) << "Doing tracker request to url : " << url << endl; + if (connection_id == 0) + { + n = 0; + sendConnect(); + } + else + sendAnnounce(); + + requestPending(); + return true; + } + + void UDPTracker::scrape() + { + } + + void UDPTracker::sendConnect() + { + transaction_id = socket->newTransactionID(); + socket->sendConnect(transaction_id,address); + int tn = 1; + for (int i = 0;i < n;i++) + tn *= 2; + conn_timer.start(60000 * tn,true); + } + + void UDPTracker::sendAnnounce() + { + // Out(SYS_TRK|LOG_NOTICE) << "UDPTracker::sendAnnounce()" << endl; + transaction_id = socket->newTransactionID(); + /* + 0 64-bit integer connection_id + 8 32-bit integer action 1 + 12 32-bit integer transaction_id + 16 20-byte string info_hash + 36 20-byte string peer_id + 56 64-bit integer downloaded + 64 64-bit integer left + 72 64-bit integer uploaded + 80 32-bit integer event + 84 32-bit integer IP address 0 + 88 32-bit integer key + 92 32-bit integer num_want -1 + 96 16-bit integer port + 98 + */ + + Uint32 ev = event; + const TorrentStats & s = tor->getStats(); + Uint16 port = Globals::instance().getServer().getPortInUse(); + Uint8 buf[98]; + WriteInt64(buf,0,connection_id); + WriteInt32(buf,8,ANNOUNCE); + WriteInt32(buf,12,transaction_id); + const SHA1Hash & info_hash = tor->getInfoHash(); + memcpy(buf+16,info_hash.getData(),20); + memcpy(buf+36,peer_id.data(),20); + WriteInt64(buf,56,s.trk_bytes_downloaded); + if (ev == COMPLETED) + WriteInt64(buf,64,0); + else + WriteInt64(buf,64,s.bytes_left); + WriteInt64(buf,72,s.trk_bytes_uploaded); + WriteInt32(buf,80,ev); + QString cip = Tracker::getCustomIP(); + if (cip.isNull()) + { + WriteUint32(buf,84,0); + } + else + { + KNetwork::KIpAddress addr(cip); + WriteUint32(buf,84,addr.IPv4Addr(true)); + } + WriteUint32(buf,88,key); + if (ev != STOPPED) + WriteInt32(buf,92,100); + else + WriteInt32(buf,92,0); + WriteUint16(buf,96,port); + + socket->sendAnnounce(transaction_id,buf,address); + } + + void UDPTracker::onConnTimeout() + { + if (connection_id) + { + connection_id = 0; + n++; + if (event != STOPPED) + sendConnect(); + else + stopDone(); + } + else + { + doRequest(); + } + } + + void UDPTracker::onResolverResults(KResolverResults res) + { + address = res.front().address(); + } + +} +#include "udptracker.moc" diff --git a/libktorrent/torrent/udptracker.h b/libktorrent/torrent/udptracker.h new file mode 100644 index 0000000..5107fb9 --- /dev/null +++ b/libktorrent/torrent/udptracker.h @@ -0,0 +1,105 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef BTUDPTRACKER_H +#define BTUDPTRACKER_H + +#include <kurl.h> +#include <qvaluelist.h> +#include <qcstring.h> +#include <qtimer.h> +#include <ksocketaddress.h> +#include "tracker.h" +#include "globals.h" +#include "peermanager.h" + + + +namespace KNetwork +{ + class KResolverResults; +} + + +namespace bt +{ + using KNetwork::KResolverResults; + + enum Event + { + NONE = 0, + COMPLETED = 1, + STARTED = 2, + STOPPED = 3 + }; + + class UDPTrackerSocket; + + /** + * @author Joris Guisson + * @brief Communicates with an UDP tracker + * + * This class is able to communicate with an UDP tracker. + * This is an implementation of the protocol described in + * http://xbtt.sourceforge.net/udp_tracker_protocol.html + */ + class UDPTracker : public Tracker + { + Q_OBJECT + public: + UDPTracker(const KURL & url,kt::TorrentInterface* tor,const PeerID & id,int tier); + virtual ~UDPTracker(); + + virtual void start(); + virtual void stop(WaitJob* wjob = 0); + virtual void completed(); + virtual void manualUpdate(); + virtual Uint32 failureCount() const {return n;} + virtual void scrape(); + + private slots: + void onConnTimeout(); + void connectRecieved(Int32 tid,Int64 connection_id); + void announceRecieved(Int32 tid,const QByteArray & buf); + void onError(Int32 tid,const QString & error_string); + void onResolverResults(KResolverResults res); + + private: + void sendConnect(); + void sendAnnounce(); + bool doRequest(); + + private: + KNetwork::KSocketAddress address; + + Int32 transaction_id; + Int64 connection_id; + + Uint32 data_read; + int n; + QTimer conn_timer; + Event event; + + static UDPTrackerSocket* socket; + static Uint32 num_instances; + }; + +} + +#endif diff --git a/libktorrent/torrent/udptrackersocket.cpp b/libktorrent/torrent/udptrackersocket.cpp new file mode 100644 index 0000000..43ef2b6 --- /dev/null +++ b/libktorrent/torrent/udptrackersocket.cpp @@ -0,0 +1,222 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <time.h> +#include <stdlib.h> +#include <unistd.h> +#include <util/array.h> +#include <ksocketaddress.h> +#include <kdatagramsocket.h> +#include <ksocketdevice.h> +#include <net/portlist.h> +#include <util/log.h> +#include <util/functions.h> +#include <klocale.h> +#include <kmessagebox.h> +#include "globals.h" +#include "udptrackersocket.h" + +using namespace KNetwork; + +namespace bt +{ + Uint16 UDPTrackerSocket::port = 4444; + + UDPTrackerSocket::UDPTrackerSocket() + { + sock = new KNetwork::KDatagramSocket(this); + sock->setAddressReuseable(true); + connect(sock,SIGNAL(readyRead()),this,SLOT(dataReceived())); + int i = 0; + if (port == 0) + port = 4444; + + bool bound = false; + + while (!(bound = sock->bind(QString::null,QString::number(port + i))) && i < 10) + { + Out() << "Failed to bind socket to port " << (port+i) << endl; + i++; + } + + + if (!bound) + { + KMessageBox::error(0, + i18n("Cannot bind to udp port %1 or the 10 following ports.").arg(port)); + } + else + { + port = port + i; + Globals::instance().getPortList().addNewPort(port,net::UDP,true); + } + } + + + UDPTrackerSocket::~UDPTrackerSocket() + { + Globals::instance().getPortList().removePort(port,net::UDP); + delete sock; + } + + void UDPTrackerSocket::sendConnect(Int32 tid,const KNetwork::KSocketAddress & addr) + { + Int64 cid = 0x41727101980LL; + Uint8 buf[16]; + + WriteInt64(buf,0,cid); + WriteInt32(buf,8,CONNECT); + WriteInt32(buf,12,tid); + + sock->send(KDatagramPacket((char*)buf,16,addr)); + transactions.insert(tid,CONNECT); + } + + void UDPTrackerSocket::sendAnnounce(Int32 tid,const Uint8* data,const KNetwork::KSocketAddress & addr) + { + transactions.insert(tid,ANNOUNCE); + sock->send(KDatagramPacket((char*)data,98,addr)); + } + + void UDPTrackerSocket::cancelTransaction(Int32 tid) + { + transactions.remove(tid); + } + + void UDPTrackerSocket::handleConnect(const QByteArray & data) + { + const Uint8* buf = (const Uint8*)data.data(); + + // Read the transaction_id and check it + Int32 tid = ReadInt32(buf,4); + QMap<Int32,Action>::iterator i = transactions.find(tid); + // if we can't find the transaction, just return + if (i == transactions.end()) + { + return; + } + + // check wether the transaction is a CONNECT + if (i.data() != CONNECT) + { + transactions.erase(i); + error(tid,QString::null); + return; + } + + // everything ok, emit signal + transactions.erase(i); + connectRecieved(tid,ReadInt64(buf,8)); + } + + void UDPTrackerSocket::handleAnnounce(const QByteArray & data) + { + const Uint8* buf = (const Uint8*)data.data(); + + // Read the transaction_id and check it + Int32 tid = ReadInt32(buf,4); + QMap<Int32,Action>::iterator i = transactions.find(tid); + // if we can't find the transaction, just return + if (i == transactions.end()) + return; + + // check wether the transaction is a ANNOUNCE + if (i.data() != ANNOUNCE) + { + transactions.erase(i); + error(tid,QString::null); + return; + } + + // everything ok, emit signal + transactions.erase(i); + announceRecieved(tid,data); + } + + void UDPTrackerSocket::handleError(const QByteArray & data) + { + const Uint8* buf = (const Uint8*)data.data(); + // Read the transaction_id and check it + Int32 tid = ReadInt32(buf,4); + QMap<Int32,Action>::iterator it = transactions.find(tid); + // if we can't find the transaction, just return + if (it == transactions.end()) + return; + + // extract error message + transactions.erase(it); + QString msg; + for (Uint32 i = 8;i < data.size();i++) + msg += (char)buf[i]; + + // emit signal + error(tid,msg); + } + + void UDPTrackerSocket::dataReceived() + { + if (sock->bytesAvailable() == 0) + { + Out(SYS_TRK|LOG_NOTICE) << "0 byte UDP packet " << endl; + // KDatagramSocket wrongly handles UDP packets with no payload + // so we need to deal with it oursleves + int fd = sock->socketDevice()->socket(); + char tmp; + read(fd,&tmp,1); + return; + } + + KDatagramPacket pck = sock->receive(); + const QByteArray & data = pck.data(); + const Uint8* buf = (const Uint8*)data.data(); + Uint32 type = ReadUint32(buf,0); + switch (type) + { + case CONNECT: + handleConnect(data); + break; + case ANNOUNCE: + handleAnnounce(data); + break; + case ERROR: + handleError(data); + break; + } + } + + Int32 UDPTrackerSocket::newTransactionID() + { + Int32 transaction_id = rand() * time(0); + while (transactions.contains(transaction_id)) + transaction_id++; + return transaction_id; + } + + void UDPTrackerSocket::setPort(Uint16 p) + { + port = p; + } + + Uint16 UDPTrackerSocket::getPort() + { + return port; + } +} + +#include "udptrackersocket.moc" diff --git a/libktorrent/torrent/udptrackersocket.h b/libktorrent/torrent/udptrackersocket.h new file mode 100644 index 0000000..1537598 --- /dev/null +++ b/libktorrent/torrent/udptrackersocket.h @@ -0,0 +1,139 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef BTUDPTRACKERSOCKET_H +#define BTUDPTRACKERSOCKET_H + +#include <qobject.h> +#include <qmap.h> +#include <qcstring.h> +#include <util/constants.h> + + +namespace KNetwork +{ + class KDatagramSocket; + class KSocketAddress; +} + +namespace bt +{ + + + enum Action + { + CONNECT = 0, + ANNOUNCE = 1, + SCRAPE = 2, + ERROR = 3 + }; + + + + /** + * @author Joris Guisson + * + * Class which handles communication with one or more UDP trackers. + */ + class UDPTrackerSocket : public QObject + { + Q_OBJECT + public: + UDPTrackerSocket(); + virtual ~UDPTrackerSocket(); + + /** + * Send a connect message. As a response to this, the connectRecieved + * signal will be emitted, classes recieving this signal should check if + * the transaction_id is the same. + * @param tid The transaction_id + * @param addr The address to send to + */ + void sendConnect(Int32 tid,const KNetwork::KSocketAddress & addr); + + /** + * Send an announce message. As a response to this, the announceRecieved + * signal will be emitted, classes recieving this signal should check if + * the transaction_id is the same. + * @param tid The transaction_id + * @param data The data to send (connect input structure, in UDP Tracker specifaction) + * @param addr The address to send to + */ + void sendAnnounce(Int32 tid,const Uint8* data,const KNetwork::KSocketAddress & addr); + + /** + * If a transaction times out, this should be used to cancel it. + * @param tid + */ + void cancelTransaction(Int32 tid); + + + /** + * Compute a free transaction_id. + * @return A free transaction_id + */ + Int32 newTransactionID(); + + /** + * Set the port ot use. + * @param p The port + */ + static void setPort(Uint16 p); + + /// Get the port in use. + static Uint16 getPort(); + private slots: + void dataReceived(); + + signals: + /** + * Emitted when a connect message is received. + * @param tid The transaction_id + * @param connection_id The connection_id returned + */ + void connectRecieved(Int32 tid,Int64 connection_id); + + /** + * Emitted when an announce message is received. + * @param tid The transaction_id + * @param buf The data + */ + void announceRecieved(Int32 tid,const QByteArray & buf); + + /** + * Signal emitted, when an error occurs during a transaction. + * @param tid The transaction_id + * @param error_string Potential error string + */ + void error(Int32 tid,const QString & error_string); + + private: + void handleConnect(const QByteArray & buf); + void handleAnnounce(const QByteArray & buf); + void handleError(const QByteArray & buf); + + private: + Uint16 udp_port; + KNetwork::KDatagramSocket* sock; + QMap<Int32,Action> transactions; + static Uint16 port; + }; +} + +#endif diff --git a/libktorrent/torrent/uploadcap.cpp b/libktorrent/torrent/uploadcap.cpp new file mode 100644 index 0000000..701d854 --- /dev/null +++ b/libktorrent/torrent/uploadcap.cpp @@ -0,0 +1,46 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#if 0 +#include <math.h> +#include <util/log.h> +#include <torrent/globals.h> +#include "uploadcap.h" +#include "peer.h" +#include "packetwriter.h" + +namespace bt +{ + + UploadCap UploadCap::self; + + UploadCap::UploadCap() : Cap(false) + { + } + + UploadCap::~UploadCap() + { + } + + + + +} +#endif + diff --git a/libktorrent/torrent/uploadcap.h b/libktorrent/torrent/uploadcap.h new file mode 100644 index 0000000..f766f59 --- /dev/null +++ b/libktorrent/torrent/uploadcap.h @@ -0,0 +1,56 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef BTUPLOADCAP_H +#define BTUPLOADCAP_H + +// DEPRECATED +#if 0 +#include "cap.h" + +namespace bt +{ + class PacketWriter; + + + + /** + * @author Joris Guisson + * @brief Keeps the upload rate under control + * + * Before a PeerUploader can send a piece, it must first ask + * permission to a UploadCap object. This object will make sure + * that the upload rate remains under a specified threshold. When the + * threshold is set to 0, no upload capping will be done. + */ + class UploadCap : public Cap + { + static UploadCap self; + + UploadCap(); + public: + virtual ~UploadCap(); + + + static UploadCap & instance() {return self;} + }; + +} +#endif +#endif diff --git a/libktorrent/torrent/uploader.cpp b/libktorrent/torrent/uploader.cpp new file mode 100644 index 0000000..0cf3677 --- /dev/null +++ b/libktorrent/torrent/uploader.cpp @@ -0,0 +1,67 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <util/log.h> +#include "uploader.h" +#include "peer.h" +#include "chunkmanager.h" +#include "request.h" +#include "uploader.h" +#include "peeruploader.h" +#include "peermanager.h" + + +namespace bt +{ + + Uploader::Uploader(ChunkManager & cman,PeerManager & pman) + : cman(cman),pman(pman),uploaded(0) + {} + + + Uploader::~Uploader() + { + } + + + + void Uploader::update(Uint32 opt_unchoked) + { + for (Uint32 i = 0;i < pman.getNumConnectedPeers();++i) + { + PeerUploader* p = pman.getPeer(i)->getPeerUploader(); + uploaded += p->update(cman,opt_unchoked); + } + } + + + Uint32 Uploader::uploadRate() const + { + Uint32 rate = 0; + for (Uint32 i = 0;i < pman.getNumConnectedPeers();++i) + { + const Peer* p = pman.getPeer(i); + rate += p->getUploadRate(); + } + return rate; + } + + +} +#include "uploader.moc" diff --git a/libktorrent/torrent/uploader.h b/libktorrent/torrent/uploader.h new file mode 100644 index 0000000..4370d69 --- /dev/null +++ b/libktorrent/torrent/uploader.h @@ -0,0 +1,75 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef BTUPLOADER_H +#define BTUPLOADER_H + +#include <qobject.h> +#include "globals.h" + +namespace bt +{ + class Peer; + class PeerID; + class ChunkManager; + class Request; + class PeerManager; + + + /** + * @author Joris Guisson + * + * Class which manages the uploading of data. It has a PeerUploader for + * each Peer. + */ + class Uploader : public QObject + { + Q_OBJECT + public: + /** + * Constructor, sets the ChunkManager. + * @param cman The ChunkManager + */ + Uploader(ChunkManager & cman,PeerManager & pman); + virtual ~Uploader(); + + /// Get the number of bytes uploaded. + Uint64 bytesUploaded() const {return uploaded;} + + /// Get the upload rate of all Peers combined. + Uint32 uploadRate() const; + + /// Set the number of bytes which have been uploaded. + void setBytesUploaded(Uint64 b) {uploaded = b;} + public slots: + /** + * Update every PeerUploader. + * @param opt_unchoked ID of optimisticly unchoked peer + */ + void update(Uint32 opt_unchoked); + + private: + ChunkManager & cman; + PeerManager & pman; + Uint64 uploaded; + }; + +} + +#endif diff --git a/libktorrent/torrent/upspeedestimater.cpp b/libktorrent/torrent/upspeedestimater.cpp new file mode 100644 index 0000000..0d6c544 --- /dev/null +++ b/libktorrent/torrent/upspeedestimater.cpp @@ -0,0 +1,148 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <math.h> +#include <util/functions.h> +#include <util/log.h> +#include <torrent/globals.h> +#include "upspeedestimater.h" + +namespace bt +{ + + UpSpeedEstimater::UpSpeedEstimater() + { + accumulated_bytes = 0; + upload_rate = 0.0; + proto_upload_rate = 0.0; + } + + + UpSpeedEstimater::~UpSpeedEstimater() + {} + + + void UpSpeedEstimater::writeBytes(Uint32 bytes,bool proto) + { + // add entry to outstanding_bytes + Entry e; + e.bytes = bytes; + e.data = !proto; + e.start_time = GetCurrentTime(); + outstanding_bytes.append(e); + } + + void UpSpeedEstimater::bytesWritten(Uint32 bytes) + { + QValueList<Entry>::iterator i = outstanding_bytes.begin(); + TimeStamp now = GetCurrentTime(); + while (bytes > 0 && i != outstanding_bytes.end()) + { + Entry e = *i; + if (e.bytes <= bytes + accumulated_bytes) + { + // first remove outstanding bytes + i = outstanding_bytes.erase(i); + bytes -= e.bytes; + accumulated_bytes = 0; + if (e.data) + { + // if it's data move it to the written_bytes list + // but first store time it takes to send in e.t + e.duration = now - e.start_time; + written_bytes.append(e); + } + else + { + e.duration = now - e.start_time; +#ifdef MEASURE_PROTO_OVERHEAD + proto_bytes.append(e); +#endif + } + } + else + { + accumulated_bytes += bytes; + bytes = 0; + } + } + } + + double UpSpeedEstimater::rate(QValueList<Entry> & el) + { + TimeStamp now = GetCurrentTime(); + const Uint32 INTERVAL = 3000; + + Uint32 tot_bytes = 0; + Uint32 oldest_time = now; + + QValueList<Entry>::iterator i = el.begin(); + while (i != el.end()) + { + Entry & e = *i; + Uint32 end_time = e.start_time + e.duration; + + if (now - end_time > INTERVAL) + { + // get rid of old entries + i = el.erase(i); + } + else if (now - e.start_time <= INTERVAL) + { + // entry was fully sent in the last 3 seconds + // so fully add it + tot_bytes += e.bytes; + if (e.start_time < oldest_time) + oldest_time = e.start_time; + i++; + } + else + { + // entry was partially sent in the last 3 seconds + // so we need to take into account a part of the bytes; + Uint32 part_dur = end_time - (now - INTERVAL); + double dur_perc = (double)part_dur / e.duration; + tot_bytes += (Uint32)ceil(dur_perc * e.bytes); + oldest_time = (now - INTERVAL); + i++; + } + } + + return (double)tot_bytes / (INTERVAL * 0.001); + } + + void UpSpeedEstimater::update() + { + if (!written_bytes.empty()) + { + upload_rate = 0; + upload_rate = rate(written_bytes); + } + + +#ifdef MEASURE_PROTO_OVERHEAD + if (!proto_bytes.empty()) + { + proto_upload_rate = 0; + proto_upload_rate = rate(proto_bytes); + } +#endif + } + +} diff --git a/libktorrent/torrent/upspeedestimater.h b/libktorrent/torrent/upspeedestimater.h new file mode 100644 index 0000000..6503499 --- /dev/null +++ b/libktorrent/torrent/upspeedestimater.h @@ -0,0 +1,86 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef BTUPSPEEDESTIMATER_H +#define BTUPSPEEDESTIMATER_H + +#include <qvaluelist.h> +#include <util/constants.h> + +namespace bt +{ + + /** + * @author Joris Guisson + * + * Measures upload speed. + */ + class UpSpeedEstimater + { + struct Entry + { + Uint32 bytes; + TimeStamp start_time; + Uint32 duration; + bool data; + }; + public: + UpSpeedEstimater(); + virtual ~UpSpeedEstimater(); + + /** + * Start sending bytes. + * @param bytes The number of bytes + * @param rec Wether to record or not (i.e. is this data) + */ + void writeBytes(Uint32 bytes,bool rec); + + /** + * The socket has finished sending bytes. + * @param bytes The number of bytes. + */ + void bytesWritten(Uint32 bytes); + + /** + * Update the upload speed estimater. + */ + void update(); + + /// Get the upload rate + double uploadRate() const {return upload_rate;} + + /// Get the protocol overhead + double protocollOverhead() const {return proto_upload_rate;} + private: + double rate(QValueList<Entry> & el); + + private: + double upload_rate; + double proto_upload_rate; + Uint32 accumulated_bytes; + QValueList<Entry> outstanding_bytes; + QValueList<Entry> written_bytes; +#ifdef MEASURE_PROTO_OVERHEAD + QValueList<Entry> proto_bytes; +#endif + }; + +} + +#endif diff --git a/libktorrent/torrent/utpex.cpp b/libktorrent/torrent/utpex.cpp new file mode 100644 index 0000000..4933218 --- /dev/null +++ b/libktorrent/torrent/utpex.cpp @@ -0,0 +1,155 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <net/address.h> +#include <util/functions.h> +#include <util/log.h> +#include "utpex.h" +#include "peer.h" +#include "packetwriter.h" +#include "bdecoder.h" +#include "bencoder.h" +#include "bnode.h" +#include "peermanager.h" + + +namespace bt +{ + + UTPex::UTPex(Peer* peer,Uint32 id) : peer(peer),id(id),last_updated(0) + {} + + + UTPex::~UTPex() + {} + + + + void UTPex::handlePexPacket(const Uint8* packet,Uint32 size) + { + if (size <= 2 || packet[1] != 1) + return; + + QByteArray tmp; + tmp.setRawData((const char*)packet,size); + BNode* node = 0; + try + { + BDecoder dec(tmp,false,2); + node = dec.decode(); + if (node && node->getType() == BNode::DICT) + { + BDictNode* dict = (BDictNode*)node; + + // ut_pex packet, emit signal to notify PeerManager + BValueNode* val = dict->getValue("added"); + if (val) + { + QByteArray data = val->data().toByteArray(); + peer->emitPex(data); + } + } + } + catch (...) + { + // just ignore invalid packets + Out(SYS_CON|LOG_DEBUG) << "Invalid extended packet" << endl; + } + delete node; + tmp.resetRawData((const char*)packet,size); + } + + bool UTPex::needsUpdate() const + { + return bt::GetCurrentTime() - last_updated >= 60*1000; + } + + void UTPex::update(PeerManager* pman) + { + last_updated = bt::GetCurrentTime(); + + std::map<Uint32,net::Address> added; + std::map<Uint32,net::Address> npeers; + + PeerManager::CItr itr = pman->beginPeerList(); + while (itr != pman->endPeerList()) + { + const Peer* p = *itr; + if (p != peer) + { + npeers.insert(std::make_pair(p->getID(),p->getAddress())); + if (peers.count(p->getID()) == 0) + { + // new one, add to added + added.insert(std::make_pair(p->getID(),p->getAddress())); + } + else + { + // erase from old list, so only the dropped ones are left + peers.erase(p->getID()); + } + } + itr++; + } + + if (!(peers.size() == 0 && added.size() == 0)) + { + // encode the whole lot + QByteArray data; + BEncoder enc(new BEncoderBufferOutput(data)); + enc.beginDict(); + enc.write("added"); + encode(enc,added); + enc.write("added.f"); // no idea what this added.f thing means + enc.write(""); + enc.write("dropped"); + encode(enc,peers); + enc.end(); + + peer->getPacketWriter().sendExtProtMsg(id,data); + } + + peers = npeers; + } + + void UTPex::encode(BEncoder & enc,const std::map<Uint32,net::Address> & ps) + { + if (ps.size() == 0) + { + enc.write(""); + return; + } + + Uint8* buf = new Uint8[ps.size() * 6]; + Uint32 size = 0; + + std::map<Uint32,net::Address>::const_iterator i = ps.begin(); + while (i != ps.end()) + { + const net::Address & addr = i->second; + WriteUint32(buf,size,addr.ip()); + WriteUint16(buf,size + 4,addr.port()); + size += 6; + i++; + } + + enc.write(buf,size); + delete [] buf; + } +} diff --git a/libktorrent/torrent/utpex.h b/libktorrent/torrent/utpex.h new file mode 100644 index 0000000..cab2f39 --- /dev/null +++ b/libktorrent/torrent/utpex.h @@ -0,0 +1,71 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef BTUTPEX_H +#define BTUTPEX_H + +#include <map> +#include <net/address.h> +#include <util/constants.h> + +namespace bt +{ + class Peer; + class PeerManager; + class BEncoder; + + /** + * @author Joris Guisson <joris.guisson@gmail.com> + * + * Class which handles µTorrent's peer exchange + */ + class UTPex + { + public: + UTPex(Peer* peer,Uint32 id); + virtual ~UTPex(); + + /** + * Handle a PEX packet + * @param packet The packet + * @param size The size of the packet + */ + void handlePexPacket(const Uint8* packet,Uint32 size); + + /// Do we need to update PEX (should happen every minute) + bool needsUpdate() const; + + /// Send a new PEX packet to the Peer + void update(PeerManager* pman); + + /// Change the ID used in the extended packets + void changeID(Uint32 nid) {id = nid;} + private: + void encode(BEncoder & enc,const std::map<Uint32,net::Address> & ps); + + private: + Peer* peer; + Uint32 id; + std::map<Uint32,net::Address> peers; + TimeStamp last_updated; + }; + +} + +#endif diff --git a/libktorrent/torrent/value.cpp b/libktorrent/torrent/value.cpp new file mode 100644 index 0000000..df063ab --- /dev/null +++ b/libktorrent/torrent/value.cpp @@ -0,0 +1,91 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <qtextcodec.h> +#include "value.h" + +namespace bt +{ + + Value::Value() : type(INT),ival(0),big_ival(0) + {} + + Value::Value(int val) : type(INT),ival(val),big_ival(val) + {} + + Value::Value(Int64 val) : type(INT64),big_ival(val) + {} + + Value::Value(const QByteArray & val) : type(STRING),ival(0),strval(val),big_ival(0) + {} + + Value::Value(const Value & val) + : type(val.type),ival(val.ival),strval(val.strval),big_ival(val.big_ival) + {} + + Value::~Value() + {} + + + QString Value::toString(const QString & encoding) const + { + if (encoding.isNull() || encoding.isEmpty()) + return toString(); + + QTextCodec* tc = QTextCodec::codecForName(encoding.ascii()); + if (!tc) + return toString(); + + return tc->toUnicode(strval); + } + + + Value & Value::operator = (const Value & val) + { + type = val.type; + ival = val.ival; + strval = val.strval; + big_ival = val.big_ival; + return *this; + } + + Value & Value::operator = (Int32 val) + { + type = INT; + ival = val; + big_ival = val; + return *this; + } + + Value & Value::operator = (Int64 val) + { + type = INT64; + big_ival = val; + return *this; + } + + Value & Value::operator = (const QByteArray & val) + { + type = STRING; + strval = val; + big_ival = 0; + return *this; + } + +} diff --git a/libktorrent/torrent/value.h b/libktorrent/torrent/value.h new file mode 100644 index 0000000..cd7c879 --- /dev/null +++ b/libktorrent/torrent/value.h @@ -0,0 +1,67 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef BTVALUE_H +#define BTVALUE_H + +#include <qstring.h> +#include <util/constants.h> + +namespace bt +{ + + /** + @author Joris Guisson + */ + class Value + { + public: + enum Type + { + STRING,INT,INT64 + }; + + + Value(); + Value(int val); + Value(Int64 val); + Value(const QByteArray & val); + Value(const Value & val); + ~Value(); + + Value & operator = (const Value & val); + Value & operator = (Int32 val); + Value & operator = (Int64 val); + Value & operator = (const QByteArray & val); + + Type getType() const {return type;} + Int32 toInt() const {return ival;} + Int64 toInt64() const {return big_ival;} + QString toString() const {return QString(strval);} + QString toString(const QString & encoding) const; + QByteArray toByteArray() const {return strval;} + private: + Type type; + Int32 ival; + QByteArray strval; + Int64 big_ival; + }; +} + +#endif diff --git a/libktorrent/util/Makefile.am b/libktorrent/util/Makefile.am new file mode 100644 index 0000000..c7f46ae --- /dev/null +++ b/libktorrent/util/Makefile.am @@ -0,0 +1,18 @@ +INCLUDES = -I$(srcdir)/../../libktorrent $(all_includes) + +METASOURCES = AUTO + +noinst_LTLIBRARIES = libutil.la +libutil_la_LDFLAGS = $(all_libraries) + + +libutil_la_SOURCES = array.cpp autorotatelogjob.cpp bitset.cpp error.cpp \ + file.cpp fileops.cpp functions.cpp httprequest.cpp log.cpp mmapfile.cpp \ + profiler.cpp ptrmap.cpp sha1hash.cpp sha1hashgen.cpp timer.cpp urlencoder.cpp \ + waitjob.cpp + + + + +KDE_CXXFLAGS = $(USE_EXCEPTIONS) $(USE_RTTI) +noinst_HEADERS = autorotatelogjob.h profiler.h diff --git a/libktorrent/util/array.cpp b/libktorrent/util/array.cpp new file mode 100644 index 0000000..e57091d --- /dev/null +++ b/libktorrent/util/array.cpp @@ -0,0 +1,27 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 "array.h" + +namespace bt +{ + + + +} diff --git a/libktorrent/util/array.h b/libktorrent/util/array.h new file mode 100644 index 0000000..1694e2a --- /dev/null +++ b/libktorrent/util/array.h @@ -0,0 +1,73 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef BTARRAY_H +#define BTARRAY_H + +#include "constants.h" + +namespace bt +{ + + /** + * @author Joris Guisson + * + * Template array classes, makes creating dynamic buffers easier + * and safer. + */ + template<class T> + class Array + { + Uint32 num; + T* data; + public: + Array(Uint32 num = 0) : num(num),data(0) + { + if (num > 0) + data = new T[num]; + } + + ~Array() + { + delete [] data; + } + + T & operator [] (Uint32 i) {return data[i];} + const T & operator [] (Uint32 i) const {return data[i];} + + operator const T* () const {return data;} + operator T* () {return data;} + + /// Get the number of elements in the array + Uint32 size() const {return num;} + + /** + * Fill the array with a value + * @param val The value + */ + void fill(T val) + { + for (Uint32 i = 0;i < num;i++) + data[i] = val; + } + }; + +} + +#endif diff --git a/libktorrent/util/autorotatelogjob.cpp b/libktorrent/util/autorotatelogjob.cpp new file mode 100644 index 0000000..c43e304 --- /dev/null +++ b/libktorrent/util/autorotatelogjob.cpp @@ -0,0 +1,88 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <kurl.h> +#include <kprocess.h> +#include <util/fileops.h> +#include "autorotatelogjob.h" +#include "log.h" + +namespace bt +{ + + AutoRotateLogJob::AutoRotateLogJob(const QString & file,Log* lg) + : KIO::Job(false),file(file),cnt(10),lg(lg) + { + update(); + } + + + AutoRotateLogJob::~AutoRotateLogJob() + {} + + void AutoRotateLogJob::kill(bool) + { + m_error = 0; + emitResult(); + } + + void AutoRotateLogJob::update() + { + while (cnt > 1) + { + QString prev = QString("%1-%2.gz").arg(file).arg(cnt - 1); + QString curr = QString("%1-%2.gz").arg(file).arg(cnt); + if (bt::Exists(prev)) // if file exists start the move job + { + KIO::Job* sj = KIO::file_move(KURL::fromPathOrURL(prev),KURL::fromPathOrURL(curr),-1,true,false,false); + connect(sj,SIGNAL(result(KIO::Job*)),this,SLOT(moveJobDone(KIO::Job* ))); + return; + } + else + { + cnt--; + } + } + + if (cnt == 1) + { + // move current log to 1 and zip it + bt::Move(file,file + "-1",true); + KIO::Job* sj = KIO::file_move(KURL::fromPathOrURL(file),KURL::fromPathOrURL(file + "-1"),-1,true,false,false); + connect(sj,SIGNAL(result(KIO::Job*)),this,SLOT(moveJobDone(KIO::Job* ))); + } + else + { + // final log file is moved, now zip it and end the job + std::system(QString("gzip " + KProcess::quote(file + "-1")).local8Bit()); + m_error = 0; + lg->logRotateDone(); + emitResult(); + } + } + + + void AutoRotateLogJob::moveJobDone(KIO::Job*) + { + cnt--; // decrease counter so the newt file will be moved in update + update(); // don't care about result of job + } + +} +#include "autorotatelogjob.moc" diff --git a/libktorrent/util/autorotatelogjob.h b/libktorrent/util/autorotatelogjob.h new file mode 100644 index 0000000..11cf06a --- /dev/null +++ b/libktorrent/util/autorotatelogjob.h @@ -0,0 +1,59 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef BTAUTOROTATELOGJOB_H +#define BTAUTOROTATELOGJOB_H + +#include <kio/job.h> +#include <cstdlib> + +namespace bt +{ + class Log; + + /** + @author Joris Guisson <joris.guisson@gmail.com> + + Job which handles the rotation of the log file. + This Job must do several move jobs which must be done sequentially. + */ + class AutoRotateLogJob : public KIO::Job + { + Q_OBJECT + public: + AutoRotateLogJob(const QString & file,Log* lg); + virtual ~AutoRotateLogJob(); + + virtual void kill(bool quietly=true); + + private slots: + void moveJobDone(KIO::Job*); + + private: + void update(); + + private: + QString file; + int cnt; + Log* lg; + }; + +} + +#endif diff --git a/libktorrent/util/bitset.cpp b/libktorrent/util/bitset.cpp new file mode 100644 index 0000000..6139e01 --- /dev/null +++ b/libktorrent/util/bitset.cpp @@ -0,0 +1,111 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <algorithm> +#include "bitset.h" +#include <string.h> + +namespace bt +{ + BitSet BitSet::null; + + BitSet::BitSet(Uint32 num_bits) : num_bits(num_bits),data(0) + { + num_bytes = (num_bits / 8) + ((num_bits % 8 > 0) ? 1 : 0); + data = new Uint8[num_bytes]; + std::fill(data,data+num_bytes,0x00); + num_on = 0; + } + + BitSet::BitSet(const Uint8* d,Uint32 num_bits) : num_bits(num_bits),data(0) + { + num_bytes = (num_bits / 8) + ((num_bits % 8 > 0) ? 1 : 0); + data = new Uint8[num_bytes]; + memcpy(data,d,num_bytes); + num_on = 0; + Uint32 i = 0; + while (i < num_bits) + { + if (get(i)) + num_on++; + i++; + } + } + + BitSet::BitSet(const BitSet & bs) : num_bits(bs.num_bits),num_bytes(bs.num_bytes),data(0),num_on(bs.num_on) + { + data = new Uint8[num_bytes]; + std::copy(bs.data,bs.data+num_bytes,data); + } + + BitSet::~BitSet() + { + delete [] data; + } + + + + BitSet & BitSet::operator = (const BitSet & bs) + { + if (data) + delete [] data; + num_bytes = bs.num_bytes; + num_bits = bs.num_bits; + data = new Uint8[num_bytes]; + std::copy(bs.data,bs.data+num_bytes,data); + num_on = bs.num_on; + return *this; + } + + void BitSet::setAll(bool on) + { + std::fill(data,data+num_bytes,on ? 0xFF : 0x00); + num_on = on ? num_bits : 0; + } + + void BitSet::clear() + { + setAll(false); + } + + void BitSet::orBitSet(const BitSet & other) + { + Uint32 i = 0; + while (i < num_bits) + { + bool val = get(i) || other.get(i); + set(i,val); + i++; + } + } + + bool BitSet::allOn() const + { + return num_on == num_bits; + } + + bool BitSet::operator == (const BitSet & bs) + { + if (this->getNumBits() != bs.getNumBits()) + return false; + + return memcmp(data,bs.data,num_bytes) == 0; + } +} + diff --git a/libktorrent/util/bitset.h b/libktorrent/util/bitset.h new file mode 100644 index 0000000..32e7e48 --- /dev/null +++ b/libktorrent/util/bitset.h @@ -0,0 +1,157 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef BTBITSET_H +#define BTBITSET_H + +#include "constants.h" + +namespace bt +{ + + /** + * @author Joris Guisson + * @brief Simple implementation of a BitSet + * + * Simple implementation of a BitSet, can only turn on and off bits. + * BitSet's are used to indicate which chunks we have or not. + */ + class BitSet + { + Uint32 num_bits,num_bytes; + Uint8* data; + Uint32 num_on; + public: + /** + * Constructor. + * @param num_bits The number of bits + */ + BitSet(Uint32 num_bits = 8); + + /** + * Manually set data. + * @param data The data + * @param num_bits The number of bits + */ + BitSet(const Uint8* data,Uint32 num_bits); + + /** + * Copy constructor. + * @param bs BitSet to copy + * @return + */ + BitSet(const BitSet & bs); + virtual ~BitSet(); + + /// See if the BitSet is null + bool isNull() const {return num_bits == 0;} + + /** + * Get the value of a bit, false means 0, true 1. + * @param i Index of Bit + */ + bool get(Uint32 i) const; + + /** + * Set the value of a bit, false means 0, true 1. + * @param i Index of Bit + * @param on False means 0, true 1 + */ + void set(Uint32 i,bool on); + + /// Set all bits on or off + void setAll(bool on); + + Uint32 getNumBytes() const {return num_bytes;} + Uint32 getNumBits() const {return num_bits;} + const Uint8* getData() const {return data;} + Uint8* getData() {return data;} + + /// Get the number of on bits + Uint32 numOnBits() const {return num_on;} + + /** + * Set all bits to 0 + */ + void clear(); + + /** + * or this BitSet with another. + * @param other The other BitSet + */ + void orBitSet(const BitSet & other); + + /** + * Assignment operator. + * @param bs BitSet to copy + * @return *this + */ + BitSet & operator = (const BitSet & bs); + + /// Check if all bit are set to 1 + bool allOn() const; + + /** + * Check for equality of bitsets + * @param bs BitSet to compare + * @return true if equal + */ + bool operator == (const BitSet & bs); + + /** + * Opposite of operator == + */ + bool operator != (const BitSet & bs) {return ! operator == (bs);} + + static BitSet null; + }; + + inline bool BitSet::get(Uint32 i) const + { + if (i >= num_bits) + return false; + + Uint32 byte = i / 8; + Uint32 bit = i % 8; + Uint8 b = data[byte] & (0x01 << (7 - bit)); + return b != 0x00; + } + + inline void BitSet::set(Uint32 i,bool on) + { + if (i >= num_bits) + return; + + Uint32 byte = i / 8; + Uint32 bit = i % 8; + if (on && !get(i)) + { + num_on++; + data[byte] |= (0x01 << (7 - bit)); + } + else if (!on && get(i)) + { + num_on--; + Uint8 b = (0x01 << (7 - bit)); + data[byte] &= (~b); + } + } +} + +#endif diff --git a/libktorrent/util/constants.h b/libktorrent/util/constants.h new file mode 100644 index 0000000..e663978 --- /dev/null +++ b/libktorrent/util/constants.h @@ -0,0 +1,96 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef BTCONSTANTS_H +#define BTCONSTANTS_H + +#include <qglobal.h> + +namespace bt +{ + typedef Q_UINT64 Uint64; + typedef Q_UINT32 Uint32; + typedef Q_UINT16 Uint16; + typedef Q_UINT8 Uint8; + + typedef Q_INT64 Int64; + typedef Q_INT32 Int32; + typedef Q_INT16 Int16; + typedef Q_INT8 Int8; + + typedef Uint64 TimeStamp; + + typedef enum + { + /* These are the old values, for compatability reasons with old chunk_info files we leave them here : + PREVIEW_PRIORITY = 4, + FIRST_PRIORITY = 3, + NORMAL_PRIORITY = 2, + LAST_PRIORITY = 1, + EXCLUDED = 0, + ONLY_SEED_PRIORITY = -1 + */ + // make sure new values are different from old values + // also leave some room if we want to add new priorities in the future + PREVIEW_PRIORITY = 60, + FIRST_PRIORITY = 50, + NORMAL_PRIORITY = 40, + LAST_PRIORITY = 30, + ONLY_SEED_PRIORITY = 20, + EXCLUDED = 10 + }Priority; + + enum ConfirmationResult + { + KEEP_DATA, + THROW_AWAY_DATA, + CANCELED + }; + + const Uint32 MAX_MSGLEN = 9 + 131072; + const Uint16 MIN_PORT = 6881; + const Uint16 MAX_PORT = 6889; + const Uint32 MAX_PIECE_LEN = 16384; + + const Uint8 CHOKE = 0; + const Uint8 UNCHOKE = 1; + const Uint8 INTERESTED = 2; + const Uint8 NOT_INTERESTED = 3; + const Uint8 HAVE = 4; + const Uint8 BITFIELD = 5; + const Uint8 REQUEST = 6; + const Uint8 PIECE = 7; + const Uint8 CANCEL = 8; + const Uint8 PORT = 9; + const Uint8 SUGGEST_PIECE = 13; + const Uint8 HAVE_ALL = 14; + const Uint8 HAVE_NONE = 15; + const Uint8 REJECT_REQUEST = 16; + const Uint8 ALLOWED_FAST = 17; + const Uint8 EXTENDED = 20; // extension protocol message + + + // flags for things which a peer supports + const Uint32 DHT_SUPPORT = 0x01; + const Uint32 EXT_PROT_SUPPORT = 0x10; + const Uint32 FAST_EXT_SUPPORT = 0x04; +} + + +#endif diff --git a/libktorrent/util/error.cpp b/libktorrent/util/error.cpp new file mode 100644 index 0000000..bb981db --- /dev/null +++ b/libktorrent/util/error.cpp @@ -0,0 +1,33 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 "error.h" + +namespace bt +{ + + Error::Error(const QString & msg) : msg(msg) + {} + + + Error::~Error() + {} + + +} diff --git a/libktorrent/util/error.h b/libktorrent/util/error.h new file mode 100644 index 0000000..8b089e4 --- /dev/null +++ b/libktorrent/util/error.h @@ -0,0 +1,44 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef BTERROR_H +#define BTERROR_H + +#include <qstring.h> + +namespace bt +{ + + /** + @author Joris Guisson + */ + class Error + { + QString msg; + public: + Error(const QString & msg); + virtual ~Error(); + + QString toString() const {return msg;} + + }; + +} + +#endif diff --git a/libktorrent/util/file.cpp b/libktorrent/util/file.cpp new file mode 100644 index 0000000..b898e07 --- /dev/null +++ b/libktorrent/util/file.cpp @@ -0,0 +1,150 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <qfile.h> +#include "config.h" +#include <klocale.h> +#include <string.h> +#include <errno.h> +#include <stdio.h> +#include <torrent/globals.h> +#include "file.h" +#include "error.h" +#include "log.h" + +namespace bt +{ + + File::File() : fptr(0) + {} + + + File::~File() + { + close(); + } + + bool File::open(const QString & file,const QString & mode) + { + this->file = file; + if (fptr) + close(); +#if HAVE_FOPEN64 + fptr = fopen64(QFile::encodeName(file),mode.ascii()); +#else + fptr = fopen(QFile::encodeName(file),mode.ascii()); +#endif + return fptr != 0; + } + + void File::close() + { + if (fptr) + { + fclose(fptr); + fptr = 0; + } + } + + void File::flush() + { + if (fptr) + fflush(fptr); + } + + Uint32 File::write(const void* buf,Uint32 size) + { + if (!fptr) + return 0; + + Uint32 ret = fwrite(buf,1,size,fptr); + if (ret != size) + { + if (errno == ENOSPC) + Out() << "Disk full !" << endl; + + throw Error(i18n("Cannot write to %1 : %2").arg(file).arg(strerror(errno))); + } + return ret; + } + + Uint32 File::read(void* buf,Uint32 size) + { + if (!fptr) + return 0; + + Uint32 ret = fread(buf,1,size,fptr); + if (ferror(fptr)) + { + clearerr(fptr); + throw Error(i18n("Cannot read from %1").arg(file)); + } + return ret; + } + + Uint64 File::seek(SeekPos from,Int64 num) + { + // printf("sizeof(off_t) = %i\n",sizeof(__off64_t)); + if (!fptr) + return 0; + + int p = SEEK_CUR; // use a default to prevent compiler warning + switch (from) + { + case BEGIN : p = SEEK_SET; break; + case END : p = SEEK_END; break; + case CURRENT : p = SEEK_CUR; break; + default: + break; + } +#if HAVE_FSEEKO64 + fseeko64(fptr,num,p); + return ftello64(fptr); +#else + fseeko(fptr,num,p); + return ftello(fptr); +#endif + } + + bool File::eof() const + { + if (!fptr) + return true; + + return feof(fptr) != 0; + } + + Uint64 File::tell() const + { + if (!fptr) + return 0; + + return ftello(fptr); + } + + QString File::errorString() const + { + return QString(strerror(errno)); + } +} diff --git a/libktorrent/util/file.h b/libktorrent/util/file.h new file mode 100644 index 0000000..323a3a7 --- /dev/null +++ b/libktorrent/util/file.h @@ -0,0 +1,114 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef BTFILE_H +#define BTFILE_H + +#include <stdio.h> +#include <qstring.h> +#include "constants.h" + +namespace bt +{ + + /** + * @author Joris Guisson + * @brief Wrapper class for stdio's FILE + * + * Wrapper class for stdio's FILE. + */ + class File + { + FILE* fptr; + QString file; + public: + /** + * Constructor. + */ + File(); + + /** + * Destructor, closes the file. + */ + virtual ~File(); + + /** + * Open the file similar to fopen + * @param file Filename + * @param mode Mode + * @return true upon succes + */ + bool open(const QString & file,const QString & mode); + + /** + * Close the file. + */ + void close(); + + /** + * Flush the file. + */ + void flush(); + + /** + * Write a bunch of data. If anything goes wrong + * an Error will be thrown. + * @param buf The data + * @param size Size of the data + * @return The number of bytes written + */ + Uint32 write(const void* buf,Uint32 size); + + /** + * Read a bunch of data. If anything goes wrong + * an Error will be thrown. + * @param buf The buffer to store the data + * @param size Size of the buffer + * @return The number of bytes read + */ + Uint32 read(void* buf,Uint32 size); + + enum SeekPos + { + BEGIN, + END, + CURRENT + }; + + /** + * Seek in the file. + * @param from Position to seek from + * @param num Number of bytes to move + * @return New position + */ + Uint64 seek(SeekPos from,Int64 num); + + /// Check to see if we are at the end of the file. + bool eof() const; + + /// Get the current position in the file. + Uint64 tell() const; + + /// Get the error string. + QString errorString() const; + }; + +} + +#endif diff --git a/libktorrent/util/fileops.cpp b/libktorrent/util/fileops.cpp new file mode 100644 index 0000000..3fcf03d --- /dev/null +++ b/libktorrent/util/fileops.cpp @@ -0,0 +1,466 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <klocale.h> +#include <kio/netaccess.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <fcntl.h> +#include <qdir.h> +#include <qfile.h> +#include <qstringlist.h> +#include "fileops.h" +#include "error.h" +#include "log.h" +#include <torrent/globals.h> +#include "file.h" +#include "array.h" + +#ifdef HAVE_XFS_XFS_H + +#if !defined(HAVE___S64) || !defined(HAVE___U64) +#include <stdint.h> +#endif + +#ifndef HAVE___U64 +typedef uint64_t __u64; +#endif + +#ifndef HAVE___S64 +typedef int64_t __s64; +#endif + +#include <xfs/xfs.h> +#endif + +#ifndef O_LARGEFILE +#define O_LARGEFILE 0 +#endif + +#if HAVE_STATVFS +#include <sys/statvfs.h> +#else +#include <sys/param.h> +#include <sys/mount.h> +#endif + +namespace bt +{ + void MakeDir(const QString & dir,bool nothrow) + { + if (mkdir(QFile::encodeName(dir),0777) < -1) + { + if (!nothrow) + throw Error(i18n("Cannot create directory %1: %2") + .arg(dir).arg(strerror(errno))); + else + { + Out() << QString("Error : Cannot create directory %1 : %2").arg(dir).arg(strerror(errno))<< endl; + } + } + } + + void SymLink(const QString & link_to,const QString & link_url,bool nothrow) + { + if (symlink(QFile::encodeName(link_to),QFile::encodeName(link_url)) != 0) + { + if (!nothrow) + throw Error(i18n("Cannot symlink %1 to %2: %3") + .arg(link_url.utf8()).arg(link_to.utf8()) + .arg(strerror(errno))); + else + Out() << QString("Error : Cannot symlink %1 to %2: %3") + .arg(link_url.utf8()).arg(link_to.utf8()) + .arg(strerror(errno)) << endl; + } + } + + void Move(const QString & src,const QString & dst,bool nothrow) + { + // Out() << "Moving " << src << " -> " << dst << endl; + if (!KIO::NetAccess::move(KURL::fromPathOrURL(src),KURL::fromPathOrURL(dst),0)) + { + if (!nothrow) + throw Error(i18n("Cannot move %1 to %2: %3") + .arg(src).arg(dst) + .arg(KIO::NetAccess::lastErrorString())); + else + Out() << QString("Error : Cannot move %1 to %2: %3") + .arg(src).arg(dst) + .arg(KIO::NetAccess::lastErrorString()) << endl; + + } + } + + void CopyFile(const QString & src,const QString & dst,bool nothrow) + { + if (!KIO::NetAccess::file_copy(KURL::fromPathOrURL(src),KURL::fromPathOrURL(dst))) + { + if (!nothrow) + throw Error(i18n("Cannot copy %1 to %2: %3") + .arg(src).arg(dst) + .arg(KIO::NetAccess::lastErrorString())); + else + Out() << QString("Error : Cannot copy %1 to %2: %3") + .arg(src).arg(dst) + .arg(KIO::NetAccess::lastErrorString()) << endl; + + } + } + + void CopyDir(const QString & src,const QString & dst,bool nothrow) + { + if (!KIO::NetAccess::dircopy(KURL::fromPathOrURL(src),KURL::fromPathOrURL(dst),0)) + { + if (!nothrow) + throw Error(i18n("Cannot copy %1 to %2: %3") + .arg(src).arg(dst) + .arg(KIO::NetAccess::lastErrorString())); + else + Out() << QString("Error : Cannot copy %1 to %2: %3") + .arg(src).arg(dst) + .arg(KIO::NetAccess::lastErrorString()) << endl; + + } + } + + bool Exists(const QString & url) + { + // Out() << "Testing if " << url << " exists " << endl; + if (access(QFile::encodeName(url),F_OK) < 0) + { + // Out() << "No " << endl; + return false; + } + else + { + // Out() << "Yes " << endl; + return true; + } + } + + static bool DelDir(const QString & fn) + { + QDir d(fn); + QStringList subdirs = d.entryList(QDir::Dirs); + + for (QStringList::iterator i = subdirs.begin(); i != subdirs.end();i++) + { + QString entry = *i; + + if (entry == ".." || entry == ".") + continue; + + if (!DelDir(d.absFilePath(entry))) + { + Out(SYS_GEN|LOG_DEBUG) << "Delete of " << fn << "/" << entry << " failed !" << endl; + return false; + } + } + + QStringList files = d.entryList(QDir::Files | QDir::System | QDir::Hidden); + for (QStringList::iterator i = files.begin(); i != files.end();i++) + { + QString entry = *i; + + if (remove(QFile::encodeName(d.absFilePath(entry))) < 0) + { + Out(SYS_GEN|LOG_DEBUG) << "Delete of " << fn << "/" << entry << " failed !" << endl; + return false; + } + } + + if (!d.rmdir(d.absPath())) + { + Out(SYS_GEN|LOG_DEBUG) << "Failed to remove " << d.absPath() << endl; + return false; + } + + return true; + } + + void Delete(const QString & url,bool nothrow) + { + QCString fn = QFile::encodeName(url); +#if HAVE_STAT64 + struct stat64 statbuf; + if (lstat64(fn, &statbuf) < 0) + return; +#else + struct stat statbuf; + if (lstat(fn, &statbuf) < 0) + return; +#endif + + bool ok = true; + // first see if it is a directory + if (S_ISDIR(statbuf.st_mode)) + { + ok = DelDir(url); + } + else + { + ok = remove(fn) >= 0; + } + + if (!ok) + { + QString err = i18n("Cannot delete %1: %2") + .arg(url) + .arg(strerror(errno)); + if (!nothrow) + throw Error(err); + else + Out() << "Error : " << err << endl; + } + } + + void Touch(const QString & url,bool nothrow) + { + if (Exists(url)) + return; + + File fptr; + if (!fptr.open(url,"wb")) + { + if (!nothrow) + throw Error(i18n("Cannot create %1: %2") + .arg(url) + .arg(fptr.errorString())); + else + Out() << "Error : Cannot create " << url << " : " + << fptr.errorString() << endl; + + } + } + + Uint64 FileSize(const QString & url) + { + int ret = 0; +#if HAVE_STAT64 + struct stat64 sb; + ret = stat64(QFile::encodeName(url),&sb); +#else + struct stat sb; + ret = stat(QFile::encodeName(url),&sb); +#endif + if (ret < 0) + throw Error(i18n("Cannot calculate the filesize of %1: %2") + .arg(url).arg(strerror(errno))); + + return (Uint64)sb.st_size; + } + + Uint64 FileSize(int fd) + { + int ret = 0; +#if HAVE_STAT64 + struct stat64 sb; + ret = fstat64(fd,&sb); +#else + struct stat sb; + ret = fstat(fd,&sb); +#endif + if (ret < 0) + throw Error(i18n("Cannot calculate the filesize : %2").arg(strerror(errno))); + + return (Uint64)sb.st_size; + } + + bool FatPreallocate(int fd,Uint64 size) + { + try + { + SeekFile(fd, size - 1, SEEK_SET); + char zero = 0; + if (write(fd, &zero, 1) == -1) + return false; + + TruncateFile(fd,size,true); + } + catch (bt::Error & e) + { + Out() << e.toString() << endl; + return false; + } + return true; + } + + bool FatPreallocate(const QString & path,Uint64 size) + { + int fd = ::open(QFile::encodeName(path),O_RDWR | O_LARGEFILE); + if (fd < 0) + throw Error(i18n("Cannot open %1 : %2").arg(path).arg(strerror(errno))); + + bool ret = FatPreallocate(fd,size); + close(fd); + return ret; + } + +#ifdef HAVE_XFS_XFS_H + + bool XfsPreallocate(int fd, Uint64 size) + { + if( ! platform_test_xfs_fd(fd) ) + { + return false; + } + + xfs_flock64_t allocopt; + allocopt.l_whence = 0; + allocopt.l_start = 0; + allocopt.l_len = size; + + return (! static_cast<bool>(xfsctl(0, fd, XFS_IOC_RESVSP64, &allocopt)) ); + + } + + bool XfsPreallocate(const QString & path, Uint64 size) + { + int fd = ::open(QFile::encodeName(path), O_RDWR | O_LARGEFILE); + if (fd < 0) + throw Error(i18n("Cannot open %1 : %2").arg(path).arg(strerror(errno))); + + bool ret = XfsPreallocate(fd,size); + close(fd); + return ret; + } + +#endif + + void TruncateFile(int fd,Uint64 size,bool quick) + { + if (FileSize(fd) == size) + return; + + if (quick) + { +#if HAVE_FTRUNCATE64 + if (ftruncate64(fd,size) == -1) +#else + if (ftruncate(fd,size) == -1) +#endif + throw Error(i18n("Cannot expand file : %1").arg(strerror(errno))); + } + else + { +#if HAVE_POSIX_FALLOCATE64 + if (posix_fallocate64(fd,0,size) != 0) + throw Error(i18n("Cannot expand file : %1").arg(strerror(errno))); +#elif HAVE_POSIX_FALLOCATE + if (posix_fallocate(fd,0,size) != 0) + throw Error(i18n("Cannot expand file : %1").arg(strerror(errno))); +#else + SeekFile(fd,0,SEEK_SET); + bt::Array<Uint8> buf(4096); + buf.fill(0); + + Uint64 written = 0; + while (written < size) + { + int to_write = size - written; + if (to_write > 4096) + to_write = 4096; + + int ret = write(fd,buf,to_write); + if (ret < 0) + throw Error(i18n("Cannot expand file : %1").arg(strerror(errno))); + else if (ret == 0 || ret != (int)to_write) + throw Error(i18n("Cannot expand file").arg(strerror(errno))); + else + written += to_write; + } +#endif + } + } + + void TruncateFile(const QString & path,Uint64 size) + { + int fd = ::open(QFile::encodeName(path),O_RDWR | O_LARGEFILE); + if (fd < 0) + throw Error(i18n("Cannot open %1 : %2").arg(path).arg(strerror(errno))); + + try + { + TruncateFile(fd,size,true); + close(fd); + } + catch (...) + { + close(fd); + throw; + } + } + + void SeekFile(int fd,Int64 off,int whence) + { +#if HAVE_LSEEK64 + if (lseek64(fd,off,whence) == -1) +#else + if (lseek(fd,off,whence) == -1) +#endif + throw Error(i18n("Cannot seek in file : %1").arg(strerror(errno))); + } + + bool FreeDiskSpace(const QString & path,Uint64 & bytes_free) + { +#if HAVE_STATVFS +#if HAVE_STATVFS64 + struct statvfs64 stfs; + if (statvfs64(path.local8Bit(), &stfs) == 0) +#else + struct statvfs stfs; + if (statvfs(path.local8Bit(), &stfs) == 0) +#endif + { + bytes_free = ((Uint64)stfs.f_bavail) * ((Uint64)stfs.f_frsize); + return true; + } + else + { + Out(SYS_GEN|LOG_DEBUG) << "Error : statvfs for " << path << " failed : " + << QString(strerror(errno)) << endl; + + return false; + } +#else + struct statfs stfs; + if (statfs(path.local8Bit(), &stfs) == 0) + { + bytes_free = ((Uint64)stfs.f_bavail) * ((Uint64)stfs.f_bsize); + return true; + } + else + { + Out(SYS_GEN|LOG_DEBUG) << "Error : statfs for " << path << " failed : " + << QString(strerror(errno)) << endl; + + return false; + } +#endif + } +} diff --git a/libktorrent/util/fileops.h b/libktorrent/util/fileops.h new file mode 100644 index 0000000..253ee96 --- /dev/null +++ b/libktorrent/util/fileops.h @@ -0,0 +1,165 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef BTFILEOPS_H +#define BTFILEOPS_H + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <util/constants.h> +class QString; + +namespace bt +{ + + /** + * Creates a directory. Convenience function around + * KIO::NetAccess::mkdir . + * @param dir The url of the dir + * @param nothrow wether or not we shouldn't throw an Error upon failure + * @throw Error upon error + */ + void MakeDir(const QString & dir,bool nothrow = false); + + /** + * Create a symbolic link @a link_url which links to @a link_to + * @param link_to The file to link to + * @param link_url The link url + * @param nothrow wether or not we shouldn't throw an Error upon failure + */ + void SymLink(const QString & link_to,const QString & link_url,bool nothrow = false); + + /** + * Move a file/dir from one location to another + * @param src The source file + * @param dst The destination file / directory + * @param nothrow wether or not we shouldn't throw an Error upon failure + */ + void Move(const QString & src,const QString & dst,bool nothrow = false); + + /** + * Copy a file. + * @param src The source file + * @param dst The destination dir/file + * @param nothrow wether or not we shouldn't throw an Error upon failure + */ + void CopyFile(const QString & src,const QString & dst,bool nothrow = false); + + /** + * Copy a file or directory + * @param src The source file + * @param dst The destination dir/file + * @param nothrow wether or not we shouldn't throw an Error upon failure + */ + void CopyDir(const QString & src,const QString & dst,bool nothrow = false); + + /** + * Check wether a file/dir exists + * @param url The file/dir + * @return true if it exits + */ + bool Exists(const QString & url); + + /** + * Delete a file or directory. + * @param url The url of the file/dir + * @param nothrow wether or not we shouldn't throw an Error upon failure + */ + void Delete(const QString & url,bool nothrow = false); + + /** + * Try to create a file. Doesn't do anything if the file + * already exists. + * @param url The url of the file + * @param nothrow wether or not we shouldn't throw an Error upon failure + */ + void Touch(const QString & url,bool nothrow = false); + + /** + * Calculates the size of a file + * @param url Name of the file + * @return The size of the file + * @throw Error if the file doesn't exist, or something else goes wrong + */ + Uint64 FileSize(const QString & url); + + /** + * Get the size of a file. + * @param fd The file descriptor of the file + * @return The size + * @throw Error if the file doesn't exist, or something else goes wrong + */ + Uint64 FileSize(int fd); + + /** + * Truncate a file (wrapper around ftruncate) + * @param fd The file descriptor of the file + * @param size The size to truncate to + * @throw Error if the file doesn't exist, or something else goes wrong + */ + void TruncateFile(int fd,Uint64 size,bool quick); + + /** + * Truncate a file (wrapper around ftruncate) + * @param fd Path of the file + * @param size The size to truncate to + * @param quick Use the quick way (doesn't prevent fragmentationt) + * @throw Error if the file doesn't exist, or something else goes wrong + */ + void TruncateFile(const QString & path,Uint64 size); + + /** + * Special truncate for FAT file systems. + */ + bool FatPreallocate(int fd,Uint64 size); + + /** + * Special truncate for FAT file systems. + */ + bool FatPreallocate(const QString & path,Uint64 size); + +#ifdef HAVE_XFS_XFS_H + /** + * Special truncate for XFS file systems. + */ + bool XfsPreallocate(int fd,Uint64 size); + + /** + * Special truncate for XFS file systems. + */ + bool XfsPreallocate(const QString & path,Uint64 size); + +#endif + + /** + * Seek in a file, wrapper around lseek + * @param fd The file descriptor + * @param off Offset + * @param whence Position to seek from + * @throw Error if something else goes wrong + */ + void SeekFile(int fd,Int64 off,int whence); + + /// Calculate the number of bytes free on the filesystem path is located + bool FreeDiskSpace(const QString & path,Uint64 & bytes_free); +} + +#endif diff --git a/libktorrent/util/functions.cpp b/libktorrent/util/functions.cpp new file mode 100644 index 0000000..744bf43 --- /dev/null +++ b/libktorrent/util/functions.cpp @@ -0,0 +1,239 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <qhostaddress.h> +#include <errno.h> +#include <sys/time.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/time.h> +#include <sys/resource.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <netdb.h> +#include <kio/netaccess.h> +#include <klocale.h> +#include <kmimetype.h> +#include <kglobal.h> +#include "functions.h" +#include "error.h" +#include "log.h" + +namespace bt +{ + + bool IsMultimediaFile(const QString & filename) + { + KMimeType::Ptr ptr = KMimeType::findByPath(filename); + QString name = ptr->name(); + return name.startsWith("audio") || name.startsWith("video") || name == "application/ogg"; + } + + QHostAddress LookUpHost(const QString & host) + { + struct hostent * he = gethostbyname(host.ascii()); + QHostAddress addr; + if (he) + { + addr.setAddress(inet_ntoa(*((struct in_addr *)he->h_addr))); + } + return addr; + } + + QString DirSeparator() + { + QString tmp; + tmp.append(QDir::separator()); + return tmp; + } + + void WriteUint64(Uint8* buf,Uint32 off,Uint64 val) + { + buf[off + 0] = (Uint8) ((val & 0xFF00000000000000ULL) >> 56); + buf[off + 1] = (Uint8) ((val & 0x00FF000000000000ULL) >> 48); + buf[off + 2] = (Uint8) ((val & 0x0000FF0000000000ULL) >> 40); + buf[off + 3] = (Uint8) ((val & 0x000000FF00000000ULL) >> 32); + buf[off + 4] = (Uint8) ((val & 0x00000000FF000000ULL) >> 24); + buf[off + 5] = (Uint8) ((val & 0x0000000000FF0000ULL) >> 16); + buf[off + 6] = (Uint8) ((val & 0x000000000000FF00ULL) >> 8); + buf[off + 7] = (Uint8) ((val & 0x00000000000000FFULL) >> 0); + } + + Uint64 ReadUint64(const Uint8* buf,Uint64 off) + { + Uint64 tmp = + ((Uint64)buf[off] << 56) | + ((Uint64)buf[off+1] << 48) | + ((Uint64)buf[off+2] << 40) | + ((Uint64)buf[off+3] << 32) | + ((Uint64)buf[off+4] << 24) | + ((Uint64)buf[off+5] << 16) | + ((Uint64)buf[off+6] << 8) | + ((Uint64)buf[off+7] << 0); + + return tmp; + } + + void WriteUint32(Uint8* buf,Uint32 off,Uint32 val) + { + buf[off + 0] = (Uint8) ((val & 0xFF000000) >> 24); + buf[off + 1] = (Uint8) ((val & 0x00FF0000) >> 16); + buf[off + 2] = (Uint8) ((val & 0x0000FF00) >> 8); + buf[off + 3] = (Uint8) (val & 0x000000FF); + } + + Uint32 ReadUint32(const Uint8* buf,Uint32 off) + { + return (buf[off] << 24) | (buf[off+1] << 16) | (buf[off+2] << 8) | buf[off + 3]; + } + + void WriteUint16(Uint8* buf,Uint32 off,Uint16 val) + { + buf[off + 0] = (Uint8) ((val & 0xFF00) >> 8); + buf[off + 1] = (Uint8) (val & 0x000FF); + } + + Uint16 ReadUint16(const Uint8* buf,Uint32 off) + { + return (buf[off] << 8) | buf[off + 1]; + } + + + void WriteInt64(Uint8* buf,Uint32 off,Int64 val) + { + buf[off + 0] = (Uint8) ((val & 0xFF00000000000000ULL) >> 56); + buf[off + 1] = (Uint8) ((val & 0x00FF000000000000ULL) >> 48); + buf[off + 2] = (Uint8) ((val & 0x0000FF0000000000ULL) >> 40); + buf[off + 3] = (Uint8) ((val & 0x000000FF00000000ULL) >> 32); + buf[off + 4] = (Uint8) ((val & 0x00000000FF000000ULL) >> 24); + buf[off + 5] = (Uint8) ((val & 0x0000000000FF0000ULL) >> 16); + buf[off + 6] = (Uint8) ((val & 0x000000000000FF00ULL) >> 8); + buf[off + 7] = (Uint8) ((val & 0x00000000000000FFULL) >> 0); + } + + Int64 ReadInt64(const Uint8* buf,Uint32 off) + { + Int64 tmp = + ((Int64)buf[off] << 56) | + ((Int64)buf[off+1] << 48) | + ((Int64)buf[off+2] << 40) | + ((Int64)buf[off+3] << 32) | + ((Int64)buf[off+4] << 24) | + ((Int64)buf[off+5] << 16) | + ((Int64)buf[off+6] << 8) | + ((Int64)buf[off+7] << 0); + + return tmp; + } + + void WriteInt32(Uint8* buf,Uint32 off,Int32 val) + { + buf[off + 0] = (Uint8) ((val & 0xFF000000) >> 24); + buf[off + 1] = (Uint8) ((val & 0x00FF0000) >> 16); + buf[off + 2] = (Uint8) ((val & 0x0000FF00) >> 8); + buf[off + 3] = (Uint8) (val & 0x000000FF); + } + + Int32 ReadInt32(const Uint8* buf,Uint32 off) + { + return (Int32)(buf[off] << 24) | (buf[off+1] << 16) | (buf[off+2] << 8) | buf[off + 3]; + } + + void WriteInt16(Uint8* buf,Uint32 off,Int16 val) + { + buf[off + 0] = (Uint8) ((val & 0xFF00) >> 8); + buf[off + 1] = (Uint8) (val & 0x000FF); + } + + Int16 ReadInt16(const Uint8* buf,Uint32 off) + { + return (Int16)(buf[off] << 8) | buf[off + 1]; + } + + void UpdateCurrentTime() + { + global_time_stamp = Now(); + } + + TimeStamp global_time_stamp = 0; + + Uint64 Now() + { + struct timeval tv; + gettimeofday(&tv,0); + global_time_stamp = (Uint64)tv.tv_sec * 1000 + (Uint64)tv.tv_usec * 0.001; + return global_time_stamp; + } + + Uint32 MaxOpenFiles() + { + struct rlimit lim; + getrlimit(RLIMIT_NOFILE,&lim); + return lim.rlim_cur; + } + + bool MaximizeLimits() + { + // first get the current limits + struct rlimit lim; + getrlimit(RLIMIT_NOFILE,&lim); + + if (lim.rlim_cur != lim.rlim_max) + { + Out(SYS_GEN|LOG_DEBUG) << "Current limit for number of files : " << lim.rlim_cur + << " (" << lim.rlim_max << " max)" << endl; + lim.rlim_cur = lim.rlim_max; + if (setrlimit(RLIMIT_NOFILE,&lim) < 0) + { + Out(SYS_GEN|LOG_DEBUG) << "Failed to maximize file limit : " + << QString(strerror(errno)) << endl; + return false; + } + } + else + { + Out(SYS_GEN|LOG_DEBUG) << "File limit allready at maximum " << endl; + } + + getrlimit(RLIMIT_DATA,&lim); + if (lim.rlim_cur != lim.rlim_max) + { + Out(SYS_GEN|LOG_DEBUG) << "Current limit for data size : " << lim.rlim_cur + << " (" << lim.rlim_max << " max)" << endl; + lim.rlim_cur = lim.rlim_max; + if (setrlimit(RLIMIT_DATA,&lim) < 0) + { + Out(SYS_GEN|LOG_DEBUG) << "Failed to maximize data limit : " + << QString(strerror(errno)) << endl; + return false; + } + } + else + { + Out(SYS_GEN|LOG_DEBUG) << "Data limit allready at maximum " << endl; + } + + return true; + } + + + + +} diff --git a/libktorrent/util/functions.h b/libktorrent/util/functions.h new file mode 100644 index 0000000..4ace51b --- /dev/null +++ b/libktorrent/util/functions.h @@ -0,0 +1,72 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef BTFUNCTIONS_H +#define BTFUNCTIONS_H + +#include "constants.h" + +class QString; +class QHostAddress; +class KURL; + +namespace bt +{ + + void WriteUint64(Uint8* buf,Uint32 off,Uint64 val); + Uint64 ReadUint64(const Uint8* buf,Uint64 off); + + void WriteUint32(Uint8* buf,Uint32 off,Uint32 val); + Uint32 ReadUint32(const Uint8* buf,Uint32 off); + + void WriteUint16(Uint8* buf,Uint32 off,Uint16 val); + Uint16 ReadUint16(const Uint8* buf,Uint32 off); + + + void WriteInt64(Uint8* buf,Uint32 off,Int64 val); + Int64 ReadInt64(const Uint8* buf,Uint32 off); + + void WriteInt32(Uint8* buf,Uint32 off,Int32 val); + Int32 ReadInt32(const Uint8* buf,Uint32 off); + + void WriteInt16(Uint8* buf,Uint32 off,Int16 val); + Int16 ReadInt16(const Uint8* buf,Uint32 off); + + void UpdateCurrentTime(); + + extern TimeStamp global_time_stamp; + + inline TimeStamp GetCurrentTime() {return global_time_stamp;} + + TimeStamp Now(); + + QHostAddress LookUpHost(const QString & host); + QString DirSeparator(); + bool IsMultimediaFile(const QString & filename); + + /** + * Maximize the file and memory limits using setrlimit. + */ + bool MaximizeLimits(); + + /// Get the maximum number of open files + Uint32 MaxOpenFiles(); +} + +#endif diff --git a/libktorrent/util/httprequest.cpp b/libktorrent/util/httprequest.cpp new file mode 100644 index 0000000..d0652bc --- /dev/null +++ b/libktorrent/util/httprequest.cpp @@ -0,0 +1,122 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <qstringlist.h> +#include <torrent/globals.h> +#include "httprequest.h" +#include "array.h" +#include "log.h" + + +namespace bt +{ + + HTTPRequest::HTTPRequest(const QString & hdr,const QString & payload,const QString & host,Uint16 port,bool verbose) : hdr(hdr),payload(payload),verbose(verbose) + { + sock = new KNetwork::KStreamSocket(host,QString::number(port),this,0); + sock->enableRead(true); + sock->enableWrite(true); + sock->setTimeout(30000); + sock->setBlocking(false); + connect(sock,SIGNAL(readyRead()),this,SLOT(onReadyRead())); + connect(sock,SIGNAL(gotError(int)),this,SLOT(onError(int ))); + connect(sock,SIGNAL(timedOut()),this,SLOT(onTimeout())); + connect(sock,SIGNAL(connected(const KResolverEntry&)), + this, SLOT(onConnect( const KResolverEntry& ))); + } + + + HTTPRequest::~HTTPRequest() + { + sock->close(); + delete sock; + } + + void HTTPRequest::start() + { + sock->connect(); + } + + void HTTPRequest::onConnect(const KResolverEntry&) + { + payload = payload.replace("$LOCAL_IP",sock->localAddress().nodeName()); + hdr = hdr.replace("$CONTENT_LENGTH",QString::number(payload.length())); + + QString req = hdr + payload; + if (verbose) + { + Out(SYS_PNP|LOG_DEBUG) << "Sending " << endl; + Out(SYS_PNP|LOG_DEBUG) << hdr << payload << endl; + } + sock->writeBlock(req.ascii(),req.length()); + } + + void HTTPRequest::onReadyRead() + { + Uint32 ba = sock->bytesAvailable(); + if (ba == 0) + { + error(this,false); + sock->close(); + return; + } + + Array<char> data(ba); + ba = sock->readBlock(data,ba); + QString strdata((const char*)data); + QStringList sl = QStringList::split("\r\n",strdata,false); + + if (verbose) + { + Out(SYS_PNP|LOG_DEBUG) << "Got reply : " << endl; + Out(SYS_PNP|LOG_DEBUG) << strdata << endl; + } + + if (sl.first().contains("HTTP") && sl.first().contains("200")) + { + // emit reply OK + replyOK(this,sl.last()); + } + else + { + // emit reply error + replyError(this,sl.last()); + } + operationFinished(this); + } + + void HTTPRequest::onError(int) + { + Out() << "HTTPRequest error : " << sock->errorString() << endl; + error(this,false); + sock->close(); + operationFinished(this); + } + + void HTTPRequest::onTimeout() + { + Out() << "HTTPRequest timeout" << endl; + error(this,true); + sock->close(); + operationFinished(this); + } + + +} +#include "httprequest.moc" diff --git a/libktorrent/util/httprequest.h b/libktorrent/util/httprequest.h new file mode 100644 index 0000000..348a84e --- /dev/null +++ b/libktorrent/util/httprequest.h @@ -0,0 +1,98 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef BTHTTPREQUEST_H +#define BTHTTPREQUEST_H + +#include <qobject.h> +#include <kurl.h> +#include <kstreamsocket.h> +#include <interfaces/exitoperation.h> +#include "constants.h" + +using KNetwork::KResolverEntry; + +namespace bt +{ + + /** + * @author Joris Guisson + * + * Just create one, fill in the fields, + * connect to the right signals and forget about it. After the reply has been received or + * an error occurred, the appropriate signal will be emitted. + */ + class HTTPRequest : public kt::ExitOperation + { + Q_OBJECT + public: + /** + * Constructor, set the url and the request header. + * @param hdr The http request header + * @param payload The payload + * @param host The host + * @param port THe port + * @param verbose Print traffic to the log + */ + HTTPRequest(const QString & hdr,const QString & payload,const QString & host, + Uint16 port,bool verbose); + virtual ~HTTPRequest(); + + /** + * Open a connetion and send the request. + */ + void start(); + + signals: + /** + * An OK reply was sent. + * @param r The sender of the request + * @param data The data of the reply + */ + void replyOK(bt::HTTPRequest* r,const QString & data); + + /** + * Anything else but an 200 OK was sent. + * @param r The sender of the request + * @param data The data of the reply + */ + void replyError(bt::HTTPRequest* r,const QString & data); + + /** + * No reply was sent and an error or timeout occurred. + * @param r The sender of the request + * @param timeout Wether or not a timeout occurred + */ + void error(bt::HTTPRequest* r,bool timeout); + + private slots: + void onReadyRead(); + void onError(int); + void onTimeout(); + void onConnect(const KResolverEntry&); + + private: + KNetwork::KStreamSocket* sock; + QString hdr,payload; + bool verbose; + }; + +} + +#endif diff --git a/libktorrent/util/log.cpp b/libktorrent/util/log.cpp new file mode 100644 index 0000000..05682a8 --- /dev/null +++ b/libktorrent/util/log.cpp @@ -0,0 +1,249 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <kurl.h> +#include <kprocess.h> +#include <klocale.h> +#include <qdatetime.h> +#include <qtextstream.h> +#include <qfile.h> +#include <qptrlist.h> +#include <iostream> +#include <stdlib.h> +#include <torrent/globals.h> +#include <interfaces/logmonitorinterface.h> +#include <qmutex.h> +#include <util/fileops.h> +#include <stdlib.h> +#include "log.h" +#include "error.h" +#include "autorotatelogjob.h" + +using namespace kt; + +namespace bt +{ + const Uint32 MAX_LOG_FILE_SIZE = 10 * 1024 * 1024; // 10 MB + + class Log::Private + { + public: + Log* parent; + QTextStream* out; + QFile fptr; + bool to_cout; + QPtrList<LogMonitorInterface> monitors; + QString tmp; + QMutex mutex; + unsigned int m_filter; + AutoRotateLogJob* rotate_job; + public: + Private(Log* parent) : parent(parent),out(0),to_cout(false),rotate_job(0) + { + out = new QTextStream(); + } + + ~Private() + { + delete out; + } + + + void setFilter(unsigned int filter) + { + m_filter = filter; + } + + void rotateLogs(const QString & file) + { + if (bt::Exists(file + "-10.gz")) + bt::Delete(file + "-10.gz",true); + + // move all log files one up + for (Uint32 i = 10;i > 1;i--) + { + QString prev = QString("%1-%2.gz").arg(file).arg(i - 1); + QString curr = QString("%1-%2.gz").arg(file).arg(i); + if (bt::Exists(prev)) + bt::Move(prev,curr,true); + } + + // move current log to 1 and zip it + bt::Move(file,file + "-1",true); + system(QString("gzip " + KProcess::quote(file + "-1")).local8Bit()); + } + + void setOutputFile(const QString & file) + { + if (fptr.isOpen()) + fptr.close(); + + if (bt::Exists(file)) + rotateLogs(file); + + fptr.setName(file); + if (!fptr.open(IO_WriteOnly)) + throw Error(i18n("Cannot open log file %1 : %2").arg(file).arg(fptr.errorString())); + + out->setDevice(&fptr); + } + + void write(const QString & line) + { + tmp += line; + } + + void finishLine() + { + // only add stuff when we are not rotating the logs + // this could result in the loss of some messages + if (!rotate_job) + { + *out << QDateTime::currentDateTime().toString() << ": " << tmp << ::endl; + fptr.flush(); + if (to_cout) + std::cout << tmp.local8Bit() << std::endl; + + if (monitors.count() > 0) + { + QPtrList<LogMonitorInterface>::iterator i = monitors.begin(); + while (i != monitors.end()) + { + kt::LogMonitorInterface* lmi = *i; + lmi->message(tmp,m_filter); + i++; + } + } + } + tmp = ""; + } + + void endline() + { + finishLine(); + if (fptr.size() > MAX_LOG_FILE_SIZE && !rotate_job) + { + tmp = "Log larger then 10 MB, rotating"; + finishLine(); + QString file = fptr.name(); + fptr.close(); // close the log file + out->setDevice(0); + // start the rotate job + rotate_job = new AutoRotateLogJob(file,parent); + } + } + + void logRotateDone() + { + fptr.open(IO_WriteOnly); + out->setDevice(&fptr); + rotate_job = 0; + } + }; + + Log::Log() + { + priv = new Private(this); + } + + + Log::~Log() + { + delete priv; + } + + + void Log::setOutputFile(const QString & file) + { + priv->setOutputFile(file); + } + + void Log::addMonitor(kt::LogMonitorInterface* m) + { + priv->monitors.append(m); + } + + void Log::removeMonitor(kt::LogMonitorInterface* m) + { + priv->monitors.remove(m); + } + + void Log::setOutputToConsole(bool on) + { + priv->to_cout = on; + } + + Log & endl(Log & lg) + { + lg.priv->endline(); + lg.priv->mutex.unlock(); // unlock after end of line + return lg; + } + + Log & Log::operator << (const KURL & url) + { + priv->write(url.prettyURL()); + return *this; + } + + Log & Log::operator << (const QString & s) + { + priv->write(s); + return *this; + } + + Log & Log::operator << (const char* s) + { + priv->write(s); + return *this; + } + + Log & Log::operator << (Uint64 v) + { + return operator << (QString::number(v)); + } + + Log & Log::operator << (Int64 v) + { + return operator << (QString::number(v)); + } + + void Log::setFilter(unsigned int filter) + { + priv->setFilter(filter); + } + + void Log::lock() + { + priv->mutex.lock(); + } + + void Log::logRotateDone() + { + priv->logRotateDone(); + } + + Log & Out(unsigned int arg) + { + Log & lg = Globals::instance().getLog(arg); + lg.lock(); + return lg; + } +} diff --git a/libktorrent/util/log.h b/libktorrent/util/log.h new file mode 100644 index 0000000..2fe0ba6 --- /dev/null +++ b/libktorrent/util/log.h @@ -0,0 +1,209 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ + +#ifndef JORISLOG_H +#define JORISLOG_H + + +#include "constants.h" +#include <qstring.h> + +// LOG MESSAGES CONSTANTS +#define LOG_NONE 0x00 +#define LOG_IMPORTANT 0x01 +#define LOG_NOTICE 0x03 +#define LOG_DEBUG 0x07 +#define LOG_ALL 0x0F + +#define SYS_GEN 0x0010 // Genereral info messages +#define SYS_CON 0x0020 // Connections +#define SYS_TRK 0x0040 // Tracker +#define SYS_DHT 0x0080 // DHT +#define SYS_DIO 0x0100 // Disk IO related stuff, saving and loading of chunks ... + +//plugins +#define SYS_IPF 0x1000 // IPFilter +#define SYS_SRC 0x2000 // Search plugin +#define SYS_PNP 0x4000 // UPnP plugin +#define SYS_INW 0x8000 // InfoWidget +#define SYS_SNF 0x10000 // ScanFolder plugin +#define SYS_PFI 0x20000 // Part file import +#define SYS_SCD 0x40000 // Scheduler plugin +#define SYS_RSS 0x80000 // RSS plugin +#define SYS_WEB 0x100000 // WebInterface plugin +#define SYS_ZCO 0x200000 // ZeroConf plugin + +class KURL; + + +namespace kt +{ + class LogMonitorInterface; +} + +namespace bt +{ + + + /** + * @author Joris Guisson + * @brief Class which writes messages to a logfile + * + * This class writes messages to a logfile. To use it, create an instance, + * set the output file and write stuff with the << operator. + * + * By default all messages will also be printed on the standard output. This + * can be turned down using the @a setOutputToConsole function. + * + * There is also the possibility to monitor what is written to the log using + * the LogMonitorInterface class. + */ + class Log + { + class Private; + + Private* priv; + public: + /** + * Constructor. + */ + Log(); + + /** + * Destructor, closes the file. + */ + virtual ~Log(); + + /** + * Enable or disable the printing of log messages to the standard + * output. + * @param on Enable or disable + */ + void setOutputToConsole(bool on); + + /** + * Add a log monitor. + * @param m The log monitor + */ + void addMonitor(kt::LogMonitorInterface* m); + + /** + * Remove a log monitor. + * @param m The log monitor + */ + void removeMonitor(kt::LogMonitorInterface* m); + + /** + * Set the output logfile. + * @param file The name of the file + * @throw Exception if the file can't be opened + */ + void setOutputFile(const QString & file); + + /** + * Write a number to the log file. + * Anything which can be passed to QString::number will do. + * @param val The value + * @return This Log + */ + template <class T> + Log & operator << (T val) + { + return operator << (QString::number(val)); + } + + /** + * Apply a function to the Log. + * @param func The function + * @return This Log + */ + Log & operator << (Log & (*func)(Log & )) + { + return func(*this); + } + + + /** + * Output a QString to the log. + * @param s The QString + * @return This Log + */ + Log & operator << (const char* s); + + /** + * Output a QString to the log. + * @param s The QString + * @return This Log + */ + Log & operator << (const QString & s); + + /** + * Output a 64 bit integer to the log. + * @param v The integer + * @return This Log + */ + Log & operator << (Uint64 v); + + /** + * Output a 64 bit integer to the log. + * @param v The integer + * @return This Log + */ + Log & operator << (Int64 v); + + /** + * Prints and endline character to the Log and flushes it. + * @param lg The Log + * @return @a lg + */ + friend Log & endl(Log & lg); + + /** + * Write an URL to the file. + * @param text The KURL + * @return This Log + */ + Log & operator << (const KURL & url); + + /** + * Sets a filter for log messages. Applies only to listeners via LogMonitorInterface! + * @param filter SYS & LOG flags combined with bitwise OR. + */ + void setFilter(unsigned int filter); + + /// Lock the mutex of the log, should be called in Out() + void lock(); + + /// Called by the auto log rotate job when it has finished + void logRotateDone(); + }; + + Log & endl(Log & lg); + + + Log & Out(unsigned int arg = 0x00); + inline Log & GenOut(unsigned int arg) {return Out(SYS_GEN|arg);} + inline Log & DHTOut(unsigned int arg) {return Out(SYS_DHT|arg);} + inline Log & ConOut(unsigned int arg) {return Out(SYS_CON|arg);} + inline Log & TrkOut(unsigned int arg) {return Out(SYS_TRK|arg);} + +} + +#endif diff --git a/libktorrent/util/mmapfile.cpp b/libktorrent/util/mmapfile.cpp new file mode 100644 index 0000000..579c67a --- /dev/null +++ b/libktorrent/util/mmapfile.cpp @@ -0,0 +1,294 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <fcntl.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <errno.h> +#include <qfile.h> +#include <kfileitem.h> +#include <kio/netaccess.h> +#include <klocale.h> +#include <util/error.h> +#include <util/log.h> +#include <torrent/globals.h> +#include "mmapfile.h" + +namespace bt +{ + + MMapFile::MMapFile() : fd(-1),data(0),size(0),file_size(0),ptr(0),mode(READ) + {} + + + MMapFile::~MMapFile() + { + if (fd > 0) + close(); + } + + bool MMapFile::open(const QString & file,Mode mode) + { +#if HAVE_STAT64 + struct stat64 sb; + stat64(QFile::encodeName(file),&sb); +#else + struct stat sb; + stat(QFile::encodeName(file),&sb); +#endif + + return open(file,mode,(Uint64)sb.st_size); + } + + bool MMapFile::open(const QString & file,Mode mode,Uint64 size) + { + // close already open file + if (fd > 0) + close(); + + // setup flags + int flag = 0,mmap_flag = 0; + switch (mode) + { + case READ: + flag = O_RDONLY; + mmap_flag = PROT_READ; + break; + case WRITE: + flag = O_WRONLY | O_CREAT; + mmap_flag = PROT_WRITE; + break; + case RW: + flag = O_RDWR | O_CREAT; + mmap_flag = PROT_READ|PROT_WRITE; + break; + } + + // Not all systems have O_LARGEFILE as an explicit flag + // (for instance, FreeBSD. Solaris does, but only if + // _LARGEFILE_SOURCE is defined in the compile). + // So OR it in if it is defined. +#ifdef O_LARGEFILE + flag |= O_LARGEFILE; +#endif + + // open the file + fd = ::open(QFile::encodeName(file) , flag);//(int)flag); + if (fd == -1) + return false; + + // read the file size + this->size = size; + this->mode = mode; + +#if HAVE_STAT64 + struct stat64 sb; + stat64(QFile::encodeName(file),&sb); +#else + struct stat sb; + stat(QFile::encodeName(file),&sb); +#endif + file_size = (Uint64)sb.st_size; + filename = file; + + // mmap the file +#if HAVE_MMAP64 + data = (Uint8*)mmap64(0, size, mmap_flag, MAP_SHARED, fd, 0); +#else + data = (Uint8*)mmap(0, size, mmap_flag, MAP_SHARED, fd, 0); +#endif + if (data == MAP_FAILED) + { + ::close(fd); + data = 0; + fd = -1; + ptr = 0; + return false; + } + ptr = 0; + return true; + } + + void MMapFile::close() + { + if (fd > 0) + { +#if HAVE_MUNMAP64 + munmap64(data,size); +#else + munmap(data,size); +#endif + ::close(fd); + ptr = size = 0; + data = 0; + fd = -1; + filename = QString::null; + } + } + + void MMapFile::flush() + { + if (fd > 0) + msync(data,size,MS_SYNC); + } + + Uint32 MMapFile::write(const void* buf,Uint32 buf_size) + { + if (fd == -1 || mode == READ) + return 0; + + // check if data fits in memory mapping + if (ptr + buf_size > size) + throw Error(i18n("Cannot write beyond end of the mmap buffer!")); + + Out() << "MMapFile::write : " << (ptr + buf_size) << " " << file_size << endl; + // enlarge the file if necessary + if (ptr + buf_size > file_size) + { + growFile(ptr + buf_size); + } + + // memcpy data + memcpy(&data[ptr],buf,buf_size); + // update ptr + ptr += buf_size; + // update file size if necessary + if (ptr >= size) + size = ptr; + + return buf_size; + } + + void MMapFile::growFile(Uint64 new_size) + { + Out() << "Growing file to " << new_size << " bytes " << endl; + Uint64 to_write = new_size - file_size; + ssize_t written; + // jump to the end of the file + lseek(fd,0,SEEK_END); + + Uint8 buf[1024]; + memset(buf,0,1024); + // write data until to_write is 0 + while (to_write > 0) + { + ssize_t w = ::write(fd,buf, to_write > 1024 ? 1024 : to_write); + if (w > 0) + to_write -= w; + else if (w < 0) + break; + } + file_size = new_size; + } + + Uint32 MMapFile::read(void* buf,Uint32 buf_size) + { + if (fd == -1 || mode == WRITE) + return 0; + + // check if we aren't going to read past the end of the file + Uint32 to_read = ptr + buf_size >= size ? size - ptr : buf_size; + // read data + memcpy(buf,data+ptr,to_read); + ptr += to_read; + return to_read; + } + + Uint64 MMapFile::seek(SeekPos from,Int64 num) + { + switch (from) + { + case BEGIN: + if (num > 0) + ptr = num; + if (ptr >= size) + ptr = size - 1; + break; + case END: + { + Int64 np = (size - 1) + num; + if (np < 0) + { + ptr = 0; + break; + } + if (np >= (Int64) size) + { + ptr = size - 1; + break; + } + ptr = np; + } + break; + case CURRENT: + { + Int64 np = ptr + num; + if (np < 0) + { + ptr = 0; + break; + } + if (np >= (Int64) size) + { + ptr = size - 1; + break; + } + ptr = np; + } + break; + } + return ptr; + } + + bool MMapFile::eof() const + { + return ptr >= size; + } + + Uint64 MMapFile::tell() const + { + return ptr; + } + + QString MMapFile::errorString() const + { + return strerror(errno); + } + + Uint64 MMapFile::getSize() const + { + return size; + } + + Uint8* MMapFile::getData(Uint64 off) + { + if (off >= size) + return 0; + return &data[off]; + } +} + diff --git a/libktorrent/util/mmapfile.h b/libktorrent/util/mmapfile.h new file mode 100644 index 0000000..ca0d782 --- /dev/null +++ b/libktorrent/util/mmapfile.h @@ -0,0 +1,146 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef BTMMAPFILE_H +#define BTMMAPFILE_H + + +#include <qstring.h> +#include <util/constants.h> + +namespace bt +{ + + /** + * @author Joris Guisson + * @brief Memory mapped file + * + * This class allows to access memory mapped files. It's pretty similar to + * File. + * TODO: make sure large files work (not really needed for the blocklist) + */ + class MMapFile + { + public: + MMapFile(); + virtual ~MMapFile(); + + enum Mode + { + READ,WRITE, RW + }; + /** + * Open the file. If mode is write and the file doesn't exist, it will + * be created. + * @param file Filename + * @param mode Mode (READ, WRITE or RW) + * @return true upon succes + */ + bool open(const QString & file,Mode mode); + + /** + * Open the file. If mode is write and the file doesn't exist, it will + * be created. + * @param file Filename + * @param mode Mode (READ, WRITE or RW) + * @param size Size of the memory mapping (the file will be enlarged to this value) + * @return true upon succes + */ + bool open(const QString & file,Mode mode,Uint64 size); + + /** + * Close the file. Undoes the memory mapping. + */ + void close(); + + /** + * Flush the file. + */ + void flush(); + + /** + * Write a bunch of data. + * @param buf The data + * @param size Size of the data + * @return The number of bytes written + */ + Uint32 write(const void* buf,Uint32 size); + + /** + * Read a bunch of data + * @param buf The buffer to store the data + * @param size Size of the buffer + * @return The number of bytes read + */ + Uint32 read(void* buf,Uint32 size); + + enum SeekPos + { + BEGIN, + END, + CURRENT + }; + + /** + * Seek in the file. + * @param from Position to seek from + * @param num Number of bytes to move + * @return New position + */ + Uint64 seek(SeekPos from,Int64 num); + + /// Check to see if we are at the end of the file. + bool eof() const; + + /// Get the current position in the file. + Uint64 tell() const; + + /// Get the error string. + QString errorString() const; + + /// Get the file size + Uint64 getSize() const; + + + /** + * Get a pointer to the mmapped region of data. + * @param off Offset into buffer, if invalid 0 will be returned + * @return Pointer to a location in the mmapped region + */ + Uint8* getData(Uint64 off); + + /// Gets the data pointer + void* getDataPointer() { return data; } + private: + void growFile(Uint64 new_size); + + private: + int fd; + Uint8* data; + Uint64 size; // size of mmapping + Uint64 file_size; // size of file + Uint64 ptr; + QString filename; + Mode mode; + }; + +} + + +#endif diff --git a/libktorrent/util/profiler.cpp b/libktorrent/util/profiler.cpp new file mode 100644 index 0000000..05c53bd --- /dev/null +++ b/libktorrent/util/profiler.cpp @@ -0,0 +1,138 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifdef KT_PROFILE +#include <qfile.h> +#include <qtextstream.h> +#include <sys/time.h> +#include "profiler.h" + +namespace bt +{ + Profile::Profile(Profile* parent,const QString & name) : parent(parent),name(name) + { + min = max = avg = 0.0; + count = 0; + start_time = 0.0; + children.setAutoDelete(true); + } + + Profile::~Profile() + { + } + + void Profile::start() + { + struct timeval tv; + gettimeofday(&tv,0); + start_time = tv.tv_sec * 1000.0 + tv.tv_usec * 0.001; + } + + void Profile::end() + { + struct timeval tv; + gettimeofday(&tv,0); + double end_time = tv.tv_sec * 1000.0 + tv.tv_usec * 0.001; + double d = end_time - start_time; + // update stuff + + if (d < min || count == 0) + min = d; + if (d > max || count == 0) + max = d; + + avg = (avg * count + d) / (count + 1); + count++; + } + + Profile* Profile::child(const QString & name) + { + QPtrList<Profile>::iterator i = children.begin(); + while (i != children.end()) + { + Profile* p = *i; + if (p->name == name) + return p; + i++; + } + + Profile* p = new Profile(this,name); + children.append(p); + return p; + } + + void Profile::save(QTextStream & out,const QString & base) + { + QString nb = base + "/" + name; + + out.precision(5); + out << qSetW(60) << nb << qSetW(10) << min << qSetW(10) << max << qSetW(10) << avg << qSetW(10) << count << endl; + + QPtrList<Profile>::iterator i = children.begin(); + while (i != children.end()) + { + Profile* p = *i; + p->save(out,nb); + i++; + } + } + + ///////////////////// + + Profiler Profiler::inst; + + Profiler::Profiler() : curr(0),root(0) + { + root = new Profile(0,"root"); + curr = root; + } + + + Profiler::~Profiler() + { + delete root; + } + + void Profiler::start(const QString & s) + { + curr = curr->child(s); + curr->start(); + } + + void Profiler::end() + { + curr->end(); + curr = curr->getParent(); + } + + void Profiler::saveToFile(const QString & fn) + { + QFile fptr(fn); + if (!fptr.open(IO_WriteOnly)) + return; + + QTextStream out(&fptr); + + out << qSetW(60) << "code" << qSetW(10) << "min" << qSetW(10) << "max" << qSetW(10) << "avg" << qSetW(10) << "count" << endl; + out << endl; + + root->save(out,QString::null); + } +} +#endif diff --git a/libktorrent/util/profiler.h b/libktorrent/util/profiler.h new file mode 100644 index 0000000..6ab06e7 --- /dev/null +++ b/libktorrent/util/profiler.h @@ -0,0 +1,108 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef BTPROFILER_H +#define BTPROFILER_H + +#ifdef KT_PROFILE +#include <qptrlist.h> +#include <util/constants.h> + +class QTextStream; + + +namespace bt +{ + /** + * Profile of one function or section of code. + */ + class Profile + { + Profile* parent; + QPtrList<Profile> children; + + QString name; + double min,max,avg; + Uint32 count; + double start_time; + public: + Profile(Profile* parent,const QString & name); + virtual ~Profile(); + + /** + * We just entered the function and will profile it. + */ + void start(); + + /** + * We just left the function, internal variables will now be updated + */ + void end(); + + /** + * Get a child, if it doesn't exist it will be created. + * @param name The name of the child + * @return The child + */ + Profile* child(const QString & name); + + /** + * Get the parent of the current profile. + */ + Profile* getParent() const {return parent;} + + /** + * Save profile information to a file. + * @param out Text stream to write to + * @param base Base path of the profiles + */ + void save(QTextStream & out,const QString & base); + }; + + /** + * @author Joris Guisson <joris.guisson@gmail.com> + * + * Class used to profile ktorrent + */ + class Profiler + { + Profile* curr; + Profile* root; + + static Profiler inst; + + Profiler(); + public: + virtual ~Profiler(); + + void start(const QString & s); + void end(); + void saveToFile(const QString & fn); + + static Profiler & instance() {return inst;} + }; +} +#define KT_PROF_START(S) bt::Profiler::instance().start(S) +#define KT_PROF_END() bt::Profiler::instance().end() +#else +#define KT_PROF_START(S) +#define KT_PROF_END() +#endif + +#endif diff --git a/libktorrent/util/ptrmap.cpp b/libktorrent/util/ptrmap.cpp new file mode 100644 index 0000000..28a4340 --- /dev/null +++ b/libktorrent/util/ptrmap.cpp @@ -0,0 +1,24 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 "ptrmap.h" + + + + diff --git a/libktorrent/util/ptrmap.h b/libktorrent/util/ptrmap.h new file mode 100644 index 0000000..36e1c20 --- /dev/null +++ b/libktorrent/util/ptrmap.h @@ -0,0 +1,181 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef BTPTRMAP_H +#define BTPTRMAP_H + +#include <map> + +namespace bt +{ + /** + * @author Joris Guisson + * @brief Map of pointers + * + * A Map where the data is a pointer. The PtrMap has an autodeletion feature. + * When autodelete is on, every time we remove something from the map, the data + * will be deleted. + */ + template <class Key,class Data> + class PtrMap + { + bool autodel; + std::map<Key,Data*> pmap; + public: + /** + * Constructor. + * @param auto_del Wether or not to enable auto deletion + */ + PtrMap(bool autodel = false) : autodel(autodel) + {} + + /** + * Destructor. Will delete all objects, if auto deletion is on. + */ + virtual ~PtrMap() + { + clear(); + } + + + /** + * Return the number of key data pairs in the map. + */ + unsigned int count() const {return pmap.size();} + + /** + * Enable or disable auto deletion. + * @param yes Enable if true, disable if false + */ + void setAutoDelete(bool yes) + { + autodel = yes; + } + + typedef typename std::map<Key,Data*>::iterator iterator; + typedef typename std::map<Key,Data*>::const_iterator const_iterator; + + iterator begin() {return pmap.begin();} + iterator end() {return pmap.end();} + + const_iterator begin() const {return pmap.begin();} + const_iterator end() const {return pmap.end();} + + /** + * Remove all objects, will delete them if autodelete is on. + */ + void clear() + { + if (autodel) + { + for (iterator i = pmap.begin();i != pmap.end();i++) + { + delete i->second; + i->second = 0; + } + } + pmap.clear(); + } + + /** + * Insert a key data pair. + * @param k The key + * @param d The data + * @param overwrite Wether or not to overwrite + * @return true if the insertion took place + */ + bool insert(const Key & k,Data* d,bool overwrite = true) + { + iterator itr = pmap.find(k); + if (itr != pmap.end()) + { + if (overwrite) + { + if (autodel) + delete itr->second; + itr->second = d; + return true; + } + else + { + return false; + } + } + else + { + pmap[k] = d; + return true; + } + } + + /** + * Find a key in the map and returns it's data. + * @param k The key + * @return The data of the key, 0 if the key isn't in the map + */ + Data* find(const Key & k) + { + iterator i = pmap.find(k); + return (i == pmap.end()) ? 0 : i->second; + } + + /** + * Find a key in the map and returns it's data. + * @param k The key + * @return The data of the key, 0 if the key isn't in the map + */ + const Data* find(const Key & k) const + { + const_iterator i = pmap.find(k); + return (i == pmap.end()) ? 0 : i->second; + } + + /** + * Check to see if a key is in the map. + * @param k The key + * @return true if it is part of the map + */ + bool contains(const Key & k) const + { + const_iterator i = pmap.find(k); + return i != pmap.end(); + } + + /** + * Erase a key from the map. Will delete + * the data if autodelete is on. + * @param key The key + * @return true if an erase took place + */ + bool erase(const Key & key) + { + iterator i = pmap.find(key); + if (i == pmap.end()) + return false; + + if (autodel) + delete i->second; + pmap.erase(i); + return true; + } + }; + +} + +#endif diff --git a/libktorrent/util/sha1hash.cpp b/libktorrent/util/sha1hash.cpp new file mode 100644 index 0000000..c7b151c --- /dev/null +++ b/libktorrent/util/sha1hash.cpp @@ -0,0 +1,131 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <qurl.h> +#include <string.h> +#include <algorithm> +#include "log.h" +#include "sha1hash.h" +#include "sha1hashgen.h" +#include "urlencoder.h" + +#include <stdio.h> + +namespace bt +{ + SHA1Hash::SHA1Hash() + { + std::fill(hash,hash+20,'\0'); + } + + SHA1Hash::SHA1Hash(const SHA1Hash & other) + { + for (int i = 0;i < 20;i++) + hash[i] = other.hash[i]; + } + + SHA1Hash::SHA1Hash(const Uint8* h) + { + memcpy(hash,h,20); + } + + + SHA1Hash::~SHA1Hash() + {} + + SHA1Hash & SHA1Hash::operator = (const SHA1Hash & other) + { + for (int i = 0;i < 20;i++) + hash[i] = other.hash[i]; + return *this; + } + + bool SHA1Hash::operator == (const SHA1Hash & other) const + { + for (int i = 0;i < 20;i++) + if (hash[i] != other.hash[i]) + return false; + + return true; + } + + SHA1Hash SHA1Hash::generate(const Uint8* data,Uint32 len) + { + SHA1HashGen hg; + + return hg.generate(data,len); + } + + QString SHA1Hash::toString() const + { + char tmp[41]; + QString fmt; + for (int i = 0;i < 20;i++) + fmt += "%02x"; + tmp[40] = '\0'; + snprintf(tmp,41,fmt.ascii(), + hash[0],hash[1],hash[2],hash[3],hash[4], + hash[5],hash[6],hash[7],hash[8],hash[9], + hash[10],hash[11],hash[12],hash[13],hash[14], + hash[15],hash[16],hash[17],hash[18],hash[19]); + return QString(tmp); + } + + QByteArray SHA1Hash::toByteArray() const + { + QByteArray arr(20); + arr.duplicate((const char*)hash,20); + return arr; + } + + QString SHA1Hash::toURLString() const + { + return URLEncoder::encode((const char*)hash,20); + } + + Log & operator << (Log & out,const SHA1Hash & h) + { + out << h.toString(); + return out; + } + + SHA1Hash operator ^ (const SHA1Hash & a,const SHA1Hash & b) + { + SHA1Hash k; + for (int i = 0;i < 20;i++) + { + k.hash[i] = a.hash[i] ^ b.hash[i]; + } + return k; + } + + bool operator < (const SHA1Hash & a,const SHA1Hash & b) + { + for (int i = 0;i < 20;i++) + { + if (a.hash[i] < b.hash[i]) + return true; + else if (a.hash[i] > b.hash[i]) + return false; + } + + return false; + } +} + diff --git a/libktorrent/util/sha1hash.h b/libktorrent/util/sha1hash.h new file mode 100644 index 0000000..a831d2d --- /dev/null +++ b/libktorrent/util/sha1hash.h @@ -0,0 +1,148 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef BTSHA1HASH_H +#define BTSHA1HASH_H + +#include <qcstring.h> +#include "constants.h" + +class QString; + +namespace bt +{ + class Log; + + /** + * @author Joris Guisson + * @brief Stores a SHA1 hash + * + * This class keeps track of a SHA1 hash. A SHA1 hash is a 20 byte + * array of bytes. + */ + class SHA1Hash + { + protected: + Uint8 hash[20]; + public: + /** + * Constructor, sets every byte in the hash to 0. + */ + SHA1Hash(); + + /** + * Copy constructor. + * @param other Hash to copy + */ + SHA1Hash(const SHA1Hash & other); + + /** + * Directly set the hash data. + * @param h The hash data must be 20 bytes large + */ + SHA1Hash(const Uint8* h); + + /** + * Destructor. + */ + virtual ~SHA1Hash(); + + /// Get the idx'th byte of the hash. + Uint8 operator [] (const Uint32 idx) const {return idx < 20 ? hash[idx] : 0;} + + /** + * Assignment operator. + * @param other Hash to copy + */ + SHA1Hash & operator = (const SHA1Hash & other); + + /** + * Test wether another hash is equal to this one. + * @param other The other hash + * @return true if equal, false otherwise + */ + bool operator == (const SHA1Hash & other) const; + + /** + * Test wether another hash is not equal to this one. + * @param other The other hash + * @return true if not equal, false otherwise + */ + bool operator != (const SHA1Hash & other) const {return !operator ==(other);} + + /** + * Generate an SHA1 hash from a bunch of data. + * @param data The data + * @param len Size in bytes of data + * @return The generated SHA1 hash + */ + static SHA1Hash generate(const Uint8* data,Uint32 len); + + /** + * Convert the hash to a printable string. + * @return The string + */ + QString toString() const; + + /** + * Convert the hash to a string, usable in http get requests. + * @return The string + */ + QString toURLString() const; + + /** + * Directly get pointer to the data. + * @return The data + */ + const Uint8* getData() const {return hash;} + + /** + * Function to print a SHA1Hash to the Log. + * @param out The Log + * @param h The hash + * @return out + */ + friend Log & operator << (Log & out,const SHA1Hash & h); + + + /** + * XOR two SHA1Hashes + * @param a The first hash + * @param b The second + * @return a xor b + */ + friend SHA1Hash operator ^ (const SHA1Hash & a,const SHA1Hash & b); + + /** + * Function to compare 2 hashes + * @param a The first hash + * @param h The second hash + * @return wether a is smaller then b + */ + friend bool operator < (const SHA1Hash & a,const SHA1Hash & b); + + /** + * Convert the hash to a byte array. + */ + QByteArray toByteArray() const; + }; + +} + +#endif diff --git a/libktorrent/util/sha1hashgen.cpp b/libktorrent/util/sha1hashgen.cpp new file mode 100644 index 0000000..5c0d9f5 --- /dev/null +++ b/libktorrent/util/sha1hashgen.cpp @@ -0,0 +1,340 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <string.h> +#include <arpa/inet.h> +#include "sha1hashgen.h" +#include "functions.h" + + + +namespace bt +{ + static inline Uint32 LeftRotate(Uint32 x,Uint32 n) + { + return ((x << n) | ((x & 0xFFFFFFFF) >> (32 - n))); + } + + + + SHA1HashGen::SHA1HashGen() : tmp_len(0),total_len(0) + { + + } + + + SHA1HashGen::~SHA1HashGen() + {} + + SHA1Hash SHA1HashGen::generate(const Uint8* data,Uint32 len) + { + h0 = 0x67452301; + h1 = 0xEFCDAB89; + h2 = 0x98BADCFE; + h3 = 0x10325476; + h4 = 0xC3D2E1F0; + + Uint32 num_64_byte_chunks = len / 64; + Uint32 left_over = len % 64; + // proces regular data + for (Uint32 i = 0;i < num_64_byte_chunks;i++) + { + processChunk(data + (64*i)); + } + + // calculate the low and high byte of the data length + Uint32 total[2] = {0,0}; + total[0] += len; + total[0] &= 0xFFFFFFFF; + + if (total[0] < len) + total[1]++; + + Uint32 high = ( total[0] >> 29 ) | ( total[1] << 3 ); + Uint32 low = ( total[0] << 3 ); + + if (left_over == 0) + { + tmp[0] = 0x80; + for (Uint32 i = 1;i < 56;i++) + tmp[i] = 0; + + // put in the length as 64-bit integer (BIG-ENDIAN) + WriteUint32(tmp,56,high); + WriteUint32(tmp,60,low); + // process the padding + processChunk(tmp); + } + else if (left_over < 56) + { + Uint32 off = num_64_byte_chunks * 64; + // copy left over bytes in tmp + memcpy(tmp,data + off, left_over); + tmp[left_over] = 0x80; + for (Uint32 i = left_over + 1;i < 56;i++) + tmp[i] = 0; + + // put in the length as 64-bit integer (BIG-ENDIAN) + WriteUint32(tmp,56,high); + WriteUint32(tmp,60,low); + // process the padding + processChunk(tmp); + } + else + { + // now we need to process 2 chunks + Uint32 off = num_64_byte_chunks * 64; + // copy left over bytes in tmp + memcpy(tmp,data + off, left_over); + tmp[left_over] = 0x80; + for (Uint32 i = left_over + 1;i < 64;i++) + tmp[i] = 0; + + // process first chunk + processChunk(tmp); + + for (Uint32 i = 0;i < 56;i++) + tmp[i] = 0; + + // put in the length as 64-bit integer (BIG-ENDIAN) + WriteUint32(tmp,56,high); + WriteUint32(tmp,60,low); + // process the second chunk + processChunk(tmp); + } + + // construct final message + Uint8 hash[20]; + WriteUint32(hash,0,h0); + WriteUint32(hash,4,h1); + WriteUint32(hash,8,h2); + WriteUint32(hash,12,h3); + WriteUint32(hash,16,h4); + + return SHA1Hash(hash); + } + + + + void SHA1HashGen::processChunk(const Uint8* chunk) + { + Uint32 w[80]; + for (int i = 0;i < 80;i++) + { + if (i < 16) + { + w[i] = ntohl(*(const Uint32*)(chunk + (4*i))); + /* w[i] = (chunk[4*i] << 24) | + (chunk[4*i + 1] << 16) | + (chunk[4*i + 2] << 8) | + chunk[4*i + 3]; + */ + } + else + { + w[i] = LeftRotate(w[i-3] ^ w[i-8] ^ w[i-14] ^ w[i-16],1); + } + } + + Uint32 a = h0; + Uint32 b = h1; + Uint32 c = h2; + Uint32 d = h3; + Uint32 e = h4; + + for (int i = 0;i < 80;i++) + { + Uint32 f,k; + if (i < 20) + { + f = (b & c) | ((~b) & d); + k = 0x5A827999; + } + else if (i < 40) + { + f = b ^ c ^ d; + k = 0x6ED9EBA1; + } + else if (i < 60) + { + f = (b & c) | (b & d) | (c & d); + k = 0x8F1BBCDC; + } + else + { + f = b ^ c ^ d; + k = 0xCA62C1D6; + } + + Uint32 temp = LeftRotate(a,5) + f + e + k + w[i]; + e = d; + d = c; + c = LeftRotate(b,30); + b = a; + a = temp; + } + h0 = (h0 + a) & 0xffffffff; + h1 = (h1 + b) & 0xffffffff; + h2 = (h2 + c) & 0xffffffff; + h3 = (h3 + d) & 0xffffffff; + h4 = (h4 + e) & 0xffffffff; + } + + + void SHA1HashGen::start() + { + h0 = 0x67452301; + h1 = 0xEFCDAB89; + h2 = 0x98BADCFE; + h3 = 0x10325476; + h4 = 0xC3D2E1F0; + tmp_len = total_len = 0; + memset(tmp,0,64); + } + + void SHA1HashGen::update(const Uint8* data,Uint32 len) + { + if (tmp_len == 0) + { + Uint32 num_64_byte_chunks = len / 64; + Uint32 left_over = len % 64; + // proces data in chunks of 64 byte + for (Uint32 i = 0;i < num_64_byte_chunks;i++) + { + processChunk(data + (64*i)); + } + + if (left_over > 0) + { + // if there is anything left over, copy it in tmp + memcpy(tmp,data + (64 * num_64_byte_chunks),left_over); + tmp_len = left_over; + } + total_len += len; + } + else + { + + if (tmp_len + len < 64) + { + // special case, not enough of data to fill tmp completely + memcpy(tmp + tmp_len,data,len); + tmp_len += len; + total_len += len; + } + else + { + // copy start of data in tmp and process it + Uint32 off = 64 - tmp_len; + memcpy(tmp + tmp_len,data, 64 - tmp_len); + processChunk(tmp); + tmp_len = 0; + + Uint32 num_64_byte_chunks = (len - off) / 64; + Uint32 left_over = (len - off) % 64; + + for (Uint32 i = 0;i < num_64_byte_chunks;i++) + { + processChunk(data + (off + (64*i))); + } + + if (left_over > 0) + { + // if there is anything left over, copy it in tmp + memcpy(tmp,data + (off + 64 * num_64_byte_chunks),left_over); + tmp_len = left_over; + } + total_len += len; + } + } + } + + + void SHA1HashGen::end() + { + // calculate the low and high byte of the data length + Uint32 total[2] = {0,0}; + total[0] += total_len; + total[0] &= 0xFFFFFFFF; + + if (total[0] < total_len) + total[1]++; + + Uint32 high = ( total[0] >> 29 ) | ( total[1] << 3 ); + Uint32 low = ( total[0] << 3 ); + + if (tmp_len == 0) + { + tmp[0] = 0x80; + for (Uint32 i = 1;i < 56;i++) + tmp[i] = 0; + + // put in the length as 64-bit integer (BIG-ENDIAN) + WriteUint32(tmp,56,high); + WriteUint32(tmp,60,low); + // process the padding + processChunk(tmp); + } + else if (tmp_len < 56) + { + tmp[tmp_len] = 0x80; + for (Uint32 i = tmp_len + 1;i < 56;i++) + tmp[i] = 0; + + // put in the length as 64-bit integer (BIG-ENDIAN) + WriteUint32(tmp,56,high); + WriteUint32(tmp,60,low); + // process the padding + processChunk(tmp); + } + else + { + // now we need to process 2 chunks + tmp[tmp_len] = 0x80; + for (Uint32 i = tmp_len + 1;i < 56;i++) + tmp[i] = 0; + + // process first chunk + processChunk(tmp); + + for (Uint32 i = 0;i < 56;i++) + tmp[i] = 0; + + // put in the length as 64-bit integer (BIG-ENDIAN) + WriteUint32(tmp,56,high); + WriteUint32(tmp,60,low); + // process the second chunk + processChunk(tmp); + } + } + + + SHA1Hash SHA1HashGen::get() const + { + // construct final message + Uint8 hash[20]; + WriteUint32(hash,0,h0); + WriteUint32(hash,4,h1); + WriteUint32(hash,8,h2); + WriteUint32(hash,12,h3); + WriteUint32(hash,16,h4); + + return SHA1Hash(hash); + } +} diff --git a/libktorrent/util/sha1hashgen.h b/libktorrent/util/sha1hashgen.h new file mode 100644 index 0000000..08cc3ad --- /dev/null +++ b/libktorrent/util/sha1hashgen.h @@ -0,0 +1,90 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef BTSHA1HASHGEN_H +#define BTSHA1HASHGEN_H + +#include "constants.h" +#include "sha1hash.h" + +namespace bt +{ + + /** + * @author Joris Guisson + * + * Generates a SHA1 hash, code based on wikipedia's pseudocode + * There are 2 ways to use this class : + * - generate : all data is present from the start + * - start, update and end : data can be delivered in chunks + * + * Mixing the 2, is not a good idea + */ + class SHA1HashGen + { + Uint32 h0; + Uint32 h1; + Uint32 h2; + Uint32 h3; + Uint32 h4; + Uint8 tmp[64]; + Uint32 tmp_len; + Uint32 total_len; + public: + SHA1HashGen(); + ~SHA1HashGen(); + + /** + * Generate a hash from a bunch of data. + * @param data The data + * @param len The length + * @return The SHA1 hash + */ + SHA1Hash generate(const Uint8* data,Uint32 len); + + /** + * Start SHA1 hash generation in chunks. + */ + void start(); + + /** + * Update the hash. + * @param data The data + * @param len Length of the data + */ + void update(const Uint8* data,Uint32 len); + + + /** + * All data has been delivered, calculate the final hash. + * @return + */ + void end(); + + /** + * Get the hash generated. + */ + SHA1Hash get() const; + private: + void processChunk(const Uint8* c); + }; + +} + +#endif diff --git a/libktorrent/util/timer.cpp b/libktorrent/util/timer.cpp new file mode 100644 index 0000000..c06b728 --- /dev/null +++ b/libktorrent/util/timer.cpp @@ -0,0 +1,64 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 "timer.h" + +namespace bt +{ + + Timer::Timer() : elapsed(0) + { + last = QTime::currentTime(); + } + + Timer::Timer(const Timer & t) : last(t.last),elapsed(t.elapsed) + {} + + Timer::~Timer() + {} + + + void Timer::update() + { + QTime now = QTime::currentTime(); + + int d = last.msecsTo(now); + if (d < 0) + d = 0; + elapsed = d; + last = now; + } + + Uint32 Timer::getElapsedSinceUpdate() const + { + QTime now = QTime::currentTime(); + int d = last.msecsTo(now); + if (d < 0) + d = 0; + return d; + } + + Timer & Timer::operator = (const Timer & t) + { + last = t.last; + elapsed = t.elapsed; + return *this; + } +} diff --git a/libktorrent/util/timer.h b/libktorrent/util/timer.h new file mode 100644 index 0000000..3277185 --- /dev/null +++ b/libktorrent/util/timer.h @@ -0,0 +1,49 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef BTTIMER_H +#define BTTIMER_H + +#include <qdatetime.h> +#include "constants.h" + +namespace bt +{ + + /** + @author Joris Guisson + */ + class Timer + { + QTime last; + Uint32 elapsed; + public: + Timer(); + Timer(const Timer & t); + virtual ~Timer(); + + void update(); + Uint32 getElapsed() const {return elapsed;} + Uint32 getElapsedSinceUpdate() const; + Timer & operator = (const Timer & t); + }; + +} + +#endif diff --git a/libktorrent/util/urlencoder.cpp b/libktorrent/util/urlencoder.cpp new file mode 100644 index 0000000..c1776de --- /dev/null +++ b/libktorrent/util/urlencoder.cpp @@ -0,0 +1,92 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 "urlencoder.h" + + +namespace bt +{ + QString hex[] = { + "%00", "%01", "%02", "%03", "%04", "%05", "%06", "%07", + "%08", "%09", "%0a", "%0b", "%0c", "%0d", "%0e", "%0f", + "%10", "%11", "%12", "%13", "%14", "%15", "%16", "%17", + "%18", "%19", "%1a", "%1b", "%1c", "%1d", "%1e", "%1f", + "%20", "%21", "%22", "%23", "%24", "%25", "%26", "%27", + "%28", "%29", "%2a", "%2b", "%2c", "%2d", "%2e", "%2f", + "%30", "%31", "%32", "%33", "%34", "%35", "%36", "%37", + "%38", "%39", "%3a", "%3b", "%3c", "%3d", "%3e", "%3f", + "%40", "%41", "%42", "%43", "%44", "%45", "%46", "%47", + "%48", "%49", "%4a", "%4b", "%4c", "%4d", "%4e", "%4f", + "%50", "%51", "%52", "%53", "%54", "%55", "%56", "%57", + "%58", "%59", "%5a", "%5b", "%5c", "%5d", "%5e", "%5f", + "%60", "%61", "%62", "%63", "%64", "%65", "%66", "%67", + "%68", "%69", "%6a", "%6b", "%6c", "%6d", "%6e", "%6f", + "%70", "%71", "%72", "%73", "%74", "%75", "%76", "%77", + "%78", "%79", "%7a", "%7b", "%7c", "%7d", "%7e", "%7f", + "%80", "%81", "%82", "%83", "%84", "%85", "%86", "%87", + "%88", "%89", "%8a", "%8b", "%8c", "%8d", "%8e", "%8f", + "%90", "%91", "%92", "%93", "%94", "%95", "%96", "%97", + "%98", "%99", "%9a", "%9b", "%9c", "%9d", "%9e", "%9f", + "%a0", "%a1", "%a2", "%a3", "%a4", "%a5", "%a6", "%a7", + "%a8", "%a9", "%aa", "%ab", "%ac", "%ad", "%ae", "%af", + "%b0", "%b1", "%b2", "%b3", "%b4", "%b5", "%b6", "%b7", + "%b8", "%b9", "%ba", "%bb", "%bc", "%bd", "%be", "%bf", + "%c0", "%c1", "%c2", "%c3", "%c4", "%c5", "%c6", "%c7", + "%c8", "%c9", "%ca", "%cb", "%cc", "%cd", "%ce", "%cf", + "%d0", "%d1", "%d2", "%d3", "%d4", "%d5", "%d6", "%d7", + "%d8", "%d9", "%da", "%db", "%dc", "%dd", "%de", "%df", + "%e0", "%e1", "%e2", "%e3", "%e4", "%e5", "%e6", "%e7", + "%e8", "%e9", "%ea", "%eb", "%ec", "%ed", "%ee", "%ef", + "%f0", "%f1", "%f2", "%f3", "%f4", "%f5", "%f6", "%f7", + "%f8", "%f9", "%fa", "%fb", "%fc", "%fd", "%fe", "%ff" + }; + + + QString URLEncoder::encode(const char* buf,Uint32 size) + { + QString res = ""; + + for (Uint32 i = 0; i < size; i++) + { + Uint8 ch = buf[i]; + if (('A' <= ch && ch <= 'Z') || ('a' <= ch && ch <= 'z') || ('0' <= ch && ch <= '9')) + { // 'A'..'Z' + res.append((char)ch); + } + else if (ch == ' ') + { // space + res.append("%20"); + } + else if (ch == '-' || ch == '_' // unreserved + || ch == '.' || ch == '!' + || ch == '~' || ch == '*' + || ch == '\'' || ch == '(' + || ch == ')') + { + res.append((char)ch); + } + else + { // other ASCII + res.append(hex[ch]); + } + } + return res; + } + +} diff --git a/libktorrent/util/urlencoder.h b/libktorrent/util/urlencoder.h new file mode 100644 index 0000000..edac33a --- /dev/null +++ b/libktorrent/util/urlencoder.h @@ -0,0 +1,40 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef BTURLENCODER_H +#define BTURLENCODER_H + +#include <qstring.h> +#include "constants.h" + +namespace bt +{ + + /** + @author Joris Guisson + */ + class URLEncoder + { + public: + static QString encode(const char* buf,Uint32 size); + }; + +} + +#endif diff --git a/libktorrent/util/waitjob.cpp b/libktorrent/util/waitjob.cpp new file mode 100644 index 0000000..d11fa14 --- /dev/null +++ b/libktorrent/util/waitjob.cpp @@ -0,0 +1,86 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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 <torrent/globals.h> +#include <kio/netaccess.h> +#include "waitjob.h" +#include "log.h" + +namespace bt +{ + + WaitJob::WaitJob(Uint32 millis) : KIO::Job(false) + { + connect(&timer,SIGNAL(timeout()),this,SLOT(timerDone())); + timer.start(millis,true); + } + + + WaitJob::~WaitJob() + {} + + void WaitJob::kill(bool) + { + m_error = 0; + emitResult(); + } + + void WaitJob::timerDone() + { + // set the error to null and emit the result + m_error = 0; + emitResult(); + } + + void WaitJob::addExitOperation(kt::ExitOperation* op) + { + exit_ops.append(op); + connect(op,SIGNAL(operationFinished( kt::ExitOperation* )), + this,SLOT(operationFinished( kt::ExitOperation* ))); + } + + void WaitJob::operationFinished(kt::ExitOperation* op) + { + if (exit_ops.count() > 0) + { + exit_ops.remove(op); + if (op->deleteAllowed()) + op->deleteLater(); + + if (exit_ops.count() == 0) + timerDone(); + } + } + + void WaitJob::execute(WaitJob* job) + { + KIO::NetAccess::synchronousRun(job,0); + } + + void SynchronousWait(Uint32 millis) + { + Out() << "SynchronousWait" << endl; + WaitJob* j = new WaitJob(millis); + KIO::NetAccess::synchronousRun(j,0); + } + +} + +#include "waitjob.moc" + diff --git a/libktorrent/util/waitjob.h b/libktorrent/util/waitjob.h new file mode 100644 index 0000000..a85ba63 --- /dev/null +++ b/libktorrent/util/waitjob.h @@ -0,0 +1,78 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * joris.guisson@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. * + ***************************************************************************/ +#ifndef BTWAITJOB_H +#define BTWAITJOB_H + +#include <qtimer.h> +#include <kio/job.h> +#include <qvaluelist.h> +#include <interfaces/exitoperation.h> +#include "constants.h" + +namespace bt +{ + + /** + * @author Joris Guisson <joris.guisson@gmail.com> + * + * Job to wait for a certain amount of time or until one or more ExitOperation's have + * finished. + */ + class WaitJob : public KIO::Job + { + Q_OBJECT + public: + WaitJob(Uint32 millis); + virtual ~WaitJob(); + + virtual void kill(bool quietly=true); + + /** + * Add an ExitOperation; + * @param op The operation + */ + void addExitOperation(kt::ExitOperation* op); + + + /** + * Execute a WaitJob + * @param job The Job + */ + static void execute(WaitJob* job); + + /// Are there any ExitOperation's we need to wait for + bool needToWait() const {return exit_ops.count() > 0;} + + private slots: + void timerDone(); + void operationFinished(kt::ExitOperation* op); + + private: + QTimer timer; + QValueList<kt::ExitOperation*> exit_ops; + }; + + void SynchronousWait(Uint32 millis); + + + +} + +#endif |