From 11a146a796d909661a8685c5e9392f9c3b13a5b1 Mon Sep 17 00:00:00 2001 From: Christophe Dumez Date: Mon, 5 Mar 2007 13:55:23 +0000 Subject: [PATCH] - Updated TODO / Changelog - Added a signal to the deleteThead for future use - Started coding of a bittorrent class to split GUI/BT (still unused) --- Changelog | 1 - TODO | 7 +- src/bittorrent.cpp | 452 +++++++++++++++++++++++++++++++++++++++++++++ src/bittorrent.h | 98 ++++++++++ src/deleteThread.h | 5 + 5 files changed, 559 insertions(+), 4 deletions(-) create mode 100644 src/bittorrent.cpp create mode 100644 src/bittorrent.h diff --git a/Changelog b/Changelog index 2b7723b99..289a678b5 100644 --- a/Changelog +++ b/Changelog @@ -5,7 +5,6 @@ - FEATURE: Support uTorrent Peer Exchange (PeX - exchanges peers between clients) - FEATURE: Added a menu action to visit qBittorrent website - FEATURE: Added a menu action to report a bug in qBittorrent - - FEATURE: Use hashtables for faster lookup - FEATURE: Improved the way parameters are passed between qBT instances (socket) - FEATURE: User is warned when hard drive becomes full and downloads are paused - FEATURE: Number of complete/incomplete sources are now displayed in download list for each torrent diff --git a/TODO b/TODO index 4817879ca..f0742f034 100644 --- a/TODO +++ b/TODO @@ -7,8 +7,8 @@ // Intermediate - Port on MacOS, Windows (and create an installer for Windows) - Progressing -- Option to shutdown computer when downloads are finished - Optimize code to use less memory/cpu +- Should create options dialog only when needed to save up some memory - Add some transparency (menus,...) - Add upnp port forwarding support @@ -31,14 +31,15 @@ - Web interface? - Use downloader class to download search plugin updates - Allow to set upload limit for each torrent +- Option to shutdown computer when downloads are finished - Add a torrent scheduler // in v1.0.0 (partial) or maybe v0.9.0 depending on next libtorrent release date -- Should create options dialog only when needed to save up some memory - Download from RSS feeds - Move finished torrent to another tab and keep on seeding them even after restart - Allow to edit the trackers for a torrent -- UPnP support // In v0.9.0 +- Splitting torrent part from GUI (& remove handles hashtable to save up some memory) +- Create options object only when necessary to save up some memory - Wait for libtorrent v0.12 official release diff --git a/src/bittorrent.cpp b/src/bittorrent.cpp new file mode 100644 index 000000000..0c226c97e --- /dev/null +++ b/src/bittorrent.cpp @@ -0,0 +1,452 @@ +/* + * Bittorrent Client using Qt4 and libtorrent. + * Copyright (C) 2006 Christophe Dumez + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Contact : chris@qbittorrent.org + */ +#include "bittorrent.h" + +#include + +// Main constructor +bittorrent::bittorrent(){ + // Creating bittorrent session + s = new session(fingerprint("qB", VERSION_MAJOR, VERSION_MINOR, VERSION_BUGFIX, 0)); + // Set severity level of libtorrent session + s->set_severity_level(alert::info); + // DHT (Trackerless), disabled until told otherwise + DHTEnabled = false; + // directory scanning, disabled until told otherwise + scanningEnabled = false; + // Enabling metadata plugin + s->add_extension(&create_metadata_plugin); +} + +// Main destructor +bittorrent::~bittorrent(){ + disableDirectoryScanning(); + delete s; +} + +// Return the torrent handle, given its hash +torrent_handle& bittorrent::getTorrentHandle(const QString& hash) const{ + return s->find_torrent(sha_hash(hash.toUtf8())); +} + +// Delete a torrent from the session, given its hash +// permanent = true means that the torrent will be removed from the hard-drive too +void bittorrent::deleteTorrent(const QString& hash, bool permanent){ + torrent_handle& h = s->find_torrent(sha_hash(hash.toUtf8())); + // Remove it from session + s->remove_torrent(h); + // Remove it from torrent backup directory + torrentBackup.remove(fileHash+".torrent"); + torrentBackup.remove(fileHash+".fastresume"); + torrentBackup.remove(fileHash+".paused"); + torrentBackup.remove(fileHash+".incremental"); + torrentBackup.remove(fileHash+".pieces"); + torrentBackup.remove(fileHash+".savepath"); + if(permanent){ + // Remove from Hard drive + qDebug("Removing this on hard drive: %s", qPrintable(savePath+QDir::separator()+fileName)); + // Deleting in a thread to avoid GUI freeze + deleteThread *deleter = new deleteThread(savePath+QDir::separator()+fileName); + connect(deleter, SIGNAL(deletionFinished(deleteThread*)), this, SLOT(cleanDeleter(deleteThread*))) + } +} + +// slot to destroy a deleteThread once it finished deletion +void bittorrent::cleanDeleter(deleteThread* deleter){ + qDebug("Deleting deleteThread because it finished deletion"); + delete deleter; +} + +// Pause a running torrent +void bittorrent::pauseTorrent(const QString& hash){ + torrent_handle& h = s->find_torrent(sha_hash(hash.toUtf8())); + if(h.is_valid() && !h.is_paused()){ + h.pause(); + } +} + +// Resume a torrent in paused state +void bittorrent::resumeTorrent(const QString& hash){ + torrent_handle& h = s->find_torrent(sha_hash(hash.toUtf8())); + if(h.is_valid() && h.is_paused()){ + h.resume(); + } +} + +// Add a torrent to the bittorrent session +void bittorrent::addTorrent(const QString& path, bool fromScanDir = false, const QString& from_url = QString()){ + torrent_handle h; + entry resume_data; + bool fastResume=false; + QDir torrentBackup(misc::qBittorrentPath() + "BT_backup"); + QString file, dest_file, scan_dir; + + // Checking if BT_backup Dir exists + // create it if it is not + if(! torrentBackup.exists()){ + if(! torrentBackup.mkpath(torrentBackup.path())){ + std::cerr << "Couldn't create the directory: '" << torrentBackup.path().toUtf8() << "'\n"; + exit 1; + } + } + // Processing torrents + file = path.trimmed().replace("file://", ""); + if(file.isEmpty()){ + return; + } + qDebug("Adding %s to download list", (const char*)file.toUtf8()); + std::ifstream in((const char*)file.toUtf8(), std::ios_base::binary); + in.unsetf(std::ios_base::skipws); + try{ + // Decode torrent file + entry e = bdecode(std::istream_iterator(in), std::istream_iterator()); + // Getting torrent file informations + torrent_info t(e); + QString hash = QString(misc::toString(t.info_hash()).c_str()); + if(s->find_torrent(t.info_hash()).is_valid()){ + // Update info Bar + if(!fromScanDir){ + if(!from_url.isNull()){ + emit duplicateTorrent(from_url); + }else{ + emit duplicateTorrent(file); + } + }else{ + // Delete torrent from scan dir + QFile::remove(file); + } + return; + } + // TODO: Remove this in a few releases (just for backward compatibility) + if(torrentBackup.exists(QString(t.name().c_str())+".torrent")){ + QFile::rename(torrentBackup.path()+QDir::separator()+QString(t.name().c_str())+".torrent", torrentBackup.path()+QDir::separator()+hash+".torrent"); + QFile::rename(torrentBackup.path()+QDir::separator()+QString(t.name().c_str())+".fastresume", torrentBackup.path()+QDir::separator()+hash+".fastresume"); + QFile::rename(torrentBackup.path()+QDir::separator()+QString(t.name().c_str())+".pieces", torrentBackup.path()+QDir::separator()+hash+".pieces"); + QFile::rename(torrentBackup.path()+QDir::separator()+QString(t.name().c_str())+".savepath", torrentBackup.path()+QDir::separator()+hash+".savepath"); + QFile::rename(torrentBackup.path()+QDir::separator()+QString(t.name().c_str())+".paused", torrentBackup.path()+QDir::separator()+hash+".paused"); + QFile::rename(torrentBackup.path()+QDir::separator()+QString(t.name().c_str())+".incremental", torrentBackup.path()+QDir::separator()+hash+".incremental"); + file = torrentBackup.path() + QDir::separator() + hash + ".torrent"; + } + //Getting fast resume data if existing + if(torrentBackup.exists(hash+".fastresume")){ + try{ + std::stringstream strStream; + strStream << hash.toStdString() << ".fastresume"; + boost::filesystem::ifstream resume_file(fs::path((const char*)torrentBackup.path().toUtf8()) / strStream.str(), std::ios_base::binary); + resume_file.unsetf(std::ios_base::skipws); + resume_data = bdecode(std::istream_iterator(resume_file), std::istream_iterator()); + fastResume=true; + }catch (invalid_encoding&) {} + catch (fs::filesystem_error&) {} + } + QString savePath = getSavePath(hash); + int row = DLListModel->rowCount(); + // Adding files to bittorrent session + if(hasFilteredFiles(hash)){ + h = s->add_torrent(t, fs::path((const char*)savePath.toUtf8()), resume_data, false); + qDebug("Full allocation mode"); + }else{ + h = s->add_torrent(t, fs::path((const char*)savePath.toUtf8()), resume_data, true); + qDebug("Compact allocation mode"); + } + if(!h.is_valid()){ + // No need to keep on, it failed. + return; + } + // Is this really useful and appropriate ? + //h.set_max_connections(60); + h.set_max_uploads(-1); + qDebug("Torrent hash is " + hash.toUtf8()); + // Load filtered files + loadFilteredFiles(h); + torrent_status torrentStatus = h.status(); + + QString newFile = torrentBackup.path() + QDir::separator() + hash + ".torrent"; + if(file != newFile){ + // Delete file from torrentBackup directory in case it exists because + // QFile::copy() do not overwrite + QFile::remove(newFile); + // Copy it to torrentBackup directory + QFile::copy(file, newFile); + } + //qDebug("Copied to torrent backup directory"); + if(fromScanDir){ + scan_dir = options->getScanDir(); + if(scan_dir.at(scan_dir.length()-1) != QDir::separator()){ + scan_dir += QDir::separator(); + } + } + // Pause torrent if it was paused last time + if(QFile::exists(misc::qBittorrentPath()+"BT_backup"+QDir::separator()+hash+".paused")){ + h.pause(); + } + // Incremental download + if(QFile::exists(misc::qBittorrentPath()+"BT_backup"+QDir::separator()+hash+".incremental")){ + qDebug("Incremental download enabled for %s", t.name().c_str()); + h.set_sequenced_download_threshold(15); + } + // If download from url + if(!from_url.isNull()){ + // remove temporary file + QFile::remove(file); + } + // Delete from scan dir to avoid trying to download it again + if(fromScanDir){ + QFile::remove(file); + } + // Send torrent addition signal + if(!from_url.isNull()){ + emit addedTorrent(from_url, h, fastResume); + }else{ + emit addedTorrent(file, h, fastResume); + } + }catch (invalid_encoding& e){ // Raised by bdecode() + std::cerr << "Could not decode file, reason: " << e.what() << '\n'; + // Display warning to tell user we can't decode the torrent file + if(!from_url.isNull()){ + emit invalidTorrent(from_url); + }else{ + emit invalidTorrent(file); + } + if(fromScanDir){ + // Remove .corrupt file in case it already exists + QFile::remove(file+".corrupt"); + //Rename file extension so that it won't display error message more than once + QFile::rename(file,file+".corrupt"); + } + } + catch (invalid_torrent_file&){ // Raised by torrent_info constructor + // Display warning to tell user we can't decode the torrent file + if(!from_url.isNull()){ + emit invalidTorrent(from_url); + }else{ + emit invalidTorrent(file); + } + if(fromScanDir){ + // Remove .corrupt file in case it already exists + QFile::remove(file+".corrupt"); + //Rename file extension so that it won't display error message more than once + QFile::rename(file,file+".corrupt"); + } + } +} + +// Check in .pieces file if the user filtered files +// in this torrent. +bool bittorrent::hasFilteredFiles(const QString& fileHash){ + QFile pieces_file(misc::qBittorrentPath()+"BT_backup"+QDir::separator()+fileHash+".pieces"); + // Read saved file + if(!pieces_file.open(QIODevice::ReadOnly | QIODevice::Text)){ + return false; + } + QByteArray pieces_selection = pieces_file.readAll(); + pieces_file.close(); + QList pieces_selection_list = pieces_selection.split('\n'); + for(int i=0; i 1){ + isFiltered = 0; + } + if(isFiltered){ + return true; + } + } + return false; +} + +// Return DHT state +bool bittorrent::isDHTEnabled() const{ + return DHTEnabled; +} + +// Enable DHT +void bittorrent::enableDHT(){ + if(!DHTEnabled){ + DHTEnabled = true; + s->start_dht(); + qDebug("DHT enabled"); + } +} + +// Disable DHT +void bittorrent::disableDHT(){ + if(DHTEnabled){ + DHTEnabled = false; + s->stop_dht(); + qDebug("DHT disabled"); + } +} + +// Read filtered pieces from .pieces file +// and ask torrent_handle to filter them +void bittorrent::loadFilteredFiles(torrent_handle &h){ + torrent_info torrentInfo = h.get_torrent_info(); + if(!h.is_valid()){ + return; + } + QString fileHash = QString(misc::toString(torrentInfo.info_hash()).c_str()); + QFile pieces_file(misc::qBittorrentPath()+"BT_backup"+QDir::separator()+fileHash+".pieces"); + // Read saved file + if(!pieces_file.open(QIODevice::ReadOnly | QIODevice::Text)){ + return; + } + QByteArray pieces_selection = pieces_file.readAll(); + pieces_file.close(); + QList pieces_selection_list = pieces_selection.split('\n'); + if(pieces_selection_list.size() != torrentInfo.num_files()+1){ + std::cerr << "Error: Corrupted pieces file\n"; + return; + } + std::vector selectionBitmask; + for(int i=0; i 1){ + isFiltered = 0; + } + selectionBitmask.push_back(isFiltered); + } + h.filter_files(selectionBitmask); +} + +// Save fastresume data for all torrents +// and remove them from the session +void bittorrent::saveFastResumeData(){ + qDebug("Saving fast resume data"); + QString file; + QDir torrentBackup(misc::qBittorrentPath() + "BT_backup"); + // Checking if torrentBackup Dir exists + // create it if it is not + if(! torrentBackup.exists()){ + torrentBackup.mkpath(torrentBackup.path()); + } + // Write fast resume data + std::vector handles = s->get_torrents(); + for(int i=0; i(out), resumeData); + } + } + // Remove torrent + s->remove_torrent(h); + } + qDebug("Fast resume data saved"); +} + +bool bittorrent::isFilePreviewPossible(const QString& hash) const{ + // See if there are supported files in the torrent + torrent_handle &h = s->find_torrent(sha_hash(hash.toUtf8())); + if(!h.is_valid()){ + return; + } + torrent_info torrentInfo = h.get_torrent_info(); + for(int i=0; i= 0){ + return true; + } + } + return false; +} + +// Scan the first level of the directory for torrent files +// and add them to download list +void bittorrent::scanDirectory(){ + QString file; + if(!scan_dir.isNull()){ + QStringList to_add; + QDir dir(scan_dir); + QStringList files = dir.entryList(QDir::Files, QDir::Unsorted); + foreach(file, files){ + QString fullPath = dir.path()+QDir::separator()+file; + if(fullPath.endsWith(".torrent")){ + to_add << fullPath; + } + } + foreach(file, to_add){ + if(options->useAdditionDialog()){ + torrentAdditionDialog *dialog = new torrentAdditionDialog(this); + connect(dialog, SIGNAL(torrentAddition(const QString&, bool, const QString&)), this, SLOT(addTorrent(const QString&, bool, const QString&))); + connect(dialog, SIGNAL(setInfoBarGUI(const QString&, const QString&)), this, SLOT(setInfoBar(const QString&, const QString&))); + dialog->showLoad(file, true); + }else{ + addTorrent(file, true); + } + } + } +} + +// Enable directory scanning +void bittorrent::enableDirectoryScanning(const QString& _scan_dir){ + if(!_scan_dir.isEmpty()){ + scan_dir = _scan_dir + timerScan = new QTimer(this); + connect(timerScan, SIGNAL(timeout()), this, SLOT(scanDirectory())); + } +} + +// Disable directory scanning +void bittorrent::disableDirectoryScanning(){ + if(!scan_dir.isNull()){ + scan_dir = QString::null; + delete timerScan; + } +} + +// Set the ports range in which is chosen the port the bittorrent +// session will listen to +void bittorrent::setListeningPortsRange(std::pair ports){ + s->listen_on(ports); +} + +// Set download rate limit +// -1 to disable +void bittorrent::setDownloadRateLimit(int rate){ + s->set_download_rate_limit(rate); +} + +// Set upload rate limit +// -1 to disable +void bittorrent::setUploadRateLimit(int rate){ + s->set_upload_rate_limit(rate); +} + +// libtorrent allow to adjust ratio for each torrent +// This function will apply to same ratio to all torrents +void bittorrent::setGlobalRatio(float ratio){ + std::vector handles = s->get_torrents(); + for(int i=0; i +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "deleteThread.h" + +#define VERSION "v0.9.0beta3" +#define VERSION_MAJOR 0 +#define VERSION_MINOR 9 +#define VERSION_BUGFIX 0 + +using namespace libtorrent; +namespace fs = boost::filesystem; + +class bittorrent{ + private: + session *s; + QHash trackerErrors; + bool DHTEnabled; + String scan_dir; + QTimer *timerScan; + + // Constructor / Destructor + bittorrent(); + ~bittorrent(); + + public: + torrent_handle& getTorrentHandle(const QString& hash) const; + bool hasFilteredFiles(const QString& fileHash) const; + bool isDHTEnabled() const; + + public slots: + void addTorrent(const QString& path, bool fromScanDir = false, const QString& from_url = QString()); + void deleteTorrent(const QString& hash, bool permanent=false); + void pauseTorrent(const QString& hash); + void resumeTorrent(const QString& hash); + void enableDHT(); + void disableDHT(); + void saveFastResumeData(); + void enableDirectoryScanning(const QString& scan_dir); + void disableDirectoryScanning(); + // Session configuration - Setters + void setListeningPortsRange(std::pair ports); + void setDownloadRateLimit(int rate); + void setUploadRateLimit(int rate); + void setGlobalRatio(float ratio); + + protected slots: + void cleanDeleter(deleteThread* deleter); + void loadFilteredFiles(torrent_handle& h); + void scanDirectory(); + + signals: + void invalidTorrent(const QString& path); + void duplicateTorrent(const QString& path); + void addedTorrent(const QString& path, torrent_handle& h, bool fastResume); + void resumedTorrent(const QString& path); +} + +#endif diff --git a/src/deleteThread.h b/src/deleteThread.h index 646cf8aec..6cdc4221b 100644 --- a/src/deleteThread.h +++ b/src/deleteThread.h @@ -27,6 +27,7 @@ #include "misc.h" class deleteThread : public QThread { + Q_OBJECT private: QString path; @@ -36,9 +37,13 @@ class deleteThread : public QThread { start(); } + signals: + void deletionFinished(deleteThread*) const; + private: void run(){ misc::removePath(path); + emit deletionFinished(this); } };