From c2244f746e83a9634c725350e004163a1168d892 Mon Sep 17 00:00:00 2001 From: Christophe Dumez Date: Fri, 27 Mar 2009 22:11:41 +0000 Subject: [PATCH] - Torrent downloading is now handled by search engine plugins to allow for more flexibility --- src/GUI.cpp | 16 +++++- src/GUI.h | 3 +- src/SearchTab.h | 1 + src/bittorrent.cpp | 1 + src/bittorrent.h | 2 +- src/search.qrc | 1 + src/searchEngine.cpp | 54 +++++++++++++++++-- src/searchEngine.h | 4 ++ src/search_engine/engines/btjunkie.py | 9 ++-- src/search_engine/engines/isohunt.py | 7 ++- src/search_engine/engines/mininova.py | 7 ++- src/search_engine/engines/piratebay.py | 7 ++- src/search_engine/engines/torrentreactor.py | 7 ++- src/search_engine/engines/versions.txt | 10 ++-- src/search_engine/helpers.py | 16 ++++++ src/search_engine/nova2.py | 2 +- src/search_engine/nova2dl.py | 60 +++++++++++++++++++++ 17 files changed, 182 insertions(+), 25 deletions(-) create mode 100755 src/search_engine/nova2dl.py diff --git a/src/GUI.cpp b/src/GUI.cpp index e67e4a3ce..7fa86a8e8 100644 --- a/src/GUI.cpp +++ b/src/GUI.cpp @@ -139,7 +139,7 @@ GUI::GUI(QWidget *parent, QStringList torrentCmdLine) : QMainWindow(parent), dis downloadingTorrentTab = new DownloadingTorrents(this, BTSession); tabs->addTab(downloadingTorrentTab, tr("Downloads") + QString::fromUtf8(" (0/0)")); tabs->setTabIcon(0, QIcon(QString::fromUtf8(":/Icons/skin/downloading.png"))); - vboxLayout->addWidget(tabs); + propertiesSplitter->insertWidget(0, tabs); connect(downloadingTorrentTab, SIGNAL(unfinishedTorrentsNumberChanged(unsigned int)), this, SLOT(updateUnfinishedTorrentNumber(unsigned int))); connect(downloadingTorrentTab, SIGNAL(torrentDoubleClicked(QString, bool)), this, SLOT(torrentDoubleClicked(QString, bool))); // Finished torrents tab @@ -237,6 +237,10 @@ GUI::GUI(QWidget *parent, QStringList torrentCmdLine) : QMainWindow(parent), dis QMainWindow::statusBar()->addPermanentWidget(upSpeedLbl); QMainWindow::statusBar()->addPermanentWidget(statusSep4); QMainWindow::statusBar()->addPermanentWidget(ratioLbl); + // Properties code + propertiesSplitter->handle(1)->setVisible(false); + stackedProperties->setVisible(false); + // Display window if necessary if(!settings.value(QString::fromUtf8("Preferences/General/StartMinimized"), false).toBool()) { show(); } @@ -294,6 +298,16 @@ GUI::~GUI() { qDebug("5"); } +void GUI::on_prop_infos_button_clicked() { + if(stackedProperties->isVisible()) { + propertiesSplitter->handle(1)->setVisible(false); + stackedProperties->setVisible(false); + } else { + stackedProperties->setVisible(true); + propertiesSplitter->handle(1)->setVisible(true); + } +} + void GUI::displayRSSTab(bool enable) { if(enable) { // RSS tab diff --git a/src/GUI.h b/src/GUI.h index 23395396f..a5da22a01 100644 --- a/src/GUI.h +++ b/src/GUI.h @@ -173,7 +173,8 @@ class GUI : public QMainWindow, private Ui::MainWindow{ void OptionsSaved(bool deleteOptions); // HTTP slots void on_actionDownload_from_URL_triggered(); - + // Properties + void on_prop_infos_button_clicked(); public slots: void trackerAuthenticationRequired(QTorrentHandle& h); diff --git a/src/SearchTab.h b/src/SearchTab.h index 3cde00245..629e5581c 100644 --- a/src/SearchTab.h +++ b/src/SearchTab.h @@ -24,6 +24,7 @@ #include "ui_search.h" +#define ENGINE_URL_COLUMN 4 #define URL_COLUMN 5 class SearchListDelegate; diff --git a/src/bittorrent.cpp b/src/bittorrent.cpp index 248207973..20b38f2a8 100644 --- a/src/bittorrent.cpp +++ b/src/bittorrent.cpp @@ -910,6 +910,7 @@ void bittorrent::setDefaultTempPath(QString temppath) { for(torrentIT = torrents.begin(); torrentIT != torrents.end(); torrentIT++) { QTorrentHandle h = QTorrentHandle(*torrentIT); if(!h.is_valid()) continue; + std::cout << "Moving storage for " << h.hash().toUtf8().data() << ", from " << h.save_path().toUtf8().data() << " to " << getSavePath(h.hash()).toUtf8().data() << std::endl; h.move_storage(getSavePath(h.hash())); } } else { diff --git a/src/bittorrent.h b/src/bittorrent.h index c1ce8fc74..fecdfdbef 100644 --- a/src/bittorrent.h +++ b/src/bittorrent.h @@ -154,11 +154,11 @@ class bittorrent : public QObject { bool enableDHT(bool b); void addConsoleMessage(QString msg, QColor color=QApplication::palette().color(QPalette::WindowText)); void addPeerBanMessage(QString msg, bool from_ipfilter); + void processDownloadedFile(QString, QString); protected slots: void scanDirectory(QString); void readAlerts(); - void processDownloadedFile(QString, QString); bool loadTrackerFile(QString hash); void saveTrackerFile(QString hash); void deleteBigRatios(); diff --git a/src/search.qrc b/src/search.qrc index 735122372..b7c46c86e 100644 --- a/src/search.qrc +++ b/src/search.qrc @@ -1,6 +1,7 @@ search_engine/nova2.py + search_engine/nova2dl.py search_engine/novaprinter.py search_engine/helpers.py search_engine/engines/btjunkie.png diff --git a/src/searchEngine.cpp b/src/searchEngine.cpp index b770efd64..dfdf6a914 100644 --- a/src/searchEngine.cpp +++ b/src/searchEngine.cpp @@ -78,6 +78,11 @@ SearchEngine::~SearchEngine(){ saveSearchHistory(); searchProcess->kill(); searchProcess->waitForFinished(); + foreach(QProcess *downloader, downloaders) { + downloader->kill(); + downloader->waitForFinished(); + delete downloader; + } delete searchTimeout; delete searchProcess; delete searchCompleter; @@ -225,6 +230,18 @@ void SearchEngine::saveResultsColumnsWidth() { } } +void SearchEngine::downloadTorrent(QString engine_url, QString torrent_url) { + QProcess *downloadProcess = new QProcess(this); + connect(downloadProcess, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(downloadFinished(int,QProcess::ExitStatus))); + downloaders << downloadProcess; + QStringList params; + params << misc::qBittorrentPath()+"search_engine"+QDir::separator()+"nova2dl.py"; + params << engine_url; + params << torrent_url; + // Launch search + downloadProcess->start("python", params, QIODevice::ReadOnly); +} + void SearchEngine::searchStarted(){ // Update SearchEngine widgets search_status->setText(tr("Searching...")); @@ -242,9 +259,10 @@ void SearchEngine::downloadSelectedItem(const QModelIndex& index){ int row = index.row(); // Get Item url QStandardItemModel *model = all_tab.at(tabWidget->currentIndex())->getCurrentSearchListModel(); - QString url = model->data(model->index(index.row(), URL_COLUMN)).toString(); + QString engine_url = model->data(model->index(index.row(), ENGINE_URL_COLUMN)).toString(); + QString torrent_url = model->data(model->index(index.row(), URL_COLUMN)).toString(); // Download from url - BTSession->downloadFromUrl(url); + downloadTorrent(engine_url, torrent_url); // Set item color to RED all_tab.at(tabWidget->currentIndex())->setRowColor(row, "red"); } @@ -267,6 +285,22 @@ void SearchEngine::readSearchOutput(){ currentSearchTab->getCurrentLabel()->setText(tr("Results")+QString::fromUtf8(" (")+misc::toQString(nb_search_results)+QString::fromUtf8("):")); } +void SearchEngine::downloadFinished(int exitcode, QProcess::ExitStatus) { + QProcess *downloadProcess = (QProcess*)sender(); + if(exitcode == 0) { + QString line = QString::fromUtf8(downloadProcess->readAllStandardOutput()).trimmed(); + QStringList parts = line.split(' '); + if(parts.length() == 2) { + QString path = parts[0]; + QString url = parts[1]; + BTSession->processDownloadedFile(url, path); + } + } + qDebug("Deleting downloadProcess"); + downloaders.removeOne(downloadProcess); + delete downloadProcess; +} + // Update nova.py search plugin if necessary void SearchEngine::updateNova() { qDebug("Updating nova"); @@ -294,6 +328,14 @@ void SearchEngine::updateNova() { // Set permissions QFile::Permissions perm=QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner | QFile::ReadUser | QFile::WriteUser | QFile::ExeUser | QFile::ReadGroup | QFile::ReadGroup; QFile(misc::qBittorrentPath()+"search_engine"+QDir::separator()+"nova2.py").setPermissions(perm); + filePath = misc::qBittorrentPath()+"search_engine"+QDir::separator()+"nova2dl.py"; + if(misc::getPluginVersion(":/search_engine/nova2dl.py") > misc::getPluginVersion(filePath)) { + if(QFile::exists(filePath)){ + QFile::remove(filePath); + } + QFile::copy(":/search_engine/nova2dl.py", filePath); + } + QFile(misc::qBittorrentPath()+"search_engine"+QDir::separator()+"nova2dl.py").setPermissions(perm); filePath = misc::qBittorrentPath()+"search_engine"+QDir::separator()+"novaprinter.py"; if(misc::getPluginVersion(":/search_engine/novaprinter.py") > misc::getPluginVersion(filePath)) { if(QFile::exists(filePath)){ @@ -301,7 +343,7 @@ void SearchEngine::updateNova() { } QFile::copy(":/search_engine/novaprinter.py", filePath); } - QFile(misc::qBittorrentPath()+"search_engine"+QDir::separator()+"helpers.py").setPermissions(perm); + QFile(misc::qBittorrentPath()+"search_engine"+QDir::separator()+"novaprinter.py").setPermissions(perm); filePath = misc::qBittorrentPath()+"search_engine"+QDir::separator()+"helpers.py"; if(misc::getPluginVersion(":/search_engine/helpers.py") > misc::getPluginVersion(filePath)) { if(QFile::exists(filePath)){ @@ -309,6 +351,7 @@ void SearchEngine::updateNova() { } QFile::copy(":/search_engine/helpers.py", filePath); } + QFile(misc::qBittorrentPath()+"search_engine"+QDir::separator()+"helpers.py").setPermissions(perm); QString destDir = misc::qBittorrentPath()+"search_engine"+QDir::separator()+"engines"+QDir::separator(); QDir shipped_subDir(":/search_engine/engines/"); QStringList files = shipped_subDir.entryList(); @@ -436,8 +479,9 @@ void SearchEngine::on_download_button_clicked(){ if(index.column() == NAME){ // Get Item url QStandardItemModel *model = all_tab.at(tabWidget->currentIndex())->getCurrentSearchListModel(); - QString url = model->data(model->index(index.row(), URL_COLUMN)).toString(); - BTSession->downloadFromUrl(url); + QString torrent_url = model->data(model->index(index.row(), URL_COLUMN)).toString(); + QString engine_url = model->data(model->index(index.row(), ENGINE_URL_COLUMN)).toString(); + downloadTorrent(engine_url, torrent_url); all_tab.at(tabWidget->currentIndex())->setRowColor(index.row(), "red"); } } diff --git a/src/searchEngine.h b/src/searchEngine.h index ec2ae7b82..4b3b2e648 100644 --- a/src/searchEngine.h +++ b/src/searchEngine.h @@ -26,6 +26,7 @@ #include #include +#include #include "ui_search.h" #include "engineSelectDlg.h" #include "SearchTab.h" @@ -42,6 +43,7 @@ class SearchEngine : public QWidget, public Ui::search_engine{ private: // Search related QProcess *searchProcess; + QList downloaders; bool search_stopped; bool no_search_results; QByteArray search_result_line_truncated; @@ -81,6 +83,8 @@ class SearchEngine : public QWidget, public Ui::search_engine{ void on_clearPatternButton_clicked(); void propagateSectionResized(int index, int oldsize , int newsize); void saveResultsColumnsWidth(); + void downloadFinished(int exitcode, QProcess::ExitStatus); + void downloadTorrent(QString engine_url, QString torrent_url); }; #endif diff --git a/src/search_engine/engines/btjunkie.py b/src/search_engine/engines/btjunkie.py index 57d663509..ffa820771 100644 --- a/src/search_engine/engines/btjunkie.py +++ b/src/search_engine/engines/btjunkie.py @@ -1,4 +1,4 @@ -#VERSION: 2.1 +#VERSION: 2.11 #AUTHORS: Christophe Dumez (chris@qbittorrent.org) # Redistribution and use in source and binary forms, with or without @@ -27,7 +27,7 @@ from novaprinter import prettyPrinter -from helpers import retrieve_url +from helpers import retrieve_url, download_file import sgmllib import re @@ -38,7 +38,10 @@ class btjunkie(object): def __init__(self): self.results = [] self.parser = self.SimpleSGMLParser(self.results, self.url) - + + def download_torrent(self, info): + print download_file(info) + class SimpleSGMLParser(sgmllib.SGMLParser): def __init__(self, results, url, *args): sgmllib.SGMLParser.__init__(self) diff --git a/src/search_engine/engines/isohunt.py b/src/search_engine/engines/isohunt.py index 90da20406..e8edc2db8 100644 --- a/src/search_engine/engines/isohunt.py +++ b/src/search_engine/engines/isohunt.py @@ -1,4 +1,4 @@ -#VERSION: 1.2 +#VERSION: 1.21 #AUTHORS: Christophe Dumez (chris@qbittorrent.org) # Redistribution and use in source and binary forms, with or without @@ -27,12 +27,15 @@ from novaprinter import prettyPrinter import re -from helpers import retrieve_url +from helpers import retrieve_url, download_file class isohunt(object): url = 'http://isohunt.com' name = 'isoHunt' + def download_torrent(self, info): + print download_file(info) + def search(self, what): i = 1 while True and i<11: diff --git a/src/search_engine/engines/mininova.py b/src/search_engine/engines/mininova.py index 7f19b559a..c519149af 100644 --- a/src/search_engine/engines/mininova.py +++ b/src/search_engine/engines/mininova.py @@ -1,4 +1,4 @@ -#VERSION: 1.21 +#VERSION: 1.22 #AUTHORS: Fabien Devaux (fab@gnux.info) # Redistribution and use in source and binary forms, with or without @@ -26,7 +26,7 @@ # POSSIBILITY OF SUCH DAMAGE. from novaprinter import prettyPrinter -from helpers import retrieve_url +from helpers import retrieve_url, download_file from xml.dom import minidom import re @@ -34,6 +34,9 @@ class mininova(object): url = 'http://www.mininova.org' name = 'Mininova' table_items = 'added cat name size seeds leech'.split() + + def download_torrent(self, info): + print download_file(info) def search(self, what): diff --git a/src/search_engine/engines/piratebay.py b/src/search_engine/engines/piratebay.py index a87f46ea8..baa47a02c 100644 --- a/src/search_engine/engines/piratebay.py +++ b/src/search_engine/engines/piratebay.py @@ -1,4 +1,4 @@ -#VERSION: 1.1 +#VERSION: 1.11 #AUTHORS: Fabien Devaux (fab@gnux.info) # Redistribution and use in source and binary forms, with or without @@ -27,7 +27,7 @@ from novaprinter import prettyPrinter import sgmllib -from helpers import retrieve_url +from helpers import retrieve_url, download_file class piratebay(object): url = 'http://thepiratebay.org' @@ -36,6 +36,9 @@ class piratebay(object): def __init__(self): self.results = [] self.parser = self.SimpleSGMLParser(self.results, self.url) + + def download_torrent(self, info): + print download_file(info) class SimpleSGMLParser(sgmllib.SGMLParser): def __init__(self, results, url, *args): diff --git a/src/search_engine/engines/torrentreactor.py b/src/search_engine/engines/torrentreactor.py index 9593d3ecf..c6d01cfec 100644 --- a/src/search_engine/engines/torrentreactor.py +++ b/src/search_engine/engines/torrentreactor.py @@ -1,4 +1,4 @@ -#VERSION: 1.1 +#VERSION: 1.11 #AUTHORS: Gekko Dam Beer (gekko04@users.sourceforge.net) # Redistribution and use in source and binary forms, with or without @@ -27,12 +27,15 @@ from novaprinter import prettyPrinter import sgmllib -from helpers import retrieve_url +from helpers import retrieve_url, download_file class torrentreactor(object): url = 'http://www.torrentreactor.net' name = 'TorrentReactor.Net' + def download_torrent(self, info): + print download_file(info) + class SimpleSGMLParser(sgmllib.SGMLParser): def __init__(self, results, url, *args): sgmllib.SGMLParser.__init__(self) diff --git a/src/search_engine/engines/versions.txt b/src/search_engine/engines/versions.txt index 4b6b8d8c3..e189f2413 100644 --- a/src/search_engine/engines/versions.txt +++ b/src/search_engine/engines/versions.txt @@ -1,5 +1,5 @@ -isohunt: 1.2 -torrentreactor: 1.1 -btjunkie: 2.1 -mininova: 1.21 -piratebay: 1.1 +isohunt: 1.21 +torrentreactor: 1.11 +btjunkie: 2.11 +mininova: 1.22 +piratebay: 1.11 diff --git a/src/search_engine/helpers.py b/src/search_engine/helpers.py index b64d4d697..1a01cf522 100644 --- a/src/search_engine/helpers.py +++ b/src/search_engine/helpers.py @@ -26,6 +26,8 @@ import re, htmlentitydefs import urllib2 +import tempfile +import os def htmlentitydecode(s): # First convert alpha entities (such as é) @@ -57,3 +59,17 @@ def retrieve_url(url): dat = dat.decode(charset, 'replace') dat = htmlentitydecode(dat) return dat.encode('utf-8', 'replace') + +def download_file(url): + """ Download file at url and write it to a file, return the path to the file and the url """ + file, path = tempfile.mkstemp() + file = os.fdopen(file, "wb") + # Download url + req = urllib2.Request(url) + response = urllib2.urlopen(url) + dat = response.read() + # Write it to a file + file.write(dat) + file.close() + # return file path + return path+" "+url diff --git a/src/search_engine/nova2.py b/src/search_engine/nova2.py index ad74f5ed2..401b1f0d6 100755 --- a/src/search_engine/nova2.py +++ b/src/search_engine/nova2.py @@ -75,7 +75,7 @@ class EngineLauncher(threading.Thread): if __name__ == '__main__': if len(sys.argv) < 2: - raise SystemExit('./nova.py [all|engine1[,engine2]*] \navailable engines: %s'% + raise SystemExit('./nova2.py [all|engine1[,engine2]*] \navailable engines: %s'% (','.join(supported_engines))) if len(sys.argv) == 2: diff --git a/src/search_engine/nova2dl.py b/src/search_engine/nova2dl.py new file mode 100755 index 000000000..4f505b2dd --- /dev/null +++ b/src/search_engine/nova2dl.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of the author nor the names of its contributors may be +# used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +#VERSION: 1.00 + +# Author: +# Christophe DUMEZ (chris@qbittorrent.org) + +import sys +import os +import glob + +supported_engines = dict() + +engines = glob.glob(os.path.join(os.path.dirname(__file__), 'engines','*.py')) +for engine in engines: + e = engine.split(os.sep)[-1][:-3] + if len(e.strip()) == 0: continue + if e.startswith('_'): continue + try: + exec "from engines.%s import %s"%(e,e) + exec "engine_url = %s.url"%e + supported_engines[engine_url] = e + except: + pass + +if __name__ == '__main__': + if len(sys.argv) < 3: + raise SystemExit('./nova2dl.py engine_url download_parameter') + engine_url = sys.argv[1].strip() + download_param = sys.argv[2].strip() + if engine_url not in supported_engines.keys(): + raise SystemExit('./nova2dl.py: this engine_url was not recognized') + exec "engine = %s()"%supported_engines[engine_url] + engine.download_torrent(download_param) + sys.exit(0) \ No newline at end of file